From 1c7ac5d5de0f7f14c0e23bc87bd89119b5a126df Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 6 Sep 2023 12:30:29 +0200 Subject: [PATCH 1/2] [Log Explorer] Add Discover fallback link (#165464) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📓 Summary Closes #165220 This PR introduces a new fallback link from the Log Explorer application to Discover. To correctly retrieve the details required to correctly navigate to Discover with the used data view and filters, the LogExplorer component accepts now a new `state$` behaviour subject as a property that can be used to notify the consumers of any change from the internal state. https://github.com/elastic/kibana/assets/34506779/c8176ef2-7a3b-4c7e-860a-450ba677412a ## 🧪 Test suite ``` ↳ Observability Log Explorer ↳ Header menu ↳ should inject the app header menu on the top navbar ↳ Discover fallback link ↳ should render a button link ↳ should navigate to discover keeping the current columns/filters/query/time/data view ``` --------- Co-authored-by: Marco Antonio Ghiani Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/discover/public/index.ts | 1 + .../plugins/log_explorer/common/constants.ts | 1 + .../common/datasets/models/dataset.ts | 2 + .../components/log_explorer/log_explorer.tsx | 21 +++- .../customizations/log_explorer_profile.tsx | 44 +++++++- x-pack/plugins/log_explorer/public/index.ts | 1 + .../common/constants.ts | 8 ++ .../common/translations.ts | 7 ++ .../observability_log_explorer/kibana.jsonc | 3 +- .../observability_log_explorer.tsx | 68 +++++++----- .../components/log_explorer_top_nav_menu.tsx | 105 ++++++++++++++++++ .../public/plugin.ts | 3 +- .../public/routes/main/main_route.tsx | 25 +++-- .../public/types.ts | 2 + .../public/utils/breadcrumbs.tsx | 10 +- .../observability_log_explorer/tsconfig.json | 2 + .../observability_log_explorer/header_menu.ts | 76 +++++++++++++ .../apps/observability_log_explorer/index.ts | 1 + .../observability_log_explorer.ts | 29 +++++ .../observability_log_explorer/header_menu.ts | 76 +++++++++++++ .../observability_log_explorer/index.ts | 1 + 21 files changed, 432 insertions(+), 54 deletions(-) create mode 100644 x-pack/plugins/observability_log_explorer/common/constants.ts create mode 100644 x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx create mode 100644 x-pack/test/functional/apps/observability_log_explorer/header_menu.ts create mode 100644 x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/header_menu.ts diff --git a/src/plugins/discover/public/index.ts b/src/plugins/discover/public/index.ts index 5af7c2bf1142a..ca3c58a4d2899 100644 --- a/src/plugins/discover/public/index.ts +++ b/src/plugins/discover/public/index.ts @@ -15,6 +15,7 @@ export function plugin(initializerContext: PluginInitializerContext) { } export type { ISearchEmbeddable, SearchInput } from './embeddable'; +export type { DiscoverAppState } from './application/main/services/discover_app_state_container'; export type { DiscoverStateContainer } from './application/main/services/discover_state'; export type { DiscoverContainerProps } from './components/discover_container'; export type { diff --git a/x-pack/plugins/log_explorer/common/constants.ts b/x-pack/plugins/log_explorer/common/constants.ts index 37f56942f332a..fc1c572ebae26 100644 --- a/x-pack/plugins/log_explorer/common/constants.ts +++ b/x-pack/plugins/log_explorer/common/constants.ts @@ -8,4 +8,5 @@ export const LOG_EXPLORER_PROFILE_ID = 'log-explorer'; // Fields constants +export const TIMESTAMP_FIELD = '@timestamp'; export const MESSAGE_FIELD = 'message'; diff --git a/x-pack/plugins/log_explorer/common/datasets/models/dataset.ts b/x-pack/plugins/log_explorer/common/datasets/models/dataset.ts index 68119fb6015b1..974a9fd4ca37f 100644 --- a/x-pack/plugins/log_explorer/common/datasets/models/dataset.ts +++ b/x-pack/plugins/log_explorer/common/datasets/models/dataset.ts @@ -8,6 +8,7 @@ import { IconType } from '@elastic/eui'; import { DataViewSpec } from '@kbn/data-views-plugin/common'; import { IndexPattern } from '@kbn/io-ts-utils'; +import { TIMESTAMP_FIELD } from '../../constants'; import { DatasetId, DatasetType, IntegrationType } from '../types'; type IntegrationBase = Pick; @@ -53,6 +54,7 @@ export class Dataset { return { id: this.id, name: this.getFullTitle(), + timeFieldName: TIMESTAMP_FIELD, title: this.name as string, }; } diff --git a/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx b/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx index fa76cadeb727b..6a945afa19ab2 100644 --- a/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx +++ b/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx @@ -5,22 +5,30 @@ * 2.0. */ +import React, { useMemo } from 'react'; import { ScopedHistory } from '@kbn/core-application-browser'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { DiscoverStart } from '@kbn/discover-plugin/public'; -import React from 'react'; +import { DiscoverAppState, DiscoverStart } from '@kbn/discover-plugin/public'; +import type { BehaviorSubject } from 'rxjs'; import { createLogExplorerProfileCustomizations, CreateLogExplorerProfileCustomizationsDeps, } from '../../customizations/log_explorer_profile'; import { createPropertyGetProxy } from '../../utils/proxies'; +import { LogExplorerProfileContext } from '../../state_machines/log_explorer_profile'; export interface CreateLogExplorerArgs extends CreateLogExplorerProfileCustomizationsDeps { discover: DiscoverStart; } +export interface LogExplorerStateContainer { + appState?: DiscoverAppState; + logExplorerState?: Partial; +} + export interface LogExplorerProps { scopedHistory: ScopedHistory; + state$?: BehaviorSubject; } export const createLogExplorer = ({ @@ -28,13 +36,16 @@ export const createLogExplorer = ({ data, discover: { DiscoverContainer }, }: CreateLogExplorerArgs) => { - const logExplorerCustomizations = [createLogExplorerProfileCustomizations({ core, data })]; - const overrideServices = { data: createDataServiceProxy(data), }; - return ({ scopedHistory }: LogExplorerProps) => { + return ({ scopedHistory, state$ }: LogExplorerProps) => { + const logExplorerCustomizations = useMemo( + () => [createLogExplorerProfileCustomizations({ core, data, state$ })], + [state$] + ); + return ( import('./custom_dataset_selector')); const LazyCustomDatasetFilters = dynamic(() => import('./custom_dataset_filters')); @@ -17,10 +20,11 @@ const LazyCustomDatasetFilters = dynamic(() => import('./custom_dataset_filters' export interface CreateLogExplorerProfileCustomizationsDeps { core: CoreStart; data: DataPublicPluginStart; + state$?: BehaviorSubject; } export const createLogExplorerProfileCustomizations = - ({ core, data }: CreateLogExplorerProfileCustomizationsDeps): CustomizationCallback => + ({ core, data, state$ }: CreateLogExplorerProfileCustomizationsDeps): CustomizationCallback => async ({ customizations, stateContainer }) => { // Lazy load dependencies const datasetServiceModuleLoadable = import('../services/datasets'); @@ -38,13 +42,26 @@ export const createLogExplorerProfileCustomizations = toasts: core.notifications.toasts, }); - // /** * Wait for the machine to be fully initialized to set the restored selection * create the DataView and set it in the stateContainer from Discover */ await waitForState(logExplorerProfileStateService, 'initialized'); + /** + * Subscribe the state$ BehaviorSubject when the consumer app wants to react to state changes. + * It emits a combined state of: + * - log explorer state machine context + * - appState from the discover stateContainer + */ + let stateSubscription: Subscription; + if (state$) { + stateSubscription = createStateUpdater({ + logExplorerProfileStateService, + stateContainer, + }).subscribe(state$); + } + /** * Replace the DataViewPicker with a custom `DatasetSelector` to pick integrations streams * Prepend the search bar with custom filter control groups depending on the selected dataset @@ -76,4 +93,25 @@ export const createLogExplorerProfileCustomizations = saveItem: { disabled: true }, }, }); + + return () => { + if (stateSubscription) { + stateSubscription.unsubscribe(); + } + }; }; + +const createStateUpdater = ({ + logExplorerProfileStateService, + stateContainer, +}: { + logExplorerProfileStateService: LogExplorerProfileStateService; + stateContainer: DiscoverStateContainer; +}) => { + return combineLatest([from(logExplorerProfileStateService), stateContainer.appState.state$]).pipe( + map(([logExplorerState, appState]) => ({ + logExplorerState: logExplorerState.context, + appState, + })) + ); +}; diff --git a/x-pack/plugins/log_explorer/public/index.ts b/x-pack/plugins/log_explorer/public/index.ts index c145f6fd88864..00750926517e6 100644 --- a/x-pack/plugins/log_explorer/public/index.ts +++ b/x-pack/plugins/log_explorer/public/index.ts @@ -9,6 +9,7 @@ import type { PluginInitializerContext } from '@kbn/core/public'; import type { LogExplorerConfig } from '../common/plugin_config'; import { LogExplorerPlugin } from './plugin'; export type { LogExplorerPluginSetup, LogExplorerPluginStart } from './types'; +export type { LogExplorerStateContainer } from './components/log_explorer'; export function plugin(context: PluginInitializerContext) { return new LogExplorerPlugin(context); diff --git a/x-pack/plugins/observability_log_explorer/common/constants.ts b/x-pack/plugins/observability_log_explorer/common/constants.ts new file mode 100644 index 0000000000000..90cd311f05940 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/constants.ts @@ -0,0 +1,8 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const OBSERVABILITY_LOG_EXPLORER_APP_ID = 'observability-log-explorer'; diff --git a/x-pack/plugins/observability_log_explorer/common/translations.ts b/x-pack/plugins/observability_log_explorer/common/translations.ts index 5ec1940fa8dff..2abf660538260 100644 --- a/x-pack/plugins/observability_log_explorer/common/translations.ts +++ b/x-pack/plugins/observability_log_explorer/common/translations.ts @@ -21,3 +21,10 @@ export const betaBadgeDescription = i18n.translate( defaultMessage: 'This application is in beta and therefore subject to change.', } ); + +export const discoverLinkTitle = i18n.translate( + 'xpack.observabilityLogExplorer.discoverLinkTitle', + { + defaultMessage: 'Discover', + } +); diff --git a/x-pack/plugins/observability_log_explorer/kibana.jsonc b/x-pack/plugins/observability_log_explorer/kibana.jsonc index 35121b578c39c..529f879a56386 100644 --- a/x-pack/plugins/observability_log_explorer/kibana.jsonc +++ b/x-pack/plugins/observability_log_explorer/kibana.jsonc @@ -13,12 +13,13 @@ ], "requiredPlugins": [ "data", + "discover", "logExplorer", "observabilityShared" ], "optionalPlugins": [ "serverless" ], - "requiredBundles": [] + "requiredBundles": ["kibanaReact"] } } diff --git a/x-pack/plugins/observability_log_explorer/public/applications/observability_log_explorer.tsx b/x-pack/plugins/observability_log_explorer/public/applications/observability_log_explorer.tsx index 7d6863e4eb45a..999ebdd3095bf 100644 --- a/x-pack/plugins/observability_log_explorer/public/applications/observability_log_explorer.tsx +++ b/x-pack/plugins/observability_log_explorer/public/applications/observability_log_explorer.tsx @@ -5,28 +5,29 @@ * 2.0. */ -import { AppMountParameters, CoreStart, ScopedHistory } from '@kbn/core/public'; +import { AppMountParameters, CoreStart } from '@kbn/core/public'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { Route, Router, Routes } from '@kbn/shared-ux-router'; import React from 'react'; import ReactDOM from 'react-dom'; import { ObservablityLogExplorerMainRoute } from '../routes/main'; import { ObservabilityLogExplorerPluginStart, ObservabilityLogExplorerStartDeps } from '../types'; +import { useKibanaContextForPluginProvider } from '../utils/use_kibana'; export const renderObservabilityLogExplorer = ( core: CoreStart, pluginsStart: ObservabilityLogExplorerStartDeps, ownPluginStart: ObservabilityLogExplorerPluginStart, - { element, history }: AppMountParameters + appParams: AppMountParameters ) => { ReactDOM.render( , - element + appParams.element ); return () => { @@ -34,40 +35,51 @@ export const renderObservabilityLogExplorer = ( // observable in the search session service pluginsStart.data.search.session.clear(); - ReactDOM.unmountComponentAtNode(element); + ReactDOM.unmountComponentAtNode(appParams.element); }; }; export interface ObservabilityLogExplorerAppProps { + appParams: AppMountParameters; core: CoreStart; plugins: ObservabilityLogExplorerStartDeps; pluginStart: ObservabilityLogExplorerPluginStart; - history: ScopedHistory; } export const ObservabilityLogExplorerApp = ({ + appParams, core, - plugins: { logExplorer, observabilityShared, serverless }, + plugins, pluginStart, - history, -}: ObservabilityLogExplorerAppProps) => ( - - - - ( - { + const { logExplorer, observabilityShared, serverless } = plugins; + const KibanaContextProviderForPlugin = useKibanaContextForPluginProvider( + core, + plugins, + pluginStart + ); + + return ( + + + + + ( + + )} /> - )} - /> - - - -); + + + + + ); +}; diff --git a/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx b/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx new file mode 100644 index 0000000000000..0e8ec200da871 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx @@ -0,0 +1,105 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import deepEqual from 'fast-deep-equal'; +import useObservable from 'react-use/lib/useObservable'; +import { type BehaviorSubject, distinctUntilChanged } from 'rxjs'; +import { HeaderMenuPortal } from '@kbn/observability-shared-plugin/public'; +import { AppMountParameters } from '@kbn/core-application-browser'; +import { + EuiBetaBadge, + EuiHeaderLink, + EuiHeaderLinks, + EuiHeaderSection, + EuiHeaderSectionItem, + useEuiTheme, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { LogExplorerStateContainer } from '@kbn/log-explorer-plugin/public'; +import { useKibanaContextForPlugin } from '../utils/use_kibana'; +import { betaBadgeDescription, betaBadgeTitle, discoverLinkTitle } from '../../common/translations'; + +interface LogExplorerTopNavMenuProps { + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; + state$: BehaviorSubject; + theme$: AppMountParameters['theme$']; +} + +export const LogExplorerTopNavMenu = ({ + setHeaderActionMenu, + state$, + theme$, +}: LogExplorerTopNavMenuProps) => { + const { euiTheme } = useEuiTheme(); + + return ( + + + + + + + + + + + ); +}; + +const DiscoverLink = React.memo( + ({ state$ }: { state$: BehaviorSubject }) => { + const { + services: { discover }, + } = useKibanaContextForPlugin(); + + const { appState, logExplorerState } = useObservable( + state$.pipe( + distinctUntilChanged((prev, curr) => { + if (!prev.appState || !curr.appState) return false; + return deepEqual( + [ + prev.appState.columns, + prev.appState.filters, + prev.appState.index, + prev.appState.query, + ], + [curr.appState.columns, curr.appState.filters, curr.appState.index, curr.appState.query] + ); + }) + ), + { appState: {}, logExplorerState: {} } + ); + + const discoverLinkParams = { + columns: appState?.columns, + filters: appState?.filters, + query: appState?.query, + dataViewSpec: logExplorerState?.datasetSelection?.selection.dataset.toDataviewSpec(), + }; + + return ( + discover.locator?.navigate(discoverLinkParams)} + color="primary" + iconType="discoverApp" + data-test-subj="logExplorerDiscoverFallbackLink" + > + {discoverLinkTitle} + + ); + } +); diff --git a/x-pack/plugins/observability_log_explorer/public/plugin.ts b/x-pack/plugins/observability_log_explorer/public/plugin.ts index 6afb62235ba15..8ca79e4ec7be4 100644 --- a/x-pack/plugins/observability_log_explorer/public/plugin.ts +++ b/x-pack/plugins/observability_log_explorer/public/plugin.ts @@ -14,6 +14,7 @@ import { PluginInitializerContext, } from '@kbn/core/public'; import { type ObservabilityLogExplorerConfig } from '../common/plugin_config'; +import { OBSERVABILITY_LOG_EXPLORER_APP_ID } from '../common/constants'; import { logExplorerAppTitle } from '../common/translations'; import { renderObservabilityLogExplorer } from './applications/observability_log_explorer'; import type { @@ -37,7 +38,7 @@ export class ObservabilityLogExplorerPlugin _pluginsSetup: ObservabilityLogExplorerSetupDeps ) { core.application.register({ - id: 'observability-log-explorer', + id: OBSERVABILITY_LOG_EXPLORER_APP_ID, title: logExplorerAppTitle, category: DEFAULT_APP_CATEGORIES.observability, euiIconType: 'logoLogging', diff --git a/x-pack/plugins/observability_log_explorer/public/routes/main/main_route.tsx b/x-pack/plugins/observability_log_explorer/public/routes/main/main_route.tsx index 5e9b22fb1ad5d..7b224da830433 100644 --- a/x-pack/plugins/observability_log_explorer/public/routes/main/main_route.tsx +++ b/x-pack/plugins/observability_log_explorer/public/routes/main/main_route.tsx @@ -5,34 +5,45 @@ * 2.0. */ -import { CoreStart, ScopedHistory } from '@kbn/core/public'; +import { AppMountParameters, CoreStart } from '@kbn/core/public'; import { LogExplorerPluginStart } from '@kbn/log-explorer-plugin/public'; import { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; import { ServerlessPluginStart } from '@kbn/serverless/public'; -import React from 'react'; +import React, { useState } from 'react'; +import { BehaviorSubject } from 'rxjs'; +import { LogExplorerTopNavMenu } from '../../components/log_explorer_top_nav_menu'; import { ObservabilityLogExplorerPageTemplate } from '../../components/page_template'; import { noBreadcrumbs, useBreadcrumbs } from '../../utils/breadcrumbs'; export interface ObservablityLogExplorerMainRouteProps { + appParams: AppMountParameters; core: CoreStart; - history: ScopedHistory; logExplorer: LogExplorerPluginStart; observabilityShared: ObservabilitySharedPluginStart; serverless?: ServerlessPluginStart; } export const ObservablityLogExplorerMainRoute = ({ + appParams: { history, setHeaderActionMenu, theme$ }, core, - history, logExplorer, observabilityShared, serverless, }: ObservablityLogExplorerMainRouteProps) => { useBreadcrumbs(noBreadcrumbs, core.chrome, serverless); + const [state$] = useState(() => new BehaviorSubject({})); + return ( - - - + <> + + + + + ); }; diff --git a/x-pack/plugins/observability_log_explorer/public/types.ts b/x-pack/plugins/observability_log_explorer/public/types.ts index f5e6526c502d9..e52ece9ca1624 100644 --- a/x-pack/plugins/observability_log_explorer/public/types.ts +++ b/x-pack/plugins/observability_log_explorer/public/types.ts @@ -6,6 +6,7 @@ */ import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { DiscoverStart } from '@kbn/discover-plugin/public'; import { LogExplorerPluginStart } from '@kbn/log-explorer-plugin/public'; import { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; import { ServerlessPluginStart } from '@kbn/serverless/public'; @@ -22,6 +23,7 @@ export interface ObservabilityLogExplorerSetupDeps { export interface ObservabilityLogExplorerStartDeps { data: DataPublicPluginStart; + discover: DiscoverStart; logExplorer: LogExplorerPluginStart; observabilityShared: ObservabilitySharedPluginStart; serverless?: ServerlessPluginStart; diff --git a/x-pack/plugins/observability_log_explorer/public/utils/breadcrumbs.tsx b/x-pack/plugins/observability_log_explorer/public/utils/breadcrumbs.tsx index a8b575d5341dc..c1eaca45b7855 100644 --- a/x-pack/plugins/observability_log_explorer/public/utils/breadcrumbs.tsx +++ b/x-pack/plugins/observability_log_explorer/public/utils/breadcrumbs.tsx @@ -9,11 +9,7 @@ import { EuiBreadcrumb } from '@elastic/eui'; import type { ChromeStart } from '@kbn/core-chrome-browser'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import { useEffect } from 'react'; -import { - betaBadgeDescription, - betaBadgeTitle, - logExplorerAppTitle, -} from '../../common/translations'; +import { logExplorerAppTitle } from '../../common/translations'; export const useBreadcrumbs = ( breadcrumbs: EuiBreadcrumb[], @@ -40,10 +36,6 @@ export function setBreadcrumbs( ...breadcrumbs, ]); } - chromeService.setBadge({ - text: betaBadgeTitle, - tooltip: betaBadgeDescription, - }); } export const noBreadcrumbs: EuiBreadcrumb[] = []; diff --git a/x-pack/plugins/observability_log_explorer/tsconfig.json b/x-pack/plugins/observability_log_explorer/tsconfig.json index 5f94d15d30fea..ae9660b421359 100644 --- a/x-pack/plugins/observability_log_explorer/tsconfig.json +++ b/x-pack/plugins/observability_log_explorer/tsconfig.json @@ -22,6 +22,8 @@ "@kbn/serverless", "@kbn/core-chrome-browser", "@kbn/config-schema", + "@kbn/core-application-browser", + "@kbn/discover-plugin", ], "exclude": [ "target/**/*" diff --git a/x-pack/test/functional/apps/observability_log_explorer/header_menu.ts b/x-pack/test/functional/apps/observability_log_explorer/header_menu.ts new file mode 100644 index 0000000000000..0831bec27b7ed --- /dev/null +++ b/x-pack/test/functional/apps/observability_log_explorer/header_menu.ts @@ -0,0 +1,76 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['discover', 'observabilityLogExplorer', 'timePicker']); + + describe('Header menu', () => { + before(async () => { + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); + await esArchiver.load( + 'x-pack/test/functional/es_archives/observability_log_explorer/data_streams' + ); + await PageObjects.observabilityLogExplorer.navigateTo(); + }); + + after(async () => { + await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); + await esArchiver.unload( + 'x-pack/test/functional/es_archives/observability_log_explorer/data_streams' + ); + }); + + it('should inject the app header menu on the top navbar', async () => { + const headerMenu = await PageObjects.observabilityLogExplorer.getHeaderMenu(); + expect(await headerMenu.isDisplayed()).to.be(true); + }); + + describe('Discover fallback link', () => { + it('should render a button link ', async () => { + const discoverLink = await PageObjects.observabilityLogExplorer.getDiscoverFallbackLink(); + expect(await discoverLink.isDisplayed()).to.be(true); + }); + + it('should navigate to discover keeping the current columns/filters/query/time/data view', async () => { + // Set timerange to specific values to match data and retrieve config + await PageObjects.discover.expandTimeRangeAsSuggestedInNoResultsMessage(); + const timeConfig = await PageObjects.timePicker.getTimeConfig(); + + // Set query bar value + await PageObjects.observabilityLogExplorer.submitQuery('*favicon*'); + + const discoverLink = await PageObjects.observabilityLogExplorer.getDiscoverFallbackLink(); + discoverLink.click(); + + await PageObjects.discover.waitForDocTableLoadingComplete(); + + await retry.try(async () => { + expect(await PageObjects.discover.getCurrentlySelectedDataView()).to.eql( + 'All log datasets' + ); + }); + + await retry.try(async () => { + expect(await PageObjects.discover.getColumnHeaders()).to.eql(['@timestamp', 'message']); + }); + + await retry.try(async () => { + expect(await PageObjects.timePicker.getTimeConfig()).to.eql(timeConfig); + }); + + await retry.try(async () => { + expect(await PageObjects.observabilityLogExplorer.getQueryBarValue()).to.eql('*favicon*'); + }); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/observability_log_explorer/index.ts b/x-pack/test/functional/apps/observability_log_explorer/index.ts index 90a52663e34ce..aec38a6bb8308 100644 --- a/x-pack/test/functional/apps/observability_log_explorer/index.ts +++ b/x-pack/test/functional/apps/observability_log_explorer/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./dataset_selection_state')); loadTestFile(require.resolve('./dataset_selector')); loadTestFile(require.resolve('./filter_controls')); + loadTestFile(require.resolve('./header_menu')); }); } diff --git a/x-pack/test/functional/page_objects/observability_log_explorer.ts b/x-pack/test/functional/page_objects/observability_log_explorer.ts index 33e85e06a16a9..7e4b83083ace0 100644 --- a/x-pack/test/functional/page_objects/observability_log_explorer.ts +++ b/x-pack/test/functional/page_objects/observability_log_explorer.ts @@ -314,5 +314,34 @@ export function ObservabilityLogExplorerPageObject({ expect(await promptTitle.getVisibleText()).to.be('No data streams found'); }, + + getHeaderMenu() { + return testSubjects.find('logExplorerHeaderMenu'); + }, + + getDiscoverFallbackLink() { + return testSubjects.find('logExplorerDiscoverFallbackLink'); + }, + + // Query Bar + getQueryBar() { + return testSubjects.find('queryInput'); + }, + + async getQueryBarValue() { + const queryBar = await testSubjects.find('queryInput'); + return queryBar.getAttribute('value'); + }, + + async typeInQueryBar(query: string) { + const queryBar = await this.getQueryBar(); + await queryBar.clearValueWithKeyboard(); + return queryBar.type(query); + }, + + async submitQuery(query: string) { + await this.typeInQueryBar(query); + await testSubjects.click('querySubmitButton'); + }, }; } diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/header_menu.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/header_menu.ts new file mode 100644 index 0000000000000..038e28a442c24 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/header_menu.ts @@ -0,0 +1,76 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['discover', 'observabilityLogExplorer', 'timePicker']); + + describe('Header menu', () => { + before(async () => { + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); + await esArchiver.load( + 'x-pack/test/functional/es_archives/observability_log_explorer/data_streams' + ); + await PageObjects.observabilityLogExplorer.navigateTo(); + }); + + after(async () => { + await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); + await esArchiver.unload( + 'x-pack/test/functional/es_archives/observability_log_explorer/data_streams' + ); + }); + + it('should inject the app header menu on the top navbar', async () => { + const headerMenu = await PageObjects.observabilityLogExplorer.getHeaderMenu(); + expect(await headerMenu.isDisplayed()).to.be(true); + }); + + describe('Discover fallback link', () => { + it('should render a button link ', async () => { + const discoverLink = await PageObjects.observabilityLogExplorer.getDiscoverFallbackLink(); + expect(await discoverLink.isDisplayed()).to.be(true); + }); + + it('should navigate to discover keeping the current columns/filters/query/time/data view', async () => { + // Set timerange to specific values to match data and retrieve config + await PageObjects.discover.expandTimeRangeAsSuggestedInNoResultsMessage(); + const timeConfig = await PageObjects.timePicker.getTimeConfig(); + + // Set query bar value + await PageObjects.observabilityLogExplorer.submitQuery('*favicon*'); + + const discoverLink = await PageObjects.observabilityLogExplorer.getDiscoverFallbackLink(); + discoverLink.click(); + + await PageObjects.discover.waitForDocTableLoadingComplete(); + + await retry.try(async () => { + expect(await PageObjects.discover.getCurrentlySelectedDataView()).to.eql( + 'All log datasets' + ); + }); + + await retry.try(async () => { + expect(await PageObjects.discover.getColumnHeaders()).to.eql(['@timestamp', 'message']); + }); + + await retry.try(async () => { + expect(await PageObjects.timePicker.getTimeConfig()).to.eql(timeConfig); + }); + + await retry.try(async () => { + expect(await PageObjects.observabilityLogExplorer.getQueryBarValue()).to.eql('*favicon*'); + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts index b0555b4447d27..77f89dad01f77 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./dataset_selection_state')); loadTestFile(require.resolve('./dataset_selector')); loadTestFile(require.resolve('./filter_controls')); + loadTestFile(require.resolve('./header_menu')); }); } From 9d5bb89b7cd3738482b9ea3247cd88077375ed3b Mon Sep 17 00:00:00 2001 From: Yngrid Coello Date: Wed, 6 Sep 2023 12:52:28 +0200 Subject: [PATCH 2/2] [Logs onboarding] Adding Elastic-Api-Version to cy command (#165814) --- .../observability_onboarding/e2e/cypress/support/commands.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/observability_onboarding/e2e/cypress/support/commands.ts b/x-pack/plugins/observability_onboarding/e2e/cypress/support/commands.ts index ea321f48a0bae..ddceaf3325bfa 100644 --- a/x-pack/plugins/observability_onboarding/e2e/cypress/support/commands.ts +++ b/x-pack/plugins/observability_onboarding/e2e/cypress/support/commands.ts @@ -119,6 +119,7 @@ Cypress.Commands.add('deleteIntegration', (integrationName: string) => { }, headers: { 'kbn-xsrf': 'e2e_test', + 'Elastic-Api-Version': '1', }, auth: { user: 'editor', pass: 'changeme' }, });