Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
e48a8b1
Migrate actions to context awareness toolkit
davismcphee Feb 14, 2026
2b506f1
Clean up approach to pass toolkit to scoped profiles manager
davismcphee Feb 15, 2026
291f3ee
Use toolkit factory
davismcphee Feb 15, 2026
3bb1518
Cleanup implementation
davismcphee Feb 15, 2026
e1bfb02
Update addFilter handling
davismcphee Feb 15, 2026
374be0e
Get rid of toolkit provider
davismcphee Feb 15, 2026
30a612a
Consolidate updateAdHocDataViews
davismcphee Feb 15, 2026
c42903e
Update context app toolkit approach
davismcphee Feb 16, 2026
85a5934
Fix embeddable filter handling
davismcphee Feb 16, 2026
36819ea
Cleanup
davismcphee Feb 16, 2026
b60962e
Fix alert flyout
davismcphee Feb 16, 2026
8efa1e0
Fix setExpandedDoc support for context and embeddable
davismcphee Feb 16, 2026
9bd8f62
Fix doc viewer scrolling
davismcphee Feb 17, 2026
02c0908
Merge branch 'main' into discover-context-awareness-toolkit
davismcphee Mar 7, 2026
3f87773
Tiny test cleanup
davismcphee Mar 7, 2026
98c7bd8
Simplify profiles manager changes
davismcphee Mar 7, 2026
4da0da7
Jest tests
davismcphee Mar 7, 2026
d3a3fa0
Fix Jest test and some async thunks
davismcphee Mar 7, 2026
3a6d462
Remove dev docs
davismcphee Mar 7, 2026
e55d287
Merge branch 'main' into discover-context-awareness-toolkit
davismcphee May 21, 2026
6aa8010
Merge branch 'main' into discover-context-awareness-toolkit
davismcphee May 22, 2026
8e8669f
Remove AdditionalCellActionsParams
davismcphee May 22, 2026
20e5edd
Update useStableCallback to be safe, and migrate addFilter to useStab…
davismcphee May 22, 2026
2f313e8
Add comments
davismcphee May 22, 2026
925ddac
Use useStableCallback for setExpandedDoc too
davismcphee May 22, 2026
1dcdd2a
Fix generateFilters calls
davismcphee May 22, 2026
c4331fb
Fix enableFilters embeddable handling
davismcphee May 22, 2026
6afb143
Changes from node scripts/lint_ts_projects --fix
kibanamachine May 22, 2026
822aea8
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine May 22, 2026
83ecde0
Docs updates
davismcphee May 22, 2026
631d17e
Revert external doc viewer changes in embeddable
davismcphee May 22, 2026
b04ee8c
Cleanup
davismcphee May 23, 2026
e925067
Merge branch 'main' into discover-context-awareness-toolkit
davismcphee May 23, 2026
d07e1de
Merge branch 'main' into discover-context-awareness-toolkit
davismcphee May 24, 2026
7266821
Merge branch 'main' into discover-context-awareness-toolkit
davismcphee May 26, 2026
ac73760
Merge branch 'main' into discover-context-awareness-toolkit
davismcphee May 26, 2026
e0892c8
Merge branch 'main' into discover-context-awareness-toolkit
davismcphee May 28, 2026
e29ade1
Merge branch 'main' into discover-context-awareness-toolkit
davismcphee May 29, 2026
c29e261
Merge branch 'main' into discover-context-awareness-toolkit
davismcphee May 29, 2026
18dcceb
Fix merge issues
davismcphee May 29, 2026
0901c9e
Merge branch 'main' into discover-context-awareness-toolkit
davismcphee May 29, 2026
eb9d179
Merge branch 'main' into discover-context-awareness-toolkit
davismcphee May 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { useEffect, useRef } from 'react';
import { useState } from 'react';
import useLatest from 'react-use/lib/useLatest';

