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

commit fb7cdcd08c0d23d9ff3fcbd8d6694e3f9a1912fe
Author: Prasad Pawar <[email protected]>
AuthorDate: Fri May 8 13:00:16 2026 +0530

    ATLAS-5288: ATLAS UI: Dashboard - Classification and Admin Audits (#621)
---
 .../src/components/TypeDefAuditDetailModal.tsx     | 108 +++++++++++++++++++++
 dashboard/src/utils/auditTypeDefUtils.ts           |  90 +++++++++++++++++
 .../views/Administrator/AdministratorLayout.tsx    |  22 ++++-
 .../views/Administrator/Audits/AuditResults.tsx    |  78 ++-------------
 dashboard/src/views/Classification/AddTag.tsx      |   1 +
 .../src/views/Classification/AddTagAttributes.tsx  |   1 +
 .../views/Classification/ClassificationForm.tsx    |   5 +-
 dashboard/src/views/Classification/DeleteTag.tsx   |   1 +
 .../EntityDetailTabs/ClassificationsTab.tsx        |   6 ++
 9 files changed, 240 insertions(+), 72 deletions(-)

diff --git a/dashboard/src/components/TypeDefAuditDetailModal.tsx 
b/dashboard/src/components/TypeDefAuditDetailModal.tsx
new file mode 100644
index 000000000..1e780d6fb
--- /dev/null
+++ b/dashboard/src/components/TypeDefAuditDetailModal.tsx
@@ -0,0 +1,108 @@
+/*
+ * 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 { Divider, Stack } from "@mui/material";
+import CustomModal from "@components/Modal";
+import { getValues } from "@components/commonComponents";
+import { StyledPaper } from "@utils/Muiutils";
+import { category } from "@utils/Enum";
+import { isArray, isEmpty } from "@utils/Utils";
+
+export interface TypeDefAuditDetail {
+       category?: string;
+       name?: string;
+       [key: string]: unknown;
+}
+
+interface TypeDefAuditDetailModalProps {
+       open: boolean;
+       onClose: () => void;
+       /** Full type-definition payload from audit `result` (same shape as 
audit tab click). */
+       detailObject: TypeDefAuditDetail | Record<string, unknown> | null;
+       maxWidth?: "xs" | "sm" | "md" | "lg" | "xl";
+}
+
+const buildTitle = (detailObject: 
TypeDefAuditDetailModalProps["detailObject"]): string => {
+       if (isEmpty(detailObject)) return "Type Details";
+       const cat = detailObject?.category;
+       const name = detailObject?.name;
+       if (cat != null && name != null && category[String(cat)]) {
+               return `${category[String(cat)]} Type Details: ${String(name)}`;
+       }
+       if (name != null) return `Type Details: ${String(name)}`;
+       return "Type Details";
+};
+
+export const TypeDefAuditDetailModal = ({
+       open,
+       onClose,
+       detailObject,
+       maxWidth = "md",
+}: TypeDefAuditDetailModalProps) => {
+       return (
+               <CustomModal
+                       open={open}
+                       onClose={onClose}
+                       title={buildTitle(detailObject)}
+                       footer={false}
+                       button1Handler={undefined}
+                       button2Handler={undefined}
+                       maxWidth={maxWidth}
+               >
+                       <StyledPaper variant="outlined">
+                               {!isEmpty(detailObject)
+                                       ? Object.entries(detailObject as 
Record<string, unknown>)
+                                                       .sort(([a], [b]) => 
a.localeCompare(b))
+                                                       .map(([keys, value]) => 
(
+                                                               <div key={keys}>
+                                                                       <Stack 
direction="row" spacing={4} marginBottom={1} marginTop={1}>
+                                                                               
<div
+                                                                               
        style={{
+                                                                               
                flex: 1,
+                                                                               
                wordBreak: "break-all",
+                                                                               
                textAlign: "left",
+                                                                               
                fontWeight: "600",
+                                                                               
        }}
+                                                                               
>
+                                                                               
        {`${keys} ${isArray(value) ? `(${(value as unknown[]).length})` : ""}`}
+                                                                               
</div>
+                                                                               
<div
+                                                                               
        style={{
+                                                                               
                flex: 1,
+                                                                               
                wordBreak: "break-all",
+                                                                               
                textAlign: "left",
+                                                                               
        }}
+                                                                               
>
+                                                                               
        {getValues(
+                                                                               
                value,
+                                                                               
                undefined,
+                                                                               
                undefined,
+                                                                               
                undefined,
+                                                                               
                "properties"
+                                                                               
        )}
+                                                                               
</div>
+                                                                       </Stack>
+                                                                       
<Divider />
+                                                               </div>
+                                                       ))
+                                       : "No Record Found"}
+                       </StyledPaper>
+               </CustomModal>
+       );
+};
+
+export default TypeDefAuditDetailModal;
diff --git a/dashboard/src/utils/auditTypeDefUtils.ts 
b/dashboard/src/utils/auditTypeDefUtils.ts
new file mode 100644
index 000000000..c23097578
--- /dev/null
+++ b/dashboard/src/utils/auditTypeDefUtils.ts
@@ -0,0 +1,90 @@
+/*
+ * 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 { jsonParse } from "@utils/Utils";
+
+/** TYPE_DEF_* audit params that use the same detail modal as the 
Administrator Audits tab */
+export const TYPE_DEF_DETAIL_MODAL_PARAMS = new Set([
+       "ENUM",
+       "ENTITY",
+       "STRUCT",
+       "RELATIONSHIP",
+]);
+
+export interface AuditRecordLike {
+       operation?: string;
+       params?: string;
+       result?: string;
+}
+
+/**
+ * Pulls the full typedef object from an audit `result` JSON for ENUM / ENTITY 
/
+ * STRUCT / RELATIONSHIP rows (matches expanded audit row click payload).
+ */
+export const extractTypeDefDetailObject = (
+       record: AuditRecordLike,
+       resolvedName: string,
+       resolvedGuid: string
+): Record<string, unknown> | null => {
+       const op = record.operation;
+       if (
+               op !== "TYPE_DEF_CREATE" &&
+               op !== "TYPE_DEF_UPDATE" &&
+               op !== "TYPE_DEF_DELETE"
+       ) {
+               return null;
+       }
+       const paramKeys = (record.params ?? "")
+               .split(",")
+               .map((k) => k.trim())
+               .filter(Boolean);
+
+       try {
+               const resultObj =
+                       typeof record.result === "string" ? 
jsonParse(record.result) : record.result;
+               if (!resultObj || typeof resultObj !== "object") return null;
+
+               const keysToScan =
+                       paramKeys.length > 0 ? paramKeys : 
Object.keys(resultObj as object);
+
+               for (const key of keysToScan) {
+                       if (!TYPE_DEF_DETAIL_MODAL_PARAMS.has(key)) continue;
+                       const list = (resultObj as Record<string, 
unknown>)[key];
+                       if (!Array.isArray(list) || list.length === 0) continue;
+                       const match = list.find((item: { name?: string; guid?: 
string }) => {
+                               if (!item || typeof item !== "object") return 
false;
+                               if (resolvedName && item.name === resolvedName) 
return true;
+                               if (resolvedGuid && item.guid === resolvedGuid) 
return true;
+                               return false;
+                       });
+                       if (match && typeof match === "object") {
+                               return match as Record<string, unknown>;
+                       }
+               }
+
+               const firstKey = paramKeys.find((k) => 
TYPE_DEF_DETAIL_MODAL_PARAMS.has(k));
+               if (firstKey) {
+                       const list = (resultObj as Record<string, 
unknown>)[firstKey];
+                       if (Array.isArray(list) && list[0] && typeof list[0] 
=== "object") {
+                               return list[0] as Record<string, unknown>;
+                       }
+               }
+       } catch {
+               /* ignore */
+       }
+       return null;
+};
diff --git a/dashboard/src/views/Administrator/AdministratorLayout.tsx 
b/dashboard/src/views/Administrator/AdministratorLayout.tsx
index 302da4313..7e1e2abc8 100644
--- a/dashboard/src/views/Administrator/AdministratorLayout.tsx
+++ b/dashboard/src/views/Administrator/AdministratorLayout.tsx
@@ -19,7 +19,7 @@ import { LinkTab } from "@components/muiComponents";
 import { Stack, Tabs } from "@mui/material";
 import { Item, samePageLinkNavigation } from "@utils/Muiutils";
 import { isEmpty } from "@utils/Utils";
