From 3e513590f70dea25367e9eafb02b0fdfdda3c683 Mon Sep 17 00:00:00 2001 From: Abdul Zahid Date: Fri, 4 Oct 2024 17:46:00 +0700 Subject: [PATCH 01/11] Add app to remember last used discover vs. observability-logs-explorer app. --- packages/deeplinks/observability/constants.ts | 3 + .../deeplinks/observability/deep_links.ts | 3 + packages/deeplinks/observability/index.ts | 1 + .../locators/observability_logs_explorer.ts | 3 + .../logs_explorer_tabs.test.tsx | 25 +++++++- .../logs_explorer_tabs/logs_explorer_tabs.tsx | 42 +++++++++--- .../applications/last_used_logs_viewer.tsx | 64 +++++++++++++++++++ .../public/plugin.ts | 16 +++++ .../public/navigation_tree.ts | 2 +- 9 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugins/observability_solution/observability_logs_explorer/public/applications/last_used_logs_viewer.tsx diff --git a/packages/deeplinks/observability/constants.ts b/packages/deeplinks/observability/constants.ts index 45868fa3a16b2..0a4fe5841fd74 100644 --- a/packages/deeplinks/observability/constants.ts +++ b/packages/deeplinks/observability/constants.ts @@ -11,6 +11,9 @@ export const LOGS_APP_ID = 'logs'; export const OBSERVABILITY_LOGS_EXPLORER_APP_ID = 'observability-logs-explorer'; +// TODO: Remove the app once context-aware switching between logs and observability logs explorer is implemented +export const LAST_USED_LOGS_VIEWER_APP_ID = 'last-used-logs-viewer'; + export const OBSERVABILITY_OVERVIEW_APP_ID = 'observability-overview'; export const METRICS_APP_ID = 'metrics'; diff --git a/packages/deeplinks/observability/deep_links.ts b/packages/deeplinks/observability/deep_links.ts index 088b9c866c03d..1253b4e889fcf 100644 --- a/packages/deeplinks/observability/deep_links.ts +++ b/packages/deeplinks/observability/deep_links.ts @@ -12,6 +12,7 @@ import { LOGS_APP_ID, METRICS_APP_ID, OBSERVABILITY_LOGS_EXPLORER_APP_ID, + LAST_USED_LOGS_VIEWER_APP_ID, OBSERVABILITY_ONBOARDING_APP_ID, OBSERVABILITY_OVERVIEW_APP_ID, SYNTHETICS_APP_ID, @@ -24,6 +25,7 @@ import { type LogsApp = typeof LOGS_APP_ID; type ObservabilityLogsExplorerApp = typeof OBSERVABILITY_LOGS_EXPLORER_APP_ID; +type LastUsedLogsViewerApp = typeof LAST_USED_LOGS_VIEWER_APP_ID; type ObservabilityOverviewApp = typeof OBSERVABILITY_OVERVIEW_APP_ID; type MetricsApp = typeof METRICS_APP_ID; type ApmApp = typeof APM_APP_ID; @@ -38,6 +40,7 @@ type InventoryApp = typeof INVENTORY_APP_ID; export type AppId = | LogsApp | ObservabilityLogsExplorerApp + | LastUsedLogsViewerApp | ObservabilityOverviewApp | ObservabilityOnboardingApp | ApmApp diff --git a/packages/deeplinks/observability/index.ts b/packages/deeplinks/observability/index.ts index 7185a4cfbcd6f..8bedc43f5d6a8 100644 --- a/packages/deeplinks/observability/index.ts +++ b/packages/deeplinks/observability/index.ts @@ -10,6 +10,7 @@ export { LOGS_APP_ID, OBSERVABILITY_LOGS_EXPLORER_APP_ID, + LAST_USED_LOGS_VIEWER_APP_ID, OBSERVABILITY_ONBOARDING_APP_ID, OBSERVABILITY_OVERVIEW_APP_ID, AI_ASSISTANT_APP_ID, diff --git a/packages/deeplinks/observability/locators/observability_logs_explorer.ts b/packages/deeplinks/observability/locators/observability_logs_explorer.ts index 037acb7d946ad..f3658dccfea21 100644 --- a/packages/deeplinks/observability/locators/observability_logs_explorer.ts +++ b/packages/deeplinks/observability/locators/observability_logs_explorer.ts @@ -49,3 +49,6 @@ export interface ObsLogsExplorerDataViewLocatorParams extends DatasetLocatorPara */ id: string; } + +// To store the last used logs viewer (either of discover or observability logs explorer) +export const OBS_LOGS_EXPLORER_LOGS_VIEWER_KEY = 'obs-logs-explorer:lastUsedViewer'; diff --git a/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx b/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx index 1782dc7ad6ac7..cc9e605aa45bb 100644 --- a/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx +++ b/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx @@ -10,10 +10,21 @@ import userEvent from '@testing-library/user-event'; import { render, screen } from '@testing-library/react'; import React from 'react'; +import { DISCOVER_APP_ID } from '@kbn/deeplinks-analytics'; +import { + ALL_DATASETS_LOCATOR_ID, + OBSERVABILITY_LOGS_EXPLORER_APP_ID, +} from '@kbn/deeplinks-observability'; import { discoverServiceMock } from '../../__mocks__/services'; import { LogsExplorerTabs, LogsExplorerTabsProps } from './logs_explorer_tabs'; import { DISCOVER_APP_LOCATOR } from '../../../common'; -import { ALL_DATASETS_LOCATOR_ID } from '@kbn/deeplinks-observability'; + +const mockSetLastUsedViewer = jest.fn(); +jest.mock('react-use/lib/useLocalStorage', () => { + return jest.fn((key: string, initialValue: string) => { + return [initialValue, mockSetLastUsedViewer]; + }); +}); const createMockLocator = (id: string) => ({ navigate: jest.fn(), @@ -86,4 +97,16 @@ describe('LogsExplorerTabs', () => { await userEvent.click(getDiscoverTab()); expect(mockDiscoverLocator.navigate).toHaveBeenCalledWith({}); }); + + it('should update the last used viewer in local storage', async () => { + renderTabs(); + + mockSetLastUsedViewer.mockClear(); + await userEvent.click(getLogsExplorerTab()); + expect(mockSetLastUsedViewer).toHaveBeenCalledWith(OBSERVABILITY_LOGS_EXPLORER_APP_ID); + + mockSetLastUsedViewer.mockClear(); + await userEvent.click(getDiscoverTab()); + expect(mockSetLastUsedViewer).toHaveBeenCalledWith(DISCOVER_APP_ID); + }); }); diff --git a/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx b/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx index 1eec001464d2a..aed8cbeb3c903 100644 --- a/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx +++ b/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx @@ -8,9 +8,16 @@ */ import { EuiTab, EuiTabs, useEuiTheme } from '@elastic/eui'; -import { AllDatasetsLocatorParams, ALL_DATASETS_LOCATOR_ID } from '@kbn/deeplinks-observability'; +import { DISCOVER_APP_ID } from '@kbn/deeplinks-analytics'; +import { + AllDatasetsLocatorParams, + ALL_DATASETS_LOCATOR_ID, + OBS_LOGS_EXPLORER_LOGS_VIEWER_KEY, + OBSERVABILITY_LOGS_EXPLORER_APP_ID, +} from '@kbn/deeplinks-observability'; import { i18n } from '@kbn/i18n'; import React, { MouseEvent } from 'react'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import { DiscoverAppLocatorParams, DISCOVER_APP_LOCATOR } from '../../../common'; import type { DiscoverServices } from '../../build_services'; @@ -29,17 +36,31 @@ export const LogsExplorerTabs = ({ services, selectedTab }: LogsExplorerTabsProp const discoverUrl = discoverLocator?.getRedirectUrl(emptyParams); const logsExplorerUrl = logsExplorerLocator?.getRedirectUrl(emptyParams); - const navigateToDiscover = createNavigateHandler(() => { - if (selectedTab !== 'discover') { - discoverLocator?.navigate(emptyParams); + const [_, setLastUsedViewer] = useLocalStorage< + typeof DISCOVER_APP_ID | typeof OBSERVABILITY_LOGS_EXPLORER_APP_ID + >(OBS_LOGS_EXPLORER_LOGS_VIEWER_KEY, OBSERVABILITY_LOGS_EXPLORER_APP_ID); + + const navigateToDiscover = createNavigateHandler( + () => { + if (selectedTab !== 'discover') { + discoverLocator?.navigate(emptyParams); + } + }, + () => { + setLastUsedViewer(DISCOVER_APP_ID); } - }); + ); - const navigateToLogsExplorer = createNavigateHandler(() => { - if (selectedTab !== 'logs-explorer') { - logsExplorerLocator?.navigate(emptyParams); + const navigateToLogsExplorer = createNavigateHandler( + () => { + if (selectedTab !== 'logs-explorer') { + logsExplorerLocator?.navigate(emptyParams); + } + }, + () => { + setLastUsedViewer(OBSERVABILITY_LOGS_EXPLORER_APP_ID); } - }); + ); return ( @@ -77,11 +98,12 @@ const isModifiedEvent = (event: MouseEvent) => const isLeftClickEvent = (event: MouseEvent) => event.button === 0; -const createNavigateHandler = (onClick: () => void) => (e: MouseEvent) => { +const createNavigateHandler = (onClick: () => void, updateState: () => void) => (e: MouseEvent) => { if (isModifiedEvent(e) || !isLeftClickEvent(e)) { return; } e.preventDefault(); + updateState(); // Update local storage onClick(); }; diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/public/applications/last_used_logs_viewer.tsx b/x-pack/plugins/observability_solution/observability_logs_explorer/public/applications/last_used_logs_viewer.tsx new file mode 100644 index 0000000000000..f3696a9816ee0 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/public/applications/last_used_logs_viewer.tsx @@ -0,0 +1,64 @@ +/* + * 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, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; +import { useLocation } from 'react-router-dom'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; +import { Router } from '@kbn/shared-ux-router'; +import { + OBSERVABILITY_LOGS_EXPLORER_APP_ID, + OBS_LOGS_EXPLORER_LOGS_VIEWER_KEY, +} from '@kbn/deeplinks-observability'; +import { DISCOVER_APP_ID } from '@kbn/deeplinks-analytics'; +import { AppMountParameters, CoreStart } from '@kbn/core/public'; + +export const renderLastUsedLogsViewerRedirect = ( + core: CoreStart, + appParams: AppMountParameters +) => { + ReactDOM.render( + + + , + appParams.element + ); + + return () => { + ReactDOM.unmountComponentAtNode(appParams.element); + }; +}; + +export const LastUsedLogsViewerRedirect = ({ core }: { core: CoreStart }) => { + const location = useLocation(); + const path = `${location.pathname}${location.search}`; + const [lastUsedLogsViewApp] = useLocalStorage< + typeof DISCOVER_APP_ID | typeof OBSERVABILITY_LOGS_EXPLORER_APP_ID + >(OBS_LOGS_EXPLORER_LOGS_VIEWER_KEY, OBSERVABILITY_LOGS_EXPLORER_APP_ID); + + if ( + lastUsedLogsViewApp && + lastUsedLogsViewApp !== DISCOVER_APP_ID && + lastUsedLogsViewApp !== OBSERVABILITY_LOGS_EXPLORER_APP_ID + ) { + throw new Error( + `Invalid last used logs viewer app: "${lastUsedLogsViewApp}". Allowed values are "${DISCOVER_APP_ID}" and "${OBSERVABILITY_LOGS_EXPLORER_APP_ID}"` + ); + } + + useEffect(() => { + if (lastUsedLogsViewApp === DISCOVER_APP_ID) { + core.application.navigateToApp(DISCOVER_APP_ID, { replace: true, path }); + } + + if (lastUsedLogsViewApp === OBSERVABILITY_LOGS_EXPLORER_APP_ID) { + core.application.navigateToApp(OBSERVABILITY_LOGS_EXPLORER_APP_ID, { replace: true, path }); + } + }, [core, path, lastUsedLogsViewApp]); + + return <>; +}; diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/public/plugin.ts b/x-pack/plugins/observability_solution/observability_logs_explorer/public/plugin.ts index 798a03da0ebdf..2e6ab0aeeaa0f 100644 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/public/plugin.ts +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/public/plugin.ts @@ -97,6 +97,22 @@ export class ObservabilityLogsExplorerPlugin }, }); + // App used solely to redirect to either "/app/observability-logs-explorer" or "/app/discover" + // based on the last used app value in localStorage + core.application.register({ + id: 'last-used-logs-viewer', + title: logsExplorerAppTitle, + visibleIn: [], + mount: async (appMountParams: AppMountParameters) => { + const [coreStart] = await core.getStartServices(); + const { renderLastUsedLogsViewerRedirect } = await import( + './applications/last_used_logs_viewer' + ); + + return renderLastUsedLogsViewerRedirect(coreStart, appMountParams); + }, + }); + core.analytics.registerEventType(DATA_RECEIVED_TELEMETRY_EVENT); // Register Locators diff --git a/x-pack/plugins/serverless_observability/public/navigation_tree.ts b/x-pack/plugins/serverless_observability/public/navigation_tree.ts index e3c61ec0b29e3..5df900ee46812 100644 --- a/x-pack/plugins/serverless_observability/public/navigation_tree.ts +++ b/x-pack/plugins/serverless_observability/public/navigation_tree.ts @@ -24,7 +24,7 @@ export const navigationTree: NavigationTreeDefinition = { title: i18n.translate('xpack.serverlessObservability.nav.discover', { defaultMessage: 'Discover', }), - link: 'observability-logs-explorer', + link: 'last-used-logs-viewer', // avoid duplicate "Discover" breadcrumbs breadcrumbStatus: 'hidden', renderAs: 'item', From ed7bf0e5ab425a59eb6e8ce6c7945dd5cfe3aab8 Mon Sep 17 00:00:00 2001 From: Abdul Zahid Date: Fri, 4 Oct 2024 18:16:21 +0700 Subject: [PATCH 02/11] Improve comments. --- packages/deeplinks/observability/constants.ts | 2 +- .../observability/locators/observability_logs_explorer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/deeplinks/observability/constants.ts b/packages/deeplinks/observability/constants.ts index 0a4fe5841fd74..25642ba69613a 100644 --- a/packages/deeplinks/observability/constants.ts +++ b/packages/deeplinks/observability/constants.ts @@ -11,7 +11,7 @@ export const LOGS_APP_ID = 'logs'; export const OBSERVABILITY_LOGS_EXPLORER_APP_ID = 'observability-logs-explorer'; -// TODO: Remove the app once context-aware switching between logs and observability logs explorer is implemented +// TODO: Remove the app once context-aware switching between discover and observability logs explorer is implemented export const LAST_USED_LOGS_VIEWER_APP_ID = 'last-used-logs-viewer'; export const OBSERVABILITY_OVERVIEW_APP_ID = 'observability-overview'; diff --git a/packages/deeplinks/observability/locators/observability_logs_explorer.ts b/packages/deeplinks/observability/locators/observability_logs_explorer.ts index f3658dccfea21..ae4cbc12cec6d 100644 --- a/packages/deeplinks/observability/locators/observability_logs_explorer.ts +++ b/packages/deeplinks/observability/locators/observability_logs_explorer.ts @@ -50,5 +50,5 @@ export interface ObsLogsExplorerDataViewLocatorParams extends DatasetLocatorPara id: string; } -// To store the last used logs viewer (either of discover or observability logs explorer) +// To store the last used logs viewer (either of discover or observability-logs-explorer) export const OBS_LOGS_EXPLORER_LOGS_VIEWER_KEY = 'obs-logs-explorer:lastUsedViewer'; From 9bf3db23df469632ae36334cfba94fdafe612619 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 4 Oct 2024 11:30:15 +0000 Subject: [PATCH 03/11] [CI] Auto-commit changed files from 'node scripts/yarn_deduplicate' --- .../observability_logs_explorer/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json b/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json index 6ea751aaed3de..be2b3c9efdff6 100644 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json @@ -50,6 +50,7 @@ "@kbn/core-analytics-browser", "@kbn/react-hooks", "@kbn/logs-data-access-plugin", + "@kbn/deeplinks-analytics", ], "exclude": [ "target/**/*" From 2740f790e39eeeda471f80c810b021772bce1ab0 Mon Sep 17 00:00:00 2001 From: Abdul Zahid Date: Fri, 4 Oct 2024 19:50:17 +0700 Subject: [PATCH 04/11] Update functional test. --- .../functional/test_suites/observability/navigation.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 ce6b68cd618a1..bcd9c031714f7 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts @@ -35,9 +35,10 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await svlCommonNavigation.sidenav.expectSectionClosed('project_settings_project_nav'); // navigate to the logs explorer tab by default - await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'observability-logs-explorer' }); + // 'last-used-logs-viewer' is wrapper app to handle the navigation between logs explorer and discover + await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'last-used-logs-viewer' }); await svlCommonNavigation.sidenav.expectLinkActive({ - deepLinkId: 'observability-logs-explorer', + deepLinkId: 'last-used-logs-viewer', }); await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'observability-logs-explorer', From 427efd7452cde3cade02b8ce8a9f0bbb7dc4fcec Mon Sep 17 00:00:00 2001 From: Abdul Zahid Date: Fri, 4 Oct 2024 21:46:50 +0700 Subject: [PATCH 05/11] Add `last-use-logs-viewer` app schema to usage collection. --- .../server/collectors/application_usage/schema.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index 06866afc65caa..3c40e197dbad3 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -159,6 +159,7 @@ export const applicationUsageSchema = { monitoring: commonSchema, 'observability-log-explorer': commonSchema, 'observability-logs-explorer': commonSchema, + 'last-used-logs-viewer': commonSchema, 'observability-overview': commonSchema, observabilityOnboarding: commonSchema, observabilityAIAssistant: commonSchema, From edfc3b9f7ea9bfeb2d2f8ccc96ffa5a0e84711b8 Mon Sep 17 00:00:00 2001 From: Abdul Zahid Date: Fri, 4 Oct 2024 22:23:34 +0700 Subject: [PATCH 06/11] Exclude `last-use-logs-viewer` from app usage tracking. --- .../server/collectors/application_usage/schema.ts | 1 - .../usage_collection/test_suites/application_usage/index.ts | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index 3c40e197dbad3..06866afc65caa 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -159,7 +159,6 @@ export const applicationUsageSchema = { monitoring: commonSchema, 'observability-log-explorer': commonSchema, 'observability-logs-explorer': commonSchema, - 'last-used-logs-viewer': commonSchema, 'observability-overview': commonSchema, observabilityOnboarding: commonSchema, observabilityAIAssistant: commonSchema, diff --git a/x-pack/test/usage_collection/test_suites/application_usage/index.ts b/x-pack/test/usage_collection/test_suites/application_usage/index.ts index 4b820f6ff01c2..5f77f8a8db99b 100644 --- a/x-pack/test/usage_collection/test_suites/application_usage/index.ts +++ b/x-pack/test/usage_collection/test_suites/application_usage/index.ts @@ -29,7 +29,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { try { const enabledAppIds = Object.keys(applicationUsageSchema).filter( // Profiling is currently disabled by default as it's in closed beta - (appId) => appId !== 'profiling' + // last-used-logs-viewer is a only redirection wrapper for the logs apps and should not be tracked + (appId) => appId !== 'profiling' && appId !== 'last-used-logs-viewer' ); expect(enabledAppIds.sort()).to.eql(appIds.sort()); } catch (err) { From de350940115cc2b6a30e6af5297b2cbcd87cf370 Mon Sep 17 00:00:00 2001 From: Abdul Zahid Date: Sat, 5 Oct 2024 00:19:30 +0700 Subject: [PATCH 07/11] Add `last-used-logs-viewer` app schema. --- .../collectors/application_usage/schema.ts | 1 + src/plugins/telemetry/schema/oss_plugins.json | 131 ++++++++++++++++++ .../test_suites/application_usage/index.ts | 3 +- 3 files changed, 133 insertions(+), 2 deletions(-) diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index 06866afc65caa..3c40e197dbad3 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -159,6 +159,7 @@ export const applicationUsageSchema = { monitoring: commonSchema, 'observability-log-explorer': commonSchema, 'observability-logs-explorer': commonSchema, + 'last-used-logs-viewer': commonSchema, 'observability-overview': commonSchema, observabilityOnboarding: commonSchema, observabilityAIAssistant: commonSchema, diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index f25c29e5f6952..74b83b595a857 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -5111,6 +5111,137 @@ } } }, + "last-used-logs-viewer": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "Always `main`" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 90 days" + } + }, + "views": { + "type": "array", + "items": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "The application view being tracked" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application sub view since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application sub view is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" + } + } + } + } + } + } + }, "observability-overview": { "properties": { "appId": { diff --git a/x-pack/test/usage_collection/test_suites/application_usage/index.ts b/x-pack/test/usage_collection/test_suites/application_usage/index.ts index 5f77f8a8db99b..4b820f6ff01c2 100644 --- a/x-pack/test/usage_collection/test_suites/application_usage/index.ts +++ b/x-pack/test/usage_collection/test_suites/application_usage/index.ts @@ -29,8 +29,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { try { const enabledAppIds = Object.keys(applicationUsageSchema).filter( // Profiling is currently disabled by default as it's in closed beta - // last-used-logs-viewer is a only redirection wrapper for the logs apps and should not be tracked - (appId) => appId !== 'profiling' && appId !== 'last-used-logs-viewer' + (appId) => appId !== 'profiling' ); expect(enabledAppIds.sort()).to.eql(appIds.sort()); } catch (err) { From 04ab97fb71640f564679a710931dc0fdda4eb578 Mon Sep 17 00:00:00 2001 From: Abdul Zahid Date: Wed, 9 Oct 2024 01:20:18 +0700 Subject: [PATCH 08/11] Add `last-used-logs-viewer` redirect for solutions view on stateful. --- .../observability/public/navigation_tree.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/observability/public/navigation_tree.ts b/x-pack/plugins/observability_solution/observability/public/navigation_tree.ts index c661976fd0765..8aad4d8d370d6 100644 --- a/x-pack/plugins/observability_solution/observability/public/navigation_tree.ts +++ b/x-pack/plugins/observability_solution/observability/public/navigation_tree.ts @@ -34,7 +34,11 @@ export function createNavTree(pluginsStart: ObservabilityPublicPluginsStart) { link: 'observability-overview', }, { - link: 'discover', + title: i18n.translate('xpack.observability.obltNav.discover', { + defaultMessage: 'Discover', + }), + // 'last-used-logs-viewer' is wrapper app to handle the navigation between observability-log-explorer and discover + link: 'last-used-logs-viewer', renderAs: 'item', children: [ { From 545446dbeeec96452f8e820421ceb8c33209dfdf Mon Sep 17 00:00:00 2001 From: Abdul Zahid Date: Wed, 9 Oct 2024 11:55:32 +0700 Subject: [PATCH 09/11] PR feedback: simplify onclick callback. --- .../logs_explorer_tabs.test.tsx | 7 +++-- .../logs_explorer_tabs/logs_explorer_tabs.tsx | 27 +++++++------------ 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx b/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx index cc9e605aa45bb..740565ed3efcb 100644 --- a/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx +++ b/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx @@ -57,11 +57,12 @@ describe('LogsExplorerTabs', () => { }, } as unknown as typeof discoverServiceMock; - render(); + const { unmount } = render(); return { mockDiscoverLocator, mockLogsExplorerLocator, + unmount, }; }; @@ -99,12 +100,14 @@ describe('LogsExplorerTabs', () => { }); it('should update the last used viewer in local storage', async () => { - renderTabs(); + const { unmount } = renderTabs('discover'); mockSetLastUsedViewer.mockClear(); await userEvent.click(getLogsExplorerTab()); expect(mockSetLastUsedViewer).toHaveBeenCalledWith(OBSERVABILITY_LOGS_EXPLORER_APP_ID); + unmount(); + renderTabs('logs-explorer'); mockSetLastUsedViewer.mockClear(); await userEvent.click(getDiscoverTab()); expect(mockSetLastUsedViewer).toHaveBeenCalledWith(DISCOVER_APP_ID); diff --git a/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx b/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx index aed8cbeb3c903..83b387e8db8ce 100644 --- a/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx +++ b/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx @@ -40,27 +40,19 @@ export const LogsExplorerTabs = ({ services, selectedTab }: LogsExplorerTabsProp typeof DISCOVER_APP_ID | typeof OBSERVABILITY_LOGS_EXPLORER_APP_ID >(OBS_LOGS_EXPLORER_LOGS_VIEWER_KEY, OBSERVABILITY_LOGS_EXPLORER_APP_ID); - const navigateToDiscover = createNavigateHandler( - () => { - if (selectedTab !== 'discover') { - discoverLocator?.navigate(emptyParams); - } - }, - () => { + const navigateToDiscover = createNavigateHandler(() => { + if (selectedTab !== 'discover') { setLastUsedViewer(DISCOVER_APP_ID); + discoverLocator?.navigate(emptyParams); } - ); + }); - const navigateToLogsExplorer = createNavigateHandler( - () => { - if (selectedTab !== 'logs-explorer') { - logsExplorerLocator?.navigate(emptyParams); - } - }, - () => { + const navigateToLogsExplorer = createNavigateHandler(() => { + if (selectedTab !== 'logs-explorer') { setLastUsedViewer(OBSERVABILITY_LOGS_EXPLORER_APP_ID); + logsExplorerLocator?.navigate(emptyParams); } - ); + }); return ( @@ -98,12 +90,11 @@ const isModifiedEvent = (event: MouseEvent) => const isLeftClickEvent = (event: MouseEvent) => event.button === 0; -const createNavigateHandler = (onClick: () => void, updateState: () => void) => (e: MouseEvent) => { +const createNavigateHandler = (onClick: () => void) => (e: MouseEvent) => { if (isModifiedEvent(e) || !isLeftClickEvent(e)) { return; } e.preventDefault(); - updateState(); // Update local storage onClick(); }; From 4a639641f78622c6245e2e080315c563ada0d25e Mon Sep 17 00:00:00 2001 From: Abdul Zahid Date: Wed, 9 Oct 2024 18:09:44 +0700 Subject: [PATCH 10/11] Make sure Discover on sidenav is active when Discover or Logs Explorer tab is selected. --- .../observability/public/navigation_tree.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability_solution/observability/public/navigation_tree.ts b/x-pack/plugins/observability_solution/observability/public/navigation_tree.ts index 8aad4d8d370d6..09476a2e9f694 100644 --- a/x-pack/plugins/observability_solution/observability/public/navigation_tree.ts +++ b/x-pack/plugins/observability_solution/observability/public/navigation_tree.ts @@ -39,11 +39,17 @@ export function createNavTree(pluginsStart: ObservabilityPublicPluginsStart) { }), // 'last-used-logs-viewer' is wrapper app to handle the navigation between observability-log-explorer and discover link: 'last-used-logs-viewer', + breadcrumbStatus: 'hidden', // avoid duplicate "Discover" breadcrumbs renderAs: 'item', children: [ { - // This is to show "observability-log-explorer" breadcrumbs when navigating from "discover" to "log explorer" - link: 'observability-logs-explorer', + link: 'discover', + children: [ + { + // This is to show "observability-log-explorer" breadcrumbs when navigating from "discover" to "log explorer" + link: 'observability-logs-explorer', + }, + ], }, ], }, From 65671c959f406b9d9bdc6324185ddbc9ecd6a052 Mon Sep 17 00:00:00 2001 From: Abdul Zahid Date: Wed, 9 Oct 2024 22:15:09 +0700 Subject: [PATCH 11/11] Persist Discover/Observability Logs Explorer tab selection based on last visited status instead of last tab clicked status. --- .../logs_explorer_tabs.test.tsx | 16 ++++++---------- .../logs_explorer_tabs/logs_explorer_tabs.tsx | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx b/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx index 740565ed3efcb..e353fe1971ec9 100644 --- a/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx +++ b/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx @@ -21,8 +21,8 @@ import { DISCOVER_APP_LOCATOR } from '../../../common'; const mockSetLastUsedViewer = jest.fn(); jest.mock('react-use/lib/useLocalStorage', () => { - return jest.fn((key: string, initialValue: string) => { - return [initialValue, mockSetLastUsedViewer]; + return jest.fn((key: string, _initialValue: string) => { + return [undefined, mockSetLastUsedViewer]; // Always use undefined as the initial value }); }); @@ -99,17 +99,13 @@ describe('LogsExplorerTabs', () => { expect(mockDiscoverLocator.navigate).toHaveBeenCalledWith({}); }); - it('should update the last used viewer in local storage', async () => { + it('should update the last used viewer in local storage for selectedTab', async () => { const { unmount } = renderTabs('discover'); - - mockSetLastUsedViewer.mockClear(); - await userEvent.click(getLogsExplorerTab()); - expect(mockSetLastUsedViewer).toHaveBeenCalledWith(OBSERVABILITY_LOGS_EXPLORER_APP_ID); + expect(mockSetLastUsedViewer).toHaveBeenCalledWith(DISCOVER_APP_ID); unmount(); - renderTabs('logs-explorer'); mockSetLastUsedViewer.mockClear(); - await userEvent.click(getDiscoverTab()); - expect(mockSetLastUsedViewer).toHaveBeenCalledWith(DISCOVER_APP_ID); + renderTabs('logs-explorer'); + expect(mockSetLastUsedViewer).toHaveBeenCalledWith(OBSERVABILITY_LOGS_EXPLORER_APP_ID); }); }); diff --git a/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx b/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx index 83b387e8db8ce..c7082c21344ac 100644 --- a/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx +++ b/src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx @@ -16,7 +16,7 @@ import { OBSERVABILITY_LOGS_EXPLORER_APP_ID, } from '@kbn/deeplinks-observability'; import { i18n } from '@kbn/i18n'; -import React, { MouseEvent } from 'react'; +import React, { MouseEvent, useEffect } from 'react'; import useLocalStorage from 'react-use/lib/useLocalStorage'; import { DiscoverAppLocatorParams, DISCOVER_APP_LOCATOR } from '../../../common'; import type { DiscoverServices } from '../../build_services'; @@ -36,24 +36,32 @@ export const LogsExplorerTabs = ({ services, selectedTab }: LogsExplorerTabsProp const discoverUrl = discoverLocator?.getRedirectUrl(emptyParams); const logsExplorerUrl = logsExplorerLocator?.getRedirectUrl(emptyParams); - const [_, setLastUsedViewer] = useLocalStorage< + const [lastUsedViewer, setLastUsedViewer] = useLocalStorage< typeof DISCOVER_APP_ID | typeof OBSERVABILITY_LOGS_EXPLORER_APP_ID >(OBS_LOGS_EXPLORER_LOGS_VIEWER_KEY, OBSERVABILITY_LOGS_EXPLORER_APP_ID); const navigateToDiscover = createNavigateHandler(() => { if (selectedTab !== 'discover') { - setLastUsedViewer(DISCOVER_APP_ID); discoverLocator?.navigate(emptyParams); } }); const navigateToLogsExplorer = createNavigateHandler(() => { if (selectedTab !== 'logs-explorer') { - setLastUsedViewer(OBSERVABILITY_LOGS_EXPLORER_APP_ID); logsExplorerLocator?.navigate(emptyParams); } }); + useEffect(() => { + if (selectedTab === 'discover' && lastUsedViewer !== DISCOVER_APP_ID) { + setLastUsedViewer(DISCOVER_APP_ID); + } + + if (selectedTab === 'logs-explorer' && lastUsedViewer !== OBSERVABILITY_LOGS_EXPLORER_APP_ID) { + setLastUsedViewer(OBSERVABILITY_LOGS_EXPLORER_APP_ID); + } + }, [setLastUsedViewer, lastUsedViewer, selectedTab]); + return (