feat(logs): add scenario run logs export
- Backend: logs export endpoint for CSV and Markdown formats - Frontend: ExportLogsModal with download button on run detail page - Updated i18n and CHANGELOG - Version bump: 1.9.1 -> 1.10.0
This commit is contained in:
@@ -407,6 +407,51 @@ export class ScenarioService {
|
||||
return Object.assign(run, { logs });
|
||||
}
|
||||
|
||||
async exportRunLogs(
|
||||
scenarioId: string,
|
||||
runId: string,
|
||||
format: "csv" | "md",
|
||||
): Promise<string> {
|
||||
await this.findOne(scenarioId); // 404 guard
|
||||
const run = await this.runRepo.findOne({
|
||||
where: { id: runId, scenarioId },
|
||||
});
|
||||
if (!run)
|
||||
throw new NotFoundException(
|
||||
`Run ${runId} not found in scenario ${scenarioId}`,
|
||||
);
|
||||
const logs = await this.runLogRepo.find({
|
||||
where: { runId },
|
||||
order: { createdAt: "ASC" },
|
||||
});
|
||||
|
||||
if (format === "csv") {
|
||||
const escapeCsv = (value: string): string =>
|
||||
/[",\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
|
||||
const rows = [
|
||||
["Timestamp", "Level", "Message"].map(escapeCsv).join(","),
|
||||
...logs.map((l) =>
|
||||
[l.createdAt.toISOString(), l.level, l.message]
|
||||
.map((v) => escapeCsv(String(v)))
|
||||
.join(","),
|
||||
),
|
||||
];
|
||||
return rows.join("\n");
|
||||
}
|
||||
|
||||
const lines = [
|
||||
`# Run ${runId} logs`,
|
||||
"",
|
||||
"| Timestamp | Level | Message |",
|
||||
"| --- | --- | --- |",
|
||||
...logs.map(
|
||||
(l) =>
|
||||
`| ${l.createdAt.toISOString()} | ${l.level} | ${l.message.replace(/\|/g, "\\|").replace(/\n/g, "<br>")} |`,
|
||||
),
|
||||
];
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
async waitForRun(
|
||||
scenarioId: string,
|
||||
runId: string,
|
||||
|
||||
Reference in New Issue
Block a user