address PR comments

This commit is contained in:
Pranita 2025-09-18 16:16:30 +05:30
parent 4e0c247c36
commit 0937deaee4
7 changed files with 95 additions and 204 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 { isNil, isNumber } from 'lodash'; import { isEmpty, isNil, isNumber } from 'lodash';
import { WebStorageStateStore } from 'oidc-client'; import { WebStorageStateStore } from 'oidc-client';
import Qs from 'qs'; import Qs from 'qs';
import { import {
@ -61,7 +61,6 @@ import { AuthProvider as AuthProviderEnum } from '../../../generated/settings/se
import { useApplicationStore } from '../../../hooks/useApplicationStore'; import { useApplicationStore } from '../../../hooks/useApplicationStore';
import useCustomLocation from '../../../hooks/useCustomLocation/useCustomLocation'; import useCustomLocation from '../../../hooks/useCustomLocation/useCustomLocation';
import { useDomainStore } from '../../../hooks/useDomainStore'; import { useDomainStore } from '../../../hooks/useDomainStore';
import { QueryFilterInterface } from '../../../pages/ExplorePage/ExplorePage.interface';
import axiosClient from '../../../rest'; import axiosClient from '../../../rest';
import { getDomainList } from '../../../rest/domainAPI'; import { getDomainList } from '../../../rest/domainAPI';
import { import {
@ -480,69 +479,18 @@ export const AuthProvider = ({
} }
// Parse and update the query parameter // Parse and update the query parameter
const urlParams = config.url.includes('?') const queryParams = Qs.parse(config.url.split('?')[1]);
? Qs.parse(config.url.split('?')[1]) // adding quotes for exact matching
: {}; const domainStatement = `(domains.fullyQualifiedName:"${escapeESReservedCharacters(
const queryParams = { ...urlParams, ...config.params }; activeDomain
)}")`;
queryParams.q = queryParams.q ?? '';
queryParams.q += isEmpty(queryParams.q)
? domainStatement
: ` AND ${domainStatement}`;
if (config.url.includes('?')) { // Update the URL with the modified query parameter
config.url = config.url.split('?')[0]; config.url = `${config.url.split('?')[0]}?${Qs.stringify(queryParams)}`;
}
const domainStatement = escapeESReservedCharacters(activeDomain);
// Create domain filter using term query for exact matching
const domainFilter = {
term: {
'domains.fullyQualifiedName': domainStatement,
},
};
const createDomainFilter = () => ({
query: domainFilter,
});
const mergeWithDomainFilter = (
existingMust: QueryFilterInterface[]
) => ({
query: {
bool: {
must: [...existingMust, domainFilter],
},
},
});
if (queryParams.query_filter) {
try {
const existingFilter = JSON.parse(
queryParams.query_filter as string
);
const existingQuery = existingFilter.query || existingFilter;
if (
existingQuery?.bool?.must &&
Array.isArray(existingQuery.bool.must)
) {
queryParams.query_filter = JSON.stringify(
mergeWithDomainFilter(existingQuery.bool.must)
);
} else if (existingQuery && typeof existingQuery === 'object') {
queryParams.query_filter = JSON.stringify(
mergeWithDomainFilter([existingQuery])
);
} else {
queryParams.query_filter = JSON.stringify(createDomainFilter());
}
} catch (error) {
queryParams.query_filter = JSON.stringify(createDomainFilter());
}
} else {
queryParams.query_filter = JSON.stringify(createDomainFilter());
}
config.params = {
...queryParams,
};
} else { } else {
config.params = { config.params = {
...config.params, ...config.params,

View File

@ -24,7 +24,7 @@ import { ERROR_PLACEHOLDER_TYPE, SIZE } from '../../../../enums/common.enum';
import { EntityType } from '../../../../enums/entity.enum'; import { EntityType } from '../../../../enums/entity.enum';
import { SearchIndex } from '../../../../enums/search.enum'; import { SearchIndex } from '../../../../enums/search.enum';
import { EntityReference } from '../../../../generated/entity/type'; import { EntityReference } from '../../../../generated/entity/type';
import { searchData } from '../../../../rest/miscAPI'; import { searchQuery } from '../../../../rest/searchAPI';
import { import {
getEntityName, getEntityName,
getEntityReferenceFromEntity, getEntityReferenceFromEntity,
@ -69,12 +69,13 @@ const AddPipeLineModal = ({
const getSearchResults = async (value = '*') => { const getSearchResults = async (value = '*') => {
try { try {
const data = await searchData(value, 1, PAGE_SIZE, '', '', '', [ const data = await searchQuery({
SearchIndex.PIPELINE, query: value,
SearchIndex.STORED_PROCEDURE, pageNumber: 1,
]); pageSize: PAGE_SIZE,
searchIndex: [SearchIndex.PIPELINE, SearchIndex.STORED_PROCEDURE],
const edgeOptions = data.data.hits.hits.map((hit) => });
const edgeOptions = data.hits.hits.map((hit) =>
getEntityReferenceFromEntity( getEntityReferenceFromEntity(
hit._source, hit._source,
hit._source.entityType as EntityType hit._source.entityType as EntityType

View File

@ -22,7 +22,7 @@ import {
Domain, Domain,
DomainType, DomainType,
} from '../../../../generated/entity/domains/domain'; } from '../../../../generated/entity/domains/domain';
import { searchData } from '../../../../rest/miscAPI'; import { searchQuery } from '../../../../rest/searchAPI';
import DomainsWidget from './DomainsWidget'; import DomainsWidget from './DomainsWidget';
const mockProps = { const mockProps = {
@ -67,7 +67,6 @@ const mockDomains: Domain[] = [
]; ];
const mockSearchResponse = { const mockSearchResponse = {
data: {
hits: { hits: {
hits: mockDomains.map((domain) => ({ hits: mockDomains.map((domain) => ({
_source: domain, _source: domain,
@ -77,16 +76,11 @@ const mockSearchResponse = {
total: { value: mockDomains.length }, total: { value: mockDomains.length },
}, },
aggregations: {}, aggregations: {},
},
status: 200,
statusText: 'OK',
headers: {},
config: {},
} as any; } as any;
// Mock API functions // Mock API functions
jest.mock('../../../../rest/miscAPI', () => ({ jest.mock('../../../../rest/searchAPI', () => ({
searchData: jest.fn(), searchQuery: jest.fn(),
})); }));
jest.mock('../../../../constants/Widgets.constant', () => ({ jest.mock('../../../../constants/Widgets.constant', () => ({
@ -99,7 +93,7 @@ jest.mock('../../../../utils/DomainUtils', () => ({
getDomainIcon: jest.fn().mockReturnValue(<div data-testid="domain-icon" />), getDomainIcon: jest.fn().mockReturnValue(<div data-testid="domain-icon" />),
})); }));
const mockSearchData = searchData as jest.MockedFunction<typeof searchData>; const mockSearchQuery = searchQuery as jest.MockedFunction<typeof searchQuery>;
const mockGetSortField = getSortField as jest.MockedFunction< const mockGetSortField = getSortField as jest.MockedFunction<
typeof getSortField typeof getSortField
@ -121,7 +115,7 @@ describe('DomainsWidget', () => {
mockGetSortField.mockReturnValue('updatedAt'); mockGetSortField.mockReturnValue('updatedAt');
mockGetSortOrder.mockReturnValue('desc'); mockGetSortOrder.mockReturnValue('desc');
mockApplySortToData.mockImplementation((data) => data); mockApplySortToData.mockImplementation((data) => data);
mockSearchData.mockResolvedValue(mockSearchResponse); mockSearchQuery.mockResolvedValue(mockSearchResponse);
}); });
const renderDomainsWidget = (props = {}) => { const renderDomainsWidget = (props = {}) => {
@ -161,15 +155,13 @@ describe('DomainsWidget', () => {
}); });
it('renders empty state when no domains', async () => { it('renders empty state when no domains', async () => {
mockSearchData.mockResolvedValue({ mockSearchQuery.mockResolvedValue({
...mockSearchResponse, ...mockSearchResponse,
data: {
hits: { hits: {
hits: [], hits: [],
total: { value: 0 }, total: { value: 0 },
}, },
aggregations: {}, aggregations: {},
},
}); });
renderDomainsWidget(); renderDomainsWidget();
@ -184,7 +176,7 @@ describe('DomainsWidget', () => {
}); });
it('renders error state when API fails', async () => { it('renders error state when API fails', async () => {
mockSearchData.mockRejectedValue(new Error('API Error')); mockSearchQuery.mockRejectedValue(new Error('API Error'));
renderDomainsWidget(); renderDomainsWidget();
@ -196,19 +188,18 @@ describe('DomainsWidget', () => {
}); });
}); });
it('calls searchData with correct parameters on mount', async () => { it('calls searchQuery with correct parameters on mount', async () => {
renderDomainsWidget(); renderDomainsWidget();
await waitFor(() => { await waitFor(() => {
expect(mockSearchData).toHaveBeenCalledWith( expect(mockSearchQuery).toHaveBeenCalledWith({
'', query: '',
1, pageNumber: 1,
PAGE_SIZE_MEDIUM, pageSize: PAGE_SIZE_MEDIUM,
'', sortField: 'updatedAt',
'updatedAt', sortOrder: 'desc',
'desc', searchIndex: 'domain_search_index',
'domain_search_index' });
);
}); });
}); });
@ -231,7 +222,7 @@ describe('DomainsWidget', () => {
// Simulate sort option selection - this would trigger the callback // Simulate sort option selection - this would trigger the callback
// Since the dropdown behavior is complex, we'll test the effect // Since the dropdown behavior is complex, we'll test the effect
// by verifying the API is called again with new sort parameters // by verifying the API is called again with new sort parameters
expect(mockSearchData).toHaveBeenCalled(); expect(mockSearchQuery).toHaveBeenCalled();
}); });
it('renders domains in full size layout', async () => { it('renders domains in full size layout', async () => {
@ -321,9 +312,8 @@ describe('DomainsWidget', () => {
}) })
); );
mockSearchData.mockResolvedValue({ mockSearchQuery.mockResolvedValue({
...mockSearchResponse, ...mockSearchResponse,
data: {
hits: { hits: {
hits: manyDomains.map((domain) => ({ hits: manyDomains.map((domain) => ({
_source: domain, _source: domain,
@ -333,7 +323,6 @@ describe('DomainsWidget', () => {
total: { value: manyDomains.length }, total: { value: manyDomains.length },
}, },
aggregations: {}, aggregations: {},
},
}); });
renderDomainsWidget(); renderDomainsWidget();
@ -358,7 +347,7 @@ describe('DomainsWidget', () => {
}); });
it('handles loading state correctly', () => { it('handles loading state correctly', () => {
mockSearchData.mockImplementation( mockSearchQuery.mockImplementation(
() => () =>
new Promise(() => { new Promise(() => {
// Never resolves to simulate loading state // Never resolves to simulate loading state
@ -377,9 +366,8 @@ describe('DomainsWidget', () => {
assets: undefined, assets: undefined,
}; };
mockSearchData.mockResolvedValue({ mockSearchQuery.mockResolvedValue({
...mockSearchResponse, ...mockSearchResponse,
data: {
hits: { hits: {
hits: [ hits: [
{ {
@ -391,7 +379,6 @@ describe('DomainsWidget', () => {
total: { value: 1 }, total: { value: 1 },
}, },
aggregations: {}, aggregations: {},
},
}); });
renderDomainsWidget(); renderDomainsWidget();

View File

@ -36,7 +36,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 { getDomainIcon } from '../../../../utils/DomainUtils'; import { getDomainIcon } from '../../../../utils/DomainUtils';
import { getDomainDetailsPath } from '../../../../utils/RouterUtils'; import { getDomainDetailsPath } from '../../../../utils/RouterUtils';
import ErrorPlaceHolder from '../../../common/ErrorWithPlaceholder/ErrorPlaceHolder'; import ErrorPlaceHolder from '../../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
@ -73,17 +73,16 @@ const DomainsWidget = ({
const sortField = getSortField(selectedSortBy); const sortField = getSortField(selectedSortBy);
const sortOrder = getSortOrder(selectedSortBy); const sortOrder = getSortOrder(selectedSortBy);
const res = await searchData( const res = await searchQuery({
'', query: '',
INITIAL_PAGING_VALUE, pageNumber: INITIAL_PAGING_VALUE,
PAGE_SIZE_MEDIUM, pageSize: PAGE_SIZE_MEDIUM,
'',
sortField, sortField,
sortOrder, sortOrder,
SearchIndex.DOMAIN searchIndex: SearchIndex.DOMAIN,
); });
const domains = res?.data?.hits?.hits.map((hit) => hit._source); const domains = res?.hits?.hits.map((hit) => hit._source);
const sortedDomains = applySortToData(domains, selectedSortBy); const sortedDomains = applySortToData(domains, selectedSortBy);
setDomains(sortedDomains as Domain[]); setDomains(sortedDomains as Domain[]);
} catch { } catch {

View File

@ -39,8 +39,8 @@ import { withPageLayout } from '../../hoc/withPageLayout';
import { useApplicationStore } from '../../hooks/useApplicationStore'; import { useApplicationStore } from '../../hooks/useApplicationStore';
import { useFqn } from '../../hooks/useFqn'; import { useFqn } from '../../hooks/useFqn';
import { FieldProp, FieldTypes } from '../../interface/FormUtils.interface'; import { FieldProp, FieldTypes } from '../../interface/FormUtils.interface';
import { searchData } from '../../rest/miscAPI';
import { postQuery } from '../../rest/queryAPI'; import { postQuery } from '../../rest/queryAPI';
import { searchQuery } from '../../rest/searchAPI';
import { getTableDetailsByFQN } from '../../rest/tableAPI'; import { getTableDetailsByFQN } from '../../rest/tableAPI';
import { getPartialNameFromFQN } from '../../utils/CommonUtils'; import { getPartialNameFromFQN } from '../../utils/CommonUtils';
import { getCurrentMillis } from '../../utils/date-time/DateTimeUtils'; import { getCurrentMillis } from '../../utils/date-time/DateTimeUtils';
@ -99,15 +99,12 @@ const AddQueryPage = () => {
searchValue = '' searchValue = ''
): Promise<DefaultOptionType[]> => { ): Promise<DefaultOptionType[]> => {
try { try {
const { data } = await searchData( const data = await searchQuery({
searchValue, query: searchValue,
INITIAL_PAGING_VALUE, pageNumber: INITIAL_PAGING_VALUE,
PAGE_SIZE_MEDIUM, pageSize: PAGE_SIZE_MEDIUM,
'', searchIndex: SearchIndex.TABLE,
'', });
'',
SearchIndex.TABLE
);
const options = data.hits.hits.map((value) => ({ const options = data.hits.hits.map((value) => ({
label: getEntityLabel(value._source), label: getEntityLabel(value._source),
value: value._source.id, value: value._source.id,

View File

@ -22,8 +22,8 @@ jest.mock('../../rest/tableAPI', () => ({
jest.mock('../../rest/queryAPI', () => ({ jest.mock('../../rest/queryAPI', () => ({
postQuery: jest.fn().mockImplementation(() => Promise.resolve()), postQuery: jest.fn().mockImplementation(() => Promise.resolve()),
})); }));
jest.mock('../../rest/miscAPI', () => ({ jest.mock('../../rest/searchAPI', () => ({
searchData: jest.fn().mockImplementation(() => Promise.resolve()), searchQuery: jest.fn().mockImplementation(() => Promise.resolve()),
})); }));
jest.mock('react-router-dom', () => ({ jest.mock('react-router-dom', () => ({
useParams: jest.fn().mockReturnValue({ fqn: MOCK_TABLE.fullyQualifiedName }), useParams: jest.fn().mockReturnValue({ fqn: MOCK_TABLE.fullyQualifiedName }),

View File

@ -176,55 +176,14 @@ export const rawSearchQuery = <
const queryWithSlash = getQueryWithSlash(query || ''); const queryWithSlash = getQueryWithSlash(query || '');
// Build structured queryFilter to handle filters properly const apiQuery =
let structuredQueryFilter = queryFilter; query && query !== '**'
? filters
? `${queryWithSlash} AND `
: queryWithSlash
: '';
// If filters is provided (for backward compatibility with simple field:value filters) const apiUrl = `/search/query?q=${apiQuery}${filters ?? ''}`;
// Combine it with the existing queryFilter
if (filters) {
// Check if filters contains AND or OR operators (which shouldn't happen in new code)
if (filters.includes(' AND ') || filters.includes(' OR ')) {
// For backward compatibility, handle complex filters with AND/OR using query_string
const filterQuery = {
query_string: {
query: filters,
default_field: '*',
},
};
structuredQueryFilter = queryFilter
? {
query: {
bool: {
must: [queryFilter.query || queryFilter, filterQuery],
},
},
}
: { query: filterQuery };
} else if (filters.includes(':')) {
// Simple field:value filters (backward compatibility)
const filterQuery = {
query_string: {
query: filters,
default_field: '*',
},
};
structuredQueryFilter = queryFilter
? {
query: {
bool: {
must: [queryFilter.query || queryFilter, filterQuery],
},
},
}
: { query: filterQuery };
}
}
// The q parameter should only contain the search text, no filters
const apiQuery = query && query !== '**' ? queryWithSlash : '';
const apiUrl = `/search/query?q=${apiQuery}`;
return APIClient.get< return APIClient.get<
SearchResponse< SearchResponse<
@ -237,7 +196,7 @@ export const rawSearchQuery = <
from: (pageNumber - 1) * pageSize, from: (pageNumber - 1) * pageSize,
size: pageSize, size: pageSize,
deleted: includeDeleted, deleted: includeDeleted,
query_filter: JSON.stringify(structuredQueryFilter), query_filter: JSON.stringify(queryFilter),
post_filter: JSON.stringify(postFilter), post_filter: JSON.stringify(postFilter),
sort_field: sortField, sort_field: sortField,
sort_order: sortOrder, sort_order: sortOrder,