/**
* Accepts a callback and returns a function with a stable identity
* that will always call the latest version of the callback when invoked
*/
export const useStableCallback = <T extends (...args: never[]) => unknown>(fn: T | undefined) => {
const ref = useRef(fn);
export const useStableCallback = <T extends (...args: Parameters<T>) => ReturnType<T>>(fn: T) => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally wrote this hook a long time ago as part of Unified Histogram, and it was moved to this package a few weeks ago to share. I reimplemented the pattern for part of this PR and a bot suggested I use this instead, which makes sense. But the original version isn't totally safe due to timing around useEffect, so I wanted to update it to a better pattern. I can extract this into a separate PR if there are concerns.

const latestFn = useLatest(fn);
const [stableFn] = useState(() => {
return ((...args: Parameters<T>) => latestFn.current(...args)) as T;
});

useEffect(() => {
ref.current = fn;
}, [fn]);

return useRef((...args: Parameters<T>) => ref.current?.(...args)).current;
return stableFn;
};
1 change: 1 addition & 0 deletions src/platform/plugins/shared/discover/moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ dependsOn:
- '@kbn/reporting-public'
- '@kbn/logging'
- '@kbn/logging-mocks'
- '@kbn/react-hooks'
tags:
- plugin
- prod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
DiscoverCustomizationProvider,
type DiscoverCustomizationService,
} from '../customizations';
import { type ScopedProfilesManager } from '../context_awareness';
import { EMPTY_CONTEXT_AWARENESS_TOOLKIT, type ScopedProfilesManager } from '../context_awareness';
import type { DiscoverServices } from '../build_services';
import { createDiscoverServicesMock } from './services';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
Expand Down Expand Up @@ -121,7 +121,10 @@ export const DiscoverTestProvider = ({
const scopedProfilesManager = useMemo(
() =>
originalScopedProfilesManager ??
services.profilesManager.createScopedProfilesManager({ scopedEbtManager }),
services.profilesManager.createScopedProfilesManager({
scopedEbtManager,
toolkit: EMPTY_CONTEXT_AWARENESS_TOOLKIT,
}),
[originalScopedProfilesManager, scopedEbtManager, services.profilesManager]
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import userEvent from '@testing-library/user-event';
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
import { setUnifiedDocViewerServices } from '@kbn/unified-doc-viewer-plugin/public/plugin';
import { mockUnifiedDocViewerServices } from '@kbn/unified-doc-viewer-plugin/public/__mocks__';
import { DocViewsRegistry } from '@kbn/unified-doc-viewer';
import { DocViewsRegistry, type DocViewerApi } from '@kbn/unified-doc-viewer';
import { ContextApp, type ContextAppProps } from './context_app';
import { createDiscoverServicesMock } from '../../__mocks__/services';
import { DiscoverTestProvider } from '../../__mocks__/test_provider';
Expand All @@ -30,8 +30,6 @@ jest.mock('./hooks/use_context_app_fetch');
jest.mock('./hooks/use_context_app_state');

const services = createDiscoverServicesMock();
const addFiltersMock = jest.spyOn(services.filterManager, 'addFilters');
const updateSavedObjectMock = jest.spyOn(services.dataViews, 'updateSavedObject');
const mockUseContextAppFetch = jest.mocked(useContextAppFetch);
const mockUseContextAppState = jest.mocked(useContextAppState);

Expand Down Expand Up @@ -91,15 +89,44 @@ const setDocViewerRegistry = (render: (props: DocViewRenderProps) => React.React
};

describe('ContextApp test', () => {
const addFilterMock = jest.fn();
const setExpandedDocMock = jest.fn();
const docViewerRef = React.createRef<DocViewerApi>();
const defaultProps: ContextAppProps = {
dataView: dataViewMock,
anchorId: 'mocked_anchor_id',
addFilter: addFilterMock,
expandedDoc: undefined,
initialDocViewerTabId: undefined,
docViewerRef,
setExpandedDoc: setExpandedDocMock,
};

const renderComponent = () => {
const StatefulContextApp = () => {
const [expandedDoc, setExpandedDocState] = React.useState(defaultProps.expandedDoc);
const [initialDocViewerTabId, setInitialDocViewerTabId] = React.useState(
defaultProps.initialDocViewerTabId
);
const setExpandedDoc: ContextAppProps['setExpandedDoc'] = (doc, options) => {
setExpandedDocMock(doc, options);
setExpandedDocState(doc);
setInitialDocViewerTabId(options?.initialTabId);
};

return (
<ContextApp
{...defaultProps}
expandedDoc={expandedDoc}
initialDocViewerTabId={initialDocViewerTabId}
setExpandedDoc={setExpandedDoc}
/>
);
};

renderWithKibanaRenderContext(
<DiscoverTestProvider services={services}>
<ContextApp {...defaultProps} />
<StatefulContextApp />
</DiscoverTestProvider>
);
};
Expand Down Expand Up @@ -148,7 +175,7 @@ describe('ContextApp test', () => {
);
});

it('should set filters correctly', async () => {
it('should call addFilter from the doc viewer', async () => {
const user = userEvent.setup();

setDocViewerRegistry(({ filter }) => (
Expand All @@ -168,15 +195,7 @@ describe('ContextApp test', () => {

await user.click(await screen.findByTestId('docViewFilterButton'));

expect(addFiltersMock).toHaveBeenCalledTimes(1);
expect(addFiltersMock).toHaveBeenCalledWith([
{
$state: { store: 'appState' },
meta: { alias: null, disabled: false, index: 'the-data-view-id', negate: false },
query: { match_phrase: { extension: 'jpg' } },
},
]);
expect(updateSavedObjectMock).toHaveBeenCalledTimes(1);
expect(updateSavedObjectMock).toHaveBeenCalledWith(dataViewMock, 0, true);
expect(addFilterMock).toHaveBeenCalledTimes(1);
expect(addFilterMock).toHaveBeenCalledWith('extension', 'jpg', '+');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ import {
import { css } from '@emotion/react';
import { cloneDeep } from 'lodash';
import { useMemoCss } from '@kbn/css-utils/public/use_memo_css';
import type { DataView, DataViewField } from '@kbn/data-views-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
import { useExecutionContext } from '@kbn/kibana-react-plugin/public';
import { generateFilters } from '@kbn/data-plugin/public';
import { i18n } from '@kbn/i18n';
import { SORT_DEFAULT_ORDER_SETTING } from '@kbn/discover-utils';
import type { UseColumnsProps } from '@kbn/unified-data-table';
import { popularizeField, useColumns } from '@kbn/unified-data-table';
import { useColumns } from '@kbn/unified-data-table';
import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import type { DiscoverGridSettings } from '@kbn/saved-search-plugin/common';
import { kbnFullBodyHeightCss } from '@kbn/css-utils/public/full_body_height_css';
import type { DataTableRecord } from '@kbn/discover-utils/types';
import type { DocViewerApi } from '@kbn/unified-doc-viewer';
import { ContextErrorMessage } from './components/context_error_message';
import { LoadingStatus } from './services/context_query_state';
import type { AppState, GlobalState } from './services/context_state';
Expand All @@ -48,23 +49,29 @@ export interface ContextAppProps {
dataView: DataView;
anchorId: string;
referrer?: string;
addFilter: DocViewFilterFn;
expandedDoc: DataTableRecord | undefined;
initialDocViewerTabId: string | undefined;
docViewerRef: React.RefObject<DocViewerApi>;
setExpandedDoc: (doc: DataTableRecord | undefined, options?: { initialTabId?: string }) => void;
}

export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) => {
export const ContextApp = ({
dataView,
anchorId,
referrer,
addFilter,
expandedDoc,
initialDocViewerTabId,
docViewerRef,
setExpandedDoc,
}: ContextAppProps) => {
const styles = useMemoCss(componentStyles);

const services = useDiscoverServices();
const { scopedEBTManager } = useScopedServices();
const {
locator,
uiSettings,
capabilities,
dataViews,
navigation,
filterManager,
core,
fieldsMetadata,
} = services;
const { locator, uiSettings, capabilities, dataViews, navigation, core, fieldsMetadata } =
services;

/**
* Context app state
Expand Down Expand Up @@ -199,23 +206,6 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
]
);

const addFilter = useCallback(
async (field: DataViewField | string, values: unknown, operation: '+' | '-') => {
const newFilters = generateFilters(filterManager, field, values, operation, dataView);
filterManager.addFilters(newFilters);
if (dataViews) {
const fieldName = typeof field === 'string' ? field : field.name;
await popularizeField(dataView, fieldName, dataViews, capabilities);
void scopedEBTManager.trackFilterAddition({
fieldName: fieldName === '_exists_' ? String(values) : fieldName,
filterOperation: fieldName === '_exists_' ? '_exists_' : operation,
fieldsMetadata,
});
}
},
[filterManager, dataView, dataViews, capabilities, scopedEBTManager, fieldsMetadata]
);

const onAddColumnWithTracking = useCallback(
(columnName: string) => {
onAddColumn(columnName);
Expand Down Expand Up @@ -289,7 +279,11 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
predecessorCount={appState.predecessorCount}
successorCount={appState.successorCount}
setAppState={stateContainer.setAppState}
addFilter={addFilter as DocViewFilterFn}
addFilter={addFilter}
expandedDoc={expandedDoc}
initialDocViewerTabId={initialDocViewerTabId}
docViewerRef={docViewerRef}
setExpandedDoc={setExpandedDoc}
rows={rows}
predecessors={fetchedState.predecessors}
successors={fetchedState.successors}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import type { FC } from 'react';
import React, { Fragment, useCallback, useMemo, useRef, useState } from 'react';
import React, { Fragment, useCallback, useMemo } from 'react';
import { EuiSpacer, useEuiPaddingSize } from '@elastic/eui';
import { css } from '@emotion/react';
import type { DataView } from '@kbn/data-views-plugin/public';
Expand Down Expand Up @@ -67,6 +67,10 @@ export interface ContextAppContentProps {
interceptedWarnings: SearchResponseWarning[];
setAppState: (newState: Partial<AppState>) => void;
addFilter: DocViewFilterFn;
expandedDoc: DataTableRecord | undefined;
initialDocViewerTabId: string | undefined;
docViewerRef: React.RefObject<DocViewerApi>;
setExpandedDoc: (doc: DataTableRecord | undefined, options?: { initialTabId?: string }) => void;
}

const controlColumnIds = ['openDetails'];
Expand Down Expand Up @@ -95,25 +99,14 @@ export function ContextAppContent({
interceptedWarnings,
setAppState,
addFilter,
expandedDoc,
initialDocViewerTabId,
docViewerRef,
setExpandedDoc,
}: ContextAppContentProps) {
const { uiSettings: config, uiActions } = useDiscoverServices();
const services = useDiscoverServices();

const [expandedDoc, setExpandedDoc] = useState<DataTableRecord | undefined>();
const [initialTabId, setInitialTabId] = useState<string | undefined>(undefined);
const docViewerRef = useRef<DocViewerApi>(null);

const setExpandedDocWithInitialTab = useCallback(
(doc: DataTableRecord | undefined, options?: { initialTabId?: string }) => {
setExpandedDoc(doc);
setInitialTabId(options?.initialTabId);
if (options?.initialTabId) {
docViewerRef.current?.setSelectedTabId(options.initialTabId);
}
},
[]
);

const isAnchorLoading =
anchorStatus === LoadingStatus.LOADING || anchorStatus === LoadingStatus.UNINITIALIZED;
const arePredecessorsLoading =
Expand Down Expand Up @@ -152,12 +145,20 @@ export function ContextAppContent({
onRemoveColumn={onRemoveColumn}
onAddColumn={onAddColumn}
onClose={() => setExpandedDoc(undefined)}
initialTabId={initialTabId}
setExpandedDoc={setExpandedDocWithInitialTab}
initialTabId={initialDocViewerTabId}
setExpandedDoc={setExpandedDoc}
docViewerRef={docViewerRef}
/>
),
[addFilter, dataView, onAddColumn, onRemoveColumn, setExpandedDocWithInitialTab, initialTabId]
[
addFilter,
dataView,
docViewerRef,
initialDocViewerTabId,
onAddColumn,
onRemoveColumn,
setExpandedDoc,
]
);

const onResize = useCallback<NonNullable<UnifiedDataTableProps['onResize']>>(
Expand All @@ -172,7 +173,6 @@ export function ContextAppContent({
const cellRenderers = useMemo(() => {
const getCellRenderers = getCellRenderersAccessor(() => ({}));
return getCellRenderers({
actions: { addFilter },
dataView,
density: getDataGridDensity(services.storage, 'discover'),
rowHeight: getRowHeight({
Expand All @@ -181,7 +181,7 @@ export function ContextAppContent({
configRowHeight,
}),
});
}, [addFilter, configRowHeight, dataView, getCellRenderersAccessor, services.storage]);
}, [configRowHeight, dataView, getCellRenderersAccessor, services.storage]);

const dataSource = useMemo(() => createDataSource({ dataView, query: undefined }), [dataView]);
const { filters } = useQuerySubscriber({ data: services.data });
Expand Down Expand Up @@ -236,7 +236,7 @@ export function ContextAppContent({
isPaginationEnabled={false}
rowsPerPageState={getDefaultRowsPerPage(services.uiSettings)}
controlColumnIds={controlColumnIds}
setExpandedDoc={setExpandedDocWithInitialTab}
setExpandedDoc={setExpandedDoc}
onFilter={addFilter}
onSetColumns={onSetColumns}
configRowHeight={configRowHeight}
Expand Down
Loading
Loading