diff --git a/packages/content-management/table_list_view_table/src/table_list_view.test.tsx b/packages/content-management/table_list_view_table/src/table_list_view.test.tsx index 408d22bd48ecf..0fc1840f26613 100644 --- a/packages/content-management/table_list_view_table/src/table_list_view.test.tsx +++ b/packages/content-management/table_list_view_table/src/table_list_view.test.tsx @@ -781,6 +781,49 @@ describe('TableListView', () => { }); }); + describe('initialFilter', () => { + const setupInitialFilter = registerTestBed( + WithServices(TableListViewTable, { + getTagList: () => [ + { id: 'id-tag-foo', name: 'foo', type: 'tag', description: '', color: '' }, + ], + }), + { + defaultProps: { ...requiredProps }, + memoryRouter: { wrapComponent: true }, + } + ); + + test('should filter by tag passed as in initialFilter prop', async () => { + let testBed: TestBed; + + const initialFilter = 'tag:(tag-1)'; + const findItems = jest.fn().mockResolvedValue({ total: 0, hits: [] }); + + await act(async () => { + testBed = await setupInitialFilter({ + findItems, + initialFilter, + urlStateEnabled: false, + }); + }); + + const { component, find } = testBed!; + component.update(); + + const getSearchBoxValue = () => find('tableListSearchBox').props().defaultValue; + + const getLastCallArgsFromFindItems = () => + findItems.mock.calls[findItems.mock.calls.length - 1]; + + // The search bar should be updated + const expected = initialFilter; + const [searchTerm] = getLastCallArgsFromFindItems(); + expect(getSearchBoxValue()).toBe(expected); + expect(searchTerm).toBe(expected); + }); + }); + describe('url state', () => { let router: Router | undefined; @@ -1265,7 +1308,7 @@ describe('TableList', () => { it('reports the page data test subject', async () => { const setPageDataTestSubject = jest.fn(); - act(() => { + await act(async () => { setup({ setPageDataTestSubject }); }); diff --git a/packages/content-management/table_list_view_table/src/table_list_view_table.tsx b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx index 2b0307c5d6f8e..33ef91dc65e4b 100644 --- a/packages/content-management/table_list_view_table/src/table_list_view_table.tsx +++ b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx @@ -293,6 +293,15 @@ function TableListViewTableComp({ const isMounted = useRef(false); const fetchIdx = useRef(0); + /** + * The "onTableSearchChange()" handler has an async behavior. We want to be able to discard + * previsous search changes and only handle the last one. For that we keep a counter of the changes. + */ + const tableSearchChangeIdx = useRef(0); + /** + * We want to build the initial query + */ + const initialQueryInitialized = useRef(false); const { canEditAdvancedSettings, @@ -333,10 +342,7 @@ function TableListViewTableComp({ showDeleteModal: false, hasUpdatedAtMetadata: false, selectedIds: [], - searchQuery: - initialQuery !== undefined - ? { text: initialQuery, query: new Query(Ast.create([]), undefined, initialQuery) } - : { text: '', query: new Query(Ast.create([]), undefined, '') }, + searchQuery: { text: '', query: new Query(Ast.create([]), undefined, '') }, pagination: { pageIndex: 0, totalItemCount: 0, @@ -348,7 +354,7 @@ function TableListViewTableComp({ direction: 'asc', }, }), - [initialPageSize, initialQuery] + [initialPageSize] ); const [state, dispatch] = useReducer(reducer, initialState); @@ -614,12 +620,67 @@ function TableListViewTableComp({ // ------------ // Callbacks // ------------ + const buildQueryFromText = useCallback( + async (text: string) => { + let ast = Ast.create([]); + let termMatch = text; + + if (searchQueryParser) { + // Parse possible tags in the search text + const { + references, + referencesToExclude, + searchQuery: searchTerm, + } = await searchQueryParser(text); + + termMatch = searchTerm; + + if (references?.length || referencesToExclude?.length) { + const allTags = getTagList(); + + if (references?.length) { + references.forEach(({ id: refId }) => { + const tag = allTags.find(({ id }) => id === refId); + if (tag) { + ast = ast.addOrFieldValue('tag', tag.name, true, 'eq'); + } + }); + } + + if (referencesToExclude?.length) { + referencesToExclude.forEach(({ id: refId }) => { + const tag = allTags.find(({ id }) => id === refId); + if (tag) { + ast = ast.addOrFieldValue('tag', tag.name, false, 'eq'); + } + }); + } + } + } + + if (termMatch.trim() !== '') { + ast = ast.addClause({ type: 'term', value: termMatch, match: 'must' }); + } + + return new Query(ast, undefined, text); + }, + [getTagList, searchQueryParser] + ); + const onTableSearchChange = useCallback( (arg: { query: Query | null; queryText: string }) => { - const query = arg.query ?? new Query(Ast.create([]), undefined, arg.queryText); - updateQuery(query); + if (arg.query) { + updateQuery(arg.query); + } else { + const idx = tableSearchChangeIdx.current + 1; + buildQueryFromText(arg.queryText).then((query) => { + if (idx === tableSearchChangeIdx.current) { + updateQuery(query); + } + }); + } }, - [updateQuery] + [updateQuery, buildQueryFromText] ); const updateTableSortAndPagination = useCallback( @@ -815,47 +876,7 @@ function TableListViewTableComp({ // Update our Query instance based on the URL "s" text const updateQueryFromURL = async (text: string = '') => { - let ast = Ast.create([]); - let termMatch = text; - - if (searchQueryParser) { - // Parse possible tags in the search text - const { - references, - referencesToExclude, - searchQuery: searchTerm, - } = await searchQueryParser(text); - - termMatch = searchTerm; - - if (references?.length || referencesToExclude?.length) { - const allTags = getTagList(); - - if (references?.length) { - references.forEach(({ id: refId }) => { - const tag = allTags.find(({ id }) => id === refId); - if (tag) { - ast = ast.addOrFieldValue('tag', tag.name, true, 'eq'); - } - }); - } - - if (referencesToExclude?.length) { - referencesToExclude.forEach(({ id: refId }) => { - const tag = allTags.find(({ id }) => id === refId); - if (tag) { - ast = ast.addOrFieldValue('tag', tag.name, false, 'eq'); - } - }); - } - } - } - - if (termMatch.trim() !== '') { - ast = ast.addClause({ type: 'term', value: termMatch, match: 'must' }); - } - - const updatedQuery = new Query(ast, undefined, text); + const updatedQuery = await buildQueryFromText(text); dispatch({ type: 'onSearchQueryChange', @@ -885,7 +906,7 @@ function TableListViewTableComp({ updateQueryFromURL(urlState.s); updateSortFromURL(urlState.sort); - }, [urlState, searchQueryParser, getTagList, urlStateEnabled]); + }, [urlState, buildQueryFromText, urlStateEnabled]); useEffect(() => { isMounted.current = true; @@ -895,6 +916,13 @@ function TableListViewTableComp({ }; }, []); + useEffect(() => { + if (initialQuery && !initialQueryInitialized.current) { + initialQueryInitialized.current = true; + buildQueryFromText(initialQuery).then(updateQuery); + } + }, [initialQuery, buildQueryFromText, updateQuery]); + const PageTemplate = useMemo(() => { return withoutPageTemplateWrapper ? ((({ diff --git a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx index 57716ee8b525e..e39d323374fbc 100644 --- a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx @@ -7,7 +7,7 @@ */ import { FormattedRelative, I18nProvider } from '@kbn/i18n-react'; -import React, { PropsWithChildren, useCallback, useState } from 'react'; +import React, { PropsWithChildren, useCallback, useMemo, useState } from 'react'; import { type TableListViewKibanaDependencies, @@ -206,6 +206,14 @@ export const DashboardListing = ({ const { getEntityName, getTableListTitle, getEntityNamePlural } = dashboardListingTableStrings; + const savedObjectsTaggingFakePlugin = useMemo(() => { + return savedObjectsTagging.hasApi // TODO: clean up this logic once https://github.com/elastic/kibana/issues/140433 is resolved + ? ({ + ui: savedObjectsTagging, + } as TableListViewKibanaDependencies['savedObjectsTagging']) + : undefined; + }, [savedObjectsTagging]); + return (