Skip to content

Commit c3de1d8

Browse files
committed
feat: download
1 parent 5167990 commit c3de1d8

File tree

6 files changed

+237
-39
lines changed

6 files changed

+237
-39
lines changed

src/app/runs/[id]/_Tabs/LogTab/logs.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import { ErrorFallback } from "@/components/Error";
22
import { EmptyStateLogs } from "@/components/logs/empty-state-logs";
33
import { EnhancedLogsViewer } from "@/components/logs/enhanced-log-viewer";
44
import { LoadingLogs } from "@/components/logs/loading-logs";
5+
import { LogViewerProvider, useLogViewerContext } from "@/components/logs/logviewer-context";
6+
import { apiPaths, createApiPath } from "@/data/api";
57
import { usePipelineRun } from "@/data/pipeline-runs/pipeline-run-detail-query";
68
import { useRunLogs } from "@/data/pipeline-runs/run-logs";
79
import { Skeleton } from "@zenml-io/react-component-library/components/server";
810
import { useState } from "react";
911
import { useParams } from "react-router-dom";
1012
import { LogCombobox } from "./combobox";
11-
import { LogViewerProvider, useLogViewerContext } from "@/components/logs/logviewer-context";
1213

1314
export function LogTab() {
1415
const { runId } = useParams() as { runId: string };
@@ -73,9 +74,12 @@ function LogDisplay({ selectedSource, runId }: LogTabContentProps) {
7374
return <ErrorFallback err={runLogs.error} />;
7475
}
7576

77+
const downloadUrl =
78+
createApiPath(apiPaths.runs.logsDownload(runId)) + "?source=" + selectedSource;
79+
7680
return (
7781
<div className="h-full w-full">
78-
<EnhancedLogsViewer logPage={runLogs.data} />
82+
<EnhancedLogsViewer downloadLink={downloadUrl} logPage={runLogs.data} />
7983
</div>
8084
);
8185
}

src/components/logs/enhanced-log-viewer.tsx

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Collapse from "@/assets/icons/collapse-text.svg?react";
22
import Expand from "@/assets/icons/expand-full.svg?react";
3-
import { LogEntry as LogEntryType, LogPage } from "@/types/logs"; // Assuming types are in src/types/logs.ts
3+
import { LogPage } from "@/types/logs"; // Assuming types are in src/types/logs.ts
44
import { Button } from "@zenml-io/react-component-library/components/server";
55
import React, { useState } from "react";
66
import { EmptyStateLogs } from "./empty-state-logs";
@@ -13,9 +13,10 @@ import { useLogSearch } from "./use-log-search";
1313
interface EnhancedLogsViewerProps {
1414
logPage: LogPage;
1515
itemsPerPage?: number;
16+
downloadLink?: string;
1617
}
1718

