You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@atlas.apache.org by pp...@apache.org on 2026/05/06 10:22:18 UTC
(atlas) branch feature/pr-4-glossary-create-update-import-errormessage created (now 2bdf21f0b)
This is an automated email from the ASF dual-hosted git repository.
pawarprasad123 pushed a change to branch feature/pr-4-glossary-create-update-import-errormessage
in repository https://gitbox.apache.org/repos/asf/atlas.git
at 2bdf21f0b ATLAS-5267: ATLAS UI: Create Glossary: Add Import glossary terms tab (template + upload)
This branch includes the following new commits:
new 2bdf21f0b ATLAS-5267: ATLAS UI: Create Glossary: Add Import glossary terms tab (template + upload)
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(atlas) 01/01: ATLAS-5267: ATLAS UI: Create Glossary: Add Import glossary terms tab (template + upload)
Posted by pp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
pawarprasad123 pushed a commit to branch feature/pr-4-glossary-create-update-import-errormessage
in repository https://gitbox.apache.org/repos/asf/atlas.git
commit 2bdf21f0b436a190c129b82abccdc0a73e4aaf9f
Author: Prasad Pawar <pr...@freestoneinfotech.com>
AuthorDate: Wed May 6 15:51:30 2026 +0530
ATLAS-5267: ATLAS UI: Create Glossary: Add Import glossary terms tab (template + upload)
---
dashboard/src/components/ImportDialog.tsx | 44 +-
dashboard/src/utils/apiErrorToastMessage.ts | 43 ++
dashboard/src/utils/glossaryImportFlow.ts | 57 +++
.../DetailPage/GlossaryDetails/TermRelation.tsx | 1 +
.../src/views/Glossary/AddUpdateCategoryForm.tsx | 1 +
.../src/views/Glossary/AddUpdateGlossaryForm.tsx | 450 ++++++++++++++++-----
dashboard/src/views/Glossary/AddUpdateTermForm.tsx | 1 +
dashboard/src/views/Glossary/AssignCategory.tsx | 1 +
dashboard/src/views/Glossary/AssignTerm.tsx | 1 +
dashboard/src/views/Glossary/DeleteGlossary.tsx | 52 +--
10 files changed, 517 insertions(+), 134 deletions(-)
diff --git a/dashboard/src/components/ImportDialog.tsx b/dashboard/src/components/ImportDialog.tsx
index a3adfbb54..10d82aa1d 100644
--- a/dashboard/src/components/ImportDialog.tsx
+++ b/dashboard/src/components/ImportDialog.tsx
@@ -37,7 +37,8 @@ import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemText from "@mui/material/ListItemText";
import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
-import { getGlossaryImport } from "../api/apiMethods/glossaryApiMethod";
+import { postGlossaryImportFormData } from "@utils/glossaryImportFlow";
+import { getApiErrorToastMessage } from "@utils/apiErrorToastMessage";
const BootstrapDialog = styled(Dialog)(({ theme }) => ({
"& .MuiDialogContent-root": {
@@ -77,22 +78,27 @@ export const ImportDialog: React.FC<CustomModalProps> = ({
const onUpload = async () => {
if (fileData) {
try {
- let apiMethod =
+ const onProgress = (progressValue: number) => {
+ setProgress(progressValue);
+ };
+ const importResp =
title == "Import Business Metadata"
- ? getBusinessMetadataImport
- : getGlossaryImport;
- let formData = new FormData();
- formData.append("file", fileData);
- const importResp = await apiMethod(formData, {
- onUploadProgress: (progressEvent: {
- loaded: number;
- total: number;
- }) => {
- let progressValue =
- (progressEvent.loaded / progressEvent.total) * 100;
- setProgress(progressValue);
- }
- });
+ ? await (async () => {
+ const formData = new FormData();
+ formData.append("file", fileData);
+ return getBusinessMetadataImport(formData, {
+ onUploadProgress: (progressEvent: {
+ loaded: number;
+ total: number;
+ }) => {
+ if (!progressEvent.total) return;
+ onProgress(
+ (progressEvent.loaded / progressEvent.total) * 100
+ );
+ }
+ });
+ })()
+ : await postGlossaryImportFormData(fileData, onProgress);
if (importResp.data.failedImportInfoList == undefined) {
toast.dismiss(toastId.current);
@@ -114,8 +120,12 @@ export const ImportDialog: React.FC<CustomModalProps> = ({
}
setImportData(importResp.data);
} catch (error) {
+ const message = getApiErrorToastMessage(error);
+ if (message === null) {
+ return;
+ }
toast.dismiss(toastId.current);
- toastId.current = toast.error(`Invalid JSON response from server`);
+ toastId.current = toast.error(message);
}
}
};
diff --git a/dashboard/src/utils/apiErrorToastMessage.ts b/dashboard/src/utils/apiErrorToastMessage.ts
new file mode 100644
index 000000000..7afa98d0b
--- /dev/null
+++ b/dashboard/src/utils/apiErrorToastMessage.ts
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+/**
+ * Resolves user-visible toast text from an axios/fetchApi error.
+ * @returns null when the caller must not toast (403 is handled in fetchApi).
+ */
+export const getApiErrorToastMessage = (error: unknown): string | null => {
+ const er = error as {
+ response?: { status?: number; data?: unknown };
+ };
+ if (er?.response?.status === 403) {
+ return null;
+ }
+ const data = er?.response?.data;
+ if (data !== null && data !== undefined && typeof data === "object") {
+ const o = data as Record<string, unknown>;
+ if (o.errorMessage != null && String(o.errorMessage).trim() !== "") {
+ return String(o.errorMessage);
+ }
+ if (o.msgDesc != null && String(o.msgDesc).trim() !== "") {
+ return String(o.msgDesc);
+ }
+ }
+ if (typeof data === "string" && data.trim() !== "") {
+ return data;
+ }
+ return "Invalid JSON response from server";
+};
diff --git a/dashboard/src/utils/glossaryImportFlow.ts b/dashboard/src/utils/glossaryImportFlow.ts
new file mode 100644
index 000000000..a3f564050
--- /dev/null
+++ b/dashboard/src/utils/glossaryImportFlow.ts
@@ -0,0 +1,57 @@
+/*
+ * 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 {
+ getGlossaryImport,
+ getGlossaryImportTmpl
+} from '@api/apiMethods/glossaryApiMethod';
+
+/**
+ * Same behavior as glossary branch of SideBarTree.downloadFile.
+ */
+export const downloadGlossaryImportTemplate = async (): Promise<void> => {
+ const apiResp = await getGlossaryImportTmpl({});
+ const text =
+ apiResp && typeof apiResp.data !== 'undefined'
+ ? String(apiResp.data)
+ : '';
+ const blob = new Blob([text], { type: 'text/plain' });
+ const url = window.URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.setAttribute('download', 'template');
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ window.URL.revokeObjectURL(url);
+};
+
+export const postGlossaryImportFormData = (
+ file: File,
+ onUploadProgress?: (progressPercent: number) => void
+) => {
+ const formData = new FormData();
+ formData.append('file', file);
+ return getGlossaryImport(formData, {
+ onUploadProgress: (progressEvent: { loaded: number; total: number }) => {
+ if (!onUploadProgress || !progressEvent.total) return;
+ const progressValue =
+ (progressEvent.loaded / progressEvent.total) * 100;
+ onUploadProgress(progressValue);
+ }
+ });
+};
diff --git a/dashboard/src/views/DetailPage/GlossaryDetails/TermRelation.tsx b/dashboard/src/views/DetailPage/GlossaryDetails/TermRelation.tsx
index ba1381528..68d069ea7 100644
--- a/dashboard/src/views/DetailPage/GlossaryDetails/TermRelation.tsx
+++ b/dashboard/src/views/DetailPage/GlossaryDetails/TermRelation.tsx
@@ -219,6 +219,7 @@ const TermRelation = ({ glossaryTypeData }: any) => {
button2Label={editModal ? "Update" : "Close"}
button2Handler={editModal ? handleSubmit(onSubmit) : handleCloseModal}
disableButton2={isSubmitting}
+ button2Loading={isSubmitting}
maxWidth="md"
>
<form onSubmit={handleSubmit(onSubmit)}>
diff --git a/dashboard/src/views/Glossary/AddUpdateCategoryForm.tsx b/dashboard/src/views/Glossary/AddUpdateCategoryForm.tsx
index 12a80b6de..bd9084861 100644
--- a/dashboard/src/views/Glossary/AddUpdateCategoryForm.tsx
+++ b/dashboard/src/views/Glossary/AddUpdateCategoryForm.tsx
@@ -146,6 +146,7 @@ const AddUpdateCategoryForm = (props: {
button1Handler={onClose}
button2Label={isAdd ? "Create" : "Update"}
disableButton2={isSubmitting}
+ button2Loading={isSubmitting}
maxWidth="sm"
button2Handler={handleSubmit(onSubmit)}
>
diff --git a/dashboard/src/views/Glossary/AddUpdateGlossaryForm.tsx b/dashboard/src/views/Glossary/AddUpdateGlossaryForm.tsx
index 4aa2248d1..5f4a22854 100644
--- a/dashboard/src/views/Glossary/AddUpdateGlossaryForm.tsx
+++ b/dashboard/src/views/Glossary/AddUpdateGlossaryForm.tsx
@@ -19,108 +19,370 @@ import CustomModal from "@components/Modal";
import GlossaryForm from "./GlossaryForm";
import { useForm } from "react-hook-form";
import {
- createGlossary,
- editGlossary
+ createGlossary,
+ editGlossary
} from "@api/apiMethods/glossaryApiMethod";
import { isEmpty, serverError } from "@utils/Utils";
+import {
+ downloadGlossaryImportTemplate,
+ postGlossaryImportFormData
+} from "@utils/glossaryImportFlow";
+import { getApiErrorToastMessage } from "@utils/apiErrorToastMessage";
import { toast } from "react-toastify";
-import { useRef } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
+import type { MouseEvent } from "react";
import { useAppDispatch, useAppSelector } from "@hooks/reducerHook";
import { fetchGlossaryData } from "@redux/slice/glossarySlice";
+import {
+ Button,
+ IconButton,
+ List,
+ ListItem,
+ ListItemText,
+ Stack,
+ ToggleButton,
+ ToggleButtonGroup
+} from "@mui/material";
+import FileDownloadIcon from "@mui/icons-material/FileDownload";
+import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
+import ImportLayout from "@views/SideBar/Import/ImportLayout";
+import { LightTooltip } from "@components/muiComponents";
+
+type GlossaryCreateTab = "create" | "import";
+
+const hasSelectedImportFile = (fileData: unknown): boolean => {
+ if (fileData == null) return false;
+ if (fileData instanceof File) return true;
+ if (typeof fileData === "object" && Object.keys(fileData as object).length > 0)
+ return true;
+ return false;
+};
const AddUpdateGlossaryForm = (props: {
- open: any;
- onClose: any;
- isAdd: any;
- node: Record<string, any> | undefined;
+ open: any;
+ onClose: any;
+ isAdd: any;
+ node: Record<string, any> | undefined;
}) => {
- const { open, onClose, isAdd, node } = props;
- const dispatch = useAppDispatch();
- const toastId: any = useRef(null);
- const { glossaryData }: any = useAppSelector((state: any) => state.glossary);
- const { id } = node || {};
- let defaultValue: Record<string, string> = {};
- let glossaryObj: Record<string, string> = {};
- if (!isAdd) {
- glossaryObj = glossaryData.find((obj: { name: string }) => {
- return obj.name == id;
- });
- const { name, shortDescription, longDescription } = glossaryObj || {};
-
- defaultValue["name"] = name;
- defaultValue["shortDescription"] = shortDescription;
- defaultValue["longDescription"] = longDescription;
- }
- const {
- control,
- handleSubmit,
- setValue,
- formState: { isSubmitting }
- } = useForm({
- defaultValues: isAdd ? {} : defaultValue,
- mode: "onChange",
- shouldUnregister: true
- });
-
- const onSubmit = async (formValues: any) => {
- let formData = { ...formValues };
- const { guid, qualifiedName } = glossaryObj;
- const {
- name,
- shortDescription,
- longDescription
- }: { name: string; shortDescription: string; longDescription: string } =
- formData;
- let data: Record<string, string> = {};
- if (!isAdd) {
- data["guid"] = guid;
- data["qualifiedName"] = qualifiedName;
- }
- data["name"] = name;
- data["shortDescription"] = !isEmpty(shortDescription)
- ? shortDescription
- : "";
- data["longDescription"] = !isEmpty(longDescription) ? longDescription : "";
-
- try {
- if (isAdd) {
- await createGlossary(data);
- } else {
- await editGlossary(guid, data);
- }
-
- await dispatch(fetchGlossaryData());
- toast.dismiss(toastId.current);
- toastId.current = toast.success(
- `Glossary ${name} was ${isAdd ? "created" : "updated"} successfully`
- );
- onClose();
- } catch (error) {
- serverError(error, toastId);
- }
- };
-
- return (
- <>
- <CustomModal
- open={open}
- onClose={onClose}
- title={isAdd ? "Create Glossary" : "Edit Glossary"}
- button1Label="Cancel"
- button1Handler={onClose}
- button2Label={isAdd ? "Create" : "Update"}
- maxWidth="sm"
- button2Handler={handleSubmit(onSubmit)}
- disableButton2={isSubmitting}
- >
- <GlossaryForm
- control={control}
- handleSubmit={handleSubmit(onSubmit)}
- setValue={setValue}
- />
- </CustomModal>
- </>
- );
+ const { open, onClose, isAdd, node } = props;
+ const dispatch = useAppDispatch();
+ const toastId: any = useRef(null);
+ const { glossaryData }: any = useAppSelector((state: any) => state.glossary);
+ const { id } = node || {};
+ let defaultValue: Record<string, string> = {};
+ let glossaryObj: Record<string, string> = {};
+ if (!isAdd) {
+ glossaryObj = glossaryData.find((obj: { name: string }) => {
+ return obj.name == id;
+ });
+ const { name, shortDescription, longDescription } = glossaryObj || {};
+
+ defaultValue["name"] = name;
+ defaultValue["shortDescription"] = shortDescription;
+ defaultValue["longDescription"] = longDescription;
+ }
+ const {
+ control,
+ handleSubmit,
+ setValue,
+ formState: { isSubmitting }
+ } = useForm({
+ defaultValues: isAdd ? {} : defaultValue,
+ mode: "onChange",
+ shouldUnregister: true
+ });
+
+ const [glossaryCreateTab, setGlossaryCreateTab] =
+ useState<GlossaryCreateTab>("create");
+ const [importFileData, setImportFileData] = useState<any>([]);
+ const [importProgress, setImportProgress] = useState(0);
+ const [importErrorDetails, setImportErrorDetails] = useState(false);
+ const [importData, setImportData] = useState<any>(null);
+ const [importUploading, setImportUploading] = useState(false);
+
+ const resetImportUi = useCallback(() => {
+ setGlossaryCreateTab("create");
+ setImportFileData([]);
+ setImportProgress(0);
+ setImportErrorDetails(false);
+ setImportData(null);
+ setImportUploading(false);
+ }, []);
+
+ const handleModalClose = useCallback(() => {
+ resetImportUi();
+ onClose();
+ }, [onClose, resetImportUi]);
+
+ useEffect(() => {
+ if (!open) {
+ resetImportUi();
+ }
+ }, [open, resetImportUi]);
+
+ const handleGlossaryCreateTabChange = (
+ _event: MouseEvent<HTMLElement>,
+ next: GlossaryCreateTab | null
+ ) => {
+ if (next == null) return;
+ setGlossaryCreateTab(next);
+ if (next === "create") {
+ setImportFileData([]);
+ setImportProgress(0);
+ setImportErrorDetails(false);
+ setImportData(null);
+ }
+ };
+
+ const handleDownloadTemplate = async () => {
+ try {
+ await downloadGlossaryImportTemplate();
+ } catch {
+ toast.dismiss(toastId.current);
+ toastId.current = toast.error("Could not download template");
+ }
+ };
+
+ const handleImportUpload = async () => {
+ if (!hasSelectedImportFile(importFileData)) return;
+ setImportUploading(true);
+ try {
+ const importResp = await postGlossaryImportFormData(
+ importFileData,
+ (pct) => setImportProgress(pct)
+ );
+ if (importResp.data.failedImportInfoList == undefined) {
+ toast.dismiss(toastId.current);
+ toastId.current = toast.success(
+ `File: ${importFileData.name} imported successfully`
+ );
+ await dispatch(fetchGlossaryData());
+ handleModalClose();
+ return;
+ }
+ if (importResp.data.failedImportInfoList != undefined) {
+ toast.dismiss(toastId.current);
+ toastId.current = toast.error(
+ importResp.data.failedImportInfoList[0].remarks
+ );
+ setImportErrorDetails(true);
+ }
+ setImportData(importResp.data);
+ } catch (error) {
+ const message = getApiErrorToastMessage(error);
+ if (message === null) {
+ return;
+ }
+ toast.dismiss(toastId.current);
+ toastId.current = toast.error(message);
+ } finally {
+ setImportUploading(false);
+ }
+ };
+
+ const onSubmit = async (formValues: any) => {
+ let formData = { ...formValues };
+ const { guid, qualifiedName } = glossaryObj;
+ const {
+ name,
+ shortDescription,
+ longDescription
+ }: { name: string; shortDescription: string; longDescription: string } =
+ formData;
+ let data: Record<string, string> = {};
+ if (!isAdd) {
+ data["guid"] = guid;
+ data["qualifiedName"] = qualifiedName;
+ }
+ data["name"] = name;
+ data["shortDescription"] = !isEmpty(shortDescription)
+ ? shortDescription
+ : "";
+ data["longDescription"] = !isEmpty(longDescription) ? longDescription : "";
+
+ try {
+ if (isAdd) {
+ await createGlossary(data);
+ } else {
+ await editGlossary(guid, data);
+ }
+
+ await dispatch(fetchGlossaryData());
+ toast.dismiss(toastId.current);
+ toastId.current = toast.success(
+ `Glossary ${name} was ${isAdd ? "created" : "updated"} successfully`
+ );
+ handleModalClose();
+ } catch (error) {
+ serverError(error, toastId);
+ }
+ };
+
+ const showImportError = isAdd && glossaryCreateTab === "import" && importErrorDetails;
+
+ const modalTitle = showImportError
+ ? "Error Details"
+ : isAdd
+ ? "Create Glossary"
+ : "Edit Glossary";
+
+ const titleIconNode =
+ showImportError ? (
+ <IconButton
+ aria-label="Back to import file"
+ onClick={(e) => {
+ e.stopPropagation();
+ setImportErrorDetails(false);
+ }}
+ size="small"
+ sx={{ color: (theme) => theme.palette.grey[500] }}
+ >
+ <LightTooltip title="Back to import file">
+ <ArrowBackIosNewIcon sx={{ fontSize: "1.25rem" }} />
+ </LightTooltip>
+ </IconButton>
+ ) : undefined;
+
+ const isImportMode = isAdd && glossaryCreateTab === "import" && !importErrorDetails;
+
+ const button2Label = (() => {
+ if (!isAdd) return "Update";
+ if (glossaryCreateTab === "create") return "Create";
+ if (importErrorDetails) return "";
+ return "Upload";
+ })();
+
+ const button2Handler = (() => {
+ if (!isAdd || glossaryCreateTab === "create") return handleSubmit(onSubmit);
+ if (importErrorDetails) return () => {};
+ return handleImportUpload;
+ })();
+
+ const disableButton2 = (() => {
+ if (!isAdd) return isSubmitting;
+ if (glossaryCreateTab === "create") return isSubmitting;
+ if (importErrorDetails) return true;
+ return !hasSelectedImportFile(importFileData);
+ })();
+
+ const button2Loading =
+ isAdd && glossaryCreateTab === "import" && !importErrorDetails
+ ? importUploading
+ : isSubmitting;
+
+ const hideButton2 = Boolean(
+ isAdd && glossaryCreateTab === "import" && importErrorDetails
+ );
+
+ return (
+ <>
+ <CustomModal
+ open={open}
+ onClose={handleModalClose}
+ title={modalTitle}
+ titleIcon={titleIconNode}
+ button1Label="Cancel"
+ button1Handler={handleModalClose}
+ button2Label={button2Label}
+ maxWidth="sm"
+ button2Handler={button2Handler}
+ disableButton2={disableButton2}
+ button2Loading={button2Loading}
+ hideButton2={hideButton2}
+ >
+ <Stack>
+ {isAdd && (
+ <Stack marginBottom="1rem">
+ <ToggleButtonGroup
+ exclusive
+ value={glossaryCreateTab}
+ onChange={handleGlossaryCreateTabChange}
+ size="small"
+ color="primary"
+ aria-label="Choose create glossary or import glossary terms"
+ >
+ <ToggleButton
+ value="create"
+ className="entity-form-toggle-btn"
+ data-cy="create-glossary-tab"
+ >
+ Create glossary
+ </ToggleButton>
+ <ToggleButton
+ value="import"
+ className="entity-form-toggle-btn"
+ data-cy="import-glossary-terms-tab"
+ >
+ Import glossary terms
+ </ToggleButton>
+ </ToggleButtonGroup>
+ </Stack>
+ )}
+ {(!isAdd || glossaryCreateTab === "create") && (
+ <GlossaryForm
+ control={control}
+ handleSubmit={handleSubmit(onSubmit)}
+ setValue={setValue}
+ />
+ )}
+ {isImportMode && (
+ <Stack gap={2}>
+ <Button
+ variant="outlined"
+ color="primary"
+ startIcon={<FileDownloadIcon />}
+ onClick={handleDownloadTemplate}
+ aria-label="Download import template"
+ >
+ Download import template
+ </Button>
+ <ImportLayout
+ setFileData={setImportFileData}
+ progressVal={importProgress}
+ setProgress={setImportProgress}
+ selectedFile={
+ importFileData !== undefined &&
+ hasSelectedImportFile(importFileData)
+ ? [importFileData]
+ : []
+ }
+ errorDetails={importErrorDetails}
+ />
+ </Stack>
+ )}
+ {showImportError && importData?.failedImportInfoList && (
+ <Stack
+ sx={{
+ width: "100%",
+ minHeight: 200,
+ maxHeight: 400,
+ bgcolor: "background.paper"
+ }}
+ >
+ <List>
+ {importData.failedImportInfoList.map(
+ (
+ value: {
+ index: number;
+ remarks: string;
+ },
+ index: number
+ ) => (
+ <ListItem key={value.index} disableGutters disablePadding>
+ <ListItemText
+ className="dropzone-listitem"
+ primary={`${index + 1}. ${value.remarks}`}
+ />
+ </ListItem>
+ )
+ )}
+ </List>
+ </Stack>
+ )}
+ </Stack>
+ </CustomModal>
+ </>
+ );
};
export default AddUpdateGlossaryForm;
diff --git a/dashboard/src/views/Glossary/AddUpdateTermForm.tsx b/dashboard/src/views/Glossary/AddUpdateTermForm.tsx
index feea62032..f5ece0289 100644
--- a/dashboard/src/views/Glossary/AddUpdateTermForm.tsx
+++ b/dashboard/src/views/Glossary/AddUpdateTermForm.tsx
@@ -142,6 +142,7 @@ const AddUpdateTermForm = (props: {
button1Handler={onClose}
button2Label={isAdd ? "Create" : "Update"}
disableButton2={isSubmitting}
+ button2Loading={isSubmitting}
maxWidth="sm"
button2Handler={handleSubmit(onSubmit)}
>
diff --git a/dashboard/src/views/Glossary/AssignCategory.tsx b/dashboard/src/views/Glossary/AssignCategory.tsx
index 9e588ae3c..5a6f8f8b9 100644
--- a/dashboard/src/views/Glossary/AssignCategory.tsx
+++ b/dashboard/src/views/Glossary/AssignCategory.tsx
@@ -288,6 +288,7 @@ const AssignCategory = ({
maxWidth="sm"
button2Handler={assignCatgeory}
disableButton2={loading}
+ button2Loading={loading}
>
<Stack>
<TextField
diff --git a/dashboard/src/views/Glossary/AssignTerm.tsx b/dashboard/src/views/Glossary/AssignTerm.tsx
index 14eceb720..e4c2fc0ed 100644
--- a/dashboard/src/views/Glossary/AssignTerm.tsx
+++ b/dashboard/src/views/Glossary/AssignTerm.tsx
@@ -452,6 +452,7 @@ const AssignTerm = ({
maxWidth="sm"
button2Handler={relatedTerm ? handleSubmit(onSubmit) : assignTerm}
disableButton2={isSubmitting}
+ button2Loading={isSubmitting}
isDirty={!isEmpty(selectedNode)}
>
{relatedTerm ? (
diff --git a/dashboard/src/views/Glossary/DeleteGlossary.tsx b/dashboard/src/views/Glossary/DeleteGlossary.tsx
index 744447494..c4b5dcf60 100644
--- a/dashboard/src/views/Glossary/DeleteGlossary.tsx
+++ b/dashboard/src/views/Glossary/DeleteGlossary.tsx
@@ -26,6 +26,7 @@ import { Typography } from "@mui/material";
import { fetchGlossaryData } from "@redux/slice/glossarySlice";
import { isEmpty, serverError } from "@utils/Utils";
import { useRef } from "react";
+import { useAsyncPending } from "@hooks/useAsyncPending";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
@@ -48,36 +49,39 @@ const DeleteGlossary = (props: {
const navigate = useNavigate();
const dispatchApi = useAppDispatch();
const toastId: any = useRef(null);
+ const { pending: deleteInProgress, run: runDelete } = useAsyncPending();
const fetchCurrentData = async () => {
await dispatchApi(fetchGlossaryData());
};
- const handleRemove = async () => {
- try {
- gtype == "term"
- ? await deleteGlossaryorType(cGuid)
- : await deleteGlossaryorTerm(guid);
- updatedData();
- fetchCurrentData();
- toast.success(
- `${
- gtype == "term" ? "Term" : "Glossary"
- } ${id} was deleted successfully`
- );
- if (!isEmpty(glossaryGuid) || !isEmpty(glossaryType)) {
- navigate(
- {
- pathname: "/"
- },
- { replace: true }
+ const handleRemove = () => {
+ void runDelete(async () => {
+ try {
+ gtype == "term"
+ ? await deleteGlossaryorType(cGuid)
+ : await deleteGlossaryorTerm(guid);
+ updatedData();
+ await fetchCurrentData();
+ toast.success(
+ `${
+ gtype == "term" ? "Term" : "Glossary"
+ } ${id} was deleted successfully`
);
+ if (!isEmpty(glossaryGuid) || !isEmpty(glossaryType)) {
+ navigate(
+ {
+ pathname: "/"
+ },
+ { replace: true }
+ );
+ }
+ onClose();
+ setExpandNode(null);
+ } catch (error) {
+ serverError(error, toastId);
}
- onClose();
- setExpandNode(null);
- } catch (error) {
- serverError(error, toastId);
- }
+ });
};
return (
@@ -92,6 +96,8 @@ const DeleteGlossary = (props: {
button2Label="Ok"
maxWidth="sm"
button2Handler={handleRemove}
+ disableButton2={deleteInProgress}
+ button2Loading={deleteInProgress}
>
<Typography fontSize={15}>
Are you sure you want to delete{" "}