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 243a7a691 fix(lite): let users inspect every LiteTopic session (#5242)
243a7a691 is described below
commit 243a7a691808718763e8feef3a94c476d1114933
Author: coder999o <[email protected]>
AuthorDate: Fri Oct 9 11:31:13 2026 +0800
fix(lite): let users inspect every LiteTopic session (#5242)
`handleViewSessions` always opened `sessionIds[0]`, so a row holding
several sessions gave users no
way to look at the others even though the table shows the count. The drawer
now keeps the row's
session list and renders a searchable selector when there is more than one,
reusing the existing
fetch and request-ownership guard; closing the drawer bumps the request id
and clears the selection.
---
web/src/pages/studio/LiteTopic.tsx | 26 +++++++++++++++++++++--
web/src/pages/studio/__tests__/LiteTopic.test.tsx | 26 +++++++++++++++++++++++
2 files changed, 50 insertions(+), 2 deletions(-)
diff --git a/web/src/pages/studio/LiteTopic.tsx
b/web/src/pages/studio/LiteTopic.tsx
index b00a4b947..8061af752 100644
--- a/web/src/pages/studio/LiteTopic.tsx
+++ b/web/src/pages/studio/LiteTopic.tsx
@@ -141,6 +141,8 @@ const LiteTopicPage: React.FC = () => {
const [sessionDrawerOpen, setSessionDrawerOpen] = useState(false);
const [sessionData, setSessionData] = useState<LiteTopicSession |
null>(null);
const [sessionLoading, setSessionLoading] = useState(false);
+ const [availableSessionIds, setAvailableSessionIds] = useState<string[]>([]);
+ const [selectedSessionId, setSelectedSessionId] = useState<string>();
// Extend TTL modal
const [extendTTLModalOpen, setExtendTTLModalOpen] = useState(false);
@@ -278,8 +280,10 @@ const LiteTopicPage: React.FC = () => {
void fetchData(patternFilter || undefined, namespace, { clear: true });
};
- const handleViewSessions = async (sessionId: string) => {
+ const handleViewSessions = async (sessionId: string, sessionIds?: string[])
=> {
const requestId = ++sessionRequestId.current;
+ if (sessionIds) setAvailableSessionIds(sessionIds);
+ setSelectedSessionId(sessionId);
setSessionDrawerOpen(true);
setSessionLoading(true);
setSessionData(null);
@@ -451,7 +455,7 @@ const LiteTopicPage: React.FC = () => {
icon={<Eye size={14} />}
onClick={(e) => {
e.stopPropagation();
- handleViewSessions(record.sessionIds![0]);
+ void handleViewSessions(record.sessionIds![0],
record.sessionIds);
}}
>
{t('liteTopic.viewSessions')}
@@ -870,11 +874,29 @@ const LiteTopicPage: React.FC = () => {
width={680}
open={sessionDrawerOpen}
onClose={() => {
+ sessionRequestId.current += 1;
setSessionDrawerOpen(false);
+ setSessionLoading(false);
setSessionData(null);
+ setAvailableSessionIds([]);
+ setSelectedSessionId(undefined);
}}
destroyOnHidden
>
+ {availableSessionIds.length > 1 && (
+ <Select
+ aria-label={t('liteTopic.sessionId')}
+ showSearch
+ optionFilterProp="label"
+ value={selectedSessionId}
+ options={availableSessionIds.map((sessionId) => ({
+ value: sessionId,
+ label: sessionId,
+ }))}
+ onChange={(sessionId) => void handleViewSessions(sessionId)}
+ style={{ width: '100%', marginBottom: 16 }}
+ />
+ )}
{renderSessionContent()}
</Drawer>
diff --git a/web/src/pages/studio/__tests__/LiteTopic.test.tsx
b/web/src/pages/studio/__tests__/LiteTopic.test.tsx
index 0dc12a554..8f160e368 100644
--- a/web/src/pages/studio/__tests__/LiteTopic.test.tsx
+++ b/web/src/pages/studio/__tests__/LiteTopic.test.tsx
@@ -156,6 +156,32 @@ describe('LiteTopic Page', () => {
expect(within(popProgressLabel.parentElement!).getByText('96%')).toBeInTheDocument();
});
+ it('allows viewing every session linked to the same LiteTopic row', async ()
=> {
+ apiMocks.queryLiteTopicList.mockResolvedValue([
+ { namespace: 'default', topicPattern: 'order-*', sessionIds:
['session-1', 'session-2'] },
+ ]);
+ apiMocks.queryLiteTopicSession.mockImplementation(async (sessionId:
string) => ({
+ sessionId,
+ clientId: sessionId === 'session-1' ? 'first-client' : 'second-client',
+ totalMessages: 100,
+ consumedMessages: 0,
+ }));
+ const user = userEvent.setup();
+ renderPage();
+
+ await user.click(await screen.findByText('查看会话'));
+ expect(await screen.findByText('first-client')).toBeInTheDocument();
+
+ await user.click(screen.getByRole('combobox', { name: '会话 ID' }));
+ await user.click(
+ await screen.findByText('session-2', { selector:
'.ant-select-item-option-content' }),
+ );
+
+ expect(apiMocks.queryLiteTopicSession).toHaveBeenCalledWith('session-2');
+ expect(await screen.findByText('second-client')).toBeInTheDocument();
+ expect(screen.queryByText('first-client')).not.toBeInTheDocument();
+ });
+
it('keeps the latest session detail when an earlier request resolves last',
async () => {
const firstSession = createDeferred<{
sessionId: string;