-import { useState } from "react";
+import { useState, useEffect } from "react";
 import { useLocation, useNavigate } from "react-router-dom";
 import BusinessMetadataTab from "./BusinessMetadataTab";
 import Enumerations from "./Enumerations";
@@ -44,6 +44,26 @@ const AdministratorLayout = () => {
     !isEmpty(activeTab) ? allTabs.findIndex((val) => val === activeTab) : 0
   );
 
+  useEffect(() => {
+    const tabIndex = !isEmpty(activeTab)
+      ? allTabs.findIndex((val) => val === activeTab)
+      : 0;
+    const resolvedIndex = tabIndex >= 0 ? tabIndex : 0;
+    setValue(resolvedIndex);
+
+    if (activeTab && activeTab !== "businessMetadata") {
+      setForm(false);
+    }
+    const createParam = searchParams.get("create");
+    if (createParam === "true" && activeTab === "businessMetadata") {
+      setForm(true);
+      setBMAttribute({});
+      const newParams = new URLSearchParams(location.search);
+      newParams.delete("create");
+      navigate({ pathname: "/administrator", search: newParams.toString() }, { 
replace: true });
+    }
+  }, [activeTab, location.search, navigate]);
+
   const handleChange = (event: React.SyntheticEvent, newValue: number) => {
     if (
       event.type !== "click" ||
diff --git a/dashboard/src/views/Administrator/Audits/AuditResults.tsx 
b/dashboard/src/views/Administrator/Audits/AuditResults.tsx
index 9deef0525..a287456f7 100644
--- a/dashboard/src/views/Administrator/Audits/AuditResults.tsx
+++ b/dashboard/src/views/Administrator/Audits/AuditResults.tsx
@@ -15,22 +15,13 @@
  * limitations under the License.
  */
 
-import {
-  Divider,
-  Grid,
-  Link,
-  List,
-  ListItem,
-  ListItemText,
-  Stack,
-  Typography,
-} from "@mui/material";
+import { Grid, Link, List, ListItem, ListItemText, Typography } from 
"@mui/material";
 import { auditAction, category } from "@utils/Enum";
-import { isArray, isEmpty, jsonParse } from "@utils/Utils";
+import { isEmpty, jsonParse } from "@utils/Utils";
 import CustomModal from "@components/Modal";
+import TypeDefAuditDetailModal from "@components/TypeDefAuditDetailModal";
 import { useState } from "react";
-import { getValues } from "@components/commonComponents";
-import { Item, StyledPaper } from "@utils/Muiutils";
+import { Item } from "@utils/Muiutils";
 import AuditsTab from "@views/DetailPage/EntityDetailTabs/AuditsTab";
 import ImportExportAudits from "./ImportExportAudits";
 
@@ -196,65 +187,12 @@ const AuditResults = ({ componentProps, row }: any) => {
         <ImportExportAudits auditObj={auditObj} />
       )}
 
-      <CustomModal
+      <TypeDefAuditDetailModal
         open={openModal}
         onClose={handleCloseModal}
-        title={`${category[currentResultObj.category]} Type Details: ${
-          currentResultObj.name
-        }`}
-        footer={false}
-        button1Handler={undefined}
-        button2Handler={undefined}
-      >
-        <StyledPaper variant="outlined">
-          {" "}
-          {!isEmpty(currentResultObj)
-            ? Object.entries(currentResultObj)
-                .sort()
-                .map(([keys, value]: [string, any]) => {
-                  return (
-                    <>
-                      <Stack
-                        direction="row"
-                        spacing={4}
-                        marginBottom={1}
-                        marginTop={1}
-                      >
-                        <div
-                          style={{
-                            flex: 1,
-                            wordBreak: "break-all",
-                            textAlign: "left",
-                            fontWeight: "600",
-                          }}
-                        >
-                          {`${keys} ${
-                            isArray(value) ? `(${value.length})` : ""
-                          }`}
-                        </div>
-                        <div
-                          style={{
-                            flex: 1,
-                            wordBreak: "break-all",
-                            textAlign: "left",
-                          }}
-                        >
-                          {getValues(
-                            value,
-                            undefined,
-                            undefined,
-                            undefined,
-                            "properties"
-                          )}
-                        </div>
-                      </Stack>
-                      <Divider />
-                    </>
-                  );
-                })
-            : "No Record Found"}
-        </StyledPaper>
-      </CustomModal>
+        detailObject={currentResultObj}
+        maxWidth="sm"
+      />
 
       {(operation == "PURGE" || operation == "AUTO_PURGE") && (
         <CustomModal
diff --git a/dashboard/src/views/Classification/AddTag.tsx 
b/dashboard/src/views/Classification/AddTag.tsx
index f06fbc6bb..1ec08ac9f 100644
--- a/dashboard/src/views/Classification/AddTag.tsx
+++ b/dashboard/src/views/Classification/AddTag.tsx
@@ -381,6 +381,7 @@ const AddTag = (props: {
       disableButton2={
         isEmpty(classificationData) ? true : isSubmitting
       }
+      button2Loading={isSubmitting}
       isDirty={
         isEmpty(classificationData)
           ? false
diff --git a/dashboard/src/views/Classification/AddTagAttributes.tsx 
b/dashboard/src/views/Classification/AddTagAttributes.tsx
index 107045a17..34d4392e8 100644
--- a/dashboard/src/views/Classification/AddTagAttributes.tsx
+++ b/dashboard/src/views/Classification/AddTagAttributes.tsx
@@ -145,6 +145,7 @@ const AddTagAttributes = ({ open, onClose }: any) => {
         button2Label="Add"
         button2Handler={handleSubmit(onSubmit)}
         disableButton2={isSubmitting}
+        button2Loading={isSubmitting}
       >
         <form onSubmit={handleSubmit(onSubmit)}>
           {/* <TagAtrributes control={control} /> */}
diff --git a/dashboard/src/views/Classification/ClassificationForm.tsx 
b/dashboard/src/views/Classification/ClassificationForm.tsx
index 170f9f16c..943cabdb6 100644
--- a/dashboard/src/views/Classification/ClassificationForm.tsx
+++ b/dashboard/src/views/Classification/ClassificationForm.tsx
@@ -48,6 +48,7 @@ import { paramsType } from "@models/detailPageType";
 import TagAtrributes from "./TagAttributes";
 import { fetchClassificationData } from 
"@redux/slice/typeDefSlices/typedefClassificationSlice";
 import { AntSwitch } from "@utils/Muiutils";
+import { refreshDashboardHomeData } from "@utils/refreshDashboardHome";
 
 const ClassificationForm = ({
   open,
@@ -202,7 +203,8 @@ const ClassificationForm = ({
           isAdd ? "created" : "updated"
         } successfully`
       );
-      fetchInitialData();
+      await fetchInitialData();
+      refreshDashboardHomeData(dispatchApi);
     } catch (error) {
       console.error(
         `Error while ${isAdd ? "creating" : "updating"} classification`,
@@ -223,6 +225,7 @@ const ClassificationForm = ({
         button1Handler={onClose}
         button2Label={isAdd ? "Create" : "Save"}
         disableButton2={isSubmitting}
+        button2Loading={isSubmitting}
         isDirty={isDirty}
         maxWidth="sm"
         button2Handler={handleSubmit(onSubmit)}
diff --git a/dashboard/src/views/Classification/DeleteTag.tsx 
b/dashboard/src/views/Classification/DeleteTag.tsx
index 71b4448f6..4bc2d7a19 100644
--- a/dashboard/src/views/Classification/DeleteTag.tsx
+++ b/dashboard/src/views/Classification/DeleteTag.tsx
@@ -77,6 +77,7 @@ const DeleteTag = (props: {
         maxWidth="sm"
         button2Handler={handleRemove}
         disableButton2={loader}
+        button2Loading={loader}
       >
         <Typography fontSize={15}>
           Are you sure you want to delete classification
diff --git 
a/dashboard/src/views/DetailPage/EntityDetailTabs/ClassificationsTab.tsx 
b/dashboard/src/views/DetailPage/EntityDetailTabs/ClassificationsTab.tsx
index c898692c5..0fdd395f6 100644
--- a/dashboard/src/views/DetailPage/EntityDetailTabs/ClassificationsTab.tsx
+++ b/dashboard/src/views/DetailPage/EntityDetailTabs/ClassificationsTab.tsx
@@ -78,6 +78,7 @@ const ClassificationsTab: React.FC<EntityDetailTabProps> = ({
   );
 
   const [openModal, setOpenModal] = useState<boolean>(false);
+  const [removeAssignmentLoading, setRemoveAssignmentLoading] = 
useState(false);
   const [tagModal, setTagModal] = useState<boolean>(false);
   const [updateTable, setUpdateTable] = useState(moment.now());
   const [rowdata, setRowData] = useState();
@@ -129,6 +130,7 @@ const ClassificationsTab: React.FC<EntityDetailTabProps> = 
({
 
   const handleRemove = async () => {
     try {
+      setRemoveAssignmentLoading(true);
       await removeClassification(guid, currentValue.selectedValue);
       if (!isEmpty(guid)) {
         dispatchApi(fetchDetailPageData(guid as string));
@@ -145,6 +147,8 @@ const ClassificationsTab: React.FC<EntityDetailTabProps> = 
({
       setOpenModal(false);
     } catch (error) {
       serverError(error, toastId);
+    } finally {
+      setRemoveAssignmentLoading(false);
     }
   };
 
@@ -381,6 +385,8 @@ const ClassificationsTab: React.FC<EntityDetailTabProps> = 
({
           button1Handler={handleCloseModal}
           button2Label="Remove"
           button2Handler={handleRemove}
+          disableButton2={removeAssignmentLoading}
+          button2Loading={removeAssignmentLoading}
         >
           <Typography fontSize={15}>
             Remove: <b>{currentValue.selectedValue}</b> assignment from{" "}

Reply via email to