fix custom property type not coming in advanced search (#17573)

* fix custom property type not coming in advance search

* added playwright test to test dashboard for same

* minor code optimization and added comments in code
This commit is contained in:
Ashish Gupta 2024-08-23 23:43:21 +05:30 committed by GitHub
parent ce19fa3389
commit d81a57c44e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 265 additions and 27 deletions

View File

@ -0,0 +1,158 @@
/*
* 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 { expect, test } from '@playwright/test';
import { CUSTOM_PROPERTIES_ENTITIES } from '../../../constant/customProperty';
import { GlobalSettingOptions } from '../../../constant/settings';
import { SidebarItem } from '../../../constant/sidebar';
import { DashboardClass } from '../../../support/entity/DashboardClass';
import { createNewPage, redirectToHomePage, uuid } from '../../../utils/common';
import {
addCustomPropertiesForEntity,
deleteCreatedProperty,
} from '../../../utils/customProperty';
import { settingClick, sidebarClick } from '../../../utils/sidebar';
// use the admin user to login
test.use({ storageState: 'playwright/.auth/admin.json' });
const dashboardEntity = new DashboardClass();
const propertyName = `pwCustomPropertyDashboardTest${uuid()}`;
const propertyValue = 'dashboardcustomproperty';
test.beforeAll('Setup pre-requests', async ({ browser }) => {
const { apiContext, afterAction } = await createNewPage(browser);
await dashboardEntity.create(apiContext);
await afterAction();
});
test.afterAll('Cleanup', async ({ browser }) => {
const { apiContext, afterAction } = await createNewPage(browser);
await dashboardEntity.delete(apiContext);
await afterAction();
});
test('CustomProperty Dashboard Filter', async ({ page }) => {
test.slow(true);
await redirectToHomePage(page);
await test.step('Create Dashboard Custom Property', async () => {
await settingClick(page, GlobalSettingOptions.DASHBOARDS, true);
await addCustomPropertiesForEntity({
page,
propertyName,
customPropertyData: CUSTOM_PROPERTIES_ENTITIES['entity_dashboard'],
customType: 'String',
});
});
await test.step('Add Custom Property in Dashboard', async () => {
await dashboardEntity.visitEntityPage(page);
await page.getByTestId('custom_properties').click();
await page
.getByRole('row', { name: `${propertyName} No data` })
.locator('svg')
.click();
await page.getByTestId('value-input').fill(propertyValue);
const saveResponse = page.waitForResponse('/api/v1/dashboards/*');
await page.getByTestId('inline-save-btn').click();
await saveResponse;
expect(
page.getByLabel('Custom Properties').getByTestId('value')
).toContainText(propertyValue);
});
await test.step(
'Filter Dashboard using AdvanceSearch Custom Property',
async () => {
await redirectToHomePage(page);
const responseExplorePage = page.waitForResponse(
'/api/v1/metadata/types/name/storedProcedure?fields=customProperties'
);
await sidebarClick(page, SidebarItem.EXPLORE);
await responseExplorePage;
const responseCustomPropertyDashboard = page.waitForResponse(
'/api/v1/metadata/types/name/dashboard?fields=customProperties'
);
await page.getByTestId('explore-tree-title-Dashboards').click();
await responseCustomPropertyDashboard;
await page.getByTestId('advance-search-button').click();
await page.waitForSelector('[role="dialog"].ant-modal');
await expect(page.locator('[role="dialog"].ant-modal')).toBeVisible();
await expect(page.locator('.ant-modal-title')).toContainText(
'Advanced Search'
);
// Select Custom Property Filter
await page
.getByTestId('advanced-search-modal')
.getByText('Owner')
.click();
await page.getByTitle('Custom Properties').click();
// Select Custom Property Field when we want filter
await page
.locator(
'.group--children .rule--field .ant-select-selector .ant-select-selection-search'
)
.click();
await page.getByTitle(propertyName).click();
// type custom property value based, on which the filter should be made on dashboard
await page
.locator('.group--children .rule--widget .ant-input')
.fill(propertyValue);
const applyAdvanceFilter = page.waitForRequest('/api/v1/search/query?*');
await page.getByTestId('apply-btn').click();
await applyAdvanceFilter;
// Validate if filter dashboard appeared
expect(page.getByTestId('advance-search-filter-text')).toContainText(
`extension.${propertyName} = '${propertyValue}'`
);
expect(page.getByTestId('entity-header-display-name')).toContainText(
dashboardEntity.entity.displayName
);
}
);
await test.step('Delete Custom Property ', async () => {
await settingClick(page, GlobalSettingOptions.DASHBOARDS, true);
await deleteCreatedProperty(page, propertyName);
});
});

View File

@ -29,6 +29,7 @@ import {
} from 'react-awesome-query-builder';
import { useHistory, useLocation, useParams } from 'react-router-dom';
import { emptyJsonTree } from '../../../constants/AdvancedSearch.constants';
import { EntityType } from '../../../enums/entity.enum';
import { SearchIndex } from '../../../enums/search.enum';
import { getTypeByFQN } from '../../../rest/metadataTypeAPI';
import advancedSearchClassBase from '../../../utils/AdvancedSearchClassBase';
@ -202,6 +203,27 @@ export const AdvanceSearchProvider = ({
});
}, [history, location.pathname]);
const fetchCustomPropertyType = async (entityType: EntityType) => {
const subfields: Record<
string,
{ type: string; valueSources: ValueSource[] }
> = {};
const res = await getTypeByFQN(entityType);
const customAttributes = res.customProperties;
if (customAttributes) {
customAttributes.forEach((attr) => {
subfields[attr.name] = {
type: 'text',
valueSources: ['value'],
};
});
}
return subfields;
};
async function getCustomAttributesSubfields() {
const subfields: Record<
string,
@ -209,34 +231,39 @@ export const AdvanceSearchProvider = ({
> = {};
try {
if (
!EntitiesSupportedCustomProperties.includes(
isArray(searchIndex) ? searchIndex[0] : searchIndex
)
) {
if (isArray(searchIndex)) {
for await (const index of searchIndex) {
if (!EntitiesSupportedCustomProperties.includes(index)) {
continue; // Skip if entity type does not support custom properties
}
const entityType = getEntityTypeFromSearchIndex(index);
if (!entityType) {
continue; // Skip if entity type is not found
}
try {
const propertyTypes = await fetchCustomPropertyType(entityType);
Object.assign(subfields, propertyTypes); // Merge the subfields after each API call
} catch (error) {
continue; // continue the loop if error occurs in one API call
}
}
return subfields;
} else {
if (!EntitiesSupportedCustomProperties.includes(searchIndex)) {
return subfields;
}
const entityType = getEntityTypeFromSearchIndex(searchIndex);
if (!entityType) {
return subfields;
}
return await fetchCustomPropertyType(entityType);
}
const entityType = getEntityTypeFromSearchIndex(
isArray(searchIndex) ? searchIndex[0] : searchIndex
);
if (!entityType) {
return subfields;
}
const res = await getTypeByFQN(entityType);
const customAttributes = res.customProperties;
if (customAttributes) {
customAttributes.forEach((attr) => {
subfields[attr.name] = {
type: 'text',
valueSources: ['value'],
};
});
}
return subfields;
} catch (error) {
// Error
return subfields;

View File

@ -36,10 +36,12 @@ import {
} from '../../../utils/ExploreUtils';
import searchClassBase from '../../../utils/SearchClassBase';
import { EXPLORE_ROOT_INDEX_MAPPING } from '../../../constants/AdvancedSearch.constants';
import serviceUtilClassBase from '../../../utils/ServiceUtilClassBase';
import { generateUUID } from '../../../utils/StringsUtils';
import { showErrorToast } from '../../../utils/ToastUtils';
import { UrlParams } from '../ExplorePage.interface';
import { useAdvanceSearch } from '../AdvanceSearchProvider/AdvanceSearchProvider.component';
import { ExploreSearchIndex, UrlParams } from '../ExplorePage.interface';
import './explore-tree.less';
import {
ExploreTreeNode,
@ -64,6 +66,7 @@ const ExploreTreeTitle = ({ node }: { node: ExploreTreeNode }) => (
const ExploreTree = ({ onFieldValueSelect }: ExploreTreeProps) => {
const { tab } = useParams<UrlParams>();
const { onChangeSearchIndex } = useAdvanceSearch();
const initTreeData = searchClassBase.getExploreTree();
const staticKeysHavingCounts = searchClassBase.staticKeysHavingCounts();
const [treeData, setTreeData] = useState(initTreeData);
@ -89,9 +92,35 @@ const ExploreTree = ({ onFieldValueSelect }: ExploreTreeProps) => {
return [searchQueryParam, defaultServiceType];
}, [location.search]);
const handleChangeSearchIndex = (
key: string,
rootIndex = SearchIndex.DATABASE,
isRoot = false
) => {
if (isRoot) {
onChangeSearchIndex(
EXPLORE_ROOT_INDEX_MAPPING[
key as keyof typeof EXPLORE_ROOT_INDEX_MAPPING
] ?? (key as ExploreSearchIndex)
);
} else {
onChangeSearchIndex(
EXPLORE_ROOT_INDEX_MAPPING[
rootIndex as keyof typeof EXPLORE_ROOT_INDEX_MAPPING
] ?? rootIndex
);
}
};
const onLoadData = useCallback(
async (treeNode: ExploreTreeNode) => {
try {
handleChangeSearchIndex(
treeNode.key,
treeNode.data?.rootIndex as SearchIndex,
treeNode.data?.isRoot
);
if (treeNode.children) {
return;
}
@ -225,6 +254,13 @@ const ExploreTree = ({ onFieldValueSelect }: ExploreTreeProps) => {
),
]);
}
handleChangeSearchIndex(
node.key,
node.data?.rootIndex as SearchIndex,
node.data?.isRoot
);
setSelectedKeys([node.key]);
},
[onFieldValueSelect]

View File

@ -14,6 +14,7 @@
import { t } from 'i18next';
import { JsonTree, Utils as QbUtils } from 'react-awesome-query-builder';
import { EntityFields } from '../enums/AdvancedSearch.enum';
import { SearchIndex } from '../enums/search.enum';
export const COMMON_DROPDOWN_ITEMS = [
{
@ -320,3 +321,17 @@ export const MISC_FIELDS = ['owner.displayName', 'tags.tagFQN'];
export const OWNER_QUICK_FILTER_DEFAULT_OPTIONS_KEY = 'displayName.keyword';
export const NULL_OPTION_KEY = 'OM_NULL_FIELD';
export const EXPLORE_ROOT_INDEX_MAPPING = {
[SearchIndex.DATABASE]: [
SearchIndex.DATABASE,
SearchIndex.DATABASE_SCHEMA,
SearchIndex.TABLE,
SearchIndex.STORED_PROCEDURE,
],
[SearchIndex.API_ENDPOINT_INDEX]: [
SearchIndex.API_ENDPOINT_INDEX,
SearchIndex.API_COLLECTION_INDEX,
],
Governance: [SearchIndex.GLOSSARY_TERM],
};

View File

@ -83,6 +83,8 @@ export const EntitiesSupportedCustomProperties: string[] = [
SearchIndex.TOPIC,
SearchIndex.CONTAINER,
SearchIndex.MLMODEL,
SearchIndex.API_ENDPOINT_INDEX,
SearchIndex.API_COLLECTION_INDEX,
SearchIndex.SEARCH_INDEX,
SearchIndex.GLOSSARY_TERM,
];