mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-30 03:46:10 +00:00
* 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
This commit is contained in:
parent
740541c0c7
commit
9c92beaecd
@ -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) => {
|
||||
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
|
||||
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 {
|
||||
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);
|
||||
|
||||
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 {
|
||||
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);
|
||||
|
||||
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();
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -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<Props> = ({
|
||||
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 (
|
||||
<Modal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* 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
|
||||
@ -10,8 +10,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { cloneDeep, isNil, isString } from 'lodash';
|
||||
import { isEmpty, isEqual, isNil, isString } from 'lodash';
|
||||
import Qs from 'qs';
|
||||
import React, {
|
||||
useCallback,
|
||||
@ -35,6 +34,8 @@ import {
|
||||
} from '../../../constants/AdvancedSearch.constants';
|
||||
import { SearchIndex } from '../../../enums/search.enum';
|
||||
import { getTypeByFQN } from '../../../rest/metadataTypeAPI';
|
||||
import { getTierOptions } from '../../../utils/AdvancedSearchUtils';
|
||||
import { EntitiesSupportedCustomProperties } from '../../../utils/CustomProperties/CustomProperty.utils';
|
||||
import { elasticSearchFormat } from '../../../utils/QueryBuilderElasticsearchFormatUtils';
|
||||
import searchClassBase from '../../../utils/SearchClassBase';
|
||||
import { getEntityTypeFromSearchIndex } from '../../../utils/SearchUtils';
|
||||
@ -53,6 +54,8 @@ const AdvancedSearchContext = React.createContext<AdvanceSearchContext>(
|
||||
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<Config>(getQbConfigs(searchIndex));
|
||||
const [config, setConfig] = useState<Config>(
|
||||
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);
|
||||
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[] }
|
||||
> = {};
|
||||
|
||||
try {
|
||||
if (!EntitiesSupportedCustomProperties.includes(searchIndex)) {
|
||||
return subfields;
|
||||
}
|
||||
|
||||
const entityType = getEntityTypeFromSearchIndex(searchIndex);
|
||||
if (!entityType) {
|
||||
return subfields;
|
||||
}
|
||||
|
||||
const res = await getTypeByFQN(entityType);
|
||||
const customAttributes = res.customProperties;
|
||||
|
||||
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 = {
|
||||
|
@ -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 ? (
|
||||
<p>AdvanceSearchModal Open</p>
|
||||
) : (
|
||||
<p>AdvanceSearchModal Close</p>
|
||||
)}
|
||||
<button onClick={onSubmit}>Apply Advance Search</button>
|
||||
<button onClick={onCancel}>Close Modal</button>
|
||||
</>
|
||||
)),
|
||||
}));
|
||||
|
||||
jest.mock('../../Loader/Loader', () =>
|
||||
jest.fn().mockReturnValue(<div>Loader</div>)
|
||||
);
|
||||
|
||||
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 (
|
||||
<>
|
||||
<button onClick={() => toggleModal(true)}>
|
||||
Open AdvanceSearch Modal
|
||||
</button>
|
||||
<button onClick={onResetAllFilters}>Reset All Filters</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mockWithAdvanceSearch =
|
||||
(Component: React.FC) =>
|
||||
(props: JSX.IntrinsicAttributes & { children?: React.ReactNode }) => {
|
||||
return (
|
||||
<AdvanceSearchProvider>
|
||||
<Component {...props} />
|
||||
</AdvanceSearchProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const ComponentWithProvider = mockWithAdvanceSearch(Children);
|
||||
|
||||
describe('AdvanceSearchProvider component', () => {
|
||||
it('should render the AdvanceSearchModal as close by default', () => {
|
||||
render(<ComponentWithProvider />);
|
||||
|
||||
expect(screen.getByText('AdvanceSearchModal Close')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call mockPush after submit advance search form', async () => {
|
||||
render(<ComponentWithProvider />);
|
||||
|
||||
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(<ComponentWithProvider />);
|
||||
});
|
||||
|
||||
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(<ComponentWithProvider />);
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByText('Reset All Filters'));
|
||||
|
||||
expect(mockPush).toHaveBeenCalled();
|
||||
});
|
||||
});
|
@ -219,7 +219,7 @@ const ExploreV1: React.FC<ExploreProps> = ({
|
||||
handleSummaryPanelDisplay(
|
||||
highlightEntityNameAndDescription(
|
||||
firstEntity._source,
|
||||
firstEntity.highlight
|
||||
firstEntity?.highlight
|
||||
)
|
||||
);
|
||||
} else {
|
||||
@ -356,7 +356,7 @@ const ExploreV1: React.FC<ExploreProps> = ({
|
||||
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
|
||||
|
@ -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<AsyncFetchListValues>
|
||||
) => 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<AsyncFetchListValues> = 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<AsyncFetchListValues>
|
||||
) => 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),
|
||||
},
|
||||
};
|
||||
|
@ -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<AsyncFetchListValues> = 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 [];
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user