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,
+ }),
+ );
+ },
+);