Skip to content

Commit 5f6d686

Browse files
Automatically trigger a reconstruction workflow on scan end (#23)
* Extract submitWorkflow logic for reuse * Extract SSE connection to useScanEvents hook * Organise components and hooks * Automatically trigger visr-reconstruction workflow at scan end * Improve graphql msw handlers * Correct workflow parameter
1 parent da57e2a commit 5f6d686

22 files changed

+407
-132
lines changed
File renamed without changes.
File renamed without changes.

src/components/SpectroscopyForm.tsx renamed to src/components/spectroscopy/SpectroscopyForm.tsx

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { useState } from "react";
21
import { Box, TextField } from "@mui/material";
2+
import { useInstrumentSession } from "../../context/instrumentSession/useInstrumentSession";
33
import NumberTextField from "./NumberTextField";
4-
import RunPlanButton from "./RunPlanButton";
5-
import RawSpectroscopyData from "./RawSpectroscopyData";
6-
import { useInstrumentSession } from "../context/instrumentSession/useInstrumentSession";
4+
import RunPlanButton from "../RunPlanButton";
5+
import { useState } from "react";
76

87
export type SpectroscopyFormData = {
98
total_number_of_scan_points: number;
@@ -13,26 +12,17 @@ export type SpectroscopyFormData = {
1312
exposure_time: number;
1413
};
1514

16-
function SpectroscopyForm() {
15+
export function SpectroscopyForm() {
16+
const { instrumentSession, setInstrumentSession } = useInstrumentSession();
1717
const [formData, setFormData] = useState<SpectroscopyFormData>({
1818
total_number_of_scan_points: 25,
1919
grid_size: 5.0,
2020
grid_origin_x: 0.0,
2121
grid_origin_y: 0.0,
2222
exposure_time: 0.1,
2323
});
24-
25-
const { instrumentSession, setInstrumentSession } = useInstrumentSession();
26-
2724
return (
28-
<Box
29-
sx={{
30-
margin: 2,
31-
display: "flex",
32-
flexDirection: "column",
33-
}}
34-
>
35-
<RawSpectroscopyData />
25+
<Box>
3626
<Box
3727
sx={{
3828
display: "grid",
@@ -98,5 +88,3 @@ function SpectroscopyForm() {
9888
</Box>
9989
);
10090
}
101-
102-
export default SpectroscopyForm;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Box } from "@mui/material";
2+
import RawSpectroscopyData from "./RawSpectroscopyData";
3+
import { SpectroscopyForm } from "./SpectroscopyForm";
4+
import { useEffect } from "react";
5+
import { useScanEvents } from "../../hooks/scanEvents";
6+
import { useSubmitWorkflow } from "../../hooks/useSubmitWorkflow";
7+
import { useInstrumentSession } from "../../context/instrumentSession/useInstrumentSession";
8+
import { visitTextToVisit } from "../../utils/common";
9+
10+
export type SpectroscopyFormData = {
11+
total_number_of_scan_points: number;
12+
grid_size: number;
13+
grid_origin_x: number;
14+
grid_origin_y: number;
15+
exposure_time: number;
16+
};
17+
18+
function SpectroscopyView() {
19+
// set off workflow when scan ends
20+
const scanEvent = useScanEvents();
21+
const { instrumentSession } = useInstrumentSession();
22+
23+
const submitWorkflow = useSubmitWorkflow("visr-reconstruction");
24+
25+
useEffect(() => {
26+
if (!scanEvent || !instrumentSession) return;
27+
if (scanEvent.status == "finished") {
28+
const visit = visitTextToVisit(instrumentSession);
29+
if (!visit) {
30+
console.warn("Invalid visit; cannot submit workflow");
31+
return;
32+
}
33+
submitWorkflow(visit, {
34+
"input-file-path": scanEvent.filepath,
35+
});
36+
}
37+
});
38+
39+
return (
40+
<Box
41+
sx={{
42+
margin: 2,
43+
display: "flex",
44+
flexDirection: "column",
45+
}}
46+
>
47+
<RawSpectroscopyData />
48+
<SpectroscopyForm />
49+
</Box>
50+
);
51+
}
52+
53+
export default SpectroscopyView;

src/components/useSpectroscopyData.ts renamed to src/components/spectroscopy/useSpectroscopyData.ts

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useEffect, useRef, useState } from "react";
22
import { type NDT } from "@diamondlightsource/davidia";
3+
import { useScanEvents } from "../../hooks/scanEvents/useScanEvents";
34

45
export type RGBColour = "red" | "green" | "blue" | "gray";
56

@@ -16,14 +17,8 @@ export interface DataChannels {
1617
blue: NDT | null;
1718
}
1819

19-
export interface ScanEventMessage {
20-
status: "running" | "finished" | "failed";
21-
filepath: string;
22-
uuid: string;
23-
snake: boolean;
24-
}
25-
2620
export function useSpectroscopyData(fetchMap: FetchMapFunction) {
21+
const scanEvent = useScanEvents();
2722
const [running, setRunning] = useState<boolean>(false);
2823
const [filepath, setFilepath] = useState<string | null>(null);
2924
const [snake, setSnake] = useState<boolean>(false);
@@ -36,37 +31,21 @@ export function useSpectroscopyData(fetchMap: FetchMapFunction) {
3631
/** Cached interval id */
3732
const pollInterval = useRef<ReturnType<typeof setInterval> | null>(null);
3833

39-
// Subscribe to SSE
34+
// React to scan updates
4035
useEffect(() => {
41-
const evtSource = new EventSource("/api/data/events");
42-
43-
evtSource.onmessage = event => {
44-
try {
45-
const msg = JSON.parse(event.data) as ScanEventMessage;
46-
console.log("SSE message:", msg);
47-
48-
if (msg.status === "running") {
49-
setRunning(true);
50-
setFilepath(msg.filepath);
51-
setSnake(msg.snake);
52-
} else if (msg.status === "finished" || msg.status === "failed") {
53-
setRunning(false); // triggers final poll below
54-
}
55-
} catch (err) {
56-
console.error("Error parsing SSE:", err);
57-
}
58-
};
59-
60-
evtSource.onerror = err => {
61-
console.warn("Temporary SSE connection error:", err);
62-
};
63-
64-
evtSource.onopen = () => {
65-
console.log("SSE connection opened or re-established");
66-
};
67-
68-
return () => evtSource.close();
69-
}, []);
36+
if (!scanEvent) return;
37+
38+
if (scanEvent.status === "running") {
39+
setRunning(true);
40+
setFilepath(scanEvent.filepath);
41+
setSnake(scanEvent.snake);
42+
} else if (
43+
scanEvent.status === "finished" ||
44+
scanEvent.status === "failed"
45+
) {
46+
setRunning(false); // triggers final poll below
47+
}
48+
}, [scanEvent]);
7049

7150
// Poll during scan + once more afterwards
7251
useEffect(() => {

src/components/SubmissionForm.tsx renamed to src/components/workflows/SubmissionForm.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { useFragment } from "react-relay";
2-
import { type JSONObject, type Visit } from "../utils/types";
2+
import { type JSONObject, type Visit } from "../../utils/types";
33
import type { JsonSchema, UISchemaElement } from "@jsonforms/core";
4-
import type { workflowTemplateFragment$key } from "../graphql/__generated__/workflowTemplateFragment.graphql";
5-
import { workflowTemplateFragment } from "../graphql/workflowTemplateFragment";
4+
import type { workflowTemplateFragment$key } from "../../graphql/__generated__/workflowTemplateFragment.graphql";
5+
import { workflowTemplateFragment } from "../../graphql/workflowTemplateFragment";
66
import TemplateSubmissionForm from "./TemplateSubmissionForm";
77

88
const SubmissionForm = (props: {

src/components/SubmittedMessagesList.tsx renamed to src/components/workflows/SubmittedMessagesList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type {
1414
SubmissionGraphQLErrorMessage,
1515
SubmissionNetworkErrorMessage,
1616
SubmissionSuccessMessage,
17-
} from "../utils/types";
17+
} from "../../utils/types";
1818

1919
const renderSubmittedMessage = (
2020
r:

src/components/TemplateSubmissionForm.tsx renamed to src/components/workflows/TemplateSubmissionForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { JsonForms } from "@jsonforms/react";
1111
import React, { useState } from "react";
1212
import { Divider, Snackbar, Stack, Typography, useTheme } from "@mui/material";
1313
import type { ErrorObject } from "ajv";
14-
import type { JSONObject, Visit } from "../utils/types";
14+
import type { JSONObject, Visit } from "../../utils/types";
1515
import { VisitInput } from "@diamondlightsource/sci-react-ui";
1616

1717
interface TemplateSubmissionFormProps {
Lines changed: 31 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import { useState } from "react";
2-
import { useLazyLoadQuery, useMutation } from "react-relay/hooks";
2+
import { useLazyLoadQuery } from "react-relay/hooks";
33
import { graphql } from "relay-runtime";
44
import type {
55
JSONObject,
66
SubmissionGraphQLErrorMessage,
77
SubmissionNetworkErrorMessage,
88
SubmissionSuccessMessage,
9-
} from "../utils/types";
9+
} from "../../utils/types";
1010
import { type Visit, visitToText } from "@diamondlightsource/sci-react-ui";
1111
import SubmissionForm from "./SubmissionForm";
1212
import type { TemplateViewQuery as TemplateViewQueryType } from "./__generated__/TemplateViewQuery.graphql";
13-
import type { TemplateViewMutation as TemplateViewMutationType } from "./__generated__/TemplateViewMutation.graphql";
14-
import { visitTextToVisit } from "../utils/common";
13+
import { visitTextToVisit } from "../../utils/common";
1514
import { Box } from "@mui/material";
1615
import SubmittedMessagesList from "./SubmittedMessagesList";
16+
import { useSubmitWorkflow } from "../../hooks/useSubmitWorkflow";
1717

1818
const templateViewQuery = graphql`
1919
query TemplateViewQuery($templateName: String!) {
@@ -23,22 +23,6 @@ const templateViewQuery = graphql`
2323
}
2424
`;
2525

26-
const templateViewMutation = graphql`
27-
mutation TemplateViewMutation(
28-
$templateName: String!
29-
$visit: VisitInput!
30-
$parameters: JSON!
31-
) {
32-
submitWorkflowTemplate(
33-
name: $templateName
34-
visit: $visit
35-
parameters: $parameters
36-
) {
37-
name
38-
}
39-
}
40-
`;
41-
4226
export default function TemplateView({
4327
templateName,
4428
visit,
@@ -63,51 +47,41 @@ export default function TemplateView({
6347
localStorage.getItem("instrumentSessionID") ?? "",
6448
);
6549

66-
const [commitMutation] =
67-
useMutation<TemplateViewMutationType>(templateViewMutation);
50+
const submit = useSubmitWorkflow(templateName);
6851

69-
function submitWorkflow(visit: Visit, parameters: object) {
70-
commitMutation({
71-
variables: {
72-
templateName: templateName,
73-
visit: visit,
74-
parameters: parameters,
75-
},
76-
onCompleted: (response, errors) => {
77-
if (errors?.length) {
78-
console.error("GraphQL errors:", errors);
79-
setSubmissionResults(prev => [
80-
{
81-
type: "graphQLError",
82-
errors: errors,
83-
},
84-
...prev,
85-
]);
86-
} else {
87-
const submittedName = response.submitWorkflowTemplate.name;
88-
console.log("Successfully submitted:", submittedName);
89-
setSubmissionResults(prev => [
90-
{
91-
type: "success",
92-
message: `${visitToText(visit)}/${submittedName}`,
93-
},
94-
...prev,
95-
]);
96-
localStorage.setItem("instrumentSessionID", visitToText(visit));
97-
}
98-
},
99-
onError: err => {
100-
console.error("Submission failed:", err);
52+
async function submitWorkflow(visit: Visit, parameters: object) {
53+
try {
54+
const { name: submittedName } = await submit(visit, parameters);
55+
setSubmissionResults(prev => [
56+
{
57+
type: "success",
58+
message: `${visitToText(visit)}/${submittedName}`,
59+
},
60+
...prev,
61+
]);
62+
localStorage.setItem("instrumentSessionID", visitToText(visit));
63+
} catch (err) {
64+
// Err is either GraphQL errors array or a single network error
65+
if (Array.isArray(err)) {
66+
setSubmissionResults(prev => [
67+
{
68+
type: "graphQLError",
69+
errors: err,
70+
},
71+
...prev,
72+
]);
73+
} else {
10174
setSubmissionResults(prev => [
10275
{
10376
type: "networkError",
104-
error: err,
77+
error: err as Error,
10578
},
10679
...prev,
10780
]);
108-
},
109-
});
81+
}
82+
}
11083
}
84+
11185
return (
11286
<Box>
11387
<SubmissionForm

src/components/__generated__/TemplateViewMutation.graphql.ts renamed to src/components/workflows/__generated__/TemplateViewMutation.graphql.ts

File renamed without changes.

0 commit comments

Comments
 (0)