From ed3b7d04197fcf8fabdace432962a7592af2d01c Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Fri, 25 Sep 2026 10:42:01 +0000 Subject: [PATCH 01/31] move JobsTable components to new folder --- .../JobsViewer/{ => JobsTable}/BaseTableRow.tsx | 5 +++-- .../components/JobsViewer/{ => JobsTable}/JobTableRow.tsx | 0 .../components/JobsViewer/{ => JobsTable}/JobsTable.tsx | 5 ++--- .../JobsViewer/{ => JobsTable}/LiveTableRow.tsx | 2 +- .../components/JobsViewer/{ => JobsTable}/StatusIcons.tsx | 4 ++-- .../JobsViewer/{ => JobsTable}/TableContent.tsx | 2 +- .../JobsViewer/{ => JobsTable}/TableRowWrapper.tsx | 2 +- frontend/unified/src/components/JobsViewer/JobsViewer.tsx | 8 +------- 8 files changed, 11 insertions(+), 17 deletions(-) rename frontend/unified/src/components/JobsViewer/{ => JobsTable}/BaseTableRow.tsx (92%) rename frontend/unified/src/components/JobsViewer/{ => JobsTable}/JobTableRow.tsx (100%) rename frontend/unified/src/components/JobsViewer/{ => JobsTable}/JobsTable.tsx (94%) rename frontend/unified/src/components/JobsViewer/{ => JobsTable}/LiveTableRow.tsx (96%) rename frontend/unified/src/components/JobsViewer/{ => JobsTable}/StatusIcons.tsx (94%) rename frontend/unified/src/components/JobsViewer/{ => JobsTable}/TableContent.tsx (98%) rename frontend/unified/src/components/JobsViewer/{ => JobsTable}/TableRowWrapper.tsx (97%) diff --git a/frontend/unified/src/components/JobsViewer/BaseTableRow.tsx b/frontend/unified/src/components/JobsViewer/JobsTable/BaseTableRow.tsx similarity index 92% rename from frontend/unified/src/components/JobsViewer/BaseTableRow.tsx rename to frontend/unified/src/components/JobsViewer/JobsTable/BaseTableRow.tsx index 0151187e..ad6d1b4e 100644 --- a/frontend/unified/src/components/JobsViewer/BaseTableRow.tsx +++ b/frontend/unified/src/components/JobsViewer/JobsTable/BaseTableRow.tsx @@ -5,7 +5,7 @@ import { useFragment } from "@apollo/client/react"; import { BaseTableRowFragmentFragment } from "./__generated__/BaseTableRow.generated"; import { getWorkflowStatusIcon } from "./StatusIcons"; import { DeepPartial } from "@apollo/client/utilities"; -import { TableRowRelayFragmentFragment } from "./__generated__/TableRowRelay.generated"; +import { TableRowWrapperFragmentFragment } from "./__generated__/TableRowWrapper.generated"; import { JobTableRow } from "./JobTableRow"; export const BASETABLEROW_FRAGMENT = gql` @@ -26,7 +26,8 @@ const BaseTableRow = ({ setSelectedWorkflow, }: { queryData: - TableRowRelayFragmentFragment | DeepPartial; + | TableRowWrapperFragmentFragment + | DeepPartial; selectedWorkflow: string | null; setSelectedWorkflow: (_: string | null) => void; }) => { diff --git a/frontend/unified/src/components/JobsViewer/JobTableRow.tsx b/frontend/unified/src/components/JobsViewer/JobsTable/JobTableRow.tsx similarity index 100% rename from frontend/unified/src/components/JobsViewer/JobTableRow.tsx rename to frontend/unified/src/components/JobsViewer/JobsTable/JobTableRow.tsx diff --git a/frontend/unified/src/components/JobsViewer/JobsTable.tsx b/frontend/unified/src/components/JobsViewer/JobsTable/JobsTable.tsx similarity index 94% rename from frontend/unified/src/components/JobsViewer/JobsTable.tsx rename to frontend/unified/src/components/JobsViewer/JobsTable/JobsTable.tsx index 0a3d5f74..dd95412a 100644 --- a/frontend/unified/src/components/JobsViewer/JobsTable.tsx +++ b/frontend/unified/src/components/JobsViewer/JobsTable/JobsTable.tsx @@ -2,9 +2,8 @@ import { WorkflowsQueryQuery, WorkflowsQueryQueryVariables, } from "./__generated__/JobsTable.generated"; -import { Suspense, useCallback, useState, useEffect } from "react"; - -import { Visit } from "./JobsViewer"; +import { Suspense, useCallback, useState } from "react"; +import { Visit } from "@diamondlightsource/sci-react-ui"; import TableContent, { TABLECONTENT_FRAGMENT } from "./TableContent"; import { gql, TypedDocumentNode } from "@apollo/client"; import { useQuery } from "@apollo/client/react"; diff --git a/frontend/unified/src/components/JobsViewer/LiveTableRow.tsx b/frontend/unified/src/components/JobsViewer/JobsTable/LiveTableRow.tsx similarity index 96% rename from frontend/unified/src/components/JobsViewer/LiveTableRow.tsx rename to frontend/unified/src/components/JobsViewer/JobsTable/LiveTableRow.tsx index 5d74e1c0..e94b6138 100644 --- a/frontend/unified/src/components/JobsViewer/LiveTableRow.tsx +++ b/frontend/unified/src/components/JobsViewer/JobsTable/LiveTableRow.tsx @@ -2,7 +2,7 @@ import { gql, TypedDocumentNode } from "@apollo/client"; import BaseTableRow, { BASETABLEROW_FRAGMENT } from "./BaseTableRow"; import { useState } from "react"; import { BaseTableRowFragmentFragment } from "./__generated__/BaseTableRow.generated"; -import { apolloClientWorkflows } from "../../../../src/ApolloClient"; +import { apolloClientWorkflows } from "../../../../../src/ApolloClient"; import { LiveTableRowSubscriptionSubscription, LiveTableRowSubscriptionSubscriptionVariables, diff --git a/frontend/unified/src/components/JobsViewer/StatusIcons.tsx b/frontend/unified/src/components/JobsViewer/JobsTable/StatusIcons.tsx similarity index 94% rename from frontend/unified/src/components/JobsViewer/StatusIcons.tsx rename to frontend/unified/src/components/JobsViewer/JobsTable/StatusIcons.tsx index 217bc796..51974f22 100644 --- a/frontend/unified/src/components/JobsViewer/StatusIcons.tsx +++ b/frontend/unified/src/components/JobsViewer/JobsTable/StatusIcons.tsx @@ -1,5 +1,5 @@ import { Typography } from "@mui/material"; -import { WorkflowStatus } from "./utils/types"; +import { WorkflowStatus } from "../utils/types"; import { CircleAlert, CircleCheckBig, @@ -8,7 +8,7 @@ import { ClockFading, } from "lucide-react"; import React from "react"; -import BaseStack from "./utils/BaseStack"; +import BaseStack from "../utils/BaseStack"; export const getWorkflowStatusIcon = ( status: WorkflowStatus, diff --git a/frontend/unified/src/components/JobsViewer/TableContent.tsx b/frontend/unified/src/components/JobsViewer/JobsTable/TableContent.tsx similarity index 98% rename from frontend/unified/src/components/JobsViewer/TableContent.tsx rename to frontend/unified/src/components/JobsViewer/JobsTable/TableContent.tsx index 9a72b21f..7eb58d29 100644 --- a/frontend/unified/src/components/JobsViewer/TableContent.tsx +++ b/frontend/unified/src/components/JobsViewer/JobsTable/TableContent.tsx @@ -12,7 +12,7 @@ import { } from "@mui/material"; import { useFragment } from "@apollo/client/react"; import { TableContentFragmentFragment } from "./__generated__/TableContent.generated"; -import { ChangeEvent, MouseEvent, useState } from "react"; +import { ChangeEvent, MouseEvent } from "react"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { gql, TypedDocumentNode } from "@apollo/client"; import TableRowWrapper, { TABLEROWWRAPPER_FRAGMENT } from "./TableRowWrapper"; diff --git a/frontend/unified/src/components/JobsViewer/TableRowWrapper.tsx b/frontend/unified/src/components/JobsViewer/JobsTable/TableRowWrapper.tsx similarity index 97% rename from frontend/unified/src/components/JobsViewer/TableRowWrapper.tsx rename to frontend/unified/src/components/JobsViewer/JobsTable/TableRowWrapper.tsx index b97fa705..ad09d117 100644 --- a/frontend/unified/src/components/JobsViewer/TableRowWrapper.tsx +++ b/frontend/unified/src/components/JobsViewer/JobsTable/TableRowWrapper.tsx @@ -3,7 +3,7 @@ import { TableCell, TableRow } from "@mui/material"; import { TableRowWrapperFragmentFragment } from "./__generated__/TableRowWrapper.generated"; import { useFragment } from "@apollo/client/react"; import BaseTableRow, { BASETABLEROW_FRAGMENT } from "./BaseTableRow"; -import { finishedStatuses } from "./utils/utils"; +import { finishedStatuses } from "../utils/utils"; import { useState } from "react"; import LiveTableRow from "./LiveTableRow"; diff --git a/frontend/unified/src/components/JobsViewer/JobsViewer.tsx b/frontend/unified/src/components/JobsViewer/JobsViewer.tsx index 2f34ec21..f778e0c5 100644 --- a/frontend/unified/src/components/JobsViewer/JobsViewer.tsx +++ b/frontend/unified/src/components/JobsViewer/JobsViewer.tsx @@ -1,11 +1,5 @@ import { Suspense } from "react"; -import JobsTable from "./JobsTable"; - -export type Visit = { - proposalCode: string; - proposalNumber: number; - number: number; -}; +import JobsTable from "./JobsTable/JobsTable"; const JobsViewer = ({ visit, From 52241315fd962d52b7b97461d71c961a0bd0c6da Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Fri, 25 Sep 2026 10:43:29 +0000 Subject: [PATCH 02/31] update Visit imports --- frontend/unified/src/components/JobsViewer/JobsViewer.tsx | 1 + frontend/unified/src/components/Plot/ArtifactSelector.tsx | 2 +- frontend/unified/src/components/Plot/Plot.tsx | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/unified/src/components/JobsViewer/JobsViewer.tsx b/frontend/unified/src/components/JobsViewer/JobsViewer.tsx index f778e0c5..48b35195 100644 --- a/frontend/unified/src/components/JobsViewer/JobsViewer.tsx +++ b/frontend/unified/src/components/JobsViewer/JobsViewer.tsx @@ -1,5 +1,6 @@ import { Suspense } from "react"; import JobsTable from "./JobsTable/JobsTable"; +import { Visit } from "@diamondlightsource/sci-react-ui"; const JobsViewer = ({ visit, diff --git a/frontend/unified/src/components/Plot/ArtifactSelector.tsx b/frontend/unified/src/components/Plot/ArtifactSelector.tsx index 6b2ca80b..ef18014c 100644 --- a/frontend/unified/src/components/Plot/ArtifactSelector.tsx +++ b/frontend/unified/src/components/Plot/ArtifactSelector.tsx @@ -4,7 +4,7 @@ import { GetWorkflowArtifactsQueryVariables, } from "./__generated__/ArtifactSelector.generated"; import { useSuspenseQuery } from "@apollo/client/react"; -import { Visit } from "../JobsViewer/JobsViewer"; +import { Visit } from "@diamondlightsource/sci-react-ui"; import { FormControl, InputLabel, MenuItem, Select } from "@mui/material"; import { useState } from "react"; diff --git a/frontend/unified/src/components/Plot/Plot.tsx b/frontend/unified/src/components/Plot/Plot.tsx index 4b39322f..1a5db0f9 100644 --- a/frontend/unified/src/components/Plot/Plot.tsx +++ b/frontend/unified/src/components/Plot/Plot.tsx @@ -1,5 +1,4 @@ import { Suspense, useEffect, useState } from "react"; -import { Visit } from "../JobsViewer/JobsViewer"; import { ArtifactSelector, Artifact } from "./ArtifactSelector"; import { Switch } from "@mui/material"; import { NDT } from "@diamondlightsource/davidia"; @@ -9,6 +8,7 @@ import { proxyService } from "../../../../tomography/src/api/services"; import loadData from "../../../../tomography/src/components/crop/SampleLoad"; import { DataPlotter } from "./DataPlotter"; import { DataLoadingProgress } from "./DataLoadingProgress"; +import { Visit } from "@diamondlightsource/sci-react-ui"; type PlotProps = { workflowName: string | null; From 8bb8fcb56d127283036a9b4f8ee6553d63a35d04 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Fri, 25 Sep 2026 10:44:06 +0000 Subject: [PATCH 03/31] add LogStreamer --- .../src/components/JobsViewer/JobsViewer.tsx | 25 ++++-- .../JobsViewer/LogStreamer/LogStreamer.tsx | 84 +++++++++++++++++++ 2 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx diff --git a/frontend/unified/src/components/JobsViewer/JobsViewer.tsx b/frontend/unified/src/components/JobsViewer/JobsViewer.tsx index 48b35195..53371db1 100644 --- a/frontend/unified/src/components/JobsViewer/JobsViewer.tsx +++ b/frontend/unified/src/components/JobsViewer/JobsViewer.tsx @@ -1,6 +1,8 @@ import { Suspense } from "react"; import JobsTable from "./JobsTable/JobsTable"; import { Visit } from "@diamondlightsource/sci-react-ui"; +import { Divider, Typography } from "@mui/material"; +import LogStreamer from "./LogStreamer/LogStreamer"; const JobsViewer = ({ visit, @@ -12,13 +14,22 @@ const JobsViewer = ({ setSelectedWorkflow: (_: string | null) => void; }) => { return ( - - - + <> + + + + + Technique + {selectedWorkflow ? ( + + ) : ( + <>No Workflow Selected + )} + ); }; diff --git a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx new file mode 100644 index 00000000..e13e6097 --- /dev/null +++ b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx @@ -0,0 +1,84 @@ +import { gql, TypedDocumentNode } from "@apollo/client"; +import { Visit } from "@diamondlightsource/sci-react-ui"; +import { + GetWorkflowTasksQuery, + GetWorkflowTasksQueryVariables, +} from "./__generated__/LogStreamer.generated"; +import { useQuery } from "@apollo/client/react"; +import { MenuItem, Select } from "@mui/material"; +import { useState } from "react"; + +const GET_WORKFLOW_TASKS: TypedDocumentNode< + GetWorkflowTasksQuery, + GetWorkflowTasksQueryVariables +> = gql` + query GetWorkflowTasks($visit: VisitInput!, $workflowName: String!) { + workflow(visit: $visit, name: $workflowName) { + name + status { + __typename + ... on WorkflowRunningStatus { + tasks { + id + name + status + } + } + ... on WorkflowSucceededStatus { + tasks { + id + name + status + } + } + ... on WorkflowFailedStatus { + tasks { + id + name + status + } + } + ... on WorkflowErroredStatus { + tasks { + id + name + status + } + } + } + } + } +`; + +const LogStreamer = ({ + visit, + selectedWorkflow, +}: { + visit: Visit; + selectedWorkflow: string; +}) => { + const [selectedTask, setSelectedTask] = useState(null); + const { data, loading, error } = useQuery(GET_WORKFLOW_TASKS, { + variables: { + visit: visit, + workflowName: selectedWorkflow, + }, + }); + + if (loading) return <>Loading...; + if (error) return <>Error: {error.message}; + if (!data) return <>No Data; + + const test: string[] = []; + return ( + <> + + + ); +}; + +export default LogStreamer; From f0013b8efb2a5c7dbc748b65964ad1d527ef6e12 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Fri, 25 Sep 2026 10:50:30 +0000 Subject: [PATCH 04/31] add task types --- frontend/unified/src/types.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frontend/unified/src/types.ts b/frontend/unified/src/types.ts index 0736ee60..80aa4411 100644 --- a/frontend/unified/src/types.ts +++ b/frontend/unified/src/types.ts @@ -34,3 +34,18 @@ export type TemplateComponentProps = { setResourceParameters: (_: object) => void; visitDirpath: string; }; + +export type TaskStatus = + | "PENDING" + | "RUNNING" + | "SUCCEEDED" + | "SKIPPED" + | "FAILED" + | "ERROR" + | "OMITTED"; + +export type Task = { + id: string; + name: string; + status: TaskStatus; +}; From d9655188196a9bf7d206389a081cc0e41dad2273 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Fri, 25 Sep 2026 15:06:13 +0000 Subject: [PATCH 05/31] move Plot components to JobsViewer folder --- .../src/components/{ => JobsViewer}/Plot/ArtifactSelector.tsx | 0 .../components/{ => JobsViewer}/Plot/DataLoadingProgress.tsx | 0 .../src/components/{ => JobsViewer}/Plot/DataPlotter.tsx | 0 .../unified/src/components/{ => JobsViewer}/Plot/Plot.tsx | 4 ++-- 4 files changed, 2 insertions(+), 2 deletions(-) rename frontend/unified/src/components/{ => JobsViewer}/Plot/ArtifactSelector.tsx (100%) rename frontend/unified/src/components/{ => JobsViewer}/Plot/DataLoadingProgress.tsx (100%) rename frontend/unified/src/components/{ => JobsViewer}/Plot/DataPlotter.tsx (100%) rename frontend/unified/src/components/{ => JobsViewer}/Plot/Plot.tsx (95%) diff --git a/frontend/unified/src/components/Plot/ArtifactSelector.tsx b/frontend/unified/src/components/JobsViewer/Plot/ArtifactSelector.tsx similarity index 100% rename from frontend/unified/src/components/Plot/ArtifactSelector.tsx rename to frontend/unified/src/components/JobsViewer/Plot/ArtifactSelector.tsx diff --git a/frontend/unified/src/components/Plot/DataLoadingProgress.tsx b/frontend/unified/src/components/JobsViewer/Plot/DataLoadingProgress.tsx similarity index 100% rename from frontend/unified/src/components/Plot/DataLoadingProgress.tsx rename to frontend/unified/src/components/JobsViewer/Plot/DataLoadingProgress.tsx diff --git a/frontend/unified/src/components/Plot/DataPlotter.tsx b/frontend/unified/src/components/JobsViewer/Plot/DataPlotter.tsx similarity index 100% rename from frontend/unified/src/components/Plot/DataPlotter.tsx rename to frontend/unified/src/components/JobsViewer/Plot/DataPlotter.tsx diff --git a/frontend/unified/src/components/Plot/Plot.tsx b/frontend/unified/src/components/JobsViewer/Plot/Plot.tsx similarity index 95% rename from frontend/unified/src/components/Plot/Plot.tsx rename to frontend/unified/src/components/JobsViewer/Plot/Plot.tsx index 1a5db0f9..95549d65 100644 --- a/frontend/unified/src/components/Plot/Plot.tsx +++ b/frontend/unified/src/components/JobsViewer/Plot/Plot.tsx @@ -4,8 +4,8 @@ import { Switch } from "@mui/material"; import { NDT } from "@diamondlightsource/davidia"; import ndarray from "ndarray"; import { decode } from "fast-png"; -import { proxyService } from "../../../../tomography/src/api/services"; -import loadData from "../../../../tomography/src/components/crop/SampleLoad"; +import { proxyService } from "../../../../../tomography/src/api/services"; +import loadData from "../../../../../tomography/src/components/crop/SampleLoad"; import { DataPlotter } from "./DataPlotter"; import { DataLoadingProgress } from "./DataLoadingProgress"; import { Visit } from "@diamondlightsource/sci-react-ui"; From d337f50a853345098a317358e41696902c599467 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Fri, 25 Sep 2026 15:06:51 +0000 Subject: [PATCH 06/31] move InspectLogMeta to JobsViewer folder --- .../unified/src/components/{ => JobsViewer}/InspectLogMeta.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename frontend/unified/src/components/{ => JobsViewer}/InspectLogMeta.tsx (100%) diff --git a/frontend/unified/src/components/InspectLogMeta.tsx b/frontend/unified/src/components/JobsViewer/InspectLogMeta.tsx similarity index 100% rename from frontend/unified/src/components/InspectLogMeta.tsx rename to frontend/unified/src/components/JobsViewer/InspectLogMeta.tsx From 4a722b314db42b5096011901929042cfe4ec7a90 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Fri, 25 Sep 2026 15:19:40 +0000 Subject: [PATCH 07/31] add JobDataViewer --- .../components/JobsViewer/JobDataViewer.tsx | 37 +++++++++++++++++++ .../src/components/JobsViewer/JobsViewer.tsx | 24 +++--------- 2 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 frontend/unified/src/components/JobsViewer/JobDataViewer.tsx diff --git a/frontend/unified/src/components/JobsViewer/JobDataViewer.tsx b/frontend/unified/src/components/JobsViewer/JobDataViewer.tsx new file mode 100644 index 00000000..67e153b5 --- /dev/null +++ b/frontend/unified/src/components/JobsViewer/JobDataViewer.tsx @@ -0,0 +1,37 @@ +import { Divider, Typography } from "@mui/material"; +import DisplayLogMeta from "./InspectLogMeta"; +import { Visit } from "@diamondlightsource/sci-react-ui"; +import LogStreamer from "./LogStreamer/LogStreamer"; +import { Plot } from "./Plot/Plot"; + +const JobDataViewer = ({ + visit, + selectedWorkflow, +}: { + visit: Visit; + selectedWorkflow: string | null; +}) => { + return ( + <> + + Log + {selectedWorkflow !== null ? ( + + ) : ( +

No workflow selected

+ )} + + Log Stream + {selectedWorkflow ? ( + + ) : ( + <>No workflow selected + )} + + Plot + + + ); +}; + +export default JobDataViewer; diff --git a/frontend/unified/src/components/JobsViewer/JobsViewer.tsx b/frontend/unified/src/components/JobsViewer/JobsViewer.tsx index 53371db1..36f409b8 100644 --- a/frontend/unified/src/components/JobsViewer/JobsViewer.tsx +++ b/frontend/unified/src/components/JobsViewer/JobsViewer.tsx @@ -1,18 +1,10 @@ -import { Suspense } from "react"; +import { Suspense, useState } from "react"; import JobsTable from "./JobsTable/JobsTable"; import { Visit } from "@diamondlightsource/sci-react-ui"; -import { Divider, Typography } from "@mui/material"; -import LogStreamer from "./LogStreamer/LogStreamer"; +import JobDataViewer from "./JobDataViewer"; -const JobsViewer = ({ - visit, - setSelectedWorkflow, - selectedWorkflow, -}: { - visit: Visit; - selectedWorkflow: string | null; - setSelectedWorkflow: (_: string | null) => void; -}) => { +const JobsViewer = ({ visit }: { visit: Visit }) => { + const [selectedWorkflow, setSelectedWorkflow] = useState(null); return ( <> @@ -22,13 +14,7 @@ const JobsViewer = ({ setSelectedWorkflow={setSelectedWorkflow} /> - - Technique - {selectedWorkflow ? ( - - ) : ( - <>No Workflow Selected - )} + ); }; From 53b1607cc63320b627930811da2221f3d220804b Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Fri, 25 Sep 2026 15:20:19 +0000 Subject: [PATCH 08/31] update App --- frontend/unified/src/App.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/unified/src/App.tsx b/frontend/unified/src/App.tsx index dde8a391..ac88104b 100644 --- a/frontend/unified/src/App.tsx +++ b/frontend/unified/src/App.tsx @@ -24,7 +24,6 @@ import JobsViewer from "./components/JobsViewer/JobsViewer"; import { templateOptions } from "./data/templates"; 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"; From cbeb3fd282d9c683ce9047c41b360b5b01c98108 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Fri, 25 Sep 2026 15:21:08 +0000 Subject: [PATCH 09/31] add LogStreamContent --- .../LogStreamer/LogStreamContent.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamContent.tsx diff --git a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamContent.tsx b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamContent.tsx new file mode 100644 index 00000000..4aadfbfc --- /dev/null +++ b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamContent.tsx @@ -0,0 +1,29 @@ +import { gql } from "@apollo/client"; +import { Visit } from "@diamondlightsource/sci-react-ui"; + +const LOG_STREAM_SUBSCRIPTION = gql` + subscription LogStreamSubscription( + $visit: VisitInput! + $workflowName: String! + $taskId: String! + ) { + logs(visit: $visit, workflowName: $workflowName, taskId: $taskId) { + content + podName + } + } +`; + +const LogStreamContent = ({ + visit, + workflowName, + taskId, +}: { + visit: Visit; + workflowName: string; + taskId: string | null; +}) => { + return <>; +}; + +export default LogStreamContent; From 339e420a78717ef461943183af1cca79921fb173 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Fri, 25 Sep 2026 15:22:08 +0000 Subject: [PATCH 10/31] add task select to LogStreamer --- .../JobsViewer/LogStreamer/LogStreamer.tsx | 18 ++++++++++++---- .../JobsViewer/LogStreamer/utils.ts | 21 +++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 frontend/unified/src/components/JobsViewer/LogStreamer/utils.ts diff --git a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx index e13e6097..b46d4e25 100644 --- a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx +++ b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx @@ -7,6 +7,8 @@ import { import { useQuery } from "@apollo/client/react"; import { MenuItem, Select } from "@mui/material"; import { useState } from "react"; +import { setFetchedTasks } from "./utils"; +import LogStreamContent from "./LogStreamContent"; const GET_WORKFLOW_TASKS: TypedDocumentNode< GetWorkflowTasksQuery, @@ -57,7 +59,7 @@ const LogStreamer = ({ visit: Visit; selectedWorkflow: string; }) => { - const [selectedTask, setSelectedTask] = useState(null); + const [selectedTaskId, setSelectedTaskId] = useState(null); const { data, loading, error } = useQuery(GET_WORKFLOW_TASKS, { variables: { visit: visit, @@ -68,15 +70,23 @@ const LogStreamer = ({ if (loading) return <>Loading...; if (error) return <>Error: {error.message}; if (!data) return <>No Data; + if (!data.workflow) return <>No Workflow Found; - const test: string[] = []; + const fetchedTasks = setFetchedTasks(data); return ( <> + ); }; diff --git a/frontend/unified/src/components/JobsViewer/LogStreamer/utils.ts b/frontend/unified/src/components/JobsViewer/LogStreamer/utils.ts new file mode 100644 index 00000000..03dbb613 --- /dev/null +++ b/frontend/unified/src/components/JobsViewer/LogStreamer/utils.ts @@ -0,0 +1,21 @@ +import { Task } from "../../../types"; +import { GetWorkflowTasksQuery } from "./__generated__/LogStreamer.generated"; + +export const isWorkflowWithTasks = (status: string) => { + return ( + status === "WorkflowRunningStatus" || + status === "WorkflowSucceededStatus" || + status === "WorkflowFailedStatus" || + status === "WorkflowErroredStatus" + ); +}; + +export const setFetchedTasks = (data: GetWorkflowTasksQuery): Task[] => { + if ( + data.workflow?.status && + isWorkflowWithTasks(data.workflow.status.__typename) + ) { + return data.workflow.status.tasks; + } + return []; +}; From 0c276111808183237c6e882a4624ed61949bc355 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Mon, 28 Sep 2026 16:03:43 +0000 Subject: [PATCH 11/31] change LogStreamer to use fragment --- .../JobsViewer/LogStreamer/LogStreamer.tsx | 81 ++++++++----------- 1 file changed, 35 insertions(+), 46 deletions(-) diff --git a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx index b46d4e25..c0fc460a 100644 --- a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx +++ b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx @@ -1,51 +1,42 @@ import { gql, TypedDocumentNode } from "@apollo/client"; import { Visit } from "@diamondlightsource/sci-react-ui"; -import { - GetWorkflowTasksQuery, - GetWorkflowTasksQueryVariables, -} from "./__generated__/LogStreamer.generated"; -import { useQuery } from "@apollo/client/react"; +import { useSuspenseFragment } from "@apollo/client/react"; import { MenuItem, Select } from "@mui/material"; import { useState } from "react"; import { setFetchedTasks } from "./utils"; import LogStreamContent from "./LogStreamContent"; +import { LogStreamerFragmentFragment } from "./__generated__/LogStreamer.generated"; -const GET_WORKFLOW_TASKS: TypedDocumentNode< - GetWorkflowTasksQuery, - GetWorkflowTasksQueryVariables -> = gql` - query GetWorkflowTasks($visit: VisitInput!, $workflowName: String!) { - workflow(visit: $visit, name: $workflowName) { - name - status { - __typename - ... on WorkflowRunningStatus { - tasks { - id - name - status - } +export const LOGSTREAMER_FRAGMENT: TypedDocumentNode = gql` + fragment LogStreamerFragment on Workflow { + status { + __typename + ... on WorkflowRunningStatus { + tasks { + id + name + status } - ... on WorkflowSucceededStatus { - tasks { - id - name - status - } + } + ... on WorkflowSucceededStatus { + tasks { + id + name + status } - ... on WorkflowFailedStatus { - tasks { - id - name - status - } + } + ... on WorkflowFailedStatus { + tasks { + id + name + status } - ... on WorkflowErroredStatus { - tasks { - id - name - status - } + } + ... on WorkflowErroredStatus { + tasks { + id + name + status } } } @@ -55,22 +46,20 @@ const GET_WORKFLOW_TASKS: TypedDocumentNode< const LogStreamer = ({ visit, selectedWorkflow, + queryData, }: { visit: Visit; selectedWorkflow: string; + queryData: LogStreamerFragmentFragment; }) => { const [selectedTaskId, setSelectedTaskId] = useState(null); - const { data, loading, error } = useQuery(GET_WORKFLOW_TASKS, { - variables: { - visit: visit, - workflowName: selectedWorkflow, - }, + const { data } = useSuspenseFragment({ + fragment: LOGSTREAMER_FRAGMENT, + fragmentName: "LogStreamerFragment", + from: queryData, }); - if (loading) return <>Loading...; - if (error) return <>Error: {error.message}; if (!data) return <>No Data; - if (!data.workflow) return <>No Workflow Found; const fetchedTasks = setFetchedTasks(data); return ( From 55ad12b8b62a44373ee4a313d0c7b5f5fc721fd5 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Wed, 30 Sep 2026 08:36:45 +0000 Subject: [PATCH 12/31] update workflows apollo client cache --- frontend/src/ApolloClient.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frontend/src/ApolloClient.ts b/frontend/src/ApolloClient.ts index e20bf45a..8ca02722 100644 --- a/frontend/src/ApolloClient.ts +++ b/frontend/src/ApolloClient.ts @@ -54,6 +54,15 @@ export const apolloClientWorkflows = new ApolloClient({ WorkflowSucceededStatus: { keyFields: ["startTime"], }, + WorkflowErroredStatus: { + keyFields: ["startTime"], + }, + WorkflowFailedStatus: { + keyFields: ["startTime"], + }, + WorkflowRunningStatus: { + keyFields: ["startTime"], + }, }, }), }); From 5af1e07fb45f8b93341753b8e31afcde4793a524 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Wed, 30 Sep 2026 08:37:19 +0000 Subject: [PATCH 13/31] update LogStreamer utils --- .../JobsViewer/LogStreamer/utils.ts | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/frontend/unified/src/components/JobsViewer/LogStreamer/utils.ts b/frontend/unified/src/components/JobsViewer/LogStreamer/utils.ts index 03dbb613..d3a20f6b 100644 --- a/frontend/unified/src/components/JobsViewer/LogStreamer/utils.ts +++ b/frontend/unified/src/components/JobsViewer/LogStreamer/utils.ts @@ -1,21 +1,15 @@ import { Task } from "../../../types"; -import { GetWorkflowTasksQuery } from "./__generated__/LogStreamer.generated"; +import { LogStreamerFragmentFragment } from "./__generated__/LogStreamer.generated"; -export const isWorkflowWithTasks = (status: string) => { - return ( - status === "WorkflowRunningStatus" || - status === "WorkflowSucceededStatus" || - status === "WorkflowFailedStatus" || - status === "WorkflowErroredStatus" - ); -}; - -export const setFetchedTasks = (data: GetWorkflowTasksQuery): Task[] => { +export const setFetchedTasks = (data: LogStreamerFragmentFragment): Task[] => { if ( - data.workflow?.status && - isWorkflowWithTasks(data.workflow.status.__typename) + data.status && + (data.status?.__typename === "WorkflowRunningStatus" || + data.status?.__typename === "WorkflowSucceededStatus" || + data.status?.__typename === "WorkflowFailedStatus" || + data.status?.__typename === "WorkflowErroredStatus") ) { - return data.workflow.status.tasks; + return data.status.tasks; } return []; }; From 20e27274892facb9a672753f34f9a454d23061d2 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Wed, 30 Sep 2026 08:38:30 +0000 Subject: [PATCH 14/31] add task status icons --- .../JobsViewer/LogStreamer/LogStreamer.tsx | 47 ++++++++++++++++--- .../LogStreamer/getTaskStatusIcon.tsx | 21 +++++++++ 2 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 frontend/unified/src/components/JobsViewer/LogStreamer/getTaskStatusIcon.tsx diff --git a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx index c0fc460a..f1f1b479 100644 --- a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx +++ b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx @@ -1,11 +1,20 @@ import { gql, TypedDocumentNode } from "@apollo/client"; import { Visit } from "@diamondlightsource/sci-react-ui"; import { useSuspenseFragment } from "@apollo/client/react"; -import { MenuItem, Select } from "@mui/material"; +import { + FormControl, + InputLabel, + ListItemIcon, + MenuItem, + Select, + Stack, + Typography, +} from "@mui/material"; import { useState } from "react"; import { setFetchedTasks } from "./utils"; import LogStreamContent from "./LogStreamContent"; import { LogStreamerFragmentFragment } from "./__generated__/LogStreamer.generated"; +import { getTaskStatusIcon } from "./getTaskStatusIcon"; export const LOGSTREAMER_FRAGMENT: TypedDocumentNode = gql` fragment LogStreamerFragment on Workflow { @@ -62,15 +71,39 @@ const LogStreamer = ({ if (!data) return <>No Data; const fetchedTasks = setFetchedTasks(data); + console.log(selectedTaskId); return ( <> - { + if (event.target.value === "") { + setSelectedTaskId(null); + } else { + setSelectedTaskId(event.target.value); + } + }} + label="Select Task" + > + + No Task - ))} - + {fetchedTasks.map((i) => ( + + + + {getTaskStatusIcon(i.status)} + + {i.name} + + + ))} + + + { + const taskStatusIconMap: { [key in TaskStatus]: React.JSX.Element } = { + PENDING: , + RUNNING: , + SUCCEEDED: , + SKIPPED: , + FAILED: , + ERROR: , + OMITTED: , + }; + return taskStatusIconMap[status]; +}; From 74cb03093bb9761c43b562fc102fb3aab7f78e11 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Wed, 30 Sep 2026 08:39:53 +0000 Subject: [PATCH 15/31] add JobDataViewer to JobsViewer --- frontend/unified/src/components/JobsViewer/JobsViewer.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/unified/src/components/JobsViewer/JobsViewer.tsx b/frontend/unified/src/components/JobsViewer/JobsViewer.tsx index 36f409b8..58dc73e2 100644 --- a/frontend/unified/src/components/JobsViewer/JobsViewer.tsx +++ b/frontend/unified/src/components/JobsViewer/JobsViewer.tsx @@ -14,7 +14,11 @@ const JobsViewer = ({ visit }: { visit: Visit }) => { setSelectedWorkflow={setSelectedWorkflow} /> - + {selectedWorkflow && ( + + + + )} ); }; From 3efb861429bda6dac428a9d528cb43c5940f9f6b Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Wed, 30 Sep 2026 09:36:10 +0000 Subject: [PATCH 16/31] add LogLine --- .../src/components/JobsViewer/LogStreamer/LogLine.tsx | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 frontend/unified/src/components/JobsViewer/LogStreamer/LogLine.tsx diff --git a/frontend/unified/src/components/JobsViewer/LogStreamer/LogLine.tsx b/frontend/unified/src/components/JobsViewer/LogStreamer/LogLine.tsx new file mode 100644 index 00000000..59f676f6 --- /dev/null +++ b/frontend/unified/src/components/JobsViewer/LogStreamer/LogLine.tsx @@ -0,0 +1,6 @@ +import { styled, Typography } from "@mui/material"; + +export const LogLine = styled(Typography)(() => ({ + fontFamily: "monospace", + fontSize: "12px", +})); From 613102f2e643f25215853b31100e0f463dc1269e Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Wed, 30 Sep 2026 10:46:03 +0000 Subject: [PATCH 17/31] update LogStreamContent to use subscription --- .../LogStreamer/LogStreamContent.tsx | 129 +++++++++++++++++- 1 file changed, 125 insertions(+), 4 deletions(-) diff --git a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamContent.tsx b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamContent.tsx index 4aadfbfc..8ab6bf07 100644 --- a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamContent.tsx +++ b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamContent.tsx @@ -1,8 +1,19 @@ -import { gql } from "@apollo/client"; +import { gql, TypedDocumentNode } from "@apollo/client"; import { Visit } from "@diamondlightsource/sci-react-ui"; +import { Box } from "@mui/material"; +import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react"; +import { LogLine } from "./LogLine"; +import { apolloClientWorkflows } from "../../../../../src/ApolloClient"; +import { + LogStreamContentSubscriptionSubscription, + LogStreamContentSubscriptionSubscriptionVariables, +} from "./__generated__/LogStreamContent.generated"; -const LOG_STREAM_SUBSCRIPTION = gql` - subscription LogStreamSubscription( +const LOGSTREAMCONTENT_SUBSCRIPTION: TypedDocumentNode< + LogStreamContentSubscriptionSubscription, + LogStreamContentSubscriptionSubscriptionVariables +> = gql` + subscription LogStreamContentSubscription( $visit: VisitInput! $workflowName: String! $taskId: String! @@ -23,7 +34,117 @@ const LogStreamContent = ({ workflowName: string; taskId: string | null; }) => { - return <>; + const [logLines, setLogLines] = useState([]); + const [subscriptionError, setSubscriptionError] = useState( + null + ); + const [logUnavailable, setLogUnavailable] = useState(false); + + const containerRef = useRef(null); + useEffect(() => { + if (containerRef.current) { + containerRef.current.scrollTop = containerRef.current.scrollHeight; + } + }, [logLines]); + + return ( + <> + {taskId && !logUnavailable && ( + + )} + + {logUnavailable ? ( + Log not available + ) : subscriptionError ? ( + {subscriptionError} + ) : logLines.length > 0 ? ( + logLines.map((line, index) => ( + <> + {line} + {index < logLines.length - 1 && "\n"} + + )) + ) : ( + + {taskId ? "Waiting for logs..." : "No task selected"} + + )} + + + ); +}; + +const LogStreamSubscription = ({ + visit, + workflowName, + taskId, + setLogLines, + setSubscriptionError, + setLogUnavailable, +}: { + visit: Visit; + workflowName: string; + taskId: string; + setLogLines: Dispatch>; + setSubscriptionError: Dispatch>; + setLogUnavailable: Dispatch>; +}) => { + apolloClientWorkflows + .subscribe({ + query: LOGSTREAMCONTENT_SUBSCRIPTION, + variables: { + visit: visit, + workflowName: workflowName, + taskId: taskId, + }, + }) + .subscribe({ + next: (result) => { + const line = result.data?.logs.content; + if (line) { + setLogLines((previousLines) => [...previousLines, line]); + } + }, + error: (error) => { + console.log("Log subscription error:", error); + const message = error instanceof Error ? error.message : String(error); + const logUnavailable = + message.includes("Log not available") || + message.includes("NoSuchKey") || + message.includes("No logs") || + message.includes("Failed to retrieve archived log artifact"); + if (logUnavailable) { + setSubscriptionError("Log not available"); + setLogUnavailable(true); + return; + } + // For non-terminal errors + setSubscriptionError("Unable to retrieve task logs"); + }, + complete: () => { + console.log("Log subscription completed"); + }, + }); + return null; }; export default LogStreamContent; From 6029ef8d1c27f98bca30d8b7bec688e32e4ddfe6 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Wed, 30 Sep 2026 10:46:24 +0000 Subject: [PATCH 18/31] add key to LogStreamContent component --- .../src/components/JobsViewer/LogStreamer/LogStreamer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx index f1f1b479..558e278e 100644 --- a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx +++ b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx @@ -108,6 +108,7 @@ const LogStreamer = ({ visit={visit} workflowName={selectedWorkflow} taskId={selectedTaskId} + key={`${selectedWorkflow}-${selectedTaskId}`} /> ); From d478e7dadae913e1ccf9030ec8e9a7578226ee4d Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Wed, 30 Sep 2026 14:10:00 +0000 Subject: [PATCH 19/31] move table utils to JobsTable folder --- .../src/components/JobsViewer/JobsTable/StatusIcons.tsx | 4 ++-- .../src/components/JobsViewer/JobsTable/TableRowWrapper.tsx | 2 +- .../components/JobsViewer/{ => JobsTable}/utils/BaseStack.tsx | 0 .../src/components/JobsViewer/{ => JobsTable}/utils/types.ts | 0 .../src/components/JobsViewer/{ => JobsTable}/utils/utils.ts | 0 5 files changed, 3 insertions(+), 3 deletions(-) rename frontend/unified/src/components/JobsViewer/{ => JobsTable}/utils/BaseStack.tsx (100%) rename frontend/unified/src/components/JobsViewer/{ => JobsTable}/utils/types.ts (100%) rename frontend/unified/src/components/JobsViewer/{ => JobsTable}/utils/utils.ts (100%) diff --git a/frontend/unified/src/components/JobsViewer/JobsTable/StatusIcons.tsx b/frontend/unified/src/components/JobsViewer/JobsTable/StatusIcons.tsx index 51974f22..217bc796 100644 --- a/frontend/unified/src/components/JobsViewer/JobsTable/StatusIcons.tsx +++ b/frontend/unified/src/components/JobsViewer/JobsTable/StatusIcons.tsx @@ -1,5 +1,5 @@ import { Typography } from "@mui/material"; -import { WorkflowStatus } from "../utils/types"; +import { WorkflowStatus } from "./utils/types"; import { CircleAlert, CircleCheckBig, @@ -8,7 +8,7 @@ import { ClockFading, } from "lucide-react"; import React from "react"; -import BaseStack from "../utils/BaseStack"; +import BaseStack from "./utils/BaseStack"; export const getWorkflowStatusIcon = ( status: WorkflowStatus, diff --git a/frontend/unified/src/components/JobsViewer/JobsTable/TableRowWrapper.tsx b/frontend/unified/src/components/JobsViewer/JobsTable/TableRowWrapper.tsx index ad09d117..b97fa705 100644 --- a/frontend/unified/src/components/JobsViewer/JobsTable/TableRowWrapper.tsx +++ b/frontend/unified/src/components/JobsViewer/JobsTable/TableRowWrapper.tsx @@ -3,7 +3,7 @@ import { TableCell, TableRow } from "@mui/material"; import { TableRowWrapperFragmentFragment } from "./__generated__/TableRowWrapper.generated"; import { useFragment } from "@apollo/client/react"; import BaseTableRow, { BASETABLEROW_FRAGMENT } from "./BaseTableRow"; -import { finishedStatuses } from "../utils/utils"; +import { finishedStatuses } from "./utils/utils"; import { useState } from "react"; import LiveTableRow from "./LiveTableRow"; diff --git a/frontend/unified/src/components/JobsViewer/utils/BaseStack.tsx b/frontend/unified/src/components/JobsViewer/JobsTable/utils/BaseStack.tsx similarity index 100% rename from frontend/unified/src/components/JobsViewer/utils/BaseStack.tsx rename to frontend/unified/src/components/JobsViewer/JobsTable/utils/BaseStack.tsx diff --git a/frontend/unified/src/components/JobsViewer/utils/types.ts b/frontend/unified/src/components/JobsViewer/JobsTable/utils/types.ts similarity index 100% rename from frontend/unified/src/components/JobsViewer/utils/types.ts rename to frontend/unified/src/components/JobsViewer/JobsTable/utils/types.ts diff --git a/frontend/unified/src/components/JobsViewer/utils/utils.ts b/frontend/unified/src/components/JobsViewer/JobsTable/utils/utils.ts similarity index 100% rename from frontend/unified/src/components/JobsViewer/utils/utils.ts rename to frontend/unified/src/components/JobsViewer/JobsTable/utils/utils.ts From 7635db0e4387dd9afe87f8d11ffb012b7c870e9f Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Wed, 30 Sep 2026 14:52:32 +0000 Subject: [PATCH 20/31] remove print statement --- .../src/components/JobsViewer/LogStreamer/LogStreamer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx index 558e278e..90935693 100644 --- a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx +++ b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx @@ -71,7 +71,7 @@ const LogStreamer = ({ if (!data) return <>No Data; const fetchedTasks = setFetchedTasks(data); - console.log(selectedTaskId); + return ( <> From 6e3267e8fbe366ad24fede9f65a9c61a02333fd6 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Wed, 30 Sep 2026 14:53:26 +0000 Subject: [PATCH 21/31] update JobDataViewer --- .../components/JobsViewer/JobDataViewer.tsx | 49 ++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/frontend/unified/src/components/JobsViewer/JobDataViewer.tsx b/frontend/unified/src/components/JobsViewer/JobDataViewer.tsx index 67e153b5..df0b0069 100644 --- a/frontend/unified/src/components/JobsViewer/JobDataViewer.tsx +++ b/frontend/unified/src/components/JobsViewer/JobDataViewer.tsx @@ -1,29 +1,66 @@ import { Divider, Typography } from "@mui/material"; -import DisplayLogMeta from "./InspectLogMeta"; import { Visit } from "@diamondlightsource/sci-react-ui"; -import LogStreamer from "./LogStreamer/LogStreamer"; +import LogStreamer, { LOGSTREAMER_FRAGMENT } from "./LogStreamer/LogStreamer"; import { Plot } from "./Plot/Plot"; +import { gql, TypedDocumentNode } from "@apollo/client"; +import { useSuspenseQuery } from "@apollo/client/react"; +import { + JobDataViewerQueryQuery, + JobDataViewerQueryQueryVariables, +} from "./__generated__/JobDataViewer.generated"; +import InspectLogMeta, { INSPECTLOGMETA_FRAGMENT } from "./InspectLogMeta"; + +const JOBDATAVIEWER_QUERY: TypedDocumentNode< + JobDataViewerQueryQuery, + JobDataViewerQueryQueryVariables +> = gql` + query JobDataViewerQuery($visit: VisitInput!, $name: String!) { + workflow(visit: $visit, name: $name) { + name + id + ...LogStreamerFragment + ...InspectLogMetaFragment + } + } + ${LOGSTREAMER_FRAGMENT} + ${INSPECTLOGMETA_FRAGMENT} +`; const JobDataViewer = ({ visit, selectedWorkflow, }: { visit: Visit; - selectedWorkflow: string | null; + selectedWorkflow: string; }) => { + const { data, error } = useSuspenseQuery(JOBDATAVIEWER_QUERY, { + variables: { + visit: visit, + name: selectedWorkflow, + }, + }); + + if (error) return <>Error: {error.message}; + if (!data) return <>No data; + if (!data.workflow) return <>No workflow data; + return ( <> Log - {selectedWorkflow !== null ? ( - + {selectedWorkflow ? ( + ) : (

No workflow selected

)} Log Stream {selectedWorkflow ? ( - + ) : ( <>No workflow selected )} From a68dcd0e49772fc2c297791ba7b50438c69a372f Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Wed, 30 Sep 2026 14:53:48 +0000 Subject: [PATCH 22/31] convert InspectLogMeta query to fragment --- .../components/JobsViewer/InspectLogMeta.tsx | 91 ++++++++----------- 1 file changed, 40 insertions(+), 51 deletions(-) diff --git a/frontend/unified/src/components/JobsViewer/InspectLogMeta.tsx b/frontend/unified/src/components/JobsViewer/InspectLogMeta.tsx index 8960903b..c2284928 100644 --- a/frontend/unified/src/components/JobsViewer/InspectLogMeta.tsx +++ b/frontend/unified/src/components/JobsViewer/InspectLogMeta.tsx @@ -10,57 +10,48 @@ import { InputLabel, } from "@mui/material"; -import { useQuery } from "@apollo/client/react"; +import { useSuspenseFragment } from "@apollo/client/react"; import { gql, type TypedDocumentNode } from "@apollo/client"; -import { - LogQueryQuery, - LogQueryQueryVariables, -} from "./__generated__/InspectLogMeta.generated"; -import { Visit } from "@diamondlightsource/sci-react-ui"; +import { InspectLogMetaFragmentFragment } from "./__generated__/InspectLogMeta.generated"; -export const InspectLog_Query: TypedDocumentNode< - LogQueryQuery, - LogQueryQueryVariables -> = gql` - query logQuery($visitobj: VisitInput!, $name: String!) { - workflow(visit: $visitobj, name: $name) { - name - id - status { - __typename - ... on WorkflowSucceededStatus { - message - startTime - tasks { +export const INSPECTLOGMETA_FRAGMENT: TypedDocumentNode = gql` + fragment InspectLogMetaFragment on Workflow { + status { + __typename + ... on WorkflowSucceededStatus { + startTime + message + tasks { + name + artifacts { name - artifacts { - name - url - mimeType - } + url + mimeType } } - ... on WorkflowErroredStatus { - message - tasks { + } + ... on WorkflowErroredStatus { + startTime + message + tasks { + name + artifacts { name - artifacts { - name - url - mimeType - } + url + mimeType } } - ... on WorkflowFailedStatus { - message - tasks { + } + ... on WorkflowFailedStatus { + startTime + message + tasks { + name + artifacts { name - artifacts { - name - url - mimeType - } + url + mimeType } } } @@ -68,17 +59,15 @@ export const InspectLog_Query: TypedDocumentNode< } `; -type DisplayLogMetaProps = { - visit: Visit; - workflowName: string; -}; - -export const DisplayLogMeta: FC = (props: { - visit: Visit; - workflowName: string; +const InspectLogMeta = ({ + queryData, +}: { + queryData: InspectLogMetaFragmentFragment; }) => { - const { loading, error, data } = useQuery(InspectLog_Query, { - variables: { visitobj: props.visit, name: props.workflowName }, + const { data } = useSuspenseFragment({ + fragment: INSPECTLOGMETA_FRAGMENT, + fragmentName: "InspectLogMetaFragment", + from: queryData, }); const artifactUrl: string[] = []; From f1b583f3762b4a1d38a97973ee8409a67874750c Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Wed, 30 Sep 2026 15:15:53 +0000 Subject: [PATCH 23/31] update App --- frontend/unified/src/App.tsx | 48 +----------------------------------- 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/frontend/unified/src/App.tsx b/frontend/unified/src/App.tsx index ac88104b..64c941f4 100644 --- a/frontend/unified/src/App.tsx +++ b/frontend/unified/src/App.tsx @@ -26,7 +26,6 @@ import { templateOptions } from "./data/templates"; import { WorkflowForm } from "./components/WorkflowForm"; import { Beamline, Technique } from "./types"; import { ParameterConfiguration } from "./components/ParameterConfiguration/ParameterConfiguration"; -import { Plot } from "./components/Plot/Plot"; import { apolloClientWorkflows } from "../../src/ApolloClient"; import { useAuth } from "@diamondlightsource/sci-react-ui"; import { @@ -122,7 +121,6 @@ export const App: React.FC = () => { null ); const [selectedScanIds, setSelectedScanIds] = useState([]); - const [selectedWorkflow, setSelectedWorkflow] = useState(null); const { loading, error, data } = useQuery(SESSION_QUERY, { variables: { username: useFedid() }, }); @@ -357,51 +355,7 @@ export const App: React.FC = () => { - - - - }> - Jobs - - - - - - - - }> - Log - - - {selectedWorkflow !== null ? ( - - ) : ( -

No workflow selected

- )} -
-
- - - }> - Plot - - - - - -
+ From 1a3068f366d98141406be809c72f40362af4e55d Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Wed, 30 Sep 2026 15:16:15 +0000 Subject: [PATCH 24/31] add Accordions --- .../components/JobsViewer/JobDataViewer.tsx | 59 ++++++++++++------- .../src/components/JobsViewer/JobsViewer.tsx | 29 ++++++--- .../JobsViewer/LogStreamer/LogStreamer.tsx | 4 +- 3 files changed, 61 insertions(+), 31 deletions(-) diff --git a/frontend/unified/src/components/JobsViewer/JobDataViewer.tsx b/frontend/unified/src/components/JobsViewer/JobDataViewer.tsx index df0b0069..a9aa7906 100644 --- a/frontend/unified/src/components/JobsViewer/JobDataViewer.tsx +++ b/frontend/unified/src/components/JobsViewer/JobDataViewer.tsx @@ -1,4 +1,9 @@ -import { Divider, Typography } from "@mui/material"; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Typography, +} from "@mui/material"; import { Visit } from "@diamondlightsource/sci-react-ui"; import LogStreamer, { LOGSTREAMER_FRAGMENT } from "./LogStreamer/LogStreamer"; import { Plot } from "./Plot/Plot"; @@ -9,6 +14,7 @@ import { JobDataViewerQueryQueryVariables, } from "./__generated__/JobDataViewer.generated"; import InspectLogMeta, { INSPECTLOGMETA_FRAGMENT } from "./InspectLogMeta"; +import { ChevronDown } from "lucide-react"; const JOBDATAVIEWER_QUERY: TypedDocumentNode< JobDataViewerQueryQuery, @@ -46,27 +52,36 @@ const JobDataViewer = ({ return ( <> - - Log - {selectedWorkflow ? ( - - ) : ( -

No workflow selected

- )} - - Log Stream - {selectedWorkflow ? ( - - ) : ( - <>No workflow selected - )} - - Plot - + + }> + Log + + + + + + + + }> + Log Stream + + + + + + + + }> + Plot + + + + + ); }; diff --git a/frontend/unified/src/components/JobsViewer/JobsViewer.tsx b/frontend/unified/src/components/JobsViewer/JobsViewer.tsx index 58dc73e2..6ed18b97 100644 --- a/frontend/unified/src/components/JobsViewer/JobsViewer.tsx +++ b/frontend/unified/src/components/JobsViewer/JobsViewer.tsx @@ -2,24 +2,39 @@ import { Suspense, useState } from "react"; import JobsTable from "./JobsTable/JobsTable"; import { Visit } from "@diamondlightsource/sci-react-ui"; import JobDataViewer from "./JobDataViewer"; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Stack, + Typography, +} from "@mui/material"; +import { ChevronDown } from "lucide-react"; const JobsViewer = ({ visit }: { visit: Visit }) => { const [selectedWorkflow, setSelectedWorkflow] = useState(null); return ( - <> + - + + }> + Jobs + + + + + {selectedWorkflow && ( )} - + ); }; diff --git a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx index 90935693..fa7f1acc 100644 --- a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx +++ b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamer.tsx @@ -73,7 +73,7 @@ const LogStreamer = ({ const fetchedTasks = setFetchedTasks(data); return ( - <> + Select Task - {LogFileTuples.map((logFilename) => { - return {logFilename}; - })} - - ) : ( -

No logs

- )} +
); }; -export default DisplayLogMeta; +export default InspectLogMeta; From 00898d070fb88f3d0f1bb5b39ce0b797b2540e30 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Thu, 1 Oct 2026 08:27:53 +0000 Subject: [PATCH 27/31] add taskCompleted state to LogStreamContent --- .../components/JobsViewer/LogStreamer/LogStreamContent.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamContent.tsx b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamContent.tsx index 8ab6bf07..798a1258 100644 --- a/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamContent.tsx +++ b/frontend/unified/src/components/JobsViewer/LogStreamer/LogStreamContent.tsx @@ -39,6 +39,7 @@ const LogStreamContent = ({ null ); const [logUnavailable, setLogUnavailable] = useState(false); + const [taskFinished, setTaskFinished] = useState(false); const containerRef = useRef(null); useEffect(() => { @@ -49,7 +50,7 @@ const LogStreamContent = ({ return ( <> - {taskId && !logUnavailable && ( + {taskId && !logUnavailable && !taskFinished && ( )} >; setSubscriptionError: Dispatch>; setLogUnavailable: Dispatch>; + setTaskFinished: Dispatch>; }) => { apolloClientWorkflows .subscribe({ @@ -141,6 +145,7 @@ const LogStreamSubscription = ({ setSubscriptionError("Unable to retrieve task logs"); }, complete: () => { + setTaskFinished(true); console.log("Log subscription completed"); }, }); From 487cf1e5a0a6269a0fddace55c95b9c1a6b8c1a5 Mon Sep 17 00:00:00 2001 From: NatLeung96 Date: Thu, 1 Oct 2026 13:47:26 +0000 Subject: [PATCH 28/31] update ArtifactSelector to use fragment --- .../components/JobsViewer/JobDataViewer.tsx | 9 +- .../JobsViewer/Plot/ArtifactSelector.tsx | 139 +++++++----------- .../src/components/JobsViewer/Plot/Plot.tsx | 11 +- 3 files changed, 73 insertions(+), 86 deletions(-) diff --git a/frontend/unified/src/components/JobsViewer/JobDataViewer.tsx b/frontend/unified/src/components/JobsViewer/JobDataViewer.tsx index a9aa7906..2da8c3d7 100644 --- a/frontend/unified/src/components/JobsViewer/JobDataViewer.tsx +++ b/frontend/unified/src/components/JobsViewer/JobDataViewer.tsx @@ -15,6 +15,7 @@ import { } from "./__generated__/JobDataViewer.generated"; import InspectLogMeta, { INSPECTLOGMETA_FRAGMENT } from "./InspectLogMeta"; import { ChevronDown } from "lucide-react"; +import { ARTIFACTSELECTOR_FRAGMENT } from "./Plot/ArtifactSelector"; const JOBDATAVIEWER_QUERY: TypedDocumentNode< JobDataViewerQueryQuery, @@ -26,10 +27,12 @@ const JOBDATAVIEWER_QUERY: TypedDocumentNode< id ...LogStreamerFragment ...InspectLogMetaFragment + ...ArtifactSelectorFragment } } ${LOGSTREAMER_FRAGMENT} ${INSPECTLOGMETA_FRAGMENT} + ${ARTIFACTSELECTOR_FRAGMENT} `; const JobDataViewer = ({ @@ -79,7 +82,11 @@ const JobDataViewer = ({ Plot - + diff --git a/frontend/unified/src/components/JobsViewer/Plot/ArtifactSelector.tsx b/frontend/unified/src/components/JobsViewer/Plot/ArtifactSelector.tsx index ef18014c..9062128f 100644 --- a/frontend/unified/src/components/JobsViewer/Plot/ArtifactSelector.tsx +++ b/frontend/unified/src/components/JobsViewer/Plot/ArtifactSelector.tsx @@ -1,77 +1,59 @@ import { gql, TypedDocumentNode } from "@apollo/client"; -import { - GetWorkflowArtifactsQuery, - GetWorkflowArtifactsQueryVariables, -} from "./__generated__/ArtifactSelector.generated"; -import { useSuspenseQuery } from "@apollo/client/react"; -import { Visit } from "@diamondlightsource/sci-react-ui"; +import { ArtifactSelectorFragmentFragment } from "./__generated__/ArtifactSelector.generated"; +import { useSuspenseFragment } from "@apollo/client/react"; 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 +export const ARTIFACTSELECTOR_FRAGMENT: TypedDocumentNode = gql` + fragment ArtifactSelectorFragment on Workflow { + name + status { + __typename + ... on WorkflowRunningStatus { + startTime + tasks { + id + name + artifacts { name - status - stepType - artifacts { - name - url - mimeType - } + url + mimeType } } - ... on WorkflowSucceededStatus { - __typename - startTime - tasks { - id + } + ... on WorkflowSucceededStatus { + startTime + tasks { + id + name + artifacts { name - status - stepType - artifacts { - name - url - mimeType - } + url + mimeType } } - ... on WorkflowFailedStatus { - tasks { - id + } + ... on WorkflowFailedStatus { + startTime + tasks { + id + name + artifacts { name - status - stepType - artifacts { - name - url - mimeType - } + url + mimeType } } - ... on WorkflowErroredStatus { - tasks { - id + } + ... on WorkflowErroredStatus { + startTime + tasks { + id + name + artifacts { name - status - stepType - artifacts { - name - url - mimeType - } + url + mimeType } } } @@ -79,49 +61,42 @@ const GET_WORKFLOW_ARTIFACTS: TypedDocumentNode< } `; -type NonNullWorkflow = NonNullable; -type NonNullWorkflowStatus = NonNullable; -type WorkflowSucceededStatus = Extract< - NonNullWorkflowStatus, - { __typename: "WorkflowSucceededStatus" } ->; -export type Artifact = WorkflowSucceededStatus["tasks"][0]["artifacts"][0]; +export type Artifact = { + name: string; + url: unknown; + mimeType: string; +}; -type TaskNameAndArtifactTuple = [string, Artifact[]]; +type TaskNameAndArtifactTuple = [taskName: string, artifacts: Artifact[]]; type ArtifactSelectorProps = { - workflowName: string; - visit: Visit; setArtifact: (_: Artifact | null) => void; isPlottingEnabled: boolean; + queryData: ArtifactSelectorFragmentFragment; }; const IMAGE_ARTIFACT_MIME_TYPES = ["image/jpeg", "image/tiff"]; export const ArtifactSelector: React.FC = ({ - workflowName, - visit, setArtifact, isPlottingEnabled, + queryData, }: ArtifactSelectorProps) => { const [selectedArtifact, setSelectedArtifact] = useState(""); - const { error, data } = useSuspenseQuery(GET_WORKFLOW_ARTIFACTS, { - variables: { - name: workflowName, - visit: visit, - }, + const { data } = useSuspenseFragment({ + fragment: ARTIFACTSELECTOR_FRAGMENT, + fragmentName: "ArtifactSelectorFragment", + from: queryData, }); - if (error) return

Error: {error.message}

; - const generateArtifactList = (): React.ReactNode[] => { - switch (data.workflow?.status?.__typename) { + switch (data.status?.__typename) { case "WorkflowSucceededStatus": case "WorkflowRunningStatus": case "WorkflowFailedStatus": case "WorkflowErroredStatus": { const taskNamesAndImageArtifacts: TaskNameAndArtifactTuple[] = - data.workflow.status.tasks + data.status.tasks .map( (task) => [ @@ -153,7 +128,7 @@ export const ArtifactSelector: React.FC = ({ return ( Artifact - {data.workflow?.status?.__typename !== "WorkflowPendingStatus" ? ( + {data.status?.__typename !== "WorkflowPendingStatus" ? (