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 b79849a5c fix(consumer): refresh risk baseline after saving group 
settings (#6182)
b79849a5c is described below

commit b79849a5c95ed1f5844f7253cc38c8ec9208f36e
Author: zmuxuny <[email protected]>
AuthorDate: Mon Oct 12 00:17:04 2026 -0700

    fix(consumer): refresh risk baseline after saving group settings (#6182)
    
    `consumer.tsx:532` was the only place that wrote `originalSettingsRef`, so 
the risk baseline stayed frozen at whatever the modal had loaded. Enabling 
consumption, saving, then disabling it inside the same session made the 
comparison at `consumer.tsx:564` false and skipped the high-risk confirmation 
dialog entirely; reverting a saved ordered-consumption change had the same 
hole. The continuation after `updateConsumerGroupSettings` (`:588-612`) also 
ran without re-checking `settingsRequ [...]
    
    Four new cases in `ConsumerPage.test.tsx` pin this. Dropping the baseline 
refresh makes `requires confirmation when disabling consumption after enabling 
it in the same session` and `requires a new confirmation when reverting a saved 
ordered-consumption change` see zero `Modal.confirm` calls; dropping the 
failure-branch guard breaks `keeps the accepted risk baseline when a settings 
save fails`; dropping the post-save request-id check breaks `does not let a 
closed group save replace the [...]
---
 .../pages/instance/__tests__/ConsumerPage.test.tsx | 149 +++++++++++++++++++++
 web/src/pages/instance/consumer.tsx                |  19 ++-
 2 files changed, 165 insertions(+), 3 deletions(-)

diff --git a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx 
b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
index 95632fe86..f318b74df 100644
--- a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
+++ b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
@@ -1926,6 +1926,155 @@ describe('Consumer page', () => {
     expect((await screen.findAllByText('消费组配置已保存')).length).toBeGreaterThan(0);
   });
 
+  it('requires confirmation when disabling consumption after enabling it in 
the same session', async () => {
+    const initialSettings = {
+      groupName: 'remote-cg',
+      retryQueueNums: 1,
+      retryMaxTimes: 16,
+      consumeEnable: false,
+      consumeMessageOrderly: false,
+      consumeBroadcastEnable: false,
+    };
+    
vi.mocked(consumerService.getConsumerGroupSettings).mockResolvedValue(initialSettings);
+    
vi.mocked(consumerService.updateConsumerGroupSettings).mockResolvedValueOnce({
+      ...initialSettings,
+      consumeEnable: true,
+    });
+    const confirm = vi.spyOn(Modal, 'confirm');
+    const user = userEvent.setup({ pointerEventsCheck: 0 });
+    renderWithProviders(<ConsumerPage />);
+    const row = await screen.findByRole('row', { name: /remote-cg/ });
+    await user.click(within(row).getByRole('button', { name: '详情' }));
+    const dialog = await screen.findByRole('dialog', { name: /remote-cg/ });
+    await user.click(within(dialog).getByRole('tab', { name: '配置' }));
+    const toggle = await within(dialog).findByRole('switch', { name: '启用消费' });
+    await waitFor(() => 
expect(within(dialog).getByLabelText('重试队列数')).toHaveValue('1'));
+    await user.click(toggle);
+    await user.click(within(dialog).getByRole('button', { name: /保\s*存/ }));
+    await waitFor(() =>
+      
expect(consumerService.updateConsumerGroupSettings).toHaveBeenCalledTimes(1),
+    );
+    await waitFor(() =>
+      expect(within(dialog).getByRole('button', { name: /保\s*存/ 
})).not.toHaveClass(
+        'ant-btn-loading',
+      ),
+    );
+    await user.click(toggle);
+    await user.click(within(dialog).getByRole('button', { name: /保\s*存/ }));
+    await waitFor(() => expect(confirm).toHaveBeenCalledTimes(1));
+    
expect(consumerService.updateConsumerGroupSettings).toHaveBeenCalledTimes(1);
+    await user.click(await screen.findByRole('button', { name: /取\s*消/ }));
+    
expect(consumerService.updateConsumerGroupSettings).toHaveBeenCalledTimes(1);
+  });
+
+  it('requires a new confirmation when reverting a saved ordered-consumption 
change', async () => {
+    const settings = {
+      groupName: 'remote-cg',
+      retryQueueNums: 1,
+      retryMaxTimes: 16,
+      consumeEnable: true,
+      consumeMessageOrderly: false,
+    };
+    
vi.mocked(consumerService.getConsumerGroupSettings).mockResolvedValue(settings);
+    
vi.mocked(consumerService.updateConsumerGroupSettings).mockResolvedValueOnce({
+      ...settings,
+      consumeMessageOrderly: true,
+    });
+    const confirm = vi.spyOn(Modal, 'confirm');
+    const user = userEvent.setup({ pointerEventsCheck: 0 });
+    renderWithProviders(<ConsumerPage />);
+    const row = await screen.findByRole('row', { name: /remote-cg/ });
+    await user.click(within(row).getByRole('button', { name: '详情' }));
+    const dialog = await screen.findByRole('dialog', { name: /remote-cg/ });
+    await user.click(within(dialog).getByRole('tab', { name: '配置' }));
+    await waitFor(() => 
expect(within(dialog).getByLabelText('重试队列数')).toHaveValue('1'));
+    const toggle = within(dialog).getByRole('switch', { name: '顺序消费' });
+    await user.click(toggle);
+    await user.click(within(dialog).getByRole('button', { name: /保\s*存/ }));
+    await user.click(await screen.findByRole('button', { name: '确认修改' }));
+    await waitFor(() =>
+      
expect(consumerService.updateConsumerGroupSettings).toHaveBeenCalledTimes(1),
+    );
+    await waitFor(() =>
+      expect(within(dialog).getByRole('button', { name: /保\s*存/ 
})).not.toHaveClass(
+        'ant-btn-loading',
+      ),
+    );
+    await user.click(toggle);
+    await user.click(within(dialog).getByRole('button', { name: /保\s*存/ }));
+    await waitFor(() => expect(confirm).toHaveBeenCalledTimes(2));
+    
expect(consumerService.updateConsumerGroupSettings).toHaveBeenCalledTimes(1);
+  });
+
+  it('keeps the accepted risk baseline when a settings save fails', async () 
=> {
+    vi.mocked(consumerService.getConsumerGroupSettings).mockResolvedValue({
+      groupName: 'remote-cg',
+      retryQueueNums: 1,
+      retryMaxTimes: 16,
+      consumeEnable: true,
+      consumeMessageOrderly: false,
+    });
+    
vi.mocked(consumerService.updateConsumerGroupSettings).mockRejectedValueOnce(
+      new Error('offline'),
+    );
+    const confirm = vi.spyOn(Modal, 'confirm');
+    const user = userEvent.setup({ pointerEventsCheck: 0 });
+    renderWithProviders(<ConsumerPage />);
+    const row = await screen.findByRole('row', { name: /remote-cg/ });
+    await user.click(within(row).getByRole('button', { name: '详情' }));
+    const dialog = await screen.findByRole('dialog', { name: /remote-cg/ });
+    await user.click(within(dialog).getByRole('tab', { name: '配置' }));
+    await waitFor(() => 
expect(within(dialog).getByLabelText('重试队列数')).toHaveValue('1'));
+    await user.click(within(dialog).getByRole('switch', { name: '启用消费' }));
+    await user.click(within(dialog).getByRole('button', { name: /保\s*存/ }));
+    await user.click(await screen.findByRole('button', { name: '确认修改' }));
+    await screen.findByText('保存消费组配置失败,请稍后重试');
+    await user.click(within(dialog).getByRole('button', { name: /保\s*存/ }));
+    await waitFor(() => expect(confirm).toHaveBeenCalledTimes(2));
+    
expect(consumerService.updateConsumerGroupSettings).toHaveBeenCalledTimes(1);
+  });
+
+  it('does not let a closed group save replace the next group risk baseline', 
async () => {
+    const settings = {
+      groupName: 'remote-cg',
+      retryQueueNums: 1,
+      retryMaxTimes: 16,
+      consumeEnable: false,
+    };
+    const pendingSave = deferred<typeof settings>();
+    vi.mocked(consumerService.listConsumerGroupPage).mockResolvedValue(
+      groupPage([group, { ...group, name: 'other-cg' }]),
+    );
+    vi.mocked(consumerService.getConsumerGroupSettings)
+      .mockResolvedValueOnce(settings)
+      .mockResolvedValueOnce({ ...settings, groupName: 'other-cg', 
consumeEnable: true });
+    
vi.mocked(consumerService.updateConsumerGroupSettings).mockReturnValueOnce(pendingSave.promise);
+    const confirm = vi.spyOn(Modal, 'confirm');
+    const user = userEvent.setup({ pointerEventsCheck: 0 });
+    renderWithProviders(<ConsumerPage />);
+    const row = await screen.findByRole('row', { name: /remote-cg/ });
+    await user.click(within(row).getByRole('button', { name: '详情' }));
+    const dialog = await screen.findByRole('dialog', { name: /remote-cg/ });
+    await user.click(within(dialog).getByRole('tab', { name: '配置' }));
+    await waitFor(() => 
expect(within(dialog).getByLabelText('重试队列数')).toHaveValue('1'));
+    await user.click(within(dialog).getByRole('button', { name: /保\s*存/ }));
+    await waitFor(() =>
+      
expect(consumerService.updateConsumerGroupSettings).toHaveBeenCalledTimes(1),
+    );
+    fireEvent.click(dialog.querySelector('.ant-modal-close') as HTMLElement);
+    fireEvent.click(
+      within(screen.getByRole('row', { name: /other-cg/ 
})).getByRole('button', { name: '详情' }),
+    );
+    const nextDialog = await screen.findByRole('dialog', { name: /other-cg/ });
+    await user.click(within(nextDialog).getByRole('tab', { name: '配置' }));
+    await waitFor(() => 
expect(within(nextDialog).getByLabelText('重试队列数')).toHaveValue('1'));
+    await act(async () => pendingSave.resolve({ ...settings, consumeEnable: 
false }));
+    await user.click(within(nextDialog).getByRole('switch', { name: '启用消费' }));
+    await user.click(within(nextDialog).getByRole('button', { name: /保\s*存/ 
}));
+    await waitFor(() => expect(confirm).toHaveBeenCalledTimes(1));
+    
expect(consumerService.updateConsumerGroupSettings).toHaveBeenCalledTimes(1);
+  });
+
   it('renders an unknown (-1) lag as unavailable in the table and the lag 
detail', async () => {
     const user = userEvent.setup({ pointerEventsCheck: 0 });
     vi.mocked(consumerService.listConsumerGroupPage).mockResolvedValue(
diff --git a/web/src/pages/instance/consumer.tsx 
b/web/src/pages/instance/consumer.tsx
index fffe1d524..c118f45ad 100644
--- a/web/src/pages/instance/consumer.tsx
+++ b/web/src/pages/instance/consumer.tsx
@@ -558,7 +558,9 @@ const ConsumerPageContent = ({
 
   const saveSettings = async () => {
     if (!settingsGroup || !selectedInstanceId) return;
+    const requestId = settingsRequestIdRef.current;
     const values = await settingsForm.validateFields();
+    if (requestId !== settingsRequestIdRef.current) return;
     const original = originalSettingsRef.current;
     const risks: string[] = [];
     if (original && values.consumeEnable === false && original.consumeEnable 
!== false) {
@@ -583,7 +585,7 @@ const ConsumerPageContent = ({
           onCancel: () => resolve(false),
         });
       });
-      if (!confirmed) return;
+      if (!confirmed || requestId !== settingsRequestIdRef.current) return;
     }
     setSettingsSubmitting(true);
     try {
@@ -592,6 +594,13 @@ const ConsumerPageContent = ({
         name: settingsGroup.name,
         ...values,
       });
+      if (requestId !== settingsRequestIdRef.current) return;
+      // Subsequent edits must be compared with the settings the broker just 
accepted,
+      // not the snapshot from when this modal was first opened.
+      originalSettingsRef.current = {
+        consumeEnable: values.consumeEnable,
+        consumeMessageOrderly: values.consumeMessageOrderly,
+      };
       setGroups((current) =>
         current.map((group) =>
           group.name === settingsGroup.name
@@ -606,9 +615,11 @@ const ConsumerPageContent = ({
       );
       message.success('消费组配置已保存');
     } catch {
-      message.error('保存消费组配置失败,请稍后重试');
+      if (requestId === settingsRequestIdRef.current) {
+        message.error('保存消费组配置失败,请稍后重试');
+      }
     } finally {
-      setSettingsSubmitting(false);
+      if (requestId === settingsRequestIdRef.current) 
setSettingsSubmitting(false);
     }
   };
 
@@ -1654,6 +1665,8 @@ const ConsumerPageContent = ({
           setShowOnlyInconsistent(false);
           setSettingsGroup(null);
           setSettingsLoading(false);
+          setSettingsSubmitting(false);
+          originalSettingsRef.current = null;
           settingsForm.resetFields();
         }}
         width={detailTab === 'progress' || detailTab === 'health' ? 1080 : 800}

Reply via email to