mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-30 18:17:53 +00:00
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:
parent
2f4355bd4e
commit
c185862e40
@ -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);
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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} />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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)} />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user