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 1afa4012 fix: reset search pagination and preserve delete page size 
(#703)
1afa4012 is described below

commit 1afa40129c7de8d081f6a33126c46f223875ca2b
Author: Anubhav <[email protected]>
AuthorDate: Sat Oct 10 22:25:16 2026 +0530

    fix: reset search pagination and preserve delete page size (#703)
    
    * fix: reset search pagination and preserve delete page size
    
    * fix
---
 src/routes/Plugin/Common/index.js                  |  30 ++--
 .../Plugin/Common/selectorPagination.test.js       |  93 ++++++++++++
 src/routes/System/Instance/index.js                |   4 +-
 src/routes/System/Namespace/index.js               |   4 +-
 src/routes/System/NamespacePlugin/index.js         |   8 +-
 src/routes/System/Plugin/index.js                  |  10 +-
 src/routes/System/Role/index.js                    |   2 +-
 src/routes/System/Scale/index.js                   |   2 +-
 src/routes/System/User/index.js                    |   2 +-
 src/routes/System/pagination.test.js               | 158 +++++++++++++++++++++
 10 files changed, 281 insertions(+), 32 deletions(-)

diff --git a/src/routes/Plugin/Common/index.js 
b/src/routes/Plugin/Common/index.js
index 0de3bf95..b1726f8e 100755
--- a/src/routes/Plugin/Common/index.js
+++ b/src/routes/Plugin/Common/index.js
@@ -94,18 +94,20 @@ export default class Common extends Component {
         type: "common/resetData",
       });
 
-      if (prevProps.plugins && prevProps.plugins.length > 0) {
-        this.getAllSelectors(selectorPage, selectorPageSize, 
prevProps.plugins);
-      } else {
-        dispatch({
-          type: "global/fetchPlugins",
-          payload: {
-            callback: (pluginList) => {
-              this.getAllSelectors(selectorPage, selectorPageSize, pluginList);
+      this.setState({ selectorPage: 1 }, () => {
+        if (plugins && plugins.length > 0) {
+          this.getAllSelectors(1, selectorPageSize, plugins);
+        } else {
+          dispatch({
+            type: "global/fetchPlugins",
+            payload: {
+              callback: (pluginList) => {
+                this.getAllSelectors(1, selectorPageSize, pluginList);
+              },
             },
-          },
-        });
-      }
+          });
+        }
+      });
     }
     if (prevProps.currentNamespaceId !== currentNamespaceId) {
       if (plugins) {
@@ -198,8 +200,10 @@ export default class Common extends Component {
 
   searchSelector = () => {
     const { plugins } = this.props;
-    const { selectorPage, selectorPageSize } = this.state;
-    this.getAllSelectors(selectorPage, selectorPageSize, plugins);
+    const { selectorPageSize } = this.state;
+    this.setState({ selectorPage: 1 }, () => {
+      this.getAllSelectors(1, selectorPageSize, plugins);
+    });
   };
 
   isDiscovery = (pluginId) => {
diff --git a/src/routes/Plugin/Common/selectorPagination.test.js 
b/src/routes/Plugin/Common/selectorPagination.test.js
index fe1b20de..a8165dbc 100644
--- a/src/routes/Plugin/Common/selectorPagination.test.js
+++ b/src/routes/Plugin/Common/selectorPagination.test.js
@@ -66,3 +66,96 @@ it("resets rule pagination and uses rule page size when 
selecting a selector", (
     },
   });
 });
+
+const makeSearchComponent = (plugins = [{ name: "divide", pluginId: "5" }]) => 
{
+  const dispatch = jest.fn();
+  const component = new Common({
+    dispatch,
+    plugins,
+    match: { params: { id: "divide" } },
+    currentNamespaceId: "namespace-1",
+  });
+  component.state = {
+    ...component.state,
+    selectorPage: 3,
+    selectorPageSize: 50,
+    selectorName: "demo",
+  };
+  component.setState = (update, callback) => {
+    component.state = { ...component.state, ...update };
+    if (callback) callback();
+  };
+  return { component, dispatch };
+};
+
+it("searches selectors from page one while preserving the filter and page 
size", () => {
+  const { component, dispatch } = makeSearchComponent();
+  component.searchSelector();
+  expect(component.state.selectorPage).toBe(1);
+  expect(dispatch).toHaveBeenCalledWith({
+    type: "common/fetchSelector",
+    payload: {
+      currentPage: 1,
+      pageSize: 50,
+      pluginId: "5",
+      name: "demo",
+      namespaceId: "namespace-1",
+    },
+  });
+});
+
+it.each([true, false])(
+  "resets selector pagination when switching plugins (cached: %s)",
+  (cached) => {
+    const plugins = [{ name: "divide", pluginId: "5" }];
+    const { component, dispatch } = makeSearchComponent(cached ? plugins : []);
+    component.componentDidUpdate({
+      ...component.props,
+      match: { params: { id: "old-plugin" } },
+    });
+    if (!cached) {
+      const fetch = dispatch.mock.calls.find(
+        ([action]) => action.type === "global/fetchPlugins",
+      );
+      expect(fetch).toBeDefined();
+      fetch[0].payload.callback(plugins);
+    }
+    expect(component.state.selectorPage).toBe(1);
+    expect(dispatch).toHaveBeenCalledWith(
+      expect.objectContaining({
+        type: "common/fetchSelector",
+        payload: expect.objectContaining({
+          currentPage: 1,
+          pageSize: 50,
+          pluginId: "5",
+        }),
+      }),
+    );
+  },
+);
+
+it.each([[[]], [[{ name: "divide", pluginId: "stale-id" }]]])(
+  "uses the current plugin list when navigation and a store update occur 
together (case %#)",
+  (previousPlugins) => {
+    const { component, dispatch } = makeSearchComponent();
+    component.componentDidUpdate({
+      ...component.props,
+      plugins: previousPlugins,
+      match: { params: { id: "old-plugin" } },
+    });
+    expect(component.state.selectorPage).toBe(1);
+    expect(dispatch).not.toHaveBeenCalledWith(
+      expect.objectContaining({ type: "global/fetchPlugins" }),
+    );
+    expect(dispatch).toHaveBeenCalledWith({
+      type: "common/fetchSelector",
+      payload: {
+        currentPage: 1,
+        pageSize: 50,
+        pluginId: "5",
+        name: "demo",
+        namespaceId: "namespace-1",
+      },
+    });
+  },
+);
diff --git a/src/routes/System/Instance/index.js 
b/src/routes/System/Instance/index.js
index 64009697..acef7185 100644
--- a/src/routes/System/Instance/index.js
+++ b/src/routes/System/Instance/index.js
@@ -282,11 +282,11 @@ export default class Instance extends Component {
   };
 
   instanceIpOnchange = (e) => {
-    this.setState({ instanceIp: e.target.value }, this.query);
+    this.setState({ instanceIp: e.target.value, currentPage: 1 }, this.query);
   };
 
   instanceTypeOnchange = (e) => {
-    this.setState({ instanceType: e }, this.query);
+    this.setState({ instanceType: e, currentPage: 1 }, this.query);
   };
 
   searchClick = () => {
diff --git a/src/routes/System/Namespace/index.js 
b/src/routes/System/Namespace/index.js
index 55a4fffd..bac68bf9 100644
--- a/src/routes/System/Namespace/index.js
+++ b/src/routes/System/Namespace/index.js
@@ -184,9 +184,7 @@ export default class Namespace extends Component {
         payload: {
           list: deleteKeys,
         },
-        fetchValue: this.currentQueryPayload({
-          pageSize: 12,
-        }),
+        fetchValue: this.currentQueryPayload(),
         callback: () => {
           this.setState({ selectedRowKeys: [] });
           refreshAuthMenus({ dispatch });
diff --git a/src/routes/System/NamespacePlugin/index.js 
b/src/routes/System/NamespacePlugin/index.js
index 3e21e7f6..14d649e3 100644
--- a/src/routes/System/NamespacePlugin/index.js
+++ b/src/routes/System/NamespacePlugin/index.js
@@ -162,11 +162,11 @@ export default class NamespacePlugin extends Component {
   };
 
   searchOnchange = (e) => {
-    this.setState({ name: e.target.value }, this.query);
+    this.setState({ name: e.target.value, currentPage: 1 }, this.query);
   };
 
   enabledOnchange = (e) => {
-    this.setState({ enabled: e }, this.query);
+    this.setState({ enabled: e, currentPage: 1 }, this.query);
   };
 
   searchClick = () => {
@@ -183,9 +183,7 @@ export default class NamespacePlugin extends Component {
           list: selectedRowKeys,
           namespaceId: currentNamespaceId,
         },
-        fetchValue: this.currentQueryPayload({
-          pageSize: 12,
-        }),
+        fetchValue: this.currentQueryPayload(),
         callback: () => {
           this.setState({ selectedRowKeys: [] });
           refreshAuthMenus({ dispatch });
diff --git a/src/routes/System/Plugin/index.js 
b/src/routes/System/Plugin/index.js
index dfdf4b31..75931c46 100644
--- a/src/routes/System/Plugin/index.js
+++ b/src/routes/System/Plugin/index.js
@@ -213,15 +213,15 @@ export default class Plugin extends Component {
   };
 
   searchOnNamechange = (e) => {
-    this.setState({ name: e.target.value }, this.query);
+    this.setState({ name: e.target.value, currentPage: 1 }, this.query);
   };
 
   searchOnRolechange = (e) => {
-    this.setState({ role: e.target.value }, this.query);
+    this.setState({ role: e.target.value, currentPage: 1 }, this.query);
   };
 
   enabledOnchange = (e) => {
-    this.setState({ enabled: e }, this.query);
+    this.setState({ enabled: e, currentPage: 1 }, this.query);
   };
 
   searchClick = () => {
@@ -237,9 +237,7 @@ export default class Plugin extends Component {
         payload: {
           list: selectedRowKeys,
         },
-        fetchValue: this.currentQueryPayload({
-          pageSize: 12,
-        }),
+        fetchValue: this.currentQueryPayload(),
         callback: () => {
           this.setState({ selectedRowKeys: [] });
           refreshAuthMenus({ dispatch });
diff --git a/src/routes/System/Role/index.js b/src/routes/System/Role/index.js
index 97f57a6d..500cc560 100644
--- a/src/routes/System/Role/index.js
+++ b/src/routes/System/Role/index.js
@@ -123,7 +123,7 @@ export default class Role extends Component {
   };
 
   searchClick = () => {
-    this.getAllRoles();
+    this.setState({ currentPage: 1 }, this.getAllRoles);
   };
 
   deleteClick = () => {
diff --git a/src/routes/System/Scale/index.js b/src/routes/System/Scale/index.js
index 5f7237b8..e80d5c3c 100644
--- a/src/routes/System/Scale/index.js
+++ b/src/routes/System/Scale/index.js
@@ -198,7 +198,7 @@ export default class Scale extends Component {
   };
 
   searchClick = () => {
-    this.getScaleRules();
+    this.setState({ currentPage: 1 }, this.getScaleRules);
   };
 
   deleteClick = () => {
diff --git a/src/routes/System/User/index.js b/src/routes/System/User/index.js
index ad839961..7b196bb9 100644
--- a/src/routes/System/User/index.js
+++ b/src/routes/System/User/index.js
@@ -148,7 +148,7 @@ export default class Manage extends Component {
   };
 
   searchClick = () => {
-    this.getAllUsers();
+    this.setState({ currentPage: 1 }, this.getAllUsers);
   };
 
   deleteClick = () => {
diff --git a/src/routes/System/pagination.test.js 
b/src/routes/System/pagination.test.js
new file mode 100644
index 00000000..ab6f4310
--- /dev/null
+++ b/src/routes/System/pagination.test.js
@@ -0,0 +1,158 @@
+/*
+ * 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 ConnectedPlugin from "./Plugin";
+import ConnectedNamespacePlugin from "./NamespacePlugin";
+import ConnectedInstance from "./Instance";
+import ConnectedRole from "./Role";
+import ConnectedUser from "./User";
+import ConnectedScale from "./Scale";
+import ConnectedNamespace from "./Namespace";
+
+jest.mock("./Plugin/AddModal", () => () => null);
+jest.mock("./Role/AddModal", () => () => null);
+jest.mock("./User/AddModal", () => () => null);
+jest.mock("./User/DataPermModal", () => () => null);
+jest.mock("./Scale/PolicyModal", () => () => null);
+jest.mock("./Scale/RuleModal", () => () => null);
+jest.mock("./Namespace/AddModal", () => () => null);
+jest.mock(
+  "../../utils/AuthButton",
+  () =>
+    ({ children }) =>
+      children,
+);
+jest.mock("../../utils/IntlUtils", () => ({
+  getCurrentLocale: jest.fn(),
+  getIntlContent: (key) => key,
+}));
+jest.mock("../../utils/AuthRoute", () => ({ refreshAuthMenus: jest.fn() }));
+jest.mock("../../components/_utils/utils", () => ({
+  defaultNamespaceId: "default",
+}));
+
+const makeComponent = (Connected) => {
+  const dispatch = jest.fn();
+  const component = new Connected.WrappedComponent({
+    dispatch,
+    currentNamespaceId: "namespace-1",
+    namespace: { namespaceList: [] },
+  });
+  component.state = {
+    ...component.state,
+    currentPage: 3,
+    pageSize: 50,
+    selectedRowKeys: ["row-1"],
+  };
+  component.setState = (update, callback) => {
+    component.state = { ...component.state, ...update };
+    if (callback) callback();
+  };
+  return { component, dispatch };
+};
+
+it.each([
+  [
+    ConnectedPlugin,
+    "searchOnNamechange",
+    "name",
+    { target: { value: "demo" } },
+  ],
+  [
+    ConnectedPlugin,
+    "searchOnRolechange",
+    "role",
+    { target: { value: "demo" } },
+  ],
+  [ConnectedPlugin, "enabledOnchange", "enabled", false],
+  [
+    ConnectedNamespacePlugin,
+    "searchOnchange",
+    "name",
+    { target: { value: "demo" } },
+  ],
+  [ConnectedNamespacePlugin, "enabledOnchange", "enabled", false],
+  [
+    ConnectedInstance,
+    "instanceIpOnchange",
+    "instanceIp",
+    { target: { value: "demo" } },
+  ],
+  [ConnectedInstance, "instanceTypeOnchange", "instanceType", "bootstrap"],
+])(
+  "resets page before querying (case %#)",
+  (Connected, handler, field, input) => {
+    const { component, dispatch } = makeComponent(Connected);
+    component[handler](input);
+    expect(component.state.currentPage).toBe(1);
+    expect(dispatch).toHaveBeenCalledTimes(1);
+    expect(dispatch.mock.calls[0][0].payload).toEqual(
+      expect.objectContaining({
+        currentPage: 1,
+        pageSize: 50,
+        [field]: input && input.target ? input.target.value : input,
+      }),
+    );
+  },
+);
+
+it.each([
+  [ConnectedRole, "searchOnchange", "roleName"],
+  [ConnectedUser, "searchOnchange", "userName"],
+  [ConnectedScale, "searchMetricNameOnchange", "metricName"],
+])(
+  "queries page one when Search is clicked (case %#)",
+  (Connected, handler, field) => {
+    const { component, dispatch } = makeComponent(Connected);
+    component[handler]({ target: { value: "demo" } });
+    expect(dispatch).not.toHaveBeenCalled();
+    component.searchClick();
+    expect(component.state.currentPage).toBe(1);
+    expect(dispatch.mock.calls[0][0].payload).toEqual(
+      expect.objectContaining({
+        currentPage: 1,
+        pageSize: 50,
+        [field]: "demo",
+      }),
+    );
+  },
+);
+
+it.each([
+  [ConnectedPlugin, "filter-namespace"],
+  [ConnectedNamespacePlugin, "namespace-1"],
+  [ConnectedNamespace, "filter-namespace"],
+])(
+  "preserves the selected page size and filters after deletion (case %#)",
+  (Connected, namespaceId) => {
+    const { component, dispatch } = makeComponent(Connected);
+    component.state = {
+      ...component.state,
+      name: "demo",
+      namespaceId: "filter-namespace",
+    };
+    component.deleteClick();
+    expect(dispatch.mock.calls[0][0].fetchValue).toEqual(
+      expect.objectContaining({
+        currentPage: 3,
+        pageSize: 50,
+        name: "demo",
+        namespaceId,
+      }),
+    );
+  },
+);

Reply via email to