mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-10-31 02:29:03 +00:00 
			
		
		
		
	Glossary expand fix (#16066)
* Fix #16046 : modify glossaryTerms api endpoint to support querying immediate children with childrenCount * fix(ui): support lazy loading for n level of glossary * fix modal update for glossaryTerm * fixed expand/collapse button bug and update glossary page issue --------- Co-authored-by: sonikashah <sonikashah94@gmail.com> Co-authored-by: Shailesh Parmar <shailesh.parmar.webdev@gmail.com>
This commit is contained in:
		
							parent
							
								
									97d3625302
								
							
						
					
					
						commit
						0701fed67e
					
				| @ -32,6 +32,7 @@ import TabsLabel from '../../common/TabsLabel/TabsLabel.component'; | ||||
| import GlossaryDetailsRightPanel from '../GlossaryDetailsRightPanel/GlossaryDetailsRightPanel.component'; | ||||
| import GlossaryHeader from '../GlossaryHeader/GlossaryHeader.component'; | ||||
| import GlossaryTermTab from '../GlossaryTermTab/GlossaryTermTab.component'; | ||||
| import { useGlossaryStore } from '../useGlossary.store'; | ||||
| import './glossary-details.less'; | ||||
| import { | ||||
|   GlossaryDetailsProps, | ||||
| @ -40,11 +41,9 @@ import { | ||||
| 
 | ||||
| const GlossaryDetails = ({ | ||||
|   permissions, | ||||
|   glossary, | ||||
|   updateGlossary, | ||||
|   updateVote, | ||||
|   handleGlossaryDelete, | ||||
|   glossaryTerms, | ||||
|   termsLoading, | ||||
|   refreshGlossaryTerms, | ||||
|   onAddGlossaryTerm, | ||||
| @ -54,7 +53,7 @@ const GlossaryDetails = ({ | ||||
| }: GlossaryDetailsProps) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const history = useHistory(); | ||||
| 
 | ||||
|   const { activeGlossary: glossary } = useGlossaryStore(); | ||||
|   const { tab: activeTab } = useParams<{ tab: string }>(); | ||||
|   const [feedCount, setFeedCount] = useState<FeedCounts>( | ||||
|     FEED_COUNT_INITIAL_DATA | ||||
| @ -152,7 +151,7 @@ const GlossaryDetails = ({ | ||||
|               entityName={getEntityName(glossary)} | ||||
|               entityType={EntityType.GLOSSARY} | ||||
|               hasEditAccess={permissions.EditDescription || permissions.EditAll} | ||||
|               isDescriptionExpanded={isEmpty(glossaryTerms)} | ||||
|               isDescriptionExpanded={isEmpty(glossary.children)} | ||||
|               isEdit={isDescriptionEditable} | ||||
|               owner={glossary?.owner} | ||||
|               showActions={!glossary.deleted} | ||||
| @ -164,10 +163,8 @@ const GlossaryDetails = ({ | ||||
| 
 | ||||
|             <GlossaryTermTab | ||||
|               isGlossary | ||||
|               childGlossaryTerms={glossaryTerms} | ||||
|               permissions={permissions} | ||||
|               refreshGlossaryTerms={refreshGlossaryTerms} | ||||
|               selectedData={glossary} | ||||
|               termsLoading={termsLoading} | ||||
|               onAddGlossaryTerm={onAddGlossaryTerm} | ||||
|               onEditGlossaryTerm={onEditGlossaryTerm} | ||||
| @ -192,7 +189,6 @@ const GlossaryDetails = ({ | ||||
|     isVersionView, | ||||
|     permissions, | ||||
|     glossary, | ||||
|     glossaryTerms, | ||||
|     termsLoading, | ||||
|     description, | ||||
|     isDescriptionEditable, | ||||
|  | ||||
| @ -24,8 +24,7 @@ export enum GlossaryTabs { | ||||
| export type GlossaryDetailsProps = { | ||||
|   isVersionView?: boolean; | ||||
|   permissions: OperationPermission; | ||||
|   glossary: Glossary; | ||||
|   glossaryTerms: GlossaryTerm[]; | ||||
| 
 | ||||
|   termsLoading: boolean; | ||||
|   updateGlossary: (value: Glossary) => Promise<void>; | ||||
|   updateVote?: (data: VotingDataProps) => Promise<void>; | ||||
|  | ||||
| @ -12,27 +12,22 @@ | ||||
|  */ | ||||
| 
 | ||||
| import { FilterOutlined } from '@ant-design/icons'; | ||||
| import { | ||||
|   Button, | ||||
|   Col, | ||||
|   Modal, | ||||
|   Row, | ||||
|   Space, | ||||
|   Table, | ||||
|   TableProps, | ||||
|   Tooltip, | ||||
| } from 'antd'; | ||||
| import Icon from '@ant-design/icons/lib/components/Icon'; | ||||
| import { Button, Col, Modal, Row, Space, TableProps, Tooltip } from 'antd'; | ||||
| import { ColumnsType, ExpandableConfig } from 'antd/lib/table/interface'; | ||||
| import { AxiosError } from 'axios'; | ||||
| import classNames from 'classnames'; | ||||
| import { compare } from 'fast-json-patch'; | ||||
| import { isEmpty, isUndefined } from 'lodash'; | ||||
| import React, { useCallback, useEffect, useMemo, useState } from 'react'; | ||||
| import { cloneDeep, isEmpty, isUndefined } from 'lodash'; | ||||
| import React, { useCallback, useMemo, useState } from 'react'; | ||||
| import { DndProvider } from 'react-dnd'; | ||||
| import { HTML5Backend } from 'react-dnd-html5-backend'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import { ReactComponent as IconDrag } from '../../../assets/svg/drag.svg'; | ||||
| import { ReactComponent as EditIcon } from '../../../assets/svg/edit-new.svg'; | ||||
| import { ReactComponent as IconDown } from '../../../assets/svg/ic-arrow-down.svg'; | ||||
| import { ReactComponent as IconRight } from '../../../assets/svg/ic-arrow-right.svg'; | ||||
| import { ReactComponent as DownUpArrowIcon } from '../../../assets/svg/ic-down-up-arrow.svg'; | ||||
| import { ReactComponent as UpDownArrowIcon } from '../../../assets/svg/ic-up-down-arrow.svg'; | ||||
| import { ReactComponent as PlusOutlinedIcon } from '../../../assets/svg/plus-outlined.svg'; | ||||
| @ -40,7 +35,11 @@ import ErrorPlaceHolder from '../../../components/common/ErrorWithPlaceholder/Er | ||||
| import { OwnerLabel } from '../../../components/common/OwnerLabel/OwnerLabel.component'; | ||||
| import RichTextEditorPreviewer from '../../../components/common/RichTextEditor/RichTextEditorPreviewer'; | ||||
| import StatusBadge from '../../../components/common/StatusBadge/StatusBadge.component'; | ||||
| import { DE_ACTIVE_COLOR } from '../../../constants/constants'; | ||||
| import { | ||||
|   API_RES_MAX_SIZE, | ||||
|   DE_ACTIVE_COLOR, | ||||
|   TEXT_BODY_COLOR, | ||||
| } from '../../../constants/constants'; | ||||
| import { GLOSSARIES_DOCS } from '../../../constants/docs.constants'; | ||||
| import { TABLE_CONSTANTS } from '../../../constants/Teams.constants'; | ||||
| import { ERROR_PLACEHOLDER_TYPE } from '../../../enums/common.enum'; | ||||
| @ -50,20 +49,28 @@ import { | ||||
|   Status, | ||||
| } from '../../../generated/entity/data/glossaryTerm'; | ||||
| import { useApplicationStore } from '../../../hooks/useApplicationStore'; | ||||
| import { patchGlossaryTerm } from '../../../rest/glossaryAPI'; | ||||
| import { | ||||
|   getFirstLevelGlossaryTerms, | ||||
|   getGlossaryTerms, | ||||
|   GlossaryTermWithChildren, | ||||
|   patchGlossaryTerm, | ||||
| } from '../../../rest/glossaryAPI'; | ||||
| import { Transi18next } from '../../../utils/CommonUtils'; | ||||
| import { getEntityName } from '../../../utils/EntityUtils'; | ||||
| import Fqn from '../../../utils/Fqn'; | ||||
| import { | ||||
|   buildTree, | ||||
|   findExpandableKeysForArray, | ||||
|   findGlossaryTermByFqn, | ||||
|   StatusClass, | ||||
|   StatusFilters, | ||||
| } from '../../../utils/GlossaryUtils'; | ||||
| import { getGlossaryPath } from '../../../utils/RouterUtils'; | ||||
| import { getTableExpandableConfig } from '../../../utils/TableUtils'; | ||||
| import { showErrorToast } from '../../../utils/ToastUtils'; | ||||
| import { DraggableBodyRowProps } from '../../common/Draggable/DraggableBodyRowProps.interface'; | ||||
| import Loader from '../../common/Loader/Loader'; | ||||
| import Table from '../../common/Table/Table'; | ||||
| import { ModifiedGlossary, useGlossaryStore } from '../useGlossary.store'; | ||||
| import { | ||||
|   GlossaryTermTabProps, | ||||
|   ModifiedGlossaryTerm, | ||||
| @ -71,37 +78,38 @@ import { | ||||
| } from './GlossaryTermTab.interface'; | ||||
| 
 | ||||
| const GlossaryTermTab = ({ | ||||
|   childGlossaryTerms = [], | ||||
|   refreshGlossaryTerms, | ||||
|   permissions, | ||||
|   isGlossary, | ||||
|   selectedData, | ||||
|   termsLoading, | ||||
|   onAddGlossaryTerm, | ||||
|   onEditGlossaryTerm, | ||||
|   className, | ||||
| }: GlossaryTermTabProps) => { | ||||
|   const { activeGlossary, updateActiveGlossary } = useGlossaryStore(); | ||||
|   const { theme } = useApplicationStore(); | ||||
|   const { t } = useTranslation(); | ||||
|   const [isLoading, setIsLoading] = useState(true); | ||||
|   const [glossaryTerms, setGlossaryTerms] = useState<ModifiedGlossaryTerm[]>( | ||||
|     [] | ||||
|   ); | ||||
| 
 | ||||
|   const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]); | ||||
|   const glossaryTerms = activeGlossary?.children as ModifiedGlossaryTerm[]; | ||||
| 
 | ||||
|   const [movedGlossaryTerm, setMovedGlossaryTerm] = | ||||
|     useState<MoveGlossaryTermType>(); | ||||
|   const [isModalOpen, setIsModalOpen] = useState<boolean>(false); | ||||
|   const [isTableLoading, setIsTableLoading] = useState(false); | ||||
|   const [isTableHovered, setIsTableHovered] = useState(false); | ||||
|   const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]); | ||||
| 
 | ||||
|   const glossaryTermStatus: Status | null = useMemo(() => { | ||||
|     if (!isGlossary) { | ||||
|       return (selectedData as GlossaryTerm).status ?? Status.Approved; | ||||
|       return (activeGlossary as GlossaryTerm).status ?? Status.Approved; | ||||
|     } | ||||
| 
 | ||||
|     return null; | ||||
|   }, [isGlossary, selectedData]); | ||||
|   }, [isGlossary, activeGlossary]); | ||||
| 
 | ||||
|   const expandableKeys = useMemo(() => { | ||||
|     return findExpandableKeysForArray(glossaryTerms); | ||||
|   }, [glossaryTerms]); | ||||
| 
 | ||||
|   const columns = useMemo(() => { | ||||
|     const data: ColumnsType<ModifiedGlossaryTerm> = [ | ||||
| @ -240,22 +248,71 @@ const GlossaryTermTab = ({ | ||||
|   }, [glossaryTerms, permissions]); | ||||
| 
 | ||||
|   const handleAddGlossaryTermClick = () => { | ||||
|     onAddGlossaryTerm(!isGlossary ? (selectedData as GlossaryTerm) : undefined); | ||||
|     onAddGlossaryTerm( | ||||
|       !isGlossary ? (activeGlossary as GlossaryTerm) : undefined | ||||
|     ); | ||||
|   }; | ||||
| 
 | ||||
|   const expandableConfig: ExpandableConfig<ModifiedGlossaryTerm> = useMemo( | ||||
|     () => ({ | ||||
|       ...getTableExpandableConfig<ModifiedGlossaryTerm>(true), | ||||
|       expandedRowKeys, | ||||
|       onExpand: (expanded, record) => { | ||||
|         setExpandedRowKeys( | ||||
|           expanded | ||||
|             ? [...expandedRowKeys, record.fullyQualifiedName || ''] | ||||
|             : expandedRowKeys.filter((key) => key !== record.fullyQualifiedName) | ||||
|       expandIcon: ({ expanded, onExpand, record }) => { | ||||
|         const { children, childrenCount } = record; | ||||
| 
 | ||||
|         return childrenCount ?? children?.length ?? 0 > 0 ? ( | ||||
|           <> | ||||
|             <IconDrag className="m-r-xs drag-icon" height={12} width={8} /> | ||||
|             <Icon | ||||
|               className="m-r-xs vertical-baseline" | ||||
|               component={expanded ? IconDown : IconRight} | ||||
|               data-testid="expand-icon" | ||||
|               style={{ fontSize: '10px', color: TEXT_BODY_COLOR }} | ||||
|               onClick={(e) => onExpand(record, e)} | ||||
|             /> | ||||
|           </> | ||||
|         ) : ( | ||||
|           <> | ||||
|             <IconDrag className="m-r-xs drag-icon" height={12} width={8} /> | ||||
|             <span className="expand-cell-empty-icon-container" /> | ||||
|           </> | ||||
|         ); | ||||
|       }, | ||||
|       expandedRowKeys: expandedRowKeys, | ||||
|       onExpand: async (expanded, record) => { | ||||
|         if (expanded) { | ||||
|           let children = record.children as GlossaryTermWithChildren[]; | ||||
|           if (!children?.length) { | ||||
|             const { data } = await getFirstLevelGlossaryTerms( | ||||
|               record.fullyQualifiedName || '' | ||||
|             ); | ||||
|             const terms = cloneDeep(glossaryTerms) ?? []; | ||||
| 
 | ||||
|             const item = findGlossaryTermByFqn( | ||||
|               terms, | ||||
|               record.fullyQualifiedName ?? '' | ||||
|             ); | ||||
| 
 | ||||
|             (item as ModifiedGlossary).children = data; | ||||
| 
 | ||||
|             updateActiveGlossary({ children: terms }); | ||||
| 
 | ||||
|             children = data; | ||||
|           } | ||||
|           setExpandedRowKeys([ | ||||
|             ...expandedRowKeys, | ||||
|             record.fullyQualifiedName || '', | ||||
|           ]); | ||||
| 
 | ||||
|           return children; | ||||
|         } else { | ||||
|           setExpandedRowKeys( | ||||
|             expandedRowKeys.filter((key) => key !== record.fullyQualifiedName) | ||||
|           ); | ||||
|         } | ||||
| 
 | ||||
|         return <Loader />; | ||||
|       }, | ||||
|     }), | ||||
|     [expandedRowKeys] | ||||
|     [glossaryTerms, updateActiveGlossary, expandedRowKeys] | ||||
|   ); | ||||
| 
 | ||||
|   const handleMoveRow = useCallback( | ||||
| @ -324,33 +381,47 @@ const GlossaryTermTab = ({ | ||||
|       handleTableHover, | ||||
|     } as DraggableBodyRowProps<GlossaryTerm>); | ||||
| 
 | ||||
|   const toggleExpandAll = () => { | ||||
|     if (expandedRowKeys.length === childGlossaryTerms.length) { | ||||
|       setExpandedRowKeys([]); | ||||
|     } else { | ||||
|       setExpandedRowKeys( | ||||
|         childGlossaryTerms.map((item) => item.fullyQualifiedName || '') | ||||
|       ); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const onDragConfirmationModalClose = useCallback(() => { | ||||
|     setIsModalOpen(false); | ||||
|     setIsTableHovered(false); | ||||
|   }, []); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (childGlossaryTerms) { | ||||
|       const data = buildTree(childGlossaryTerms); | ||||
|       setGlossaryTerms(data as ModifiedGlossaryTerm[]); | ||||
|       setExpandedRowKeys( | ||||
|         childGlossaryTerms.map((item) => item.fullyQualifiedName || '') | ||||
|       ); | ||||
|     } | ||||
|     setIsLoading(false); | ||||
|   }, [childGlossaryTerms]); | ||||
|   const fetchAllTerms = async () => { | ||||
|     setIsTableLoading(true); | ||||
|     const { data } = await getGlossaryTerms({ | ||||
|       glossary: activeGlossary?.id || '', | ||||
|       limit: API_RES_MAX_SIZE, | ||||
|       fields: 'children,owner,parent', | ||||
|     }); | ||||
|     updateActiveGlossary({ | ||||
|       children: buildTree(data) as ModifiedGlossary['children'], | ||||
|     }); | ||||
|     const keys = data.reduce((prev, curr) => { | ||||
|       if (curr.children?.length) { | ||||
|         prev.push(curr.fullyQualifiedName ?? ''); | ||||
|       } | ||||
| 
 | ||||
|   if (termsLoading || isLoading) { | ||||
|       return prev; | ||||
|     }, [] as string[]); | ||||
| 
 | ||||
|     setExpandedRowKeys(keys); | ||||
| 
 | ||||
|     setIsTableLoading(false); | ||||
|   }; | ||||
| 
 | ||||
|   const toggleExpandAll = () => { | ||||
|     if (expandedRowKeys.length === expandableKeys.length) { | ||||
|       setExpandedRowKeys([]); | ||||
|     } else { | ||||
|       fetchAllTerms(); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const isAllExpanded = useMemo(() => { | ||||
|     return expandedRowKeys.length === expandableKeys.length; | ||||
|   }, [expandedRowKeys, expandableKeys]); | ||||
| 
 | ||||
|   if (termsLoading) { | ||||
|     return <Loader />; | ||||
|   } | ||||
| 
 | ||||
| @ -382,15 +453,13 @@ const GlossaryTermTab = ({ | ||||
|             type="text" | ||||
|             onClick={toggleExpandAll}> | ||||
|             <Space align="center" size={4}> | ||||
|               {expandedRowKeys.length === childGlossaryTerms.length ? ( | ||||
|               {isAllExpanded ? ( | ||||
|                 <DownUpArrowIcon color={DE_ACTIVE_COLOR} height="14px" /> | ||||
|               ) : ( | ||||
|                 <UpDownArrowIcon color={DE_ACTIVE_COLOR} height="14px" /> | ||||
|               )} | ||||
| 
 | ||||
|               {expandedRowKeys.length === childGlossaryTerms.length | ||||
|                 ? t('label.collapse-all') | ||||
|                 : t('label.expand-all')} | ||||
|               {isAllExpanded ? t('label.collapse-all') : t('label.expand-all')} | ||||
|             </Space> | ||||
|           </Button> | ||||
|         </div> | ||||
| @ -437,7 +506,9 @@ const GlossaryTermTab = ({ | ||||
|             renderElement={<strong />} | ||||
|             values={{ | ||||
|               from: movedGlossaryTerm?.from.name, | ||||
|               to: movedGlossaryTerm?.to?.name ?? getEntityName(selectedData), | ||||
|               to: | ||||
|                 movedGlossaryTerm?.to?.name ?? | ||||
|                 (activeGlossary && getEntityName(activeGlossary)), | ||||
|               entity: isUndefined(movedGlossaryTerm?.to) | ||||
|                 ? '' | ||||
|                 : t('label.term-lowercase'), | ||||
|  | ||||
| @ -12,12 +12,9 @@ | ||||
|  */ | ||||
| 
 | ||||
| import { OperationPermission } from '../../../context/PermissionProvider/PermissionProvider.interface'; | ||||
| import { Glossary } from '../../../generated/entity/data/glossary'; | ||||
| import { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm'; | ||||
| 
 | ||||
| export interface GlossaryTermTabProps { | ||||
|   selectedData: Glossary | GlossaryTerm; | ||||
|   childGlossaryTerms: GlossaryTerm[]; | ||||
|   isGlossary: boolean; | ||||
|   termsLoading: boolean; | ||||
|   refreshGlossaryTerms: () => void; | ||||
|  | ||||
| @ -25,13 +25,14 @@ import { | ||||
|   mockedGlossaryTerms, | ||||
|   MOCK_PERMISSIONS, | ||||
| } from '../../../mocks/Glossary.mock'; | ||||
| import { useGlossaryStore } from '../useGlossary.store'; | ||||
| import GlossaryTermTab from './GlossaryTermTab.component'; | ||||
| 
 | ||||
| const mockOnAddGlossaryTerm = jest.fn(); | ||||
| const mockRefreshGlossaryTerms = jest.fn(); | ||||
| const mockOnEditGlossaryTerm = jest.fn(); | ||||
| 
 | ||||
| const mockProps1 = { | ||||
| const mockProps = { | ||||
|   childGlossaryTerms: [], | ||||
|   isGlossary: false, | ||||
|   permissions: MOCK_PERMISSIONS, | ||||
| @ -42,11 +43,6 @@ const mockProps1 = { | ||||
|   onEditGlossaryTerm: mockOnEditGlossaryTerm, | ||||
| }; | ||||
| 
 | ||||
| const mockProps2 = { | ||||
|   ...mockProps1, | ||||
|   childGlossaryTerms: mockedGlossaryTerms, | ||||
| }; | ||||
| 
 | ||||
| jest.mock('../../../rest/glossaryAPI', () => ({ | ||||
|   getGlossaryTerms: jest | ||||
|     .fn() | ||||
| @ -75,10 +71,16 @@ jest.mock('../../common/Loader/Loader', () => | ||||
| jest.mock('../../common/OwnerLabel/OwnerLabel.component', () => ({ | ||||
|   OwnerLabel: jest.fn().mockImplementation(() => <div>OwnerLabel</div>), | ||||
| })); | ||||
| jest.mock('../useGlossary.store', () => ({ | ||||
|   useGlossaryStore: jest.fn().mockImplementation(() => ({ | ||||
|     activeGlossary: mockedGlossaryTerms[0], | ||||
|     updateActiveGlossary: jest.fn(), | ||||
|   })), | ||||
| })); | ||||
| 
 | ||||
| describe('Test GlossaryTermTab component', () => { | ||||
|   it('should show the ErrorPlaceHolder component, if no glossary is present', () => { | ||||
|     const { container } = render(<GlossaryTermTab {...mockProps1} />, { | ||||
|     const { container } = render(<GlossaryTermTab {...mockProps} />, { | ||||
|       wrapper: MemoryRouter, | ||||
|     }); | ||||
| 
 | ||||
| @ -86,7 +88,7 @@ describe('Test GlossaryTermTab component', () => { | ||||
|   }); | ||||
| 
 | ||||
|   it('should call the onAddGlossaryTerm fn onClick of add button in ErrorPlaceHolder', () => { | ||||
|     const { container } = render(<GlossaryTermTab {...mockProps1} />, { | ||||
|     const { container } = render(<GlossaryTermTab {...mockProps} />, { | ||||
|       wrapper: MemoryRouter, | ||||
|     }); | ||||
| 
 | ||||
| @ -96,7 +98,14 @@ describe('Test GlossaryTermTab component', () => { | ||||
|   }); | ||||
| 
 | ||||
|   it('should contain all necessary fields value in table when glossary data is not empty', async () => { | ||||
|     const { container } = render(<GlossaryTermTab {...mockProps2} />, { | ||||
|     (useGlossaryStore as unknown as jest.Mock).mockImplementation(() => ({ | ||||
|       activeGlossary: { | ||||
|         ...mockedGlossaryTerms[0], | ||||
|         children: mockedGlossaryTerms, | ||||
|       }, | ||||
|       updateActiveGlossary: jest.fn(), | ||||
|     })); | ||||
|     const { container } = render(<GlossaryTermTab {...mockProps} />, { | ||||
|       wrapper: MemoryRouter, | ||||
|     }); | ||||
| 
 | ||||
|  | ||||
| @ -51,6 +51,7 @@ import { AssetSelectionModal } from '../../DataAssets/AssetsSelectionModal/Asset | ||||
| import { GlossaryTabs } from '../GlossaryDetails/GlossaryDetails.interface'; | ||||
| import GlossaryHeader from '../GlossaryHeader/GlossaryHeader.component'; | ||||
| import GlossaryTermTab from '../GlossaryTermTab/GlossaryTermTab.component'; | ||||
| import { useGlossaryStore } from '../useGlossary.store'; | ||||
| import { GlossaryTermsV1Props } from './GlossaryTermsV1.interface'; | ||||
| import AssetsTabs, { AssetsTabRef } from './tabs/AssetsTabs.component'; | ||||
| import { AssetsOfEntity } from './tabs/AssetsTabs.interface'; | ||||
| @ -58,7 +59,6 @@ import GlossaryOverviewTab from './tabs/GlossaryOverviewTab.component'; | ||||
| 
 | ||||
| const GlossaryTermsV1 = ({ | ||||
|   glossaryTerm, | ||||
|   childGlossaryTerms, | ||||
|   handleGlossaryTermUpdate, | ||||
|   handleGlossaryTermDelete, | ||||
|   permissions, | ||||
| @ -82,6 +82,8 @@ const GlossaryTermsV1 = ({ | ||||
|     FEED_COUNT_INITIAL_DATA | ||||
|   ); | ||||
|   const [assetCount, setAssetCount] = useState<number>(0); | ||||
|   const { activeGlossary } = useGlossaryStore(); | ||||
|   const childGlossaryTerms = activeGlossary?.children ?? []; | ||||
| 
 | ||||
|   const assetPermissions = useMemo(() => { | ||||
|     const glossaryTermStatus = glossaryTerm.status ?? Status.Approved; | ||||
| @ -198,12 +200,10 @@ const GlossaryTermsV1 = ({ | ||||
|               key: 'terms', | ||||
|               children: ( | ||||
|                 <GlossaryTermTab | ||||
|                   childGlossaryTerms={childGlossaryTerms} | ||||
|                   className="p-md glossary-term-table-container" | ||||
|                   isGlossary={false} | ||||
|                   permissions={permissions} | ||||
|                   refreshGlossaryTerms={refreshGlossaryTerms} | ||||
|                   selectedData={glossaryTerm} | ||||
|                   termsLoading={termsLoading} | ||||
|                   onAddGlossaryTerm={onAddGlossaryTerm} | ||||
|                   onEditGlossaryTerm={onEditGlossaryTerm} | ||||
|  | ||||
| @ -19,7 +19,6 @@ export interface GlossaryTermsV1Props { | ||||
|   isVersionView?: boolean; | ||||
|   permissions: OperationPermission; | ||||
|   glossaryTerm: GlossaryTerm; | ||||
|   childGlossaryTerms: GlossaryTerm[]; | ||||
|   handleGlossaryTermUpdate: (data: GlossaryTerm) => Promise<void>; | ||||
|   handleGlossaryTermDelete: (id: string) => Promise<void>; | ||||
|   refreshGlossaryTerms: () => void; | ||||
|  | ||||
| @ -88,7 +88,7 @@ const mockProps = { | ||||
| 
 | ||||
| describe('Test Glossary-term component', () => { | ||||
|   it('Should render Glossary-term component', async () => { | ||||
|     render(<GlossaryTerms {...mockProps} childGlossaryTerms={[]} />); | ||||
|     render(<GlossaryTerms {...mockProps} />); | ||||
| 
 | ||||
|     const glossaryTerm = screen.getByTestId('glossary-term'); | ||||
|     const tabs = await screen.findAllByRole('tab'); | ||||
|  | ||||
| @ -19,10 +19,7 @@ import { useTranslation } from 'react-i18next'; | ||||
| import { useHistory, useParams } from 'react-router-dom'; | ||||
| import { withActivityFeed } from '../../components/AppRouter/withActivityFeed'; | ||||
| import { HTTP_STATUS_CODE } from '../../constants/Auth.constants'; | ||||
| import { | ||||
|   API_RES_MAX_SIZE, | ||||
|   getGlossaryTermDetailsPath, | ||||
| } from '../../constants/constants'; | ||||
| import { getGlossaryTermDetailsPath } from '../../constants/constants'; | ||||
| import { usePermissionProvider } from '../../context/PermissionProvider/PermissionProvider'; | ||||
| import { | ||||
|   OperationPermission, | ||||
| @ -33,13 +30,12 @@ import { | ||||
|   CreateThread, | ||||
|   ThreadType, | ||||
| } from '../../generated/api/feed/createThread'; | ||||
| import { Glossary } from '../../generated/entity/data/glossary'; | ||||
| import { GlossaryTerm } from '../../generated/entity/data/glossaryTerm'; | ||||
| import { VERSION_VIEW_GLOSSARY_PERMISSION } from '../../mocks/Glossary.mock'; | ||||
| import { postThread } from '../../rest/feedsAPI'; | ||||
| import { | ||||
|   addGlossaryTerm, | ||||
|   getGlossaryTerms, | ||||
|   getFirstLevelGlossaryTerms, | ||||
|   ListGlossaryTermsParams, | ||||
|   patchGlossaryTerm, | ||||
| } from '../../rest/glossaryAPI'; | ||||
| @ -57,6 +53,7 @@ import GlossaryTermsV1 from './GlossaryTerms/GlossaryTermsV1.component'; | ||||
| import { GlossaryV1Props } from './GlossaryV1.interfaces'; | ||||
| import './glossaryV1.less'; | ||||
| import ImportGlossary from './ImportGlossary/ImportGlossary'; | ||||
| import { useGlossaryStore } from './useGlossary.store'; | ||||
| 
 | ||||
| const GlossaryV1 = ({ | ||||
|   isGlossaryActive, | ||||
| @ -74,13 +71,15 @@ const GlossaryV1 = ({ | ||||
|   const { t } = useTranslation(); | ||||
|   const { action, tab } = | ||||
|     useParams<{ action: EntityAction; glossaryName: string; tab: string }>(); | ||||
| 
 | ||||
|   const history = useHistory(); | ||||
|   const [threadLink, setThreadLink] = useState<string>(''); | ||||
|   const [threadType, setThreadType] = useState<ThreadType>( | ||||
|     ThreadType.Conversation | ||||
|   ); | ||||
|   const { postFeed, deleteFeed, updateFeed } = useActivityFeedProvider(); | ||||
| 
 | ||||
|   const [activeGlossaryTerm, setActiveGlossaryTerm] = | ||||
|     useState<GlossaryTerm | null>(null); | ||||
|   const { getEntityPermission } = usePermissionProvider(); | ||||
|   const [isLoading, setIsLoading] = useState(true); | ||||
|   const [isTermsLoading, setIsTermsLoading] = useState(false); | ||||
| @ -94,13 +93,12 @@ const GlossaryV1 = ({ | ||||
|     useState<OperationPermission>(DEFAULT_ENTITY_PERMISSION); | ||||
| 
 | ||||
|   const [isEditModalOpen, setIsEditModalOpen] = useState(false); | ||||
|   const [activeGlossaryTerm, setActiveGlossaryTerm] = useState< | ||||
|     GlossaryTerm | undefined | ||||
|   >(); | ||||
| 
 | ||||
|   const [editMode, setEditMode] = useState(false); | ||||
| 
 | ||||
|   const [glossaryTerms, setGlossaryTerms] = useState<GlossaryTerm[]>([]); | ||||
|   const { id } = selectedData ?? {}; | ||||
|   const { activeGlossary, updateActiveGlossary } = useGlossaryStore(); | ||||
| 
 | ||||
|   const { id, fullyQualifiedName } = activeGlossary ?? {}; | ||||
| 
 | ||||
|   const isImportAction = useMemo( | ||||
|     () => action === EntityAction.IMPORT, | ||||
| @ -124,12 +122,14 @@ const GlossaryV1 = ({ | ||||
|   ) => { | ||||
|     refresh ? setIsTermsLoading(true) : setIsLoading(true); | ||||
|     try { | ||||
|       const { data } = await getGlossaryTerms({ | ||||
|         ...params, | ||||
|         limit: API_RES_MAX_SIZE, | ||||
|         fields: 'children,owner,parent', | ||||
|       const { data } = await getFirstLevelGlossaryTerms( | ||||
|         params?.glossary ?? params?.parent ?? '' | ||||
|       ); | ||||
|       updateActiveGlossary({ | ||||
|         children: data.map((data) => | ||||
|           data.childrenCount ?? 0 > 0 ? { ...data, children: [] } : data | ||||
|         ), | ||||
|       }); | ||||
|       setGlossaryTerms(data); | ||||
|     } catch (error) { | ||||
|       showErrorToast(error as AxiosError); | ||||
|     } finally { | ||||
| @ -187,16 +187,18 @@ const GlossaryV1 = ({ | ||||
|   const loadGlossaryTerms = useCallback( | ||||
|     (refresh = false) => { | ||||
|       fetchGlossaryTerm( | ||||
|         isGlossaryActive ? { glossary: id } : { parent: id }, | ||||
|         isGlossaryActive | ||||
|           ? { glossary: fullyQualifiedName } | ||||
|           : { parent: fullyQualifiedName }, | ||||
|         refresh | ||||
|       ); | ||||
|     }, | ||||
|     [id, isGlossaryActive] | ||||
|     [fullyQualifiedName, isGlossaryActive] | ||||
|   ); | ||||
| 
 | ||||
|   const handleGlossaryTermModalAction = ( | ||||
|     editMode: boolean, | ||||
|     glossaryTerm: GlossaryTerm | undefined | ||||
|     glossaryTerm: GlossaryTerm | null | ||||
|   ) => { | ||||
|     setEditMode(editMode); | ||||
|     setActiveGlossaryTerm(glossaryTerm); | ||||
| @ -349,8 +351,6 @@ const GlossaryV1 = ({ | ||||
|         !isEmpty(selectedData) && | ||||
|         (isGlossaryActive ? ( | ||||
|           <GlossaryDetails | ||||
|             glossary={selectedData as Glossary} | ||||
|             glossaryTerms={glossaryTerms} | ||||
|             handleGlossaryDelete={onGlossaryDelete} | ||||
|             isVersionView={isVersionsView} | ||||
|             permissions={glossaryPermission} | ||||
| @ -359,16 +359,15 @@ const GlossaryV1 = ({ | ||||
|             updateGlossary={updateGlossary} | ||||
|             updateVote={updateVote} | ||||
|             onAddGlossaryTerm={(term) => | ||||
|               handleGlossaryTermModalAction(false, term) | ||||
|               handleGlossaryTermModalAction(false, term ?? null) | ||||
|             } | ||||
|             onEditGlossaryTerm={(term) => | ||||
|               handleGlossaryTermModalAction(true, term) | ||||
|               handleGlossaryTermModalAction(true, term ?? null) | ||||
|             } | ||||
|             onThreadLinkSelect={onThreadLinkSelect} | ||||
|           /> | ||||
|         ) : ( | ||||
|           <GlossaryTermsV1 | ||||
|             childGlossaryTerms={glossaryTerms} | ||||
|             glossaryTerm={selectedData as GlossaryTerm} | ||||
|             handleGlossaryTermDelete={onGlossaryTermDelete} | ||||
|             handleGlossaryTermUpdate={onGlossaryTermUpdate} | ||||
| @ -380,7 +379,7 @@ const GlossaryV1 = ({ | ||||
|             termsLoading={isTermsLoading} | ||||
|             updateVote={updateVote} | ||||
|             onAddGlossaryTerm={(term) => | ||||
|               handleGlossaryTermModalAction(false, term) | ||||
|               handleGlossaryTermModalAction(false, term ?? null) | ||||
|             } | ||||
|             onAssetClick={onAssetClick} | ||||
|             onEditGlossaryTerm={(term) => | ||||
|  | ||||
| @ -124,6 +124,12 @@ jest.mock('./ImportGlossary/ImportGlossary', () => | ||||
| jest.mock('../../components/AppRouter/withActivityFeed', () => ({ | ||||
|   withActivityFeed: jest.fn().mockImplementation((component) => component), | ||||
| })); | ||||
| jest.mock('./useGlossary.store', () => ({ | ||||
|   useGlossaryStore: jest.fn().mockImplementation(() => ({ | ||||
|     activeGlossary: mockedGlossaryTerms[0], | ||||
|     updateActiveGlossary: jest.fn(), | ||||
|   })), | ||||
| })); | ||||
| 
 | ||||
| const mockProps: GlossaryV1Props = { | ||||
|   selectedData: mockedGlossaries[0], | ||||
|  | ||||
| @ -0,0 +1,52 @@ | ||||
| /* | ||||
|  *  Copyright 2024 Collate. | ||||
|  *  Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  *  you may not use this file except in compliance with the License. | ||||
|  *  You may obtain a copy of the License at | ||||
|  *  http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  *  Unless required by applicable law or agreed to in writing, software | ||||
|  *  distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  *  See the License for the specific language governing permissions and | ||||
|  *  limitations under the License. | ||||
|  */ | ||||
| import { create } from 'zustand'; | ||||
| import { Glossary } from '../../generated/entity/data/glossary'; | ||||
| import { GlossaryTermWithChildren } from '../../rest/glossaryAPI'; | ||||
| 
 | ||||
| export type ModifiedGlossary = Glossary & { | ||||
|   children?: GlossaryTermWithChildren[]; | ||||
| }; | ||||
| 
 | ||||
| export const useGlossaryStore = create<{ | ||||
|   glossaries: Glossary[]; | ||||
|   activeGlossary: ModifiedGlossary; | ||||
|   setGlossaries: (glossaries: Glossary[]) => void; | ||||
|   setActiveGlossary: (glossary: ModifiedGlossary) => void; | ||||
|   updateGlossary: (glossary: Glossary) => void; | ||||
|   updateActiveGlossary: (glossary: Partial<ModifiedGlossary>) => void; | ||||
| }>()((set, get) => ({ | ||||
|   glossaries: [], | ||||
|   activeGlossary: {} as ModifiedGlossary, | ||||
| 
 | ||||
|   setGlossaries: (glossaries: Glossary[]) => { | ||||
|     set({ glossaries }); | ||||
|   }, | ||||
|   updateGlossary: (glossary: Glossary) => { | ||||
|     const { glossaries } = get(); | ||||
| 
 | ||||
|     const newGlossaries = glossaries.map((g) => | ||||
|       g.fullyQualifiedName === glossary.fullyQualifiedName ? glossary : g | ||||
|     ); | ||||
| 
 | ||||
|     set({ glossaries: newGlossaries }); | ||||
|   }, | ||||
|   setActiveGlossary: (glossary: ModifiedGlossary) => { | ||||
|     set({ activeGlossary: glossary }); | ||||
|   }, | ||||
|   updateActiveGlossary: (glossary: Partial<ModifiedGlossary>) => { | ||||
|     const { activeGlossary } = get(); | ||||
| 
 | ||||
|     set({ activeGlossary: { ...activeGlossary, ...glossary } as Glossary }); | ||||
|   }, | ||||
| })); | ||||
| @ -189,7 +189,7 @@ const TreeAsyncSelectList: FC<Omit<AsyncSelectListProps, 'fetchOptions'>> = ({ | ||||
|             label: value, | ||||
|           }; | ||||
|     }); | ||||
|     selectedTagsRef.current = selectedValues; | ||||
|     selectedTagsRef.current = selectedValues as SelectOption[]; | ||||
|     onChange?.(selectedValues); | ||||
|   }; | ||||
| 
 | ||||
|  | ||||
| @ -23,6 +23,10 @@ import { VotingDataProps } from '../../../components/Entity/Voting/voting.interf | ||||
| import EntitySummaryPanel from '../../../components/Explore/EntitySummaryPanel/EntitySummaryPanel.component'; | ||||
| import { EntityDetailsObjectInterface } from '../../../components/Explore/ExplorePage.interface'; | ||||
| import GlossaryV1 from '../../../components/Glossary/GlossaryV1.component'; | ||||
| import { | ||||
|   ModifiedGlossary, | ||||
|   useGlossaryStore, | ||||
| } from '../../../components/Glossary/useGlossary.store'; | ||||
| import PageLayoutV1 from '../../../components/PageLayoutV1/PageLayoutV1'; | ||||
| import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants'; | ||||
| import { PAGE_SIZE_LARGE, ROUTES } from '../../../constants/constants'; | ||||
| @ -55,16 +59,24 @@ const GlossaryPage = () => { | ||||
|   const { permissions } = usePermissionProvider(); | ||||
|   const { fqn: glossaryFqn } = useFqn(); | ||||
|   const history = useHistory(); | ||||
|   const [glossaries, setGlossaries] = useState<Glossary[]>([]); | ||||
| 
 | ||||
|   const [isLoading, setIsLoading] = useState(true); | ||||
|   const [selectedData, setSelectedData] = useState<Glossary | GlossaryTerm>(); | ||||
| 
 | ||||
|   const [isRightPanelLoading, setIsRightPanelLoading] = useState(true); | ||||
|   const [previewAsset, setPreviewAsset] = | ||||
|     useState<EntityDetailsObjectInterface>(); | ||||
| 
 | ||||
|   const { | ||||
|     glossaries, | ||||
|     setGlossaries, | ||||
|     activeGlossary, | ||||
|     setActiveGlossary, | ||||
|     updateActiveGlossary, | ||||
|   } = useGlossaryStore(); | ||||
| 
 | ||||
|   const isGlossaryActive = useMemo(() => { | ||||
|     setIsRightPanelLoading(true); | ||||
|     setSelectedData(undefined); | ||||
|     setActiveGlossary({} as ModifiedGlossary); | ||||
| 
 | ||||
|     if (glossaryFqn) { | ||||
|       return Fqn.split(glossaryFqn).length === 1; | ||||
| @ -140,7 +152,7 @@ const GlossaryPage = () => { | ||||
|         fields: | ||||
|           'relatedTerms,reviewers,tags,owner,children,votes,domain,extension', | ||||
|       }); | ||||
|       setSelectedData(response); | ||||
|       setActiveGlossary(response as ModifiedGlossary); | ||||
|     } catch (error) { | ||||
|       showErrorToast(error as AxiosError); | ||||
|     } finally { | ||||
| @ -153,7 +165,7 @@ const GlossaryPage = () => { | ||||
|       if (!isGlossaryActive) { | ||||
|         fetchGlossaryTermDetails(); | ||||
|       } else { | ||||
|         setSelectedData( | ||||
|         setActiveGlossary( | ||||
|           glossaries.find( | ||||
|             (glossary) => glossary.fullyQualifiedName === glossaryFqn | ||||
|           ) || glossaries[0] | ||||
| @ -167,25 +179,17 @@ const GlossaryPage = () => { | ||||
|   }, [isGlossaryActive, glossaryFqn, glossaries]); | ||||
| 
 | ||||
|   const updateGlossary = async (updatedData: Glossary) => { | ||||
|     const jsonPatch = compare(selectedData as Glossary, updatedData); | ||||
|     const jsonPatch = compare(activeGlossary as Glossary, updatedData); | ||||
| 
 | ||||
|     try { | ||||
|       const response = await patchGlossaries( | ||||
|         selectedData?.id as string, | ||||
|         activeGlossary?.id as string, | ||||
|         jsonPatch | ||||
|       ); | ||||
| 
 | ||||
|       setGlossaries((pre) => { | ||||
|         return pre.map((item) => { | ||||
|           if (item.name === response.name) { | ||||
|             return response; | ||||
|           } else { | ||||
|             return item; | ||||
|           } | ||||
|         }); | ||||
|       }); | ||||
|       updateActiveGlossary(response); | ||||
| 
 | ||||
|       if (selectedData?.name !== updatedData.name) { | ||||
|       if (activeGlossary?.name !== updatedData.name) { | ||||
|         history.push(getGlossaryPath(response.fullyQualifiedName)); | ||||
|         fetchGlossaryList(); | ||||
|       } | ||||
| @ -198,36 +202,24 @@ const GlossaryPage = () => { | ||||
|     async (data: VotingDataProps) => { | ||||
|       try { | ||||
|         const isGlossaryEntity = | ||||
|           Fqn.split(selectedData?.fullyQualifiedName ?? '').length <= 1; | ||||
|           Fqn.split(activeGlossary?.fullyQualifiedName ?? '').length <= 1; | ||||
| 
 | ||||
|         if (isGlossaryEntity) { | ||||
|           const { | ||||
|             entity: { votes }, | ||||
|           } = await updateGlossaryVotes(selectedData?.id ?? '', data); | ||||
|           setSelectedData( | ||||
|             (pre) => | ||||
|               pre && { | ||||
|                 ...pre, | ||||
|                 votes, | ||||
|               } | ||||
|           ); | ||||
|           } = await updateGlossaryVotes(activeGlossary?.id ?? '', data); | ||||
|           updateActiveGlossary({ votes }); | ||||
|         } else { | ||||
|           const { | ||||
|             entity: { votes }, | ||||
|           } = await updateGlossaryTermVotes(selectedData?.id ?? '', data); | ||||
|           setSelectedData( | ||||
|             (pre) => | ||||
|               pre && { | ||||
|                 ...pre, | ||||
|                 votes, | ||||
|               } | ||||
|           ); | ||||
|           } = await updateGlossaryTermVotes(activeGlossary?.id ?? '', data); | ||||
|           updateActiveGlossary({ votes }); | ||||
|         } | ||||
|       } catch (error) { | ||||
|         showErrorToast(error as AxiosError); | ||||
|       } | ||||
|     }, | ||||
|     [setSelectedData, selectedData] | ||||
|     [updateActiveGlossary, activeGlossary] | ||||
|   ); | ||||
| 
 | ||||
|   const handleGlossaryDelete = async (id: string) => { | ||||
| @ -260,18 +252,18 @@ const GlossaryPage = () => { | ||||
| 
 | ||||
|   const handleGlossaryTermUpdate = useCallback( | ||||
|     async (updatedData: GlossaryTerm) => { | ||||
|       const jsonPatch = compare(selectedData as GlossaryTerm, updatedData); | ||||
|       const jsonPatch = compare(activeGlossary as GlossaryTerm, updatedData); | ||||
|       if (isEmpty(jsonPatch)) { | ||||
|         return; | ||||
|       } | ||||
|       try { | ||||
|         const response = await patchGlossaryTerm( | ||||
|           selectedData?.id as string, | ||||
|           activeGlossary?.id as string, | ||||
|           jsonPatch | ||||
|         ); | ||||
|         if (response) { | ||||
|           setSelectedData(response); | ||||
|           if (selectedData?.name !== updatedData.name) { | ||||
|           setActiveGlossary(response as ModifiedGlossary); | ||||
|           if (activeGlossary?.name !== updatedData.name) { | ||||
|             history.push(getGlossaryPath(response.fullyQualifiedName)); | ||||
|             fetchGlossaryList(); | ||||
|           } | ||||
| @ -284,7 +276,7 @@ const GlossaryPage = () => { | ||||
|         showErrorToast(error as AxiosError); | ||||
|       } | ||||
|     }, | ||||
|     [selectedData] | ||||
|     [activeGlossary] | ||||
|   ); | ||||
| 
 | ||||
|   const handleGlossaryTermDelete = async (id: string) => { | ||||
| @ -373,7 +365,7 @@ const GlossaryPage = () => { | ||||
|           isSummaryPanelOpen={Boolean(previewAsset)} | ||||
|           isVersionsView={false} | ||||
|           refreshActiveGlossaryTerm={fetchGlossaryTermDetails} | ||||
|           selectedData={selectedData as Glossary} | ||||
|           selectedData={activeGlossary as Glossary} | ||||
|           updateGlossary={updateGlossary} | ||||
|           updateVote={updateVote} | ||||
|           onAssetClick={handleAssetClick} | ||||
|  | ||||
| @ -289,3 +289,23 @@ export const searchGlossaryTerms = async (search: string, page = 1) => { | ||||
| 
 | ||||
|   return data; | ||||
| }; | ||||
| 
 | ||||
| export type GlossaryTermWithChildren = Omit<GlossaryTerm, 'children'> & { | ||||
|   children?: GlossaryTerm[]; | ||||
| }; | ||||
| 
 | ||||
| export const getFirstLevelGlossaryTerms = async (parentFQN: string) => { | ||||
|   const apiUrl = `/glossaryTerms`; | ||||
| 
 | ||||
|   const { data } = await APIClient.get< | ||||
|     PagingResponse<GlossaryTermWithChildren[]> | ||||
|   >(apiUrl, { | ||||
|     params: { | ||||
|       directChildrenOf: parentFQN, | ||||
|       fields: 'childrenCount', | ||||
|       limit: 100000, | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   return data; | ||||
| }; | ||||
|  | ||||
| @ -10,6 +10,7 @@ | ||||
|  *  See the License for the specific language governing permissions and | ||||
|  *  limitations under the License. | ||||
|  */ | ||||
| import { ModifiedGlossaryTerm } from '../components/Glossary/GlossaryTermTab/GlossaryTermTab.interface'; | ||||
| import { EntityType } from '../enums/entity.enum'; | ||||
| import { | ||||
|   MOCKED_GLOSSARY_TERMS, | ||||
| @ -17,7 +18,12 @@ import { | ||||
|   MOCKED_GLOSSARY_TERMS_TREE, | ||||
|   MOCKED_GLOSSARY_TERMS_TREE_1, | ||||
| } from '../mocks/Glossary.mock'; | ||||
| import { buildTree, getQueryFilterToExcludeTerm } from './GlossaryUtils'; | ||||
| import { | ||||
|   buildTree, | ||||
|   findExpandableKeys, | ||||
|   findExpandableKeysForArray, | ||||
|   getQueryFilterToExcludeTerm, | ||||
| } from './GlossaryUtils'; | ||||
| 
 | ||||
| describe('Glossary Utils', () => { | ||||
|   it('getQueryFilterToExcludeTerm returns the correct query filter', () => { | ||||
| @ -79,4 +85,78 @@ describe('Glossary Utils', () => { | ||||
|       MOCKED_GLOSSARY_TERMS_TREE_1 | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   it('should return an empty array if no glossary term is provided', () => { | ||||
|     const expandableKeys = findExpandableKeys(); | ||||
| 
 | ||||
|     expect(expandableKeys).toEqual([]); | ||||
|   }); | ||||
| 
 | ||||
|   it('should return an array of expandable keys when glossary term has children', () => { | ||||
|     const glossaryTerm = { | ||||
|       fullyQualifiedName: 'example', | ||||
|       children: [ | ||||
|         { | ||||
|           fullyQualifiedName: 'child1', | ||||
|           children: [ | ||||
|             { | ||||
|               fullyQualifiedName: 'grandchild1', | ||||
|             }, | ||||
|             { | ||||
|               childrenCount: 2, | ||||
|               fullyQualifiedName: 'grandchild2', | ||||
|             }, | ||||
|           ], | ||||
|         }, | ||||
|         { | ||||
|           fullyQualifiedName: 'child2', | ||||
|         }, | ||||
|       ], | ||||
|     }; | ||||
| 
 | ||||
|     const expandableKeys = findExpandableKeys( | ||||
|       glossaryTerm as ModifiedGlossaryTerm | ||||
|     ); | ||||
| 
 | ||||
|     expect(expandableKeys).toEqual(['grandchild2', 'child1', 'example']); | ||||
|   }); | ||||
| 
 | ||||
|   it('should return an array of expandable keys when glossary term has childrenCount', () => { | ||||
|     const glossaryTerm = { | ||||
|       fullyQualifiedName: 'example', | ||||
|       childrenCount: 2, | ||||
|     }; | ||||
| 
 | ||||
|     const expandableKeys = findExpandableKeys( | ||||
|       glossaryTerm as ModifiedGlossaryTerm | ||||
|     ); | ||||
| 
 | ||||
|     expect(expandableKeys).toEqual(['example']); | ||||
|   }); | ||||
| 
 | ||||
|   it('should find expandable keys for an array of glossary terms', () => { | ||||
|     const glossaryTerms = [ | ||||
|       { | ||||
|         fullyQualifiedName: 'example1', | ||||
|         children: [ | ||||
|           { | ||||
|             fullyQualifiedName: 'child1', | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|       { | ||||
|         fullyQualifiedName: 'example2', | ||||
|         childrenCount: 2, | ||||
|       }, | ||||
|       { | ||||
|         fullyQualifiedName: 'example3', | ||||
|       }, | ||||
|     ]; | ||||
| 
 | ||||
|     const expandableKeys = findExpandableKeysForArray( | ||||
|       glossaryTerms as ModifiedGlossaryTerm[] | ||||
|     ); | ||||
| 
 | ||||
|     expect(expandableKeys).toEqual(['example1', 'example2']); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| @ -15,6 +15,7 @@ import { DefaultOptionType } from 'antd/lib/select'; | ||||
| import { isEmpty } from 'lodash'; | ||||
| import { StatusType } from '../components/common/StatusBadge/StatusBadge.interface'; | ||||
| import { ModifiedGlossaryTerm } from '../components/Glossary/GlossaryTermTab/GlossaryTermTab.interface'; | ||||
| import { ModifiedGlossary } from '../components/Glossary/useGlossary.store'; | ||||
| import { FQN_SEPARATOR_CHAR } from '../constants/char.constants'; | ||||
| import { EntityType } from '../enums/entity.enum'; | ||||
| import { Glossary } from '../generated/entity/data/glossary'; | ||||
| @ -148,7 +149,7 @@ export const getGlossaryBreadcrumbs = (fqn: string) => { | ||||
| export const findGlossaryTermByFqn = ( | ||||
|   list: ModifiedGlossaryTerm[], | ||||
|   fullyQualifiedName: string | ||||
| ): GlossaryTerm | Glossary | null => { | ||||
| ): GlossaryTerm | Glossary | ModifiedGlossary | null => { | ||||
|   for (const item of list) { | ||||
|     if (item.fullyQualifiedName === fullyQualifiedName) { | ||||
|       return item; | ||||
| @ -197,3 +198,53 @@ export const convertGlossaryTermsToTreeOptions = ( | ||||
| 
 | ||||
|   return treeData; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Finds the expandable keys in a glossary term. | ||||
|  * @param glossaryTerm - The glossary term to search for expandable keys. | ||||
|  * @returns An array of expandable keys found in the glossary term. | ||||
|  */ | ||||
| export const findExpandableKeys = ( | ||||
|   glossaryTerm?: ModifiedGlossaryTerm | ||||
| ): string[] => { | ||||
|   let expandableKeys: string[] = []; | ||||
| 
 | ||||
|   if (!glossaryTerm) { | ||||
|     return expandableKeys; | ||||
|   } | ||||
| 
 | ||||
|   if (glossaryTerm.children) { | ||||
|     glossaryTerm.children.forEach((child) => { | ||||
|       expandableKeys = expandableKeys.concat( | ||||
|         findExpandableKeys(child as ModifiedGlossaryTerm) | ||||
|       ); | ||||
|     }); | ||||
|     if (glossaryTerm.fullyQualifiedName) { | ||||
|       expandableKeys.push(glossaryTerm.fullyQualifiedName); | ||||
|     } | ||||
|   } else if (glossaryTerm.childrenCount) { | ||||
|     if (glossaryTerm.fullyQualifiedName) { | ||||
|       expandableKeys.push(glossaryTerm.fullyQualifiedName); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return expandableKeys; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Finds the expandable keys for an array of glossary terms. | ||||
|  * | ||||
|  * @param glossaryTerms - An array of ModifiedGlossaryTerm objects. | ||||
|  * @returns An array of expandable keys. | ||||
|  */ | ||||
| export const findExpandableKeysForArray = ( | ||||
|   glossaryTerms: ModifiedGlossaryTerm[] | ||||
| ): string[] => { | ||||
|   let expandableKeys: string[] = []; | ||||
| 
 | ||||
|   glossaryTerms.forEach((glossaryTerm) => { | ||||
|     expandableKeys = expandableKeys.concat(findExpandableKeys(glossaryTerm)); | ||||
|   }); | ||||
| 
 | ||||
|   return expandableKeys; | ||||
| }; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Chirag Madlani
						Chirag Madlani