Revamp Glossary UI to support URL updation (#3453)

This commit is contained in:
darth-coder00 2022-03-17 01:21:05 +05:30 committed by GitHub
parent 22b394d63f
commit cbe667b7cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 588 additions and 208 deletions

View File

@ -19,6 +19,9 @@ node_modules/
build/ build/
dist/ dist/
# mockups
mock-api/
# macOS # macOS
.DS_Store .DS_Store

View File

@ -19,6 +19,9 @@ node_modules/
build/ build/
dist/ dist/
# mockups
mock-api/
# Ignore files (Prettier has trouble parsing files without extension) # Ignore files (Prettier has trouble parsing files without extension)
.gitignore .gitignore
.prettierignore .prettierignore

View File

@ -21,12 +21,13 @@ import {
getGlossariesByName, getGlossariesByName,
getGlossaryTermByFQN, getGlossaryTermByFQN,
} from '../../axiosAPIs/glossaryAPI'; } from '../../axiosAPIs/glossaryAPI';
import { ROUTES } from '../../constants/constants'; import { getGlossaryPath } from '../../constants/constants';
import { CreateGlossaryTerm } from '../../generated/api/data/createGlossaryTerm'; import { CreateGlossaryTerm } from '../../generated/api/data/createGlossaryTerm';
import { Glossary } from '../../generated/entity/data/glossary'; import { Glossary } from '../../generated/entity/data/glossary';
import { GlossaryTerm } from '../../generated/entity/data/glossaryTerm'; import { GlossaryTerm } from '../../generated/entity/data/glossaryTerm';
import { useAuth } from '../../hooks/authHooks'; import { useAuth } from '../../hooks/authHooks';
import useToastContext from '../../hooks/useToastContext'; import useToastContext from '../../hooks/useToastContext';
import jsonData from '../../jsons/en';
import AddGlossaryTerm from '../AddGlossaryTerm/AddGlossaryTerm.component'; import AddGlossaryTerm from '../AddGlossaryTerm/AddGlossaryTerm.component';
import PageContainerV1 from '../containers/PageContainerV1'; import PageContainerV1 from '../containers/PageContainerV1';
import Loader from '../Loader/Loader'; import Loader from '../Loader/Loader';
@ -44,43 +45,65 @@ const AddGlossaryTermPage = () => {
const [parentGlossaryData, setParentGlossaryData] = useState<GlossaryTerm>(); const [parentGlossaryData, setParentGlossaryData] = useState<GlossaryTerm>();
const goToGlossary = () => { const goToGlossary = (name = '') => {
history.push(ROUTES.GLOSSARY); history.push(getGlossaryPath(name));
}; };
const handleCancel = () => { const handleCancel = () => {
goToGlossary(); goToGlossary();
}; };
const handleShowErrorToast = (errMessage: string) => {
showToast({
variant: 'error',
body: errMessage,
});
};
const handleSaveFailure = (errorMessage = '') => {
handleShowErrorToast(
errorMessage || jsonData['api-error-messages']['add-glossary-term-error']
);
setStatus('initial');
};
const onSave = (data: CreateGlossaryTerm) => { const onSave = (data: CreateGlossaryTerm) => {
setStatus('waiting'); setStatus('waiting');
addGlossaryTerm(data) addGlossaryTerm(data)
.then(() => { .then((res) => {
setStatus('success'); if (res.data) {
setTimeout(() => { setStatus('success');
setStatus('initial'); setTimeout(() => {
goToGlossary(); setStatus('initial');
}, 500); goToGlossary(res?.data?.fullyQualifiedName);
}, 500);
} else {
handleSaveFailure();
}
}) })
.catch((err: AxiosError) => { .catch((err: AxiosError) => {
showToast({ handleSaveFailure(err.response?.data?.message);
variant: 'error',
body: err.message || 'Something went wrong!',
});
setStatus('initial');
}); });
}; };
const fetchGlossaryData = () => { const fetchGlossaryData = () => {
getGlossariesByName(glossaryName, ['tags', 'owner', 'reviewers']) getGlossariesByName(glossaryName, ['tags', 'owner', 'reviewers'])
.then((res: AxiosResponse) => { .then((res: AxiosResponse) => {
setGlossaryData(res.data); if (res.data) {
setGlossaryData(res.data);
} else {
setGlossaryData(undefined);
handleShowErrorToast(
jsonData['api-error-messages']['fetch-glossary-error']
);
}
}) })
.catch((err: AxiosError) => { .catch((err: AxiosError) => {
showToast({ setGlossaryData(undefined);
variant: 'error', handleShowErrorToast(
body: err.message || 'Error while fetching glossary!', err.response?.data?.message ||
}); jsonData['api-error-messages']['fetch-glossary-error']
);
}) })
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}; };
@ -93,14 +116,21 @@ const AddGlossaryTermPage = () => {
'tags', 'tags',
]) ])
.then((res: AxiosResponse) => { .then((res: AxiosResponse) => {
setParentGlossaryData(res.data); if (res.data) {
setParentGlossaryData(res.data);
} else {
setParentGlossaryData(undefined);
handleShowErrorToast(
jsonData['api-error-messages']['fetch-glossary-term-error']
);
}
}) })
.catch((err: AxiosError) => { .catch((err: AxiosError) => {
setParentGlossaryData(undefined); setParentGlossaryData(undefined);
showToast({ handleShowErrorToast(
variant: 'error', err.response?.data?.message ||
body: err.message || 'Error while fetching glossary terms!', jsonData['api-error-messages']['fetch-glossary-term-error']
}); );
}); });
}; };

