From 67093743b2e7d2b191565f23de3a7393d30541a1 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Thu, 23 Nov 2023 00:29:08 -0400 Subject: [PATCH] Fix broken tests and add new tests --- .../top_nav/discover_topnav.test.tsx | 108 +++++++++++++- .../components/top_nav/discover_topnav.tsx | 2 +- .../log_explorer_tabs.test.tsx | 135 ++++++++++++++++++ .../log_explorer_tabs/log_explorer_tabs.tsx | 2 + .../apps/observability_log_explorer/app.ts | 2 +- .../common/discover/group1/_url_state.ts | 3 - .../cypress/e2e/navigation.cy.ts | 4 +- .../test_suites/observability/navigation.ts | 14 +- .../observability_log_explorer/app.ts | 35 ++++- 9 files changed, 284 insertions(+), 21 deletions(-) create mode 100644 src/plugins/discover/public/components/log_explorer_tabs/log_explorer_tabs.test.tsx diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.test.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.test.tsx index 548470ae8213493..bdb02bdaf33e099 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.test.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.test.tsx @@ -9,7 +9,7 @@ import React, { ReactElement } from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; -import { DiscoverTopNav, DiscoverTopNavProps } from './discover_topnav'; +import { DiscoverTopNav, DiscoverTopNavProps, ServerlessTopNav } from './discover_topnav'; import { TopNavMenu, TopNavMenuData } from '@kbn/navigation-plugin/public'; import { setHeaderActionMenuMounter } from '../../../../kibana_services'; import { discoverServiceMock as mockDiscoverService } from '../../../../__mocks__/services'; @@ -18,14 +18,14 @@ import { DiscoverMainProvider } from '../../services/discover_state_provider'; import type { SearchBarCustomization, TopNavCustomization } from '../../../../customizations'; import type { DiscoverCustomizationId } from '../../../../customizations/customization_service'; import { useDiscoverCustomization } from '../../../../customizations'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { LogExplorerTabs } from '../../../../components/log_explorer_tabs'; setHeaderActionMenuMounter(jest.fn()); jest.mock('@kbn/kibana-react-plugin/public', () => ({ ...jest.requireActual('@kbn/kibana-react-plugin/public'), - useKibana: () => ({ - services: mockDiscoverService, - }), + useKibana: jest.fn(), })); const MockCustomSearchBar: typeof mockDiscoverService.navigation.ui.AggregateQueryTopNavMenu = @@ -84,6 +84,8 @@ function getProps( }; } +const mockUseKibana = useKibana as jest.Mock; + describe('Discover topnav component', () => { beforeEach(() => { mockTopNavCustomization.defaultMenu = undefined; @@ -105,6 +107,10 @@ describe('Discover topnav component', () => { throw new Error(`Unknown customization id: ${id}`); } }); + + mockUseKibana.mockReturnValue({ + services: mockDiscoverService, + }); }); test('generated config of TopNavMenu config is correct when discover save permissions are assigned', () => { @@ -278,4 +284,98 @@ describe('Discover topnav component', () => { expect(topNav.prop('dataViewPickerComponentProps')).toBeUndefined(); }); }); + + describe('ServerlessTopNav', () => { + it('should not render when serverless plugin is not defined', () => { + const props = getProps(); + const component = mountWithIntl( + + + + ); + expect(component.find(ServerlessTopNav)).toHaveLength(0); + const searchBar = component.find(mockDiscoverService.navigation.ui.AggregateQueryTopNavMenu); + expect(searchBar.prop('badges')).toBeDefined(); + expect(searchBar.prop('config')).toBeDefined(); + expect(searchBar.prop('setMenuMountPoint')).toBeDefined(); + }); + + it('should render when serverless plugin is defined and displayMode is "standalone"', () => { + mockUseKibana.mockReturnValue({ + services: { + ...mockDiscoverService, + serverless: true, + }, + }); + const props = getProps(); + const component = mountWithIntl( + + + + ); + expect(component.find(ServerlessTopNav)).toHaveLength(1); + const searchBar = component.find(mockDiscoverService.navigation.ui.AggregateQueryTopNavMenu); + expect(searchBar.prop('badges')).toBeUndefined(); + expect(searchBar.prop('config')).toBeUndefined(); + expect(searchBar.prop('setMenuMountPoint')).toBeUndefined(); + }); + + it('should not render when serverless plugin is defined and displayMode is not "standalone"', () => { + mockUseKibana.mockReturnValue({ + services: { + ...mockDiscoverService, + serverless: true, + }, + }); + const props = getProps(); + props.stateContainer.customizationContext.displayMode = 'embedded'; + const component = mountWithIntl( + + + + ); + expect(component.find(ServerlessTopNav)).toHaveLength(0); + const searchBar = component.find(mockDiscoverService.navigation.ui.AggregateQueryTopNavMenu); + expect(searchBar.prop('badges')).toBeUndefined(); + expect(searchBar.prop('config')).toBeUndefined(); + expect(searchBar.prop('setMenuMountPoint')).toBeUndefined(); + }); + + describe('LogExplorerTabs', () => { + it('should render when showLogExplorerTabs is true', () => { + mockUseKibana.mockReturnValue({ + services: { + ...mockDiscoverService, + serverless: true, + }, + }); + const props = getProps(); + props.stateContainer.customizationContext.showLogExplorerTabs = true; + const component = mountWithIntl( + + + + ); + expect(component.find(ServerlessTopNav)).toHaveLength(1); + expect(component.find(LogExplorerTabs)).toHaveLength(1); + }); + + it('should not render when showLogExplorerTabs is false', () => { + mockUseKibana.mockReturnValue({ + services: { + ...mockDiscoverService, + serverless: true, + }, + }); + const props = getProps(); + const component = mountWithIntl( + + + + ); + expect(component.find(ServerlessTopNav)).toHaveLength(1); + expect(component.find(LogExplorerTabs)).toHaveLength(0); + }); + }); + }); }); diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx index 333e07f19b42399..88360b1fccacbfa 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -294,7 +294,7 @@ export const DiscoverTopNav = ({ ); }; -const ServerlessTopNav = ({ +export const ServerlessTopNav = ({ customizationContext, topNavMenu, topNavBadges, diff --git a/src/plugins/discover/public/components/log_explorer_tabs/log_explorer_tabs.test.tsx b/src/plugins/discover/public/components/log_explorer_tabs/log_explorer_tabs.test.tsx new file mode 100644 index 000000000000000..336c445b2a61588 --- /dev/null +++ b/src/plugins/discover/public/components/log_explorer_tabs/log_explorer_tabs.test.tsx @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import userEvent from '@testing-library/user-event'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { discoverServiceMock } from '../../__mocks__/services'; +import { LogExplorerTabs, LogExplorerTabsProps } from './log_explorer_tabs'; +import { DISCOVER_APP_LOCATOR } from '../../../common'; +import { ALL_DATASETS_LOCATOR_ID } from '@kbn/deeplinks-observability'; +import { of } from 'rxjs'; +import { omit } from 'lodash'; + +const getRedirectUrl = (id: string, params: Record) => + `${id}-${Object.keys(params) + .sort() + .map((key) => `${key}:${JSON.stringify(params[key])}`) + .join(',')}`; + +const createMockLocator = (id: string) => ({ + navigate: jest.fn(), + getRedirectUrl: jest.fn((params) => getRedirectUrl(id, params)), +}); + +describe('LogExplorerTabs', () => { + const renderTabs = (selectedTab: LogExplorerTabsProps['selectedTab'] = 'discover') => { + const mockDiscoverLocator = createMockLocator(DISCOVER_APP_LOCATOR); + const mockLogExplorerLocator = createMockLocator(ALL_DATASETS_LOCATOR_ID); + const queryState = { + time: { + from: 'now-15m', + to: 'now', + }, + refreshInterval: { + pause: true, + value: 0, + }, + query: { + language: 'kuery', + query: '', + }, + filters: [], + }; + const services = { + ...discoverServiceMock, + data: { + ...discoverServiceMock.data, + query: { + ...discoverServiceMock.data.query, + state$: of({ state: queryState }), + getState: jest.fn(() => queryState), + }, + }, + share: { + ...discoverServiceMock.share, + url: { + ...discoverServiceMock.share?.url, + locators: { + get: jest.fn((id) => { + switch (id) { + case DISCOVER_APP_LOCATOR: + return mockDiscoverLocator; + case ALL_DATASETS_LOCATOR_ID: + return mockLogExplorerLocator; + default: + throw new Error(`Unknown locator id: ${id}`); + } + }), + }, + }, + }, + } as unknown as typeof discoverServiceMock; + const params = { + columns: ['@timestamp'], + sort: [['@timestamp', 'desc']], + dataViewSpec: { + id: 'test', + title: 'test', + timeFieldName: '@timestamp', + }, + }; + const adjustedQuery = { ...omit(queryState, 'time'), timeRange: queryState.time }; + const combinedParams = { ...params, ...adjustedQuery }; + + render(); + + return { + params: combinedParams, + queryState, + mockDiscoverLocator, + mockLogExplorerLocator, + }; + }; + + const getDiscoverTab = () => screen.getByText('Discover').closest('a')!; + const getLogExplorerTab = () => screen.getByText('Logs Explorer').closest('a')!; + + it('should render properly', () => { + const { params, mockDiscoverLocator, mockLogExplorerLocator } = renderTabs(); + expect(getDiscoverTab()).toBeInTheDocument(); + expect(mockDiscoverLocator.getRedirectUrl).toHaveBeenCalledWith(params); + expect(getDiscoverTab()).toHaveAttribute('href', getRedirectUrl(DISCOVER_APP_LOCATOR, params)); + expect(getLogExplorerTab()).toBeInTheDocument(); + expect(mockLogExplorerLocator.getRedirectUrl).toHaveBeenCalledWith(params); + expect(getLogExplorerTab()).toHaveAttribute( + 'href', + getRedirectUrl(ALL_DATASETS_LOCATOR_ID, params) + ); + }); + + it('should render Discover as the selected tab', () => { + const { params, mockDiscoverLocator, mockLogExplorerLocator } = renderTabs(); + expect(getDiscoverTab()).toHaveAttribute('aria-selected', 'true'); + userEvent.click(getDiscoverTab()); + expect(mockDiscoverLocator.navigate).not.toHaveBeenCalled(); + expect(getLogExplorerTab()).toHaveAttribute('aria-selected', 'false'); + userEvent.click(getLogExplorerTab()); + expect(mockLogExplorerLocator.navigate).toHaveBeenCalledWith(params); + }); + + it('should render Log Explorer as the selected tab', () => { + const { params, mockDiscoverLocator, mockLogExplorerLocator } = renderTabs('log-explorer'); + expect(getLogExplorerTab()).toHaveAttribute('aria-selected', 'true'); + userEvent.click(getLogExplorerTab()); + expect(mockLogExplorerLocator.navigate).not.toHaveBeenCalled(); + expect(getDiscoverTab()).toHaveAttribute('aria-selected', 'false'); + userEvent.click(getDiscoverTab()); + expect(mockDiscoverLocator.navigate).toHaveBeenCalledWith(params); + }); +}); diff --git a/src/plugins/discover/public/components/log_explorer_tabs/log_explorer_tabs.tsx b/src/plugins/discover/public/components/log_explorer_tabs/log_explorer_tabs.tsx index 9fa5ec5d70a6abb..a11874a014a2ccf 100644 --- a/src/plugins/discover/public/components/log_explorer_tabs/log_explorer_tabs.tsx +++ b/src/plugins/discover/public/components/log_explorer_tabs/log_explorer_tabs.tsx @@ -63,6 +63,7 @@ export const LogExplorerTabs = ({ services, params, selectedTab }: LogExplorerTa href={discoverUrl} onClick={navigateToDiscover} css={{ '.euiTab__content': { lineHeight: euiTheme.size.xxxl } }} + data-test-subj="discoverTab" > {i18n.translate('discover.logExplorerTabs.discover', { defaultMessage: 'Discover', @@ -73,6 +74,7 @@ export const LogExplorerTabs = ({ services, params, selectedTab }: LogExplorerTa href={logExplorerUrl} onClick={navigateToLogExplorer} css={{ '.euiTab__content': { lineHeight: euiTheme.size.xxxl } }} + data-test-subj="logExplorerTab" > {i18n.translate('discover.logExplorerTabs.logExplorer', { defaultMessage: 'Logs Explorer', diff --git a/x-pack/test/functional/apps/observability_log_explorer/app.ts b/x-pack/test/functional/apps/observability_log_explorer/app.ts index c60bf38205e3a7f..5d6c85e3c75189d 100644 --- a/x-pack/test/functional/apps/observability_log_explorer/app.ts +++ b/x-pack/test/functional/apps/observability_log_explorer/app.ts @@ -20,7 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.navigationalSearch.searchFor('log explorer'); const results = await PageObjects.navigationalSearch.getDisplayedResults(); - expect(results[0].label).to.eql('Log Explorer'); + expect(results[0].label).to.eql('Logs Explorer'); }); it('is shown in the observability side navigation', async () => { diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/group1/_url_state.ts b/x-pack/test_serverless/functional/test_suites/common/discover/group1/_url_state.ts index 13629a6007937fd..c0dc9c0dd89f1eb 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/group1/_url_state.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/group1/_url_state.ts @@ -77,9 +77,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); describe('Side nav', function () { - // Discover does not exist in Serverless O11y side nav (Log Explorer instead) - this.tags('skipSvlOblt'); - it('should sync Lens global state to Discover sidebar link and carry over the state when navigating to Discover', async () => { await PageObjects.common.navigateToApp('discover'); await PageObjects.common.navigateToApp('lens'); diff --git a/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/navigation.cy.ts b/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/navigation.cy.ts index b3d6c225f50c4e3..8453042cc716174 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/navigation.cy.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cypress/e2e/navigation.cy.ts @@ -13,7 +13,7 @@ describe.skip('Serverless', () => { it('contains the side navigation for observabilitity serverless', () => { cy.loginAsElasticUser(); - cy.contains('Log Explorer'); + cy.contains('Logs Explorer'); cy.contains('Dashboards'); cy.contains('Alerts'); cy.contains('AIOps'); @@ -26,7 +26,7 @@ describe.skip('Serverless', () => { it('navigates to discover-dashboard-viz links', () => { cy.loginAsElasticUser(); - cy.contains('Log Explorer').click(); + cy.contains('Logs Explorer').click(); cy.url().should('include', '/app/observability-log-explorer'); cy.contains('Dashboards').click(); diff --git a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts index a295d93e009bc06..892359f901ffe51 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts @@ -41,15 +41,11 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { }); await svlCommonNavigation.sidenav.expectSectionClosed('project_settings_project_nav'); - // navigate to log explorer - await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'observability-log-explorer' }); - await svlCommonNavigation.sidenav.expectLinkActive({ - deepLinkId: 'observability-log-explorer', - }); - await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ - deepLinkId: 'observability-log-explorer', - }); - await expect(await browser.getCurrentUrl()).contain('/app/observability-log-explorer'); + // navigate to discover + await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'discover' }); + await svlCommonNavigation.sidenav.expectLinkActive({ deepLinkId: 'discover' }); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'discover' }); + expect(await browser.getCurrentUrl()).contain('/app/discover'); // check the aiops subsection await svlCommonNavigation.sidenav.openSection('observability_project_nav.aiops'); // open ai ops subsection diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/app.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/app.ts index 271bba6b45035f0..ac94cde7d0d2fd4 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/app.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/app.ts @@ -17,6 +17,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const synthtrace = getService('svlLogsSynthtraceClient'); const dataGrid = getService('dataGrid'); + const browser = getService('browser'); + const testSubjects = getService('testSubjects'); + const kibanaServer = getService('kibanaServer'); describe('Application', () => { before(async () => { @@ -34,11 +37,41 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.svlCommonNavigation.search.searchFor('log explorer'); const results = await PageObjects.svlCommonNavigation.search.getDisplayedResults(); - expect(results[0].label).to.eql('Log Explorer'); + expect(results[0].label).to.eql('Logs Explorer'); await PageObjects.svlCommonNavigation.search.hideSearch(); }); + it('should support navigating between Discover tabs', async () => { + await kibanaServer.savedObjects.create({ + type: 'index-pattern', + id: 'metrics-*', + overwrite: true, + attributes: { + title: 'metrics-*', + }, + }); + await PageObjects.svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'discover' }); + await PageObjects.svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ + deepLinkId: 'discover', + }); + expect(await browser.getCurrentUrl()).contain('/app/discover'); + await testSubjects.click('logExplorerTab'); + await PageObjects.svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ + deepLinkId: 'observability-log-explorer', + }); + expect(await browser.getCurrentUrl()).contain('/app/observability-log-explorer'); + await testSubjects.click('discoverTab'); + await PageObjects.svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ + deepLinkId: 'discover', + }); + expect(await browser.getCurrentUrl()).contain('/app/discover'); + await kibanaServer.savedObjects.delete({ + type: 'index-pattern', + id: 'metrics-*', + }); + }); + it('should load logs', async () => { const from = '2023-08-03T10:24:14.035Z'; const to = '2023-08-03T10:24:14.091Z';