18-
export function EnhancedLogsViewer({ logPage }: EnhancedLogsViewerProps) {
19+
export function EnhancedLogsViewer({ logPage, downloadLink }: EnhancedLogsViewerProps) {
1920
const { currentPage, setCurrentPage } = useLogViewerContext();
2021
const [textWrapEnabled, setTextWrapEnabled] = useState(true);
2122
const { searchQuery, setSearchQuery } = useLogViewerContext();
@@ -40,19 +41,6 @@ export function EnhancedLogsViewer({ logPage }: EnhancedLogsViewerProps) {
4041
setCurrentPage(1);
4142
}, [searchQuery]);
4243

43-
const handleDownloadLogs = () => {
44-
const logText = getOriginalLogText(logsToDisplay);
45-
const blob = new Blob([logText], { type: "text/plain" });
46-
const url = URL.createObjectURL(blob);
47-
const a = document.createElement("a");
48-
a.href = url;
49-
a.download = `logs-${new Date().toISOString().split("T")[0]}.txt`;
50-
document.body.appendChild(a);
51-
a.click();
52-
document.body.removeChild(a);
53-
URL.revokeObjectURL(url);
54-
};
55-
5644
const handleToggleTextWrap = () => {
5745
setTextWrapEnabled((prev) => !prev);
5846
};
@@ -62,8 +50,8 @@ export function EnhancedLogsViewer({ logPage }: EnhancedLogsViewerProps) {
6250
return (
6351
<div className="flex h-full flex-col space-y-5">
6452
<LogToolbar
53+
downloadLink={downloadLink}
6554
onSearchChange={setSearchQuery}
66-
onDownload={handleDownloadLogs}
6755
searchQuery={searchQuery}
6856
currentMatchIndex={currentMatchIndex}
6957
totalMatches={totalMatches}
@@ -81,8 +69,8 @@ export function EnhancedLogsViewer({ logPage }: EnhancedLogsViewerProps) {
8169
return (
8270
<div className="flex h-full flex-col space-y-5">
8371
<LogToolbar
72+
downloadLink={downloadLink}
8473
onSearchChange={setSearchQuery}
85-
onDownload={handleDownloadLogs}
8674
searchQuery={searchQuery}
8775
currentMatchIndex={currentMatchIndex}
8876
totalMatches={totalMatches}
@@ -159,7 +147,3 @@ export function EnhancedLogsViewer({ logPage }: EnhancedLogsViewerProps) {
159147
</div>
160148
);
161149
}
162-
163-
function getOriginalLogText(logs: LogEntryType[]) {
164-
return logs.map((log) => log.message).join("\n");
165-
}

src/components/logs/toolbar.tsx

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { useLogViewerContext } from "./logviewer-context";
77

88
interface LogToolbarProps {
99
onSearchChange: (searchTerm: string) => void;
10-
onDownload: () => void;
10+
downloadLink?: string;
11+
1112
// Search-related props from useLogSearch hook
1213
searchQuery: string;
1314
currentMatchIndex?: number;
@@ -18,8 +19,7 @@ interface LogToolbarProps {
1819

1920
export function LogToolbar({
2021
onSearchChange,
21-
22-
onDownload,
22+
downloadLink,
2323
searchQuery,
2424
currentMatchIndex = 0,
2525
totalMatches = 0,
@@ -74,17 +74,21 @@ export function LogToolbar({
7474
</div>
7575

7676
{/* Right side - Action Buttons */}
77-
<Button
78-
size="md"
79-
emphasis="subtle"
80-
intent="secondary"
81-
onClick={onDownload}
82-
title="Download logs as file"
83-
className="bg-theme-surface-primary"
84-
>
85-
<Download className="mr-1 h-5 w-5 fill-theme-text-tertiary" />
86-
Download
87-
</Button>
77+
{downloadLink && (
78+
<Button
79+
asChild
80+
size="md"
81+
emphasis="subtle"
82+
intent="secondary"
83+
title="Download logs as file"
84+
className="bg-theme-surface-primary"
85+
>
86+
<a href={downloadLink}>
87+
<Download className="mr-1 h-5 w-5 fill-theme-text-tertiary" />
88+
Download
89+
</a>
90+
</Button>
91+
)}
8892
</div>
8993
</div>
9094
</>

src/components/steps/step-sheet/LogsTab.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { LoadingLogs } from "@/components/logs/loading-logs";
33
import { useStepLogs } from "@/data/steps/step-logs-query";
44
import { ErrorFallback } from "../../Error";
55
import { useLogViewerContext } from "@/components/logs/logviewer-context";
6+
import { apiPaths, createApiPath } from "@/data/api";
67

78
type Props = {
89
stepId: string;
@@ -23,9 +24,11 @@ export function StepLogsTab({ stepId }: Props) {
2324
return <LoadingLogs />;
2425
}
2526

27+
const downloadUrl = createApiPath(apiPaths.steps.logsDownload(stepId));
28+
2629
return (
2730
<div className="space-y-5">
28-
<EnhancedLogsViewer logPage={data} />
31+
<EnhancedLogsViewer downloadLink={downloadUrl} logPage={data} />
2932
</div>
3033
);
3134
}

src/data/api.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const apiPaths = {
3030
detail: (id: string) => `/runs/${id}`,
3131
dag: (id: string) => `/runs/${id}/dag`,
3232
logs: (runId: string) => `/runs/${runId}/logs`,
33+
logsDownload: (runId: string) => `/runs/${runId}/logs/download`,
3334
refresh: (runId: string) => `/runs/${runId}/refresh`,
3435
stop: (runId: string) => `/runs/${runId}/stop`
3536
},
@@ -70,7 +71,8 @@ export const apiPaths = {
7071
},
7172
steps: {
7273
detail: (stepId: string) => `/steps/${stepId}`,
73-
logs: (stepId: string) => `/steps/${stepId}/logs`
74+
logs: (stepId: string) => `/steps/${stepId}/logs`,
75+
logsDownload: (stepId: string) => `/steps/${stepId}/logs/download`
7476
},
7577
users: {
7678
all: "/users",

0 commit comments

Comments
 (0)