mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-02 04:13:17 +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,
|
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)}`;
|
||||||
|
@ -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}*`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -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 });
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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) => {
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user