This is an automated email from the ASF dual-hosted git repository.

ppawar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/atlas.git


The following commit(s) were added to refs/heads/master by this push:
     new 2e161e6bd ATLAS-5146: Statistics UI: Kafka topics should be listed in 
sorter order, along with average processing time field (#469)
2e161e6bd is described below

commit 2e161e6bd7f32b047c43db7827426dd6be42c1b6
Author: Prasad Pawar <[email protected]>
AuthorDate: Fri Nov 21 15:29:23 2025 +0530

    ATLAS-5146: Statistics UI: Kafka topics should be listed in sorter order, 
along with average processing time field (#469)
---
 dashboard/src/styles/commonComponents.scss         |  2 +-
 dashboard/src/views/Statistics/ServerStats.tsx     | 89 ++++++++++++++++------
 dashboard/src/views/Statistics/Statistics.tsx      |  4 +-
 dashboardv2/public/css/scss/override.scss          | 37 +++++++++
 .../site/Statistics_Topic_Offset_table_tmpl.html   | 13 ++--
 .../public/js/views/site/StatisticsTimelineView.js | 59 +++++++++++---
 dashboardv3/public/css/scss/override.scss          | 35 +++++++++
 .../site/Statistics_Topic_Offset_table_tmpl.html   | 15 ++--
 .../public/js/views/site/StatisticsTimelineView.js | 59 +++++++++++---
 9 files changed, 255 insertions(+), 58 deletions(-)

diff --git a/dashboard/src/styles/commonComponents.scss 
b/dashboard/src/styles/commonComponents.scss
index 93aade94c..d16457a8f 100644
--- a/dashboard/src/styles/commonComponents.scss
+++ b/dashboard/src/styles/commonComponents.scss
@@ -94,7 +94,7 @@
 }
 
 .MuiDialog-paperWidthMd {
-  max-width: 800px !important;
+  max-width: 1110px !important;
 }
 
 .chip-items {
diff --git a/dashboard/src/views/Statistics/ServerStats.tsx 
b/dashboard/src/views/Statistics/ServerStats.tsx
index 32a9cc18a..5edc0532d 100644
--- a/dashboard/src/views/Statistics/ServerStats.tsx
+++ b/dashboard/src/views/Statistics/ServerStats.tsx
@@ -31,6 +31,7 @@ import {
   AccordionSummary
 } from "@components/muiComponents";
 import { numberFormatWithComma } from "@utils/Helper";
+import { useMemo, useState } from "react";
 import { useAppSelector } from "@hooks/reducerHook";
 import { isEmpty } from "@utils/Utils";
 import { stats } from "@utils/Enum";
@@ -81,6 +82,40 @@ const ServerStats = ({ selectedValue, currentMetricsData }: 
any) => {
     serverData?.Notification?.topicDetails
   );
 
+  const [topicSort, setTopicSort] = useState<{
+    key: string;
+    order: "asc" | "desc";
+  }>({ key: "label", order: "asc" });
+
+  const handleSort = (key: string) => {
+    setTopicSort((prev) => {
+      const nextOrder = prev.key === key && prev.order === "asc" ? "desc" : 
"asc";
+      return { key, order: nextOrder };
+    });
+  };
+
+  const sortedOffsetTableData = useMemo(() => {
+    const list = Array.isArray(offSetTabelData) ? [...offSetTabelData] : [];
+    const { key, order } = topicSort;
+    const dir = order === "asc" ? 1 : -1;
+    return list.sort((a: any, b: any) => {
+      if (key === "label") {
+        const av = String(a.label || "").toLowerCase();
+        const bv = String(b.label || "").toLowerCase();
+        if (av < bv) return -1 * dir;
+        if (av > bv) return 1 * dir;
+        return 0;
+      }
+      const aTopic = serverData?.Notification?.topicDetails?.[a.label] || {};
+      const bTopic = serverData?.Notification?.topicDetails?.[b.label] || {};
+      const av = Number(aTopic?.[key] ?? 0);
+      const bv = Number(bTopic?.[key] ?? 0);
+      if (av < bv) return -1 * dir;
+      if (av > bv) return 1 * dir;
+      return 0;
+    });
+  }, [JSON.stringify(offSetTabelData), 
JSON.stringify(serverData?.Notification?.topicDetails), topicSort]);
+
   let notificationTableHeader = [
     "Count",
     "Avg",
@@ -91,20 +126,13 @@ const ServerStats = ({ selectedValue, currentMetricsData 
}: any) => {
     // "Failed"
   ];
 
-  let topicOffsetTableCol = [
-    "Kafka Topic-Partition",
-    "Start Offset",
-    "Current Offset",
-    "Processed",
-    "Failed",
-    "Last Message Processed Time"
-  ];
   let topciOffsetTableHeader = [
     "offsetStart",
     "offsetCurrent",
     "processedMessageCount",
     "failedMessageCount",
-    "lastMessageProcessedTime"
+    "lastMessageProcessedTime",
+    "avgProcessingTime"
   ];
 
   let tableCol = [
@@ -235,15 +263,28 @@ const ServerStats = ({ selectedValue, currentMetricsData 
}: any) => {
               <Table className="classificationTable" size="small">
                 <TableHead>
                   <TableRow>
-                    {topicOffsetTableCol.map((col) => {
-                      return (
-                        <>
-                          <TableCell align="left">
-                            <Typography fontWeight="600">{col}</Typography>
-                          </TableCell>
-                        </>
-                      );
-                    })}
+                    <TableCell align="left" onClick={() => 
handleSort("label")} sx={{ cursor: "pointer" }}>
+                      <Typography fontWeight="600">
+                        Kafka Topic-Partition{topicSort.key === "label" ? 
(topicSort.order === "asc" ? " ▲" : " ▼") : ""}
+                      </Typography>
+                    </TableCell>
+                    {topciOffsetTableHeader.map((key) => (
+                      <TableCell key={key} align="left" onClick={() => 
handleSort(key)} sx={{ cursor: "pointer" }}>
+                        <Typography fontWeight="600">
+                          {(
+                            {
+                              offsetStart: "Start Offset",
+                              offsetCurrent: "Current Offset",
+                              processedMessageCount: "Processed",
+                              failedMessageCount: "Failed",
+                              lastMessageProcessedTime: "Last Message 
Processed Time",
+                              avgProcessingTime: "Average message processing 
time"
+                            } as any
+                          )[key]}
+                          {topicSort.key === key ? (topicSort.order === "asc" 
? " ▲" : " ▼") : ""}
+                        </Typography>
+                      </TableCell>
+                    ))}
                   </TableRow>
                 </TableHead>
                 <TableBody>
@@ -254,20 +295,22 @@ const ServerStats = ({ selectedValue, currentMetricsData 
}: any) => {
                       </TableCell>
                     </TableRow>
                   ) : (
-                    offSetTabelData?.map((obj, index) => (
+                    sortedOffsetTableData?.map((obj, index) => (
                       <TableRow key={index}>
                         <TableCell>{obj.label}</TableCell>
                         {topciOffsetTableHeader.map((header) => {
                           let returnVal =
-                            serverData?.Notification?.topicDetails?.[
-                              obj.label
-                            ]?.[header];
+                            
serverData?.Notification?.topicDetails?.[obj.label]?.[header];
+                          const typeForKey =
+                            header === "avgProcessingTime"
+                              ? stats.Notification["lastMessageProcessedTime"]
+                              : stats.Notification[header];
                           return (
                             <TableCell align="left">
                               {returnVal
                                 ? getStatsValue({
                                     value: returnVal,
-                                    type: stats.Notification[header]
+                                    type: typeForKey
                                   })
                                 : 0}
                             </TableCell>
diff --git a/dashboard/src/views/Statistics/Statistics.tsx 
b/dashboard/src/views/Statistics/Statistics.tsx
index f6c0eecb6..6204a6d08 100644
--- a/dashboard/src/views/Statistics/Statistics.tsx
+++ b/dashboard/src/views/Statistics/Statistics.tsx
@@ -158,6 +158,7 @@ const Statistics = ({
             <Autocomplete
               value={selectedValue}
               onChange={(_event: any, newValue: any) => {
+                if (!newValue) return;
                 const { value } = newValue;
                 setSelectedValue(newValue);
                 if (value != "Current") {
@@ -169,6 +170,7 @@ const Statistics = ({
                 ...metricStatsListOptions
               ]}
               getOptionLabel={(option) => option.label}
+              isOptionEqualToValue={(option, value) => option?.value === 
value?.value}
               disableClearable
               clearOnEscape={false}
               size="small"
@@ -180,7 +182,7 @@ const Statistics = ({
                   fullWidth
                 />
               )}
-              defaultValue={"Current"}
+              defaultValue={{ label: "Current", value: "Current" }}
               sx={{
                 minWidth: "250px",
                 backgroundColor: "#f6f7fb",
diff --git a/dashboardv2/public/css/scss/override.scss 
b/dashboardv2/public/css/scss/override.scss
index 26b706547..269530288 100644
--- a/dashboardv2/public/css/scss/override.scss
+++ b/dashboardv2/public/css/scss/override.scss
@@ -665,4 +665,41 @@ div.columnmanager-dropdown-container {
 }
 .search-tab-body{
     padding: 0px 18px 18px 18px !important;
+}
+
+th.sortable {
+    cursor: pointer;
+    user-select: none;
+    position: relative;
+    padding-right: 18px; 
+}
+
+table > thead > tr > th.sortable {
+    padding-right: 24px !important; 
+}
+.sort-icon {
+    display: inline-block;
+    position: absolute;
+    right: 8px;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 0;
+    height: 0;
+    border-left: 4px solid transparent;
+    border-right: 4px solid transparent;
+    border-top: 6px solid #bbb; 
+}
+.sort-icon.asc {
+    border-top-color: #38bb9b; 
+    transform: translateY(-50%) rotate(180deg);
+}
+.sort-icon.desc {
+    border-top-color: #38bb9b; 
+}
+
+th.sortable[data-sort-key="processedMessageCount"],
+th.sortable[data-sort-key="failedMessageCount"] {
+    white-space: nowrap;
+    vertical-align: top;
+    text-align: center;
 }
\ No newline at end of file
diff --git 
a/dashboardv2/public/js/templates/site/Statistics_Topic_Offset_table_tmpl.html 
b/dashboardv2/public/js/templates/site/Statistics_Topic_Offset_table_tmpl.html
index b4c8d8677..77fadb407 100644
--- 
a/dashboardv2/public/js/templates/site/Statistics_Topic_Offset_table_tmpl.html
+++ 
b/dashboardv2/public/js/templates/site/Statistics_Topic_Offset_table_tmpl.html
@@ -16,12 +16,13 @@
 -->
 <thead>
     <tr>
-        <th>Kafka Topic-Partition</th>
-        <th>Start Offset</th>
-        <th>Current Offset</th>
-        <th>Processed</th>
-        <th>Failed</th>
-        <th>Last Message Processed Time</th>
+        <th data-sort-key="label" class="sortable">Kafka Topic-Partition <span 
class="sort-icon"></span></th>
+        <th data-sort-key="offsetStart" class="sortable">Start Offset <span 
class="sort-icon"></span></th>
+        <th data-sort-key="offsetCurrent" class="sortable">Current Offset 
<span class="sort-icon"></span></th>
+        <th data-sort-key="processedMessageCount" class="sortable">Processed 
<span class="sort-icon"></span></th>
+        <th data-sort-key="failedMessageCount" class="sortable">Failed <span 
class="sort-icon"></span></th>
+        <th data-sort-key="lastMessageProcessedTime" class="sortable">Last 
Message Processed Time <span class="sort-icon"></span></th>
+        <th data-sort-key="avgProcessingTime" class="sortable">Average message 
processing time <span class="sort-icon"></span></th>
     </tr>
 </thead>
 {{#if data}}
diff --git a/dashboardv2/public/js/views/site/StatisticsTimelineView.js 
b/dashboardv2/public/js/views/site/StatisticsTimelineView.js
index d7caa60e7..b6a604fe8 100644
--- a/dashboardv2/public/js/views/site/StatisticsTimelineView.js
+++ b/dashboardv2/public/js/views/site/StatisticsTimelineView.js
@@ -696,17 +696,56 @@ define(['require',
                         return returnObj
                     }
 
-                    that.ui.offsetCard.html(
-                        TopicOffsetTable({
-                            data: data.Notification.topicDetails,
-                            tableHeader: ["offsetStart", "offsetCurrent", 
"processedMessageCount", "failedMessageCount", "lastMessageProcessedTime"],
-                            tableCol: 
offsetTableColumn(data.Notification.topicDetails),
-                            getTmplValue: function(argument, args) {
-                                var returnVal = 
data.Notification.topicDetails[argument.label][args];
-                                return returnVal ? that.getValue({ value: 
returnVal, type: Enums.stats.Notification[args] }) : 0;
+                    var sortState = { key: 'label', order: 'asc' };
+                    var renderTopicTable = function(){
+                        var rows = 
offsetTableColumn(data.Notification.topicDetails);
+                        var dir = sortState.order === 'asc' ? 1 : -1;
+                        rows = rows.sort(function(a, b){
+                            if (sortState.key === 'label') {
+                                var av = (a.label || '').toLowerCase();
+                                var bv = (b.label || '').toLowerCase();
+                                if (av < bv) return -1 * dir;
+                                if (av > bv) return 1 * dir;
+                                return 0;
                             }
-                        })
-                    )
+                            var at = data.Notification.topicDetails[a.label] 
|| {};
+                            var bt = data.Notification.topicDetails[b.label] 
|| {};
+                            var avn = Number(at[sortState.key] || 0);
+                            var bvn = Number(bt[sortState.key] || 0);
+                            if (avn < bvn) return -1 * dir;
+                            if (avn > bvn) return 1 * dir;
+                            return 0;
+                        });
+
+                        that.ui.offsetCard.html(
+                            TopicOffsetTable({
+                                data: data.Notification.topicDetails,
+                                tableHeader: ["offsetStart", "offsetCurrent", 
"processedMessageCount", "failedMessageCount", "lastMessageProcessedTime", 
"avgProcessingTime"],
+                                tableCol: rows,
+                                getTmplValue: function(argument, args) {
+                                    var returnVal = 
data.Notification.topicDetails[argument.label][args];
+                                    var typeKey = (args === 
'avgProcessingTime') ? 'lastMessageProcessedTime' : args;
+                                    return returnVal ? that.getValue({ value: 
returnVal, type: Enums.stats.Notification[typeKey] }) : 0;
+                                }
+                            })
+                        );
+
+                        var $ths = that.ui.offsetCard.find('th.sortable');
+                        $ths.find('.sort-icon').removeClass('asc desc');
+                        $ths.filter('[data-sort-key="' + sortState.key + 
'"]').find('.sort-icon').addClass(sortState.order);
+
+                        
that.ui.offsetCard.find('th.sortable').off('click').on('click', function(){
+                            var key = $(this).data('sort-key');
+                            if (sortState.key === key) {
+                                sortState.order = (sortState.order === 'asc') 
? 'desc' : 'asc';
+                            } else {
+                                sortState.key = key;
+                                sortState.order = 'asc';
+                            }
+                            renderTopicTable();
+                        });
+                    };
+                    renderTopicTable();
                     that.ui.notificationDetails.removeClass('hide');
                 }
 
diff --git a/dashboardv3/public/css/scss/override.scss 
b/dashboardv3/public/css/scss/override.scss
index 1f4a786f5..933fe45d0 100644
--- a/dashboardv3/public/css/scss/override.scss
+++ b/dashboardv3/public/css/scss/override.scss
@@ -636,4 +636,39 @@ div.columnmanager-dropdown-container {
     ul {
         list-style: disc
     }
+}
+ 
+th.sortable {
+    cursor: pointer;
+    user-select: none;
+    position: relative;
+    padding-right: 18px;
+}
+table > thead > tr > th.sortable {
+    padding-right: 24px !important;
+}
+.sort-icon {
+    display: inline-block;
+    position: absolute;
+    right: 8px;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 0;
+    height: 0;
+    border-left: 4px solid transparent;
+    border-right: 4px solid transparent;
+    border-top: 6px solid #bbb;
+}
+.sort-icon.asc {
+    border-top-color: #38bb9b;
+    transform: translateY(-50%) rotate(180deg);
+}
+.sort-icon.desc {
+    border-top-color: #38bb9b;
+}
+th.sortable[data-sort-key="processedMessageCount"],
+th.sortable[data-sort-key="failedMessageCount"] {
+    white-space: nowrap;
+    vertical-align: top;
+    text-align: center;
 }
\ No newline at end of file
diff --git 
a/dashboardv3/public/js/templates/site/Statistics_Topic_Offset_table_tmpl.html 
b/dashboardv3/public/js/templates/site/Statistics_Topic_Offset_table_tmpl.html
index b4c8d8677..37654cdcf 100644
--- 
a/dashboardv3/public/js/templates/site/Statistics_Topic_Offset_table_tmpl.html
+++ 
b/dashboardv3/public/js/templates/site/Statistics_Topic_Offset_table_tmpl.html
@@ -16,15 +16,16 @@
 -->
 <thead>
     <tr>
-        <th>Kafka Topic-Partition</th>
-        <th>Start Offset</th>
-        <th>Current Offset</th>
-        <th>Processed</th>
-        <th>Failed</th>
-        <th>Last Message Processed Time</th>
+        <th data-sort-key="label" class="sortable">Kafka Topic-Partition <span 
class="sort-icon"></span></th>
+        <th data-sort-key="offsetStart" class="sortable">Start Offset <span 
class="sort-icon"></span></th>
+        <th data-sort-key="offsetCurrent" class="sortable">Current Offset 
<span class="sort-icon"></span></th>
+        <th data-sort-key="processedMessageCount" class="sortable">Processed 
<span class="sort-icon"></span></th>
+        <th data-sort-key="failedMessageCount" class="sortable">Failed <span 
class="sort-icon"></span></th>
+        <th data-sort-key="lastMessageProcessedTime" class="sortable">Last 
Message Processed Time <span class="sort-icon"></span></th>
+        <th data-sort-key="avgProcessingTime" class="sortable">Average message 
processing time <span class="sort-icon"></span></th>
     </tr>
 </thead>
-{{#if data}}
+{{#if tableCol}}
 <tbody>
     {{#each tableCol}}
     <tr>
diff --git a/dashboardv3/public/js/views/site/StatisticsTimelineView.js 
b/dashboardv3/public/js/views/site/StatisticsTimelineView.js
index d7caa60e7..b6a604fe8 100644
--- a/dashboardv3/public/js/views/site/StatisticsTimelineView.js
+++ b/dashboardv3/public/js/views/site/StatisticsTimelineView.js
@@ -696,17 +696,56 @@ define(['require',
                         return returnObj
                     }
 
-                    that.ui.offsetCard.html(
-                        TopicOffsetTable({
-                            data: data.Notification.topicDetails,
-                            tableHeader: ["offsetStart", "offsetCurrent", 
"processedMessageCount", "failedMessageCount", "lastMessageProcessedTime"],
-                            tableCol: 
offsetTableColumn(data.Notification.topicDetails),
-                            getTmplValue: function(argument, args) {
-                                var returnVal = 
data.Notification.topicDetails[argument.label][args];
-                                return returnVal ? that.getValue({ value: 
returnVal, type: Enums.stats.Notification[args] }) : 0;
+                    var sortState = { key: 'label', order: 'asc' };
+                    var renderTopicTable = function(){
+                        var rows = 
offsetTableColumn(data.Notification.topicDetails);
+                        var dir = sortState.order === 'asc' ? 1 : -1;
+                        rows = rows.sort(function(a, b){
+                            if (sortState.key === 'label') {
+                                var av = (a.label || '').toLowerCase();
+                                var bv = (b.label || '').toLowerCase();
+                                if (av < bv) return -1 * dir;
+                                if (av > bv) return 1 * dir;
+                                return 0;
                             }
-                        })
-                    )
+                            var at = data.Notification.topicDetails[a.label] 
|| {};
+                            var bt = data.Notification.topicDetails[b.label] 
|| {};
+                            var avn = Number(at[sortState.key] || 0);
+                            var bvn = Number(bt[sortState.key] || 0);
+                            if (avn < bvn) return -1 * dir;
+                            if (avn > bvn) return 1 * dir;
+                            return 0;
+                        });
+
+                        that.ui.offsetCard.html(
+                            TopicOffsetTable({
+                                data: data.Notification.topicDetails,
+                                tableHeader: ["offsetStart", "offsetCurrent", 
"processedMessageCount", "failedMessageCount", "lastMessageProcessedTime", 
"avgProcessingTime"],
+                                tableCol: rows,
+                                getTmplValue: function(argument, args) {
+                                    var returnVal = 
data.Notification.topicDetails[argument.label][args];
+                                    var typeKey = (args === 
'avgProcessingTime') ? 'lastMessageProcessedTime' : args;
+                                    return returnVal ? that.getValue({ value: 
returnVal, type: Enums.stats.Notification[typeKey] }) : 0;
+                                }
+                            })
+                        );
+
+                        var $ths = that.ui.offsetCard.find('th.sortable');
+                        $ths.find('.sort-icon').removeClass('asc desc');
+                        $ths.filter('[data-sort-key="' + sortState.key + 
'"]').find('.sort-icon').addClass(sortState.order);
+
+                        
that.ui.offsetCard.find('th.sortable').off('click').on('click', function(){
+                            var key = $(this).data('sort-key');
+                            if (sortState.key === key) {
+                                sortState.order = (sortState.order === 'asc') 
? 'desc' : 'asc';
+                            } else {
+                                sortState.key = key;
+                                sortState.order = 'asc';
+                            }
+                            renderTopicTable();
+                        });
+                    };
+                    renderTopicTable();
                     that.ui.notificationDetails.removeClass('hide');
                 }
 

Reply via email to