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: { selectProps: {
'data-testid': 'glossary-terms-container', 'data-testid': 'glossary-terms-container',
}, },
open: false,
hasNoActionButtons: true,
isTreeSelect: true,
tagType: TagSource.Glossary, tagType: TagSource.Glossary,
placeholder: t('label.select-field', { placeholder: t('label.select-field', {
field: t('label.glossary-term-plural'), field: t('label.glossary-term-plural'),
@ -248,7 +251,7 @@ const AddDomainForm = ({
style, style,
experts: expertsList.map((item) => item.name ?? ''), experts: expertsList.map((item) => item.name ?? ''),
owners: ownersList ?? [], owners: ownersList ?? [],
tags: [...(formData.tags || []), ...(formData.glossaryTerms || [])], tags: [...(formData.tags ?? []), ...(formData.glossaryTerms ?? [])],
} as CreateDomain | CreateDataProduct; } as CreateDomain | CreateDataProduct;
onSubmit(data); onSubmit(data);

View File

@ -248,7 +248,7 @@ const AddGlossaryTermForm = ({
required: false, required: false,
label: t('label.related-term-plural'), label: t('label.related-term-plural'),
id: 'root/relatedTerms', id: 'root/relatedTerms',
type: FieldTypes.ASYNC_SELECT_LIST, type: FieldTypes.TREE_ASYNC_SELECT_LIST,
props: { props: {
className: 'glossary-select', className: 'glossary-select',
'data-testid': 'related-terms', 'data-testid': 'related-terms',
@ -256,6 +256,8 @@ const AddGlossaryTermForm = ({
placeholder: t('label.add-entity', { placeholder: t('label.add-entity', {
entity: t('label.related-term-plural'), entity: t('label.related-term-plural'),
}), }),
open: false,
hasNoActionButtons: true,
fetchOptions: fetchGlossaryList, fetchOptions: fetchGlossaryList,
initialOptions: glossaryTerm?.relatedTerms?.map((data) => ({ initialOptions: glossaryTerm?.relatedTerms?.map((data) => ({
label: data.fullyQualifiedName, label: data.fullyQualifiedName,

View File

@ -40,4 +40,6 @@ export interface AsyncSelectListProps {
search: string, search: string,
page: number page: number
) => Promise<PagingResponse<SelectOption[]>>; ) => 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 { AxiosError } from 'axios';
import { debounce, get, isEmpty, isNull, isUndefined, pick } from 'lodash'; import { debounce, get, isEmpty, isNull, isUndefined, pick } from 'lodash';
import { CustomTagProps } from 'rc-select/lib/BaseSelect'; 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 { useTranslation } from 'react-i18next';
import { ReactComponent as ArrowIcon } from '../../../assets/svg/ic-arrow-down.svg'; import { ReactComponent as ArrowIcon } from '../../../assets/svg/ic-arrow-down.svg';
import { PAGE_SIZE_LARGE, TEXT_BODY_COLOR } from '../../../constants/constants'; import { PAGE_SIZE_LARGE, TEXT_BODY_COLOR } from '../../../constants/constants';
@ -67,6 +74,8 @@ const TreeAsyncSelectList: FC<Omit<AsyncSelectListProps, 'fetchOptions'>> = ({
isSubmitLoading, isSubmitLoading,
filterOptions = [], filterOptions = [],
onCancel, onCancel,
open: openProp = true,
hasNoActionButtons,
...props ...props
}) => { }) => {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@ -76,9 +85,14 @@ const TreeAsyncSelectList: FC<Omit<AsyncSelectListProps, 'fetchOptions'>> = ({
const expandableKeys = useRef<string[]>([]); const expandableKeys = useRef<string[]>([]);
const [expandedRowKeys, setExpandedRowKeys] = useState<Key[]>([]); const [expandedRowKeys, setExpandedRowKeys] = useState<Key[]>([]);
const [searchOptions, setSearchOptions] = useState<Glossary[] | null>(null); const [searchOptions, setSearchOptions] = useState<Glossary[] | null>(null);
const [open, setOpen] = useState(openProp); // state for controlling dropdown visibility
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const handleDropdownVisibleChange = (visible: boolean) => {
setOpen(visible);
};
const fetchGlossaryListInternal = async () => { const fetchGlossaryListInternal = async () => {
setIsLoading(true); setIsLoading(true);
try { try {
@ -286,14 +300,15 @@ const TreeAsyncSelectList: FC<Omit<AsyncSelectListProps, 'fetchOptions'>> = ({
return ( return (
<TreeSelect <TreeSelect
autoFocus
open
showSearch showSearch
treeCheckStrictly treeCheckStrictly
treeCheckable treeCheckable
autoFocus={open}
className="async-select-list" className="async-select-list"
data-testid="tag-selector" data-testid="tag-selector"
dropdownRender={dropdownRender} dropdownRender={
hasNoActionButtons ? (menu: ReactElement) => menu : dropdownRender
}
dropdownStyle={{ width: 300 }} dropdownStyle={{ width: 300 }}
filterTreeNode={false} filterTreeNode={false}
loadData={({ id, name }) => { loadData={({ id, name }) => {
@ -315,6 +330,7 @@ const TreeAsyncSelectList: FC<Omit<AsyncSelectListProps, 'fetchOptions'>> = ({
/> />
) )
} }
open={open}
showCheckedStrategy={TreeSelect.SHOW_ALL} showCheckedStrategy={TreeSelect.SHOW_ALL}
style={{ width: '100%' }} style={{ width: '100%' }}
switcherIcon={ switcherIcon={
@ -328,6 +344,7 @@ const TreeAsyncSelectList: FC<Omit<AsyncSelectListProps, 'fetchOptions'>> = ({
treeData={treeData} treeData={treeData}
treeExpandedKeys={isEmpty(searchOptions) ? undefined : expandedRowKeys} treeExpandedKeys={isEmpty(searchOptions) ? undefined : expandedRowKeys}
onChange={handleChange} onChange={handleChange}
onDropdownVisibleChange={handleDropdownVisibleChange}
onSearch={onSearch} onSearch={onSearch}
onTreeExpand={setExpandedRowKeys} onTreeExpand={setExpandedRowKeys}
{...props} {...props}

View File

@ -42,6 +42,7 @@ export enum FieldTypes {
COLOR_PICKER = 'color_picker', COLOR_PICKER = 'color_picker',
DOMAIN_SELECT = 'domain_select', DOMAIN_SELECT = 'domain_select',
CRON_EDITOR = 'cron_editor', CRON_EDITOR = 'cron_editor',
TREE_ASYNC_SELECT_LIST = 'tree_async_select_list',
} }
export enum HelperTextType { export enum HelperTextType {

View File

@ -18,6 +18,7 @@ import { EntityTags } from 'Models';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import AsyncSelectList from '../../../components/common/AsyncSelectList/AsyncSelectList'; import AsyncSelectList from '../../../components/common/AsyncSelectList/AsyncSelectList';
import { SelectOption } from '../../../components/common/AsyncSelectList/AsyncSelectList.interface'; import { SelectOption } from '../../../components/common/AsyncSelectList/AsyncSelectList.interface';
import TreeAsyncSelectList from '../../../components/common/AsyncSelectList/TreeAsyncSelectList';
import { TagSource } from '../../../generated/entity/data/container'; import { TagSource } from '../../../generated/entity/data/container';
import { TagLabel } from '../../../generated/type/tagLabel'; import { TagLabel } from '../../../generated/type/tagLabel';
import tagClassBase from '../../../utils/TagClassBase'; import tagClassBase from '../../../utils/TagClassBase';
@ -30,6 +31,9 @@ export interface TagSuggestionProps {
initialOptions?: SelectOption[]; initialOptions?: SelectOption[];
onChange?: (newTags: TagLabel[]) => void; onChange?: (newTags: TagLabel[]) => void;
selectProps?: SelectProps; selectProps?: SelectProps;
isTreeSelect?: boolean;
hasNoActionButtons?: boolean;
open?: boolean;
} }
const TagSuggestion: React.FC<TagSuggestionProps> = ({ const TagSuggestion: React.FC<TagSuggestionProps> = ({
@ -39,6 +43,9 @@ const TagSuggestion: React.FC<TagSuggestionProps> = ({
initialOptions, initialOptions,
tagType = TagSource.Classification, tagType = TagSource.Classification,
selectProps, selectProps,
isTreeSelect = false,
hasNoActionButtons = false,
open = true,
}) => { }) => {
const isGlossaryType = useMemo( const isGlossaryType = useMemo(
() => tagType === TagSource.Glossary, () => tagType === TagSource.Glossary,
@ -81,21 +88,28 @@ const TagSuggestion: React.FC<TagSuggestionProps> = ({
} }
}; };
return ( const commonProps = {
<AsyncSelectList fetchOptions: isGlossaryType ? fetchGlossaryList : tagClassBase.getTags,
fetchOptions={isGlossaryType ? fetchGlossaryList : tagClassBase.getTags} initialOptions,
initialOptions={initialOptions} ...selectProps,
{...selectProps} mode: 'multiple' as const,
mode="multiple" placeholder:
placeholder={ placeholder ??
placeholder ?? t('label.select-field', {
t('label.select-field', { field: t('label.tag-plural'),
field: t('label.tag-plural'), }),
}) value: value?.map((item) => item.tagFQN) ?? [],
} onChange: handleTagSelection,
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 React, { Fragment, ReactNode } from 'react';
import AsyncSelectList from '../components/common/AsyncSelectList/AsyncSelectList'; import AsyncSelectList from '../components/common/AsyncSelectList/AsyncSelectList';
import { AsyncSelectListProps } from '../components/common/AsyncSelectList/AsyncSelectList.interface'; import { AsyncSelectListProps } from '../components/common/AsyncSelectList/AsyncSelectList.interface';
import TreeAsyncSelectList from '../components/common/AsyncSelectList/TreeAsyncSelectList';
import ColorPicker from '../components/common/ColorPicker/ColorPicker.component'; import ColorPicker from '../components/common/ColorPicker/ColorPicker.component';
import DomainSelectableList from '../components/common/DomainSelectableList/DomainSelectableList.component'; import DomainSelectableList from '../components/common/DomainSelectableList/DomainSelectableList.component';
import { DomainSelectableListProps } from '../components/common/DomainSelectableList/DomainSelectableList.interface'; import { DomainSelectableListProps } from '../components/common/DomainSelectableList/DomainSelectableList.interface';
@ -172,6 +173,15 @@ export const getField = (field: FieldProp) => {
break; break;
case FieldTypes.TREE_ASYNC_SELECT_LIST:
fieldElement = (
<TreeAsyncSelectList
{...(props as unknown as Omit<AsyncSelectListProps, 'fetchOptions'>)}
/>
);
break;
case FieldTypes.ASYNC_SELECT_LIST: case FieldTypes.ASYNC_SELECT_LIST:
fieldElement = ( fieldElement = (
<AsyncSelectList {...(props as unknown as AsyncSelectListProps)} /> <AsyncSelectList {...(props as unknown as AsyncSelectListProps)} />