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);
}