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

dengliming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new 1bcd0397 fix: make table sorters compare the displayed field (#635) 
(#674)
1bcd0397 is described below

commit 1bcd0397cb9986b0a27609a5535793f110bca6c6
Author: satyam-311 <[email protected]>
AuthorDate: Sun Sep 27 10:47:37 2026 +0530

    fix: make table sorters compare the displayed field (#635) (#674)
    
    Several column sorters compared a different field from the column being
    sorted:
    
    - Instance: lastHeartBeatTime, dateCreated and instanceState compared
      instanceType.
    - Plugin and NamespacePlugin: the sort column compared role.
    
    Add small sorter helpers (sortByDate, sortByNumber) that compare the
    column's own value with a date or numeric comparator, and order missing
    or invalid values explicitly before present ones. Cover them with unit
    tests.
    
    Fixes #635
---
 src/routes/System/Instance/index.js        |  7 ++-
 src/routes/System/NamespacePlugin/index.js |  3 +-
 src/routes/System/Plugin/index.js          |  3 +-
 src/utils/sorter.js                        | 58 ++++++++++++++++++
 src/utils/sorter.test.js                   | 94 ++++++++++++++++++++++++++++++
 5 files changed, 160 insertions(+), 5 deletions(-)

diff --git a/src/routes/System/Instance/index.js 
b/src/routes/System/Instance/index.js
index 805cc685..64009697 100644
--- a/src/routes/System/Instance/index.js
+++ b/src/routes/System/Instance/index.js
@@ -34,6 +34,7 @@ import * as echarts from "echarts";
 import { resizableComponents } from "../../../utils/resizable";
 import { getCurrentLocale, getIntlContent } from "../../../utils/IntlUtils";
 import AuthButton from "../../../utils/AuthButton";
+import { sortByDate, sortByNumber } from "../../../utils/sorter";
 
 const { Text } = Typography;
 
@@ -409,7 +410,7 @@ export default class Instance extends Component {
           ellipsis: true,
           key: "lastHeartBeatTime",
           width: 120,
-          sorter: (a, b) => (a.instanceType > b.instanceType ? 1 : -1),
+          sorter: sortByDate("lastHeartBeatTime"),
           render: (text) => {
             return (
               <div style={{ color: "#1f640a" }}>
@@ -425,7 +426,7 @@ export default class Instance extends Component {
           ellipsis: true,
           key: "dateCreated",
           width: 120,
-          sorter: (a, b) => (a.instanceType > b.instanceType ? 1 : -1),
+          sorter: sortByDate("dateCreated"),
           render: (text) => {
             return (
               <div style={{ color: "#1f640a" }}>
@@ -441,7 +442,7 @@ export default class Instance extends Component {
           ellipsis: true,
           key: "instanceState",
           width: 120,
-          sorter: (a, b) => (a.instanceType > b.instanceType ? 1 : -1),
+          sorter: sortByNumber("instanceState"),
           render: (state) => {
             if (state === 1) {
               return (
diff --git a/src/routes/System/NamespacePlugin/index.js 
b/src/routes/System/NamespacePlugin/index.js
index 0fc9f335..3e21e7f6 100644
--- a/src/routes/System/NamespacePlugin/index.js
+++ b/src/routes/System/NamespacePlugin/index.js
@@ -33,6 +33,7 @@ import { Link } from "dva/router";
 import { resizableComponents } from "../../../utils/resizable";
 import { getCurrentLocale, getIntlContent } from "../../../utils/IntlUtils";
 import AuthButton from "../../../utils/AuthButton";
+import { sortByNumber } from "../../../utils/sorter";
 import { refreshAuthMenus } from "../../../utils/AuthRoute";
 import {
   getUpdateModal,
@@ -299,7 +300,7 @@ export default class NamespacePlugin extends Component {
           ellipsis: true,
           key: "sort",
           width: 120,
-          sorter: (a, b) => (a.role > b.role ? 1 : -1),
+          sorter: sortByNumber("sort"),
           render: (text) => {
             return <div style={{ color: "#014955" }}>{text}</div>;
           },
diff --git a/src/routes/System/Plugin/index.js 
b/src/routes/System/Plugin/index.js
index 7bfeccd7..dfdf4b31 100644
--- a/src/routes/System/Plugin/index.js
+++ b/src/routes/System/Plugin/index.js
@@ -36,6 +36,7 @@ import { resizableComponents } from 
"../../../utils/resizable";
 import AddModal from "./AddModal";
 import { getCurrentLocale, getIntlContent } from "../../../utils/IntlUtils";
 import AuthButton from "../../../utils/AuthButton";
+import { sortByNumber } from "../../../utils/sorter";
 import { refreshAuthMenus } from "../../../utils/AuthRoute";
 import { getUpdateModal, updatePluginsEnabled } from "../../../utils/plugin";
 
@@ -377,7 +378,7 @@ export default class Plugin extends Component {
           ellipsis: true,
           key: "sort",
           width: 120,
-          sorter: (a, b) => (a.role > b.role ? 1 : -1),
+          sorter: sortByNumber("sort"),
           render: (text) => {
             return <div style={{ color: "#014955" }}>{text}</div>;
           },
diff --git a/src/utils/sorter.js b/src/utils/sorter.js
new file mode 100644
index 00000000..57b9883e
--- /dev/null
+++ b/src/utils/sorter.js
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+const isMissing = (value) =>
+  value === null || value === undefined || value === "";
+
+// Missing values are ordered before present values, so they stay together
+// at the start of an ascending sort and at the end of a descending sort.
+const compareWithMissing = (a, b, compare) => {
+  const aMissing = isMissing(a);
+  const bMissing = isMissing(b);
+  if (aMissing || bMissing) {
+    if (aMissing && bMissing) {
+      return 0;
+    }
+    return aMissing ? -1 : 1;
+  }
+  return compare(a, b);
+};
+
+export const compareNumber = (a, b) =>
+  compareWithMissing(a, b, (x, y) => {
+    const numberA = Number(x);
+    const numberB = Number(y);
+    const aInvalid = Number.isNaN(numberA);
+    const bInvalid = Number.isNaN(numberB);
+    if (aInvalid || bInvalid) {
+      if (aInvalid && bInvalid) {
+        return 0;
+      }
+      return aInvalid ? -1 : 1;
+    }
+    return numberA - numberB;
+  });
+
+export const compareDate = (a, b) =>
+  compareWithMissing(a, b, (x, y) =>
+    compareNumber(new Date(x).getTime(), new Date(y).getTime()),
+  );
+
+export const sortByNumber = (field) => (a, b) =>
+  compareNumber(a[field], b[field]);
+
+export const sortByDate = (field) => (a, b) => compareDate(a[field], b[field]);
diff --git a/src/utils/sorter.test.js b/src/utils/sorter.test.js
new file mode 100644
index 00000000..cfbb822e
--- /dev/null
+++ b/src/utils/sorter.test.js
@@ -0,0 +1,94 @@
+/*
+ * 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 { compareDate, compareNumber, sortByDate, sortByNumber } from 
"./sorter";
+
+describe("compareNumber", () => {
+  it("orders numbers and numeric strings by value", () => {
+    expect(["42", 8, "100", 0].sort(compareNumber)).toEqual([
+      0,
+      8,
+      "42",
+      "100",
+    ]);
+  });
+
+  it("orders missing values before present values", () => {
+    // Array#sort always moves undefined to the end without calling the
+    // comparator, so undefined is checked directly below.
+    expect([3, null, 1, ""].sort(compareNumber)).toEqual([null, "", 1, 3]);
+    expect(compareNumber(undefined, 1)).toBeLessThan(0);
+    expect(compareNumber(1, undefined)).toBeGreaterThan(0);
+  });
+
+  it("treats two missing values as equal", () => {
+    expect(compareNumber(null, undefined)).toBe(0);
+  });
+});
+
+describe("compareDate", () => {
+  it("orders date strings and timestamps chronologically", () => {
+    const dates = [
+      "2026-09-26 10:00:00",
+      1735689600000, // 2025-01-01T00:00:00Z
+      "2024-05-01T08:30:00Z",
+    ];
+    expect([...dates].sort(compareDate)).toEqual([
+      "2024-05-01T08:30:00Z",
+      1735689600000,
+      "2026-09-26 10:00:00",
+    ]);
+  });
+
+  it("orders missing and invalid dates before valid dates", () => {
+    expect(["2026-01-01", null, "not a date"].sort(compareDate)).toEqual([
+      null,
+      "not a date",
+      "2026-01-01",
+    ]);
+  });
+});
+
+describe("column sorters", () => {
+  it("sorts rows by the requested numeric field, ignoring other fields", () => 
{
+    const rows = [
+      { role: "a", sort: 300 },
+      { role: "b", sort: 10 },
+      { role: "c", sort: 200 },
+    ];
+    expect([...rows].sort(sortByNumber("sort")).map((row) => 
row.sort)).toEqual(
+      [10, 200, 300],
+    );
+  });
+
+  it("sorts rows by the requested date field, ignoring other fields", () => {
+    const rows = [
+      { instanceType: "http", lastHeartBeatTime: "2026-09-03 00:00:00" },
+      { instanceType: "http", lastHeartBeatTime: "2026-09-01 00:00:00" },
+      { instanceType: "http", lastHeartBeatTime: "2026-09-02 00:00:00" },
+    ];
+    expect(
+      [...rows]
+        .sort(sortByDate("lastHeartBeatTime"))
+        .map((row) => row.lastHeartBeatTime),
+    ).toEqual([
+      "2026-09-01 00:00:00",
+      "2026-09-02 00:00:00",
+      "2026-09-03 00:00:00",
+    ]);
+  });
+});

Reply via email to