mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-10-31 18:48:35 +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
	 Pranita Fulsundar
						Pranita Fulsundar