feat(platform): copy quality issue notification

This commit is contained in:
lingniu
2026-07-04 14:05:48 +08:00
parent e515a4251e
commit a91eefd614
2 changed files with 93 additions and 1 deletions

View File

@@ -249,6 +249,10 @@ function priorityIssueRows(rows: QualityIssueRow[]): PriorityIssueRow[] {
.slice(0, 5);
}
function priorityIssueFromRow(row: QualityIssueRow): PriorityIssueRow {
return priorityIssueRows([row])[0];
}
function priorityIssueNotificationText(row: PriorityIssueRow) {
const lookup = qualityIssueVehicleLookup(row);
const dateFrom = issueEvidenceDate(row.lastSeen);
@@ -817,6 +821,7 @@ export function Quality({
<Button size="small" disabled={!lookup.key || !onOpenHistory} onClick={() => openIssueHistory(row)}></Button>
<Button size="small" disabled={!lookup.key || !onOpenRaw} onClick={() => openIssueRaw(row)}> RAW</Button>
<Button size="small" disabled={!lookup.key || !onOpenMileage} onClick={() => openIssueMileage(row)}></Button>
<Button size="small" onClick={() => copyText(priorityIssueNotificationText(priorityIssueFromRow(row)), '告警通知')}></Button>
<Button size="small" disabled={!lookup.key} onClick={() => onOpenVehicle(lookup.key, row.protocol)}>
{lookup.label}
</Button>

View File

@@ -2708,7 +2708,7 @@ test('copies notification text from quality priority queue', async () => {
render(<App />);
expect(await screen.findByText('处置优先队列')).toBeInTheDocument();
fireEvent.click(await screen.findByRole('button', { name: '复制通知' }));
fireEvent.click(screen.getAllByRole('button', { name: '复制通知' })[0]);
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【P0 告警通知】'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆粤A通知1 / 13307795425'));
@@ -2912,6 +2912,93 @@ test('copies priority queue notification digest on quality page', async () => {
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('告警筛选http://localhost:3000/#/quality'));
});
test('copies notification text from regular quality issue row', async () => {
window.history.replaceState(null, '', '/#/quality');
const writeText = vi.fn(() => Promise.resolve());
Object.defineProperty(navigator, 'clipboard', {
configurable: true,
value: { writeText }
});
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
return {
ok: true,
json: async () => ({
data: { linkHealth: [], kafkaLag: 0, activeConnections: 120000, capacityFindings: [], redisOnlineKeys: 368, tdengineWritable: true, mysqlWritable: true, runtime: { requestTimeoutMs: 5000 } },
traceId: 'trace-test',
timestamp: 1783094400000
})
} as Response;
}
if (path.includes('/api/quality/summary')) {
return {
ok: true,
json: async () => ({
data: {
issueVehicleCount: 1,
issueRecordCount: 1,
errorCount: 0,
warningCount: 1,
protocols: [{ name: 'GB32960', count: 1 }],
issueTypes: [{ name: 'FIELD_MISSING', count: 1 }]
},
traceId: 'trace-test',
timestamp: 1783094400000
})
} as Response;
}
if (path.includes('/api/quality/issues')) {
return {
ok: true,
json: async () => ({
data: {
items: [{
vin: 'VIN-ROW-NOTIFY',
plate: '粤A行通知',
phone: '13307795425',
sourceEndpoint: '115.29.187.205:32960',
protocol: 'GB32960',
issueType: 'FIELD_MISSING',
severity: 'warning',
lastSeen: '2026-07-03 20:12:10',
detail: '位置字段缺失,影响轨迹回放'
}],
total: 1,
limit: 20,
offset: 0
},
traceId: 'trace-test',
timestamp: 1783094400000
})
} as Response;
}
return {
ok: true,
json: async () => ({
data: { items: [], total: 0, limit: 20, offset: 0 },
traceId: 'trace-test',
timestamp: 1783094400000
})
} as Response;
});
render(<App />);
expect(await screen.findByText('质量问题')).toBeInTheDocument();
const copyButtons = screen.getAllByRole('button', { name: '复制通知' });
expect(copyButtons.length).toBeGreaterThanOrEqual(2);
fireEvent.click(copyButtons[copyButtons.length - 1]);
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【P1 告警通知】字段缺失'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆粤A行通知 / VIN-ROW-NOTIFY'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('数据来源GB32960'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('建议动作:核对解析字段'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('详情:位置字段缺失,影响轨迹回放'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('轨迹证据http://localhost:3000/#/history?keyword=VIN-ROW-NOTIFY&protocol=GB32960&dateFrom=2026-07-03&dateTo=2026-07-04'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆服务http://localhost:3000/#/detail?keyword=VIN-ROW-NOTIFY&protocol=GB32960'));
});
test('opens realtime map context from quality priority queue', async () => {
window.history.replaceState(null, '', '/#/quality');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {