From c185862e4090db2129edde18c29980931e7c2a1d Mon Sep 17 00:00:00 2001 From: Pranita Fulsundar Date: Wed, 14 May 2025 14:30:49 +0530 Subject: [PATCH] fix(ui): tree dropdown for glossary terms inside form (#21173) * fix: add tree dropdown for glossary terms inside form * fix: add domain form --- .../AddDomainForm/AddDomainForm.component.tsx | 5 ++- .../AddGlossaryTermForm.component.tsx | 4 +- .../AsyncSelectList.interface.ts | 2 + .../AsyncSelectList/TreeAsyncSelectList.tsx | 25 +++++++++-- .../ui/src/interface/FormUtils.interface.ts | 1 + .../pages/TasksPage/shared/TagSuggestion.tsx | 42 ++++++++++++------- .../main/resources/ui/src/utils/formUtils.tsx | 10 +++++ 7 files changed, 69 insertions(+), 20 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Domain/AddDomainForm/AddDomainForm.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Domain/AddDomainForm/AddDomainForm.component.tsx index 3555870aee2..6f586b58e81 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Domain/AddDomainForm/AddDomainForm.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Domain/AddDomainForm/AddDomainForm.component.tsx @@ -117,6 +117,9 @@ const AddDomainForm = ({ selectProps: { 'data-testid': 'glossary-terms-container', }, + open: false, + hasNoActionButtons: true, + isTreeSelect: true, tagType: TagSource.Glossary, placeholder: t('label.select-field', { field: t('label.glossary-term-plural'), @@ -248,7 +251,7 @@ const AddDomainForm = ({ style, experts: expertsList.map((item) => item.name ?? ''), owners: ownersList ?? [], - tags: [...(formData.tags || []), ...(formData.glossaryTerms || [])], + tags: [...(formData.tags ?? []), ...(formData.glossaryTerms ?? [])], } as CreateDomain | CreateDataProduct; onSubmit(data); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/AddGlossaryTermForm/AddGlossaryTermForm.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/AddGlossaryTermForm/AddGlossaryTermForm.component.tsx index 20950863841..86f45a555ac 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/AddGlossaryTermForm/AddGlossaryTermForm.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/AddGlossaryTermForm/AddGlossaryTermForm.component.tsx @@ -248,7 +248,7 @@ const AddGlossaryTermForm = ({ required: false, label: t('label.related-term-plural'), id: 'root/relatedTerms', - type: FieldTypes.ASYNC_SELECT_LIST, + type: FieldTypes.TREE_ASYNC_SELECT_LIST, props: { className: 'glossary-select', 'data-testid': 'related-terms', @@ -256,6 +256,8 @@ const AddGlossaryTermForm = ({ placeholder: t('label.add-entity', { entity: t('label.related-term-plural'), }), + open: false, + hasNoActionButtons: true, fetchOptions: fetchGlossaryList, initialOptions: glossaryTerm?.relatedTerms?.map((data) => ({ label: data.fullyQualifiedName, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/AsyncSelectList/AsyncSelectList.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/common/AsyncSelectList/AsyncSelectList.interface.ts index ca89368b7ab..ec011272919 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/AsyncSelectList/AsyncSelectList.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/AsyncSelectList/AsyncSelectList.interface.ts @@ -40,4 +40,6 @@ export interface AsyncSelectListProps { search: string, page: number ) => Promise>; + open?: boolean; + hasNoActionButtons?: boolean; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/AsyncSelectList/TreeAsyncSelectList.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/AsyncSelectList/TreeAsyncSelectList.tsx index ccd96f06df9..92df52526aa 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/AsyncSelectList/TreeAsyncSelectList.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/AsyncSelectList/TreeAsyncSelectList.tsx @@ -25,7 +25,14 @@ import { Key } from 'antd/lib/table/interface'; import { AxiosError } from 'axios'; import { debounce, get, isEmpty, isNull, isUndefined, pick } from 'lodash'; import { CustomTagProps } from 'rc-select/lib/BaseSelect'; -import React, { FC, useEffect, useMemo, useRef, useState } from 'react'; +import React, { + FC, + ReactElement, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { useTranslation } from 'react-i18next'; import { ReactComponent as ArrowIcon } from '../../../assets/svg/ic-arrow-down.svg'; import { PAGE_SIZE_LARGE, TEXT_BODY_COLOR } from '../../../constants/constants'; @@ -67,6 +74,8 @@ const TreeAsyncSelectList: FC> = ({ isSubmitLoading, filterOptions = [], onCancel, + open: openProp = true, + hasNoActionButtons, ...props }) => { const [isLoading, setIsLoading] = useState(false); @@ -76,9 +85,14 @@ const TreeAsyncSelectList: FC> = ({ const expandableKeys = useRef([]); const [expandedRowKeys, setExpandedRowKeys] = useState([]); const [searchOptions, setSearchOptions] = useState(null); + const [open, setOpen] = useState(openProp); // state for controlling dropdown visibility const form = Form.useFormInstance(); + const handleDropdownVisibleChange = (visible: boolean) => { + setOpen(visible); + }; + const fetchGlossaryListInternal = async () => { setIsLoading(true); try { @@ -286,14 +300,15 @@ const TreeAsyncSelectList: FC> = ({ return ( menu : dropdownRender + } dropdownStyle={{ width: 300 }} filterTreeNode={false} loadData={({ id, name }) => { @@ -315,6 +330,7 @@ const TreeAsyncSelectList: FC> = ({ /> ) } + open={open} showCheckedStrategy={TreeSelect.SHOW_ALL} style={{ width: '100%' }} switcherIcon={ @@ -328,6 +344,7 @@ const TreeAsyncSelectList: FC> = ({ treeData={treeData} treeExpandedKeys={isEmpty(searchOptions) ? undefined : expandedRowKeys} onChange={handleChange} + onDropdownVisibleChange={handleDropdownVisibleChange} onSearch={onSearch} onTreeExpand={setExpandedRowKeys} {...props} diff --git a/openmetadata-ui/src/main/resources/ui/src/interface/FormUtils.interface.ts b/openmetadata-ui/src/main/resources/ui/src/interface/FormUtils.interface.ts index 6a09c4be747..884ae7bbab0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/interface/FormUtils.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/interface/FormUtils.interface.ts @@ -42,6 +42,7 @@ export enum FieldTypes { COLOR_PICKER = 'color_picker', DOMAIN_SELECT = 'domain_select', CRON_EDITOR = 'cron_editor', + TREE_ASYNC_SELECT_LIST = 'tree_async_select_list', } export enum HelperTextType { diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/TagSuggestion.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/TagSuggestion.tsx index 4cf17be92e6..26220e079e7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/TagSuggestion.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/TagSuggestion.tsx @@ -18,6 +18,7 @@ import { EntityTags } from 'Models'; import React, { useMemo } from 'react'; import AsyncSelectList from '../../../components/common/AsyncSelectList/AsyncSelectList'; import { SelectOption } from '../../../components/common/AsyncSelectList/AsyncSelectList.interface'; +import TreeAsyncSelectList from '../../../components/common/AsyncSelectList/TreeAsyncSelectList'; import { TagSource } from '../../../generated/entity/data/container'; import { TagLabel } from '../../../generated/type/tagLabel'; import tagClassBase from '../../../utils/TagClassBase'; @@ -30,6 +31,9 @@ export interface TagSuggestionProps { initialOptions?: SelectOption[]; onChange?: (newTags: TagLabel[]) => void; selectProps?: SelectProps; + isTreeSelect?: boolean; + hasNoActionButtons?: boolean; + open?: boolean; } const TagSuggestion: React.FC = ({ @@ -39,6 +43,9 @@ const TagSuggestion: React.FC = ({ initialOptions, tagType = TagSource.Classification, selectProps, + isTreeSelect = false, + hasNoActionButtons = false, + open = true, }) => { const isGlossaryType = useMemo( () => tagType === TagSource.Glossary, @@ -81,21 +88,28 @@ const TagSuggestion: React.FC = ({ } }; - return ( - item.tagFQN) ?? []} - onChange={handleTagSelection} + const commonProps = { + fetchOptions: isGlossaryType ? fetchGlossaryList : tagClassBase.getTags, + initialOptions, + ...selectProps, + mode: 'multiple' as const, + placeholder: + placeholder ?? + t('label.select-field', { + field: t('label.tag-plural'), + }), + value: value?.map((item) => item.tagFQN) ?? [], + onChange: handleTagSelection, + }; + + return isTreeSelect ? ( + + ) : ( + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx index e951ff53437..ede73b32a29 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx @@ -31,6 +31,7 @@ import { compact, startCase, toString } from 'lodash'; import React, { Fragment, ReactNode } from 'react'; import AsyncSelectList from '../components/common/AsyncSelectList/AsyncSelectList'; import { AsyncSelectListProps } from '../components/common/AsyncSelectList/AsyncSelectList.interface'; +import TreeAsyncSelectList from '../components/common/AsyncSelectList/TreeAsyncSelectList'; import ColorPicker from '../components/common/ColorPicker/ColorPicker.component'; import DomainSelectableList from '../components/common/DomainSelectableList/DomainSelectableList.component'; import { DomainSelectableListProps } from '../components/common/DomainSelectableList/DomainSelectableList.interface'; @@ -172,6 +173,15 @@ export const getField = (field: FieldProp) => { break; + case FieldTypes.TREE_ASYNC_SELECT_LIST: + fieldElement = ( + )} + /> + ); + + break; + case FieldTypes.ASYNC_SELECT_LIST: fieldElement = (