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 f597fecc2 fix(audit): let the timeline target be activated from the 
keyboard (#4580)
f597fecc2 is described below

commit f597fecc2442d061ab02f2e0fd69bf6bcd342559
Author: Zhao Jianing <[email protected]>
AuthorDate: Mon Sep 21 21:03:15 2026 +0800

    fix(audit): let the timeline target be activated from the keyboard (#4580)
    
    The audit timeline's target cell in `web/src/pages/ops/audit.tsx` was 
already marked up as interactive — `role="button"`, `tabIndex={0}` and an 
`aria-label` were all there — so a keyboard user could Tab to it and assistive 
tech announced it as a button, but Enter and Space did nothing: `onClick` was 
the only handler. That cell is the sole entry point to the resource operation 
timeline, so the timeline was unreachable without a pointer (WCAG 2.1.1 
Keyboard).
    
    The `setTimelineResource` call is lifted into an `openTimeline` closure 
shared by `onClick` and a new `onKeyDown` that fires on Enter or Space and 
calls `preventDefault()`, so Space does not also scroll the page. `alerts.tsx` 
and `MainLayout.tsx` already paired the two handlers, which leaves no 
`role="button"` node in the web app that cannot be activated from the keyboard. 
The added test drives `fireEvent.keyDown` and asserts the resulting 
`listAuditRecords` arguments, so extracting t [...]
---
 web/src/pages/ops/__tests__/AuditPage.test.tsx | 21 ++++++++++++++++++++-
 web/src/pages/ops/audit.tsx                    | 20 +++++++++++++-------
 2 files changed, 33 insertions(+), 8 deletions(-)

diff --git a/web/src/pages/ops/__tests__/AuditPage.test.tsx 
b/web/src/pages/ops/__tests__/AuditPage.test.tsx
index ee0bea6fa..4aa7c1d85 100644
--- a/web/src/pages/ops/__tests__/AuditPage.test.tsx
+++ b/web/src/pages/ops/__tests__/AuditPage.test.tsx
@@ -16,7 +16,7 @@
  */
 
 import { App } from 'antd';
-import { act, render, screen, waitFor } from '@testing-library/react';
+import { act, fireEvent, render, screen, waitFor } from 
'@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import type React from 'react';
 import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 
'vitest';
@@ -204,6 +204,25 @@ describe('Audit page', () => {
     );
   });
 
+  it('opens a resource timeline from the keyboard on a non-empty audit 
target', async () => {
+    renderWithProviders(<AuditPage />);
+
+    const target = await screen.findByRole('button', { name: '查看 topic-a 
操作时间线' });
+    target.focus();
+    fireEvent.keyDown(target, { key: 'Enter' });
+
+    expect(await screen.findByText('资源操作时间线')).toBeInTheDocument();
+    await waitFor(() =>
+      expect(opsService.listAuditRecords).toHaveBeenCalledWith({
+        page: 1,
+        pageSize: 20,
+        resourceType: 'TOPIC',
+        target: 'topic-a',
+        clusterId: 'prod-cn',
+      }),
+    );
+  });
+
   it('does not offer a timeline action for an empty audit target', async () => 
{
     vi.mocked(opsService.listAuditRecords).mockResolvedValueOnce({
       items: [
diff --git a/web/src/pages/ops/audit.tsx b/web/src/pages/ops/audit.tsx
index 930933f7d..d33fd79ca 100644
--- a/web/src/pages/ops/audit.tsx
+++ b/web/src/pages/ops/audit.tsx
@@ -362,6 +362,12 @@ const AuditPage: React.FC = () => {
       render: (_: string, record) => {
         const target = record.target;
         if (!target?.trim()) return <Text type="secondary">-</Text>;
+        const openTimeline = () =>
+          setTimelineResource({
+            resourceType: record.resourceType,
+            target,
+            clusterId: record.clusterId || null,
+          });
         return (
           <Tooltip title={describeAuditRecord(record, t)}>
             <Text
@@ -370,13 +376,13 @@ const AuditPage: React.FC = () => {
               tabIndex={0}
               aria-label={t('audit.timelineView', { target })}
               style={{ fontSize: 14, cursor: 'pointer' }}
-              onClick={() =>
-                setTimelineResource({
-                  resourceType: record.resourceType,
-                  target,
-                  clusterId: record.clusterId || null,
-                })
-              }
+              onClick={openTimeline}
+              onKeyDown={(event) => {
+                if (event.key === 'Enter' || event.key === ' ') {
+                  event.preventDefault();
+                  openTimeline();
+                }
+              }}
             >
               {target}
             </Text>

Reply via email to