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');
}