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, InternalAxiosRequestConfig,
} from 'axios'; } from 'axios';
import { CookieStorage } from 'cookie-storage'; import { CookieStorage } from 'cookie-storage';
import { isEmpty, isNil, isNumber } from 'lodash'; import { isNil, isNumber } from 'lodash';
import { WebStorageStateStore } from 'oidc-client'; import { WebStorageStateStore } from 'oidc-client';
import Qs from 'qs'; import Qs from 'qs';
import { import {
@ -480,14 +480,42 @@ export const AuthProvider = ({
// Parse and update the query parameter // Parse and update the query parameter
const queryParams = Qs.parse(config.url.split('?')[1]); const queryParams = Qs.parse(config.url.split('?')[1]);
// adding quotes for exact matching // Escape special characters for exact matching
const domainStatement = `(domains.fullyQualifiedName:"${escapeESReservedCharacters( const domainStatement = escapeESReservedCharacters(activeDomain);
activeDomain
)}")`; // Move domain filter to proper queryFilter structure
queryParams.q = queryParams.q ?? ''; if (queryParams.query_filter) {
queryParams.q += isEmpty(queryParams.q) // Merge with existing query_filter
? domainStatement const existingFilter = JSON.parse(queryParams.query_filter as string);
: ` AND ${domainStatement}`; 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 // Update the URL with the modified query parameter
config.url = `${config.url.split('?')[0]}?${Qs.stringify(queryParams)}`; config.url = `${config.url.split('?')[0]}?${Qs.stringify(queryParams)}`;

View File

@ -148,7 +148,7 @@ export const DatabaseSchemaTable = ({
setIsLoading(true); setIsLoading(true);
try { try {
const response = await searchQuery({ const response = await searchQuery({
query: `(name.keyword:*${searchValue}*) OR (description.keyword:*${searchValue}*)`, query: '',
pageNumber, pageNumber,
pageSize: PAGE_SIZE, pageSize: PAGE_SIZE,
queryFilter: { queryFilter: {
@ -156,6 +156,24 @@ export const DatabaseSchemaTable = ({
bool: { bool: {
must: [ must: [
{ term: { 'database.fullyQualifiedName': decodedDatabaseFQN } }, { 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 { try {
setIsLoading(true); 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({ const res = await searchQuery({
pageNumber: page, pageNumber: page,
pageSize: pageSize, pageSize: pageSize,
searchIndex: index, searchIndex: index,
query: `*${searchValue}*`, query: `*${searchValue}*`,
filters: queryParam as string, filters: filterString,
queryFilter: queryFilter, queryFilter: structuredQueryFilter,
}); });
const hits = res.hits.hits as SearchedDataProps['data']; const hits = res.hits.hits as SearchedDataProps['data'];
handlePagingChange({ total: res.hits.total.value ?? 0 }); handlePagingChange({ total: res.hits.total.value ?? 0 });

View File

@ -39,7 +39,7 @@ import {
WidgetCommonProps, WidgetCommonProps,
WidgetConfig, WidgetConfig,
} from '../../../pages/CustomizablePage/CustomizablePage.interface'; } from '../../../pages/CustomizablePage/CustomizablePage.interface';
import { searchData } from '../../../rest/miscAPI'; import { searchQuery } from '../../../rest/searchAPI';
import entityUtilClassBase from '../../../utils/EntityUtilClassBase'; import entityUtilClassBase from '../../../utils/EntityUtilClassBase';
import { getEntityName } from '../../../utils/EntityUtils'; import { getEntityName } from '../../../utils/EntityUtils';
import { getDomainPath, getUserPath } from '../../../utils/RouterUtils'; import { getDomainPath, getUserPath } from '../../../utils/RouterUtils';
@ -124,27 +124,33 @@ const MyDataWidgetInternal = ({
setIsLoading(true); setIsLoading(true);
try { try {
const teamsIds = (currentUser.teams ?? []).map((team) => team.id); const teamsIds = (currentUser.teams ?? []).map((team) => team.id);
const mergedIds = [ const ownerIds = [...teamsIds, currentUser.id];
...teamsIds.map((id) => `owners.id:${id}`),
`owners.id:${currentUser.id}`, // Create proper queryFilter structure instead of filter string
].join(' OR '); const queryFilterObj = {
query: {
bool: {
should: ownerIds.map((id) => ({ term: { 'owners.id': id } })),
minimum_should_match: 1,
},
},
};
const queryFilter = `(${mergedIds})`;
const sortField = getSortField(selectedFilter); const sortField = getSortField(selectedFilter);
const sortOrder = getSortOrder(selectedFilter); const sortOrder = getSortOrder(selectedFilter);
const res = await searchData( const res = await searchQuery({
'', query: '',
INITIAL_PAGING_VALUE, pageNumber: INITIAL_PAGING_VALUE,
PAGE_SIZE_MEDIUM, pageSize: PAGE_SIZE_MEDIUM,
queryFilter, queryFilter: queryFilterObj,
sortField, sortField,
sortOrder, sortOrder,
SearchIndex.ALL searchIndex: SearchIndex.ALL,
); });
// Extract useful details from the Response // Extract useful details from the Response
const ownedAssets = res?.data?.hits?.hits; const ownedAssets = res?.hits?.hits;
const sourceData = ownedAssets.map((hit) => hit._source); const sourceData = ownedAssets.map((hit) => hit._source);
// Apply client-side sorting as well to ensure consistent results // Apply client-side sorting as well to ensure consistent results

View File

@ -74,14 +74,39 @@ const TableConstraintsModal = ({
setIsRelatedColumnLoading(true); setIsRelatedColumnLoading(true);
try { try {
const encodedValue = getEncodedFqn(escapeESReservedCharacters(value)); const encodedValue = getEncodedFqn(escapeESReservedCharacters(value));
const data = await searchQuery({ const serviceFilter = getServiceNameQueryFilter(
query:
value &&
`(columns.name.keyword:*${encodedValue}*) OR (columns.fullyQualifiedName:*${encodedValue}*)`,
searchIndex: SearchIndex.TABLE,
queryFilter: getServiceNameQueryFilter(
tableDetails?.service?.name ?? '' tableDetails?.service?.name ?? ''
), );
const data = await searchQuery({
query: value ? `*${encodedValue}*` : '',
searchIndex: SearchIndex.TABLE,
queryFilter: value
? {
query: {
bool: {
must: [
serviceFilter.query,
{
bool: {
should: [
{
wildcard: {
'columns.name.keyword': `*${encodedValue}*`,
},
},
{
wildcard: {
'columns.fullyQualifiedName': `*${encodedValue}*`,
},
},
],
},
},
],
},
},
}
: serviceFilter,
pageNumber: 1, pageNumber: 1,
pageSize: PAGE_SIZE, pageSize: PAGE_SIZE,
includeDeleted: false, includeDeleted: false,

View File

@ -32,6 +32,7 @@ import {
import { getEncodedFqn } from '../utils/StringsUtils'; import { getEncodedFqn } from '../utils/StringsUtils';
import APIClient from './index'; import APIClient from './index';
import { searchData } from './miscAPI'; import { searchData } from './miscAPI';
import { searchQuery } from './searchAPI';
interface ServiceRequestParams { interface ServiceRequestParams {
limit?: number; limit?: number;
@ -193,6 +194,36 @@ export const searchService = async ({
filters?: string; filters?: string;
deleted?: boolean; deleted?: boolean;
}) => { }) => {
// 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 { 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( const response = await searchData(
search ?? WILD_CARD_CHAR, search ?? WILD_CARD_CHAR,
currentPage, currentPage,
@ -205,6 +236,7 @@ export const searchService = async ({
); );
return response.data; return response.data;
}
}; };
export const restoreService = async (serviceCategory: string, id: string) => { export const restoreService = async (serviceCategory: string, id: string) => {

View File

@ -96,6 +96,47 @@ export const fetchFilterOptions = async (
filters: string, filters: string,
searchIndex: SearchIndex | SearchIndex[] searchIndex: SearchIndex | 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 { 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({ const response = await searchQuery({
query: `*${searchText}*`, query: `*${searchText}*`,
filters, filters,
@ -105,4 +146,5 @@ export const fetchFilterOptions = async (
}); });
return response; return response;
}
}; };