diff --git a/frontend/unified/src/App.tsx b/frontend/unified/src/App.tsx index 1f7f38b..54490d2 100644 --- a/frontend/unified/src/App.tsx +++ b/frontend/unified/src/App.tsx @@ -14,6 +14,7 @@ import { WorkflowForm } from "./components/WorkflowForm"; import { DisplayLogMeta } from "./components/InspectLogMeta"; import { Beamline, Technique } from "./types"; import { ParameterConfiguration } from "./components/ParameterConfiguration/ParameterConfiguration"; +import { Plot } from "./components/Plot/Plot"; import { ApolloProvider, useQuery } from "@apollo/client/react"; import { apolloClientWorkflows } from "../../src/ApolloClient"; import { gql, type TypedDocumentNode } from "@apollo/client"; @@ -302,10 +303,14 @@ export const App: React.FC = () => { Plot - Log diff --git a/frontend/unified/src/components/Plot/ArtifactSelector.tsx b/frontend/unified/src/components/Plot/ArtifactSelector.tsx new file mode 100644 index 0000000..c20ba03 --- /dev/null +++ b/frontend/unified/src/components/Plot/ArtifactSelector.tsx @@ -0,0 +1,169 @@ +import { gql, TypedDocumentNode } from "@apollo/client"; +import { + GetWorkflowArtifactsQuery, + GetWorkflowArtifactsQueryVariables, +} from "./__generated__/ArtifactSelector.generated"; +import { useSuspenseQuery } from "@apollo/client/react"; +import { Visit } from "../JobsViewer/JobsViewer"; +import { FormControl, InputLabel, MenuItem, Select } from "@mui/material"; +import { useState } from "react"; + +const GET_WORKFLOW_ARTIFACTS: TypedDocumentNode< + GetWorkflowArtifactsQuery, + GetWorkflowArtifactsQueryVariables +> = gql` + query GetWorkflowArtifacts($visit: VisitInput!, $name: String!) { + workflow(visit: $visit, name: $name) { + name + status { + __typename + ... on WorkflowPendingStatus { + message + } + ... on WorkflowRunningStatus { + tasks { + id + name + status + stepType + artifacts { + name + url + mimeType + } + } + } + ... on WorkflowSucceededStatus { + __typename + startTime + tasks { + id + name + status + stepType + artifacts { + name + url + mimeType + } + } + } + ... on WorkflowFailedStatus { + tasks { + id + name + status + stepType + artifacts { + name + url + mimeType + } + } + } + ... on WorkflowErroredStatus { + tasks { + id + name + status + stepType + artifacts { + name + url + mimeType + } + } + } + } + } + } +`; + +type NonNullWorkflow = NonNullable; +type NonNullWorkflowStatus = NonNullable; +type WorkflowSucceededStatus = Extract< + NonNullWorkflowStatus, + { __typename: "WorkflowSucceededStatus" } +>; +export type Artifact = WorkflowSucceededStatus["tasks"][0]["artifacts"][0]; + +type TaskNameAndArtifactTuple = [string, Artifact[]]; + +type ArtifactSelectorProps = { + workflowName: string; + visit: Visit; + setArtifact: (_: Artifact | null) => void; + isPlottingEnabled: boolean; +}; + +const IMAGE_ARTIFACT_MIME_TYPES = ["image/jpeg", "image/tiff"]; + +export const ArtifactSelector: React.FC = ({ + workflowName, + visit, + setArtifact, + isPlottingEnabled, +}: ArtifactSelectorProps) => { + const [selectedArtifact, setSelectedArtifact] = useState(""); + const { error, data } = useSuspenseQuery(GET_WORKFLOW_ARTIFACTS, { + variables: { + name: workflowName, + visit: visit, + }, + }); + + if (error) return

Error: {error.message}

; + + const generateArtifactList = (): React.ReactNode[] => { + switch (data.workflow?.status?.__typename) { + case "WorkflowSucceededStatus": { + const taskNamesAndImageArtifacts: TaskNameAndArtifactTuple[] = + data.workflow.status.tasks + .map( + (task) => + [ + task.name, + task.artifacts.filter((artifact) => + IMAGE_ARTIFACT_MIME_TYPES.includes(artifact.mimeType) + ), + ] as TaskNameAndArtifactTuple + ) + .filter(([_, artifacts]) => artifacts.length > 0); + + return taskNamesAndImageArtifacts.map(([taskName, artifacts]) => { + return artifacts.map((artifact) => { + const label = `${taskName}: ${artifact.name}`; + return ( + + {label} + + ); + }); + }); + } + default: + console.error("Handle other workflow status cases"); + return [default]; + } + }; + + return ( + + Artifact +