diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx index a83929ddd2e..da0a8d26e8d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx @@ -26,7 +26,7 @@ import { InternalAxiosRequestConfig, } from 'axios'; import { CookieStorage } from 'cookie-storage'; -import { isEmpty, isNil, isNumber } from 'lodash'; +import { isNil, isNumber } from 'lodash'; import { WebStorageStateStore } from 'oidc-client'; import Qs from 'qs'; import { @@ -480,14 +480,42 @@ export const AuthProvider = ({ // Parse and update the query parameter const queryParams = Qs.parse(config.url.split('?')[1]); - // adding quotes for exact matching - const domainStatement = `(domains.fullyQualifiedName:"${escapeESReservedCharacters( - activeDomain - )}")`; - queryParams.q = queryParams.q ?? ''; - queryParams.q += isEmpty(queryParams.q) - ? domainStatement - : ` AND ${domainStatement}`; + // Escape special characters for exact matching + const domainStatement = escapeESReservedCharacters(activeDomain); + + // Move domain filter to proper queryFilter structure + if (queryParams.query_filter) { + // Merge with existing query_filter + const existingFilter = JSON.parse(queryParams.query_filter as string); + queryParams.query_filter = JSON.stringify({ + query: { + bool: { + must: [ + existingFilter.query || existingFilter, + { + match: { + 'domains.fullyQualifiedName': domainStatement, + }, + }, + ], + }, + }, + }); + } else { + // Create new query_filter with proper structure + queryParams.query_filter = JSON.stringify({ + query: { + match: { + 'domains.fullyQualifiedName': domainStatement, + }, + }, + }); + } + + // Clear the q parameter if it exists since we're using query_filter + if (queryParams.q === '') { + delete queryParams.q; + } // Update the URL with the modified query parameter config.url = `${config.url.split('?')[0]}?${Qs.stringify(queryParams)}`; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/DatabaseSchema/DatabaseSchemaTable/DatabaseSchemaTable.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/DatabaseSchema/DatabaseSchemaTable/DatabaseSchemaTable.tsx index b6fa6439518..86e29c3adc6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/DatabaseSchema/DatabaseSchemaTable/DatabaseSchemaTable.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/DatabaseSchema/DatabaseSchemaTable/DatabaseSchemaTable.tsx @@ -148,7 +148,7 @@ export const DatabaseSchemaTable = ({ setIsLoading(true); try { const response = await searchQuery({ - query: `(name.keyword:*${searchValue}*) OR (description.keyword:*${searchValue}*)`, + query: '', pageNumber, pageSize: PAGE_SIZE, queryFilter: { @@ -156,6 +156,24 @@ export const DatabaseSchemaTable = ({ bool: { must: [ { term: { 'database.fullyQualifiedName': decodedDatabaseFQN } }, + ...(searchValue + ? [ + { + bool: { + should: [ + { + wildcard: { 'name.keyword': `*${searchValue}*` }, + }, + { + wildcard: { + 'description.keyword': `*${searchValue}*`, + }, + }, + ], + }, + }, + ] + : []), ], }, }, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/tabs/AssetsTabs.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/tabs/AssetsTabs.component.tsx index 06c2bda27cd..6eb630e832b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/tabs/AssetsTabs.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/tabs/AssetsTabs.component.tsx @@ -231,13 +231,68 @@ const AssetsTabs = forwardRef( }) => { try { setIsLoading(true); + + // Convert filter string with OR/AND to proper queryFilter structure + let structuredQueryFilter = queryFilter; + let filterString = queryParam as string; + + if ( + filterString && + (filterString.includes(' OR ') || filterString.includes(' AND ')) + ) { + // Parse the filter string to create a proper query structure + if (filterString.includes(' OR ')) { + // Handle OR case - e.g., "(owners.id:1 OR owners.id:2)" + const cleanFilter = filterString.replace(/[()]/g, ''); + const terms = cleanFilter.split(' OR ').map((term) => { + const [field, value] = term.split(':'); + + return { term: { [field]: value } }; + }); + + structuredQueryFilter = { + query: { + bool: { + should: terms, + minimum_should_match: 1, + }, + }, + }; + filterString = ''; // Clear the filter string since we're using queryFilter + } else if (filterString.includes(' AND ')) { + // Handle AND case - e.g., "disabled:false AND !classification.name:Tier" + const terms = filterString.split(' AND ').map((term) => { + const trimmedTerm = term.trim(); + if (trimmedTerm.startsWith('!')) { + // Handle negation + const [field, value] = trimmedTerm.substring(1).split(':'); + + return { bool: { must_not: [{ term: { [field]: value } }] } }; + } else { + const [field, value] = trimmedTerm.split(':'); + + return { term: { [field]: value } }; + } + }); + + structuredQueryFilter = { + query: { + bool: { + must: terms, + }, + }, + }; + filterString = ''; // Clear the filter string since we're using queryFilter + } + } + const res = await searchQuery({ pageNumber: page, pageSize: pageSize, searchIndex: index, query: `*${searchValue}*`, - filters: queryParam as string, - queryFilter: queryFilter, + filters: filterString, + queryFilter: structuredQueryFilter, }); const hits = res.hits.hits as SearchedDataProps['data']; handlePagingChange({ total: res.hits.total.value ?? 0 }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MyData/MyDataWidget/MyDataWidget.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/MyData/MyDataWidget/MyDataWidget.component.tsx index 7bc097d075c..8c2008dd7e5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/MyData/MyDataWidget/MyDataWidget.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/MyData/MyDataWidget/MyDataWidget.component.tsx @@ -39,7 +39,7 @@ import { WidgetCommonProps, WidgetConfig, } from '../../../pages/CustomizablePage/CustomizablePage.interface'; -import { searchData } from '../../../rest/miscAPI'; +import { searchQuery } from '../../../rest/searchAPI'; import entityUtilClassBase from '../../../utils/EntityUtilClassBase'; import { getEntityName } from '../../../utils/EntityUtils'; import { getDomainPath, getUserPath } from '../../../utils/RouterUtils'; @@ -124,27 +124,33 @@ const MyDataWidgetInternal = ({ setIsLoading(true); try { const teamsIds = (currentUser.teams ?? []).map((team) => team.id); - const mergedIds = [ - ...teamsIds.map((id) => `owners.id:${id}`), - `owners.id:${currentUser.id}`, - ].join(' OR '); + const ownerIds = [...teamsIds, currentUser.id]; + + // Create proper queryFilter structure instead of filter string + const queryFilterObj = { + query: { + bool: { + should: ownerIds.map((id) => ({ term: { 'owners.id': id } })), + minimum_should_match: 1, + }, + }, + }; - const queryFilter = `(${mergedIds})`; const sortField = getSortField(selectedFilter); const sortOrder = getSortOrder(selectedFilter); - const res = await searchData( - '', - INITIAL_PAGING_VALUE, - PAGE_SIZE_MEDIUM, - queryFilter, + const res = await searchQuery({ + query: '', + pageNumber: INITIAL_PAGING_VALUE, + pageSize: PAGE_SIZE_MEDIUM, + queryFilter: queryFilterObj, sortField, sortOrder, - SearchIndex.ALL - ); + searchIndex: SearchIndex.ALL, + }); // Extract useful details from the Response - const ownedAssets = res?.data?.hits?.hits; + const ownedAssets = res?.hits?.hits; const sourceData = ownedAssets.map((hit) => hit._source); // Apply client-side sorting as well to ensure consistent results diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableConstraints/TableConstraintsModal/TableConstraintsModal.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableConstraints/TableConstraintsModal/TableConstraintsModal.component.tsx index cc387013906..bd8d5b15f63 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableConstraints/TableConstraintsModal/TableConstraintsModal.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableConstraints/TableConstraintsModal/TableConstraintsModal.component.tsx @@ -74,14 +74,39 @@ const TableConstraintsModal = ({ setIsRelatedColumnLoading(true); try { const encodedValue = getEncodedFqn(escapeESReservedCharacters(value)); + const serviceFilter = getServiceNameQueryFilter( + tableDetails?.service?.name ?? '' + ); const data = await searchQuery({ - query: - value && - `(columns.name.keyword:*${encodedValue}*) OR (columns.fullyQualifiedName:*${encodedValue}*)`, + query: value ? `*${encodedValue}*` : '', searchIndex: SearchIndex.TABLE, - queryFilter: getServiceNameQueryFilter( - tableDetails?.service?.name ?? '' - ), + queryFilter: value + ? { + query: { + bool: { + must: [ + serviceFilter.query, + { + bool: { + should: [ + { + wildcard: { + 'columns.name.keyword': `*${encodedValue}*`, + }, + }, + { + wildcard: { + 'columns.fullyQualifiedName': `*${encodedValue}*`, + }, + }, + ], + }, + }, + ], + }, + }, + } + : serviceFilter, pageNumber: 1, pageSize: PAGE_SIZE, includeDeleted: false, diff --git a/openmetadata-ui/src/main/resources/ui/src/rest/serviceAPI.ts b/openmetadata-ui/src/main/resources/ui/src/rest/serviceAPI.ts index fe94a05b218..6e3154a9364 100644 --- a/openmetadata-ui/src/main/resources/ui/src/rest/serviceAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/rest/serviceAPI.ts @@ -32,6 +32,7 @@ import { import { getEncodedFqn } from '../utils/StringsUtils'; import APIClient from './index'; import { searchData } from './miscAPI'; +import { searchQuery } from './searchAPI'; interface ServiceRequestParams { limit?: number; @@ -193,18 +194,49 @@ export const searchService = async ({ filters?: string; deleted?: boolean; }) => { - const response = await searchData( - search ?? WILD_CARD_CHAR, - currentPage, - limit, - filters ?? '', - '', - '', - searchIndex, - deleted - ); + // Check if filters contains OR operators and convert to proper queryFilter + if (filters && filters.includes(' OR ')) { + const cleanFilter = filters.replace(/[()]/g, ''); + const terms = cleanFilter.split(' OR ').map((term) => { + const [field, value] = term.split(':'); - return response.data; + return { term: { [field]: value } }; + }); + + const queryFilterObj = { + query: { + bool: { + should: terms, + minimum_should_match: 1, + }, + }, + }; + + const response = await searchQuery({ + query: search ?? WILD_CARD_CHAR, + pageNumber: currentPage, + pageSize: limit, + queryFilter: queryFilterObj, + searchIndex, + includeDeleted: deleted, + }); + + return response; + } else { + // Use original searchData for non-OR filters + const response = await searchData( + search ?? WILD_CARD_CHAR, + currentPage, + limit, + filters ?? '', + '', + '', + searchIndex, + deleted + ); + + return response.data; + } }; export const restoreService = async (serviceCategory: string, id: string) => { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/Query/QueryUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/Query/QueryUtils.ts index 724c74518ef..1b4cb33c81f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/Query/QueryUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/Query/QueryUtils.ts @@ -96,13 +96,55 @@ export const fetchFilterOptions = async ( filters: string, searchIndex: SearchIndex | SearchIndex[] ) => { - const response = await searchQuery({ - query: `*${searchText}*`, - filters, - pageNumber: 1, - pageSize: PAGE_SIZE_BASE, - searchIndex, - }); + // Check if filters contains AND operators and convert to proper queryFilter + if (filters && filters.includes(' AND ')) { + const terms = filters.split(' AND ').map((term) => { + const trimmedTerm = term.trim(); + if (trimmedTerm.startsWith('!')) { + // Handle negation - e.g., "!classification.name:Tier" + const [field, value] = trimmedTerm.substring(1).split(':'); - return response; + return { bool: { must_not: [{ term: { [field]: value } }] } }; + } else { + // Regular term - e.g., "disabled:false" + const [field, value] = trimmedTerm.split(':'); + + return { + term: { + [field]: + value === 'false' ? false : value === 'true' ? true : value, + }, + }; + } + }); + + const queryFilterObj = { + query: { + bool: { + must: terms, + }, + }, + }; + + const response = await searchQuery({ + query: `*${searchText}*`, + queryFilter: queryFilterObj, + pageNumber: 1, + pageSize: PAGE_SIZE_BASE, + searchIndex, + }); + + return response; + } else { + // Use original filters parameter for non-AND filters + const response = await searchQuery({ + query: `*${searchText}*`, + filters, + pageNumber: 1, + pageSize: PAGE_SIZE_BASE, + searchIndex, + }); + + return response; + } };