From 9c92beaecdcccd22e8ce9aeb8c535bbae136f722 Mon Sep 17 00:00:00 2001 From: Abhishek Porwal <80886271+Abhishek332@users.noreply.github.com> Date: Tue, 23 Jan 2024 17:45:38 +0530 Subject: [PATCH] fix(#14326): tier dropdown is not working in advance search (#14780) * improvement in advance search based on custom property * fix a reading undefined property issue * wip: advance search based on tier * some code cleanup and improvement * some fixes * fix: ui flicker when advanceSearched is apply and refresh the page * some cleanup * no need to call customproperty api call, if entity not suppport customProperties * minor change * fix: autocomplete not working in tier search option in advance search modal * added unit test for advance search provider component * some cleanup * added testcase for open modal * added testcase for resetAllFilters method * removed unwanted code * added e2e test for testing tier advance search * fix: e2e search flow for single field * fix: string field not working after giving listValues in TierSearch * fix: group query e2e test fix * used asyncFetch way to get the tierOptions synchronously * some cleanup * remove unwanted lines * some cleanup * fix: selected option show option value instead of option title --- .../ui/cypress/common/advancedSearch.js | 94 ++++++++----- .../Flow/AdvancedSearchQuickFilters.spec.js | 11 +- .../ui/cypress/e2e/Flow/SearchFlow.spec.js | 47 +++++-- .../Explore/AdvanceSearchModal.component.tsx | 59 +-------- .../AdvanceSearchProvider.component.tsx | 73 ++++++++--- .../AdvanceSearchProvider.test.tsx | 124 ++++++++++++++++++ .../ExploreV1/ExploreV1.component.tsx | 4 +- .../src/constants/AdvancedSearch.constants.ts | 45 +++++-- .../ui/src/utils/AdvancedSearchUtils.tsx | 23 +++- 9 files changed, 338 insertions(+), 142 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.test.tsx diff --git a/openmetadata-ui/src/main/resources/ui/cypress/common/advancedSearch.js b/openmetadata-ui/src/main/resources/ui/cypress/common/advancedSearch.js index df19359e75c..c93ac895436 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/common/advancedSearch.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/common/advancedSearch.js @@ -138,16 +138,15 @@ export const FIELDS = { searchCriteriaSecondGroup: 'PersonalData.SpecialCategory', responseValueSecondGroup: '"tagFQN":"PersonalData.SpecialCategory"', }, - // skipping tier for now, as it is not working, BE need to fix it - - // Tiers: { - // name: 'Tier', - // testid: '[title="Tier"]', - // searchCriteriaFirstGroup: 'Tier.Tier1', - // responseValueFirstGroup: '"tagFQN":"Tier.Tier1"', - // searchCriteriaSecondGroup: 'Tier.Tier2', - // responseValueSecondGroup: '"tagFQN":"Tier.Tier2"', - // }, + Tiers: { + name: 'Tier', + testid: '[title="Tier"]', + searchCriteriaFirstGroup: 'Tier.Tier1', + responseValueFirstGroup: '"tagFQN":"Tier.Tier1"', + searchCriteriaSecondGroup: 'Tier.Tier2', + responseValueSecondGroup: '"tagFQN":"Tier.Tier2"', + isLocalSearch: true, + }, Service: { name: 'Service', testid: '[title="Service"]', @@ -193,12 +192,21 @@ export const OPERATOR = { }, }; -export const searchForField = (condition, fieldid, searchCriteria, index) => { - interceptURL('GET', '/api/v1/search/aggregate?*', 'suggestApi'); +export const searchForField = ( + condition, + fieldId, + searchCriteria, + index, + isLocalSearch = false +) => { + if (!isLocalSearch) { + interceptURL('GET', '/api/v1/search/aggregate?*', 'suggestApi'); + } + // Click on field dropdown cy.get('.rule--field > .ant-select > .ant-select-selector').eq(index).click(); // Select owner fields - cy.get(`${fieldid}`).eq(index).click(); + cy.get(`${fieldId}`).eq(index).click(); // Select the condition cy.get('.rule--operator > .ant-select > .ant-select-selector') .eq(index) @@ -219,8 +227,16 @@ export const searchForField = (condition, fieldid, searchCriteria, index) => { cy.get('.widget--widget > .ant-select > .ant-select-selector') .eq(index) .type(searchCriteria); + + // checking filter is working + cy.get( + `.ant-select-item-option-active[title="${searchCriteria}"]` + ).should('be.visible'); + // select value from dropdown - verifyResponseStatusCode('@suggestApi', 200); + if (!isLocalSearch) { + verifyResponseStatusCode('@suggestApi', 200); + } cy.get(`.ant-select-dropdown [title = '${searchCriteria}']`) .trigger('mouseover') .trigger('click'); @@ -240,12 +256,13 @@ export const checkmustPaths = ( field, searchCriteria, index, - responseSearch + responseSearch, + isLocalSearch ) => { goToAdvanceSearch(); // Search with advance search - searchForField(condition, field, searchCriteria, index); + searchForField(condition, field, searchCriteria, index, isLocalSearch); interceptURL( 'GET', @@ -271,12 +288,13 @@ export const checkmust_notPaths = ( field, searchCriteria, index, - responseSearch + responseSearch, + isLocalSearch ) => { goToAdvanceSearch(); // Search with advance search - searchForField(condition, field, searchCriteria, index); + searchForField(condition, field, searchCriteria, index, isLocalSearch); interceptURL( 'GET', `/api/v1/search/query?q=&index=*&from=0&size=10&deleted=false&query_filter=*must_not*${encodeURI( @@ -417,16 +435,17 @@ export const addTag = ({ tag, term, serviceName, entity }) => { export const checkAddGroupWithOperator = ( condition_1, condition_2, - fieldid, + fieldId, searchCriteria_1, searchCriteria_2, index_1, index_2, - operatorindex, + operatorIndex, filter_1, filter_2, response_1, - response_2 + response_2, + isLocalSearch = false ) => { goToAdvanceSearch(); // Click on field dropdown @@ -435,7 +454,7 @@ export const checkAddGroupWithOperator = ( .should('be.visible') .click(); // Select owner fields - cy.get(fieldid).eq(0).should('be.visible').click(); + cy.get(fieldId).eq(0).should('be.visible').click(); // Select the condition cy.get('.rule--operator > .ant-select > .ant-select-selector') .eq(index_1) @@ -456,13 +475,17 @@ export const checkAddGroupWithOperator = ( .should('be.visible') .type(searchCriteria_1); } else { - interceptURL('GET', '/api/v1/search/aggregate?*', 'suggestApi'); + if (!isLocalSearch) { + interceptURL('GET', '/api/v1/search/aggregate?*', 'suggestApi'); + } cy.get('.widget--widget > .ant-select > .ant-select-selector') .eq(index_1) .should('be.visible') .type(searchCriteria_1); - verifyResponseStatusCode('@suggestApi', 200); + if (!isLocalSearch) { + verifyResponseStatusCode('@suggestApi', 200); + } cy.get('.ant-select-dropdown') .not('.ant-select-dropdown-hidden') .find(`[title="${searchCriteria_1}"]`) @@ -484,13 +507,13 @@ export const checkAddGroupWithOperator = ( // Select the AND/OR condition cy.get( - `.group--conjunctions > .ant-btn-group > :nth-child(${operatorindex})` + `.group--conjunctions > .ant-btn-group > :nth-child(${operatorIndex})` ).click(); // Click on field dropdown cy.get('.rule--field').eq(index_2).should('be.visible').click(); - cy.get(fieldid).eq(2).should('be.visible').click(); + cy.get(fieldId).eq(2).should('be.visible').click(); // Select the condition cy.get('.rule--operator').eq(index_2).should('be.visible').click(); @@ -509,12 +532,17 @@ export const checkAddGroupWithOperator = ( .should('be.visible') .type(searchCriteria_2); } else { - interceptURL('GET', '/api/v1/search/aggregate?*', 'suggestApi'); + if (!isLocalSearch) { + interceptURL('GET', '/api/v1/search/aggregate?*', 'suggestApi'); + } cy.get('.widget--widget > .ant-select > .ant-select-selector') .eq(index_2) .should('be.visible') .type(searchCriteria_2); - verifyResponseStatusCode('@suggestApi', 200); + + if (!isLocalSearch) { + verifyResponseStatusCode('@suggestApi', 200); + } cy.get('.ant-select-dropdown') .not('.ant-select-dropdown-hidden') @@ -547,12 +575,12 @@ export const checkAddGroupWithOperator = ( export const checkAddRuleWithOperator = ( condition_1, condition_2, - fieldid, + fieldId, searchCriteria_1, searchCriteria_2, index_1, index_2, - operatorindex, + operatorIndex, filter_1, filter_2, response_1, @@ -562,7 +590,7 @@ export const checkAddRuleWithOperator = ( // Click on field dropdown cy.get('.rule--field').eq(index_1).should('be.visible').click(); // Select owner fields - cy.get(fieldid).eq(0).should('be.visible').click(); + cy.get(fieldId).eq(0).should('be.visible').click(); // Select the condition cy.get('.rule--operator').eq(index_1).should('be.visible').click(); @@ -604,13 +632,13 @@ export const checkAddRuleWithOperator = ( // Select the AND/OR condition cy.get( - `.group--conjunctions > .ant-btn-group > :nth-child(${operatorindex})` + `.group--conjunctions > .ant-btn-group > :nth-child(${operatorIndex})` ).click(); // Click on field dropdown cy.get('.rule--field').eq(index_2).should('be.visible').click(); - cy.get(fieldid).eq(2).should('be.visible').click(); + cy.get(fieldId).eq(2).should('be.visible').click(); // Select the condition cy.get('.rule--operator').eq(index_2).should('be.visible').click(); diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/AdvancedSearchQuickFilters.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/AdvancedSearchQuickFilters.spec.js index ae8b80b3933..bf4c614cf41 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/AdvancedSearchQuickFilters.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/AdvancedSearchQuickFilters.spec.js @@ -11,7 +11,11 @@ * limitations under the License. */ -import { addOwner, removeOwner } from '../../common/advancedSearch'; +import { + addOwner, + goToAdvanceSearch, + removeOwner, +} from '../../common/advancedSearch'; import { searchAndClickOnOption } from '../../common/advancedSearchQuickFilters'; import { interceptURL, verifyResponseStatusCode } from '../../common/common'; import { QUICK_FILTERS_BY_ASSETS } from '../../constants/advancedSearchQuickFilters.constants'; @@ -78,10 +82,7 @@ describe(`Advanced search quick filters should work properly for assets`, () => }); const testIsNullAndIsNotNullFilters = (operatorTitle, queryFilter, alias) => { - cy.sidebarClick(SidebarItem.EXPLORE); - const asset = QUICK_FILTERS_BY_ASSETS[0]; - cy.get(`[data-testid="${asset.tab}"]`).scrollIntoView().click(); - cy.get('[data-testid="advance-search-button"]').click(); + goToAdvanceSearch(); // Check Is Null or Is Not Null cy.get('.rule--operator > .ant-select > .ant-select-selector').eq(0).click(); diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/SearchFlow.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/SearchFlow.spec.js index 1b2afdf8a58..c91099d9bf8 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/SearchFlow.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/SearchFlow.spec.js @@ -68,9 +68,12 @@ describe('Advance search', () => { checkmustPaths( condition.name, field.testid, - Cypress._.toLower(field.searchCriteriaFirstGroup), + field.isLocalSearch + ? field.searchCriteriaFirstGroup + : Cypress._.toLower(field.searchCriteriaFirstGroup), 0, - field.responseValueFirstGroup + field.responseValueFirstGroup, + field.isLocalSearch ); }); @@ -78,9 +81,12 @@ describe('Advance search', () => { checkmust_notPaths( condition.name, field.testid, - Cypress._.toLower(field.searchCriteriaFirstGroup), + field.isLocalSearch + ? field.searchCriteriaFirstGroup + : Cypress._.toLower(field.searchCriteriaFirstGroup), 0, - field.responseValueFirstGroup + field.responseValueFirstGroup, + field.isLocalSearch ); }); }); @@ -108,15 +114,20 @@ describe('Advance search', () => { CONDITIONS_MUST.equalTo.name, CONDITIONS_MUST_NOT.notEqualTo.name, field.testid, - Cypress._.toLower(field.searchCriteriaFirstGroup), - Cypress._.toLower(field.searchCriteriaSecondGroup), + field.isLocalSearch + ? field.searchCriteriaFirstGroup + : Cypress._.toLower(field.searchCriteriaFirstGroup), + field.isLocalSearch + ? field.searchCriteriaSecondGroup + : Cypress._.toLower(field.searchCriteriaSecondGroup), 0, 1, operator.index, CONDITIONS_MUST.equalTo.filter, CONDITIONS_MUST_NOT.notEqualTo.filter, field.responseValueFirstGroup, - val + val, + field.isLocalSearch ); }); }); @@ -131,15 +142,20 @@ describe('Advance search', () => { CONDITIONS_MUST.anyIn.name, CONDITIONS_MUST_NOT.notIn.name, field.testid, - Cypress._.toLower(field.searchCriteriaFirstGroup), - Cypress._.toLower(field.searchCriteriaSecondGroup), + field.isLocalSearch + ? field.searchCriteriaFirstGroup + : Cypress._.toLower(field.searchCriteriaFirstGroup), + field.isLocalSearch + ? field.searchCriteriaSecondGroup + : Cypress._.toLower(field.searchCriteriaSecondGroup), 0, 1, operator.index, CONDITIONS_MUST.anyIn.filter, CONDITIONS_MUST_NOT.notIn.filter, field.responseValueFirstGroup, - val + val, + field.isLocalSearch ); }); }); @@ -152,15 +168,20 @@ describe('Advance search', () => { CONDITIONS_MUST.contains.name, CONDITIONS_MUST_NOT.notContains.name, field.testid, - Cypress._.toLower(field.searchCriteriaFirstGroup), - Cypress._.toLower(field.searchCriteriaSecondGroup), + field.isLocalSearch + ? field.searchCriteriaFirstGroup + : Cypress._.toLower(field.searchCriteriaFirstGroup), + field.isLocalSearch + ? field.searchCriteriaSecondGroup + : Cypress._.toLower(field.searchCriteriaSecondGroup), 0, 1, operator.index, CONDITIONS_MUST.contains.filter, CONDITIONS_MUST_NOT.notContains.filter, field.responseValueFirstGroup, - val + val, + field.isLocalSearch ); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchModal.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchModal.component.tsx index 523ccd8c34c..a01f27b8655 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchModal.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchModal.component.tsx @@ -12,18 +12,9 @@ */ import { Button, Modal, Space, Typography } from 'antd'; -import { cloneDeep } from 'lodash'; -import React, { FunctionComponent, useEffect } from 'react'; -import { - Builder, - FieldGroup, - Query, - ValueSource, -} from 'react-awesome-query-builder'; +import React, { FunctionComponent } from 'react'; +import { Builder, Query } from 'react-awesome-query-builder'; import { useTranslation } from 'react-i18next'; -import { getTypeByFQN } from '../../rest/metadataTypeAPI'; -import { EntitiesSupportedCustomProperties } from '../../utils/CustomProperties/CustomProperty.utils'; -import { getEntityTypeFromSearchIndex } from '../../utils/SearchUtils'; import './advanced-search-modal.less'; import { useAdvanceSearch } from './AdvanceSearchProvider/AdvanceSearchProvider.component'; @@ -39,51 +30,7 @@ export const AdvancedSearchModal: FunctionComponent = ({ onCancel, }: Props) => { const { t } = useTranslation(); - const { - config, - treeInternal, - onTreeUpdate, - onReset, - onUpdateConfig, - searchIndex, - } = useAdvanceSearch(); - - const updatedConfig = cloneDeep(config); - - async function getCustomAttributesSubfields() { - try { - const entityType = getEntityTypeFromSearchIndex(searchIndex); - if (!entityType) { - return; - } - const res = await getTypeByFQN(entityType); - const customAttributes = res.customProperties; - - const subfields: Record< - string, - { type: string; valueSources: ValueSource[] } - > = {}; - - if (customAttributes) { - customAttributes.forEach((attr) => { - subfields[attr.name] = { - type: 'text', - valueSources: ['value'], - }; - }); - } - (updatedConfig.fields.extension as FieldGroup).subfields = subfields; - onUpdateConfig(updatedConfig); - } catch (error) { - // Error - } - } - - useEffect(() => { - if (visible && EntitiesSupportedCustomProperties.includes(searchIndex)) { - getCustomAttributesSubfields(); - } - }, [visible, searchIndex]); + const { config, treeInternal, onTreeUpdate, onReset } = useAdvanceSearch(); return ( ( export const AdvanceSearchProvider = ({ children, }: AdvanceSearchProviderProps) => { + const tierOptions = useMemo(getTierOptions, []); + const tabsInfo = searchClassBase.getTabsInfo(); const location = useLocation(); const history = useHistory(); @@ -68,7 +71,10 @@ export const AdvanceSearchProvider = ({ return tabInfo[0] as ExploreSearchIndex; }, [tab]); - const [config, setConfig] = useState(getQbConfigs(searchIndex)); + const [config, setConfig] = useState( + getQbConfigs(searchIndex, tierOptions) + ); + const [initialised, setInitialised] = useState(false); const defaultTree = useMemo( () => QbUtils.checkTree(QbUtils.loadTree(emptyJsonTree), config), @@ -117,7 +123,7 @@ export const AdvanceSearchProvider = ({ ); useEffect(() => { - setConfig(getQbConfigs(searchIndex)); + setConfig(getQbConfigs(searchIndex, tierOptions)); }, [searchIndex]); const handleChange = useCallback( @@ -150,7 +156,7 @@ export const AdvanceSearchProvider = ({ setTreeInternal(QbUtils.checkTree(QbUtils.loadTree(emptyJsonTree), config)); setQueryFilter(undefined); setSQLQuery(''); - }, []); + }, [config]); const handleConfigUpdate = (updatedConfig: Config) => { setConfig(updatedConfig); @@ -171,20 +177,24 @@ export const AdvanceSearchProvider = ({ }, [history, location.pathname]); async function getCustomAttributesSubfields() { - const updatedConfig = cloneDeep(config); + const subfields: Record< + string, + { type: string; valueSources: ValueSource[] } + > = {}; + try { + if (!EntitiesSupportedCustomProperties.includes(searchIndex)) { + return subfields; + } + const entityType = getEntityTypeFromSearchIndex(searchIndex); if (!entityType) { - return; + return subfields; } + const res = await getTypeByFQN(entityType); const customAttributes = res.customProperties; - const subfields: Record< - string, - { type: string; valueSources: ValueSource[] } - > = {}; - if (customAttributes) { customAttributes.forEach((attr) => { subfields[attr.name] = { @@ -193,30 +203,55 @@ export const AdvanceSearchProvider = ({ }; }); } - (updatedConfig.fields.extension as FieldGroup).subfields = subfields; - return updatedConfig; + return subfields; } catch (error) { // Error - return updatedConfig; + return subfields; } } + const loadData = async () => { + const actualConfig = getQbConfigs(searchIndex, tierOptions); + + const extensionSubField = await getCustomAttributesSubfields(); + + if (!isEmpty(extensionSubField)) { + (actualConfig.fields.extension as FieldGroup).subfields = + extensionSubField; + } + + setConfig(actualConfig); + setInitialised(true); + }; + const loadTree = useCallback( async (treeObj: JsonTree) => { - const updatedConfig = (await getCustomAttributesSubfields()) ?? config; + const updatedConfig = config; const tree = QbUtils.checkTree(QbUtils.loadTree(treeObj), updatedConfig); + setTreeInternal(tree); const qFilter = { query: elasticSearchFormat(tree, updatedConfig), }; + if (isEqual(qFilter, queryFilter)) { + return; + } + setQueryFilter(qFilter); setSQLQuery(QbUtils.sqlFormat(tree, updatedConfig) ?? ''); }, - [config] + [config, queryFilter] ); useEffect(() => { + loadData(); + }, [searchIndex]); + + useEffect(() => { + if (!initialised) { + return; + } if (jsonTree) { loadTree(jsonTree); } else { @@ -224,7 +259,7 @@ export const AdvanceSearchProvider = ({ } setLoading(false); - }, [jsonTree]); + }, [jsonTree, initialised]); const handleSubmit = useCallback(() => { const qFilter = { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.test.tsx new file mode 100644 index 00000000000..5b40f65fb66 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.test.tsx @@ -0,0 +1,124 @@ +/* + * Copyright 2024 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { act, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { + AdvanceSearchProvider, + useAdvanceSearch, +} from './AdvanceSearchProvider.component'; + +jest.mock('../../../rest/metadataTypeAPI', () => ({ + getTypeByFQN: jest.fn().mockResolvedValue({}), +})); + +jest.mock('../../../rest/tagAPI', () => ({ + getTags: jest.fn().mockResolvedValue({}), +})); + +jest.mock('../AdvanceSearchModal.component', () => ({ + AdvancedSearchModal: jest + .fn() + .mockImplementation(({ visible, onSubmit, onCancel }) => ( + <> + {visible ? ( +

AdvanceSearchModal Open

+ ) : ( +

AdvanceSearchModal Close

+ )} + + + + )), +})); + +jest.mock('../../Loader/Loader', () => + jest.fn().mockReturnValue(
Loader
) +); + +const mockPush = jest.fn(); + +jest.mock('react-router-dom', () => ({ + useLocation: jest.fn().mockReturnValue({ + search: 'queryFilter={"some":"value"}', + }), + useParams: jest.fn().mockReturnValue({ + tab: 'tabValue', + }), + useHistory: jest.fn().mockImplementation(() => ({ + push: mockPush, + })), +})); + +const Children = () => { + const { toggleModal, onResetAllFilters } = useAdvanceSearch(); + + return ( + <> + + + + ); +}; + +const mockWithAdvanceSearch = + (Component: React.FC) => + (props: JSX.IntrinsicAttributes & { children?: React.ReactNode }) => { + return ( + + + + ); + }; + +const ComponentWithProvider = mockWithAdvanceSearch(Children); + +describe('AdvanceSearchProvider component', () => { + it('should render the AdvanceSearchModal as close by default', () => { + render(); + + expect(screen.getByText('AdvanceSearchModal Close')).toBeInTheDocument(); + }); + + it('should call mockPush after submit advance search form', async () => { + render(); + + userEvent.click(screen.getByText('Apply Advance Search')); + + expect(mockPush).toHaveBeenCalled(); + }); + + it('should open the AdvanceSearchModal on call of toggleModal with true', async () => { + await act(async () => { + render(); + }); + + expect(screen.getByText('AdvanceSearchModal Close')).toBeInTheDocument(); + + userEvent.click(screen.getByText('Open AdvanceSearch Modal')); + + expect(screen.getByText('AdvanceSearchModal Open')).toBeInTheDocument(); + }); + + it('onResetAllFilters call mockPush should be called', async () => { + await act(async () => { + render(); + }); + + userEvent.click(screen.getByText('Reset All Filters')); + + expect(mockPush).toHaveBeenCalled(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.component.tsx index 8d531745feb..5db22a90142 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.component.tsx @@ -219,7 +219,7 @@ const ExploreV1: React.FC = ({ handleSummaryPanelDisplay( highlightEntityNameAndDescription( firstEntity._source, - firstEntity.highlight + firstEntity?.highlight ) ); } else { @@ -356,7 +356,7 @@ const ExploreV1: React.FC = ({ handleClosePanel={handleClosePanel} highlights={omit( { - ...firstEntity.highlight, // highlights of firstEntity that we get from the query api + ...firstEntity?.highlight, // highlights of firstEntity that we get from the query api 'tag.name': ( selectedQuickFilters?.find( (filterOption) => filterOption.key === TAG_FQN_KEY diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/AdvancedSearch.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/AdvancedSearch.constants.ts index eb62d5e33e5..7889ee982c6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/AdvancedSearch.constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/AdvancedSearch.constants.ts @@ -13,9 +13,12 @@ import { t } from 'i18next'; import { + AsyncFetchListValues, + AsyncFetchListValuesResult, BasicConfig, Fields, JsonTree, + ListItem, SelectFieldSettings, Utils as QbUtils, } from 'react-awesome-query-builder'; @@ -304,6 +307,23 @@ export const autocomplete: (args: { }; }; +export const autoCompleteTier: ( + tierOptions: Promise +) => SelectFieldSettings['asyncFetch'] = (tierOptions) => { + return async (search) => { + const resolvedTierOptions = (await tierOptions) as ListItem[]; + + return { + values: !search + ? resolvedTierOptions + : resolvedTierOptions.filter((tier) => + tier.title?.toLowerCase()?.includes(search.toLowerCase()) + ), + hasMore: false, + } as AsyncFetchListValuesResult; + }; +}; + const mainWidgetProps = { fullWidth: true, valueLabel: t('label.criteria') + ':', @@ -313,7 +333,8 @@ const mainWidgetProps = { * Common fields that exit for all searchable entities */ const getCommonQueryBuilderFields = ( - entitySearchIndex: SearchIndex = SearchIndex.TABLE + entitySearchIndex: SearchIndex = SearchIndex.TABLE, + tierOptions: Promise = Promise.resolve([]) ) => { const commonQueryBuilderFields: Fields = { deleted: { @@ -356,10 +377,7 @@ const getCommonQueryBuilderFields = ( type: 'select', mainWidgetProps, fieldSettings: { - asyncFetch: autocomplete({ - searchIndex: entitySearchIndex ?? [SearchIndex.TAG], - entityField: EntityFields.TIER, - }), + asyncFetch: autoCompleteTier(tierOptions), useAsyncSearch: true, }, }, @@ -522,15 +540,16 @@ const getInitialConfigWithoutFields = () => { /** * Builds search index specific configuration for the query builder */ -export const getQbConfigs: (searchIndex: SearchIndex) => BasicConfig = ( - searchIndex -) => { +export const getQbConfigs: ( + searchIndex: SearchIndex, + tierOptions: Promise +) => BasicConfig = (searchIndex, tierOptions) => { switch (searchIndex) { case SearchIndex.MLMODEL: return { ...getInitialConfigWithoutFields(), fields: { - ...getCommonQueryBuilderFields(SearchIndex.MLMODEL), + ...getCommonQueryBuilderFields(SearchIndex.MLMODEL, tierOptions), ...getServiceQueryBuilderFields(SearchIndex.MLMODEL), }, }; @@ -539,7 +558,7 @@ export const getQbConfigs: (searchIndex: SearchIndex) => BasicConfig = ( return { ...getInitialConfigWithoutFields(), fields: { - ...getCommonQueryBuilderFields(SearchIndex.PIPELINE), + ...getCommonQueryBuilderFields(SearchIndex.PIPELINE, tierOptions), ...getServiceQueryBuilderFields(SearchIndex.PIPELINE), }, }; @@ -548,7 +567,7 @@ export const getQbConfigs: (searchIndex: SearchIndex) => BasicConfig = ( return { ...getInitialConfigWithoutFields(), fields: { - ...getCommonQueryBuilderFields(SearchIndex.DASHBOARD), + ...getCommonQueryBuilderFields(SearchIndex.DASHBOARD, tierOptions), ...getServiceQueryBuilderFields(SearchIndex.DASHBOARD), }, }; @@ -557,7 +576,7 @@ export const getQbConfigs: (searchIndex: SearchIndex) => BasicConfig = ( return { ...getInitialConfigWithoutFields(), fields: { - ...getCommonQueryBuilderFields(SearchIndex.TABLE), + ...getCommonQueryBuilderFields(SearchIndex.TABLE, tierOptions), ...getServiceQueryBuilderFields(SearchIndex.TABLE), ...tableQueryBuilderFields, }, @@ -567,7 +586,7 @@ export const getQbConfigs: (searchIndex: SearchIndex) => BasicConfig = ( return { ...getInitialConfigWithoutFields(), fields: { - ...getCommonQueryBuilderFields(SearchIndex.TOPIC), + ...getCommonQueryBuilderFields(SearchIndex.TOPIC, tierOptions), ...getServiceQueryBuilderFields(SearchIndex.TOPIC), }, }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx index 0a1f350cbca..093cf940648 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx @@ -16,7 +16,10 @@ import { Button, Checkbox, MenuProps, Space, Typography } from 'antd'; import i18next from 'i18next'; import { isArray, isEmpty } from 'lodash'; import React from 'react'; -import { RenderSettings } from 'react-awesome-query-builder'; +import { + AsyncFetchListValues, + RenderSettings, +} from 'react-awesome-query-builder'; import ProfilePicture from '../components/common/ProfilePicture/ProfilePicture'; import { AssetsOfEntity } from '../components/Glossary/GlossaryTerms/tabs/AssetsTabs.interface'; import { SearchDropdownOption } from '../components/SearchDropdown/SearchDropdown.interface'; @@ -38,6 +41,7 @@ import { TableSearchSource, TopicSearchSource, } from '../interface/search.interface'; +import { getTags } from '../rest/tagAPI'; import { getCountBadge } from '../utils/CommonUtils'; import { getEntityName } from './EntityUtils'; import searchClassBase from './SearchClassBase'; @@ -393,3 +397,20 @@ export const getOptionsFromAggregationBucket = (buckets: Bucket[]) => { count: option.doc_count ?? 0, })); }; + +export const getTierOptions: () => Promise = async () => { + try { + const { data: tiers } = await getTags({ + parent: 'Tier', + }); + + const tierFields = tiers.map((tier) => ({ + title: tier.fullyQualifiedName, // tier.name, + value: tier.fullyQualifiedName, + })); + + return tierFields; + } catch (error) { + return []; + } +};