mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-31 20:51:26 +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