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/
dist/
# mockups
mock-api/
# macOS
.DS_Store

View File

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

View File

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

View File

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

View File

@ -295,8 +295,14 @@ export const getUserPath = (username: string) => {
return path;
};
export const getGlossaryPath = () => {
return ROUTES.GLOSSARY;
export const getGlossaryPath = (fqn?: string) => {
let path = ROUTES.GLOSSARY;
if (fqn) {
path = ROUTES.GLOSSARY_DETAILS;
path = path.replace(PLACEHOLDER_GLOSSARY_NAME, fqn);
}
return path;
};
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 AddGlossary from '../../components/AddGlossary/AddGlossary.component';
import PageContainerV1 from '../../components/containers/PageContainerV1';
import { ROUTES } from '../../constants/constants';
import { getGlossaryPath } from '../../constants/constants';
import { CreateGlossary } from '../../generated/api/data/createGlossary';
import { useAuth } from '../../hooks/authHooks';
import useToastContext from '../../hooks/useToastContext';
import jsonData from '../../jsons/en';
import { getTagCategories, getTaglist } from '../../utils/TagsUtils';
const AddGlossaryPage: FunctionComponent = () => {
@ -21,30 +22,44 @@ const AddGlossaryPage: FunctionComponent = () => {
const [isTagLoading, setIsTagLoading] = useState<boolean>(false);
const [status, setStatus] = useState<LoadingState>('initial');
const goToGlossary = () => {
history.push(ROUTES.GLOSSARY);
const goToGlossary = (name = '') => {
history.push(getGlossaryPath(name));
};
const handleCancel = () => {
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) => {
setStatus('waiting');
addGlossaries(data)
.then(() => {
setStatus('success');
setTimeout(() => {
setStatus('initial');
goToGlossary();
}, 500);
.then((res) => {
if (res.data) {
setStatus('success');
setTimeout(() => {
setStatus('initial');
goToGlossary(res.data.name);
}, 500);
} else {
handleSaveFailure();
}
})
.catch((err: AxiosError) => {
showToast({
variant: 'error',
body: err.message || 'Something went wrong!',
});
setStatus('initial');
handleSaveFailure(err.response?.data?.message);
});
};
@ -52,7 +67,19 @@ const AddGlossaryPage: FunctionComponent = () => {
setIsTagLoading(true);
getTagCategories()
.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(() => {
setIsTagLoading(false);

View File

@ -1,8 +1,27 @@
import { findByText, render } from '@testing-library/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>);
});
@ -12,7 +31,7 @@ jest.mock('../../axiosAPIs/glossaryAPI', () => ({
describe('Test GlossaryComponent page', () => {
it('GlossaryComponent Page Should render', async () => {
const { container } = render(<GlossaryPage />);
const { container } = render(<GlossaryPageV1 />);
const glossaryComponent = await findByText(
container,

View File

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

View File

@ -12,6 +12,7 @@
*/
import { AxiosError, AxiosResponse } from 'axios';
import { cloneDeep, isEmpty } from 'lodash';
import {
FormattedGlossarySuggestion,
FormattedGlossaryTermData,
@ -37,6 +38,10 @@ export interface GlossaryTermTreeNode {
name: string;
}
/**
* To get all glossary terms
* @returns promise of list of formatted glossary terms
*/
export const fetchGlossaryTerms = (): Promise<FormattedGlossaryTermData[]> => {
return new Promise<FormattedGlossaryTermData[]>((resolve, reject) => {
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 = (
terms: Array<FormattedGlossaryTermData> = []
): Array<string> => {
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 = (
listTermFQN: Array<string>
): 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[] => {
return data.map((d) => {
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 = (
leafFqn: 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[]) => {
if (treeNodes) {
for (let i = 0; i < treeNodes.length; i++) {
@ -139,6 +172,11 @@ const optimiseGlossaryTermTree = (treeNodes?: GlossaryTermTreeNode[]) => {
return treeNodes;
};
/**
* To generate glossry tree from searched terms
* @param searchedTerms list of formatted searched terms
* @returns list of glossary tree
*/
export const getSearchedGlossaryTermTree = (
searchedTerms: FormattedGlossarySuggestion[]
): GlossaryTermTreeNode[] => {
@ -153,6 +191,12 @@ export const getSearchedGlossaryTermTree = (
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 = (
glossaries: ModifiedGlossaryData[],
searchedTerms: FormattedGlossarySuggestion[]
@ -171,6 +215,10 @@ export const updateGlossaryListBySearchedTerms = (
}, [] as ModifiedGlossaryData[]);
};
/**
* To get actions for action dropdown button
* @returns list of action items
*/
export const getActionsList = () => {
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) => {
const keys = fqn.split('.').reduce((prev, curr) => {
const currFqn = prev.length ? `${prev[prev.length - 1]}.${curr}` : curr;
@ -190,12 +244,80 @@ export const getHierarchicalKeysByFQN = (fqn: string) => {
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 = (
glossaries: Array<ModifiedGlossaryData>
): Promise<Array<ModifiedGlossaryData>> => {
return new Promise<Array<ModifiedGlossaryData>>((resolve, reject) => {
const promises = glossaries.map((glossary) =>
getGlossaryTerms(glossary.id, 100, [
getGlossaryTerms(glossary.id, 1000, [
'children',
'relatedTerms',
'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 = (
paging = '',
limit = 10,