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 9d288da90 fix(web): preserve the original message body in downloads
and copies (#4777)
9d288da90 is described below
commit 9d288da909fe1a258f72d35ff1b9adea5321e528
Author: Wang1rrr <[email protected]>
AuthorDate: Thu Sep 24 10:47:58 2026 +0800
fix(web): preserve the original message body in downloads and copies (#4777)
The download and copy actions reused the display formatter, which parses
the body as JSON and re-serialises it, rounding unsafe integers and rewriting
whitespace even though the received body is already a string. Both now use the
received body directly; the display policy is unchanged.
---
.../pages/instance/__tests__/MessagePage.test.tsx | 66 ++++++++++++++++++++++
web/src/pages/instance/message.tsx | 4 +-
2 files changed, 68 insertions(+), 2 deletions(-)
diff --git a/web/src/pages/instance/__tests__/MessagePage.test.tsx
b/web/src/pages/instance/__tests__/MessagePage.test.tsx
index b737ab01b..963f0a79f 100644
--- a/web/src/pages/instance/__tests__/MessagePage.test.tsx
+++ b/web/src/pages/instance/__tests__/MessagePage.test.tsx
@@ -23,6 +23,7 @@ import { MemoryRouter } from 'react-router-dom';
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from
'vitest';
import type { MessageRecord } from '../../../api/message';
import { LangProvider } from '../../../i18n/LangContext';
+import * as downloadUtils from '../../../utils/download';
const messageServiceMocks = vi.hoisted(() => ({
consumeMessageDirectly: vi.fn(),
@@ -276,6 +277,71 @@ describe('Message page query history', () => {
expect(messageServiceMocks.queryMessages).not.toHaveBeenCalled();
});
+ it.each([
+ ['UnsafeInteger', '{"orderId":9007199254740993}'],
+ ['Int64Max', '{"orderId":9223372036854775807}'],
+ ['SafeInteger', '{\n "orderId": 9007199254740991\n}'],
+ ['QuotedId', '{\n "orderId": "9223372036854775807"\n}'],
+ ['JsonWhitespace', '{\r\n\t"message": "你好", "enabled": true\r\n}\r\n'],
+ ['PlainText', '订单状态: ready\r\n next line\r\n'],
+ ])('preservesOriginal%sBodyWhenDownloadingTest', async (_name, body) => {
+ const user = userEvent.setup();
+ const download = vi.spyOn(downloadUtils,
'downloadBlob').mockImplementation(() => {});
+ const msgId = 'MID-DOWNLOAD';
+ messageServiceMocks.queryMessages.mockResolvedValue([{
...createMessage(msgId), body }]);
+ renderWithProviders(<MessagePage />);
+
+ await user.click(lastElement(screen.getAllByRole('combobox')));
+ await user.click(lastElement(await screen.findAllByText('order-create')));
+ await user.click(screen.getByRole('button', { name: /^search查询$/ }));
+
+ const row = await screen.findByRole('row', { name: new RegExp(msgId) });
+ await user.click(within(row).getByRole('button', { name: /下载/ }));
+
+ expect(download).toHaveBeenCalledTimes(1);
+ const [blob, filename] = download.mock.calls[0];
+ expect(filename).toBe(`${msgId}.json`);
+ expect(blob.type).toBe('application/json');
+ await expect(blob.text()).resolves.toBe(body);
+ });
+
+ it('copiesOriginalBodyFromMessageDetailsTest', async () => {
+ const user = userEvent.setup();
+ const body = '{ "orderId":9223372036854775807 }\r\n';
+ messageServiceMocks.queryMessages.mockResolvedValue([{
...createMessage('MID-COPY'), body }]);
+ renderWithProviders(<MessagePage />);
+
+ await user.click(lastElement(screen.getAllByRole('combobox')));
+ await user.click(lastElement(await screen.findAllByText('order-create')));
+ await user.click(screen.getByRole('button', { name: /^search查询$/ }));
+ const row = await screen.findByRole('row', { name: /MID-COPY/ });
+ await user.click(within(row).getByRole('button', { name: /详情/ }));
+
+ const dialog = await screen.findByRole('dialog', { name: '消息详情' });
+ const bodyParagraph = within(dialog).getByText(/"orderId":/);
+ const originalExecCommand = Object.getOwnPropertyDescriptor(document,
'execCommand');
+ let copiedText: string | undefined;
+ const execCommand = vi.fn(() => {
+ copiedText = document.getSelection()?.toString();
+ return true;
+ });
+ Object.defineProperty(document, 'execCommand', {
+ configurable: true,
+ value: execCommand,
+ });
+ try {
+ await user.click(within(bodyParagraph).getByRole('button'));
+ expect(execCommand).toHaveBeenCalledWith('copy');
+ expect(copiedText).toBe(body);
+ } finally {
+ if (originalExecCommand) {
+ Object.defineProperty(document, 'execCommand', originalExecCommand);
+ } else {
+ Reflect.deleteProperty(document, 'execCommand');
+ }
+ }
+ });
+
it('shows the redelivery count on the message detail panel', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
messageServiceMocks.queryMessages.mockResolvedValue([
diff --git a/web/src/pages/instance/message.tsx
b/web/src/pages/instance/message.tsx
index 4806404d4..921f42dd6 100644
--- a/web/src/pages/instance/message.tsx
+++ b/web/src/pages/instance/message.tsx
@@ -728,7 +728,7 @@ const MessagePageContent = ({
};
const handleDownload = (record: MessageRecord) => {
- const blob = new Blob([formatBody(record.body)], { type:
'application/json' });
+ const blob = new Blob([record.body], { type: 'application/json' });
downloadBlob(blob, `${record.msgId}.json`);
message.success(t('messagePage.downloadSuccess'));
};
@@ -943,7 +943,7 @@ const MessagePageContent = ({
{t('topic.messageBody')}
</Typography.Title>
<Paragraph
- copyable
+ copyable={{ text: selectedMsg.body }}
style={{
background: '#f5f5f5',
padding: '12px 16px',