View File

@ -58,25 +58,13 @@ type Props = {
handleAddGlossaryTermClick: () => void; handleAddGlossaryTermClick: () => void;
updateGlossary: (value: Glossary) => void; updateGlossary: (value: Glossary) => void;
handleGlossaryTermUpdate: (value: GlossaryTerm) => void; handleGlossaryTermUpdate: (value: GlossaryTerm) => void;
handleSelectedData: ( handleSelectedData: (key: string) => void;
data: Glossary | GlossaryTerm,
pos: string,
key: string
) => void;
handleChildLoading: (status: boolean) => void; handleChildLoading: (status: boolean) => void;
handleSearchText: (text: string) => void; handleSearchText: (text: string) => void;
onGlossaryDelete: (id: string) => void; onGlossaryDelete: (id: string) => void;
onGlossaryTermDelete: (id: string) => void; onGlossaryTermDelete: (id: string) => void;
onAssetPaginate: (num: number) => void; onAssetPaginate: (num: number) => void;
isChildLoading: boolean; isChildLoading: boolean;
// handlePathChange: (
// glossary: string,
// glossaryTermsFQN?: string | undefined
// ) => void;
};
type ModifiedDataNode = DataNode & {
data: Glossary | GlossaryTerm;
}; };
const GlossaryV1 = ({ const GlossaryV1 = ({
@ -163,14 +151,8 @@ Props) => {
const key = node.key as string; const key = node.key as string;
if (selectedKey !== key) { if (selectedKey !== key) {
handleChildLoading(true); handleChildLoading(true);
const breadCrumbData = (treeRef.current?.state.keyEntities[key].nodes || handleSelectedData(key);
[]) as ModifiedDataNode[];
const selData = breadCrumbData[breadCrumbData.length - 1].data;
const pos = treeRef.current?.state.keyEntities[key].pos;
handleSelectedData(selData, pos as string, key);
} }
// handlePathChange(key.split('.')[0], key);
// handleSelectedKey(key);
}; };
useEffect(() => { useEffect(() => {

View File

@ -295,8 +295,14 @@ export const getUserPath = (username: string) => {
return path; return path;
}; };
export const getGlossaryPath = () => { export const getGlossaryPath = (fqn?: string) => {
return ROUTES.GLOSSARY; let path = ROUTES.GLOSSARY;
if (fqn) {
path = ROUTES.GLOSSARY_DETAILS;
path = path.replace(PLACEHOLDER_GLOSSARY_NAME, fqn);
}
return path;
}; };
export const getGlossaryTermsPath = ( export const getGlossaryTermsPath = (

View File

@ -0,0 +1,31 @@
/*
* Copyright 2021 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.
*/
const jsonData = {
'api-error-messages': {
'add-glossary-error': 'Error while adding glossary!',
'add-glossary-term-error': 'Error while adding glossary term!',
'delete-glossary-error': 'Error while deleting glossary!',
'delete-glossary-term-error': 'Error while deleting glossary term!',
'elastic-search-error': 'Error while fetch data from Elasticsearch!',
'fetch-data-error': 'Error while fetching data!',
'fetch-glossary-error': 'Error while fetching glossary!',
'fetch-glossary-list-error': 'Error while fetching glossaries!',
'fetch-glossary-term-error': 'Error while fetching glossary term!',
'fetch-tags-error': 'Error while fetching tags!',
'update-glossary-term-error': 'Error while updating glossary term!',
'update-description-error': 'Error while updating description!',
},
};
export default jsonData;

View File

@ -6,10 +6,11 @@ import { useAuthContext } from '../../auth-provider/AuthProvider';
import { addGlossaries } from '../../axiosAPIs/glossaryAPI'; import { addGlossaries } from '../../axiosAPIs/glossaryAPI';
import AddGlossary from '../../components/AddGlossary/AddGlossary.component'; import AddGlossary from '../../components/AddGlossary/AddGlossary.component';
import PageContainerV1 from '../../components/containers/PageContainerV1'; import PageContainerV1 from '../../components/containers/PageContainerV1';
import { ROUTES } from '../../constants/constants'; import { getGlossaryPath } from '../../constants/constants';
import { CreateGlossary } from '../../generated/api/data/createGlossary'; import { CreateGlossary } from '../../generated/api/data/createGlossary';
import { useAuth } from '../../hooks/authHooks'; import { useAuth } from '../../hooks/authHooks';
import useToastContext from '../../hooks/useToastContext'; import useToastContext from '../../hooks/useToastContext';
import jsonData from '../../jsons/en';
import { getTagCategories, getTaglist } from '../../utils/TagsUtils'; import { getTagCategories, getTaglist } from '../../utils/TagsUtils';
const AddGlossaryPage: FunctionComponent = () => { const AddGlossaryPage: FunctionComponent = () => {
@ -21,30 +22,44 @@ const AddGlossaryPage: FunctionComponent = () => {
const [isTagLoading, setIsTagLoading] = useState<boolean>(false); const [isTagLoading, setIsTagLoading] = useState<boolean>(false);
const [status, setStatus] = useState<LoadingState>('initial'); const [status, setStatus] = useState<LoadingState>('initial');
const goToGlossary = () => { const goToGlossary = (name = '') => {
history.push(ROUTES.GLOSSARY); history.push(getGlossaryPath(name));
}; };
const handleCancel = () => { const handleCancel = () => {
goToGlossary(); goToGlossary();
}; };
const handleShowErrorToast = (errMessage: string) => {
showToast({
variant: 'error',
body: errMessage,
});
};
const handleSaveFailure = (errorMessage = '') => {
handleShowErrorToast(
errorMessage || jsonData['api-error-messages']['add-glossary-error']
);
setStatus('initial');
};
const onSave = (data: CreateGlossary) => { const onSave = (data: CreateGlossary) => {
setStatus('waiting'); setStatus('waiting');
addGlossaries(data) addGlossaries(data)
.then(() => { .then((res) => {
setStatus('success'); if (res.data) {
setTimeout(() => { setStatus('success');
setStatus('initial'); setTimeout(() => {
goToGlossary(); setStatus('initial');
}, 500); goToGlossary(res.data.name);
}, 500);
} else {
handleSaveFailure();
}
}) })
.catch((err: AxiosError) => { .catch((err: AxiosError) => {
showToast({ handleSaveFailure(err.response?.data?.message);
variant: 'error',
body: err.message || 'Something went wrong!',
});
setStatus('initial');
}); });
}; };
@ -52,7 +67,19 @@ const AddGlossaryPage: FunctionComponent = () => {
setIsTagLoading(true); setIsTagLoading(true);
getTagCategories() getTagCategories()
.then((res) => { .then((res) => {
setTagList(getTaglist(res.data)); if (res.data) {
setTagList(getTaglist(res.data));
} else {
handleShowErrorToast(
jsonData['api-error-messages']['fetch-tags-error']
);
}
})
.catch((err: AxiosError) => {
handleShowErrorToast(
err.response?.data?.message ||
jsonData['api-error-messages']['fetch-tags-error']
);
}) })
.finally(() => { .finally(() => {
setIsTagLoading(false); setIsTagLoading(false);

View File

@ -1,8 +1,27 @@
import { findByText, render } from '@testing-library/react'; import { findByText, render } from '@testing-library/react';
import React from 'react'; import React from 'react';
import GlossaryPage from './GlossaryPage.component'; import GlossaryPageV1 from './GlossaryPageV1.component';
jest.mock('../../components/Glossary/Glossary.component', () => { jest.mock('react-router-dom', () => ({
useHistory: jest.fn(),
useParams: jest.fn().mockReturnValue({
glossaryName: 'GlossaryName',
}),
}));
jest.mock('../../auth-provider/AuthProvider', () => {
return {
useAuthContext: jest.fn(() => ({
isAuthDisabled: false,
isAuthenticated: true,
isProtectedRoute: jest.fn().mockReturnValue(true),
isTourRoute: jest.fn().mockReturnValue(false),
onLogoutHandler: jest.fn(),
})),
};
});
jest.mock('../../components/Glossary/GlossaryV1.component', () => {
return jest.fn().mockReturnValue(<div>Glossary.component</div>); return jest.fn().mockReturnValue(<div>Glossary.component</div>);
}); });
@ -12,7 +31,7 @@ jest.mock('../../axiosAPIs/glossaryAPI', () => ({
describe('Test GlossaryComponent page', () => { describe('Test GlossaryComponent page', () => {
it('GlossaryComponent Page Should render', async () => { it('GlossaryComponent Page Should render', async () => {
const { container } = render(<GlossaryPage />); const { container } = render(<GlossaryPageV1 />);
const glossaryComponent = await findByText( const glossaryComponent = await findByText(
container, container,

View File

@ -13,7 +13,7 @@
import { AxiosError, AxiosResponse } from 'axios'; import { AxiosError, AxiosResponse } from 'axios';
import { compare } from 'fast-json-patch'; import { compare } from 'fast-json-patch';
import { cloneDeep, extend } from 'lodash'; import { cloneDeep, extend, isEmpty } from 'lodash';
import { import {
FormattedGlossarySuggestion, FormattedGlossarySuggestion,
GlossarySuggestionHit, GlossarySuggestionHit,
@ -22,7 +22,7 @@ import {
SearchResponse, SearchResponse,
} from 'Models'; } from 'Models';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { useAuthContext } from '../../auth-provider/AuthProvider'; import { useAuthContext } from '../../auth-provider/AuthProvider';
import { import {
deleteGlossary, deleteGlossary,
@ -38,6 +38,7 @@ import GlossaryV1 from '../../components/Glossary/GlossaryV1.component';
import Loader from '../../components/Loader/Loader'; import Loader from '../../components/Loader/Loader';
import { import {
getAddGlossaryTermsPath, getAddGlossaryTermsPath,
getGlossaryPath,
PAGE_SIZE, PAGE_SIZE,
ROUTES, ROUTES,
} from '../../constants/constants'; } from '../../constants/constants';
@ -47,11 +48,14 @@ import { Glossary } from '../../generated/entity/data/glossary';
import { GlossaryTerm } from '../../generated/entity/data/glossaryTerm'; import { GlossaryTerm } from '../../generated/entity/data/glossaryTerm';
import { useAuth } from '../../hooks/authHooks'; import { useAuth } from '../../hooks/authHooks';
import useToastContext from '../../hooks/useToastContext'; import useToastContext from '../../hooks/useToastContext';
import jsonData from '../../jsons/en';
import { formatDataResponse } from '../../utils/APIUtils'; import { formatDataResponse } from '../../utils/APIUtils';
import { import {
getChildGlossaryTerms, getChildGlossaryTerms,
getGlossariesWithRootTerms, getGlossariesWithRootTerms,
getHierarchicalKeysByFQN, getHierarchicalKeysByFQN,
getTermDataFromGlossary,
getTermPosFromGlossaries,
updateGlossaryListBySearchedTerms, updateGlossaryListBySearchedTerms,
} from '../../utils/GlossaryUtils'; } from '../../utils/GlossaryUtils';
@ -60,8 +64,7 @@ export type ModifiedGlossaryData = Glossary & {
}; };
const GlossaryPageV1 = () => { const GlossaryPageV1 = () => {
// const { glossaryName, glossaryTermsFQN } = const { glossaryName } = useParams<Record<string, string>>();
// useParams<{ [key: string]: string }>();
const { isAdminUser } = useAuth(); const { isAdminUser } = useAuth();
const { isAuthDisabled } = useAuthContext(); const { isAuthDisabled } = useAuthContext();
@ -88,6 +91,13 @@ const GlossaryPageV1 = () => {
currPage: 1, currPage: 1,
}); });
const handleShowErrorToast = (errMessage: string) => {
showToast({
variant: 'error',
body: errMessage,
});
};
const handleChildLoading = (status: boolean) => { const handleChildLoading = (status: boolean) => {
setIsChildLoading(status); setIsChildLoading(status);
}; };
@ -100,6 +110,16 @@ const GlossaryPageV1 = () => {
setExpandedKey(key); setExpandedKey(key);
}; };
const handleSearchText = (text: string) => {
setSearchText(text);
};
/**
* Selects glossary after fetching list
* if no fqn is present in route params
* @param data Glossary to be selected initially
* @param noSetData bool to decide if data is already set
*/
const initSelectGlossary = (data: Glossary, noSetData = false) => { const initSelectGlossary = (data: Glossary, noSetData = false) => {
if (!noSetData) { if (!noSetData) {
setSelectedData(data); setSelectedData(data);
@ -109,47 +129,24 @@ const GlossaryPageV1 = () => {
setExpandedKey([data.name]); setExpandedKey([data.name]);
}; };
const fetchGlossaryList = (paging = '') => { /**
setIsLoading(true); * To fetch glossary term data
getGlossariesWithRootTerms(paging, 100, ['owner', 'tags', 'reviewers']) * @param fqn fullyQualifiedName of term
.then((data: ModifiedGlossaryData[]) => { * @param pos hierarchical position of term in existing tree
if (data?.length) { * @param arrGlossary list of available/fetched glossaries
setGlossaries(data); */
setGlossariesList(data);
initSelectGlossary(data[0]);
} else {
setGlossariesList([]);
}
setIsLoading(false);
})
.catch((err: AxiosError) => {
showToast({
variant: 'error',
body: err.response?.data?.message ?? 'Something went wrong!',
});
setIsLoading(false);
})
.finally(() => {
handleChildLoading(false);
});
};
const fetchGlossaryTermByName = ( const fetchGlossaryTermByName = (
name: string, fqn: string,
pos: string[], pos: number[],
key?: string arrGlossary: ModifiedGlossaryData[]
) => { ) => {
getGlossaryTermByFQN(name, [ getGlossaryTermByFQN(fqn, ['children', 'relatedTerms', 'reviewers', 'tags'])
'children',
'relatedTerms',
'reviewers',
'tags',
])
.then(async (res: AxiosResponse) => { .then(async (res: AxiosResponse) => {
const { data } = res; const { data } = res;
if (data) { if (data) {
const clonedGlossaryList = cloneDeep(glossariesList); const clonedGlossaryList = cloneDeep(arrGlossary);
let treeNode = clonedGlossaryList[+pos[0]]; let treeNode = clonedGlossaryList[pos[0]];
for (let i = 1; i < pos.length; i++) { for (let i = 1; i < pos.length; i++) {
if (treeNode.children) { if (treeNode.children) {
treeNode = treeNode.children[+pos[i]] as ModifiedGlossaryData; treeNode = treeNode.children[+pos[i]] as ModifiedGlossaryData;
@ -193,29 +190,188 @@ const GlossaryPageV1 = () => {
extend(treeNode, { ...data, children }); extend(treeNode, { ...data, children });
setSelectedData(data); setSelectedData(data);
if (key) { if (fqn) {
handleSelectedKey(key); if (!expandedKey.length) {
setExpandedKey(getHierarchicalKeysByFQN(fqn));
}
handleSelectedKey(fqn);
} }
setGlossariesList(clonedGlossaryList); setGlossariesList(clonedGlossaryList);
setIsGlossaryActive(false); setIsGlossaryActive(false);
} else {
handleShowErrorToast(
jsonData['api-error-messages']['fetch-glossary-term-error']
);
} }
}) })
.catch((err: AxiosError) => { .catch((err: AxiosError) => {
showToast({ handleShowErrorToast(
variant: 'error', err.response?.data?.message ||
body: jsonData['api-error-messages']['fetch-glossary-term-error']
err.response?.data?.message ?? );
'Error while fetching glossary terms!',
});
}) })
.finally(() => { .finally(() => {
setIsLoading(false);
handleChildLoading(false); handleChildLoading(false);
setLoadingKey((pre) => { setLoadingKey((pre) => {
return pre.filter((item) => item !== key); return pre.filter((item) => item !== fqn);
}); });
}); });
}; };
/**
* To fetch Assets using glossary term
* @param fqn fullyQualifiedName of term
* @param forceReset bool to reset the page to 1, incase of change in glossary term
*/
const fetchGlossaryTermAssets = (fqn: string, forceReset = false) => {
if (fqn) {
const tagName = fqn;
searchData(
'',
forceReset ? 1 : assetData.currPage,
PAGE_SIZE,
`(tags:"${tagName}")`,
'',
'',
myDataSearchIndex
)
.then((res: SearchResponse) => {
const hits = res?.data?.hits?.hits;
if (hits?.length > 0) {
setAssetData((pre) => {
const data = formatDataResponse(hits);
const total = res.data.hits.total.value;
return forceReset
? {
data,
total,
currPage: 1,
}
: { ...pre, data, total };
});
} else {
setAssetData((pre) => {
const data = [] as GlossaryTermAssets['data'];
const total = 0;
return forceReset
? {
data,
total,
currPage: 1,
}
: { ...pre, data, total };
});
}
})
.catch((err: AxiosError) => {
handleShowErrorToast(
err.response?.data?.message ||
jsonData['api-error-messages']['elastic-search-error']
);
});
} else {
setAssetData({ data: [], total: 0, currPage: 1 });
}
};
/**
* To select data based on glossary or term name
* @param dataFQN fullyQualifiedName of glossary or term
* @param arrGlossary list of available/fetched glossaries
*/
const selectDataByFQN = (
dataFQN: string,
arrGlossary: ModifiedGlossaryData[]
) => {
handleChildLoading(true);
const hierarchy = getTermPosFromGlossaries(arrGlossary, dataFQN);
if (hierarchy.length < 2) {
setSelectedData(arrGlossary[hierarchy[0]]);
handleSelectedKey(dataFQN);
if (!expandedKey.length) {
setExpandedKey([dataFQN]);
}
setIsGlossaryActive(true);
setIsLoading(false);
handleChildLoading(false);
} else {
setLoadingKey((pre) => {
return !pre.includes(dataFQN) ? [...pre, dataFQN] : pre;
});
fetchGlossaryTermByName(dataFQN, hierarchy, arrGlossary);
fetchGlossaryTermAssets(dataFQN, true);
}
};
/**
* To check if glossary/term already exists and add to tree if they don't
* Then select the glossary/term by it's fqn
* @param arrGlossary list of available/fetched glossaries
* @param fqn fullyQualifiedName of glossary or term
*/
const checkAndFetchDataByFQN = (
arrGlossary: ModifiedGlossaryData[],
fqn: string
) => {
let modifiedData = cloneDeep(arrGlossary);
const arrFQN = getHierarchicalKeysByFQN(fqn);
const glossary: ModifiedGlossaryData | GlossaryTerm = modifiedData.find(
(item) => item.name === arrFQN[0]
) as ModifiedGlossaryData;
const data = getTermDataFromGlossary(glossary, fqn);
if (isEmpty(data)) {
modifiedData = updateGlossaryListBySearchedTerms(modifiedData, [
{ fqdn: arrFQN[arrFQN.length - 1] },
] as FormattedGlossarySuggestion[]);
}
selectDataByFQN(fqn, modifiedData);
};
/**
* To fetch the list of all glossaries,
* and check for selection if nested fqn available
* @param termFqn fullyQualifiedName of term
* @param paging cursor pagination
*/
const fetchGlossaryList = (termFqn = '', paging = '') => {
setIsLoading(true);
getGlossariesWithRootTerms(paging, 1000, ['owner', 'tags', 'reviewers'])
.then((data: ModifiedGlossaryData[]) => {
if (data?.length) {
setGlossaries(data);
setGlossariesList(data);
if (termFqn) {
checkAndFetchDataByFQN(data, termFqn);
} else {
initSelectGlossary(data[0]);
setIsLoading(false);
handleChildLoading(false);
}
} else {
setGlossariesList([]);
setIsLoading(false);
handleChildLoading(false);
}
})
.catch((err: AxiosError) => {
handleShowErrorToast(
err.response?.data?.message ||
jsonData['api-error-messages']['fetch-glossary-list-error']
);
setIsLoading(false);
handleChildLoading(false);
});
};
/**
* To update glossary tree based on searched terms
* @param arrGlossaries list of glossaries
* @param newGlossaries set of glossaries present in searched terms
* @param searchedTerms list of formatted searched terms
*/
const getSearchedGlossaries = ( const getSearchedGlossaries = (
arrGlossaries: ModifiedGlossaryData[], arrGlossaries: ModifiedGlossaryData[],
newGlossaries: string[], newGlossaries: string[],
@ -247,6 +403,9 @@ const GlossaryPageV1 = () => {
} }
}; };
/**
* To fetch terms based on search text
*/
const fetchSearchedTerms = useCallback(() => { const fetchSearchedTerms = useCallback(() => {
if (searchText) { if (searchText) {
searchData( searchData(
@ -298,6 +457,11 @@ const GlossaryPageV1 = () => {
} }
}, [searchText]); }, [searchText]);
/**
* To save updated glossary using patch method
* @param updatedData glossary with new values
* @returns promise of api response
*/
const saveUpdatedGlossaryData = ( const saveUpdatedGlossaryData = (
updatedData: Glossary updatedData: Glossary
): Promise<AxiosResponse> => { ): Promise<AxiosResponse> => {
@ -309,6 +473,10 @@ const GlossaryPageV1 = () => {
) as unknown as Promise<AxiosResponse>; ) as unknown as Promise<AxiosResponse>;
}; };
/**
* To update glossary
* @param updatedData glossary with new values
*/
const updateGlossary = (updatedData: Glossary) => { const updateGlossary = (updatedData: Glossary) => {
saveUpdatedGlossaryData(updatedData) saveUpdatedGlossaryData(updatedData)
.then((res: AxiosResponse) => { .then((res: AxiosResponse) => {
@ -337,17 +505,25 @@ const GlossaryPageV1 = () => {
} }
}); });
}); });
} else {
handleShowErrorToast(
jsonData['api-error-messages']['update-description-error']
);
} }
}) })
.catch((err: AxiosError) => { .catch((err: AxiosError) => {
showToast({ handleShowErrorToast(
variant: 'error', err.response?.data?.message ||
body: jsonData['api-error-messages']['update-description-error']
err.response?.data?.message ?? 'Error while updating description!', );
});
}); });
}; };
/**
* To save updated glossary term using patch method
* @param updatedData glossary term with new values
* @returns promise of api response
*/
const saveUpdatedGlossaryTermData = ( const saveUpdatedGlossaryTermData = (
updatedData: GlossaryTerm updatedData: GlossaryTerm
): Promise<AxiosResponse> => { ): Promise<AxiosResponse> => {
@ -359,20 +535,33 @@ const GlossaryPageV1 = () => {
) as unknown as Promise<AxiosResponse>; ) as unknown as Promise<AxiosResponse>;
}; };
/**
* To update glossary term
* @param updatedData glossary term with new values
*/
const handleGlossaryTermUpdate = (updatedData: GlossaryTerm) => { const handleGlossaryTermUpdate = (updatedData: GlossaryTerm) => {
saveUpdatedGlossaryTermData(updatedData) saveUpdatedGlossaryTermData(updatedData)
.then((res: AxiosResponse) => { .then((res: AxiosResponse) => {
setSelectedData(res.data); if (res.data) {
setSelectedData(res.data);
} else {
handleShowErrorToast(
jsonData['api-error-messages']['update-glossary-term-error']
);
}
}) })
.catch((err: AxiosError) => { .catch((err: AxiosError) => {
showToast({ handleShowErrorToast(
variant: 'error', err.response?.data?.message ||
body: jsonData['api-error-messages']['update-glossary-term-error']
err.response?.data?.message ?? 'Error while updating glossaryTerm!', );
});
}); });
}; };
/**
* To delete glossary by id
* @param id glossary id
*/
const handleGlossaryDelete = (id: string) => { const handleGlossaryDelete = (id: string) => {
setDeleteStatus('waiting'); setDeleteStatus('waiting');
deleteGlossary(id) deleteGlossary(id)
@ -381,14 +570,18 @@ const GlossaryPageV1 = () => {
fetchGlossaryList(); fetchGlossaryList();
}) })
.catch((err: AxiosError) => { .catch((err: AxiosError) => {
showToast({ handleShowErrorToast(
variant: 'error', err.response?.data?.message ||
body: err.response?.data?.message ?? 'Something went wrong!', jsonData['api-error-messages']['delete-glossary-error']
}); );
setDeleteStatus('initial'); setDeleteStatus('initial');
}); });
}; };
/**
* To delete glossary term by id
* @param id glossary term id
*/
const handleGlossaryTermDelete = (id: string) => { const handleGlossaryTermDelete = (id: string) => {
setDeleteStatus('waiting'); setDeleteStatus('waiting');
deleteGlossaryTerm(id) deleteGlossaryTerm(id)
@ -397,18 +590,24 @@ const GlossaryPageV1 = () => {
fetchGlossaryList(); fetchGlossaryList();
}) })
.catch((err: AxiosError) => { .catch((err: AxiosError) => {
showToast({ handleShowErrorToast(
variant: 'error', err.response?.data?.message ||
body: err.response?.data?.message ?? 'Something went wrong!', jsonData['api-error-messages']['delete-glossary-term-error']
}); );
setDeleteStatus('initial'); setDeleteStatus('initial');
}); });
}; };
/**
* To redirect to add glossary page
*/
const handleAddGlossaryClick = () => { const handleAddGlossaryClick = () => {
history.push(ROUTES.ADD_GLOSSARY); history.push(ROUTES.ADD_GLOSSARY);
}; };
/**
* To redirct to add glossary term page
*/
const handleAddGlossaryTermClick = () => { const handleAddGlossaryTermClick = () => {
const activeTerm = selectedKey.split('.'); const activeTerm = selectedKey.split('.');
const glossaryName = activeTerm[0]; const glossaryName = activeTerm[0];
@ -419,88 +618,39 @@ const GlossaryPageV1 = () => {
} }
}; };
const fetchGlossaryTermAssets = (data: GlossaryTerm, forceReset = false) => { /**
if (data?.fullyQualifiedName || data?.name) { * handle assets page change
const tagName = data?.fullyQualifiedName || data?.name; // Incase fqn is not fetched yet. * @param page new page number
searchData( */
'',
forceReset ? 1 : assetData.currPage,
PAGE_SIZE,
`(tags:"${tagName}")`,
'',
'',
myDataSearchIndex
).then((res: SearchResponse) => {
const hits = res.data.hits.hits;
if (hits.length > 0) {
setAssetData((pre) => {
const data = formatDataResponse(hits);
const total = res.data.hits.total.value;
return forceReset
? {
data,
total,
currPage: 1,
}
: { ...pre, data, total };
});
} else {
setAssetData((pre) => {
const data = [] as GlossaryTermAssets['data'];
const total = 0;
return forceReset
? {
data,
total,
currPage: 1,
}
: { ...pre, data, total };
});
}
});
} else {
setAssetData({ data: [], total: 0, currPage: 1 });
}
};
const handleAssetPagination = (page: number) => { const handleAssetPagination = (page: number) => {
setAssetData((pre) => ({ ...pre, currPage: page })); setAssetData((pre) => ({ ...pre, currPage: page }));
}; };
const handleSelectedData = ( /**
data: Glossary | GlossaryTerm, * handle route change on selecting glossary or glossary term
pos: string, * @param key fqn of glossary or Term
key: string */
) => { const handleSelectedData = (key: string) => {
handleChildLoading(true); const path = getGlossaryPath(key);
const hierarchy = pos.split('-').splice(1); history.push(path);
// console.log(hierarchy); };
if (hierarchy.length < 2) {
setSelectedData(data); /**
handleSelectedKey(key); * Fetch details to show based on route params
setIsGlossaryActive(true); * and existing data list
handleChildLoading(false); */
const fetchData = () => {
if (glossariesList.length) {
checkAndFetchDataByFQN(glossariesList, glossaryName);
} else { } else {
setLoadingKey((pre) => { fetchGlossaryList(glossaryName);
return !pre.includes(key) ? [...pre, key] : pre;
});
fetchGlossaryTermByName(
(data as GlossaryTerm)?.fullyQualifiedName || data?.name,
hierarchy,
key
);
fetchGlossaryTermAssets(data as GlossaryTerm, true);
} }
}; };
const handleSearchText = (text: string) => {
setSearchText(text);
};
useEffect(() => { useEffect(() => {
fetchGlossaryTermAssets(selectedData as GlossaryTerm); fetchGlossaryTermAssets(
(selectedData as GlossaryTerm)?.fullyQualifiedName || ''
);
}, [assetData.currPage]); }, [assetData.currPage]);
useEffect(() => { useEffect(() => {
@ -508,8 +658,8 @@ const GlossaryPageV1 = () => {
}, [searchText]); }, [searchText]);
useEffect(() => { useEffect(() => {
fetchGlossaryList(); fetchData();
}, []); }, [glossaryName]);
return ( return (
<PageContainerV1 className="tw-pt-4"> <PageContainerV1 className="tw-pt-4">

View File

@ -12,6 +12,7 @@
*/ */
import { AxiosError, AxiosResponse } from 'axios'; import { AxiosError, AxiosResponse } from 'axios';
import { cloneDeep, isEmpty } from 'lodash';
import { import {
FormattedGlossarySuggestion, FormattedGlossarySuggestion,
FormattedGlossaryTermData, FormattedGlossaryTermData,
@ -37,6 +38,10 @@ export interface GlossaryTermTreeNode {
name: string; name: string;
} }
/**
* To get all glossary terms
* @returns promise of list of formatted glossary terms
*/
export const fetchGlossaryTerms = (): Promise<FormattedGlossaryTermData[]> => { export const fetchGlossaryTerms = (): Promise<FormattedGlossaryTermData[]> => {
return new Promise<FormattedGlossaryTermData[]>((resolve, reject) => { return new Promise<FormattedGlossaryTermData[]>((resolve, reject) => {
searchData(WILD_CARD_CHAR, 1, 1000, '', '', '', SearchIndex.GLOSSARY) searchData(WILD_CARD_CHAR, 1, 1000, '', '', '', SearchIndex.GLOSSARY)
@ -50,12 +55,22 @@ export const fetchGlossaryTerms = (): Promise<FormattedGlossaryTermData[]> => {
}); });
}; };
/**
* To get list of fqns from list of glossary terms
* @param terms formatted glossary terms
* @returns list of term fqns
*/
export const getGlossaryTermlist = ( export const getGlossaryTermlist = (
terms: Array<FormattedGlossaryTermData> = [] terms: Array<FormattedGlossaryTermData> = []
): Array<string> => { ): Array<string> => {
return terms.map((term: FormattedGlossaryTermData) => term?.fqdn); return terms.map((term: FormattedGlossaryTermData) => term?.fqdn);
}; };
/**
* To get child terms of any node if available
* @param listTermFQN fqn of targeted child terms
* @returns promise of list of glossary terms
*/
export const getChildGlossaryTerms = ( export const getChildGlossaryTerms = (
listTermFQN: Array<string> listTermFQN: Array<string>
): Promise<GlossaryTerm[]> => { ): Promise<GlossaryTerm[]> => {
@ -76,6 +91,11 @@ export const getChildGlossaryTerms = (
}); });
}; };
/**
* To recursively generate RcTree data from glossary list
* @param data list of glossary or glossary terms
* @returns RcTree data node
*/
export const generateTreeData = (data: ModifiedGlossaryData[]): DataNode[] => { export const generateTreeData = (data: ModifiedGlossaryData[]): DataNode[] => {
return data.map((d) => { return data.map((d) => {
return d.children?.length return d.children?.length
@ -93,6 +113,13 @@ export const generateTreeData = (data: ModifiedGlossaryData[]): DataNode[] => {
}); });
}; };
/**
* Creates glossary term tree node from fqn
* and root node name
* @param leafFqn node fqn
* @param name root node name
* @returns node for glossary tree
*/
const createGlossaryTermNode = ( const createGlossaryTermNode = (
leafFqn: string, leafFqn: string,
name: string name: string
@ -112,6 +139,12 @@ const createGlossaryTermNode = (
}; };
}; };
/**
* To merge the duplicate glossaries and terms
* to generate optimised tree
* @param treeNodes list of glossary nodes with duplicate items
* @returns list of glossary nodes with unique items
*/
const optimiseGlossaryTermTree = (treeNodes?: GlossaryTermTreeNode[]) => { const optimiseGlossaryTermTree = (treeNodes?: GlossaryTermTreeNode[]) => {
if (treeNodes) { if (treeNodes) {
for (let i = 0; i < treeNodes.length; i++) { for (let i = 0; i < treeNodes.length; i++) {
@ -139,6 +172,11 @@ const optimiseGlossaryTermTree = (treeNodes?: GlossaryTermTreeNode[]) => {
return treeNodes; return treeNodes;
}; };
/**
* To generate glossry tree from searched terms
* @param searchedTerms list of formatted searched terms
* @returns list of glossary tree
*/
export const getSearchedGlossaryTermTree = ( export const getSearchedGlossaryTermTree = (
searchedTerms: FormattedGlossarySuggestion[] searchedTerms: FormattedGlossarySuggestion[]
): GlossaryTermTreeNode[] => { ): GlossaryTermTreeNode[] => {
@ -153,6 +191,12 @@ export const getSearchedGlossaryTermTree = (
return termTree; return termTree;
}; };
/**
* To get Tree of glossaries based on search result
* @param glossaries list of glossaries
* @param searchedTerms list of formatted searched terms
* @returns glossary list based on searched terms
*/
export const updateGlossaryListBySearchedTerms = ( export const updateGlossaryListBySearchedTerms = (
glossaries: ModifiedGlossaryData[], glossaries: ModifiedGlossaryData[],
searchedTerms: FormattedGlossarySuggestion[] searchedTerms: FormattedGlossarySuggestion[]
@ -171,6 +215,10 @@ export const updateGlossaryListBySearchedTerms = (
}, [] as ModifiedGlossaryData[]); }, [] as ModifiedGlossaryData[]);
}; };
/**
* To get actions for action dropdown button
* @returns list of action items
*/
export const getActionsList = () => { export const getActionsList = () => {
return [ return [
{ {
@ -180,6 +228,12 @@ export const getActionsList = () => {
]; ];
}; };
/**
* To get hierarchy of fqns from glossary to targeted term
* from given fqn
* @param fqn fqn of glossary or glossary term
* @returns list of fqns
*/
export const getHierarchicalKeysByFQN = (fqn: string) => { export const getHierarchicalKeysByFQN = (fqn: string) => {
const keys = fqn.split('.').reduce((prev, curr) => { const keys = fqn.split('.').reduce((prev, curr) => {
const currFqn = prev.length ? `${prev[prev.length - 1]}.${curr}` : curr; const currFqn = prev.length ? `${prev[prev.length - 1]}.${curr}` : curr;
@ -190,12 +244,80 @@ export const getHierarchicalKeysByFQN = (fqn: string) => {
return keys; return keys;
}; };
/**
* To get glossary term data from glossary object
* @param glossary parent glossary
* @param termFqn fqn of targeted glossary term
* @returns Glossary term or {}
*/
export const getTermDataFromGlossary = (
glossary: ModifiedGlossaryData,
termFqn: string
) => {
let data: ModifiedGlossaryData | GlossaryTerm = cloneDeep(glossary);
const arrFQN = getHierarchicalKeysByFQN(termFqn);
for (let i = 1; i < arrFQN.length; i++) {
data = data?.children
? ((data.children as unknown as GlossaryTerm[])?.find(
(item) =>
item.fullyQualifiedName === arrFQN[i] || item.name === arrFQN[i]
) as GlossaryTerm)
: ({} as GlossaryTerm);
if (isEmpty(data)) {
break;
}
}
return data;
};
/**
* To get relative indexed position of
* glossary term from tree of glossaries
* @param arrGlossary list of glossary
* @param termFqn fqn of target glossary term
* @returns array of numbered positions
*/
export const getTermPosFromGlossaries = (
arrGlossary: ModifiedGlossaryData[],
termFqn: string
) => {
const arrFQN = getHierarchicalKeysByFQN(termFqn);
const glossaryIdx = arrGlossary.findIndex((item) => item.name === arrFQN[0]);
const pos = [];
if (glossaryIdx !== -1) {
pos.push(glossaryIdx);
let data: ModifiedGlossaryData | GlossaryTerm = arrGlossary[glossaryIdx];
for (let i = 1; i < arrFQN.length; i++) {
const index = data?.children
? (data.children as unknown as GlossaryTerm[])?.findIndex(
(item) =>
item.fullyQualifiedName === arrFQN[i] || item.name === arrFQN[i]
)
: -1;
if (index === -1) {
break;
}
data = (data?.children ? data?.children[index] : {}) as GlossaryTerm;
pos.push(index);
}
}
return pos;
};
/**
* Fetches and adds root terms to each glossary
* @param glossaries list of glossaries
* @returns promise of list of glossaries with root terms
*/
const getRootTermEmbeddedGlossary = ( const getRootTermEmbeddedGlossary = (
glossaries: Array<ModifiedGlossaryData> glossaries: Array<ModifiedGlossaryData>
): Promise<Array<ModifiedGlossaryData>> => { ): Promise<Array<ModifiedGlossaryData>> => {
return new Promise<Array<ModifiedGlossaryData>>((resolve, reject) => { return new Promise<Array<ModifiedGlossaryData>>((resolve, reject) => {
const promises = glossaries.map((glossary) => const promises = glossaries.map((glossary) =>
getGlossaryTerms(glossary.id, 100, [ getGlossaryTerms(glossary.id, 1000, [
'children', 'children',
'relatedTerms', 'relatedTerms',
'reviewers', 'reviewers',
@ -222,6 +344,13 @@ const getRootTermEmbeddedGlossary = (
}); });
}; };
/**
* Fetches list of glossaries with root terms in each of them
* @param paging pagination cursor
* @param limit result count
* @param arrQueryFields api query-string
* @returns promise of api response
*/
export const getGlossariesWithRootTerms = ( export const getGlossariesWithRootTerms = (
paging = '', paging = '',
limit = 10, limit = 10,