From 6e3ad4549e600815b19e18485bc9552ef3c3195d Mon Sep 17 00:00:00 2001 From: Brijesh Bhalala Date: Tue, 27 May 2025 12:46:35 +0530 Subject: [PATCH] ATLAS-5044: [React UI] Fix node expand/collapse and selection issues in TreeView component within sidebar --- .../src/components/DialogShowMoreLess.tsx | 147 ++-- .../src/components/Table/TableFilters.tsx | 12 +- dashboard/src/components/TreeNodeIcons.tsx | 75 +- dashboard/src/models/treeStructureType.ts | 1 + dashboard/src/utils/CommonViewFunction.ts | 39 + dashboard/src/utils/Utils.ts | 22 +- .../src/views/Classification/DeleteTag.tsx | 5 +- .../views/DetailPage/DetailPageAttributes.tsx | 675 ++++++----------- .../src/views/DetailPage/EntityDetailPage.tsx | 18 +- .../GlossaryDetails/TermRelation.tsx | 7 +- .../src/views/Glossary/AssignCategory.tsx | 318 -------- .../src/views/Glossary/AssignGlossaryItem.tsx | 349 +++++++++ .../src/views/Glossary/AssignRelatedTerm.tsx | 250 +++++++ dashboard/src/views/Glossary/AssignTerm.tsx | 696 ------------------ .../src/views/Glossary/DeleteGlossary.tsx | 5 +- .../src/views/SearchResult/SearchResult.tsx | 12 +- .../SideBar/SideBarTree/GlossaryTree.tsx | 139 ++-- .../views/SideBar/SideBarTree/SideBarTree.tsx | 285 ++++--- 18 files changed, 1297 insertions(+), 1758 deletions(-) delete mode 100644 dashboard/src/views/Glossary/AssignCategory.tsx create mode 100644 dashboard/src/views/Glossary/AssignGlossaryItem.tsx create mode 100644 dashboard/src/views/Glossary/AssignRelatedTerm.tsx delete mode 100644 dashboard/src/views/Glossary/AssignTerm.tsx diff --git a/dashboard/src/components/DialogShowMoreLess.tsx b/dashboard/src/components/DialogShowMoreLess.tsx index ba9b3732271..72b5997c3a9 100644 --- a/dashboard/src/components/DialogShowMoreLess.tsx +++ b/dashboard/src/components/DialogShowMoreLess.tsx @@ -30,15 +30,17 @@ import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline"; import MoreHorizIcon from "@mui/icons-material/MoreHoriz"; import AddTag from "@views/Classification/AddTag"; import { useAppDispatch, useAppSelector } from "@hooks/reducerHook"; -import AssignTerm from "@views/Glossary/AssignTerm"; -import AssignCategory from "@views/Glossary/AssignCategory"; import AddTagAttributes from "@views/Classification/AddTagAttributes"; import { fetchGlossaryDetails } from "@redux/slice/glossaryDetailsSlice"; import { fetchDetailPageData } from "@redux/slice/detailPageSlice"; import { fetchGlossaryData } from "@redux/slice/glossarySlice"; +import AssignGlossaryItem from "@views/Glossary/AssignGlossaryItem"; +import { + assignGlossaryType, + assignTermstoEntites +} from "@api/apiMethods/glossaryApiMethod"; const CHIP_MAX_WIDTH = "200px"; -const ITEM_HEIGHT = 48; const DialogShowMoreLess = ({ value, @@ -81,14 +83,12 @@ const DialogShowMoreLess = ({ const toastId: any = useRef(null); const { guid }: any = useParams(); const dispatchApi = useAppDispatch(); - // const navigate = useNavigate(); const location = useLocation(); const searchParams = new URLSearchParams(location.search); const gType = searchParams.get("gtype"); const [openModal, setOpenModal] = useState(false); const [tagModal, setTagModal] = useState(false); const [termModal, setTermModal] = useState(false); - const [categoryModal, setCategoryModal] = useState(false); const [attributeModal, setAttributeModal] = useState(false); const [removeLoader, setRemoveLoader] = useState(false); @@ -99,9 +99,7 @@ const DialogShowMoreLess = ({ const handleCloseTermModal = () => { setTermModal(false); }; - const handleCloseCategoryModal = () => { - setCategoryModal(false); - }; + const handleCloseAttributeModal = () => { setAttributeModal(false); }; @@ -131,7 +129,7 @@ const DialogShowMoreLess = ({ setRemoveLoader(true); if (colName == "Classification") { await removeApiMethod( - detailPage ? entity.guid : value.guid, + entity?.guid || value.guid, currentValue.selectedValue ); } else if (colName == "Term" || colName == "Category") { @@ -149,28 +147,30 @@ const DialogShowMoreLess = ({ } } ); - if (isEmpty(guid) || detailPage) { + if ((isEmpty(guid) || detailPage) && !relatedTerm) { await removeApiMethod( - detailPage ? selectedTerm.guid : selectedTerm.termGuid, + selectedTerm?.guid || selectedTerm.termGuid, { - guid: detailPage ? entity.guid : value.guid, - relationshipGuid: detailPage - ? selectedTerm.relationshipGuid - : selectedTerm.relationGuid + guid: entity?.guid || value.guid, + relationshipGuid: + selectedTerm?.relationshipGuid || selectedTerm.relationGuid } ); - } else if (!detailPage && !isShowMoreLess) { + } else if ((!detailPage && !isShowMoreLess) || relatedTerm) { let values = { ...value }; let data; if (colName == "Term") { data = values?.[columnVal].filter( - (obj: { displayText: string }) => { - return obj.displayText != currentValue.selectedValue; + (obj: { qualifiedName: string }) => { + return obj.qualifiedName != currentValue.selectedValue; } ); - - values["terms"] = data; + if (relatedTerm) { + values[columnVal] = data; + } else { + values["terms"] = data; + } } else { data = values?.[columnVal].filter( (obj: { displayText: string }) => { @@ -180,11 +180,7 @@ const DialogShowMoreLess = ({ values["categories"] = data; } - await removeApiMethod( - guid, - colName == "Term" ? "category" : "term", - values - ); + await removeApiMethod(guid, values); } } setOpenModal(false); @@ -238,9 +234,7 @@ const DialogShowMoreLess = ({ if (colName == "Classification" || colName == "Propagated Classification") { let keys = Array.from(searchParams.keys()); for (let i = 0; i < keys.length; i++) { - // if (keys[i] != "searchType") { searchParams.delete(keys[i]); - // } } searchParams.set("tag", values); @@ -285,20 +279,26 @@ const DialogShowMoreLess = ({ }; const assignTitle = () => { - if (colName == "Classification") { - return "Add Classification"; - } else if (colName == "Term") { - return "Add Term"; + switch (colName) { + case "Classification": + return "Add Classification"; + case "Term": + return "Add Term"; + default: + return ""; } }; const removeTitle = () => { - if (colName == "Classification") { - return "Remove Classification Assignment"; - } else if (colName == "Term") { - return "Remove Term Assignment"; - } else if (colName == "Category") { - return "Remove Category Assignment"; + switch (colName) { + case "Classification": + return "Remove Classification Assignment"; + case "Term": + return "Remove Term Assignment"; + case "Category": + return "Remove Category Assignment"; + default: + return ""; } }; @@ -425,14 +425,18 @@ const DialogShowMoreLess = ({ color="primary" size="small" onClick={() => { - if (colName == "Classification") { - setTagModal(true); - } else if (colName == "Term") { - setTermModal(true); - } else if (colName == "Category") { - setCategoryModal(true); - } else if (colName == "Attribute") { - setAttributeModal(true); + switch (colName) { + case "Classification": + setTagModal(true); + break; + case "Term": + setTermModal(true); + break; + case "Attribute": + setAttributeModal(true); + break; + default: + break; } }} > @@ -449,11 +453,6 @@ const DialogShowMoreLess = ({ anchorEl={openMenu} open={open} onClose={handleClose} - PaperProps={{ - style: { - maxHeight: ITEM_HEIGHT * 4.5 - } - }} > {value?.[columnVal].map((obj: any, index: number) => { if (index > 0) { @@ -513,14 +512,18 @@ const DialogShowMoreLess = ({ color="primary" size="small" onClick={() => { - if (colName == "Classification") { - setTagModal(true); - } else if (colName == "Term") { - setTermModal(true); - } else if (colName == "Category") { - setCategoryModal(true); - } else if (colName == "Attribute") { - setAttributeModal(true); + switch (colName) { + case "Classification": + setTagModal(true); + break; + case "Term": + setTermModal(true); + break; + case "Attribute": + setAttributeModal(true); + break; + default: + break; } }} > @@ -564,35 +567,33 @@ const DialogShowMoreLess = ({ setRowSelection={undefined} /> )} + {termModal && colName == "Term" && !relatedTerm && ( - )} {termModal && colName == "Term" && relatedTerm && ( - - )} - - {categoryModal && colName == "Category" && ( - )} diff --git a/dashboard/src/components/Table/TableFilters.tsx b/dashboard/src/components/Table/TableFilters.tsx index 94d424296be..bf638c39304 100644 --- a/dashboard/src/components/Table/TableFilters.tsx +++ b/dashboard/src/components/Table/TableFilters.tsx @@ -46,7 +46,8 @@ import Filters from "@components/QueryBuilder/Filters"; import { attributeFilter } from "@utils/CommonViewFunction"; import { toast } from "react-toastify"; import { downloadSearchResultsCSV } from "@api/apiMethods/downloadApiMethod"; -import AssignTerm from "@views/Glossary/AssignTerm"; +import AssignGlossaryItem from "@views/Glossary/AssignGlossaryItem"; +import { assignTermstoEntites } from "@api/apiMethods/glossaryApiMethod"; export const StyledMenu = styled((props: MenuProps) => (
@@ -497,14 +498,17 @@ export const TableFilter = ({ )} {termModal && ( - )} diff --git a/dashboard/src/components/TreeNodeIcons.tsx b/dashboard/src/components/TreeNodeIcons.tsx index e566240300d..1387deb5e41 100644 --- a/dashboard/src/components/TreeNodeIcons.tsx +++ b/dashboard/src/components/TreeNodeIcons.tsx @@ -49,13 +49,41 @@ import DeleteGlossary from "@views/Glossary/DeleteGlossary"; import AddUpdateCategoryForm from "@views/Glossary/AddUpdateCategoryForm"; import MoreHorizOutlinedIcon from "@mui/icons-material/MoreHorizOutlined"; +const NodeMenuIconButton = ({ + onClick, + visible, + className = "tree-item-more-label", + iconClassName = "", + ...props +}: { + onClick: (e: React.MouseEvent) => void; + visible: boolean; + className?: string; + iconClassName?: string; + [key: string]: any; +}) => ( + + + +); + const TreeNodeIcons = (props: { node: any; treeName: string; updatedData: any; isEmptyServicetype: boolean | undefined; + hovered: any; }) => { - const { node, treeName, updatedData, isEmptyServicetype } = props; + const { node, treeName, updatedData, isEmptyServicetype, hovered } = props; const navigate = useNavigate(); const toastId: any = useRef(null); const [expandNode, setExpandNode] = useState(null); @@ -65,7 +93,7 @@ const TreeNodeIcons = (props: { const { savedSearchData }: any = useAppSelector( (state: any) => state.savedSearch ); - const [deleteTagModal, setdeleteTagModal] = useState(false); + const [deleteTagModal, setDeleteTagModal] = useState(false); const [deleteGlossaryModal, setDeleteGlossaryModal] = useState(false); const [tagModal, setTagModal] = useState(false); @@ -96,7 +124,7 @@ const TreeNodeIcons = (props: { }; const handleCloseDeleteTagModal = () => { - setdeleteTagModal(false); + setDeleteTagModal(false); }; const handleCloseDeleteGlossaryModal = () => { @@ -162,6 +190,7 @@ const TreeNodeIcons = (props: { serverError(error, toastId); } }; + return ( <> {(node.types == "child" || node.types == undefined) && @@ -174,16 +203,10 @@ const TreeNodeIcons = (props: { (treeName == "CustomFilters" || treeName == "Classifications" || treeName == "Glossary") && ( - { - handleClickNodeMenu(e); - }} - size="small" - className="tree-item-more-label" - data-cy="dropdownMenuButton" - > - - + )} {(((treeName == "Classifications" || treeName == "Glossary") && @@ -193,16 +216,11 @@ const TreeNodeIcons = (props: { (node.types == "child" && !isEmpty(node.children) && node.cGuid != undefined)) && ( - { - handleClickNodeMenu(e); - }} - className="tree-item-more-label" - size="small" - data-cy="dropdownMenuButton" - > - - + )} { @@ -226,6 +244,7 @@ const TreeNodeIcons = (props: { { e.stopPropagation(); + setExpandNode(null); if ( treeName == "Classifications" && !addOnClassification.includes(node.id) @@ -272,6 +291,7 @@ const TreeNodeIcons = (props: { (treeName == "Glossary" && isEmptyServicetype)) && ( { + setExpandNode(null); if (treeName == "Classifications") { const searchParams = new URLSearchParams(); searchParams.set( @@ -284,7 +304,6 @@ const TreeNodeIcons = (props: { }`, search: searchParams.toString() }); - setExpandNode(null); } if (treeName == "Glossary" && node.types == "parent") { setGlossaryModal(true); @@ -301,7 +320,6 @@ const TreeNodeIcons = (props: { pathname: `/glossary/${node.cGuid}`, search: searchParams.toString() }); - setExpandNode(null); } }} data-cy="createClassification" @@ -330,12 +348,11 @@ const TreeNodeIcons = (props: { (treeName == "Glossary" && node.types == "child" && isEmptyServicetype)) && ( - // && - // !isEmpty(gtype) { + setExpandNode(null); if (treeName == "Classifications") { - setdeleteTagModal(true); + setDeleteTagModal(true); } if (treeName == "Glossary") { setDeleteGlossaryModal(true); @@ -513,7 +530,6 @@ const TreeNodeIcons = (props: { @@ -522,7 +538,6 @@ const TreeNodeIcons = (props: { diff --git a/dashboard/src/models/treeStructureType.ts b/dashboard/src/models/treeStructureType.ts index 2980cb9cfcb..690a8cdbbb7 100644 --- a/dashboard/src/models/treeStructureType.ts +++ b/dashboard/src/models/treeStructureType.ts @@ -27,6 +27,7 @@ export interface TypeHeaderState { } export interface TreeNode { + text?: string | null; id: string; label: string; children?: TreeNode[]; diff --git a/dashboard/src/utils/CommonViewFunction.ts b/dashboard/src/utils/CommonViewFunction.ts index e0f345b344e..4c2495d98da 100644 --- a/dashboard/src/utils/CommonViewFunction.ts +++ b/dashboard/src/utils/CommonViewFunction.ts @@ -17,6 +17,7 @@ * limitations under the License. */ +import { useMemo } from "react"; import { extractFromUrlForSearch, queryBuilderApiOperatorToUI, @@ -26,6 +27,7 @@ import { } from "./Enum"; import { convertToValidDate, + customSortBy, formatedDate, getUrlState, isEmpty, @@ -305,3 +307,40 @@ export const generateObjectForSaveSearchApi = (options) => { return obj; } }; + +export const getGlossaryChildrenData = (serviceTypeData: any[]) => { + const child = (childs: any): any[] => { + if (isEmpty(childs)) return []; + + return customSortBy( + childs.map((obj: any) => { + const { name = "", children, types = "", parent = "" } = obj; + return { + id: name, + label: name, + text: name, + children: !isEmpty(children) ? child(children.filter(Boolean)) : [], + types, + parent, + guid: obj?.guid || "", + cGuid: obj?.cGuid || "" + }; + }), + ["label"] + ); + }; + + return serviceTypeData.map((entity: any) => { + const key = Object.keys(entity || {})?.[0] ?? {}; + const value = entity?.[key] ?? {}; + return { + id: value?.name ?? "", + label: value?.name ?? "", + text: value?.name ?? "", + children: child(value?.children ?? []), + types: value?.types ?? "", + parent: value?.parent ?? "", + guid: value?.guid ?? "" + }; + }); +}; diff --git a/dashboard/src/utils/Utils.ts b/dashboard/src/utils/Utils.ts index dec30ec04dc..4e3705181cc 100644 --- a/dashboard/src/utils/Utils.ts +++ b/dashboard/src/utils/Utils.ts @@ -440,7 +440,13 @@ let groupBy = function (xs: any[], key: string) { }; const noTreeData = () => { - return [{ id: "No Records Found", label: "No Records Found" }]; + return [ + { + id: "No Records Found", + label: "No Records Found", + text: "No Records Found" + } + ]; }; const sanitizeHtmlContent = (htmlContent: HTMLElement | string | any) => { @@ -797,6 +803,16 @@ const globalSearchParams = { dslParams: {} }; +const showToastError = (msg: string, toastId: any) => { + toast.dismiss(toastId.current); + toastId.current = toast.error(msg); +}; + +const showToastSuccess = (msg: string, toastId: any) => { + toast.dismiss(toastId.current); + toastId.current = toast.success(msg); +}; + export { customSortBy, customSortByObjectKeys, @@ -835,5 +851,7 @@ export { setNavigate, getNavigate, globalSearchParams, - globalSearchFilterInitialQuery + globalSearchFilterInitialQuery, + showToastError, + showToastSuccess }; diff --git a/dashboard/src/views/Classification/DeleteTag.tsx b/dashboard/src/views/Classification/DeleteTag.tsx index bdcd7820ee1..adf918ca380 100644 --- a/dashboard/src/views/Classification/DeleteTag.tsx +++ b/dashboard/src/views/Classification/DeleteTag.tsx @@ -29,11 +29,10 @@ import { toast } from "react-toastify"; const DeleteTag = (props: { open: boolean; onClose: () => void; - setExpandNode: any; node: any; updatedData: any; }) => { - const { open, onClose, setExpandNode, node, updatedData } = props; + const { open, onClose, node, updatedData } = props; const navigate = useNavigate(); const dispatchApi = useAppDispatch(); const toastId: any = useRef(null); @@ -57,9 +56,9 @@ const DeleteTag = (props: { { replace: true } ); onClose(); - setExpandNode(null); } catch (error) { setLoader(false); + onClose(); console.log(`Error occur while removing ${node.id}`, error); serverError(error, toastId); } diff --git a/dashboard/src/views/DetailPage/DetailPageAttributes.tsx b/dashboard/src/views/DetailPage/DetailPageAttributes.tsx index c04e5794788..28fd39907f2 100644 --- a/dashboard/src/views/DetailPage/DetailPageAttributes.tsx +++ b/dashboard/src/views/DetailPage/DetailPageAttributes.tsx @@ -15,6 +15,8 @@ * limitations under the License. */ +import { useState } from "react"; +import { useParams, useSearchParams } from "react-router-dom"; import { CustomButton, LightTooltip } from "@components/muiComponents"; import SkeletonLoader from "@components/SkeletonLoader"; import { @@ -25,27 +27,100 @@ import { ToggleButtonGroup, Typography } from "@mui/material"; +import EditOutlinedIcon from "@mui/icons-material/EditOutlined"; +import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline"; +import { StyledPaper } from "@utils/Muiutils"; import { extractKeyValueFromEntity, isEmpty, sanitizeHtmlContent } from "@utils/Utils"; -import { useState } from "react"; -import EditOutlinedIcon from "@mui/icons-material/EditOutlined"; import { removeClassification } from "@api/apiMethods/classificationApiMethod"; -import { useParams, useSearchParams } from "react-router-dom"; +import { + assignGlossaryType, + assignTermstoEntites, + removeTermorCategory +} from "@api/apiMethods/glossaryApiMethod"; import ClassificationForm from "@views/Classification/ClassificationForm"; import AddUpdateTermForm from "@views/Glossary/AddUpdateTermForm"; import AddUpdateCategoryForm from "@views/Glossary/AddUpdateCategoryForm"; -import { removeTermorCategory } from "@api/apiMethods/glossaryApiMethod"; -import { StyledPaper } from "@utils/Muiutils"; import ShowMoreView from "@components/ShowMore/ShowMoreView"; -import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline"; import AddTag from "@views/Classification/AddTag"; import AddTagAttributes from "@views/Classification/AddTagAttributes"; -import AssignCategory from "@views/Glossary/AssignCategory"; -import AssignTerm from "@views/Glossary/AssignTerm"; import ShowMoreText from "@components/ShowMore/ShowMoreText"; +import AddUpdateGlossaryForm from "@views/Glossary/AddUpdateGlossaryForm"; +import AssignGlossaryItem from "@views/Glossary/AssignGlossaryItem"; + +const initialModalState = { + tag: false, + editTerm: false, + editCategory: false, + glossary: false, + addTag: false, + attribute: false, + addTerm: false, + category: false +}; + +const Loader = () => ( + + + +); + +const Section = ({ + title, + tooltip, + onAdd, + dataKey, + showMoreProps, + loading, + children, + data +}: any) => ( + + + + {title} + + {tooltip && ( + + + + + + )} + + + {loading ? ( + + ) : ( + children || ( + + ) + )} + + +); const DetailPageAttribute = ({ data, @@ -60,28 +135,11 @@ const DetailPageAttribute = ({ const { guid, tagName, bmguid } = useParams(); const gtypeParams = searchParams.get("gtype"); const [alignment, setAlignment] = useState("formatted"); - const [tagModal, setTagModal] = useState(false); - const [editTermModal, setEditTermModal] = useState(false); - const [editCategoryModal, setEditCategoryModal] = useState(false); - - const [openAddTagModal, setOpenAddTagModal] = useState(false); - const [attributeModal, setAttributeModal] = useState(false); - const [openAddTermModal, setOpenAddTermModal] = useState(false); + const [modals, setModals] = useState(initialModalState); - const [categoryModal, setCategoryModal] = useState(false); + const handleModal = (modal: keyof typeof initialModalState, open: boolean) => + setModals((prev) => ({ ...prev, [modal]: open })); - const handleCloseTermModal = () => { - setOpenAddTermModal(false); - }; - const handleCloseAddTagModal = () => { - setOpenAddTagModal(false); - }; - const handleCloseCategoryModal = () => { - setCategoryModal(false); - }; - const handleCloseAttributeModal = () => { - setAttributeModal(false); - }; const handleChange = ( _event: React.MouseEvent, newAlignment: string @@ -89,18 +147,6 @@ const DetailPageAttribute = ({ setAlignment(newAlignment); }; - const handleCloseTagModal = () => { - setTagModal(false); - }; - - const handleCloseEditTermModal = () => { - setEditTermModal(false); - }; - - const handleCloseEditCategoryModal = () => { - setEditCategoryModal(false); - }; - const { name }: { name: string; found: boolean; key: any } = extractKeyValueFromEntity(data); @@ -138,16 +184,24 @@ const DetailPageAttribute = ({ size="small" onClick={() => { if (!isEmpty(tagName)) { - setTagModal(true); - } - if (!isEmpty(guid) && gtypeParams == "term") { - setEditTermModal(true); - } - if (!isEmpty(guid) && gtypeParams == "category") { - setEditCategoryModal(true); + handleModal("tag", true); + } else if (!isEmpty(guid)) { + switch (gtypeParams) { + case "glossary": + handleModal("glossary", true); + break; + case "term": + handleModal("editTerm", true); + break; + case "category": + handleModal("editCategory", true); + break; + default: + break; + } } }} - data-cy="addTag" + data-cy="editButton" > @@ -168,7 +222,7 @@ const DetailPageAttribute = ({ }} > {" "} - {shortDescription != undefined && ( + {shortDescription !== undefined && ( <> - {shortDescription != undefined ? `Long` : ""} Description + {shortDescription !== undefined ? `Long` : ""} Description - {alignment == "formatted" ? ( + {alignment === "formatted" ? (
- {!isEmpty(gtypeParams) && - (gtypeParams == "term" || gtypeParams == "category") && - (loading ? ( - - - - ) : ( - gtypeParams != "category" && ( - - - - Classifications - - - { - setOpenAddTagModal(true); - }} - > - {" "} - - - - - - - - ) - ))} - {!isEmpty(gtypeParams) && - gtypeParams == "category" && - (loading ? ( - - - - ) : ( - !isEmpty(guid) && - gtypeParams == "category" && ( - - - - Terms - - - { - setOpenAddTermModal(true); - }} - > - {" "} - - - - - - - - ) - ))} - {!isEmpty(gtypeParams) && - gtypeParams == "term" && - (loading ? ( - - - - ) : ( - !isEmpty(guid) && - gtypeParams == "term" && ( - - - - Categories - - - { - setCategoryModal(true); - }} - > - {" "} - - - - - - - - ) - ))} + {!isEmpty(gtypeParams) && gtypeParams == "term" && ( +
handleModal("addTag", true)} + dataKey="classifications" + showMoreProps={{ + maxVisible: 4, + title: "Classifications", + displayKey: "typeName", + removeApiMethod: removeClassification, + removeTagsTitle: "Remove Classification Assignment", + id: "Classifications" + }} + loading={loading} + data={data} + /> + )} + {!isEmpty(gtypeParams) && gtypeParams === "category" && ( +
handleModal("addTerm", true)} + dataKey="terms" + showMoreProps={{ + maxVisible: 4, + title: "Terms", + displayKey: "displayText", + removeApiMethod: removeTermorCategory, + removeTagsTitle: "Remove Term Assignment", + id: "Terms" + }} + loading={loading} + data={data} + /> + )} + {!isEmpty(gtypeParams) && gtypeParams === "term" && ( +
handleModal("category", true)} + dataKey="categories" + showMoreProps={{ + maxVisible: 4, + title: "Category", + displayKey: "displayText", + removeApiMethod: removeTermorCategory, + removeTagsTitle: "Remove Category Assignment", + id: "Category" + }} + loading={loading} + data={data} + /> + )} {!isEmpty(superTypes) && ( - <> - {loading ? ( - - - - ) : ( - - - - Direct super-classifications - - - - - - - )} - +
)} {!isEmpty(subTypes) && ( - <> - {loading ? ( - - - - ) : ( - - - - Direct sub-classifications - - - - - - - )} - +
)} - {attributeDefs != undefined && ( - <> - <> - {loading ? ( - - - - ) : ( - - - - Attributes: - - - { - setAttributeModal(true); - }} - > - {" "} - - - - - - - - - )} - - + {attributeDefs !== undefined && ( +
handleModal("attribute", true)} + dataKey="attributeDefs" + showMoreProps={{ + maxVisible: 4, + title: "Attributes", + displayKey: "name", + id: "Attributes" + }} + loading={loading} + data={data} + /> )}
- {tagModal && ( + {modals.tag && ( handleModal("tag", false)} + setTagModal={undefined} /> )} - {openAddTagModal && ( + {modals.addTag && ( handleModal("addTag", false)} setUpdateTable={undefined} setRowSelection={undefined} /> )} - {editTermModal && ( + {modals.editTerm && ( handleModal("editTerm", false)} node={undefined} dataObj={data} /> )} - {editCategoryModal && ( + {modals.editCategory && ( handleModal("editCategory", false)} node={undefined} dataObj={data} /> )} - {attributeModal && ( + {modals.attribute && ( handleModal("attribute", false)} /> )} - {categoryModal && ( - handleModal("category", false)} data={data || {}} updateTable={undefined} + relatedItem={false} + itemType="category" + dataKey="categories" + assignApiMethod={assignGlossaryType} + treeLabel="Category" /> )} - {openAddTermModal && ( - handleModal("addTerm", false)} data={data || {}} updateTable={undefined} - relatedTerm={undefined} + relatedItem={undefined} + itemType="term" + dataKey="terms" + assignApiMethod={assignTermstoEntites} + treeLabel="Term" + /> + )} + + {modals.glossary && ( + handleModal("glossary", false)} + node={data || {}} /> )} diff --git a/dashboard/src/views/DetailPage/EntityDetailPage.tsx b/dashboard/src/views/DetailPage/EntityDetailPage.tsx index 6d88d31b02e..868c31d9cc6 100644 --- a/dashboard/src/views/DetailPage/EntityDetailPage.tsx +++ b/dashboard/src/views/DetailPage/EntityDetailPage.tsx @@ -45,11 +45,14 @@ import { cloneDeep } from "@utils/Helper"; import { fetchDetailPageData } from "@redux/slice/detailPageSlice"; import React from "react"; import AddTag from "@views/Classification/AddTag"; -import AssignTerm from "@views/Glossary/AssignTerm"; -import { removeTerm } from "@api/apiMethods/glossaryApiMethod"; +import { + assignTermstoEntites, + removeTerm +} from "@api/apiMethods/glossaryApiMethod"; import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline"; import ShowMoreView from "@components/ShowMore/ShowMoreView"; import LineageTab from "./EntityDetailTabs/LineageTab"; +import AssignGlossaryItem from "@views/Glossary/AssignGlossaryItem"; const EntityDetailPage: React.FC = () => { const { guid } = useParams(); @@ -538,13 +541,18 @@ const EntityDetailPage: React.FC = () => { setRowSelection={undefined} /> )} + {openAddTermModal && ( - )} diff --git a/dashboard/src/views/DetailPage/GlossaryDetails/TermRelation.tsx b/dashboard/src/views/DetailPage/GlossaryDetails/TermRelation.tsx index b6e53d44e19..97585ab99cb 100644 --- a/dashboard/src/views/DetailPage/GlossaryDetails/TermRelation.tsx +++ b/dashboard/src/views/DetailPage/GlossaryDetails/TermRelation.tsx @@ -22,10 +22,7 @@ import VisibilityIcon from "@mui/icons-material/Visibility"; import EditOutlinedIcon from "@mui/icons-material/EditOutlined"; import { termRelationAttributeList } from "@utils/Enum"; import DialogShowMoreLess from "@components/DialogShowMoreLess"; -import { - assignGlossaryType, - removeTerm -} from "@api/apiMethods/glossaryApiMethod"; +import { assignGlossaryType } from "@api/apiMethods/glossaryApiMethod"; import CustomModal from "@components/Modal"; import { isEmpty, serverError } from "@utils/Utils"; import { TableLayout } from "@components/Table/TableLayout"; @@ -126,7 +123,7 @@ const TermRelation = ({ glossaryTypeData }: any) => { colName="Term" relatedTerm={true} displayText="qualifiedName" - removeApiMethod={removeTerm} + removeApiMethod={assignGlossaryType} isShowMoreLess={false} detailPage={true} /> diff --git a/dashboard/src/views/Glossary/AssignCategory.tsx b/dashboard/src/views/Glossary/AssignCategory.tsx deleted file mode 100644 index 68fc0ce84bd..00000000000 --- a/dashboard/src/views/Glossary/AssignCategory.tsx +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 { assignGlossaryType } from "@api/apiMethods/glossaryApiMethod"; -import FormTreeView from "@components/Forms/FormTreeView"; -import CustomModal from "@components/Modal"; -import { useAppDispatch, useAppSelector } from "@hooks/reducerHook"; -import { - ChildrenInterface, - ChildrenInterfaces, - ServiceTypeInterface -} from "@models/entityTreeType"; -import { - EnumCategoryRelation, - EnumCategoryRelations -} from "@models/glossaryTreeType"; -import { Stack, TextField } from "@mui/material"; -import { fetchDetailPageData } from "@redux/slice/detailPageSlice"; -import { fetchGlossaryDetails } from "@redux/slice/glossaryDetailsSlice"; -import { fetchGlossaryData } from "@redux/slice/glossarySlice"; -import { cloneDeep } from "@utils/Helper"; -import { - customSortBy, - customSortByObjectKeys, - isEmpty, - noTreeData, - serverError -} from "@utils/Utils"; -import moment from "moment-timezone"; -import { useMemo, useRef, useState } from "react"; -import { useLocation, useParams } from "react-router-dom"; -import { toast } from "react-toastify"; - -const AssignCategory = ({ - open, - onClose, - data, - updateTable -}: { - open: boolean; - onClose: () => void; - data: any; - updateTable: any; -}) => { - const { categories } = data; - const dispatchApi = useAppDispatch(); - const location = useLocation(); - const { guid: entityGuid } = useParams(); - const searchParams = new URLSearchParams(location.search); - const gType = searchParams.get("gtype"); - const toastId: any = useRef(null); - const categoryNames = !isEmpty(categories) - ? categories?.map((obj: { displayText: string }) => { - return obj.displayText; - }) - : []; - const { glossaryData, loader }: any = useAppSelector( - (state: any) => state.glossary - ); - const [loading, setLoading] = useState(false); - const [searchTerm, setSearchTerm] = useState(""); - const [selectedNode, setSelectedNode] = useState(null); - - const glossary = [...glossaryData]; - - const updatedGlossary = glossary.map((gloss) => { - if (isEmpty(gloss?.catgeories)) { - return gloss; - } - - return { - ...gloss, - categories: gloss?.categories?.filter( - (category: { displayText: string }) => - !categoryNames.includes(category.displayText) - ) - }; - }); - - let newServiceTypeArr: any = []; - - newServiceTypeArr = - updatedGlossary != null - ? updatedGlossary.map( - (glossary: { - name: string; - subTypes: []; - guid: string; - superTypes: string[]; - categories: EnumCategoryRelation[]; - terms: EnumCategoryRelation[]; - }) => { - let categoryRelation: EnumCategoryRelation[] = []; - - glossary?.categories?.map((obj: EnumCategoryRelations) => { - if (obj.parentCategoryGuid != undefined) { - categoryRelation.push(obj as EnumCategoryRelations); - } - }); - - const getChildren = (glossaries: { - children: EnumCategoryRelation[]; - parent: string; - }) => { - return !isEmpty(glossaries.children) - ? glossaries.children - .map((glossariesType: any) => { - const getChild = () => { - return categoryRelation - .map((obj: EnumCategoryRelations) => { - if ( - obj.parentCategoryGuid == - glossariesType.categoryGuid - ) { - return { - ["name"]: obj.displayText, - id: obj.displayText, - ["children"]: [], - types: "child", - parent: glossaries.parent, - cGuid: obj.categoryGuid, - guid: glossary.guid - }; - } - }) - .filter(Boolean); - }; - if (glossariesType.parentCategoryGuid == undefined) { - return { - ["name"]: glossariesType.displayText, - id: glossariesType.displayText, - ["children"]: getChild(), - types: "child", - parent: glossaries.parent, - cGuid: glossariesType.categoryGuid, - guid: glossary.guid - }; - } - }) - .filter(Boolean) - : []; - }; - - let name: string = glossary.name, - children: any = getChildren({ - children: glossary?.categories, - parent: glossary.name - }); - - return { - [name]: { - ["name"]: name, - ["children"]: children || [], - id: glossary.guid, - types: "parent", - parent: name, - guid: glossary.guid - } - }; - } - ) - : []; - - const generateChildrenData = useMemo(() => { - const child = (childs: any) => { - return customSortBy( - childs.map((obj: ChildrenInterface) => { - return { - id: obj?.name, - label: obj?.name, - children: - obj?.children != undefined - ? child( - obj.children.filter( - Boolean - ) as unknown as ChildrenInterfaces[] - ) - : [], - types: obj?.types, - parent: obj?.parent, - guid: obj?.guid, - cGuid: obj?.cGuid - }; - }), - ["label"] - ); - }; - - return (serviceTypeData: ServiceTypeInterface[]) => - serviceTypeData.map((entity: any) => ({ - id: entity[Object.keys(entity)[0]].name, - label: entity[Object.keys(entity)[0]].name, - children: child( - entity[Object.keys(entity)[0]].children as ChildrenInterfaces[] - ), - types: entity[Object.keys(entity)[0]].types, - parent: entity[Object.keys(entity)[0]].parent, - guid: entity[Object.keys(entity)[0]].guid - })); - }, []); - - const treeData = useMemo(() => { - return !isEmpty(updatedGlossary) - ? generateChildrenData( - customSortByObjectKeys(newServiceTypeArr as ServiceTypeInterface[]) - ) - : noTreeData(); - }, []); - - const handleNodeSelect = (nodeId: any) => { - setSelectedNode(nodeId); - }; - - const assignCatgeory = async () => { - if (isEmpty(selectedNode)) { - toast.dismiss(toastId.current); - toastId.current = toast.error(`No Term Selected`); - return; - } - let selectedTerm: any = selectedNode; - let termGlossaryNames = selectedTerm.split("@"); - let termName: string = termGlossaryNames[0]; - let glossaryName: string = termGlossaryNames[1]; - - let glossaryObj = updatedGlossary.find( - (obj: { name: string }) => obj.name == glossaryName - ); - let termObj = !isEmpty(glossaryObj?.categories) - ? glossaryObj?.categories.find( - (category: { displayText: string }) => - category.displayText == termName - ) - : {}; - - const { categoryGuid } = termObj || {}; - - try { - setLoading(true); - let categoryData: any = cloneDeep(data); - if (categoryData.categories) { - categoryData.categories.push({ categoryGuid: categoryGuid }); - } else { - categoryData.categories = [{ categoryGuid: categoryGuid }]; - } - - await assignGlossaryType(entityGuid as string, categoryData); - toast.success(`${"Category"} is associated successfully`); - setLoading(false); - onClose(); - if (!isEmpty(updateTable)) { - updateTable(moment.now()); - } - if (!isEmpty(entityGuid)) { - let params: any = { gtype: gType, guid: entityGuid }; - dispatchApi(fetchGlossaryData()); - dispatchApi(fetchGlossaryDetails(params)); - dispatchApi(fetchDetailPageData(entityGuid as string)); - } - } catch (error) { - setLoading(false); - console.log(`Error occur while assigning ${"Category"}`, error); - serverError(error, toastId); - } - }; - - return ( - <> - - - ) => { - let newValue: string = e.target.value; - setSearchTerm(newValue); - }} - /> - - - - - ); -}; - -export default AssignCategory; diff --git a/dashboard/src/views/Glossary/AssignGlossaryItem.tsx b/dashboard/src/views/Glossary/AssignGlossaryItem.tsx new file mode 100644 index 00000000000..ea890f20099 --- /dev/null +++ b/dashboard/src/views/Glossary/AssignGlossaryItem.tsx @@ -0,0 +1,349 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 React, { useRef, useState } from "react"; +import { useForm } from "react-hook-form"; +import { useLocation, useParams } from "react-router-dom"; +import { toast } from "react-toastify"; +import Stack from "@mui/material/Stack"; +import TextField from "@mui/material/TextField"; +import CustomModal from "@components/Modal"; +import FormTreeView from "@components/Forms/FormTreeView"; +import RelatedTermStepper from "./AssignRelatedTerm"; +import { + customSortByObjectKeys, + isEmpty, + noTreeData, + serverError, + showToastError, + showToastSuccess +} from "@utils/Utils"; +import moment from "moment-timezone"; +import { useAppDispatch, useAppSelector } from "@hooks/reducerHook"; +import { fetchDetailPageData } from "@redux/slice/detailPageSlice"; +import { fetchGlossaryDetails } from "@redux/slice/glossaryDetailsSlice"; +import { fetchGlossaryData } from "@redux/slice/glossarySlice"; +import { cloneDeep } from "@utils/Helper"; +import { + assignTermstoCategory, + assignTermstoEntites +} from "@api/apiMethods/glossaryApiMethod"; +import { getGlossaryChildrenData } from "@utils/CommonViewFunction"; + +const steps = ["Select Item", "Attributes"]; + +const AssignGlossaryItem = ({ + open, + onClose, + data, + updateTable, + relatedItem, + columnVal, + setRowSelection, + itemType, + dataKey, + assignApiMethod, + treeLabel +}: { + open: boolean; + onClose: () => void; + data: any; + updateTable: any; + relatedItem?: boolean; + columnVal?: string | undefined; + setRowSelection?: any; + itemType: "term" | "category"; + dataKey: string; + assignApiMethod: Function; + treeLabel: string; +}) => { + const dispatchApi = useAppDispatch(); + const location = useLocation(); + const toastId: any = useRef(null); + const searchParams = new URLSearchParams(location.search); + const gType = searchParams.get("gtype"); + const { guid: entityGuid } = useParams(); + const { glossaryData, loader }: any = useAppSelector( + (state: any) => state.glossary + ); + + const { guid, meanings } = data; + + const assignedNames = !isEmpty( + meanings || data[dataKey] || data?.[columnVal!] + ) + ? (meanings || data[dataKey] || data?.[columnVal!])?.map( + (obj: { displayText: string }) => { + return obj.displayText; + } + ) + : []; + + const [searchTerm, setSearchTerm] = useState(""); + const [selectedNode, setSelectedNode] = useState(null); + const [activeStep, setActiveStep] = useState(0); + const [completed, setCompleted] = useState<{ [k: number]: boolean }>({}); + const { + control, + handleSubmit, + formState: { isSubmitting } + } = useForm(); + + const totalSteps = () => steps?.length; + const completedSteps = () => Object.keys(completed)?.length; + const isLastStep = () => activeStep === totalSteps() - 1; + const allStepsCompleted = () => completedSteps() === totalSteps(); + const handleNext = () => { + if (isEmpty(selectedNode)) { + toast.error(`Please select ${treeLabel} for association`); + return; + } + const newActiveStep = + isLastStep() && !allStepsCompleted() + ? steps.findIndex((_step, i) => !(i in completed)) + : activeStep + 1; + setActiveStep(newActiveStep); + }; + const handleBack = () => setActiveStep((prev) => prev - 1); + const handleStep = (step: number) => () => setActiveStep(step); + const handleReset = () => { + setActiveStep(0); + setCompleted({}); + }; + + const updatedGlossary = !isEmpty(glossaryData) + ? glossaryData.map((gloss: any) => { + if (isEmpty(gloss[dataKey])) return gloss; + return { + ...gloss, + [dataKey]: gloss[dataKey].filter( + (item: { displayText: string }) => + !assignedNames.includes(item.displayText) + ) + }; + }) + : []; + + const newServiceTypeArr = !isEmpty(updatedGlossary) + ? updatedGlossary.map((glossary: any) => { + const { name = "", guid = "", [dataKey]: items = [] } = glossary; + return { + [name]: { + name, + children: (items || []).map((item: any) => ({ + name: item.displayText, + id: item.displayText, + children: [], + types: "child", + parent: name, + cGuid: item[`${itemType}Guid`], + guid + })), + id: guid, + types: "parent", + parent: name, + guid + } + }; + }) + : []; + + const treeData = !isEmpty(updatedGlossary) + ? getGlossaryChildrenData(customSortByObjectKeys(newServiceTypeArr)) + : noTreeData(); + + const handleNodeSelect = (nodeId: any) => { + setSelectedNode(nodeId); + }; + + const assignItem = async () => { + if (isEmpty(selectedNode)) { + showToastError(`No ${treeLabel} Selected`, toastId); + return; + } + const [itemName, glossaryName] = selectedNode!.split("@"); + const glossaryObj = !isEmpty(updatedGlossary) + ? updatedGlossary.find( + (obj: { name: string }) => obj.name === glossaryName + ) + : {}; + const items = glossaryObj ? glossaryObj[dataKey] : []; + const itemObj = !isEmpty(items) + ? items.find( + (item: { displayText: string }) => item.displayText === itemName + ) + : {}; + const itemGuid = itemObj ? itemObj[`${itemType}Guid`] : undefined; + + try { + let payload: any = cloneDeep(data); + if (itemType === "term") { + if (data.terms) { + payload.terms = [...data.terms, { termGuid: itemGuid }]; + } else { + payload.terms = [{ termGuid: itemGuid }]; + } + + if (gType == "category" && !isEmpty(entityGuid)) { + await assignTermstoCategory(entityGuid as string, payload); + } else { + payload = [{ ["guid"]: guid || entityGuid }]; + if (isEmpty(guid) && isEmpty(entityGuid)) { + payload = data.map((obj: { guid: any }) => { + return { guid: obj.guid }; + }); + } + await assignTermstoEntites(itemGuid, payload); + } + } else { + payload = { + ...data, + [dataKey]: [ + ...(data[dataKey] || []), + { [`${itemType}Guid`]: itemGuid } + ] + }; + await assignApiMethod(entityGuid as string, payload); + } + + showToastSuccess(`${treeLabel} is associated successfully`, toastId); + onClose(); + if (!isEmpty(updateTable)) updateTable(moment.now()); + if (!isEmpty(entityGuid)) { + dispatchApi(fetchDetailPageData(entityGuid as string)); + if (!isEmpty(gType)) { + let params: any = { gtype: gType, guid: entityGuid }; + dispatchApi(fetchGlossaryData()); + dispatchApi(fetchGlossaryDetails(params)); + } + } + if (!isEmpty(setRowSelection)) setRowSelection({}); + } catch (error) { + serverError(error, toastId); + } + }; + + const onSubmit = async (values: any) => { + let formData = { ...values }; + if (isEmpty(selectedNode)) { + showToastError(`No ${treeLabel} Selected`, toastId); + return; + } + if (activeStep === 0) { + showToastError("Please click on next step", toastId); + return; + } + const [itemName, glossaryName] = selectedNode!.split("@"); + const glossaryObj = !isEmpty(updatedGlossary) + ? updatedGlossary.find( + (obj: { name: string }) => obj.name === glossaryName + ) + : {}; + const items = glossaryObj ? glossaryObj[dataKey] : []; + const itemObj = !isEmpty(items) + ? items.find( + (item: { displayText: string }) => item.displayText === itemName + ) + : {}; + const itemGuid = itemObj ? itemObj[`${itemType}Guid`] : undefined; + + try { + let relatedData = { ...formData, [`${itemType}Guid`]: itemGuid }; + let itemData: any = cloneDeep(data); + columnVal && !isEmpty(itemData[columnVal]) + ? itemData[columnVal].push(relatedData) + : columnVal && (itemData[columnVal] = [relatedData]); + await assignApiMethod(entityGuid as string, itemData); + if (!isEmpty(updateTable)) updateTable(moment.now()); + if (!isEmpty(entityGuid)) { + let params: any = { gtype: gType, guid: entityGuid }; + dispatchApi(fetchGlossaryDetails(params)); + dispatchApi(fetchDetailPageData(entityGuid as string)); + } + showToastSuccess(`${treeLabel} is associated successfully`, toastId); + onClose(); + } catch (error) { + serverError(error, toastId); + } + }; + + const modalTitle = () => { + if (relatedItem) return `Assign ${treeLabel} to ${columnVal}`; + if (isEmpty(entityGuid) && !relatedItem) + return `Assign ${treeLabel} to entity`; + if (!isEmpty(entityGuid) && !isEmpty(gType) && !relatedItem) + return `Assign ${treeLabel} to Category`; + return `Assign ${treeLabel} to entity`; + }; + + return ( + + {relatedItem ? ( + + ) : ( + + ) => { + setSearchTerm(e.target.value); + }} + /> + + + )} + + ); +}; + +export default AssignGlossaryItem; diff --git a/dashboard/src/views/Glossary/AssignRelatedTerm.tsx b/dashboard/src/views/Glossary/AssignRelatedTerm.tsx new file mode 100644 index 00000000000..204251687d9 --- /dev/null +++ b/dashboard/src/views/Glossary/AssignRelatedTerm.tsx @@ -0,0 +1,250 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 Stepper from "@mui/material/Stepper"; +import Step from "@mui/material/Step"; +import StepButton from "@mui/material/StepButton"; +import Stack from "@mui/material/Stack"; +import Button from "@mui/material/Button"; +import Typography from "@mui/material/Typography"; +import FormTreeView from "@components/Forms/FormTreeView"; +import { TextField, InputLabel } from "@mui/material"; +import { Controller } from "react-hook-form"; + +const steps = ["Select Term", "Attributes"]; + +const TreeSearchInput = ({ + value, + onChange +}: { + value: string; + onChange: (v: string) => void; +}) => ( + ) => + onChange(e.target.value) + } + /> +); + +const FormInputField = ({ + control, + name, + label, + placeholder +}: { + control: any; + name: string; + label: string; + placeholder: string; +}) => ( + + ( + <> +
+ {label} +
+ onChange(e.target.value)} + variant="outlined" + size="small" + placeholder={placeholder} + className="form-textfield" + /> + + )} + /> +
+); + +const StepperContent = ({ + activeStep, + allStepsCompleted, + handleReset, + searchTerm, + setSearchTerm, + treeData, + loader, + handleNodeSelect, + control, + handleSubmit, + onSubmit +}: any) => ( + + {allStepsCompleted() ? ( + <> + + All steps completed - you're finished + + + + + + + ) : ( + <> + {activeStep === 0 ? ( + <> + + + + ) : ( + +
+ + + + + + + + + + + + +
+
+ )} + + )} +
+); + +const AssignRelatedTerm = ({ + activeStep, + completed, + handleStep, + handleBack, + handleNext, + handleReset, + allStepsCompleted, + searchTerm, + setSearchTerm, + treeData, + loader, + handleNodeSelect, + control, + handleSubmit, + onSubmit +}: any) => ( + + + {steps.map((label, index) => ( + + + {label} + + + ))} + + + + + + + + +); + +export default AssignRelatedTerm; diff --git a/dashboard/src/views/Glossary/AssignTerm.tsx b/dashboard/src/views/Glossary/AssignTerm.tsx deleted file mode 100644 index e7525a12c08..00000000000 --- a/dashboard/src/views/Glossary/AssignTerm.tsx +++ /dev/null @@ -1,696 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 { - assignGlossaryType, - assignTermstoCategory, - assignTermstoEntites -} from "@api/apiMethods/glossaryApiMethod"; -import FormTreeView from "@components/Forms/FormTreeView"; -import CustomModal from "@components/Modal"; -import { useAppDispatch, useAppSelector } from "@hooks/reducerHook"; -import { - ChildrenInterface, - ChildrenInterfaces, - ServiceTypeInterface -} from "@models/entityTreeType"; -import { - EnumCategoryRelation, - EnumCategoryRelations -} from "@models/glossaryTreeType"; -import Button from "@mui/material/Button"; - -import InputLabel from "@mui/material/InputLabel"; -import Stack from "@mui/material/Stack"; -import Step from "@mui/material/Step"; -import StepButton from "@mui/material/StepButton"; -import Stepper from "@mui/material/Stepper"; -import TextField from "@mui/material/TextField"; -import Typography from "@mui/material/Typography"; -import { fetchDetailPageData } from "@redux/slice/detailPageSlice"; -import { fetchGlossaryDetails } from "@redux/slice/glossaryDetailsSlice"; -import { fetchGlossaryData } from "@redux/slice/glossarySlice"; -import { cloneDeep } from "@utils/Helper"; -import { - customSortBy, - customSortByObjectKeys, - isEmpty, - noTreeData, - serverError -} from "@utils/Utils"; -import moment from "moment-timezone"; -import React from "react"; -import { useMemo, useRef, useState } from "react"; -import { Controller, useForm } from "react-hook-form"; -import { useLocation, useParams } from "react-router-dom"; -import { toast } from "react-toastify"; - -const steps = ["Select Term", "Attributes"]; - -const AssignTerm = ({ - open, - onClose, - data, - updateTable, - relatedTerm, - columnVal, - setRowSelection -}: { - open: boolean; - onClose: () => void; - data: any; - updateTable: any; - relatedTerm: any; - columnVal?: any; - setRowSelection?: any; -}) => { - const { guid, meanings, terms } = data; - const dispatchApi = useAppDispatch(); - const location = useLocation(); - const toastId: any = useRef(null); - const searchParams = new URLSearchParams(location.search); - const gType = searchParams.get("gtype"); - const { guid: entityGuid } = useParams(); - const termNames = !isEmpty(meanings || terms || data[columnVal]) - ? (meanings || terms || data[columnVal])?.map( - (obj: { displayText: string }) => { - return obj.displayText; - } - ) - : []; - const { glossaryData, loader }: any = useAppSelector( - (state: any) => state.glossary - ); - const [searchTerm, setSearchTerm] = useState(""); - const [selectedNode, setSelectedNode] = useState(null); - const [activeStep, setActiveStep] = React.useState(0); - const [completed, setCompleted] = React.useState<{ - [k: number]: boolean; - }>({}); - const { - control, - handleSubmit, - formState: { isSubmitting } - } = useForm(); - - const glossary = [...glossaryData]; - - const totalSteps = () => { - return steps.length; - }; - - const completedSteps = () => { - return Object.keys(completed).length; - }; - - const isLastStep = () => { - return activeStep === totalSteps() - 1; - }; - - const allStepsCompleted = () => { - return completedSteps() === totalSteps(); - }; - - const handleNext = () => { - if (isEmpty(selectedNode)) { - toast.error("Please select Term for association"); - return; - } - const newActiveStep = - isLastStep() && !allStepsCompleted() - ? steps.findIndex((_step, i) => !(i in completed)) - : activeStep + 1; - setActiveStep(newActiveStep); - }; - - const handleBack = () => { - setActiveStep((prevActiveStep) => prevActiveStep - 1); - }; - - const handleStep = (step: number) => () => { - setActiveStep(step); - }; - - const handleReset = () => { - setActiveStep(0); - setCompleted({}); - }; - - const updatedGlossary = glossary.map((gloss) => { - if (isEmpty(gloss?.terms)) { - return gloss; - } - - return { - ...gloss, - terms: gloss?.terms?.filter( - (term: { displayText: string }) => !termNames.includes(term.displayText) - ) - }; - }); - - let newServiceTypeArr: any = []; - - newServiceTypeArr = - updatedGlossary != null - ? updatedGlossary.map( - (glossary: { - name: string; - subTypes: []; - guid: string; - superTypes: string[]; - categories: EnumCategoryRelation[]; - terms: EnumCategoryRelation[]; - }) => { - let categoryRelation: EnumCategoryRelation[] = []; - - glossary?.categories?.map((obj: EnumCategoryRelations) => { - if (obj.parentCategoryGuid != undefined) { - categoryRelation.push(obj as EnumCategoryRelations); - } - }); - - const getChildren = (glossaries: { - children: EnumCategoryRelation[]; - parent: string; - }) => { - return !isEmpty(glossaries.children) - ? glossaries.children - .map((glossariesType: any) => { - const getChild = () => { - return categoryRelation - .map((obj: EnumCategoryRelations) => { - if ( - obj.parentCategoryGuid == - glossariesType.categoryGuid - ) { - return { - ["name"]: obj.displayText, - id: obj.displayText, - ["children"]: [], - types: "child", - parent: glossaries.parent, - cGuid: obj.termGuid, - guid: glossary.guid - }; - } - }) - .filter(Boolean); - }; - if (glossariesType.parentCategoryGuid == undefined) { - return { - ["name"]: glossariesType.displayText, - id: glossariesType.displayText, - ["children"]: getChild(), - types: "child", - parent: glossaries.parent, - cGuid: glossariesType.termGuid, - guid: glossary.guid - }; - } - }) - .filter(Boolean) - : []; - }; - - let name: string = glossary.name, - children: any = getChildren({ - children: glossary?.terms, - parent: glossary.name - }); - - return { - [name]: { - ["name"]: name, - ["children"]: children || [], - id: glossary.guid, - types: "parent", - parent: name, - guid: glossary.guid - } - }; - } - ) - : []; - - const generateChildrenData = useMemo(() => { - const child = (childs: any) => { - return customSortBy( - childs.map((obj: ChildrenInterface) => { - return { - id: obj?.name, - label: obj?.name, - children: - obj?.children != undefined - ? child( - obj.children.filter( - Boolean - ) as unknown as ChildrenInterfaces[] - ) - : [], - types: obj?.types, - parent: obj?.parent, - guid: obj?.guid, - cGuid: obj?.cGuid - }; - }), - ["label"] - ); - }; - - return (serviceTypeData: ServiceTypeInterface[]) => - serviceTypeData.map((entity: any) => ({ - id: entity[Object.keys(entity)[0]].name, - label: entity[Object.keys(entity)[0]].name, - children: child( - entity[Object.keys(entity)[0]].children as ChildrenInterfaces[] - ), - types: entity[Object.keys(entity)[0]].types, - parent: entity[Object.keys(entity)[0]].parent, - guid: entity[Object.keys(entity)[0]].guid - })); - }, []); - - const treeData = useMemo(() => { - return !isEmpty(updatedGlossary) - ? generateChildrenData( - customSortByObjectKeys(newServiceTypeArr as ServiceTypeInterface[]) - ) - : noTreeData(); - }, []); - - const handleNodeSelect = (nodeId: any) => { - setSelectedNode(nodeId); - }; - - const assignTerm = async () => { - if (isEmpty(selectedNode)) { - toast.dismiss(toastId.current); - toastId.current = toast.error(`No Term Selected`); - return; - } - let selectedTerm: any = selectedNode; - let termGlossaryNames = selectedTerm.split("@"); - let termName: string = termGlossaryNames[0]; - let glossaryName: string = termGlossaryNames[1]; - - let glossaryObj = updatedGlossary.find( - (obj: { name: string }) => obj.name == glossaryName - ); - let termObj = !isEmpty(glossaryObj?.terms) - ? glossaryObj?.terms.find( - (term: { displayText: string }) => term.displayText == termName - ) - : {}; - - const { termGuid } = termObj || {}; - - try { - if (gType == "category" && !isEmpty(entityGuid)) { - let termData: any = cloneDeep(data); - if (data.terms) { - termData.terms = [...data.terms, { termGuid: termGuid }]; - } else { - termData.terms = [{ termGuid: termGuid }]; - } - - await assignTermstoCategory(entityGuid as string, termData); - } else { - let termData: any = [{ ["guid"]: guid || entityGuid }]; - - if (isEmpty(guid) && isEmpty(entityGuid)) { - termData = data.map((obj: { guid: any }) => { - return { guid: obj.guid }; - }); - } - await assignTermstoEntites(termGuid, termData); - } - toast.success(`Term is associated successfully`); - onClose(); - if (!isEmpty(updateTable)) { - updateTable(moment.now()); - } - - if (!isEmpty(entityGuid)) { - dispatchApi(fetchDetailPageData(entityGuid as string)); - - if (!isEmpty(gType)) { - const params = { gtype: gType, entityGuid }; - dispatchApi(fetchGlossaryData()); - dispatchApi(fetchGlossaryDetails(params)); - } - } - - if (!isEmpty(setRowSelection)) { - setRowSelection({}); - } - } catch (error) { - console.log(`Error occur while assigningTerm`, error); - serverError(error, toastId); - } - }; - - const onSubmit = async (values: any) => { - let formData = { ...values }; - - if (isEmpty(selectedNode)) { - toast.dismiss(toastId.current); - toastId.current = toast.error(`No Term Selected`); - return; - } - if (activeStep == 0) { - toast.dismiss(toastId.current); - toastId.current = toast.error("Please click on next step"); - return; - } - - let selectedTerm: any = selectedNode; - let termGlossaryNames = selectedTerm.split("@"); - let termName: string = termGlossaryNames[0]; - let glossaryName: string = termGlossaryNames[1]; - - let glossaryObj = updatedGlossary.find( - (obj: { name: string }) => obj.name == glossaryName - ); - let termObj = !isEmpty(glossaryObj?.terms) - ? glossaryObj?.terms.find( - (term: { displayText: string }) => term.displayText == termName - ) - : {}; - - const { termGuid } = termObj || {}; - - try { - let relatedTermData = { ...formData, ...{ termGuid: termGuid } }; - - let termData: any = cloneDeep(data); - !isEmpty(termData[columnVal]) - ? termData[columnVal].push(relatedTermData) - : (termData[columnVal] = [relatedTermData]); - await assignGlossaryType(entityGuid as string, termData); - if (!isEmpty(updateTable)) { - updateTable(moment.now()); - } - if (!isEmpty(entityGuid)) { - let params: any = { gtype: gType, guid: entityGuid }; - dispatchApi(fetchGlossaryDetails(params)); - dispatchApi(fetchDetailPageData(entityGuid as string)); - } - toast.dismiss(toastId.current); - toastId.current = toast.success(`Term is associated successfully`); - onClose(); - } catch (error) { - console.log(`Error occur while assigningTerm`, error); - serverError(error, toastId); - } - }; - - const termModalTitle = () => { - let type: string = ""; - if (relatedTerm) { - type = columnVal; - } - if (isEmpty(entityGuid) && !relatedTerm) { - type = "entity"; - } else if (!isEmpty(entityGuid) && !isEmpty(gType) && !relatedTerm) { - type = "Catgeory"; - } - - return `Assign term to ${type}`; - }; - - return ( - <> - - {relatedTerm ? ( - - - {steps.map((label, index) => ( - - - {label} - - - ))} - - - {allStepsCompleted() ? ( - - - All steps completed - you're finished - - - - - - - ) : ( - - {activeStep === 0 ? ( - <> - ) => { - let newValue: string = e.target.value; - setSearchTerm(newValue); - }} - /> - - - ) : ( - -
- - ( - <> -
- description -
- - { - const value = e.target.value; - onChange(value); - }} - variant="outlined" - size="small" - placeholder={"description"} - className="form-textfield" - /> - - )} - /> -
- - ( - <> -
- expression -
- - { - const value = e.target.value; - onChange(value); - }} - variant="outlined" - size="small" - placeholder={"expression"} - className="form-textfield" - /> - - )} - /> -
- - ( - <> -
- steward -
- - { - const value = e.target.value; - onChange(value); - }} - variant="outlined" - size="small" - placeholder={"steward"} - className="form-textfield" - /> - - )} - /> -
- - ( - <> -
- source -
- - { - const value = e.target.value; - onChange(value); - }} - variant="outlined" - size="small" - placeholder={"source"} - className="form-textfield" - /> - - )} - /> -
-
-
- )} - - - - - -
- )} -
-
- ) : ( - - ) => { - let newValue: string = e.target.value; - setSearchTerm(newValue); - }} - /> - - - )} -
- - ); -}; - -export default AssignTerm; diff --git a/dashboard/src/views/Glossary/DeleteGlossary.tsx b/dashboard/src/views/Glossary/DeleteGlossary.tsx index 4f04c8a902a..ec7acd5f1ac 100644 --- a/dashboard/src/views/Glossary/DeleteGlossary.tsx +++ b/dashboard/src/views/Glossary/DeleteGlossary.tsx @@ -32,11 +32,10 @@ import { toast } from "react-toastify"; const DeleteGlossary = (props: { open: boolean; onClose: () => void; - setExpandNode: any; node: any; updatedData: any; }) => { - const { open, onClose, setExpandNode, node, updatedData } = props; + const { open, onClose, node, updatedData } = props; const { id, guid, cGuid, types } = node; const gtype: string | undefined | null = types != "parent" ? "term" : "glossary"; @@ -74,9 +73,9 @@ const DeleteGlossary = (props: { ); } onClose(); - setExpandNode(null); } catch (error) { console.log(`Error occur while removing ${id}`, error); + onClose(); serverError(error, toastId); } }; diff --git a/dashboard/src/views/SearchResult/SearchResult.tsx b/dashboard/src/views/SearchResult/SearchResult.tsx index 90f0e55f4da..46e350b8cb3 100644 --- a/dashboard/src/views/SearchResult/SearchResult.tsx +++ b/dashboard/src/views/SearchResult/SearchResult.tsx @@ -765,17 +765,7 @@ const SearchResult = ({ classificationParams, glossaryTypeParams }: any) => { const getDefaultSort = useMemo(() => [{ id: "name", asc: true }], []); return ( - + {!isEmpty(classificationParams || glossaryTypeParams) && ( { const dispatch = useAppDispatch(); @@ -43,7 +37,7 @@ const GlossaryTree = ({ sideBarOpen, searchTerm }: Props) => { (state: any) => state.glossary ); const [glossaryType, setGlossaryType] = useState(true); - const [glossaryTypeData, setGlossaryData] = useState< + const [glossaryTypeData, setGlossaryTypeData] = useState< ServiceTypeArrType >([]); @@ -70,9 +64,14 @@ const GlossaryTree = ({ sideBarOpen, searchTerm }: Props) => { terms: EnumCategoryRelation[]; }) => { let categoryRelation: EnumCategoryRelation[] = []; - - glossary?.categories?.map((obj: EnumCategoryRelations) => { - if (obj.parentCategoryGuid != undefined) { + const { + categories = [], + terms = [], + name = "", + guid = "" + } = glossary; + categories?.map((obj: EnumCategoryRelations) => { + if (!isEmpty(obj.parentCategoryGuid)) { categoryRelation.push(obj as EnumCategoryRelations); } }); @@ -81,42 +80,51 @@ const GlossaryTree = ({ sideBarOpen, searchTerm }: Props) => { children: EnumCategoryRelation[]; parent: string; }) => { - return !isEmpty(glossaries.children) - ? glossaries.children + const { children = [], parent = "" } = glossaries; + return !isEmpty(children) + ? children .map((glossariesType: any) => { const getChild = () => { return categoryRelation .map((obj: EnumCategoryRelations) => { + const { + displayText = "", + termGuid = "", + categoryGuid = "", + parentCategoryGuid = "" + } = obj || {}; if ( - obj.parentCategoryGuid == - glossariesType.categoryGuid + parentCategoryGuid == + glossariesType?.categoryGuid ) { return { - ["name"]: obj.displayText, - id: obj.displayText, + ["name"]: displayText, + id: displayText, ["children"]: [], types: "child", - parent: glossaries.parent, - cGuid: glossaryType - ? obj.termGuid - : obj.categoryGuid, - guid: glossary.guid + parent: parent, + cGuid: glossaryType ? termGuid : categoryGuid, + guid: guid }; } }) .filter(Boolean); }; - if (glossariesType.parentCategoryGuid == undefined) { + const { + parentCategoryGuid = "", + displayText = "", + termGuid = "", + categoryGuid = "" + } = glossariesType || {}; + if (isEmpty(parentCategoryGuid)) { return { - ["name"]: glossariesType.displayText, - id: glossariesType.displayText, + ["name"]: displayText, + id: displayText, ["children"]: getChild(), types: "child", - parent: glossaries.parent, - cGuid: glossaryType - ? glossariesType.termGuid - : glossariesType.categoryGuid, - guid: glossary.guid + parent: parent, + cGuid: glossaryType ? termGuid : categoryGuid, + guid: guid }; } }) @@ -124,75 +132,36 @@ const GlossaryTree = ({ sideBarOpen, searchTerm }: Props) => { : []; }; - let name: string = glossary.name, - children: any = glossaryType - ? getChildren({ - children: glossary?.terms, - parent: glossary.name - }) - : getChildren({ - children: glossary?.categories, - parent: glossary.name - }); + let children: any = glossaryType + ? getChildren({ + children: terms, + parent: name + }) + : getChildren({ + children: categories, + parent: name + }); return { [name]: { - ["name"]: name, + ["name"]: name || "", ["children"]: children || [], - id: glossary.guid, + id: guid || "", types: "parent", - parent: name, - guid: glossary.guid + parent: name || "", + guid: guid || "" } }; } ) : []; - setGlossaryData(newServiceTypeArr); + setGlossaryTypeData(newServiceTypeArr); }, [glossaryData, glossaryType]); - const generateChildrenData = useMemo(() => { - const child = (childs: any) => { - return customSortBy( - childs.map((obj: ChildrenInterface) => { - return { - id: obj?.name, - label: obj?.name, - children: - obj?.children != undefined - ? child( - obj.children.filter( - Boolean - ) as unknown as ChildrenInterfaces[] - ) - : [], - types: obj?.types, - parent: obj?.parent, - guid: obj?.guid, - cGuid: obj?.cGuid - }; - }), - ["label"] - ); - }; - - return (serviceTypeData: ServiceTypeInterface[]) => - serviceTypeData.map((entity: any) => ({ - id: entity[Object.keys(entity)[0]].name, - label: entity[Object.keys(entity)[0]].name, - children: child( - entity[Object.keys(entity)[0]].children as ChildrenInterfaces[] - ), - types: entity[Object.keys(entity)[0]].types, - parent: entity[Object.keys(entity)[0]].parent, - guid: entity[Object.keys(entity)[0]].guid - })); - }, [glossaryType]); - const treeData = useMemo(() => { return !isEmpty(glossaryData) - ? generateChildrenData( + ? getGlossaryChildrenData( customSortByObjectKeys(glossaryTypeData as ServiceTypeInterface[]) ) : noTreeData(); diff --git a/dashboard/src/views/SideBar/SideBarTree/SideBarTree.tsx b/dashboard/src/views/SideBar/SideBarTree/SideBarTree.tsx index 125ceee9273..ba4526e06bc 100644 --- a/dashboard/src/views/SideBar/SideBarTree/SideBarTree.tsx +++ b/dashboard/src/views/SideBar/SideBarTree/SideBarTree.tsx @@ -64,7 +64,6 @@ import CircularProgress from "@mui/material/CircularProgress"; import { globalSearchFilterInitialQuery, isEmpty } from "@utils/Utils"; import LaunchOutlinedIcon from "@mui/icons-material/LaunchOutlined"; import { getGlossaryImportTmpl } from "@api/apiMethods/glossaryApiMethod"; -import { toast } from "react-toastify"; import { EnumTypeDefData, TreeNode } from "@models/treeStructureType"; import ImportDialog from "@components/ImportDialog"; import TreeIcons from "@components/Treeicons"; @@ -78,8 +77,11 @@ import { AntSwitch } from "@utils/Muiutils"; type CustomContentRootProps = HTMLAttributes & { selectedNodeType?: any; selectedNodeTag?: any; + selectedNodeGlossary?: any; + selectedNodeTerm?: any; selectedNodeRelationship?: any; selectedNodeBM?: any; + selectedNodeCF?: any; node?: any; selectedNode?: any; }; @@ -115,13 +117,14 @@ const CustomContentRoot = styled("div")( }, ...((props.selectedNodeType === props.node || props.selectedNodeTag === props.node || + props.selectedNodeGlossary === props.node || + props.selectedNodeTerm === props.node || props.selectedNodeRelationship === props.node || - props.selectedNodeBM === props.node) && { + props.selectedNodeBM === props.node || + props?.selectedNodeCF === props.node) && { "&.Mui-selected .MuiTreeItem-contentBar": { backgroundColor: "rgba(255,255,255,0.08)", borderLeft: "4px solid #2ccebb" - // color: "white" - // borderRadius: "4px" } }), ...(props?.selectedNode == props?.node && { @@ -148,7 +151,6 @@ const CustomContentRoot = styled("div")( } }, "&.Mui-selected.Mui-focused .MuiTreeItem-contentBar": { - // backgroundColor: "#0E8173" backgroundColor: "rgba(255,255,255,0.08)" } }) @@ -185,10 +187,13 @@ const CustomContent = forwardRef(function CustomContent( preventSelection(event); }; - const handleClick = (event: MouseEvent) => { + const handleExpansionClick = (event: MouseEvent) => { event.stopPropagation(); - event.preventDefault(); handleExpansion(event); + }; + + const handleLabelClick = (event: MouseEvent) => { + event.stopPropagation(); handleSelection(event); }; @@ -200,15 +205,18 @@ const CustomContent = forwardRef(function CustomContent( (isValidElement(props.label) && (props.label.props.selectedNodeType === props.label.props.node || props.label.props.selectedNodeTag === props.label.props.node || + props.label.props.selectedNodeGlossary === + props.label.props.node || + props.label.props.selectedNodeTerm === props.label.props.node || props.label.props.selectedNodeRelationship === props.label.props.node || - props.label.props.selectedNodeBM === props.label.props.node)) || + props.label.props.selectedNodeBM === props.label.props.node || + props.label.props.selectedNodeCF === props.label.props.node)) || selected, "Mui-focused": focused, "Mui-disabled": disabled })} sx={{ position: "relative" }} - // selectedNode={props.label.props.selectedNode} selectedNodeType={ isValidElement(props.label) ? props.label.props.selectedNodeType @@ -219,6 +227,16 @@ const CustomContent = forwardRef(function CustomContent( ? props.label.props.selectedNodeTag : undefined } + selectedNodeGlossary={ + isValidElement(props.label) + ? props.label.props.selectedNodeGlossary + : undefined + } + selectedNodeTerm={ + isValidElement(props.label) + ? props.label.props.selectedNodeTerm + : undefined + } selectedNodeRelationship={ isValidElement(props.label) ? props.label.props.selectedNodeRelationship @@ -229,16 +247,29 @@ const CustomContent = forwardRef(function CustomContent( ? props.label.props.selectedNodeBM : undefined } + selectedNodeCF={ + isValidElement(props.label) + ? props.label.props.selectedNodeCF + : undefined + } node={isValidElement(props.label) ? props.label.props.node : undefined} - onClick={(e) => { - handleClick(e); - }} onMouseDown={handleMouseDown} ref={ref as Ref} >
-
{icon}
- +
+ {icon} +
+ {label} @@ -254,7 +285,6 @@ const CustomTreeItem = memo( ({ type: null, tag: null, + glossary: null, + term: null, relationship: null, - businessMetadata: null + businessMetadata: null, + customFilters: null }); - + const [hoveredNodeId, setHoveredNodeId] = useState(null); const [openModal, setOpenModal] = useState(false); - const toastId: any = useRef(null); const open = Boolean(expand); const [expandedItems, setExpandedItems] = useState([]); const [tagModal, setTagModal] = useState(false); @@ -326,6 +361,10 @@ const BarTreeView: FC<{ (state: any) => state.businessMetaData ); + const { glossaryData = [] }: any = useAppSelector( + (state: any) => state.glossary + ); + const filteredData = useMemo(() => { return treeData.filter((node) => { return ( @@ -373,12 +412,26 @@ const BarTreeView: FC<{ const searchParams = new URLSearchParams(location.search); const nodeIdFromParamsType = searchParams.get("type"); const nodeIdFromParamsTag = searchParams.get("tag"); + const nodeIdFromParamsGlossary = searchParams.get("gtype") == "glossary"; + let nodeIdFromParamsTermRaw = searchParams.get("term"); + const nodeIdFromParamsTerm = nodeIdFromParamsTermRaw + ? nodeIdFromParamsTermRaw.split("@")[0] + : null; const nodeIdFromParamsRelationshipName = searchParams.get("relationshipName"); const nodeIdFromBMName = location.pathname.includes( "/administrator/businessMetadata" ); + const nodeIdFromCFName = + treeName == "CustomFilters" && searchParams.get("isCF"); + + const glossaryGuid = searchParams.get("gId"); + const glossaryName = !isEmpty(glossaryData) + ? glossaryData.find( + (obj: { guid: string | null }) => obj.guid === glossaryGuid + )?.name + : ""; const bmObj = !isEmpty(businessMetaData?.businessMetadataDefs) ? businessMetaData?.businessMetadataDefs?.find((obj: EnumTypeDefData) => { if (bmguid == obj.guid) { @@ -391,24 +444,33 @@ const BarTreeView: FC<{ setSelectedNode({ type: nodeIdFromParamsType, tag: nodeIdFromParamsTag, + glossary: nodeIdFromParamsGlossary ? glossaryName : null, + term: nodeIdFromParamsTerm, relationship: nodeIdFromParamsRelationshipName, - businessMetadata: nodeIdFromBMName ? name : null + businessMetadata: nodeIdFromBMName ? name : null, + customFilters: null }); if ( !nodeIdFromParamsType && !nodeIdFromParamsTag && + !nodeIdFromParamsGlossary && + !nodeIdFromParamsTerm && !nodeIdFromParamsRelationshipName && - !nodeIdFromBMName + !nodeIdFromBMName && + !nodeIdFromCFName ) { setSelectedNode({ type: null, tag: null, + glossary: null, + term: null, relationship: null, - businessMetadata: null + businessMetadata: null, + customFilters: null }); } - }, [location.search]); + }, [location.search, location.pathname, treeData]); const getEmptyTypesTitle = () => { switch (treeName) { @@ -461,51 +523,57 @@ const BarTreeView: FC<{ const handleClickNode = (nodeId: string) => { const searchParams = new URLSearchParams(location.search); - const isTypeMatch = searchParams.get("type") === nodeId; - const isTagMatch = searchParams.get("tag") === nodeId; - const isRelationshipMatch = searchParams.get("relationshipName") === nodeId; - const isBusinessMetadataMatch = location.pathname.includes( - "/administrator/businessMetadata" + + const nodeTypeMap: { [key: string]: boolean } = { + type: searchParams.get("type") === nodeId, + tag: searchParams.get("tag") === nodeId, + glossary: searchParams.get("gType") === "glossary", + term: searchParams.get("term")?.split("@")[0] === nodeId, + relationship: searchParams.get("relationshipName") === nodeId, + businessMetadata: location.pathname.includes( + "/administrator/businessMetadata" + ), + customFilters: + treeName == "CustomFilters" && searchParams.get("isCF") === nodeId + }; + + const glossaryGuid = searchParams.get("gId"); + + const glossaryName = !isEmpty(glossaryData) + ? glossaryData.find( + (obj: { guid: string | null }) => obj.guid === glossaryGuid + )?.name + : ""; + + const matchedType = Object.keys(nodeTypeMap).find( + (key) => nodeTypeMap[key] ); - if (isTypeMatch) { - setSelectedNode({ - type: nodeId, - tag: null, - relationship: null, - businessMetadata: null - }); - } - if (isTagMatch) { - setSelectedNode({ - type: null, - tag: nodeId, - relationship: null, - businessMetadata: null - }); - } - if (isRelationshipMatch) { + if (matchedType) { setSelectedNode({ - type: null, - tag: null, - relationship: nodeId, - businessMetadata: null - }); - } - if (isBusinessMetadataMatch) { - setSelectedNode({ - type: null, - tag: null, - relationship: null, - businessMetadata: nodeId + type: matchedType === "type" ? nodeId : null, + tag: matchedType === "tag" ? nodeId : null, + glossary: matchedType === "glossary" ? glossaryName : null, + term: matchedType === "term" ? nodeId : null, + relationship: matchedType === "relationship" ? nodeId : null, + businessMetadata: matchedType === "businessMetadata" ? nodeId : null, + customFilters: matchedType === "customFilters" ? nodeId : null }); } }; const getNodeId = (node: TreeNode) => { - if (treeName == "Classifications" && node.types == "parent") { + if ( + (treeName == "Classifications" || + treeName == "Glossary" || + treeName == "CustomFilters") && + node.types == "parent" + ) { return node.label; - } else if (treeName == "Classifications" && node.types == "child") { + } else if ( + (treeName == "Classifications" || treeName == "CustomFilters") && + node.types == "child" + ) { return `${node.id}@${node.label}`; } return !isEmpty(node?.parent) ? `${node.id}@${node?.parent}` : node.id; @@ -517,8 +585,7 @@ const BarTreeView: FC<{ searchParams: URLSearchParams, navigate: NavigateFunction, isEmptyServicetype: boolean | undefined, - savedSearchData: any, - toastId: any + savedSearchData: any ) => { globalSearchFilterInitialQuery.setQuery({}); searchParams.delete("tabActive"); @@ -549,8 +616,7 @@ const BarTreeView: FC<{ treeName, searchParams, navigate, - isEmptyServicetype, - toastId + isEmptyServicetype ); } }; @@ -560,7 +626,8 @@ const BarTreeView: FC<{ node.children === undefined || isEmpty(node.children) || treeName === "Classifications" || - (treeName === "Glossary" && node.types === "child") + treeName === "Glossary" + // && node.types === "child" ); }; @@ -569,7 +636,7 @@ const BarTreeView: FC<{ treeName: string, searchParams: URLSearchParams, isEmptyServicetype: boolean | undefined, - savedSearchData: any + savedSearchData: any[] ) => { searchParams.set( "searchType", @@ -683,8 +750,7 @@ const BarTreeView: FC<{ treeName: string, searchParams: URLSearchParams, navigate: NavigateFunction, - isEmptyServicetype: boolean | undefined, - toastId: any + isEmptyServicetype: boolean | undefined ) => { switch (treeName) { case "Business MetaData": @@ -700,15 +766,30 @@ const BarTreeView: FC<{ navigate( { pathname: `glossary/${ - node.cGuid !== undefined ? node.cGuid : node.guid + node.cGuid !== undefined ? node.cGuid : node.guid || "" }`, search: searchParams.toString() }, { replace: true } ); } else if (node.types === "parent") { - toast.dismiss(toastId.current); - toastId.current = toast.warning("Create a Term or Category"); + const searchParams = new URLSearchParams(); + searchParams.set( + "gId", + node.cGuid !== undefined ? node.cGuid : node.guid || "" + ); + searchParams.set("gtype", "glossary"); + searchParams.set("viewType", "term"); + + navigate( + { + pathname: `glossary/${ + node.cGuid !== undefined ? node.cGuid : node.guid + }`, + search: searchParams.toString() + }, + { replace: true } + ); } else { searchParams.delete("relationshipName"); navigate( @@ -790,48 +871,72 @@ const BarTreeView: FC<{ key={node.id} itemId={getNodeId(node)} label={ -
) => { + if ( + (treeName == "CustomFilters" && + isEmptyServicetype && + node.types == "parent") || + (treeName == "Glossary" && node.types == "parent") + ) { + return; + } handleNodeClick( node, treeName, searchParams, navigate, isEmptyServicetype, - savedSearchData, - toastId + savedSearchData ); }, className: "custom-treeitem-label" } as any)} > - {node.id != "No Records Found" && ( - - )} - - {(treeName == "Entities" || - treeName == "Classifications" || - treeName == "CustomFilters" || - treeName == "Glossary") && - node.id != "No Records Found" && ( - setHoveredNodeId(node.id)} + onMouseLeave={() => setHoveredNodeId(null)} + > + {node.id != "No Records Found" && ( + )} -
+ + {(treeName == "Entities" || + treeName == "Classifications" || + treeName == "CustomFilters" || + treeName == "Glossary") && + node.id != "No Records Found" && ( + + )} + + } > {node.children && node.children.map((child) => renderTreeItem(child))}