This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 3a78b1824 fix(alerts): reset list state when the alerts domain route
switches (#4004)
3a78b1824 is described below
commit 3a78b1824b8ee349b8963371148509c4337650d7
Author: 烤化の初雪 <[email protected]>
AuthorDate: Mon Sep 7 18:22:51 2026 +0800
fix(alerts): reset list state when the alerts domain route switches (#4004)
/ops/alerts and /ops/business-alerts render the same AlertsPage
instance, so React preserved page, search and status-filter state when
navigating between the two menu entries. Paging to page 2 and then
switching domains fetched the other domain with the retained page and
showed an empty table (rc-pagination clamps the displayed page to 1),
and a retained search silently narrowed the other domain's rule list.
Drop the previous domain's list state during render when the domain
prop changes, so each domain loads from a clean page 1 with no filters.
Co-authored-by: unbridled-41
<[email protected]>
---
web/src/pages/ops/__tests__/AlertsPage.test.tsx | 48 +++++++++++++++++++++++++
web/src/pages/ops/alerts.tsx | 17 +++++++++
2 files changed, 65 insertions(+)
diff --git a/web/src/pages/ops/__tests__/AlertsPage.test.tsx
b/web/src/pages/ops/__tests__/AlertsPage.test.tsx
index e9c1f7a39..77abe6db6 100644
--- a/web/src/pages/ops/__tests__/AlertsPage.test.tsx
+++ b/web/src/pages/ops/__tests__/AlertsPage.test.tsx
@@ -315,6 +315,54 @@ describe('AlertsPage', () => {
expect(screen.getByText('21')).toBeInTheDocument();
});
+ it('resets page, search and status filters when the domain switches', async
() => {
+ vi.mocked(listAlertRulesPage).mockClear();
+ vi.mocked(listAlertRulesPage).mockResolvedValue({
+ items: [cloneRule(alertRules[0])],
+ total: 21,
+ page: 2,
+ size: 20,
+ });
+ const user = userEvent.setup();
+ const { rerender } = renderPage();
+
+ await screen.findByText('Broker disk usage');
+ await user.type(screen.getByPlaceholderText('搜索规则名称或指标'), 'disk');
+ await waitFor(() =>
+ expect(listAlertRulesPage).toHaveBeenLastCalledWith(
+ 'CLUSTER',
+ expect.objectContaining({ search: 'disk' }),
+ ),
+ );
+ const secondPage = document.querySelector('.ant-pagination-item-2') as
HTMLElement | null;
+ if (!secondPage) throw new Error('Pagination page 2 not found');
+ await user.click(secondPage);
+ await waitFor(() =>
+ expect(listAlertRulesPage).toHaveBeenLastCalledWith(
+ 'CLUSTER',
+ expect.objectContaining({ page: 2 }),
+ ),
+ );
+
+ rerender(
+ <App>
+ <LangProvider>
+ <AlertsPage domain="BUSINESS" />
+ </LangProvider>
+ </App>,
+ );
+
+ await waitFor(() =>
+ expect(listAlertRulesPage).toHaveBeenLastCalledWith('BUSINESS', {
+ enabled: undefined,
+ page: 1,
+ pageSize: 20,
+ search: undefined,
+ }),
+ );
+ expect(screen.getByPlaceholderText('搜索规则名称或指标')).toHaveValue('');
+ });
+
it('uses the business rule API and loads only business metrics for the
selected instance', async () => {
vi.mocked(listNativeAlertMetrics).mockResolvedValue([
{
diff --git a/web/src/pages/ops/alerts.tsx b/web/src/pages/ops/alerts.tsx
index 242d257d4..3280abedb 100644
--- a/web/src/pages/ops/alerts.tsx
+++ b/web/src/pages/ops/alerts.tsx
@@ -172,6 +172,23 @@ const AlertsPage = ({ domain = 'CLUSTER' }:
AlertsPageProps) => {
const [selectedRuleIds, setSelectedRuleIds] = useState<Key[]>([]);
const [bulkAction, setBulkAction] = useState<'enable' | 'disable' | 'delete'
| null>(null);
const [form] = Form.useForm();
+ // The same component instance serves /ops/alerts and /ops/business-alerts,
so the
+ // list state from the previous domain must be dropped when the route
switches
+ // (React's documented "adjust state when a prop changes" pattern). Clear
the loaded
+ // rows/total/runtime and re-enter the loading state as well, otherwise the
previous
+ // domain's rules stay visible with loading=false until the new page
resolves.
+ const [renderedDomain, setRenderedDomain] = useState(domain);
+ if (renderedDomain !== domain) {
+ setRenderedDomain(domain);
+ setPage(1);
+ setSearch('');
+ setEnabledFilter(undefined);
+ setSelectedRuleIds([]);
+ setRules([]);
+ setTotalRules(0);
+ setRuntime([]);
+ setLoading(true);
+ }
const selectedMetric = Form.useWatch('metric', form);
const selectedOperator = Form.useWatch('operator', form);
const selectedThresholdUnit = Form.useWatch('thresholdUnit', form);