fix(ui): tree dropdown for glossary terms inside form (#21173)

* fix: add tree dropdown for glossary terms inside form

* fix: add domain form
This commit is contained in:
Pranita Fulsundar 2025-05-14 14:30:49 +05:30 committed by GitHub
parent 2f4355bd4e
commit c185862e40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 69 additions and 20 deletions

View File

@ -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);

View File

@ -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,

View File

@ -40,4 +40,6 @@ export interface AsyncSelectListProps {
search: string,
page: number
) => Promise<PagingResponse<SelectOption[]>>;
open?: boolean;
hasNoActionButtons?: boolean;
}

View File

@ -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<Omit<AsyncSelectListProps, 'fetchOptions'>> = ({
isSubmitLoading,
filterOptions = [],
onCancel,
open: openProp = true,
hasNoActionButtons,
...props
}) => {
const [isLoading, setIsLoading] = useState(false);
@ -76,9 +85,14 @@ const TreeAsyncSelectList: FC<Omit<AsyncSelectListProps, 'fetchOptions'>> = ({
const expandableKeys = useRef<string[]>([]);
const [expandedRowKeys, setExpandedRowKeys] = useState<Key[]>([]);
const [searchOptions, setSearchOptions] = useState<Glossary[] | null>(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<Omit<AsyncSelectListProps, 'fetchOptions'>> = ({
return (
<TreeSelect
autoFocus
open
showSearch
treeCheckStrictly
treeCheckable
autoFocus={open}
className="async-select-list"
data-testid="tag-selector"
dropdownRender={dropdownRender}
dropdownRender={
hasNoActionButtons ? (menu: ReactElement) => menu : dropdownRender
}
dropdownStyle={{ width: 300 }}
filterTreeNode={false}
loadData={({ id, name }) => {
@ -315,6 +330,7 @@ const TreeAsyncSelectList: FC<Omit<AsyncSelectListProps, 'fetchOptions'>> = ({
/>
)
}
open={open}
showCheckedStrategy={TreeSelect.SHOW_ALL}
style={{ width: '100%' }}
switcherIcon={
@ -328,6 +344,7 @@ const TreeAsyncSelectList: FC<Omit<AsyncSelectListProps, 'fetchOptions'>> = ({
treeData={treeData}
treeExpandedKeys={isEmpty(searchOptions) ? undefined : expandedRowKeys}
onChange={handleChange}
onDropdownVisibleChange={handleDropdownVisibleChange}
onSearch={onSearch}
onTreeExpand={setExpandedRowKeys}
{...props}

View File

@ -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 {

View File

@ -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<TagSuggestionProps> = ({
@ -39,6 +43,9 @@ const TagSuggestion: React.FC<TagSuggestionProps> = ({
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<TagSuggestionProps> = ({
}
};
return (
<AsyncSelectList
fetchOptions={isGlossaryType ? fetchGlossaryList : tagClassBase.getTags}
initialOptions={initialOptions}
{...selectProps}
mode="multiple"
placeholder={
placeholder ??
t('label.select-field', {
field: t('label.tag-plural'),
})
}
value={value?.map((item) => 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 ? (
<TreeAsyncSelectList
{...commonProps}
hasNoActionButtons={hasNoActionButtons}
open={open}
/>
) : (
<AsyncSelectList {...commonProps} />
);
};

View File

@ -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 = (
<TreeAsyncSelectList
{...(props as unknown as Omit<AsyncSelectListProps, 'fetchOptions'>)}
/>
);
break;
case FieldTypes.ASYNC_SELECT_LIST:
fieldElement = (
<AsyncSelectList {...(props as unknown as AsyncSelectListProps)} />