diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/AdvancedSearch.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/AdvancedSearch.constants.ts index 622cd795e80..dfea5d1686b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/AdvancedSearch.constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/AdvancedSearch.constants.ts @@ -12,7 +12,6 @@ */ import { t } from 'i18next'; -import { isUndefined, uniq } from 'lodash'; import { BasicConfig, Fields, @@ -24,7 +23,6 @@ import AntdConfig from 'react-awesome-query-builder/lib/config/antd'; import { EntityFields, SuggestionField } from '../enums/AdvancedSearch.enum'; import { SearchIndex } from '../enums/search.enum'; import { getAggregateFieldOptions } from '../rest/miscAPI'; -import { suggestQuery } from '../rest/searchAPI'; import { renderAdvanceSearchButtons } from '../utils/AdvancedSearchUtils'; import { getCombinedQueryFilterObject } from '../utils/ExplorePage/ExplorePageUtils'; @@ -220,64 +218,28 @@ export const emptyJsonTree: JsonTree = { */ export const autocomplete: (args: { searchIndex: SearchIndex | SearchIndex[]; - entitySearchIndex: SearchIndex | SearchIndex[]; entityField: EntityFields; suggestField?: SuggestionField; -}) => SelectFieldSettings['asyncFetch'] = ({ - searchIndex, - suggestField, - entitySearchIndex, - entityField, -}) => { - const isUserAndTeamSearchIndex = - searchIndex.includes(SearchIndex.USER) || - searchIndex.includes(SearchIndex.TEAM); - +}) => SelectFieldSettings['asyncFetch'] = ({ searchIndex, entityField }) => { return (search) => { - if (search) { - return suggestQuery({ - query: search ?? '*', - searchIndex: searchIndex, - field: suggestField, - // fetch source if index is type of user or team and both - fetchSource: isUserAndTeamSearchIndex, - }).then((resp) => { - return { - values: uniq(resp).map(({ text, _source }) => { - // set displayName or name if index is type of user or team and both. - // else set the text - const name = - isUserAndTeamSearchIndex && !isUndefined(_source) - ? _source?.displayName || _source.name - : text; + return getAggregateFieldOptions( + searchIndex, + entityField, + search ?? '', + JSON.stringify(getCombinedQueryFilterObject()) + ).then((response) => { + const buckets = + response.data.aggregations[`sterms#${entityField}`].buckets; - return { - value: name, - title: name, - }; - }), - hasMore: false, - }; - }); - } else { - return getAggregateFieldOptions( - entitySearchIndex, - entityField, - '', - JSON.stringify(getCombinedQueryFilterObject()) - ).then((response) => { - const buckets = - response.data.aggregations[`sterms#${entityField}`].buckets; - - return { - values: buckets.map((bucket) => ({ - value: bucket.key, - title: bucket.label ?? bucket.key, - })), - hasMore: false, - }; - }); - } + return { + values: buckets.map((bucket) => ({ + value: bucket.key, + title: bucket.label ?? bucket.key, + })), + hasMore: false, + }; + }); + // } }; }; @@ -306,8 +268,10 @@ const getCommonQueryBuilderFields = ( fieldSettings: { asyncFetch: autocomplete({ - searchIndex: [SearchIndex.USER, SearchIndex.TEAM], - entitySearchIndex: [SearchIndex.USER, SearchIndex.TEAM], + searchIndex: entitySearchIndex ?? [ + SearchIndex.USER, + SearchIndex.TEAM, + ], entityField: EntityFields.OWNER, }), useAsyncSearch: true, @@ -320,8 +284,10 @@ const getCommonQueryBuilderFields = ( mainWidgetProps, fieldSettings: { asyncFetch: autocomplete({ - searchIndex: [SearchIndex.TAG, SearchIndex.GLOSSARY], - entitySearchIndex, + searchIndex: entitySearchIndex ?? [ + SearchIndex.TAG, + SearchIndex.GLOSSARY, + ], entityField: EntityFields.TAG, }), useAsyncSearch: true, @@ -334,8 +300,7 @@ const getCommonQueryBuilderFields = ( mainWidgetProps, fieldSettings: { asyncFetch: autocomplete({ - searchIndex: [SearchIndex.TAG, SearchIndex.GLOSSARY], - entitySearchIndex, + searchIndex: entitySearchIndex ?? [SearchIndex.TAG], entityField: EntityFields.TIER, }), useAsyncSearch: true, @@ -364,9 +329,7 @@ const getServiceQueryBuilderFields = (index: SearchIndex) => { fieldSettings: { asyncFetch: autocomplete({ searchIndex: index, - entitySearchIndex: index, entityField: EntityFields.SERVICE, - suggestField: SuggestionField.SERVICE, }), useAsyncSearch: true, }, @@ -387,9 +350,7 @@ const tableQueryBuilderFields: Fields = { fieldSettings: { asyncFetch: autocomplete({ searchIndex: SearchIndex.TABLE, - entitySearchIndex: SearchIndex.TABLE, entityField: EntityFields.DATABASE, - suggestField: SuggestionField.DATABASE, }), useAsyncSearch: true, }, @@ -402,9 +363,7 @@ const tableQueryBuilderFields: Fields = { fieldSettings: { asyncFetch: autocomplete({ searchIndex: SearchIndex.TABLE, - entitySearchIndex: SearchIndex.TABLE, entityField: EntityFields.DATABASE_SCHEMA, - suggestField: SuggestionField.SCHEMA, }), useAsyncSearch: true, }, @@ -417,9 +376,7 @@ const tableQueryBuilderFields: Fields = { fieldSettings: { asyncFetch: autocomplete({ searchIndex: SearchIndex.TABLE, - entitySearchIndex: SearchIndex.TABLE, entityField: EntityFields.COLUMN, - suggestField: SuggestionField.COLUMN, }), useAsyncSearch: true, }, diff --git a/openmetadata-ui/src/main/resources/ui/src/enums/AdvancedSearch.enum.ts b/openmetadata-ui/src/main/resources/ui/src/enums/AdvancedSearch.enum.ts index b46790b5e03..ab49ecd9458 100644 --- a/openmetadata-ui/src/main/resources/ui/src/enums/AdvancedSearch.enum.ts +++ b/openmetadata-ui/src/main/resources/ui/src/enums/AdvancedSearch.enum.ts @@ -38,10 +38,10 @@ export enum EntityFields { OWNER = 'displayName.keyword', TAG = 'tags.tagFQN', TIER = 'tier.tagFQN', - SERVICE = 'service.name', - DATABASE = 'database.name', - DATABASE_SCHEMA = 'databaseSchema.name', - COLUMN = 'columns.name', + SERVICE = 'service.name.keyword', + DATABASE = 'database.name.keyword', + DATABASE_SCHEMA = 'databaseSchema.name.keyword', + COLUMN = 'columns.name.keyword', CHART = 'charts.displayName.keyword', TASK = 'tasks.displayName.keyword', } diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DataInsightPage/DataInsightProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DataInsightPage/DataInsightProvider.tsx index 885d5dd611f..9735df98806 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DataInsightPage/DataInsightProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DataInsightPage/DataInsightProvider.tsx @@ -51,7 +51,6 @@ export const DataInsightContext = createContext( ); const fetchTeamSuggestions = autocomplete({ searchIndex: SearchIndex.TEAM, - entitySearchIndex: SearchIndex.TEAM, entityField: EntityFields.OWNER, }); diff --git a/openmetadata-ui/src/main/resources/ui/src/rest/searchAPI.test.ts b/openmetadata-ui/src/main/resources/ui/src/rest/searchAPI.test.ts index 0c5c8a14f6d..5b8943f2687 100644 --- a/openmetadata-ui/src/main/resources/ui/src/rest/searchAPI.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/rest/searchAPI.test.ts @@ -83,113 +83,9 @@ const mockTableSearchResponse = { }, }; -const mockSuggestUserResponse = { - suggest: { - 'metadata-suggest': [ - { - text: 'a', - offset: 0, - length: 1, - options: [ - { - text: 'Aaron Johnson', - _index: 'user_search_index', - _type: '_doc', - _id: '2cae227c-e2c4-487c-b52c-a96ae242d90d', - _score: 10.0, - _source: { - id: '2cae227c-e2c4-487c-b52c-a96ae242d90d', - name: 'aaron_johnson0', - fullyQualifiedName: 'aaron_johnson0', - displayName: 'Aaron Johnson', - version: 0.1, - updatedAt: 1661336540995, - updatedBy: 'anonymous', - email: 'aaron_johnson0@gmail.com', - href: 'http://localhost:8585/api/v1/users/2cae227c-e2c4-487c-b52c-a96ae242d90d', - isAdmin: false, - deleted: false, - roles: [], - inheritedRoles: [], - entityType: 'user', - teams: null, - some: { - nested: { - nullValue: null, - }, - }, - }, - }, - ], - }, - ], - }, -}; - describe('searchAPI tests', () => { beforeEach(() => jest.resetModules()); - it('suggestQuery should return object and aggregations', async () => { - jest.mock('./index', () => ({ - get: jest - .fn() - .mockImplementation(() => - Promise.resolve({ data: mockTableSearchResponse }) - ), - })); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { searchQuery } = require('./searchAPI'); - const res = await searchQuery({ searchIndex: SearchIndex.TABLE }); - - expect(res.hits.total.value).toBe(10_000); - - expect(res.hits.hits).toHaveLength(1); - expect(res.hits.hits[0]._index).toEqual(SearchIndex.TABLE); - expect(res.hits.hits[0]._source).toEqual( - expect.objectContaining({ - id: '9b30a945-239a-4cb7-93b0-f1b7425aed41', - name: 'raw_product_catalog', - fullyQualifiedName: - 'sample_data.ecommerce_db.shopify.raw_product_catalog', - description: - 'This is a raw product catalog table contains the product listing, price, seller etc.. represented in our online DB. ', - version: 0.1, - updatedAt: 1661336543968, - updatedBy: 'anonymous', - href: 'http://localhost:8585/api/v1/tables/9b30a945-239a-4cb7-93b0-f1b7425aed41', - tableType: 'Regular', - }) - ); - - expect(res.aggregations).toEqual( - expect.objectContaining({ - EntityType: { - buckets: expect.arrayContaining([ - { - key: 'table', - doc_count: 10960, - }, - ]), - }, - ServiceName: { - buckets: expect.arrayContaining([ - { - key: 'trino', - doc_count: 10924, - }, - { - key: 'sample_data', - doc_count: 36, - }, - ]), - }, - Tags: { - buckets: [], - }, - }) - ); - }); - it('searchQuery should not return nulls', async () => { jest.mock('./index', () => ({ get: jest @@ -224,70 +120,4 @@ describe('searchAPI tests', () => { expect(res.hits.hits[0]._source.type).toBe('table'); }); - - it('suggestQuery should return object and text', async () => { - jest.mock('./index', () => ({ - get: jest - .fn() - .mockImplementation(() => - Promise.resolve({ data: mockSuggestUserResponse }) - ), - })); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { suggestQuery } = require('./searchAPI'); - const res = await suggestQuery({ searchIndex: SearchIndex.USER }); - - expect(res).toEqual([ - expect.objectContaining({ - _index: SearchIndex.USER, - _source: expect.objectContaining({ - id: '2cae227c-e2c4-487c-b52c-a96ae242d90d', - name: 'aaron_johnson0', - fullyQualifiedName: 'aaron_johnson0', - displayName: 'Aaron Johnson', - version: 0.1, - updatedAt: 1661336540995, - updatedBy: 'anonymous', - email: 'aaron_johnson0@gmail.com', - href: 'http://localhost:8585/api/v1/users/2cae227c-e2c4-487c-b52c-a96ae242d90d', - isAdmin: false, - deleted: false, - roles: [], - inheritedRoles: [], - entityType: 'user', - }), - }), - ]); - }); - - it('suggestQuery should not return nulls', async () => { - jest.mock('./index', () => ({ - get: jest - .fn() - .mockImplementation(() => - Promise.resolve({ data: mockSuggestUserResponse }) - ), - })); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { suggestQuery } = require('./searchAPI'); - const res = await suggestQuery({ searchIndex: SearchIndex.USER }); - - // Deep checking for null values - expect(flatten(res[0]._source).filter(isNull)).toHaveLength(0); - }); - - it('suggestQuery should have type field', async () => { - jest.mock('./index', () => ({ - get: jest - .fn() - .mockImplementation(() => - Promise.resolve({ data: mockSuggestUserResponse }) - ), - })); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { suggestQuery } = require('./searchAPI'); - const res = await suggestQuery({ searchIndex: SearchIndex.USER }); - - expect(res[0]._source.type).toBe('user'); - }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/rest/searchAPI.ts b/openmetadata-ui/src/main/resources/ui/src/rest/searchAPI.ts index 7e8ba26be07..696e10f845e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/rest/searchAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/rest/searchAPI.ts @@ -19,12 +19,9 @@ import { Aggregations, DataInsightSearchResponse, KeysOfUnion, - RawSuggestResponse, SearchIndexSearchSourceMapping, SearchRequest, SearchResponse, - SuggestRequest, - SuggestResponse, } from '../interface/search.interface'; import { omitDeep } from '../utils/APIUtils'; import { getQueryWithSlash } from '../utils/SearchUtils'; @@ -290,139 +287,3 @@ export const searchQueryDataInsight = async ( res.data ) as unknown as DataInsightSearchResponse; }; - -/** - * Formats a response from {@link rawSuggestQuery} - * - * Warning: avoid this pattern unless applying custom transformation to the raw response! - * ```ts - * const response = await rawSuggestQuery(req); - * const data = formatSuggestQueryResponse(response.data); - * ``` - * - * Instead use the shorthand {@link suggestQuery} - * ```ts - * const data = suggestQuery(req); - * ``` - * - * @param data - */ -export const formatSuggestQueryResponse = < - SI extends SearchIndex | SearchIndex[], - TIncludeFields extends KeysOfUnion< - SearchIndexSearchSourceMapping[SI extends Array - ? SI[number] - : SI] - > ->( - data: RawSuggestResponse< - SI extends Array ? SI[number] : SI, - TIncludeFields - > -): SuggestResponse< - SI extends Array ? SI[number] : SI, - TIncludeFields -> => { - let _data; - - _data = data; - - // Elasticsearch responses use 'null' for missing values, we want undefined - _data = omitDeep< - SuggestResponse< - SI extends Array ? SI[number] : SI, - TIncludeFields - > - >(_data.suggest['metadata-suggest'][0].options, isNil); - - /* Elasticsearch objects use `entityType` to track their type, but the EntityReference interface uses `type` - This copies `entityType` into `type` (if `entityType` exists) so responses implement EntityReference */ - _data = _data.map((datum) => - '_source' in datum - ? 'entityType' in datum._source - ? { - ...datum, - _source: { - ...(datum._source as SearchIndexSearchSourceMapping[SI extends Array - ? SI[number] - : SI]), - type: ( - datum._source as SearchIndexSearchSourceMapping[SI extends Array - ? SI[number] - : SI] - ).entityType, - }, - } - : datum - : datum - ); - - return _data; -}; - -/** - * Executes a request to /search/suggest, returning the raw response. - * Warning: Only call this function directly in special cases. Otherwise use {@link suggestQuery} - * - * @param req Request object - */ -export const rawSuggestQuery = < - SI extends SearchIndex | SearchIndex[], - TIncludeFields extends KeysOfUnion< - SearchIndexSearchSourceMapping[SI extends Array - ? SI[number] - : SI] - > ->( - req: SuggestRequest -): Promise< - AxiosResponse< - RawSuggestResponse< - SI extends Array ? SI[number] : SI, - TIncludeFields - > - > -> => { - const { query, searchIndex, field, fetchSource } = req; - - return APIClient.get< - RawSuggestResponse< - SI extends Array ? SI[number] : SI, - TIncludeFields - > - >('/search/suggest', { - params: { - q: query, - field, - index: getSearchIndexParam(searchIndex), - fetch_source: fetchSource, - include_source_fields: req.fetchSource ? req.includeFields : undefined, - }, - }); -}; - -/** - * Access point for the Suggestion API. - * Executes a request to /search/suggest, returning a formatted response. - * - * @param req Request object - */ -export const suggestQuery = async < - SI extends SearchIndex | SearchIndex[], - TIncludeFields extends KeysOfUnion< - SearchIndexSearchSourceMapping[SI extends Array - ? SI[number] - : SI] - > ->( - req: SuggestRequest -): Promise< - SuggestResponse< - SI extends Array ? SI[number] : SI, - TIncludeFields - > -> => { - const res = await rawSuggestQuery(req); - - return formatSuggestQueryResponse(res.data); -};