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 aa0b16f6 [ISSUE #1619] Prevent duplicate Topic create requests (#1629)
aa0b16f6 is described below
commit aa0b16f66e37317b8684097e35689905cb8479eb
Author: 0 <[email protected]>
AuthorDate: Tue Aug 11 20:32:49 2026 +0800
[ISSUE #1619] Prevent duplicate Topic create requests (#1629)
---
.../pages/instance/__tests__/TopicPage.test.tsx | 22 ++++++++++++++++++++--
web/src/pages/instance/topic.tsx | 15 +++++++++++++--
2 files changed, 33 insertions(+), 4 deletions(-)
diff --git a/web/src/pages/instance/__tests__/TopicPage.test.tsx
b/web/src/pages/instance/__tests__/TopicPage.test.tsx
index 0750eeb3..9144c18f 100644
--- a/web/src/pages/instance/__tests__/TopicPage.test.tsx
+++ b/web/src/pages/instance/__tests__/TopicPage.test.tsx
@@ -16,7 +16,7 @@
*/
import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from
'vitest';
-import { render, screen, waitFor, within } from '@testing-library/react';
+import { fireEvent, render, screen, waitFor, within } from
'@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { App } from 'antd';
@@ -154,6 +154,24 @@ describe('TopicPage', () => {
vi.clearAllMocks();
});
+ it('ignores duplicate Topic creates while the first request is pending',
async () => {
+ topicServiceMocks.createTopic.mockImplementation(() => new Promise(() =>
{}));
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ expect(await screen.findByText('topic-01')).toBeInTheDocument();
+ await user.click(screen.getByRole('button', { name: /创建 Topic/ }));
+ const dialog = await screen.findByRole('dialog');
+ await user.type(within(dialog).getByLabelText('Topic 名称'), 'new-topic');
+ const create = within(dialog).getByRole('button', { name: /创\s*建/ });
+
+ fireEvent.click(create);
+ fireEvent.click(create);
+
+ await waitFor(() =>
expect(topicServiceMocks.createTopic).toHaveBeenCalledTimes(1));
+ expect(create).toHaveClass('ant-btn-loading');
+ });
+
it('downloads the currently filtered topics when exporting', async () => {
const user = userEvent.setup();
const clickSpy = vi.spyOn(HTMLAnchorElement.prototype,
'click').mockImplementation(vi.fn());
@@ -422,7 +440,7 @@ describe('TopicPage', () => {
expect(await screen.findByText('共 0 个 Topic')).toBeInTheDocument();
expect(topicServiceMocks.listTopics).not.toHaveBeenCalled();
- expect(document.querySelector('.ant-spin-spinning')).toBeNull();
+ await waitFor(() =>
expect(document.querySelector('.ant-spin-spinning')).toBeNull());
expect(screen.getByRole('button', { name: /导入/ })).toBeDisabled();
expect(screen.getByRole('button', { name: /创建 Topic/ })).toBeDisabled();
});
diff --git a/web/src/pages/instance/topic.tsx b/web/src/pages/instance/topic.tsx
index c34428b1..45c28a74 100644
--- a/web/src/pages/instance/topic.tsx
+++ b/web/src/pages/instance/topic.tsx
@@ -291,6 +291,7 @@ const TopicPage = () => {
const [rebuilding, setRebuilding] = useState(false);
const [selectedTopic, setSelectedTopic] = useState<Topic | null>(null);
const [modalOpen, setModalOpen] = useState(false);
+ const [creating, setCreating] = useState(false);
const [form] = Form.useForm();
const [sendModalOpen, setSendModalOpen] = useState(false);
const [sendTopic, setSendTopic] = useState<Topic | null>(null);
@@ -306,6 +307,7 @@ const TopicPage = () => {
const [importing, setImporting] = useState(false);
const topicRequestIdRef = useRef(0);
+ const createInFlightRef = useRef(false);
useEffect(() => {
if (!selectedInstanceId) {
@@ -682,10 +684,13 @@ const TopicPage = () => {
// ─── Create modal submit ──────────────────────────────────────
const handleCreate = async () => {
+ if (createInFlightRef.current) return;
if (!selectedInstanceId) {
message.error('请先选择实例');
return;
}
+ createInFlightRef.current = true;
+ setCreating(true);
try {
const values = await form.validateFields();
const created = await createTopic({
@@ -696,8 +701,13 @@ const TopicPage = () => {
message.success(`Topic「${created.name}」创建成功`);
setModalOpen(false);
form.resetFields();
- } catch {
- message.error('创建 Topic 失败,请稍后重试');
+ } catch (error) {
+ if (!(error && typeof error === 'object' && 'errorFields' in error)) {
+ message.error('创建 Topic 失败,请稍后重试');
+ }
+ } finally {
+ createInFlightRef.current = false;
+ setCreating(false);
}
};
@@ -1117,6 +1127,7 @@ const TopicPage = () => {
form.resetFields();
}}
onOk={handleCreate}
+ confirmLoading={creating}
okText="创建"
cancelText="取消"
width={560}