Skip to content

Commit

Permalink
Feat(canvas): Add file viewer and job exec logic
Browse files Browse the repository at this point in the history
  • Loading branch information
leoank committed Aug 25, 2024
1 parent ddadc0b commit c2289f1
Show file tree
Hide file tree
Showing 22 changed files with 874 additions and 103 deletions.
1 change: 0 additions & 1 deletion canvas/app/dashboard/project/all/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { PageContainer } from "../../_layout/page-container";
import { AllProjects } from "./all-projects";
import { AllProjectsHeading } from "./all-projects-heading";
import { AllProjectSkeleton } from "./all-projects-skeleton";
import { NoProjects } from "./no-projects";

export default function AllProjectPage() {
return (
Expand Down
22 changes: 11 additions & 11 deletions canvas/app/dashboard/project/id/[id]/job.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@ import {
import { TProjectStepJob } from "@/services/job";
import { ProjectStepJobRunsContainer } from "./runs-container";
import React from "react";
import { useProjectStore } from "@/stores/project";

export type TProjectStepJobProps = {
job: TProjectStepJob;
};

export function ProjectStepJob(props: TProjectStepJobProps) {
const { job } = props;
const { step } = useProjectStore((state) => ({ step: state.currentStep }));
const { job, ...rest } = props;

return (
<Accordion type="single" collapsible>
<AccordionItem value="item-1">
<AccordionTrigger className="hover:no-underline">
{job.name}
</AccordionTrigger>
<AccordionContent>
<ProjectStepJobRunsContainer job={job} />
</AccordionContent>
</AccordionItem>
</Accordion>
<AccordionItem value={`job-${job.id}-${step!.id}`} {...rest}>
<AccordionTrigger className="hover:no-underline font-bold text-xl">
{job.name}
</AccordionTrigger>
<AccordionContent>
<ProjectStepJobRunsContainer job={job} />
</AccordionContent>
</AccordionItem>
);
}
7 changes: 5 additions & 2 deletions canvas/app/dashboard/project/id/[id]/jobs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,12 @@ export function ProjectStepJobs() {
</div>
</div>
<div className="mt-4">
<Accordion type="single" collapsible>
<Accordion
type="multiple"
defaultValue={data.response.map((res) => `job-${res.id}-${step!.id}`)}
>
{data.response.map((job) => (
<ProjectStepJob key={job.id} job={job} />
<ProjectStepJob key={`${job.id} ${step!.id}`} job={job} />
))}
</Accordion>
</div>
Expand Down
8 changes: 6 additions & 2 deletions canvas/app/dashboard/project/id/[id]/project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { TProject } from "@/services/projects";
import { ProjectStoreProvider } from "@/stores/project";
import { StepsContainer } from "./steps-container";
import { ProjectActions } from "./project-actions";
import { TakeCredentials } from "./take-credentials";

export type TProjectMainContentProps = {
project: TProject;
Expand All @@ -18,14 +19,17 @@ export function ProjectMainContent(props: TProjectMainContentProps) {
return (
<ProjectStoreProvider project={project}>
<div className="flex flex-col flex-1 overflow-auto">
<PageHeading heading={project.name} />
<PageHeading
heading={project.name}
primaryAction={<TakeCredentials projectId={project.id} />}
/>
<Breadcrumb
links={[
{ title: "All Projects", href: PROJECTS_LISTING_URL },
{ title: project.name },
]}
/>
<div className="flex flex-1 flex-col">
<div className="flex flex-1 flex-col overflow-auto">
<div className="py-4">
<p>{project.description}</p>
<div className="flex justify-end">
Expand Down
96 changes: 87 additions & 9 deletions canvas/app/dashboard/project/id/[id]/run.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,107 @@
"use client";

import { Button } from "@/components/ui/button";
import { TProjectStepJobRun } from "@/services/run";
import { Loader2 } from "lucide-react";
import React from "react";
import { JobBadge } from "./badge";
import { FileViewer } from "@/components/custom/file-viewer";
import { ViewInput } from "./view-input";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import { downloadFile } from "@/utils/download";
import { getFile } from "@/services/s3";
import { useProjectStore } from "@/stores/project";

export type TProjectStepJobRunProps = {
run: TProjectStepJobRun;
};

export function ProjectStepJobRun(props: TProjectStepJobRunProps) {
const { run } = props;
const { project } = useProjectStore((state) => ({ project: state.project }));

async function handleDownload(url: string) {
let response = await getFile(url, {
projectId: project.id,
toString: {},
});

if (!response) {
alert("Failed to download file");
return;
}

downloadFile({
content: response,
filename: url.split("/").pop()!,
});
}

return (
<div className="space-y-4">
<div>Name: {run.name}</div>
<div>
Status: <JobBadge status={run.run_status} />
<div className="space-y-4 pt-4 rounded-md border border-gray-300 p-4 my-4 ">
<div className="flex flex-1 items-center space-x-4">
<div className="flex-1">{run.name}</div>
<JobBadge status={run.run_status} />
<ViewInput
viewButtonTitle="View input"
inputsRecord={run.inputs}
isEditable={false}
title={`Inputs - ${run.name}`}
emptyInputPlaceholder="No input was provided."
/>
</div>

{run.run_status === "success" && (
<div>
<FileViewer fileName="s3_file_url.test" />
</div>
<Table>
<TableCaption>Job Output</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Type</TableHead>
<TableHead>URI</TableHead>
<TableHead className="w-[200px] pl-6">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{Object.entries(run.outputs).map(([name, data]) => (
<TableRow key={name}>
<TableCell className="font-medium">{name}</TableCell>
<TableCell>{data.type}</TableCell>
<TableCell>
{data.uri || "s3://ip-merck-dev/projects/ASMA/test3.txt"}
</TableCell>
<TableCell>
<div className="space-x-2 flex items-center">
<FileViewer
fileName={
data.uri || "s3://ip-merck-dev/projects/ASMA/test3.txt"
}
/>
<Button
onClick={() =>
handleDownload(
data.uri ||
"s3://ip-merck-dev/projects/ASMA/xlsx.xlsx"
)
}
size="sm"
variant="outline"
>
Download
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</div>
);
Expand Down
43 changes: 14 additions & 29 deletions canvas/app/dashboard/project/id/[id]/runs-actions.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { ActionsButtons } from "@/components/custom/action-buttons";
import { toast } from "@/components/ui/use-toast";
import { executeJob, TProjectStepJob } from "@/services/job";
import { TRunStatus } from "@/services/run";
import { TProjectStepJobRun, TRunStatus } from "@/services/run";
import { useMutation } from "@tanstack/react-query";
import React from "react";
import { mutate } from "swr";

export type TRunsActionsProps = {
job: TProjectStepJob;
runs: TProjectStepJobRun[];

mutateKey: string;
};

export function RunsActions(props: TRunsActionsProps) {
const { job, mutateKey } = props;
const { job, runs, mutateKey } = props;

const [status, setStatus] = React.useState<TRunStatus>("init");

const {
mutate: executeJobMutation,
Expand All @@ -35,7 +39,7 @@ export function RunsActions(props: TRunsActionsProps) {
// will tell swr to refetch data
mutate(mutateKey);
toast({
title: "Job completed successfully",
title: "Job started successfully",
className: "bg-black text-white",
});
} else if (error) {
Expand All @@ -46,21 +50,18 @@ export function RunsActions(props: TRunsActionsProps) {
});
}
}, [error, isSuccess, mutateKey]);
const [currentState, setCurrentState] = React.useState<TRunStatus>("init");

React.useEffect(() => {
if (isSuccess) {
setCurrentState("running");
} else if (error) {
setCurrentState("failed");
} else if (isPending) {
setCurrentState("pending");
if (!runs || runs.length === 0) {
setStatus("init");
} else {
setStatus(runs[runs.length - 1].run_status);
}
}, [error, isPending, isSuccess]);
}, [runs]);

return (
<ActionsButtons
currentState={currentState}
currentState={isPending ? "pending" : status}
cancelButton={{
isDisabled: isSuccess,
onClick: () => {
Expand All @@ -81,26 +82,10 @@ export function RunsActions(props: TRunsActionsProps) {
isDisabled: false,
tooltipText: "Executing this job.",
}}
successButton={{
isDisabled: false,
onClick: () => {
setCurrentState("pending");
},
tooltipText: "Click to run this job again.",
}}
failedButton={{
tooltipText:
"Last execution was failed. Press this button to retry this job.",
onClick: () => {
setCurrentState("pending");
setTimeout(() => {
setCurrentState("running");
}, 3000);

setTimeout(() => {
setCurrentState("success");
}, 6000);
},
onClick: submitJobRequest,
}}
/>
);
Expand Down
Loading

0 comments on commit c2289f1

Please sign in to comment.