Skip to content

Commit 935d25c

Browse files
authored
[Discover] Moves the inspector menu item to the tab menu (elastic#258767)
## Summary Closes elastic#257958 Moves the inspector to the tab menu as it makes more sense to be there. <img width="672" height="369" alt="image" src="https://github.com/user-attachments/assets/411bef59-fe09-4481-ace6-f4ae61ea347e" /> I am still showing the inspect at the top nav for the by value sessions. ### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
1 parent a5f4846 commit 935d25c

27 files changed

Lines changed: 416 additions & 110 deletions

File tree

src/platform/packages/shared/kbn-unified-tabs/src/components/tabbed_content/tabbed_content.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ export interface TabbedContentProps
6363
onEBTEvent: (event: TabsEBTEvent) => void;
6464
tabContentIdOverride?: string;
6565
appendRight?: React.ReactNode;
66-
/** Optional function to provide additional menu items for tabs */
66+
/** Optional function to provide menu items placed after rename/duplicate */
67+
getTopTabMenuItems?: (item: TabItem) => TabMenuItem[];
68+
/** Optional function to provide additional menu items placed at the end of the menu */
6769
getAdditionalTabMenuItems?: (item: TabItem) => TabMenuItem[];
6870
}
6971

@@ -107,6 +109,7 @@ export const TabbedContent: React.FC<TabbedContentProps> = ({
107109
disableDragAndDrop = false,
108110
disableTabsBarMenu = false,
109111
appendRight,
112+
getTopTabMenuItems,
110113
getAdditionalTabMenuItems,
111114
}) => {
112115
const { euiTheme } = useEuiTheme();
@@ -396,6 +399,7 @@ export const TabbedContent: React.FC<TabbedContentProps> = ({
396399
onDuplicate,
397400
onCloseOtherTabs,
398401
onCloseTabsToTheRight,
402+
getTopTabMenuItems,
399403
getAdditionalTabMenuItems,
400404
});
401405
}, [
@@ -404,6 +408,7 @@ export const TabbedContent: React.FC<TabbedContentProps> = ({
404408
onDuplicate,
405409
onCloseOtherTabs,
406410
onCloseTabsToTheRight,
411+
getTopTabMenuItems,
407412
getAdditionalTabMenuItems,
408413
]);
409414

src/platform/packages/shared/kbn-unified-tabs/src/utils/get_tab_menu_items.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ export interface GetTabMenuItemsFnProps {
3939
onDuplicate: (item: TabItem) => void;
4040
onCloseOtherTabs: (item: TabItem) => void;
4141
onCloseTabsToTheRight: (item: TabItem) => void;
42-
/** Optional function to provide additional menu items for tabs */
42+
/** Optional function to provide menu items placed after the core tab menu items */
43+
getTopTabMenuItems?: (item: TabItem) => TabMenuItem[];
44+
/** Optional function to provide additional menu items placed at the end of the menu */
4345
getAdditionalTabMenuItems?: (item: TabItem) => TabMenuItem[];
4446
}
4547

@@ -49,6 +51,7 @@ export const getTabMenuItemsFn = ({
4951
onDuplicate,
5052
onCloseOtherTabs,
5153
onCloseTabsToTheRight,
54+
getTopTabMenuItems,
5255
getAdditionalTabMenuItems,
5356
}: GetTabMenuItemsFnProps): GetTabMenuItems => {
5457
return (item) => {
@@ -98,6 +101,11 @@ export const getTabMenuItemsFn = ({
98101
);
99102
}
100103

104+
const topItems = getTopTabMenuItems?.(item);
105+
if (topItems && topItems.length > 0) {
106+
items.push(...topItems);
107+
}
108+
101109
if (closeOtherTabsItem || closeTabsToTheRightItem) {
102110
if (items.length > 0) {
103111
items.push(DividerMenuItem);

src/platform/plugins/shared/discover/public/application/main/components/tabs_view/tabs_view.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,13 @@ export const TabsView = (props: SingleTabViewProps) => {
4040
const currentDataView = useCurrentTabRuntimeState((tab) => tab.currentDataView$);
4141
const scopedEbtManager = useCurrentTabRuntimeState((tab) => tab.scopedEbtManager$);
4242

43-
const { shouldCollapseAppMenu, onResize, getAdditionalTabMenuItems, topNavMenuItems } =
44-
useAppMenuData({ currentDataView });
43+
const {
44+
shouldCollapseAppMenu,
45+
onResize,
46+
getTopTabMenuItems,
47+
getAdditionalTabMenuItems,
48+
topNavMenuItems,
49+
} = useAppMenuData({ currentDataView });
4550

4651
const onEvent: UnifiedTabsProps['onEBTEvent'] = useCallback(
4752
(event) => {
@@ -92,6 +97,7 @@ export const TabsView = (props: SingleTabViewProps) => {
9297
onChanged={onChanged}
9398
onEBTEvent={onEvent}
9499
onClearRecentlyClosed={onClearRecentlyClosed}
100+
getTopTabMenuItems={getTopTabMenuItems}
95101
getAdditionalTabMenuItems={getAdditionalTabMenuItems}
96102
appendRight={
97103
<AppMenuComponent config={topNavMenuItems} isCollapsed={shouldCollapseAppMenu} />

src/platform/plugins/shared/discover/public/application/main/components/tabs_view/use_app_menu_data.test.tsx

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,23 @@ describe('useAppMenuData', () => {
8080
});
8181
});
8282

83-
it('does not add "Switch to ES|QL" when ES|QL is disabled', async () => {
83+
it('does not add "Switch to ES|QL" when ES|QL is disabled but still shows inspect', async () => {
8484
const { toolkit, hook } = await setup({ enableEsql: false });
8585
const items = hook.result.current.getAdditionalTabMenuItems?.(toolkit.getCurrentTab());
8686

87-
expect(items).toEqual([]);
87+
expect(items).not.toContainEqual(
88+
expect.objectContaining({
89+
'data-test-subj': 'unifiedTabs_tabMenuItem_switchToESQL',
90+
})
91+
);
92+
93+
const topItems = hook.result.current.getTopTabMenuItems?.(toolkit.getCurrentTab());
94+
95+
expect(topItems).toContainEqual(
96+
expect.objectContaining({
97+
'data-test-subj': 'unifiedTabs_tabMenuItem_inspect',
98+
})
99+
);
88100
});
89101

90102
it('keeps "Switch to classic" for the current tab when in ES|QL mode', async () => {
@@ -124,4 +136,23 @@ describe('useAppMenuData', () => {
124136

125137
expect(items).toEqual([]);
126138
});
139+
140+
it('adds "Inspect" menu item for the current tab', async () => {
141+
const { toolkit, hook } = await setup();
142+
const items = hook.result.current.getTopTabMenuItems?.(toolkit.getCurrentTab());
143+
144+
expect(items).toContainEqual(
145+
expect.objectContaining({
146+
'data-test-subj': 'unifiedTabs_tabMenuItem_inspect',
147+
name: 'inspect',
148+
label: 'Inspect',
149+
})
150+
);
151+
152+
const inspectItem = items?.find((menuItem): menuItem is TabMenuItemWithClick => {
153+
return menuItem !== 'divider' && menuItem.name === 'inspect';
154+
});
155+
156+
expect(inspectItem?.onClick).toBeInstanceOf(Function);
157+
});
127158
});

src/platform/plugins/shared/discover/public/application/main/components/tabs_view/use_app_menu_data.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@ import {
2121
useInternalStateSelector,
2222
useCurrentTabAction,
2323
selectAllTabs,
24+
useRuntimeStateManager,
25+
selectTabRuntimeState,
2426
} from '../../state_management/redux';
2527
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
2628
import { ESQL_TRANSITION_MODAL_KEY } from '../../../../../common/constants';
2729
import { useTopNavMenuItems } from '../top_nav/use_top_nav_menu_items';
2830
import { isDataViewSource } from '../../../../../common/data_sources';
31+
import { getInspectorRequestAdapters } from '../../hooks/use_inspector';
32+
import { createContextsAdapter } from '../../../../context_awareness/hooks';
2933

3034
const APP_MENU_COLLAPSE_THRESHOLD = 800;
3135

@@ -36,6 +40,7 @@ interface UseAppMenuDataParams {
3640
interface UseAppMenuDataResult {
3741
shouldCollapseAppMenu: boolean;
3842
onResize: EuiResizeObserverProps['onResize'];
43+
getTopTabMenuItems: UnifiedTabsProps['getTopTabMenuItems'];
3944
getAdditionalTabMenuItems: UnifiedTabsProps['getAdditionalTabMenuItems'];
4045
topNavMenuItems: AppMenuConfig | undefined;
4146
}
@@ -49,6 +54,7 @@ export const useAppMenuData = ({ currentDataView }: UseAppMenuDataParams): UseAp
4954
const persistedDiscoverSession = useInternalStateSelector(
5055
(state) => state.persistedDiscoverSession
5156
);
57+
const runtimeStateManager = useRuntimeStateManager();
5258
const [shouldCollapseAppMenu, setShouldCollapseAppMenu] = useState(false);
5359

5460
const transitionFromDataViewToESQL = useCurrentTabAction(
@@ -57,12 +63,79 @@ export const useAppMenuData = ({ currentDataView }: UseAppMenuDataParams): UseAp
5763
const transitionFromESQLToDataView = useCurrentTabAction(
5864
internalStateActions.transitionFromESQLToDataView
5965
);
66+
const setExpandedDoc = useCurrentTabAction(internalStateActions.setExpandedDoc);
6067

6168
const onResize: EuiResizeObserverProps['onResize'] = useCallback((dimensions) => {
6269
if (!dimensions) return;
6370
setShouldCollapseAppMenu(dimensions.width < APP_MENU_COLLAPSE_THRESHOLD);
6471
}, []);
6572

73+
const getTopTabMenuItems = useCallback<NonNullable<UnifiedTabsProps['getTopTabMenuItems']>>(
74+
(item) => {
75+
const tab = allTabs.find((t) => t.id === item.id);
76+
const isCurrentTab = tab?.id === currentTabId;
77+
78+
if (!isCurrentTab || !currentDataView) {
79+
return [];
80+
}
81+
82+
return [
83+
{
84+
'data-test-subj': 'unifiedTabs_tabMenuItem_inspect',
85+
name: 'inspect',
86+
label: i18n.translate('discover.localMenu.inspectTitle', {
87+
defaultMessage: 'Inspect',
88+
}),
89+
onClick: () => {
90+
dispatch(setExpandedDoc({ expandedDoc: undefined }));
91+
92+
const tabRuntimeState = selectTabRuntimeState(runtimeStateManager, tab.id);
93+
const dataStateContainer = tabRuntimeState?.dataStateContainer$.getValue();
94+
95+
if (!dataStateContainer || !tabRuntimeState) {
96+
return;
97+
}
98+
99+
const cascadedDocumentsFetcher = tabRuntimeState.cascadedDocumentsFetcher$.getValue();
100+
const scopedProfilesManager = tabRuntimeState.scopedProfilesManager$.getValue();
101+
const getContextsAdapter = createContextsAdapter({
102+
scopedProfilesManager,
103+
dataDocuments$: dataStateContainer.data$.documents$,
104+
});
105+
106+
const requestAdapters = getInspectorRequestAdapters(
107+
dataStateContainer,
108+
cascadedDocumentsFetcher
109+
);
110+
111+
const session = services.inspector.open(
112+
{
113+
requests: requestAdapters,
114+
contexts: getContextsAdapter({
115+
onOpenDocDetails: (record) => {
116+
session?.close();
117+
dispatch(setExpandedDoc({ expandedDoc: record }));
118+
},
119+
}),
120+
},
121+
{ title: persistedDiscoverSession?.title }
122+
);
123+
},
124+
},
125+
];
126+
},
127+
[
128+
allTabs,
129+
currentDataView,
130+
currentTabId,
131+
dispatch,
132+
persistedDiscoverSession,
133+
runtimeStateManager,
134+
services,
135+
setExpandedDoc,
136+
]
137+
);
138+
66139
// Provide "Switch to ES|QL" and "Switch to Classic" menu items for the selected tab
67140
const getAdditionalTabMenuItems = useCallback<
68141
NonNullable<UnifiedTabsProps['getAdditionalTabMenuItems']>
@@ -105,7 +178,6 @@ export const useAppMenuData = ({ currentDataView }: UseAppMenuDataParams): UseAp
105178
onClick: () => {
106179
services.trackUiMetric?.(METRIC_TYPE.CLICK, `esql:back_to_classic_clicked`);
107180

108-
// Determine if we should show the ES|QL to Data View transition modal
109181
const shouldShowESQLToDataViewTransitionModal =
110182
!persistedDiscoverSession || unsavedTabIds.includes(tab.id);
111183

@@ -139,6 +211,7 @@ export const useAppMenuData = ({ currentDataView }: UseAppMenuDataParams): UseAp
139211
return {
140212
shouldCollapseAppMenu,
141213
onResize,
214+
getTopTabMenuItems,
142215
getAdditionalTabMenuItems,
143216
topNavMenuItems,
144217
};

src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ describe('Discover topnav component', () => {
146146
});
147147

148148
const itemIds = capturedTopNavMenu?.items?.map((item) => item.id) || [];
149-
expect(itemIds).toEqual(['inspect', 'new', 'open']);
149+
expect(itemIds).toEqual(['new', 'open']);
150150
expect(capturedTopNavMenu?.primaryActionItem?.id).toBe('save');
151151
});
152152

@@ -157,7 +157,7 @@ describe('Discover topnav component', () => {
157157
});
158158

159159
const itemIds = capturedTopNavMenu?.items?.map((item) => item.id) || [];
160-
expect(itemIds).toEqual(['inspect', 'new', 'open']);
160+
expect(itemIds).toEqual(['new', 'open']);
161161
expect(capturedTopNavMenu?.primaryActionItem).toBeUndefined();
162162
});
163163

src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type { ESQLEditorRestorableState } from '@kbn/esql-editor';
1818
import { useESQLQueryStats } from '@kbn/esql/public';
1919
import { type Query, type TimeRange, type AggregateQuery } from '@kbn/es-query';
2020
import type { DataViewPickerProps, UnifiedSearchDraft } from '@kbn/unified-search-plugin/public';
21+
import type { DiscoverSession } from '@kbn/saved-search-plugin/common';
2122
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2223
import { ESQL_TRANSITION_MODAL_KEY } from '../../../../../common/constants';
2324
import {
@@ -44,10 +45,47 @@ import {
4445
import { DiscoverTopNavMenu } from './discover_topnav_menu';
4546
import { ESQLToDataViewTransitionModal } from './esql_dataview_transition';
4647
import { onSaveDiscoverSession } from './save_discover_session';
47-
import { useDiscoverTopNav } from './use_discover_topnav';
48+
import {
49+
isDiscoverInspectorInTabMenu,
50+
useDiscoverTopNavWithInspector,
51+
useDiscoverTopNavWithoutInspector,
52+
} from './use_discover_topnav';
4853
import { useESQLVariables } from './use_esql_variables';
4954
import type { UpdateESQLQueryFn } from '../../../../context_awareness/types';
5055

56+
function DiscoverTopNavMenuSection({
57+
persistedDiscoverSession,
58+
}: {
59+
persistedDiscoverSession: DiscoverSession | undefined;
60+
}) {
61+
const services = useDiscoverServices();
62+
const customizationContext = useDiscoverCustomizationContext();
63+
if (isDiscoverInspectorInTabMenu(services, customizationContext)) {
64+
return <DiscoverTopNavMenuNoInspector persistedDiscoverSession={persistedDiscoverSession} />;
65+
}
66+
return <DiscoverTopNavMenuWithInspector persistedDiscoverSession={persistedDiscoverSession} />;
67+
}
68+
69+
function DiscoverTopNavMenuWithInspector({
70+
persistedDiscoverSession,
71+
}: {
72+
persistedDiscoverSession: DiscoverSession | undefined;
73+
}) {
74+
const { topNavBadges, topNavMenu } = useDiscoverTopNavWithInspector({ persistedDiscoverSession });
75+
return <DiscoverTopNavMenu topNavBadges={topNavBadges} topNavMenu={topNavMenu} />;
76+
}
77+
78+
function DiscoverTopNavMenuNoInspector({
79+
persistedDiscoverSession,
80+
}: {
81+
persistedDiscoverSession: DiscoverSession | undefined;
82+
}) {
83+
const { topNavBadges, topNavMenu } = useDiscoverTopNavWithoutInspector({
84+
persistedDiscoverSession,
85+
});
86+
return <DiscoverTopNavMenu topNavBadges={topNavBadges} topNavMenu={topNavMenu} />;
87+
}
88+
5189
export interface DiscoverTopNavProps {
5290
savedQuery?: string;
5391
esqlModeErrors?: Error;
@@ -236,10 +274,6 @@ export const DiscoverTopNav = ({
236274
[dataView.id, dispatch, services, getState, runtimeStateManager, transitionFromESQLToDataView]
237275
);
238276

239-
const { topNavBadges, topNavMenu } = useDiscoverTopNav({
240-
persistedDiscoverSession,
241-
});
242-
243277
const changeDataView = useCurrentTabAction(internalStateActions.changeDataView);
244278
const onChangeDataView = useCallback(
245279
(dataViewOrDataViewId: string | DataView) => {
@@ -341,7 +375,7 @@ export const DiscoverTopNav = ({
341375

342376
return (
343377
<span>
344-
<DiscoverTopNavMenu topNavBadges={topNavBadges} topNavMenu={topNavMenu} />
378+
<DiscoverTopNavMenuSection persistedDiscoverSession={persistedDiscoverSession} />
345379
<SearchBar
346380
useBackgroundSearchButton={
347381
customizationContext.displayMode !== 'embedded' &&

src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav_menu.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import useUnmount from 'react-use/lib/useUnmount';
2020
import type { AppMenuConfig } from '@kbn/core-chrome-app-menu-components';
2121
import type { ChromeBreadcrumbsBadge } from '@kbn/core-chrome-browser';
2222
import useObservable from 'react-use/lib/useObservable';
23-
import type { useDiscoverTopNav } from './use_discover_topnav';
23+
import type { DiscoverTopNavHookResult } from './use_discover_topnav';
2424
import type { DiscoverCustomizationContext } from '../../../../customizations';
2525
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
2626
import { getReadOnlyBadge } from '../../../discover_router';
@@ -82,10 +82,7 @@ export const DiscoverTopNavMenuProvider = ({
8282
);
8383
};
8484

85-
export const DiscoverTopNavMenu = ({
86-
topNavBadges,
87-
topNavMenu,
88-
}: ReturnType<typeof useDiscoverTopNav>) => {
85+
export const DiscoverTopNavMenu = ({ topNavBadges, topNavMenu }: DiscoverTopNavHookResult) => {
8986
const { topNavBadges$, topNavMenu$ } = useContext(discoverTopNavMenuContext);
9087

9188
useLayoutEffect(() => {

0 commit comments

Comments
 (0)