diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts index 2dcf6d29afe..a9d5a73d5fc 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts @@ -414,6 +414,19 @@ test.describe('Data Contracts', () => { ); await page.getByTestId('delete-contract-button').click(); + + await expect( + page + .locator('.ant-modal-title') + .getByText(`Delete dataContract "${DATA_CONTRACT_DETAILS.name}"`) + ).toBeVisible(); + + await page.getByTestId('confirmation-text-input').click(); + await page.getByTestId('confirmation-text-input').fill('DELETE'); + + await expect(page.getByTestId('confirm-button')).toBeEnabled(); + + await page.getByTestId('confirm-button').click(); await deleteContractResponse; await toastNotification(page, '"Contract" deleted successfully!'); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataAssetRules/DataAssetRules.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataAssetRules/DataAssetRules.component.tsx index 3d91c5d1121..77e7b51314c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataAssetRules/DataAssetRules.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataAssetRules/DataAssetRules.component.tsx @@ -31,6 +31,7 @@ import { useTranslation } from 'react-i18next'; import { ReactComponent as AddPlaceHolderIcon } from '../../assets/svg/add-placeholder.svg'; import { ReactComponent as IconEdit } from '../../assets/svg/edit-new.svg'; import { ReactComponent as IconDelete } from '../../assets/svg/ic-delete.svg'; +import { EntityReferenceFields } from '../../enums/AdvancedSearch.enum'; import { SIZE } from '../../enums/common.enum'; import { ProviderType, @@ -43,6 +44,7 @@ import { updateSettingsConfig, } from '../../rest/settingConfigAPI'; import i18n, { t } from '../../utils/i18next/LocalUtil'; +import jsonLogicSearchClassBase from '../../utils/JSONLogicSearchClassBase'; import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils'; import QueryBuilderWidget from '../common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget'; import RichTextEditorPreviewerNew from '../common/RichTextEditor/RichTextEditorPreviewNew'; @@ -134,6 +136,18 @@ export const SemanticsRuleForm: React.FC<{ form.setFieldsValue(semanticsRule); }, [semanticsRule]); + const queryBuilderFields = useMemo(() => { + const fields = jsonLogicSearchClassBase.getMapFields(); + + return { + [EntityReferenceFields.TAG]: fields[EntityReferenceFields.TAG], + [EntityReferenceFields.TIER]: fields[EntityReferenceFields.TIER], + [EntityReferenceFields.DOMAIN]: fields[EntityReferenceFields.DOMAIN], + [EntityReferenceFields.DATA_PRODUCT]: + fields[EntityReferenceFields.DATA_PRODUCT], + }; + }, []); + return (
{/* @ts-expect-error because Form.Item will provide value and onChange */}
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/AddDataContract/add-data-contract.less b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/AddDataContract/add-data-contract.less index f6b2fe6e85a..c8773132925 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/AddDataContract/add-data-contract.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/AddDataContract/add-data-contract.less @@ -223,6 +223,9 @@ } } .ant-form-item-control-input-content { + .group--field { + align-self: baseline; + } input[type='text'] { padding: 8px 14px; color: @grey-700; @@ -239,7 +242,7 @@ border: 1px solid @grey-300; padding: 4px 12px; color: @grey-700; - height: 40px; + min-height: 40px; box-shadow: 0 1px 2px 0px @grey-27; &:focus, @@ -318,9 +321,18 @@ height: 40px !important; box-shadow: 0 1px 2px 0px @grey-27; - &:focus, - &:hover { - border-color: @primary-color; + .ant-select-selector, + .ant-mentions { + border: 1px solid @grey-300; + padding: 4px 12px !important; + color: @grey-700; + min-height: 40px !important; + box-shadow: 0 1px 2px 0px @grey-27; + + &:focus, + &:hover { + border-color: @primary-color; + } } } } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractExecutionChart/contract-execution-chart.less b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractExecutionChart/contract-execution-chart.less index cf58ccb6b07..89838076099 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractExecutionChart/contract-execution-chart.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractExecutionChart/contract-execution-chart.less @@ -15,5 +15,5 @@ display: flex; flex-direction: column; gap: 16px; - align-items: end; + align-items: flex-end; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSemanticFormTab/ContractSemanticFormTab.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSemanticFormTab/ContractSemanticFormTab.tsx index 2f475d05f85..e23da32fd30 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSemanticFormTab/ContractSemanticFormTab.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSemanticFormTab/ContractSemanticFormTab.tsx @@ -19,14 +19,16 @@ import Card from 'antd/lib/card/Card'; import TextArea from 'antd/lib/input/TextArea'; import classNames from 'classnames'; import { isNull } from 'lodash'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ReactComponent as DeleteIcon } from '../../../assets/svg/ic-trash.svg'; import { ReactComponent as LeftOutlined } from '../../../assets/svg/left-arrow.svg'; import { ReactComponent as RightIcon } from '../../../assets/svg/right-arrow.svg'; import { ReactComponent as PlusIcon } from '../../../assets/svg/x-colored.svg'; +import { EntityReferenceFields } from '../../../enums/AdvancedSearch.enum'; import { EntityType } from '../../../enums/entity.enum'; import { DataContract } from '../../../generated/entity/data/dataContract'; +import jsonLogicSearchClassBase from '../../../utils/JSONLogicSearchClassBase'; import ExpandableCard from '../../common/ExpandableCard/ExpandableCard'; import QueryBuilderWidget from '../../common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget'; import { EditIconButton } from '../../common/IconButtons/EditIconButton'; @@ -105,6 +107,15 @@ export const ContractSemanticFormTab: React.FC<{ setEditingKey(0); }, [initialValues]); + // Remove extension field from common config + const queryBuilderFields = useMemo(() => { + const fields = jsonLogicSearchClassBase.getCommonConfig(); + + delete fields[EntityReferenceFields.EXTENSION]; + + return fields; + }, []); + return ( <> @@ -253,6 +264,7 @@ export const ContractSemanticFormTab: React.FC<{ name={[field.name, 'rule']}> {/* @ts-expect-error because Form.Item will provide value and onChange */} { ); const [contract, setContract] = useState(); const [isLoading, setIsLoading] = useState(true); + const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const fetchContract = async () => { try { @@ -55,21 +57,30 @@ export const ContractTab = () => { const handleDelete = async () => { if (contract?.id) { - try { - await deleteContractById(contract.id); - showSuccessToast( - t('server.entity-deleted-successfully', { - entity: t('label.contract'), - }) - ); - fetchContract(); - setTabMode(DataContractTabMode.VIEW); - } catch (err) { - showErrorToast(err as AxiosError); - } + setIsDeleteModalVisible(true); } }; + const handleContractDeleteConfirm = async () => { + if (!contract?.id) { + return; + } + try { + await deleteContractById(contract.id); + showSuccessToast( + t('server.entity-deleted-successfully', { + entity: t('label.contract'), + }) + ); + fetchContract(); + setTabMode(DataContractTabMode.VIEW); + } catch (error) { + showErrorToast(error as AxiosError); + } + + setIsDeleteModalVisible(false); + }; + useEffect(() => { fetchContract(); }, [id]); @@ -122,6 +133,18 @@ export const ContractTab = () => { return isLoading ? ( ) : ( -
{content}
+
+ {content} + { + setIsDeleteModalVisible(false); + }} + onDelete={handleContractDeleteConfirm} + /> +
); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx index 7b9f16cb8f3..f28e5df29ec 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx @@ -60,12 +60,13 @@ import { useAdvanceSearch } from '../../../../../Explore/AdvanceSearchProvider/A import { SearchOutputType } from '../../../../../Explore/AdvanceSearchProvider/AdvanceSearchProvider.interface'; import './query-builder-widget.less'; -const QueryBuilderWidget: FC = ({ - onChange, - schema, - value, - ...props -}) => { +const QueryBuilderWidget: FC< + WidgetProps & { + fields?: Config['fields']; + defaultField?: string; + subField?: string; + } +> = ({ onChange, schema, value, fields, defaultField, subField, ...props }) => { const { config, treeInternal, @@ -202,7 +203,7 @@ const QueryBuilderWidget: FC = ({ } else { const emptyJsonTree = outputType === SearchOutputType.JSONLogic - ? getEmptyJsonTreeForQueryBuilder() + ? getEmptyJsonTreeForQueryBuilder(defaultField, subField) : getEmptyJsonTree(); const tree = QbUtils.loadTree(emptyJsonTree); @@ -253,6 +254,7 @@ const QueryBuilderWidget: FC = ({ )} { // Store the actions for external access if (!queryActions) { diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/DataContract.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/DataContract.constants.ts index e583450b03e..973b7480ed8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/DataContract.constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/DataContract.constants.ts @@ -11,6 +11,8 @@ * limitations under the License. */ +import { EntityReferenceFields } from '../enums/AdvancedSearch.enum'; + export enum DataContractMode { YAML, UI, @@ -28,3 +30,14 @@ export enum EDataContractTab { SEMANTICS, QUALITY, } + +export const DATA_ASSET_RULE_FIELDS_NOT_TO_RENDER = [ + EntityReferenceFields.EXTENSION, + EntityReferenceFields.OWNERS, + EntityReferenceFields.NAME, + EntityReferenceFields.DESCRIPTION, + EntityReferenceFields.TIER, + EntityReferenceFields.SERVICE, + EntityReferenceFields.DISPLAY_NAME, + EntityReferenceFields.DELETED, +]; diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/JSONLogicSearch.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/JSONLogicSearch.constants.ts new file mode 100644 index 00000000000..4df934e1a3e --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/constants/JSONLogicSearch.constants.ts @@ -0,0 +1,37 @@ +/* + * 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 { EntityReferenceFields } from '../enums/AdvancedSearch.enum'; + +export const GLOSSARY_ENTITY_FIELDS_KEYS: EntityReferenceFields[] = [ + EntityReferenceFields.REVIEWERS, + EntityReferenceFields.UPDATED_BY, +]; + +export const TABLE_ENTITY_FIELDS_KEYS: EntityReferenceFields[] = [ + EntityReferenceFields.DATABASE, + EntityReferenceFields.DATABASE_SCHEMA, + EntityReferenceFields.TABLE_TYPE, +]; + +export const COMMON_ENTITY_FIELDS_KEYS: EntityReferenceFields[] = [ + EntityReferenceFields.SERVICE, + EntityReferenceFields.OWNERS, + EntityReferenceFields.DISPLAY_NAME, + EntityReferenceFields.NAME, + EntityReferenceFields.DESCRIPTION, + EntityReferenceFields.TAG, + EntityReferenceFields.DOMAIN, + EntityReferenceFields.DATA_PRODUCT, + EntityReferenceFields.TIER, + EntityReferenceFields.EXTENSION, +]; diff --git a/openmetadata-ui/src/main/resources/ui/src/enums/AdvancedSearch.enum.ts b/openmetadata-ui/src/main/resources/ui/src/enums/AdvancedSearch.enum.ts index 660321e0793..2711b2aeccb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/enums/AdvancedSearch.enum.ts +++ b/openmetadata-ui/src/main/resources/ui/src/enums/AdvancedSearch.enum.ts @@ -96,9 +96,12 @@ export enum EntityReferenceFields { DISPLAY_NAME = 'displayName', TAG = 'tags', TIER = 'tier.tagFQN', + DOMAIN = 'domain', + DATA_PRODUCT = 'dataProduct', TABLE_TYPE = 'tableType', EXTENSION = 'extension', SERVICE = 'service.name', UPDATED_BY = 'updatedBy', CHANGE_DESCRIPTION = 'changeDescription', + DELETED = 'deleted', } 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 e6f7f4dd4e4..5861756f59a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx @@ -454,7 +454,8 @@ export const getEmptyJsonTree = ( * This structure allows easy addition of groups and rules */ export const getEmptyJsonTreeForQueryBuilder = ( - defaultField: string = EntityReferenceFields.OWNERS + defaultField: string = EntityReferenceFields.OWNERS, + subField = 'fullyQualifiedName' ): OldJsonTree => { const uuid1 = QbUtils.uuid(); const uuid2 = QbUtils.uuid(); @@ -483,7 +484,7 @@ export const getEmptyJsonTreeForQueryBuilder = ( type: 'rule', id: uuid3, properties: { - field: 'owners.fullyQualifiedName', + field: `${defaultField}.${subField}`, operator: 'select_equals', value: [], valueSrc: ['value'], diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/JSONLogicSearchClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/JSONLogicSearchClassBase.ts index bf44fa19005..cfceb231fbb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/JSONLogicSearchClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/JSONLogicSearchClassBase.ts @@ -12,24 +12,36 @@ */ import { AntdConfig, + AsyncFetchListValuesResult, Config, + FieldOrGroup, Fields, + ListItem, ListValues, Operators, SelectFieldSettings, } from '@react-awesome-query-builder/antd'; -import { get, sortBy } from 'lodash'; +import { get, sortBy, toLower } from 'lodash'; import { TEXT_FIELD_OPERATORS } from '../constants/AdvancedSearch.constants'; import { PAGE_SIZE_BASE } from '../constants/constants'; +import { + COMMON_ENTITY_FIELDS_KEYS, + GLOSSARY_ENTITY_FIELDS_KEYS, + TABLE_ENTITY_FIELDS_KEYS, +} from '../constants/JSONLogicSearch.constants'; import { EntityFields, EntityReferenceFields, } from '../enums/AdvancedSearch.enum'; import { SearchIndex } from '../enums/search.enum'; import { searchData } from '../rest/miscAPI'; +import { getTags } from '../rest/tagAPI'; import advancedSearchClassBase from './AdvancedSearchClassBase'; import { t } from './i18next/LocalUtil'; -import { renderJSONLogicQueryBuilderButtons } from './QueryBuilderUtils'; +import { + getFieldsByKeys, + renderJSONLogicQueryBuilderButtons, +} from './QueryBuilderUtils'; class JSONLogicSearchClassBase { baseConfig = AntdConfig as Config; @@ -143,6 +155,8 @@ class JSONLogicSearchClassBase { }, }; + mapFields: Record; + defaultSelectOperators = [ 'select_equals', 'select_not_equals', @@ -152,135 +166,8 @@ class JSONLogicSearchClassBase { 'is_not_null', ]; - public searchAutocomplete: (args: { - searchIndex: SearchIndex | SearchIndex[]; - fieldName: string; - fieldLabel: string; - }) => SelectFieldSettings['asyncFetch'] = ({ - searchIndex, - fieldName, - fieldLabel, - }) => { - return (search) => { - return searchData( - Array.isArray(search) ? search.join(',') : search ?? '', - 1, - PAGE_SIZE_BASE, - '', - '', - '', - searchIndex ?? SearchIndex.DATA_ASSET, - false, - false, - false - ).then((response) => { - const data = response.data.hits.hits; - - return { - values: data.map((item) => ({ - value: get(item._source, fieldName, ''), - title: get(item._source, fieldLabel, ''), - })), - hasMore: false, - }; - }); - }; - }; - - mainWidgetProps = { - fullWidth: true, - valueLabel: t('label.criteria') + ':', - }; - - glossaryEntityFields: Fields = { - [EntityReferenceFields.REVIEWERS]: { - label: t('label.reviewer-plural'), - type: '!group', - mode: 'some', - defaultField: 'fullyQualifiedName', - subfields: { - fullyQualifiedName: { - label: 'Reviewers New', - type: 'select', - mainWidgetProps: this.mainWidgetProps, - operators: this.defaultSelectOperators, - fieldSettings: { - asyncFetch: advancedSearchClassBase.autocomplete({ - searchIndex: [SearchIndex.USER, SearchIndex.TEAM], - entityField: EntityFields.DISPLAY_NAME_KEYWORD, - }), - useAsyncSearch: true, - }, - }, - }, - }, - [EntityReferenceFields.UPDATED_BY]: { - label: t('label.updated-by'), - type: 'select', - mainWidgetProps: this.mainWidgetProps, - operators: [...this.defaultSelectOperators, 'isOwner', 'isReviewer'], - fieldSettings: { - asyncFetch: advancedSearchClassBase.autocomplete({ - searchIndex: [SearchIndex.USER, SearchIndex.TEAM], - entityField: EntityFields.DISPLAY_NAME_KEYWORD, - }), - useAsyncSearch: true, - }, - }, - }; - - tableEntityFields: Fields = { - [EntityReferenceFields.DATABASE]: { - label: t('label.database'), - type: 'select', - mainWidgetProps: this.mainWidgetProps, - operators: this.defaultSelectOperators, - fieldSettings: { - asyncFetch: advancedSearchClassBase.autocomplete({ - searchIndex: SearchIndex.TABLE, - entityField: EntityFields.DATABASE_NAME, - isCaseInsensitive: true, - }), - useAsyncSearch: true, - }, - }, - - [EntityReferenceFields.DATABASE_SCHEMA]: { - label: t('label.database-schema'), - type: 'select', - mainWidgetProps: this.mainWidgetProps, - operators: this.defaultSelectOperators, - fieldSettings: { - asyncFetch: advancedSearchClassBase.autocomplete({ - searchIndex: SearchIndex.TABLE, - entityField: EntityFields.DATABASE_SCHEMA_NAME, - isCaseInsensitive: true, - }), - useAsyncSearch: true, - }, - }, - - [EntityReferenceFields.TABLE_TYPE]: { - label: t('label.table-type'), - type: 'select', - mainWidgetProps: this.mainWidgetProps, - operators: this.defaultSelectOperators, - fieldSettings: { - asyncFetch: advancedSearchClassBase.autocomplete({ - searchIndex: SearchIndex.TABLE, - entityField: EntityFields.TABLE_TYPE, - }), - useAsyncSearch: true, - }, - }, - }; - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public getCommonConfig = (_: { - entitySearchIndex?: Array; - tierOptions?: Promise; - }) => { - return { + constructor() { + this.mapFields = { [EntityReferenceFields.SERVICE]: { label: t('label.service'), type: 'select', @@ -375,12 +262,229 @@ class JSONLogicSearchClassBase { }, }, }, + [EntityReferenceFields.DOMAIN]: { + label: t('label.domain'), + type: '!group', + mode: 'some', + defaultField: 'fullyQualifiedName', + subfields: { + fullyQualifiedName: { + label: 'Domain', + type: 'select', + mainWidgetProps: this.mainWidgetProps, + operators: this.defaultSelectOperators, + fieldSettings: { + asyncFetch: this.searchAutocomplete({ + searchIndex: SearchIndex.DOMAIN, + fieldName: 'fullyQualifiedName', + fieldLabel: 'name', + }), + useAsyncSearch: true, + }, + }, + }, + }, + [EntityReferenceFields.DATA_PRODUCT]: { + label: t('label.data-product'), + type: '!group', + mode: 'some', + defaultField: 'fullyQualifiedName', + subfields: { + fullyQualifiedName: { + label: 'Data Product', + type: 'select', + mainWidgetProps: this.mainWidgetProps, + operators: this.defaultSelectOperators, + fieldSettings: { + asyncFetch: this.searchAutocomplete({ + searchIndex: SearchIndex.DATA_PRODUCT, + fieldName: 'fullyQualifiedName', + fieldLabel: 'name', + }), + useAsyncSearch: true, + }, + }, + }, + }, + [EntityReferenceFields.TIER]: { + label: t('label.tier'), + type: 'select', + mainWidgetProps: this.mainWidgetProps, + operators: this.defaultSelectOperators, + fieldSettings: { + asyncFetch: this.autoCompleteTier, + useAsyncSearch: true, + }, + }, [EntityReferenceFields.EXTENSION]: { label: t('label.custom-property-plural'), type: '!struct', mainWidgetProps: this.mainWidgetProps, subfields: {}, }, + [EntityReferenceFields.DATABASE]: { + label: t('label.database'), + type: 'select', + mainWidgetProps: this.mainWidgetProps, + operators: this.defaultSelectOperators, + fieldSettings: { + asyncFetch: advancedSearchClassBase.autocomplete({ + searchIndex: SearchIndex.TABLE, + entityField: EntityFields.DATABASE_NAME, + isCaseInsensitive: true, + }), + useAsyncSearch: true, + }, + }, + + [EntityReferenceFields.DATABASE_SCHEMA]: { + label: t('label.database-schema'), + type: 'select', + mainWidgetProps: this.mainWidgetProps, + operators: this.defaultSelectOperators, + fieldSettings: { + asyncFetch: advancedSearchClassBase.autocomplete({ + searchIndex: SearchIndex.TABLE, + entityField: EntityFields.DATABASE_SCHEMA_NAME, + isCaseInsensitive: true, + }), + useAsyncSearch: true, + }, + }, + + [EntityReferenceFields.TABLE_TYPE]: { + label: t('label.table-type'), + type: 'select', + mainWidgetProps: this.mainWidgetProps, + operators: this.defaultSelectOperators, + fieldSettings: { + asyncFetch: advancedSearchClassBase.autocomplete({ + searchIndex: SearchIndex.TABLE, + entityField: EntityFields.TABLE_TYPE, + }), + useAsyncSearch: true, + }, + }, + + [EntityReferenceFields.REVIEWERS]: { + label: t('label.reviewer-plural'), + type: '!group', + mode: 'some', + defaultField: 'fullyQualifiedName', + subfields: { + fullyQualifiedName: { + label: 'Reviewers New', + type: 'select', + mainWidgetProps: this.mainWidgetProps, + operators: this.defaultSelectOperators, + fieldSettings: { + asyncFetch: advancedSearchClassBase.autocomplete({ + searchIndex: [SearchIndex.USER, SearchIndex.TEAM], + entityField: EntityFields.DISPLAY_NAME_KEYWORD, + }), + useAsyncSearch: true, + }, + }, + }, + }, + [EntityReferenceFields.UPDATED_BY]: { + label: t('label.updated-by'), + type: 'select', + mainWidgetProps: this.mainWidgetProps, + operators: [...this.defaultSelectOperators, 'isOwner', 'isReviewer'], + fieldSettings: { + asyncFetch: advancedSearchClassBase.autocomplete({ + searchIndex: [SearchIndex.USER, SearchIndex.TEAM], + entityField: EntityFields.DISPLAY_NAME_KEYWORD, + }), + useAsyncSearch: true, + }, + }, + }; + } + + public getMapFields = () => { + return this.mapFields; + }; + + public searchAutocomplete: (args: { + searchIndex: SearchIndex | SearchIndex[]; + fieldName: string; + fieldLabel: string; + }) => SelectFieldSettings['asyncFetch'] = ({ + searchIndex, + fieldName, + fieldLabel, + }) => { + return (search) => { + return searchData( + Array.isArray(search) ? search.join(',') : search ?? '', + 1, + PAGE_SIZE_BASE, + '', + '', + '', + searchIndex ?? SearchIndex.DATA_ASSET, + false, + false, + false + ).then((response) => { + const data = response.data.hits.hits; + + return { + values: data.map((item) => ({ + value: get(item._source, fieldName, ''), + title: get(item._source, fieldLabel, ''), + })), + hasMore: false, + }; + }); + }; + }; + + mainWidgetProps = { + fullWidth: true, + valueLabel: t('label.criteria') + ':', + }; + + public autoCompleteTier: SelectFieldSettings['asyncFetch'] = async ( + searchOrValues: string | (string | number)[] | null + ) => { + let resolvedTierOptions: ListItem[] = []; + + try { + const { data: tiers } = await getTags({ + parent: 'Tier', + limit: 50, + }); + + const tierFields = tiers.map((tier) => ({ + title: tier.fullyQualifiedName, // tier.name, + value: tier.fullyQualifiedName, + })); + + resolvedTierOptions = tierFields as ListItem[]; + } catch (error) { + resolvedTierOptions = []; + } + + const search = Array.isArray(searchOrValues) + ? searchOrValues.join(',') + : searchOrValues; + + return { + values: !search + ? resolvedTierOptions + : resolvedTierOptions.filter((tier: ListItem) => + tier.title?.toLowerCase()?.includes(toLower(search)) + ), + hasMore: false, + } as AsyncFetchListValuesResult; + }; + + public getCommonConfig = () => { + return { + ...getFieldsByKeys(COMMON_ENTITY_FIELDS_KEYS, this.mapFields), }; }; @@ -389,8 +493,14 @@ class JSONLogicSearchClassBase { ): Fields { let configs: Fields = {}; const configIndexMapping: Partial> = { - [SearchIndex.TABLE]: this.tableEntityFields, - [SearchIndex.GLOSSARY_TERM]: this.glossaryEntityFields, + [SearchIndex.TABLE]: getFieldsByKeys( + TABLE_ENTITY_FIELDS_KEYS, + this.mapFields + ), + [SearchIndex.GLOSSARY_TERM]: getFieldsByKeys( + GLOSSARY_ENTITY_FIELDS_KEYS, + this.mapFields + ), }; entitySearchIndex.forEach((index) => { @@ -404,13 +514,12 @@ class JSONLogicSearchClassBase { */ public getQueryBuilderFields = ({ entitySearchIndex = [SearchIndex.TABLE], - tierOptions, }: { entitySearchIndex?: Array; tierOptions?: Promise; }) => { const fieldsConfig = { - ...this.getCommonConfig({ entitySearchIndex, tierOptions }), + ...this.getCommonConfig(), ...this.getEntitySpecificQueryBuilderFields(entitySearchIndex), }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.tsx index afd1b669fc3..4ab55ea0ae5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.tsx @@ -864,3 +864,18 @@ export const getEntityTypeAggregationFilter = ( return qFilter; }; + +export const getFieldsByKeys = ( + keys: EntityReferenceFields[], + mapFields: Record +): Record => { + const filteredFields: Record = {}; + + keys.forEach((key) => { + if (mapFields[key]) { + filteredFields[key] = mapFields[key]; + } + }); + + return filteredFields; +};