Skip to content

Commit 5d9b442

Browse files
committed
feat: add input/output sandbox download support
1 parent 9d208cd commit 5d9b442

File tree

4 files changed

+108
-1
lines changed

4 files changed

+108
-1
lines changed

packages/diracx-web-components/src/components/JobMonitor/JobDataService.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ import utc from "dayjs/plugin/utc";
66

77
dayjs.extend(utc);
88
import { fetcher } from "../../hooks/utils";
9-
import { Filter, SearchBody, Job, JobHistory } from "../../types";
9+
import {
10+
Filter,
11+
SearchBody,
12+
Job,
13+
JobHistory,
14+
JobSandboxPFNResponse,
15+
SandboxUrlResponse,
16+
} from "../../types";
1017

1118
function processSearchBody(searchBody: SearchBody) {
1219
searchBody.search = searchBody.search?.map((filter: Filter) => {
@@ -203,3 +210,35 @@ export async function getJobHistory(
203210

204211
return { data: data[0].LoggingInfo };
205212
}
213+
214+
/**
215+
* Retrieves the sandbox information for a given job ID and sandbox type.
216+
* @param jobId - The ID of the job.
217+
* @param sbType - The type of the sandbox (input or output).
218+
* @param accessToken - The authentication token.
219+
* @returns A Promise that resolves to an object containing the headers and data of the sandboxes.
220+
*/
221+
export function getJobSandbox(
222+
diracxUrl: string | null,
223+
jobId: number,
224+
sbType: "input" | "output",
225+
accessToken: string,
226+
): Promise<{ headers: Headers; data: JobSandboxPFNResponse }> {
227+
const url = `${diracxUrl}/api/jobs/${jobId}/sandbox/${sbType}`;
228+
return fetcher([url, accessToken]);
229+
}
230+
231+
/**
232+
* Retrieves the sandbox URL for a given PFN.
233+
* @param pfn - The PFN of the job.
234+
* @param accessToken - The authentication token.
235+
* @returns A Promise that resolves to an object containing the headers and data of the sandbox URL.
236+
*/
237+
export function getJobSandboxUrl(
238+
diracxUrl: string | null,
239+
pfn: string,
240+
accessToken: string,
241+
): Promise<{ headers: Headers; data: SandboxUrlResponse }> {
242+
const url = `${diracxUrl}/api/jobs/sandbox?pfn=${pfn}`;
243+
return fetcher([url, accessToken]);
244+
}

packages/diracx-web-components/src/components/JobMonitor/JobDataTable.tsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import { JobHistoryDialog } from "./JobHistoryDialog";
3131
import {
3232
deleteJobs,
3333
getJobHistory,
34+
getJobSandbox,
35+
getJobSandboxUrl,
3436
killJobs,
3537
refreshJobs,
3638
rescheduleJobs,
@@ -359,6 +361,53 @@ export function JobDataTable({
359361
setIsHistoryDialogOpen(false);
360362
};
361363

364+
const handleSandboxDownload = async (
365+
jobId: number | null,
366+
sbType: "input" | "output",
367+
) => {
368+
if (!jobId) return;
369+
setBackdropOpen(true);
370+
try {
371+
const { data } = await getJobSandbox(
372+
diracxUrl,
373+
jobId,
374+
sbType,
375+
accessToken,
376+
);
377+
if (!data) throw new Error(`No sandbox found`);
378+
const pfn = data[0];
379+
setBackdropOpen(false);
380+
if (pfn) {
381+
const { data } = await getJobSandboxUrl(diracxUrl, pfn, accessToken);
382+
if (data?.url) {
383+
const link = document.createElement("a");
384+
link.href = data.url;
385+
link.download = `${sbType}-sandbox-${jobId}.tar.gz`;
386+
document.body.appendChild(link);
387+
link.click();
388+
document.body.removeChild(link);
389+
setSnackbarInfo({
390+
open: true,
391+
message: `Downloading ${sbType} sandbox of ${jobId}...`,
392+
severity: "info",
393+
});
394+
} else throw new Error(`Could not fetch the sandbox from ${data.url}`);
395+
} else throw new Error(`No ${sbType} sandbox found`);
396+
} catch (error: unknown) {
397+
let errorMessage = "An unknown error occurred";
398+
if (error instanceof Error) {
399+
errorMessage = error.message;
400+
}
401+
setSnackbarInfo({
402+
open: true,
403+
message: `Fetching sandbox of ${jobId} failed: ` + errorMessage,
404+
severity: "error",
405+
});
406+
} finally {
407+
setBackdropOpen(false);
408+
}
409+
};
410+
362411
/**
363412
* The toolbar components for the data grid
364413
*/
@@ -394,6 +443,14 @@ export function JobDataTable({
394443
label: "Get history",
395444
onClick: (id: number | null) => handleHistory(id),
396445
},
446+
{
447+
label: "Download input sandbox",
448+
onClick: (id: number | null) => handleSandboxDownload(id, "input"),
449+
},
450+
{
451+
label: "Download output sandbox",
452+
onClick: (id: number | null) => handleSandboxDownload(id, "output"),
453+
},
397454
],
398455
[handleHistory],
399456
);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Types for sandbox-related API responses
2+
3+
// Response for /api/jobs/<jobId>/sandbox/<sbType>
4+
export type JobSandboxPFNResponse = string[];
5+
6+
// Response for /api/jobs/sandbox?pfn=...
7+
export interface SandboxUrlResponse {
8+
url: string;
9+
expires_in: number;
10+
}

packages/diracx-web-components/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export * from "./DashboardItem";
55
export * from "./SearchBody";
66
export * from "./Job";
77
export * from "./JobHistory";
8+
export * from "./Sandbox";

0 commit comments

Comments
 (0)