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 42d2ff924a49d7c4dae60fcff82e4bf284034af3
Author: Prasad Pawar <[email protected]>
AuthorDate: Fri May 8 12:56:21 2026 +0530

    ATLAS-5256: ATLAS UI: Header - Create Button and Dropdown Menu. (#618)
    
    * ATLAS-5256: ATLAS UI: Header - Create Button and Dropdown Menu
    
    * ATLAS-5256: ATLAS UI: Header - Create Button and Dropdown Menu
---
 .../components/CreateDropdown/CreateDropdown.tsx   | 176 +++++++++++++++++++++
 dashboard/src/components/CreateDropdown/index.ts   |   1 +
 dashboard/src/components/QueryBuilder/Filters.tsx  | 123 +++++++++++---
 .../src/components/ShowMore/DrawerBodyChipView.tsx |   1 +
 dashboard/src/components/ShowMore/ShowMoreView.tsx |   3 +
 dashboard/src/components/Table/TablePagination.tsx | 120 ++++++++------
 dashboard/src/components/TreeNodeIcons.tsx         |  42 +++--
 dashboard/src/utils/Enum.ts                        |   9 ++
 .../BusinessMetadataAtrributeForm.tsx              |   1 +
 .../BusinessMetadata/BusinessMetadataForm.tsx      |   2 +
 .../BusinessMetadataDetailsLayout.tsx              |   2 +
 .../EntityDetailTabs/PropagationPropertyModal.tsx  |   1 +
 dashboard/src/views/Entity/EntityForm.tsx          |   1 +
 dashboard/src/views/SaveFilters/SaveFilters.tsx    |   1 +
 dashboard/src/views/Statistics/ServerStats.tsx     |   7 +-
 dashboard/src/views/Statistics/Statistics.tsx      |  45 ++++--
 16 files changed, 432 insertions(+), 103 deletions(-)

diff --git a/dashboard/src/components/CreateDropdown/CreateDropdown.tsx 
b/dashboard/src/components/CreateDropdown/CreateDropdown.tsx
new file mode 100644
index 000000000..cb32d4e2a
--- /dev/null
+++ b/dashboard/src/components/CreateDropdown/CreateDropdown.tsx
@@ -0,0 +1,176 @@
+/*
+ * 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 { useState, useCallback } from "react";
+import { Button, Menu, MenuItem } from "@mui/material";
+import AddIcon from "@mui/icons-material/Add";
+import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
+import CategoryIcon from "@mui/icons-material/Category";
+import LocalOfferIcon from "@mui/icons-material/LocalOffer";
+import MenuBookIcon from "@mui/icons-material/MenuBook";
+import BusinessCenterIcon from "@mui/icons-material/BusinessCenter";
+import ListIcon from "@mui/icons-material/List";
+import { useNavigate } from "react-router-dom";
+import EntityForm from "@views/Entity/EntityForm";
+import ClassificationForm from "@views/Classification/ClassificationForm";
+import AddUpdateGlossaryForm from "@views/Glossary/AddUpdateGlossaryForm";
+const CreateDropdown = () => {
+       const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
+       const [entityModal, setEntityModal] = useState(false);
+       const [classificationModal, setClassificationModal] = useState(false);
+       const [glossaryModal, setGlossaryModal] = useState(false);
+       const navigate = useNavigate();
+
+       const open = Boolean(anchorEl);
+
+       const handleClick = useCallback((event: React.MouseEvent<HTMLElement>) 
=> {
+               setAnchorEl(event.currentTarget);
+       }, []);
+
+       const handleClose = useCallback(() => {
+               setAnchorEl(null);
+       }, []);
+
+       const handleEntityClick = useCallback(() => {
+               handleClose();
+               setEntityModal(true);
+       }, [handleClose]);
+
+       const handleClassificationClick = useCallback(() => {
+               handleClose();
+               setClassificationModal(true);
+       }, [handleClose]);
+
+       const handleGlossaryClick = useCallback(() => {
+               handleClose();
+               setGlossaryModal(true);
+       }, [handleClose]);
+
+       const handleBusinessMetadataClick = useCallback(() => {
+               handleClose();
+               navigate({
+                       pathname: "/administrator",
+                       search: "tabActive=businessMetadata&create=true"
+               });
+       }, [handleClose, navigate]);
+
+       const handleEnumClick = useCallback(() => {
+               handleClose();
+               navigate({
+                       pathname: "/administrator",
+                       search: "tabActive=enum"
+               });
+       }, [handleClose, navigate]);
+
+       return (
+               <>
+                       <Button
+                               variant="contained"
+                               color="primary"
+                               size="small"
+                               onClick={handleClick}
+                               endIcon={<ExpandMoreIcon />}
+                               startIcon={<AddIcon />}
+                               aria-controls={open ? "create-menu" : undefined}
+                               aria-haspopup="true"
+                               aria-expanded={open ? "true" : undefined}
+                               data-cy="create-dropdown"
+                       >
+                               Create
+                       </Button>
+                       <Menu
+                               id="create-menu"
+                               anchorEl={anchorEl}
+                               open={open}
+                               onClose={handleClose}
+                               anchorOrigin={{ horizontal: "left", vertical: 
"bottom" }}
+                               transformOrigin={{ horizontal: "left", 
vertical: "top" }}
+                               slotProps={{
+                                       paper: {
+                                               elevation: 2,
+                                               sx: { mt: 1.5, minWidth: 200 }
+                                       }
+                               }}
+                       >
+                               <MenuItem
+                                       onClick={handleEntityClick}
+                                       data-cy="create-entity"
+                                       sx={{ gap: 1.5 }}
+                               >
+                                       <CategoryIcon fontSize="small" />
+                                       Entity
+                               </MenuItem>
+                               <MenuItem
+                                       onClick={handleClassificationClick}
+                                       data-cy="create-classification"
+                                       sx={{ gap: 1.5 }}
+                               >
+                                       <LocalOfferIcon fontSize="small" />
+                                       Classification
+                               </MenuItem>
+                               <MenuItem
+                                       onClick={handleGlossaryClick}
+                                       data-cy="create-glossary"
+                                       sx={{ gap: 1.5 }}
+                               >
+                                       <MenuBookIcon fontSize="small" />
+                                       Glossary
+                               </MenuItem>
+                               <MenuItem
+                                       onClick={handleBusinessMetadataClick}
+                                       data-cy="create-business-metadata"
+                                       sx={{ gap: 1.5 }}
+                               >
+                                       <BusinessCenterIcon fontSize="small" />
+                                       Business Metadata
+                               </MenuItem>
+                               <MenuItem
+                                       onClick={handleEnumClick}
+                                       data-cy="create-enum"
+                                       sx={{ gap: 1.5 }}
+                               >
+                                       <ListIcon fontSize="small" />
+                                       Enum
+                               </MenuItem>
+                       </Menu>
+
+                       {entityModal && (
+                               <EntityForm
+                                       open={entityModal}
+                                       onClose={() => setEntityModal(false)}
+                               />
+                       )}
+                       {classificationModal && (
+                               <ClassificationForm
+                                       open={classificationModal}
+                                       isAdd={true}
+                                       onClose={() => 
setClassificationModal(false)}
+                               />
+                       )}
+                       {glossaryModal && (
+                               <AddUpdateGlossaryForm
+                                       open={glossaryModal}
+                                       isAdd={true}
+                                       onClose={() => setGlossaryModal(false)}
+                                       node={undefined}
+                               />
+                       )}
+               </>
+       );
+};
+
+export default CreateDropdown;
diff --git a/dashboard/src/components/CreateDropdown/index.ts 
b/dashboard/src/components/CreateDropdown/index.ts
new file mode 100644
index 000000000..48cc7382b
--- /dev/null
+++ b/dashboard/src/components/CreateDropdown/index.ts
@@ -0,0 +1 @@
+export { default } from "./CreateDropdown";
diff --git a/dashboard/src/components/QueryBuilder/Filters.tsx 
b/dashboard/src/components/QueryBuilder/Filters.tsx
index 2b305510e..d162f28e6 100644
--- a/dashboard/src/components/QueryBuilder/Filters.tsx
+++ b/dashboard/src/components/QueryBuilder/Filters.tsx
@@ -25,7 +25,7 @@ import {
   FormControlLabel
 } from "@mui/material";
 
-import { useState } from "react";
+import { useState, useEffect, useMemo } from "react";
 import {
   Accordion,
   AccordionDetails,
@@ -47,6 +47,7 @@ import { useAppSelector } from "@hooks/reducerHook";
 import { cloneDeep } from "@utils/Helper";
 import { getObjDef } from 
"@views/Administrator/Audits/AuditsFilter/AuditFiltersFields";
 import { attributeFilter } from "@utils/CommonViewFunction";
+import { getDisplayOperator } from "@utils/Enum";
 import moment from "moment";
 import RelationshipFilters from "./RelationshipFilters";
 import TypeFilters from "./TypeFilters/TypeFilters";
@@ -70,28 +71,66 @@ const Filters = ({
   const tagParams = searchParams.get("tag");
   const relationshipParams = searchParams.get("relationshipName");
   const entityFilterParams = searchParams.get("entityFilters");
-  const [checkedEntities, setCheckedEntities] = useState<any>(
-    !isEmpty(searchParams.get("includeDE"))
-      ? searchParams.get("includeDE")
-      : false
+  const [checkedEntities, setCheckedEntities] = useState<boolean>(
+    searchParams.get("includeDE") === "true" || searchParams.get("includeDE") 
=== true
   );
-  const [checkedSubClassifications, setCheckedSubClassifications] =
-    useState<any>(
-      !isEmpty(searchParams.get("excludeSC"))
-        ? searchParams.get("excludeSC")
-        : false
-    );
-  const [checkedSubTypes, setCheckedSubTypes] = useState<any>(
-    !isEmpty(searchParams.get("excludeST"))
-      ? searchParams.get("excludeST")
-      : false
+  const [checkedSubClassifications, setCheckedSubClassifications] = 
useState<boolean>(
+    searchParams.get("excludeSC") === "true" || searchParams.get("excludeSC") 
=== true
+  );
+  const [checkedSubTypes, setCheckedSubTypes] = useState<boolean>(
+    searchParams.get("excludeST") === "true" || searchParams.get("excludeST") 
=== true
   );
+  const parseFiltersFromUrl = (params: string | null) => {
+    if (isEmpty(params)) return null;
+    const parsed = attributeFilter.extractUrl({
+      value: params,
+      formatDate: true
+    });
+    if (!parsed?.rules) return null;
+    const rulesArr = Array.isArray(parsed.rules)
+      ? parsed.rules
+      : Object.keys(parsed.rules || {}).map((k) => parsed.rules[k]);
+    const mappedRules = rulesArr
+      .filter((r) => r && !r.condition)
+      .map((r, i) => ({
+        field: r.id,
+        operator: getDisplayOperator(r.operator) || r.operator,
+        value: r.value,
+        id: `url-rule-${i}`
+      }));
+    if (mappedRules.length === 0) return null;
+    return {
+      combinator: (parsed.condition || "AND").toLowerCase(),
+      rules: mappedRules
+    };
+  };
+
+  const entityFiltersFromUrl = parseFiltersFromUrl(entityFilterParams);
+  const tagFilterParams = searchParams.get("tagFilters");
+  const relationshipFilterParams = searchParams.get("relationshipFilters");
+  const tagFiltersFromUrl = parseFiltersFromUrl(tagFilterParams);
+  const relationshipFiltersFromUrl = 
parseFiltersFromUrl(relationshipFilterParams);
+
   const [typeQuery, setTypeQuery] = useState(
     !isEmpty(globalSearchFilterInitialQuery.getQuery()?.entityFilters) &&
       !isEmpty(entityFilterParams)
       ? globalSearchFilterInitialQuery.getQuery()?.entityFilters
-      : initialQuery
+      : !isEmpty(entityFiltersFromUrl)
+        ? entityFiltersFromUrl
+        : initialQuery
   );
+
+  useEffect(() => {
+    if (
+      !isEmpty(entityFilterParams) &&
+      isEmpty(globalSearchFilterInitialQuery.getQuery()?.entityFilters) &&
+      !isEmpty(entityFiltersFromUrl)
+    ) {
+      globalSearchFilterInitialQuery.setQuery({
+        entityFilters: entityFiltersFromUrl
+      });
+    }
+  }, [entityFilterParams]);
   const [classificationQuery, setClassificationQuery] = useState(
     !isEmpty(globalSearchFilterInitialQuery.getQuery()?.tagFilters)
       ? globalSearchFilterInitialQuery.getQuery()?.tagFilters
@@ -125,28 +164,63 @@ const Filters = ({
     businessMetadata: businessMetadataDefs
   };
 
+  const appliedIncludeDE = searchParams.get("includeDE") === "true" || 
searchParams.get("includeDE") === true;
+  const appliedExcludeSC = searchParams.get("excludeSC") === "true" || 
searchParams.get("excludeSC") === true;
+  const appliedExcludeST = searchParams.get("excludeST") === "true" || 
searchParams.get("excludeST") === true;
+
+  const hasChanges = useMemo(() => {
+    const switchChanged =
+      !!checkedEntities !== !!appliedIncludeDE ||
+      !!checkedSubClassifications !== !!appliedExcludeSC ||
+      !!checkedSubTypes !== !!appliedExcludeST;
+
+    const normalizeQuery = (q: RuleGroupType | null) => {
+      if (!q || !q.rules || q.rules.length === 0) return JSON.stringify({ 
combinator: "and", rules: [] });
+      return JSON.stringify({ combinator: q.combinator || "and", rules: 
q.rules });
+    };
+
+    const entityQueryChanged =
+      normalizeQuery(typeQuery) !== normalizeQuery(entityFiltersFromUrl || 
initialQuery);
+    const tagQueryChanged =
+      normalizeQuery(classificationQuery) !== normalizeQuery(tagFiltersFromUrl 
|| initialQuery);
+    const relationshipQueryChanged =
+      normalizeQuery(relationshipQuery) !== 
normalizeQuery(relationshipFiltersFromUrl || initialQuery);
+
+    return switchChanged || entityQueryChanged || tagQueryChanged || 
relationshipQueryChanged;
+  }, [
+    checkedEntities,
+    checkedSubClassifications,
+    checkedSubTypes,
+    appliedIncludeDE,
+    appliedExcludeSC,
+    appliedExcludeST,
+    typeQuery,
+    classificationQuery,
+    relationshipQuery,
+    entityFiltersFromUrl,
+    tagFiltersFromUrl,
+    relationshipFiltersFromUrl
+  ]);
+
   const handleSwitchChangeEntities = (
     event: React.ChangeEvent<HTMLInputElement>
   ) => {
     event.stopPropagation();
-    searchParams.set("includeDE", event.target.checked);
-    setCheckedEntities(event.target.checked);
+    setCheckedEntities(Boolean(event.target.checked));
   };
 
   const handleSwitchChangeSubClassification = (
     event: React.ChangeEvent<HTMLInputElement>
   ) => {
     event.stopPropagation();
-    searchParams.set("excludeSC", event.target.checked);
-    setCheckedSubClassifications(event.target.checked);
+    setCheckedSubClassifications(Boolean(event.target.checked));
   };
 
   const handleSwitchChangeSubTypes = (
     event: React.ChangeEvent<HTMLInputElement>
   ) => {
     event.stopPropagation();
-    searchParams.set("excludeST", event.target.checked);
-    setCheckedSubTypes(event.target.checked);
+    setCheckedSubTypes(Boolean(event.target.checked));
   };
 
   const paramsObject: Record<string, any> = {};
@@ -187,9 +261,9 @@ const Filters = ({
     let rules_widgets = null;
     let systemAttrArr;
 
-    if (!isEmpty(paramsObject)) {
+    if (!isEmpty(paramsObject?.entityFilters)) {
       rules_widgets = attributeFilter.extractUrl({
-        value: undefined,
+        value: paramsObject.entityFilters,
         formatDate: true
       });
     }
@@ -699,6 +773,7 @@ const Filters = ({
                 <CustomButton
                   variant="contained"
                   size="small"
+                  disabled={!hasChanges}
                   onClick={() => {
                     applyFilter();
                   }}
diff --git a/dashboard/src/components/ShowMore/DrawerBodyChipView.tsx 
b/dashboard/src/components/ShowMore/DrawerBodyChipView.tsx
index 5a082ef7e..38f702af6 100644
--- a/dashboard/src/components/ShowMore/DrawerBodyChipView.tsx
+++ b/dashboard/src/components/ShowMore/DrawerBodyChipView.tsx
@@ -388,6 +388,7 @@ const DrawerBodyChipView = ({
           button2Label="Remove"
           button2Handler={handleRemove}
           disableButton2={removeLoader}
+          button2Loading={removeLoader}
         >
           <Typography fontSize={14}>
             Remove:{" "}
diff --git a/dashboard/src/components/ShowMore/ShowMoreView.tsx 
b/dashboard/src/components/ShowMore/ShowMoreView.tsx
index 5a0f1cfc0..3761d0a4d 100644
--- a/dashboard/src/components/ShowMore/ShowMoreView.tsx
+++ b/dashboard/src/components/ShowMore/ShowMoreView.tsx
@@ -182,6 +182,8 @@ const ShowMoreView = ({
       }
     } catch (error) {
       serverError(error, toastId);
+    } finally {
+      setRemoveLoader(false);
     }
   };
 
@@ -379,6 +381,7 @@ const ShowMoreView = ({
           button2Label="Remove"
           button2Handler={handleRemove}
           disableButton2={removeLoader}
+          button2Loading={removeLoader}
         >
           <Typography fontSize={14}>
             Remove:{" "}
diff --git a/dashboard/src/components/Table/TablePagination.tsx 
b/dashboard/src/components/Table/TablePagination.tsx
index 7577f0007..792d1a711 100644
--- a/dashboard/src/components/Table/TablePagination.tsx
+++ b/dashboard/src/components/Table/TablePagination.tsx
@@ -369,6 +369,18 @@ const TablePagination: React.FC<PaginationProps> = ({
     ? pageTo
     : Math.min((pageIndex + 1) * pageSize, totalDatasetRows);
 
+  /** Last page may return fewer than `limit` rows; cap "to" at known total. */
+  const displayToCapped =
+    isServerSide &&
+    typeof totalCount === "number" &&
+    totalCount >= 0
+      ? Math.min(displayTo, totalCount)
+      : displayTo;
+
+  const footerRangeStart =
+    totalDatasetRows === 0 ? 0 : Math.min(displayFrom, displayToCapped);
+  const footerRangeEnd = totalDatasetRows === 0 ? 0 : displayToCapped;
+
   return (
     <Stack
       spacing={{ xs: 1, sm: 2 }}
@@ -381,8 +393,16 @@ const TablePagination: React.FC<PaginationProps> = ({
     >
       <div>
         <span className="text-grey">
-          Showing <u>{totalDatasetRows.toLocaleString()} records</u> From{" "}
-          {displayFrom} - {displayTo}
+          {totalDatasetRows === 0 ? (
+            "No records to display"
+          ) : (
+            <>
+              Showing {footerRangeStart.toLocaleString()}-
+              {footerRangeEnd.toLocaleString()} of{" "}
+              {totalDatasetRows.toLocaleString()}{" "}
+              {totalDatasetRows === 1 ? "record" : "records"}
+            </>
+          )}
         </span>
       </div>
 
@@ -491,25 +511,27 @@ const TablePagination: React.FC<PaginationProps> = ({
                     value={pendingGoToPageVal}
                   />
                   <LightTooltip title="Goto Page">
-                    <IconButton
-                      type="button"
-                      size="small"
-                      className={`${
-                        !isEmpty(pendingGoToPageVal)
-                          ? "cursor-pointer"
-                          : "cursor-not-allowed"
-                      } table-pagination-gotopage-button`}
-                      aria-label="search"
-                      onClick={() => {
-                        if (!isEmpty(pendingGoToPageVal)) {
-                          setGoToPageTrigger(pendingGoToPageVal);
-                          handleGoToPage();
-                        }
-                      }}
-                      disabled={isEmpty(pendingGoToPageVal)}
-                    >
-                      Go
-                    </IconButton>
+                    <span style={{ display: "inline-flex" }}>
+                      <IconButton
+                        type="button"
+                        size="small"
+                        className={`${
+                          !isEmpty(pendingGoToPageVal)
+                            ? "cursor-pointer"
+                            : "cursor-not-allowed"
+                        } table-pagination-gotopage-button`}
+                        aria-label="search"
+                        onClick={() => {
+                          if (!isEmpty(pendingGoToPageVal)) {
+                            setGoToPageTrigger(pendingGoToPageVal);
+                            handleGoToPage();
+                          }
+                        }}
+                        disabled={isEmpty(pendingGoToPageVal)}
+                      >
+                        Go
+                      </IconButton>
+                    </span>
                   </LightTooltip>
                 </Paper>
               </Stack>
@@ -517,19 +539,21 @@ const TablePagination: React.FC<PaginationProps> = ({
 
             <Stack flexDirection="row" alignItems="center">
               <LightTooltip title="Previous">
-                <IconButton
-                  size="small"
-                  className="pagination-page-change-btn"
-                  onClick={handlePreviousPage}
-                  disabled={isPreviousDisabled}
-                  aria-label="previous page"
-                >
-                  {theme.direction === "rtl" ? (
-                    <KeyboardArrowRight />
-                  ) : (
-                    <KeyboardArrowLeft />
-                  )}
-                </IconButton>
+                <span style={{ display: "inline-flex" }}>
+                  <IconButton
+                    size="small"
+                    className="pagination-page-change-btn"
+                    onClick={handlePreviousPage}
+                    disabled={isPreviousDisabled}
+                    aria-label="previous page"
+                  >
+                    {theme.direction === "rtl" ? (
+                      <KeyboardArrowRight />
+                    ) : (
+                      <KeyboardArrowLeft />
+                    )}
+                  </IconButton>
+                </span>
               </LightTooltip>
 
               <LightTooltip title={`Page ${activePage}`}>
@@ -539,19 +563,21 @@ const TablePagination: React.FC<PaginationProps> = ({
               </LightTooltip>
 
               <LightTooltip title="Next">
-                <IconButton
-                  size="small"
-                  className="pagination-page-change-btn"
-                  onClick={handleNextPage}
-                  disabled={isNextDisabled}
-                  aria-label="next page"
-                >
-                  {theme.direction === "rtl" ? (
-                    <KeyboardArrowLeft />
-                  ) : (
-                    <KeyboardArrowRight />
-                  )}
-                </IconButton>
+                <span style={{ display: "inline-flex" }}>
+                  <IconButton
+                    size="small"
+                    className="pagination-page-change-btn"
+                    onClick={handleNextPage}
+                    disabled={isNextDisabled}
+                    aria-label="next page"
+                  >
+                    {theme.direction === "rtl" ? (
+                      <KeyboardArrowLeft />
+                    ) : (
+                      <KeyboardArrowRight />
+                    )}
+                  </IconButton>
+                </span>
               </LightTooltip>
             </Stack>
           </>
diff --git a/dashboard/src/components/TreeNodeIcons.tsx 
b/dashboard/src/components/TreeNodeIcons.tsx
index 3e56f70e8..5403a2a9d 100644
--- a/dashboard/src/components/TreeNodeIcons.tsx
+++ b/dashboard/src/components/TreeNodeIcons.tsx
@@ -16,6 +16,7 @@
  */
 
 import { useRef, useState } from "react";
+import { useAsyncPending } from "../hooks/useAsyncPending";
 import IconButton from "@mui/material/IconButton";
 import Menu from "@mui/material/Menu";
 import Stack from "@mui/material/Stack";
@@ -72,6 +73,9 @@ const TreeNodeIcons = (props: {
   const [glossaryModal, setGlossaryModal] = useState<boolean>(false);
   const [termModal, setTermModal] = useState(false);
   const [categoryModal, setCategoryModal] = useState(false);
+  const [savedSearchDeleteLoading, setSavedSearchDeleteLoading] =
+    useState(false);
+  const { pending: renameSubmitting, run: runRenameAction } = 
useAsyncPending();
 
   const openNode = Boolean(expandNode);
 
@@ -128,6 +132,7 @@ const TreeNodeIcons = (props: {
 
   const handleRemove = async () => {
     try {
+      setSavedSearchDeleteLoading(true);
       await removeSavedSearch(selectedSearchData.guid);
       setDeleteModal(false);
       setExpandNode(null);
@@ -142,23 +147,27 @@ const TreeNodeIcons = (props: {
       toastId.current = toast.success(`${node.id} was deleted successfully`);
     } catch (error) {
       serverError(error, toastId);
+    } finally {
+      setSavedSearchDeleteLoading(false);
     }
   };
 
-  const handleEdit = async () => {
-    try {
-      let filterData = { ...selectedSearchData, name: value };
-      await editSavedSearch(filterData as CustomFiltersNodeType, "PUT");
-      updatedData();
-      setRenameModal(false);
-      setExpandNode(null);
-      toast.dismiss(toastId.current);
-      toastId.current = toast.success(
-        `${filterData.name} was updated successfully`
-      );
-    } catch (error) {
-      serverError(error, toastId);
-    }
+  const handleEdit = () => {
+    void runRenameAction(async () => {
+      try {
+        let filterData = { ...selectedSearchData, name: value };
+        await editSavedSearch(filterData as CustomFiltersNodeType, "PUT");
+        updatedData();
+        setRenameModal(false);
+        setExpandNode(null);
+        toast.dismiss(toastId.current);
+        toastId.current = toast.success(
+          `${filterData.name} was updated successfully`
+        );
+      } catch (error) {
+        serverError(error, toastId);
+      }
+    });
   };
   return (
     <>
@@ -483,7 +492,8 @@ const TreeNodeIcons = (props: {
         button1Handler={handleCloseRenameModal}
         button2Label="Update"
         button2Handler={handleEdit}
-        // disableButton2={value}
+        disableButton2={renameSubmitting}
+        button2Loading={renameSubmitting}
       >
         <Stack
           sx={{
@@ -518,6 +528,8 @@ const TreeNodeIcons = (props: {
         button1Handler={handleCloseDeleteModal}
         button2Label="Ok"
         button2Handler={handleRemove}
+        disableButton2={savedSearchDeleteLoading}
+        button2Loading={savedSearchDeleteLoading}
       >
         <Typography fontSize={15}>
           Are you sure you want to delete <strong>{node.id}</strong> ?{""}
diff --git a/dashboard/src/utils/Enum.ts b/dashboard/src/utils/Enum.ts
index 76ee0712d..50644400b 100644
--- a/dashboard/src/utils/Enum.ts
+++ b/dashboard/src/utils/Enum.ts
@@ -319,6 +319,15 @@ export const queryBuilderUIOperatorToAPI = {
 
 export const queryBuilderApiOperatorToUI = invert(queryBuilderUIOperatorToAPI);
 
+/**
+ * Converts API operator (eq, lte, gte, etc.) to display symbol (=, <=, >=, 
etc.)
+ * Used when rendering filter query chips above search results table
+ */
+export const getDisplayOperator = (operator: string): string => {
+       const mapped = (queryBuilderApiOperatorToUI as Map<string, 
string>).get(operator);
+       return mapped ?? operator;
+};
+
 export const queryBuilderDateRangeUIValueToAPI: Record<string, string> = {
   Today: "TODAY",
   Yesterday: "YESTERDAY",
diff --git 
a/dashboard/src/views/BusinessMetadata/BusinessMetadataAtrributeForm.tsx 
b/dashboard/src/views/BusinessMetadata/BusinessMetadataAtrributeForm.tsx
index 6e161f68f..d32eb7017 100644
--- a/dashboard/src/views/BusinessMetadata/BusinessMetadataAtrributeForm.tsx
+++ b/dashboard/src/views/BusinessMetadata/BusinessMetadataAtrributeForm.tsx
@@ -898,6 +898,7 @@ const BusinessMetadataAttributeForm = ({
               maxWidth="sm"
               button2Handler={handleSubmit(onSubmit)}
               disableButton2={isSubmitting}
+              button2Loading={isSubmitting}
             >
               <Stack gap={2} paddingTop="2rem" paddingBottom="2rem">
                 <EnumCreateUpdate
diff --git a/dashboard/src/views/BusinessMetadata/BusinessMetadataForm.tsx 
b/dashboard/src/views/BusinessMetadata/BusinessMetadataForm.tsx
index 950a6b3ff..9d31691e5 100644
--- a/dashboard/src/views/BusinessMetadata/BusinessMetadataForm.tsx
+++ b/dashboard/src/views/BusinessMetadata/BusinessMetadataForm.tsx
@@ -42,6 +42,7 @@ import BusinessMetadataAttributeForm from 
"./BusinessMetadataAtrributeForm";
 import { setEditBMAttribute } from "@redux/slice/createBMSlice";
 import { cloneDeep } from "@utils/Helper";
 import { fetchBusinessMetaData } from 
"@redux/slice/typeDefSlices/typedefBusinessMetadataSlice";
+import { fetchEntityData } from 
"@redux/slice/typeDefSlices/typedefEntitySlice";
 import { defaultType } from "@utils/Enum";
 import { getTypeName } from "@utils/CommonViewFunction";
 
@@ -340,6 +341,7 @@ const BusinessMetaDataForm = ({
       let bmName = response?.data?.businessMetadataDefs?.[0]?.name;
       toastMssg(bmName);
       dispatchState(fetchBusinessMetaData());
+      dispatchState(fetchEntityData());
       setBMAttribute({});
       setForm(false);
     } catch (error) {
diff --git 
a/dashboard/src/views/DetailPage/BusinessMetadataDetails/BusinessMetadataDetailsLayout.tsx
 
b/dashboard/src/views/DetailPage/BusinessMetadataDetails/BusinessMetadataDetailsLayout.tsx
index bb6f8f138..214c9faad 100644
--- 
a/dashboard/src/views/DetailPage/BusinessMetadataDetails/BusinessMetadataDetailsLayout.tsx
+++ 
b/dashboard/src/views/DetailPage/BusinessMetadataDetails/BusinessMetadataDetailsLayout.tsx
@@ -45,6 +45,7 @@ import { createEditBusinessMetadata } from 
"@api/apiMethods/typeDefApiMethods";
 import { cloneDeep } from "@utils/Helper";
 import { defaultAttrObj, defaultType } from "@utils/Enum";
 import { fetchBusinessMetaData } from 
"@redux/slice/typeDefSlices/typedefBusinessMetadataSlice";
+import { fetchEntityData } from 
"@redux/slice/typeDefSlices/typedefEntitySlice";
 import { getTypeName } from "@utils/CommonViewFunction";
 
 const BusinessMetadataDetailsLayout = () => {
@@ -241,6 +242,7 @@ const BusinessMetadataDetailsLayout = () => {
     try {
       await createEditBusinessMetadata("business_metadata", "PUT", data);
       dispatchState(fetchBusinessMetaData());
+      dispatchState(fetchEntityData());
       toast.success(
         "One or more Business Metadata attributes were updated successfully"
       );
diff --git 
a/dashboard/src/views/DetailPage/EntityDetailTabs/PropagationPropertyModal.tsx 
b/dashboard/src/views/DetailPage/EntityDetailTabs/PropagationPropertyModal.tsx
index 5ab64aef6..1ea05c701 100644
--- 
a/dashboard/src/views/DetailPage/EntityDetailTabs/PropagationPropertyModal.tsx
+++ 
b/dashboard/src/views/DetailPage/EntityDetailTabs/PropagationPropertyModal.tsx
@@ -421,6 +421,7 @@ const PropagationPropertyModal = ({
         button2Label="Update"
         button2Handler={() => onSubmit()}
         disableButton2={loading}
+        button2Loading={loading}
       >
         <Stack gap={2}>
           <Stack direction="row" alignItems="center" gap="0.5rem">
diff --git a/dashboard/src/views/Entity/EntityForm.tsx 
b/dashboard/src/views/Entity/EntityForm.tsx
index f075b943f..e6a66fa99 100644
--- a/dashboard/src/views/Entity/EntityForm.tsx
+++ b/dashboard/src/views/Entity/EntityForm.tsx
@@ -521,6 +521,7 @@ const EntityForm = ({
       button2Label={guid ? "Update" : "Create"}
       button2Handler={handleSubmit(onSubmit)}
       disableButton2={isSubmitting}
+      button2Loading={isSubmitting}
       isDirty={isDirty}
       maxWidth="md"
     >
diff --git a/dashboard/src/views/SaveFilters/SaveFilters.tsx 
b/dashboard/src/views/SaveFilters/SaveFilters.tsx
index fed2fe27f..ac65237ec 100644
--- a/dashboard/src/views/SaveFilters/SaveFilters.tsx
+++ b/dashboard/src/views/SaveFilters/SaveFilters.tsx
@@ -206,6 +206,7 @@ const SaveFilters = ({
         maxWidth="sm"
         button2Handler={handleSubmit(onSubmit)}
         disableButton2={isSubmitting}
+        button2Loading={isSubmitting}
       >
         {" "}
         <form onSubmit={handleSubmit(onSubmit)}>
diff --git a/dashboard/src/views/Statistics/ServerStats.tsx 
b/dashboard/src/views/Statistics/ServerStats.tsx
index 9f6974614..5f131c41c 100644
--- a/dashboard/src/views/Statistics/ServerStats.tsx
+++ b/dashboard/src/views/Statistics/ServerStats.tsx
@@ -118,12 +118,11 @@ const ServerStats = ({ selectedValue, currentMetricsData 
}: any) => {
 
   let notificationTableHeader = [
     "Count",
-    "Avg",
-    "Time (ms)",
+    "Avg time (ms)",
     "Creates",
     "Updates",
-    "Deletes"
-    // "Failed"
+    "Deletes",
+    "Failed"
   ];
 
   let topciOffsetTableHeader = [
diff --git a/dashboard/src/views/Statistics/Statistics.tsx 
b/dashboard/src/views/Statistics/Statistics.tsx
index 6204a6d08..51bb42077 100644
--- a/dashboard/src/views/Statistics/Statistics.tsx
+++ b/dashboard/src/views/Statistics/Statistics.tsx
@@ -23,6 +23,7 @@ import ClassificationStats from "./ClassificationStats";
 import {
   Autocomplete,
   Badge,
+  CircularProgress,
   IconButton,
   Stack,
   TextField
@@ -72,6 +73,7 @@ const Statistics = ({
     value: "Current"
   });
   const [loading, setLoading] = useState(true);
+  const [refreshBusy, setRefreshBusy] = useState(false);
 
   useEffect(() => {
     fetchMetricsStatsDetails();
@@ -117,12 +119,20 @@ const Statistics = ({
   };
 
   const handleRefresh = async () => {
-    await fetchMetricsStatsDetails();
-    await dispatch(fetchMetricEntity());
-    if (toastId.current) {
-      toast.dismiss(toastId.current);
+    if (refreshBusy) {
+      return;
+    }
+    setRefreshBusy(true);
+    try {
+      await fetchMetricsStatsDetails();
+      await dispatch(fetchMetricEntity());
+      if (toastId.current) {
+        toast.dismiss(toastId.current);
+      }
+      toastId.current = toast.success("Metric data is refreshed");
+    } finally {
+      setRefreshBusy(false);
     }
-    toastId.current = toast.success("Metric data is refreshed");
   };
 
   return (
@@ -137,14 +147,23 @@ const Statistics = ({
         postTitleIcon={
           <Stack justifyContent="center" alignItems="center">
             <LightTooltip title="Refresh Data">
-              <IconButton
-                size="small"
-                onClick={() => {
-                  handleRefresh();
-                }}
-              >
-                <Refresh />
-              </IconButton>
+              <span>
+                <IconButton
+                  size="small"
+                  onClick={() => {
+                    void handleRefresh();
+                  }}
+                  disabled={refreshBusy || loading}
+                  aria-busy={refreshBusy}
+                  aria-label="Refresh metrics data"
+                >
+                  {refreshBusy ? (
+                    <CircularProgress size={20} thickness={4} color="inherit" 
/>
+                  ) : (
+                    <Refresh />
+                  )}
+                </IconButton>
+              </span>
             </LightTooltip>
           </Stack>
         }

Reply via email to