This is an automated email from the ASF dual-hosted git repository.
pawarprasad123 pushed a commit to branch ATLAS-5246-v1
in repository https://gitbox.apache.org/repos/asf/atlas.git
The following commit(s) were added to refs/heads/ATLAS-5246-v1 by this push:
new b0e2bbfde ATLAS-5267: ATLAS UI: Create Glossary: Add Import glossary
terms tab (template + upload) (#617)
b0e2bbfde is described below
commit b0e2bbfdef18cd5d79781050c77735acad7dd71c
Author: Prasad Pawar <[email protected]>
AuthorDate: Fri May 8 12:54:08 2026 +0530
ATLAS-5267: ATLAS UI: Create Glossary: Add Import glossary terms tab
(template + upload) (#617)
---
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{" "}