refactor searchQuery to avoid AND and OR operators

This commit is contained in:
Pranita 2025-09-08 13:26:22 +05:30
parent 103857f90c
commit 079ff5cbb4
7 changed files with 257 additions and 51 deletions

View File

@ -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)}`;

View File

@ -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}*`,
},
},
],
},
},
]
: []),
],
},
},

View File

@ -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 });

View File

@ -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

View File

@ -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,

View File

@ -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) => {

View File

@ -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;
}
};