From cb733a2ad87d79984aae2d4febe421e2a1f253fa Mon Sep 17 00:00:00 2001 From: Ashish Gupta Date: Wed, 8 Jan 2025 11:08:34 +0530 Subject: [PATCH] fix the search for custom properties in advance search for some types (#19113) * fix the search in advance search custom properties * modify v1/metadata/types/customProperties api to get customPropertyConfig along with name and type * added config list in the entity ref list * fix type of duration, timestamp and other types * minor ui changes * remove customProperty type which are not supported for now * added playwright test around the duration type property * fix flaky test --------- Co-authored-by: sonikashah (cherry picked from commit 2179b43232803a9ebb7465fb1d545b887d68252c) --- .../service/util/SchemaFieldExtractor.java | 59 +++++--- .../AdvanceSearchCustomProperty.spec.ts | 140 ++++++++++++++++++ .../utils/advancedSearchCustomProperty.ts | 28 ++++ .../AdvanceSearchProvider.component.tsx | 30 ++-- .../AdvanceSearchProvider.interface.ts | 4 + .../CustomPropertyTable/PropertyValue.tsx | 2 +- .../src/constants/AdvancedSearch.constants.ts | 10 ++ .../connections/pipeline/airbyteConnection.ts | 6 +- .../metadataIngestion/dbtPipeline.ts | 2 +- .../components/react-awesome-query.less | 9 ++ .../ui/src/utils/AdvancedSearchClassBase.ts | 107 +++++++++++-- .../ui/src/utils/AdvancedSearchUtils.tsx | 24 ++- .../QueryBuilderElasticsearchFormatUtils.js | 9 +- 13 files changed, 376 insertions(+), 54 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AdvanceSearchCustomProperty.spec.ts create mode 100644 openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearchCustomProperty.ts diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/util/SchemaFieldExtractor.java b/openmetadata-service/src/main/java/org/openmetadata/service/util/SchemaFieldExtractor.java index e6d2d446cd5..dfbb05a8e33 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/util/SchemaFieldExtractor.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/util/SchemaFieldExtractor.java @@ -32,7 +32,7 @@ import org.openmetadata.service.jdbi3.TypeRepository; @Slf4j public class SchemaFieldExtractor { - private static final Map> entityFieldsCache = + private static final Map> entityFieldsCache = new ConcurrentHashMap<>(); public SchemaFieldExtractor() { @@ -55,7 +55,7 @@ public class SchemaFieldExtractor { Schema mainSchema = loadMainSchema(schemaPath, entityType, schemaUri, schemaClient); // Extract fields from the schema - Map fieldTypesMap = new LinkedHashMap<>(); + Map fieldTypesMap = new LinkedHashMap<>(); Deque processingStack = new ArrayDeque<>(); Set processedFields = new HashSet<>(); extractFieldsFromSchema(mainSchema, "", fieldTypesMap, processingStack, processedFields); @@ -75,7 +75,7 @@ public class SchemaFieldExtractor { SchemaClient schemaClient = new CustomSchemaClient(schemaUri); Deque processingStack = new ArrayDeque<>(); Set processedFields = new HashSet<>(); - Map fieldTypesMap = entityFieldsCache.get(entityType); + Map fieldTypesMap = entityFieldsCache.get(entityType); addCustomProperties( typeEntity, schemaUri, schemaClient, fieldTypesMap, processingStack, processedFields); return convertMapToFieldList(fieldTypesMap); @@ -90,7 +90,7 @@ public class SchemaFieldExtractor { SchemaClient schemaClient = new CustomSchemaClient(schemaUri); EntityUtil.Fields fieldsParam = new EntityUtil.Fields(Set.of("customProperties")); Type typeEntity = repository.getByName(uriInfo, entityType, fieldsParam, Include.ALL, false); - Map fieldTypesMap = new LinkedHashMap<>(); + Map fieldTypesMap = new LinkedHashMap<>(); Set processedFields = new HashSet<>(); Deque processingStack = new ArrayDeque<>(); addCustomProperties( @@ -170,7 +170,7 @@ public class SchemaFieldExtractor { private static void extractFieldsFromSchema( Schema schema, String parentPath, - Map fieldTypesMap, + Map fieldTypesMap, Deque processingStack, Set processedFields) { if (processingStack.contains(schema)) { @@ -206,7 +206,8 @@ public class SchemaFieldExtractor { arraySchema, fullFieldName, fieldTypesMap, processingStack, processedFields); } else { String fieldType = mapSchemaTypeToSimpleType(fieldSchema); - fieldTypesMap.putIfAbsent(fullFieldName, fieldType); + fieldTypesMap.putIfAbsent( + fullFieldName, new FieldDefinition(fullFieldName, fieldType, null)); processedFields.add(fullFieldName); LOG.debug("Added field '{}', Type: '{}'", fullFieldName, fieldType); // Recursively process nested objects or arrays @@ -220,7 +221,7 @@ public class SchemaFieldExtractor { handleArraySchema(arraySchema, parentPath, fieldTypesMap, processingStack, processedFields); } else { String fieldType = mapSchemaTypeToSimpleType(schema); - fieldTypesMap.putIfAbsent(parentPath, fieldType); + fieldTypesMap.putIfAbsent(parentPath, new FieldDefinition(parentPath, fieldType, null)); LOG.debug("Added field '{}', Type: '{}'", parentPath, fieldType); } } finally { @@ -231,7 +232,7 @@ public class SchemaFieldExtractor { private static void handleReferenceSchema( ReferenceSchema referenceSchema, String fullFieldName, - Map fieldTypesMap, + Map fieldTypesMap, Deque processingStack, Set processedFields) { @@ -239,7 +240,8 @@ public class SchemaFieldExtractor { String referenceType = determineReferenceType(refUri); if (referenceType != null) { - fieldTypesMap.putIfAbsent(fullFieldName, referenceType); + fieldTypesMap.putIfAbsent( + fullFieldName, new FieldDefinition(fullFieldName, referenceType, null)); processedFields.add(fullFieldName); LOG.debug("Added field '{}', Type: '{}'", fullFieldName, referenceType); if (referenceType.startsWith("array<") && referenceType.endsWith(">")) { @@ -255,7 +257,7 @@ public class SchemaFieldExtractor { referredSchema, fullFieldName, fieldTypesMap, processingStack, processedFields); } } else { - fieldTypesMap.putIfAbsent(fullFieldName, "object"); + fieldTypesMap.putIfAbsent(fullFieldName, new FieldDefinition(fullFieldName, "object", null)); processedFields.add(fullFieldName); LOG.debug("Added field '{}', Type: 'object'", fullFieldName); extractFieldsFromSchema( @@ -270,7 +272,7 @@ public class SchemaFieldExtractor { private static void handleArraySchema( ArraySchema arraySchema, String fullFieldName, - Map fieldTypesMap, + Map fieldTypesMap, Deque processingStack, Set processedFields) { @@ -282,7 +284,8 @@ public class SchemaFieldExtractor { if (itemsReferenceType != null) { String arrayFieldType = "array<" + itemsReferenceType + ">"; - fieldTypesMap.putIfAbsent(fullFieldName, arrayFieldType); + fieldTypesMap.putIfAbsent( + fullFieldName, new FieldDefinition(fullFieldName, arrayFieldType, null)); processedFields.add(fullFieldName); LOG.debug("Added field '{}', Type: '{}'", fullFieldName, arrayFieldType); Schema referredItemsSchema = itemsReferenceSchema.getReferredSchema(); @@ -292,7 +295,8 @@ public class SchemaFieldExtractor { } } String arrayType = mapSchemaTypeToSimpleType(itemsSchema); - fieldTypesMap.putIfAbsent(fullFieldName, "array<" + arrayType + ">"); + fieldTypesMap.putIfAbsent( + fullFieldName, new FieldDefinition(fullFieldName, "array<" + arrayType + ">", null)); processedFields.add(fullFieldName); LOG.debug("Added field '{}', Type: 'array<{}>'", fullFieldName, arrayType); @@ -306,7 +310,7 @@ public class SchemaFieldExtractor { Type typeEntity, String schemaUri, SchemaClient schemaClient, - Map fieldTypesMap, + Map fieldTypesMap, Deque processingStack, Set processedFields) { if (typeEntity == null || typeEntity.getCustomProperties() == null) { @@ -320,9 +324,13 @@ public class SchemaFieldExtractor { LOG.debug("Processing custom property '{}'", fullFieldName); + Object customPropertyConfigObj = customProperty.getCustomPropertyConfig(); + if (isEntityReferenceList(propertyType)) { String referenceType = "array"; - fieldTypesMap.putIfAbsent(fullFieldName, referenceType); + FieldDefinition referenceFieldDefinition = + new FieldDefinition(fullFieldName, referenceType, customPropertyConfigObj); + fieldTypesMap.putIfAbsent(fullFieldName, referenceFieldDefinition); processedFields.add(fullFieldName); LOG.debug("Added custom property '{}', Type: '{}'", fullFieldName, referenceType); @@ -337,7 +345,9 @@ public class SchemaFieldExtractor { } } else if (isEntityReference(propertyType)) { String referenceType = "entityReference"; - fieldTypesMap.putIfAbsent(fullFieldName, referenceType); + FieldDefinition referenceFieldDefinition = + new FieldDefinition(fullFieldName, referenceType, customPropertyConfigObj); + fieldTypesMap.putIfAbsent(fullFieldName, referenceFieldDefinition); processedFields.add(fullFieldName); LOG.debug("Added custom property '{}', Type: '{}'", fullFieldName, referenceType); @@ -351,17 +361,22 @@ public class SchemaFieldExtractor { fullFieldName); } } else { - fieldTypesMap.putIfAbsent(fullFieldName, propertyType); + FieldDefinition entityFieldDefinition = + new FieldDefinition(fullFieldName, propertyType, customPropertyConfigObj); + fieldTypesMap.putIfAbsent(fullFieldName, entityFieldDefinition); processedFields.add(fullFieldName); LOG.debug("Added custom property '{}', Type: '{}'", fullFieldName, propertyType); } } } - private List convertMapToFieldList(Map fieldTypesMap) { + private List convertMapToFieldList(Map fieldTypesMap) { List fieldsList = new ArrayList<>(); - for (Map.Entry entry : fieldTypesMap.entrySet()) { - fieldsList.add(new FieldDefinition(entry.getKey(), entry.getValue())); + for (Map.Entry entry : fieldTypesMap.entrySet()) { + FieldDefinition fieldDef = entry.getValue(); + fieldsList.add( + new FieldDefinition( + fieldDef.getName(), fieldDef.getType(), fieldDef.getCustomPropertyConfig())); } return fieldsList; } @@ -622,10 +637,12 @@ public class SchemaFieldExtractor { public static class FieldDefinition { private String name; private String type; + private Object customPropertyConfig; - public FieldDefinition(String name, String type) { + public FieldDefinition(String name, String type, Object customPropertyConfig) { this.name = name; this.type = type; + this.customPropertyConfig = customPropertyConfig; } } } diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AdvanceSearchCustomProperty.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AdvanceSearchCustomProperty.spec.ts new file mode 100644 index 00000000000..1642742a5a2 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AdvanceSearchCustomProperty.spec.ts @@ -0,0 +1,140 @@ +/* + * Copyright 2025 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 test, { expect } from '@playwright/test'; +import { CUSTOM_PROPERTIES_ENTITIES } from '../../constant/customProperty'; +import { GlobalSettingOptions } from '../../constant/settings'; +import { SidebarItem } from '../../constant/sidebar'; +import { TableClass } from '../../support/entity/TableClass'; +import { + selectOption, + showAdvancedSearchDialog, +} from '../../utils/advancedSearch'; +import { advanceSearchSaveFilter } from '../../utils/advancedSearchCustomProperty'; +import { createNewPage, redirectToHomePage, uuid } from '../../utils/common'; +import { addCustomPropertiesForEntity } from '../../utils/customProperty'; +import { settingClick, sidebarClick } from '../../utils/sidebar'; + +// use the admin user to login +test.use({ storageState: 'playwright/.auth/admin.json' }); + +test.describe('Advanced Search Custom Property', () => { + const table = new TableClass(); + const durationPropertyName = `pwCustomPropertyDurationTest${uuid()}`; + const durationPropertyValue = 'PT1H30M'; + + test.beforeAll('Setup pre-requests', async ({ browser }) => { + const { apiContext, afterAction } = await createNewPage(browser); + await table.create(apiContext); + await afterAction(); + }); + + test.afterAll('Cleanup', async ({ browser }) => { + const { apiContext, afterAction } = await createNewPage(browser); + await table.delete(apiContext); + await afterAction(); + }); + + test('Create, Assign and Test Advance Search for Duration', async ({ + page, + }) => { + test.slow(true); + + await redirectToHomePage(page); + + await test.step('Create and Assign Custom Property Value', async () => { + await settingClick(page, GlobalSettingOptions.TABLES, true); + + await addCustomPropertiesForEntity({ + page, + propertyName: durationPropertyName, + customPropertyData: CUSTOM_PROPERTIES_ENTITIES['entity_table'], + customType: 'Duration', + }); + + await table.visitEntityPage(page); + + await page.getByTestId('custom_properties').click(); // Tab Click + + await page + .getByTestId(`custom-property-${durationPropertyName}-card`) + .locator('svg') + .click(); // Add Custom Property Value + + await page.getByTestId('duration-input').fill(durationPropertyValue); + + const saveResponse = page.waitForResponse('/api/v1/tables/*'); + await page.getByTestId('inline-save-btn').click(); + await saveResponse; + }); + + await test.step('Verify Duration Type in Advance Search ', async () => { + await sidebarClick(page, SidebarItem.EXPLORE); + + await showAdvancedSearchDialog(page); + + const ruleLocator = page.locator('.rule').nth(0); + + // Perform click on rule field + await selectOption( + page, + ruleLocator.locator('.rule--field .ant-select'), + 'Custom Properties' + ); + + // Perform click on custom property type to filter + await selectOption( + page, + ruleLocator.locator('.rule--field .ant-select'), + durationPropertyName + ); + + const inputElement = ruleLocator.locator( + '.rule--widget--TEXT input[type="text"]' + ); + + await inputElement.fill(durationPropertyValue); + + await advanceSearchSaveFilter(page, durationPropertyValue); + + await expect( + page.getByTestId( + `table-data-card_${table.entityResponseData.fullyQualifiedName}` + ) + ).toBeVisible(); + + // Check around the Partial Search Value + const partialSearchValue = durationPropertyValue.slice(0, 3); + + await page.getByTestId('advance-search-filter-btn').click(); + + await expect(page.locator('[role="dialog"].ant-modal')).toBeVisible(); + + // Perform click on operator + await selectOption( + page, + ruleLocator.locator('.rule--operator .ant-select'), + 'Contains' + ); + + await inputElement.fill(partialSearchValue); + + await advanceSearchSaveFilter(page, partialSearchValue); + + await expect( + page.getByTestId( + `table-data-card_${table.entityResponseData.fullyQualifiedName}` + ) + ).toBeVisible(); + }); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearchCustomProperty.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearchCustomProperty.ts new file mode 100644 index 00000000000..2540d71065b --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearchCustomProperty.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2025 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, Page } from '@playwright/test'; + +export const advanceSearchSaveFilter = async ( + page: Page, + propertyValue: string +) => { + const searchResponse = page.waitForResponse( + '/api/v1/search/query?*index=dataAsset&from=0&size=10*' + ); + await page.getByTestId('apply-btn').click(); + + const res = await searchResponse; + const json = await res.json(); + + expect(JSON.stringify(json)).toContain(propertyValue); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx index 9f612e84c28..cd8cff8ea12 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx @@ -27,16 +27,15 @@ import { JsonTree, Utils as QbUtils, ValueField, + ValueSource, } from 'react-awesome-query-builder'; import { useHistory, useParams } from 'react-router-dom'; -import { - emptyJsonTree, - TEXT_FIELD_OPERATORS, -} from '../../../constants/AdvancedSearch.constants'; +import { emptyJsonTree } from '../../../constants/AdvancedSearch.constants'; import { SearchIndex } from '../../../enums/search.enum'; import useCustomLocation from '../../../hooks/useCustomLocation/useCustomLocation'; import { TabsInfoData } from '../../../pages/ExplorePage/ExplorePage.interface'; import { getAllCustomProperties } from '../../../rest/metadataTypeAPI'; +import advancedSearchClassBase from '../../../utils/AdvancedSearchClassBase'; import { getTierOptions, getTreeConfig, @@ -225,15 +224,24 @@ export const AdvanceSearchProvider = ({ Object.entries(res).forEach(([_, fields]) => { if (Array.isArray(fields) && fields.length > 0) { - fields.forEach((field: { name: string; type: string }) => { - if (field.name && field.type) { - subfields[field.name] = { - type: 'text', - valueSources: ['value'], - operators: TEXT_FIELD_OPERATORS, + fields.forEach( + (field: { + name: string; + type: string; + customPropertyConfig: { + config: string | string[]; }; + }) => { + if (field.name && field.type) { + const { subfieldsKey, dataObject } = + advancedSearchClassBase.getCustomPropertiesSubFields(field); + subfields[subfieldsKey] = { + ...dataObject, + valueSources: dataObject.valueSources as ValueSource[], + }; + } } - }); + ); } }); } catch (error) { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.interface.ts index 65506e5ab97..9f7bbe6b11e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.interface.ts @@ -51,3 +51,7 @@ export interface AdvanceSearchContext { } export type FilterObject = Record; +export interface CustomPropertyEnumConfig { + multiSelect: boolean; + values: string[]; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/PropertyValue.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/PropertyValue.tsx index 20d27652208..38ec5a201a1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/PropertyValue.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/PropertyValue.tsx @@ -905,7 +905,7 @@ export const PropertyValue: FC = ({ return (
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 74a10290ad1..219264b059c 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 @@ -299,6 +299,16 @@ export const TEXT_FIELD_OPERATORS = [ 'is_null', 'is_not_null', ]; + +export const RANGE_FIELD_OPERATORS = ['between', 'not_between']; + +export const LIST_VALUE_OPERATORS = [ + 'select_equals', + 'select_not_equals', + 'is_null', + 'is_not_null', +]; + /** * Generates a query builder tree with a group containing an empty rule */ diff --git a/openmetadata-ui/src/main/resources/ui/src/generated/entity/services/connections/pipeline/airbyteConnection.ts b/openmetadata-ui/src/main/resources/ui/src/generated/entity/services/connections/pipeline/airbyteConnection.ts index e257fdcd17d..ed2040e65eb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/generated/entity/services/connections/pipeline/airbyteConnection.ts +++ b/openmetadata-ui/src/main/resources/ui/src/generated/entity/services/connections/pipeline/airbyteConnection.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 Collate. + * Copyright 2025 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,9 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - - /** +/** * Airbyte Metadata Database Connection Config */ export interface AirbyteConnection { diff --git a/openmetadata-ui/src/main/resources/ui/src/generated/metadataIngestion/dbtPipeline.ts b/openmetadata-ui/src/main/resources/ui/src/generated/metadataIngestion/dbtPipeline.ts index 7374438ec42..07d5c6194c3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/generated/metadataIngestion/dbtPipeline.ts +++ b/openmetadata-ui/src/main/resources/ui/src/generated/metadataIngestion/dbtPipeline.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 Collate. + * Copyright 2025 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 diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/components/react-awesome-query.less b/openmetadata-ui/src/main/resources/ui/src/styles/components/react-awesome-query.less index d9bb04362eb..e52449a7956 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/components/react-awesome-query.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/components/react-awesome-query.less @@ -255,6 +255,15 @@ } } +.rule--widget--NUMBER { + .widget--valuesrc { + display: none; + } + .ant-input-number { + width: 100%; + } +} + .rule--widget { min-width: 180px; .ant-select, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts index 6afe1cdcef3..8a35255f46b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts @@ -22,11 +22,20 @@ import { SelectFieldSettings, } from 'react-awesome-query-builder'; import AntdConfig from 'react-awesome-query-builder/lib/config/antd'; -import { SEARCH_INDICES_WITH_COLUMNS_FIELD } from '../constants/AdvancedSearch.constants'; +import { CustomPropertyEnumConfig } from '../components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.interface'; +import { + LIST_VALUE_OPERATORS, + RANGE_FIELD_OPERATORS, + SEARCH_INDICES_WITH_COLUMNS_FIELD, + TEXT_FIELD_OPERATORS, +} from '../constants/AdvancedSearch.constants'; import { EntityFields, SuggestionField } from '../enums/AdvancedSearch.enum'; import { SearchIndex } from '../enums/search.enum'; import { getAggregateFieldOptions } from '../rest/miscAPI'; -import { renderAdvanceSearchButtons } from './AdvancedSearchUtils'; +import { + getCustomPropertyAdvanceSearchEnumOptions, + renderAdvanceSearchButtons, +} from './AdvancedSearchUtils'; import { getCombinedQueryFilterObject } from './ExplorePage/ExplorePageUtils'; import { renderQueryBuilderFilterButtons } from './QueryBuilderUtils'; @@ -208,12 +217,7 @@ class AdvancedSearchClassBase { [EntityFields.COLUMN_DESCRIPTION_STATUS]: { label: t('label.column-description'), type: 'select', - operators: [ - 'select_equals', - 'select_not_equals', - 'is_null', - 'is_not_null', - ], + operators: LIST_VALUE_OPERATORS, mainWidgetProps: this.mainWidgetProps, valueSources: ['value'], fieldSettings: { @@ -680,12 +684,7 @@ class AdvancedSearchClassBase { descriptionStatus: { label: t('label.description'), type: 'select', - operators: [ - 'select_equals', - 'select_not_equals', - 'is_null', - 'is_not_null', - ], + operators: LIST_VALUE_OPERATORS, mainWidgetProps: this.mainWidgetProps, valueSources: ['value'], fieldSettings: { @@ -899,6 +898,86 @@ class AdvancedSearchClassBase { }, }; }; + + public getCustomPropertiesSubFields(field: { + name: string; + type: string; + customPropertyConfig: { + config: string | string[] | CustomPropertyEnumConfig; + }; + }) { + { + switch (field.type) { + case 'array': + case 'entityReference': + return { + subfieldsKey: field.name + `.displayName`, + dataObject: { + type: 'select', + label: field.name, + fieldSettings: { + asyncFetch: this.autocomplete({ + searchIndex: ( + (field.customPropertyConfig.config ?? []) as string[] + ).join(',') as SearchIndex, + entityField: EntityFields.DISPLAY_NAME_KEYWORD, + }), + useAsyncSearch: true, + }, + }, + }; + + case 'enum': + return { + subfieldsKey: field.name, + dataObject: { + type: 'select', + operators: LIST_VALUE_OPERATORS, + fieldSettings: { + listValues: getCustomPropertyAdvanceSearchEnumOptions( + ( + field.customPropertyConfig + .config as CustomPropertyEnumConfig + ).values + ), + }, + }, + }; + + case 'date-cp': { + return { + subfieldsKey: field.name, + dataObject: { + type: 'date', + operators: RANGE_FIELD_OPERATORS, + }, + }; + } + + case 'timestamp': + case 'integer': + case 'number': { + return { + subfieldsKey: field.name, + dataObject: { + type: 'number', + operators: RANGE_FIELD_OPERATORS, + }, + }; + } + + default: + return { + subfieldsKey: field.name, + dataObject: { + type: 'text', + valueSources: ['value'], + operators: TEXT_FIELD_OPERATORS, + }, + }; + } + } + } } const advancedSearchClassBase = new AdvancedSearchClassBase(); 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 5111714d087..ad6f119a80e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx @@ -14,7 +14,7 @@ import Icon, { CloseCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { Button, Checkbox, MenuProps, Space, Typography } from 'antd'; import i18next from 'i18next'; -import { isArray, isEmpty } from 'lodash'; +import { isArray, isEmpty, toLower } from 'lodash'; import React from 'react'; import { AsyncFetchListValues, @@ -452,3 +452,25 @@ export const getTreeConfig = ({ ? advancedSearchClassBase.getQbConfigs(tierOptions, index, isExplorePage) : jsonLogicSearchClassBase.getQbConfigs(tierOptions, index, isExplorePage); }; + +export const formatQueryValueBasedOnType = ( + value: string[], + field: string, + type: string +) => { + if (field.includes('extension') && type === 'text') { + return value.map((item) => toLower(item)); + } + + return value; +}; + +export const getCustomPropertyAdvanceSearchEnumOptions = ( + enumValues: string[] +) => { + return enumValues.reduce((acc: Record, value) => { + acc[value] = value; + + return acc; + }, {}); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.js b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.js index 74b81de257f..a2de5a564fb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.js +++ b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderElasticsearchFormatUtils.js @@ -15,6 +15,7 @@ import { defaultConjunction } from 'react-awesome-query-builder/lib/utils/defaultUtils'; import { getWidgetForFieldOp } from 'react-awesome-query-builder/lib/utils/ruleUtils'; +import { formatQueryValueBasedOnType } from './AdvancedSearchUtils'; /* * This script is a modified version of https://github.com/ukrbublik/react-awesome-query-builder/blob/5.1.2/modules/export/elasticSearch.js @@ -372,7 +373,13 @@ export function elasticSearchFormat(tree, config) { }, }; } else { - return buildEsRule(field, value, operator, config, valueSrc); + return buildEsRule( + field, + formatQueryValueBasedOnType(value, field, _valueType), + operator, + config, + valueSrc + ); } }