Skip to content

Commit 6c0c6d1

Browse files
feat: add DownloadLogs component and integrate into task details (#56)
* feat: add DownloadLogs component and integrate into task details * feat: enhance DownloadLogs component with detailed log formatting and error handling --------- Co-authored-by: paypes <[email protected]>
1 parent af1c470 commit 6c0c6d1

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

src/modules/tasks/DownloadLogs.tsx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { cn } from '@/lib/utils';
2+
import { useMutation } from '@tanstack/react-query';
3+
import { LoaderCircle } from 'lucide-react';
4+
import { Button } from '@/components/ui/button';
5+
import { getIExec } from '@/externals/iexecSdkClient';
6+
7+
export function DownloadLogs({
8+
taskid,
9+
className,
10+
}: {
11+
taskid: string;
12+
className?: string;
13+
}) {
14+
const {
15+
mutate: downloadLogs,
16+
isPending,
17+
isError,
18+
} = useMutation({
19+
mutationKey: ['downloadLogs', taskid],
20+
mutationFn: async () => {
21+
if (!taskid) {
22+
throw new Error('Task ID is required');
23+
}
24+
25+
const iexec = await getIExec();
26+
const logs = await iexec.task.fetchLogs(taskid);
27+
28+
if (!logs || logs.length === 0) {
29+
throw new Error('No logs available for this task');
30+
}
31+
32+
const timestamp = new Date().toISOString();
33+
const header = `Task Logs for ${taskid}\nGenerated on: ${timestamp}\nTotal workers: ${logs.length}\n\n${'='.repeat(80)}\n`;
34+
35+
const logsString =
36+
header +
37+
logs
38+
.map(({ worker, stdout, stderr }) => {
39+
let workerLog = `\n----- worker ${worker} -----\n\n`;
40+
41+
if (stdout && stdout.trim()) {
42+
workerLog += `stdout:\n${stdout.trim()}\n\n`;
43+
} else {
44+
workerLog += `stdout: (empty)\n\n`;
45+
}
46+
47+
if (stderr && stderr.trim()) {
48+
workerLog += `stderr:\n${stderr.trim()}\n\n`;
49+
} else {
50+
workerLog += `stderr: (empty)\n\n`;
51+
}
52+
53+
return workerLog;
54+
})
55+
.join('\n' + '='.repeat(80) + '\n');
56+
57+
const blob = new Blob([logsString], { type: 'text/plain' });
58+
const url = URL.createObjectURL(blob);
59+
60+
const link = document.createElement('a');
61+
link.href = url;
62+
link.download = `task-${taskid}-logs.txt`;
63+
document.body.appendChild(link);
64+
link.click();
65+
document.body.removeChild(link);
66+
67+
URL.revokeObjectURL(url);
68+
69+
return logs;
70+
},
71+
onError: (error) => {
72+
console.error('Failed to download logs:', error);
73+
},
74+
});
75+
76+
if (!taskid) {
77+
return null;
78+
}
79+
80+
return (
81+
<div className="flex items-center gap-2">
82+
<Button
83+
onClick={() => downloadLogs()}
84+
variant="outline"
85+
size="sm"
86+
className={cn(className)}
87+
disabled={isPending}
88+
>
89+
{isPending && <LoaderCircle className="mr-2 h-4 w-4 animate-spin" />}
90+
{isPending ? 'Downloading...' : 'Download logs'}
91+
</Button>
92+
{isError && (
93+
<p className="text-sm text-red-500">
94+
Failed to download logs, please retry later
95+
</p>
96+
)}
97+
</div>
98+
);
99+
}

src/modules/tasks/task/buildTaskDetails.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,26 @@ import SmartLinkGroup from '@/components/SmartLinkGroup';
44
import Bytes from '@/modules/Bytes';
55
import JsonBlock from '@/modules/JsonBlock';
66
import TaskEvent from '@/modules/events/TaskEvent';
7+
import useUserStore from '@/stores/useUser.store';
78
import { taskResultToObject } from '@/utils/format';
89
import {
910
formatDateCompact,
1011
formatElapsedTime,
1112
} from '@/utils/formatElapsedTime';
1213
import { truncateAddress } from '@/utils/truncateAddress';
1314
import { ClaimButton } from '../ClaimButton';
15+
import { DownloadLogs } from '../DownloadLogs';
1416
import { DownloadResult } from '../DownloadResult';
1517
import StatusCell from '../StatusCell';
1618

1719
export function buildTaskDetails({ task }: { task: TaskQuery['task'] }) {
1820
if (!task) {
1921
return {};
2022
}
23+
24+
const { address: userAddress } = useUserStore.getState();
25+
const isCurrentUser = task.requester.address === userAddress;
26+
2127
return {
2228
...(task.taskid && {
2329
Taskid: (
@@ -136,6 +142,9 @@ export function buildTaskDetails({ task }: { task: TaskQuery['task'] }) {
136142
...(task.resultDigest && {
137143
'Result digest': <Bytes>{task.resultDigest}</Bytes>,
138144
}),
145+
...(isCurrentUser && {
146+
Logs: <DownloadLogs taskid={task.taskid} />,
147+
}),
139148
...(task.finalDeadline && {
140149
Deadline: (
141150
<p>

0 commit comments

Comments
 (0)