mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-29 19:05:16 +00:00
refactor searchQuery to avoid AND and OR operators
This commit is contained in:
parent
103857f90c
commit
079ff5cbb4
@ -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)}`;
|
||||
|
@ -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}*`,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -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 });
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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) => {
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user