Skip to content

Commit

Permalink
Remember tab choice between logs explorer and discover (elastic#194930)
Browse files Browse the repository at this point in the history
Closes elastic#193321 

## Summary

The PR adds the redirection point when "Discover" menu item is clicked
on the sidenav in serverless (or solution nav on stateful). Based on
what tab between "Discover" or "Logs Explorer" the user clicked
recently, "Discover" will point to that app/tab. Previously, "Discover"
would always point to "Logs Explorer" on serverless and to "Discover" on
stateful.

In order to implement this, a temporary app `last-used-logs-viewer` is
registered in `observability-logs-explorer` plugin whose only job is to
read the last stored value in local storage and perform the redirection.

Doing the redirection from a temporary app should help prevent
triggering unnecessary telemetry and history entries. And it should be
fairly easy to undo once context aware redirection is in place.

~With this implementation, only the behavior of user clicking "Discover"
on the sidenav and clicking the tabs is affected and any deeplinks from
other apps or direct links should work as is.~ The tab choice will be
updated even if the apps are visited via url.



https://github.com/user-attachments/assets/8a0308db-9ddb-47b6-b1a5-8ed70662040d

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
awahab07 and kibanamachine authored Oct 14, 2024
1 parent c53b2a8 commit fed9a19
Show file tree
Hide file tree
Showing 14 changed files with 287 additions and 10 deletions.
3 changes: 3 additions & 0 deletions packages/deeplinks/observability/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 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';

export const METRICS_APP_ID = 'metrics';
Expand Down
3 changes: 3 additions & 0 deletions packages/deeplinks/observability/deep_links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -38,6 +40,7 @@ type InventoryApp = typeof INVENTORY_APP_ID;
export type AppId =
| LogsApp
| ObservabilityLogsExplorerApp
| LastUsedLogsViewerApp
| ObservabilityOverviewApp
| ObservabilityOnboardingApp
| ApmApp
Expand Down
1 change: 1 addition & 0 deletions packages/deeplinks/observability/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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 [undefined, mockSetLastUsedViewer]; // Always use undefined as the initial value
});
});

const createMockLocator = (id: string) => ({
navigate: jest.fn(),
Expand Down Expand Up @@ -46,11 +57,12 @@ describe('LogsExplorerTabs', () => {
},
} as unknown as typeof discoverServiceMock;

render(<LogsExplorerTabs services={services} selectedTab={selectedTab} />);
const { unmount } = render(<LogsExplorerTabs services={services} selectedTab={selectedTab} />);

return {
mockDiscoverLocator,
mockLogsExplorerLocator,
unmount,
};
};

Expand Down Expand Up @@ -86,4 +98,14 @@ describe('LogsExplorerTabs', () => {
await userEvent.click(getDiscoverTab());
expect(mockDiscoverLocator.navigate).toHaveBeenCalledWith({});
});

it('should update the last used viewer in local storage for selectedTab', async () => {
const { unmount } = renderTabs('discover');
expect(mockSetLastUsedViewer).toHaveBeenCalledWith(DISCOVER_APP_ID);

unmount();
mockSetLastUsedViewer.mockClear();
renderTabs('logs-explorer');
expect(mockSetLastUsedViewer).toHaveBeenCalledWith(OBSERVABILITY_LOGS_EXPLORER_APP_ID);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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 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';

Expand All @@ -29,6 +36,10 @@ export const LogsExplorerTabs = ({ services, selectedTab }: LogsExplorerTabsProp
const discoverUrl = discoverLocator?.getRedirectUrl(emptyParams);
const logsExplorerUrl = logsExplorerLocator?.getRedirectUrl(emptyParams);

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') {
discoverLocator?.navigate(emptyParams);
Expand All @@ -41,6 +52,16 @@ export const LogsExplorerTabs = ({ services, selectedTab }: LogsExplorerTabsProp
}
});

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 (
<EuiTabs bottomBorder={false} data-test-subj="logsExplorerTabs">
<EuiTab
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
131 changes: 131 additions & 0 deletions src/plugins/telemetry/schema/oss_plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,22 @@ 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',
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',
},
],
},
],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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(
<Router history={appParams.history}>
<LastUsedLogsViewerRedirect core={core} />
</Router>,
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 <></>;
};
Loading

0 comments on commit fed9a19

Please sign in to comment.