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 9d22a2729 fix(audit): surface the export cap instead of advising a 
retry that cannot work (#5895)
9d22a2729 is described below

commit 9d22a272942a92f3402ebb365d41578a87c731fa
Author: 烤化の初雪 <[email protected]>
AuthorDate: Mon Oct 26 19:50:30 2026 +0800

    fix(audit): surface the export cap instead of advising a retry that cannot 
work (#5895)
    
    * fix(audit): surface the export cap instead of advising a retry that 
cannot work
    GET /audit-logs/export refuses any filtered result set above
    AuditService.MAX_EXPORT_RECORDS with
        400 {"code":400,"message":"Audit log export exceeds the maximum of 10000
        records; narrow the filters"}
    The audit page's export handler caught the failure without the error object 
and
    showed its generic "导出审计日志失败,请稍后重试" ("... try again later"), which is
    advice that can never succeed: the same filters produce the same 400. The 
one
    actionable fact - narrow the filters - is the only thing the toast drops.
    The utilities the fix needs already exist: exportAuditLogs is a JSON call, 
so
    error.response.data.message carries the reason, and describeApiError is the
    shared extractor the alert and instance pages use for exactly this (they 
surface
    alert-rule import conflicts the same way). Fall back to the existing string 
when
    the failure carries no server message.
    Tests (web/): npx vitest run src/pages/ops/__tests__/AuditPage.test.tsx
    - new case "surfaces the export cap instead of telling the user to retry" 
fails
      before this change (the cap message is absent and the generic toast is 
shown)
      and passes after it; 12 tests in the file pass.
    - npx eslint on both files reports no errors; npx tsc -b passes.
---
 web/node_modules                               |  1 +
 web/src/pages/ops/__tests__/AuditPage.test.tsx | 19 +++++++++++++++++++
 web/src/pages/ops/audit.tsx                    |  7 +++++--
 3 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/web/node_modules b/web/node_modules
new file mode 120000
index 000000000..483d31606
--- /dev/null
+++ b/web/node_modules
@@ -0,0 +1 @@
+/home/xiaoyang/rmq-worktrees/pr5926/web/node_modules
\ No newline at end of file
diff --git a/web/src/pages/ops/__tests__/AuditPage.test.tsx 
b/web/src/pages/ops/__tests__/AuditPage.test.tsx
index 14e27120f..186094096 100644
--- a/web/src/pages/ops/__tests__/AuditPage.test.tsx
+++ b/web/src/pages/ops/__tests__/AuditPage.test.tsx
@@ -158,6 +158,25 @@ describe('Audit page', () => {
     expect(revokeObjectURL).toHaveBeenCalledWith('blob:audit');
   });
 
+  it('surfaces the export cap instead of telling the user to retry', async () 
=> {
+    vi.mocked(opsService.exportAuditLogs).mockRejectedValueOnce({
+      response: {
+        data: {
+          code: 400,
+          message: 'Audit log export exceeds the maximum of 10000 records; 
narrow the filters',
+        },
+      },
+    });
+    const user = userEvent.setup();
+    renderWithProviders(<AuditPage />);
+
+    expect(await screen.findAllByText('topic-a')).not.toHaveLength(0);
+    await user.click(screen.getByRole('button', { name: /导出/ }));
+
+    expect(await screen.findByText(/narrow the filters/)).toBeInTheDocument();
+    expect(screen.queryByText('导出审计日志失败,请稍后重试')).not.toBeInTheDocument();
+  });
+
   it('renders control-plane audit labels and parsed detail values', async () 
=> {
     vi.mocked(opsService.listAuditRecords).mockResolvedValueOnce({
       items: [
diff --git a/web/src/pages/ops/audit.tsx b/web/src/pages/ops/audit.tsx
index 9f79e1077..c533df47b 100644
--- a/web/src/pages/ops/audit.tsx
+++ b/web/src/pages/ops/audit.tsx
@@ -49,6 +49,7 @@ import {
   listAuditRecords,
 } from '../../services/opsService';
 import { downloadBlob } from '../../utils/download';
+import { describeApiError } from '../../utils/apiError';
 import { formatUtcDateTime } from '../../utils/format';
 import { tableScrollX } from '../../utils/table';
 import {
@@ -329,8 +330,10 @@ const AuditPage: React.FC = () => {
       const csv = await exportAuditLogs(activeFilter);
       const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
       downloadBlob(blob, 
`rocketmq-audit-logs-${dayjs().format('YYYY-MM-DD')}.csv`);
-    } catch {
-      message.error(t('audit.exportFailed'));
+    } catch (error) {
+      // The server refuses an export above AuditService.MAX_EXPORT_RECORDS 
with a 400 that names
+      // the cap and says to narrow the filters; retrying the same request can 
never succeed.
+      message.error(describeApiError(error, t('audit.exportFailed')));
     } finally {
       setExporting(false);
     }

Reply via email to