Copilot commented on code in PR #889:
URL:
https://github.com/apache/rocketmq-dashboard/pull/889#discussion_r3708468095
##########
web/src/pages/instance/consumer.tsx:
##########
@@ -648,7 +692,13 @@ const ConsumerPage = () => {
</Button>
<Button
icon={<ExportOutlined />}
- onClick={() => message.success(`已导出 ${filtered.length} 个 Group`)}
+ onClick={() => {
+ downloadCsv(
+ `rocketmq-consumer-groups-${new Date().toISOString().slice(0,
10)}.csv`,
Review Comment:
导出文件名使用 `new Date().toISOString().slice(0, 10)` 会基于 UTC 日期,用户在非 UTC
时区可能出现文件名日期偏差(跨日时尤为明显)。该文件已引入 dayjs,建议用本地日期格式化以保持与其他导出一致。
##########
web/src/pages/instance/__tests__/ConsumerPage.test.tsx:
##########
@@ -108,6 +116,48 @@ describe('Consumer page', () => {
expect(consumerService.listConsumerGroups).toHaveBeenCalledTimes(1);
});
+ it('downloads the currently filtered consumer groups when exporting', async
() => {
+ const user = userEvent.setup();
+ const clickSpy = vi.spyOn(HTMLAnchorElement.prototype,
'click').mockImplementation(vi.fn());
+ let exportedBlob: Blob | undefined;
+ vi.mocked(URL.createObjectURL).mockImplementation((blob) => {
Review Comment:
这里对 `URL.createObjectURL` 使用 `mockImplementation`
会把实现覆盖到后续测试(`vi.clearAllMocks()` 只清调用记录,不恢复实现),可能造成用例间耦合。该测试只需要拦截一次调用,建议用
`mockImplementationOnce`。
##########
web/src/pages/instance/__tests__/ConsumerPage.test.tsx:
##########
@@ -108,6 +116,48 @@ describe('Consumer page', () => {
expect(consumerService.listConsumerGroups).toHaveBeenCalledTimes(1);
});
+ it('downloads the currently filtered consumer groups when exporting', async
() => {
+ const user = userEvent.setup();
+ const clickSpy = vi.spyOn(HTMLAnchorElement.prototype,
'click').mockImplementation(vi.fn());
+ let exportedBlob: Blob | undefined;
+ vi.mocked(URL.createObjectURL).mockImplementation((blob) => {
+ exportedBlob = blob as Blob;
+ return 'blob:consumer-group-export';
+ });
+ vi.mocked(consumerService.listConsumerGroups).mockResolvedValue([
+ {
+ ...group,
+ name: 'orders-cg',
+ namespace: 'trade',
Review Comment:
该用例声明验证了“formula-prefix protection”,但当前包含公式前缀(`=formula-risk`)的 group
会被过滤掉并不会进入导出内容,导致防护逻辑没有被真正覆盖。建议把被导出的 group(当前是 orders-cg)的 namespace 设置为
`=...`,再断言导出的 CSV 中出现前置单引号。
This issue also appears on line 155 of the same file.
##########
web/src/pages/instance/consumer.tsx:
##########
@@ -112,6 +112,50 @@ const formatDelay = (totalSeconds: number): string => {
return parts.length > 0 ? parts.join('') : '0秒';
};
+const GROUP_EXPORT_COLUMNS: Array<{ header: string; value: (group:
ConsumerGroup) => unknown }> = [
+ { header: 'Name', value: (group) => group.name },
+ { header: 'Namespace', value: (group) => group.namespace },
+ { header: 'Cluster ID', value: (group) => group.clusterId },
+ { header: 'Subscription Mode', value: (group) => group.subscriptionMode },
+ { header: 'Consume Type', value: (group) => group.consumeType },
+ { header: 'Online Instances', value: (group) => group.onlineInstances },
+ { header: 'Total Lag', value: (group) => group.totalLag },
+ { header: 'Delay Seconds', value: (group) => group.delaySeconds },
+ { header: 'Subscription Data Type', value: (group) =>
group.subscriptionDataType },
+ { header: 'Delivery Order Type', value: (group) => group.deliveryOrderType },
+ { header: 'Retry Max Times', value: (group) => group.retryMaxTimes },
+ { header: 'Subscribed Topics', value: (group) =>
group.subscribedTopics.join(';') },
+ { header: 'Created At', value: (group) => group.createdAt },
+ { header: 'Updated At', value: (group) => group.updatedAt },
+];
+
+const escapeCsvCell = (value: unknown) => {
+ const text = value == null ? '' : String(value);
+ const formulaSafeText = /^[=+\-@]/.test(text) ? `'${text}` : text;
Review Comment:
CSV 公式注入防护这里仅匹配 ^[=+\-@],但项目里其他 CSV 导出(例如 DLQ 导出)也把 `\t`/`\r` 前缀纳入了判定,避免通过
tab/CR 前缀绕过。建议对齐现有实现,把 `\t`/`\r` 也加入正则。
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]