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 85176a463 fix(dlq): drop the previous group messages when the detail
drawer changes group (#4752)
85176a463 is described below
commit 85176a463ee43f0141f781abf45291528725b405
Author: 烤化の初雪 <[email protected]>
AuthorDate: Thu Sep 24 17:46:52 2026 +0800
fix(dlq): drop the previous group messages when the detail drawer changes
group (#4752)
test(dlq): pin the in-flight window of the detail drawer switch
The fix claims the drawer is empty for the group being opened both when
that load fails and while it is still in flight; only the failure path
had a case. Pin the in-flight window too, where the header has already
switched while the request is still open.
fix(dlq): drop the previous group messages when the detail drawer changes
group
Opening the message detail drawer for another DLQ group reused the rows
and the total of the group that was open before: openDetailDrawer cleared
only the page, the selection and the error. The instance switch and the
drawer's own close handler both clear detailMessages/detailTotal, so the
group switch was the one scope change that did not.
A failed load for the second group then left the first group's messages
rendered under the second group's title, with the export kept enabled by
the stale total and the batch resend acting on those msgIds.
---
web/src/pages/instance/__tests__/DLQPage.test.tsx | 96 +++++++++++++++++++++++
web/src/pages/instance/dlq.tsx | 4 +
2 files changed, 100 insertions(+)
diff --git a/web/src/pages/instance/__tests__/DLQPage.test.tsx
b/web/src/pages/instance/__tests__/DLQPage.test.tsx
index b84e2a421..ebb44bf60 100644
--- a/web/src/pages/instance/__tests__/DLQPage.test.tsx
+++ b/web/src/pages/instance/__tests__/DLQPage.test.tsx
@@ -259,6 +259,102 @@ describe('DLQ page', () => {
);
});
+ it('drops the previous group messages when the next detail load fails',
async () => {
+
vi.mocked(messageService.listDLQGroups).mockResolvedValue(pageOf([dlqGroup,
secondDlqGroup]));
+ vi.mocked(messageService.listDLQMessages)
+ .mockResolvedValueOnce({
+ items: [
+ {
+ msgId: 'order-dead-letter-1',
+ topic: 'orders',
+ queueId: 0,
+ offset: 11,
+ storeTime: 1_700_000_000_000,
+ keys: 'order-1',
+ body: 'dead',
+ bodyBase64: null,
+ properties: {},
+ propertiesTruncated: false,
+ },
+ ],
+ total: 1,
+ page: 1,
+ size: 20,
+ })
+ .mockRejectedValueOnce(new Error('broker unavailable'));
+
+ const user = userEvent.setup();
+ renderWithProviders(<DLQPage />);
+
+ const orderRow = (await screen.findByText('cg-order')).closest('tr');
+ if (!orderRow) throw new Error('DLQ group row not found');
+ await user.click(within(orderRow).getByRole('button', { name: /消息明细/ }));
+ expect(await screen.findByText('order-dead-letter-1')).toBeInTheDocument();
+
+ const paymentRow = (await
screen.findByText('-cg-"payment"')).closest('tr');
+ if (!paymentRow) throw new Error('second DLQ group row not found');
+ await user.click(within(paymentRow).getByRole('button', { name: /消息明细/ }));
+
+ expect(await screen.findByText('DLQ 消息明细 ·
-cg-"payment"')).toBeInTheDocument();
+ expect(await screen.findByText('broker unavailable')).toBeInTheDocument();
+ // the drawer now belongs to another group, so the previous group's rows,
its total and
+ // the export that is enabled from that total must not survive the failed
load
+ expect(screen.queryByText('order-dead-letter-1')).not.toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /导出全部/ })).toBeDisabled();
+ });
+
+ it('clears the previous group messages while the next detail load is in
flight', async () => {
+ let resolveSecondDetail!: (page: DLQMessagePage) => void;
+
vi.mocked(messageService.listDLQGroups).mockResolvedValue(pageOf([dlqGroup,
secondDlqGroup]));
+ vi.mocked(messageService.listDLQMessages)
+ .mockResolvedValueOnce({
+ items: [
+ {
+ msgId: 'order-dead-letter-1',
+ topic: 'orders',
+ queueId: 0,
+ offset: 11,
+ storeTime: 1_700_000_000_000,
+ keys: 'order-1',
+ body: 'dead',
+ bodyBase64: null,
+ properties: {},
+ propertiesTruncated: false,
+ },
+ ],
+ total: 1,
+ page: 1,
+ size: 20,
+ })
+ .mockImplementationOnce(
+ () =>
+ new Promise<DLQMessagePage>((resolve) => {
+ resolveSecondDetail = resolve;
+ }),
+ );
+
+ const user = userEvent.setup();
+ renderWithProviders(<DLQPage />);
+
+ const orderRow = (await screen.findByText('cg-order')).closest('tr');
+ if (!orderRow) throw new Error('DLQ group row not found');
+ await user.click(within(orderRow).getByRole('button', { name: /消息明细/ }));
+ expect(await screen.findByText('order-dead-letter-1')).toBeInTheDocument();
+
+ const paymentRow = (await
screen.findByText('-cg-"payment"')).closest('tr');
+ if (!paymentRow) throw new Error('second DLQ group row not found');
+ await user.click(within(paymentRow).getByRole('button', { name: /消息明细/ }));
+
+ // the drawer already belongs to the second group while its request is
still open: the first
+ // group's rows and the total that enables the export must be gone before
the response lands
+ expect(await screen.findByText('DLQ 消息明细 ·
-cg-"payment"')).toBeInTheDocument();
+ await waitFor(() => expect(screen.getByRole('button', { name: /导出全部/
})).toBeDisabled());
+ expect(screen.queryByText('order-dead-letter-1')).not.toBeInTheDocument();
+ expect(screen.queryByText('共 1 条消息')).not.toBeInTheDocument();
+
+ await act(async () => resolveSecondDetail({ items: [], total: 0, page: 1,
size: 20 }));
+ });
+
it('does not let an old-instance detail resend overwrite the new instance
drawer', async () => {
let resolveResend!: (result: DLQResendResult) => void;
let resolveSecondDetail!: (page: DLQMessagePage) => void;
diff --git a/web/src/pages/instance/dlq.tsx b/web/src/pages/instance/dlq.tsx
index 21da027ee..165159427 100644
--- a/web/src/pages/instance/dlq.tsx
+++ b/web/src/pages/instance/dlq.tsx
@@ -377,6 +377,10 @@ const DLQPage = () => {
setDetailGroup(group);
setDetailOpen(true);
setDetailPage(1);
+ // The drawer now belongs to another group: its rows, and the total the
export and the
+ // pagination are driven by, must not survive from the group that was open
before.
+ setDetailMessages([]);
+ setDetailTotal(0);
setDetailSelectedMsgIds([]);
setDetailError(null);
void loadDetailMessages(group, 1, detailPageSize);