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 f24be26c5 fix(producer): invalidate connection queries when the group
changes (#7208)
f24be26c5 is described below
commit f24be26c58933f7017f9f9986f5cb862348cdb9e
Author: messere <[email protected]>
AuthorDate: Mon Oct 26 20:32:56 2026 +0800
fix(producer): invalidate connection queries when the group changes (#7208)
* fix(producer): invalidate connection queries when the group changes
---
web/src/pages/studio/Producer.tsx | 10 ++-
web/src/pages/studio/__tests__/Producer.test.tsx | 103 +++++++++++++++++++++++
2 files changed, 111 insertions(+), 2 deletions(-)
diff --git a/web/src/pages/studio/Producer.tsx
b/web/src/pages/studio/Producer.tsx
index c606a2967..5dbdc45d9 100644
--- a/web/src/pages/studio/Producer.tsx
+++ b/web/src/pages/studio/Producer.tsx
@@ -123,8 +123,8 @@ const ProducerPage = () => {
}, []);
// Invalidates any in-flight connection query and drops its visible results,
- // shared by every scope change (instance / topic) so a slow response can
never
- // repopulate results that belong to a different scope.
+ // shared by every scope change (instance / topic / producer group) so a slow
+ // response can never repopulate results that belong to a different scope.
const resetConnectionQuery = () => {
queryRequestIdRef.current += 1;
queryInFlightRef.current = null;
@@ -375,6 +375,12 @@ const ProducerPage = () => {
onSearch={(value) => {
void loadProducerGroups(value);
}}
+ onChange={() => {
+ // Editing the optional group filter is a criteria change like
a topic edit:
+ // an in-flight query belongs to the group it was started
with, and its rows
+ // must not survive the edit or land underneath the new
criteria.
+ resetConnectionQuery();
+ }}
filterOption={(inputValue, option) =>
option?.value.toLowerCase().includes(inputValue.toLowerCase())
?? false
}
diff --git a/web/src/pages/studio/__tests__/Producer.test.tsx
b/web/src/pages/studio/__tests__/Producer.test.tsx
index b4b33736d..eccb2a665 100644
--- a/web/src/pages/studio/__tests__/Producer.test.tsx
+++ b/web/src/pages/studio/__tests__/Producer.test.tsx
@@ -636,6 +636,109 @@ describe('ProducerPage', () => {
expect(await screen.findByText('payment-producer-1')).toBeInTheDocument();
});
+ it('clears stale connection results when the producer group changes', async
() => {
+ const user = userEvent.setup();
+ vi.mocked(queryProducerConnection).mockResolvedValue(
+ producerResult([
+ {
+ clientId: 'producer-1',
+ clientAddr: '192.168.1.10',
+ topic: 'order-events',
+ producerGroup: 'pg-order',
+ language: 'JAVA',
+ versionDesc: '5.1.0',
+ },
+ ]),
+ );
+ renderWithProviders(<ProducerPage />);
+
+ await waitFor(() => expect(fetchTopicList).toHaveBeenCalledTimes(1));
+ const [, topicSelect, groupInput] = screen.getAllByRole('combobox');
+ fireEvent.mouseDown(topicSelect.parentElement!);
+ await user.click(
+ await screen.findByText('order-events', { selector:
'.ant-select-item-option-content' }),
+ );
+ await user.type(groupInput, 'pg-order');
+ await user.click(screen.getByRole('button', { name: /搜索/ }));
+
+ expect(await screen.findByText('producer-1')).toBeInTheDocument();
+ expect(await screen.findByText('生产者连接健康')).toBeInTheDocument();
+
+ // Editing the group after a completed query is a criteria change like a
topic edit:
+ // the rows on screen no longer describe what the form would search for.
+ await user.type(groupInput, '-v2');
+
+ await waitFor(() => {
+ expect(screen.queryByText('producer-1')).not.toBeInTheDocument();
+ });
+ expect(screen.queryByText('生产者连接健康')).not.toBeInTheDocument();
+ expect(screen.queryByText('就绪')).not.toBeInTheDocument();
+ });
+
+ it('discards a slow connection response after the producer group changes',
async () => {
+ const user = userEvent.setup();
+ let resolveGroupAQuery: ((value: ProducerConnectionResult) => void) |
undefined;
+ vi.mocked(queryProducerConnection)
+ .mockImplementationOnce(
+ () =>
+ new Promise((resolve) => {
+ resolveGroupAQuery = resolve;
+ }),
+ )
+ .mockResolvedValueOnce(
+ producerResult([
+ {
+ clientId: 'payment-producer-1',
+ clientAddr: '192.168.1.20',
+ topic: 'order-events',
+ producerGroup: 'pg-payment',
+ language: 'JAVA',
+ versionDesc: '5.1.0',
+ },
+ ]),
+ );
+ renderWithProviders(<ProducerPage />);
+
+ await waitFor(() => expect(fetchTopicList).toHaveBeenCalledTimes(1));
+ const [, topicSelect, groupInput] = screen.getAllByRole('combobox');
+ fireEvent.mouseDown(topicSelect.parentElement!);
+ await user.click(
+ await screen.findByText('order-events', { selector:
'.ant-select-item-option-content' }),
+ );
+ await user.type(groupInput, 'pg-order');
+ await user.click(screen.getByRole('button', { name: /搜索/ }));
+ await waitFor(() =>
expect(queryProducerConnection).toHaveBeenCalledTimes(1));
+
+ // While the pg-order query is still pending, edit the group.
+ await user.type(groupInput, '2');
+
+ resolveGroupAQuery?.(
+ producerResult([
+ {
+ clientId: 'stale-producer',
+ clientAddr: '192.168.1.10',
+ language: 'JAVA',
+ versionDesc: '5.1.0',
+ },
+ ]),
+ );
+ await waitFor(() => {
+ expect(screen.queryByText('stale-producer')).not.toBeInTheDocument();
+ });
+ expect(screen.queryByText('生产者连接健康')).not.toBeInTheDocument();
+
+ // The group edit must also release the in-flight slot so a fresh query
runs.
+ await user.click(screen.getByRole('button', { name: /搜索/ }));
+ await waitFor(() => {
+ expect(queryProducerConnection).toHaveBeenLastCalledWith(
+ 'instance-1',
+ 'order-events',
+ 'pg-order2',
+ );
+ });
+ expect(await screen.findByText('payment-producer-1')).toBeInTheDocument();
+ });
+
it('does not discover producer groups before a topic is selected', async ()
=> {
const user = userEvent.setup();
renderWithProviders(<ProducerPage />);