From e997c63bed4af349a98e7c9e257aa5fa8963db83 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Tue, 12 May 2026 16:50:25 +0000 Subject: [PATCH 01/40] Add a blueapi status card --- .../src/components/BlueapiWorkerState.tsx | 48 +++++++++++++++++++ apps/i15-1/src/routes/Robot.tsx | 5 +- 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 apps/i15-1/src/components/BlueapiWorkerState.tsx diff --git a/apps/i15-1/src/components/BlueapiWorkerState.tsx b/apps/i15-1/src/components/BlueapiWorkerState.tsx new file mode 100644 index 00000000..1a12bdbc --- /dev/null +++ b/apps/i15-1/src/components/BlueapiWorkerState.tsx @@ -0,0 +1,48 @@ +import { useGetWorkerState } from "@atlas/blueapi-query"; +import { Card, CardContent, Stack, Typography, useTheme } from "@mui/material"; + +function getStateColorMap() { + const theme = useTheme(); + return { + IDLE: theme.palette.info.main, + RUNNING: theme.palette.success.main, + PAUSING: theme.palette.warning.main, + PAUSED: theme.palette.warning.main, + HALTING: theme.palette.warning.main, + STOPPING: theme.palette.error.main, + ABORTING: theme.palette.error.main, + SUSPENDING: theme.palette.error.main, + PANICKED: theme.palette.error.main, + UNKNOWN: theme.palette.background.paper, + }; +} + +export function BlueapiWorkerState() { + const theme = useTheme(); + const workerState = useGetWorkerState(); + const stateMap = getStateColorMap(); + + return ( + + + + Blueapi worker state: + + {workerState.data} + + + + + ); +} diff --git a/apps/i15-1/src/routes/Robot.tsx b/apps/i15-1/src/routes/Robot.tsx index ee0a2e49..40b1ff74 100644 --- a/apps/i15-1/src/routes/Robot.tsx +++ b/apps/i15-1/src/routes/Robot.tsx @@ -1,10 +1,11 @@ import { useInstrumentSession } from "../context/instrumentSession/useInstrumentSession"; -import { Box, Typography, Stack, useTheme, TextField } from "@mui/material"; +import { Box, Typography, Stack, useTheme } from "@mui/material"; import { useState } from "react"; import { NumberInput } from "../components/NumberInput"; import RunPlanButton from "../components/RunPlanButton"; import { ReadOnlyPv } from "@atlas/pvws-config"; import { StatusCard } from "../components/StatusCard"; +import { BlueapiWorkerState } from "../components/BlueapiWorkerState"; type RobotSampleFormData = { puck: number; @@ -16,6 +17,7 @@ function StatusSidebar() { return ( + Date: Tue, 12 May 2026 16:58:58 +0000 Subject: [PATCH 02/40] Add abort button --- apps/i15-1/src/components/AbortPlanButton.tsx | 28 +++++++++++++++++++ apps/i15-1/src/routes/Robot.tsx | 28 +++++++++++-------- 2 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 apps/i15-1/src/components/AbortPlanButton.tsx diff --git a/apps/i15-1/src/components/AbortPlanButton.tsx b/apps/i15-1/src/components/AbortPlanButton.tsx new file mode 100644 index 00000000..d7e3fd81 --- /dev/null +++ b/apps/i15-1/src/components/AbortPlanButton.tsx @@ -0,0 +1,28 @@ +import { Button, useTheme } from "@mui/material"; +import { useEffect, useState } from "react"; + +import type { WorkerStateRequest } from "@atlas/blueapi"; +import { useSetWorkerState } from "@atlas/blueapi-query"; + +export function AbortPlanButton() { + const theme = useTheme(); + const workerState = useSetWorkerState(); + + const handleClick = async () => { + const workerRequest: WorkerStateRequest = { + new_state: "ABORTING", + reason: "Abort button pressed", + }; + workerState.mutate(workerRequest); + }; + return ( + + ); +} diff --git a/apps/i15-1/src/routes/Robot.tsx b/apps/i15-1/src/routes/Robot.tsx index 40b1ff74..f614d974 100644 --- a/apps/i15-1/src/routes/Robot.tsx +++ b/apps/i15-1/src/routes/Robot.tsx @@ -6,6 +6,7 @@ import RunPlanButton from "../components/RunPlanButton"; import { ReadOnlyPv } from "@atlas/pvws-config"; import { StatusCard } from "../components/StatusCard"; import { BlueapiWorkerState } from "../components/BlueapiWorkerState"; +import { AbortPlanButton } from "../components/AbortPlanButton"; type RobotSampleFormData = { puck: number; @@ -77,7 +78,7 @@ function Robot() { borderColor: theme.palette.primary.main, }} > - + Sample Position @@ -99,17 +100,20 @@ function Robot() { }} /> - - + + + + + From cb83b97f68cc59643a6a3771852a5a17eeaf6524 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Tue, 12 May 2026 16:59:58 +0000 Subject: [PATCH 03/40] Tidy up --- apps/i15-1/src/components/AbortPlanButton.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/i15-1/src/components/AbortPlanButton.tsx b/apps/i15-1/src/components/AbortPlanButton.tsx index d7e3fd81..4a130dfb 100644 --- a/apps/i15-1/src/components/AbortPlanButton.tsx +++ b/apps/i15-1/src/components/AbortPlanButton.tsx @@ -1,11 +1,9 @@ import { Button, useTheme } from "@mui/material"; -import { useEffect, useState } from "react"; import type { WorkerStateRequest } from "@atlas/blueapi"; import { useSetWorkerState } from "@atlas/blueapi-query"; export function AbortPlanButton() { - const theme = useTheme(); const workerState = useSetWorkerState(); const handleClick = async () => { From bec4ed7ca7165b470b1f7831e75fa9d3b8334928 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Tue, 12 May 2026 17:20:50 +0000 Subject: [PATCH 04/40] Try adding a handler --- apps/i15-1/src/components/AbortPlanButton.tsx | 2 +- apps/i15-1/src/mocks/handlers.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/i15-1/src/components/AbortPlanButton.tsx b/apps/i15-1/src/components/AbortPlanButton.tsx index 4a130dfb..a229f8ca 100644 --- a/apps/i15-1/src/components/AbortPlanButton.tsx +++ b/apps/i15-1/src/components/AbortPlanButton.tsx @@ -1,4 +1,4 @@ -import { Button, useTheme } from "@mui/material"; +import { Button } from "@mui/material"; import type { WorkerStateRequest } from "@atlas/blueapi"; import { useSetWorkerState } from "@atlas/blueapi-query"; diff --git a/apps/i15-1/src/mocks/handlers.ts b/apps/i15-1/src/mocks/handlers.ts index 695f4b49..89be0e86 100644 --- a/apps/i15-1/src/mocks/handlers.ts +++ b/apps/i15-1/src/mocks/handlers.ts @@ -23,6 +23,10 @@ export const handlers = [ return HttpResponse.json("IDLE"); }), + http.put("/api/worker/state", () => { + return HttpResponse.json("ABORTING"); + }), + http.get("/oauth2/userinfo", () => { return HttpResponse.json({ preferredUsername: "abc123456" }); }), From 2e2e94d99b0d5c77f6f5ed2f6f14487280d34ddc Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Wed, 13 May 2026 09:51:09 +0000 Subject: [PATCH 05/40] Add a first snackbar --- apps/i15-1/src/components/AbortPlanButton.tsx | 59 +++++++++++++++---- apps/i15-1/src/mocks/handlers.ts | 4 -- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/apps/i15-1/src/components/AbortPlanButton.tsx b/apps/i15-1/src/components/AbortPlanButton.tsx index a229f8ca..6e9446d1 100644 --- a/apps/i15-1/src/components/AbortPlanButton.tsx +++ b/apps/i15-1/src/components/AbortPlanButton.tsx @@ -1,26 +1,65 @@ -import { Button } from "@mui/material"; +import { + Alert, + Button, + Snackbar, + Tooltip, + type SnackbarCloseReason, +} from "@mui/material"; import type { WorkerStateRequest } from "@atlas/blueapi"; import { useSetWorkerState } from "@atlas/blueapi-query"; +import React, { useState } from "react"; export function AbortPlanButton() { const workerState = useSetWorkerState(); + const [openSnackbar, setOpenSnackbar] = useState(false); - const handleClick = async () => { + const abortPlan = async () => { const workerRequest: WorkerStateRequest = { new_state: "ABORTING", reason: "Abort button pressed", }; workerState.mutate(workerRequest); }; + + const handleClick = async () => { + setOpenSnackbar(true); + await abortPlan(); + }; + + const handleSnackbarClose = ( + _event: React.SyntheticEvent | Event, + reason?: SnackbarCloseReason, + ) => { + if (reason === "clickaway") { + return; + } + + setOpenSnackbar(false); + }; + return ( - + + + + + + + Abort button pressed, will abort current plan ... + + + ); } diff --git a/apps/i15-1/src/mocks/handlers.ts b/apps/i15-1/src/mocks/handlers.ts index 89be0e86..695f4b49 100644 --- a/apps/i15-1/src/mocks/handlers.ts +++ b/apps/i15-1/src/mocks/handlers.ts @@ -23,10 +23,6 @@ export const handlers = [ return HttpResponse.json("IDLE"); }), - http.put("/api/worker/state", () => { - return HttpResponse.json("ABORTING"); - }), - http.get("/oauth2/userinfo", () => { return HttpResponse.json({ preferredUsername: "abc123456" }); }), From d4b9912714b261b6d9009f7982f1cd16fba0297b Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Wed, 13 May 2026 14:40:19 +0000 Subject: [PATCH 06/40] Get the msw handler to more or less show abort --- apps/i15-1/src/mocks/handlers.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/i15-1/src/mocks/handlers.ts b/apps/i15-1/src/mocks/handlers.ts index 695f4b49..80434943 100644 --- a/apps/i15-1/src/mocks/handlers.ts +++ b/apps/i15-1/src/mocks/handlers.ts @@ -3,11 +3,15 @@ import { http, HttpResponse, ws } from "msw"; const fakeTaskId = "7304e8e0-81c6-4978-9a9d-9046ab79ce3c"; let workerStatus = { status: "IDLE", duration: 0 }; +function setWorkerState(new_state: string) { + workerStatus.status = new_state; +} + const fakePvws = ws.link("wss://pvws.diamond.ac.uk/pvws/pv"); export const handlers = [ http.put("/api/worker/task", () => { - workerStatus.status = "RUNNING"; + setWorkerState("RUNNING"); return HttpResponse.json({ task_id: fakeTaskId, }); @@ -19,8 +23,13 @@ export const handlers = [ }); }), - http.put("/api/worker/state", () => { - return HttpResponse.json("IDLE"); + http.put("/api/worker/state", async ({ request }) => { + // @ts-ignore + const { new_state } = await request.json(); + if (new_state === "ABORTING") { + setWorkerState(new_state); + } + return HttpResponse.json(workerStatus.status); }), http.get("/oauth2/userinfo", () => { From 28b869fdae2dc0eb008eea211c7e2f3c5600bd96 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Wed, 13 May 2026 15:27:45 +0000 Subject: [PATCH 07/40] Update the Trackable task from blueapi --- packages/blueapi/src/tasks.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/blueapi/src/tasks.ts b/packages/blueapi/src/tasks.ts index 20e91ca7..59bdd360 100644 --- a/packages/blueapi/src/tasks.ts +++ b/packages/blueapi/src/tasks.ts @@ -12,6 +12,20 @@ export interface Task { metadata: object; } +type TaskResult = { + outcome: "success"; + result?: any; + type?: string; +}; + +type TaskError = { + outcome: "error"; + type?: string; + message?: string; +}; + +export type TaskOutcome = TaskResult | TaskError | null; + /** A representation of a task that the worker recognizes */ export interface TrackableTask { task_id: string; @@ -20,6 +34,7 @@ export interface TrackableTask { is_complete: boolean; is_pending: boolean; errors: string[]; + outcome: TaskOutcome; } /** Diagnostic information on the tasks */ From de12c1372d155ec13d9a4d9bf2bfbb0579ba3368 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Wed, 13 May 2026 15:28:04 +0000 Subject: [PATCH 08/40] Add a snackbar to the run plan button --- apps/i15-1/src/components/AbortPlanButton.tsx | 2 +- apps/i15-1/src/components/RunPlanButton.tsx | 84 +++++++++++++++---- 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/apps/i15-1/src/components/AbortPlanButton.tsx b/apps/i15-1/src/components/AbortPlanButton.tsx index 6e9446d1..9f1eb81f 100644 --- a/apps/i15-1/src/components/AbortPlanButton.tsx +++ b/apps/i15-1/src/components/AbortPlanButton.tsx @@ -54,7 +54,7 @@ export function AbortPlanButton() { open={openSnackbar} autoHideDuration={5000} onClose={handleSnackbarClose} - anchorOrigin={{ vertical: "bottom", horizontal: "right" }} + anchorOrigin={{ vertical: "bottom", horizontal: "left" }} > Abort button pressed, will abort current plan ... diff --git a/apps/i15-1/src/components/RunPlanButton.tsx b/apps/i15-1/src/components/RunPlanButton.tsx index b28f8f61..ab8bdb77 100644 --- a/apps/i15-1/src/components/RunPlanButton.tsx +++ b/apps/i15-1/src/components/RunPlanButton.tsx @@ -1,5 +1,11 @@ -import { Button } from "@mui/material"; -import { useState } from "react"; +import { + Alert, + Button, + Snackbar, + Tooltip, + type SnackbarCloseReason, +} from "@mui/material"; +import React, { useState } from "react"; import { useGetWorkerState, @@ -16,24 +22,36 @@ type RunPlanButtonProps = { buttonText?: string; }; +type SeverityLevel = "success" | "info" | "warning" | "error"; + const RunPlanButton = ({ name, params, instrumentSession, buttonText = "Run", }: RunPlanButtonProps) => { + const [openSnackbar, setOpenSnackbar] = useState(false); + const [msg, setMsg] = useState(`Running ${name} plan`); + const [severity, setSeverity] = useState("info"); + + const [loading, setLoading] = useState(false); + const user = useUserAuth(); const submitTask = useSubmitTask(); const startTask = useSetActiveTask(); + const submitAndRunTask = async (task: TaskRequest) => { - await submitTask - .mutateAsync(task) - .then((response) => startTask.mutateAsync(response.task_id)); + await submitTask.mutateAsync(task).then((response) => { + if (response) { + startTask.mutateAsync(response.task_id); + } else { + throw new Error("Task couldn't be submitted"); + } + }); }; - const [loading, setLoading] = useState(false); - const handleClick = async () => { + const runOnClick = async () => { const taskRequest: TaskRequest = { name: name, params: params, @@ -44,6 +62,28 @@ const RunPlanButton = ({ setLoading(false); }; + const handleClick = async () => { + setOpenSnackbar(true); + await runOnClick().catch((error) => { + setSeverity("error"); + setMsg( + `Failed to run plan ${name}, see console and blueapi logs for full error.`, + ); + console.log(`${msg}. Reason: ${error}`); + }); + }; + + const handleSnackbarClose = ( + _event: React.SyntheticEvent | Event, + reason?: SnackbarCloseReason, + ) => { + if (reason === "clickaway") { + return; + } + + setOpenSnackbar(false); + }; + const isButtonDisabled = () => { const workerState = useGetWorkerState(); const disable = @@ -54,15 +94,27 @@ const RunPlanButton = ({ }; return ( - + + + + + {msg} + + + ); }; From 85d7ab43be2169222116836da4eb3a6864b14d88 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Wed, 13 May 2026 15:36:20 +0000 Subject: [PATCH 09/40] Move snackbar back --- apps/i15-1/src/components/AbortPlanButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/i15-1/src/components/AbortPlanButton.tsx b/apps/i15-1/src/components/AbortPlanButton.tsx index 9f1eb81f..6e9446d1 100644 --- a/apps/i15-1/src/components/AbortPlanButton.tsx +++ b/apps/i15-1/src/components/AbortPlanButton.tsx @@ -54,7 +54,7 @@ export function AbortPlanButton() { open={openSnackbar} autoHideDuration={5000} onClose={handleSnackbarClose} - anchorOrigin={{ vertical: "bottom", horizontal: "left" }} + anchorOrigin={{ vertical: "bottom", horizontal: "right" }} > Abort button pressed, will abort current plan ... From b71754001af86826c431510e017bc6304f0a44c6 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Fri, 15 May 2026 17:02:52 +0000 Subject: [PATCH 10/40] Add more snackbar and try to mock --- apps/i15-1/src/components/RunPlanButton.tsx | 24 ++++++++++++++++----- apps/i15-1/src/mocks/handlers.ts | 13 +++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/apps/i15-1/src/components/RunPlanButton.tsx b/apps/i15-1/src/components/RunPlanButton.tsx index ab8bdb77..c3117cdc 100644 --- a/apps/i15-1/src/components/RunPlanButton.tsx +++ b/apps/i15-1/src/components/RunPlanButton.tsx @@ -11,8 +11,9 @@ import { useGetWorkerState, useSetActiveTask, useSubmitTask, + useTask, } from "@atlas/blueapi-query"; -import type { TaskRequest } from "@atlas/blueapi"; +import type { TaskRequest, TaskResponse } from "@atlas/blueapi"; import { useUserAuth } from "../context/userAuth/useUserAuth"; type RunPlanButtonProps = { @@ -41,7 +42,9 @@ const RunPlanButton = ({ const submitTask = useSubmitTask(); const startTask = useSetActiveTask(); - const submitAndRunTask = async (task: TaskRequest) => { + const submitAndRunTask = async ( + task: TaskRequest, + ): Promise => { await submitTask.mutateAsync(task).then((response) => { if (response) { startTask.mutateAsync(response.task_id); @@ -57,13 +60,23 @@ const RunPlanButton = ({ params: params, instrument_session: instrumentSession, }; - setLoading(true); - await submitAndRunTask(taskRequest); - setLoading(false); + await submitAndRunTask(taskRequest).then((response) => { + if (response) { + const { data } = useTask(response.task_id); + if (data?.is_complete && data.outcome?.outcome === "success") { + setSeverity("success"); + setMsg("Plan succeeded"); + } else { + setSeverity("error"); + setMsg(`Plan failed with error ${data?.errors[0]}`); // typing to use data.outcome.message needs fixing + } + } + }); }; const handleClick = async () => { setOpenSnackbar(true); + setLoading(true); await runOnClick().catch((error) => { setSeverity("error"); setMsg( @@ -71,6 +84,7 @@ const RunPlanButton = ({ ); console.log(`${msg}. Reason: ${error}`); }); + setLoading(false); }; const handleSnackbarClose = ( diff --git a/apps/i15-1/src/mocks/handlers.ts b/apps/i15-1/src/mocks/handlers.ts index 80434943..04ec4cee 100644 --- a/apps/i15-1/src/mocks/handlers.ts +++ b/apps/i15-1/src/mocks/handlers.ts @@ -23,6 +23,19 @@ export const handlers = [ }); }), + http.get<{ task_id: string }>("/api/tasks/:task_id", ({ taskId }) => { + const { task_id } = taskId; + return HttpResponse.json({ + task_id: task_id, + task: { name: "fake-task", params: {}, metadata: {} }, + request_id: "00", + is_complete: true, + is_pending: false, + errors: [""], + outcome: { outcome: "success", type: "str", result: null }, + }); + }), + http.put("/api/worker/state", async ({ request }) => { // @ts-ignore const { new_state } = await request.json(); From cb84630ff46a74668c4490cbd3b4f9814854505c Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Mon, 18 May 2026 15:50:28 +0000 Subject: [PATCH 11/40] Fix the snackbar - now just need to poll --- apps/i15-1/src/components/RunPlanButton.tsx | 47 ++++++++++++--------- apps/i15-1/src/mocks/handlers.ts | 7 ++- packages/blueapi-query/src/index.ts | 2 +- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/apps/i15-1/src/components/RunPlanButton.tsx b/apps/i15-1/src/components/RunPlanButton.tsx index c3117cdc..65c05719 100644 --- a/apps/i15-1/src/components/RunPlanButton.tsx +++ b/apps/i15-1/src/components/RunPlanButton.tsx @@ -8,6 +8,7 @@ import { import React, { useState } from "react"; import { + useBlueapi, useGetWorkerState, useSetActiveTask, useSubmitTask, @@ -39,36 +40,38 @@ const RunPlanButton = ({ const user = useUserAuth(); + const workerState = useGetWorkerState(); + const blueapi = useBlueapi(); + const submitTask = useSubmitTask(); const startTask = useSetActiveTask(); const submitAndRunTask = async ( task: TaskRequest, ): Promise => { - await submitTask.mutateAsync(task).then((response) => { + await submitTask.mutateAsync(task).then(async (response) => { if (response) { - startTask.mutateAsync(response.task_id); + await runTask(response.task_id).catch((error) => { + throw new Error(error); + }); } else { throw new Error("Task couldn't be submitted"); } }); }; - const runOnClick = async () => { - const taskRequest: TaskRequest = { - name: name, - params: params, - instrument_session: instrumentSession, - }; - await submitAndRunTask(taskRequest).then((response) => { + const runTask = async (task_id: string) => { + await startTask.mutateAsync(task_id).then(async (response) => { if (response) { - const { data } = useTask(response.task_id); - if (data?.is_complete && data.outcome?.outcome === "success") { - setSeverity("success"); - setMsg("Plan succeeded"); - } else { - setSeverity("error"); - setMsg(`Plan failed with error ${data?.errors[0]}`); // typing to use data.outcome.message needs fixing + // TODO This needs to poll not return right away! + const data = await blueapi.tasks.get(task_id); + if (data.is_complete) { + if (data.outcome?.outcome === "success") { + setSeverity("success"); + setMsg("Plan succeeded"); + } else if (data.outcome?.outcome === "error") { + throw new Error(`${data.errors[0]}`); + } } } }); @@ -77,12 +80,17 @@ const RunPlanButton = ({ const handleClick = async () => { setOpenSnackbar(true); setLoading(true); - await runOnClick().catch((error) => { + const taskRequest: TaskRequest = { + name: name, + params: params, + instrument_session: instrumentSession, + }; + await submitAndRunTask(taskRequest).catch((error) => { setSeverity("error"); setMsg( `Failed to run plan ${name}, see console and blueapi logs for full error.`, ); - console.log(`${msg}. Reason: ${error}`); + console.log(`${msg}.\n Reason: ${error}`); }); setLoading(false); }; @@ -99,7 +107,6 @@ const RunPlanButton = ({ }; const isButtonDisabled = () => { - const workerState = useGetWorkerState(); const disable = user.person == null || user.person == undefined || @@ -120,7 +127,7 @@ const RunPlanButton = ({ diff --git a/apps/i15-1/src/mocks/handlers.ts b/apps/i15-1/src/mocks/handlers.ts index 04ec4cee..e2c24dd2 100644 --- a/apps/i15-1/src/mocks/handlers.ts +++ b/apps/i15-1/src/mocks/handlers.ts @@ -23,15 +23,14 @@ export const handlers = [ }); }), - http.get<{ task_id: string }>("/api/tasks/:task_id", ({ taskId }) => { - const { task_id } = taskId; + http.get("/api/tasks/:task_id", () => { return HttpResponse.json({ - task_id: task_id, + task_id: fakeTaskId, task: { name: "fake-task", params: {}, metadata: {} }, request_id: "00", is_complete: true, is_pending: false, - errors: [""], + errors: [], outcome: { outcome: "success", type: "str", result: null }, }); }), diff --git a/packages/blueapi-query/src/index.ts b/packages/blueapi-query/src/index.ts index 3cd43be9..9cbdb5b5 100644 --- a/packages/blueapi-query/src/index.ts +++ b/packages/blueapi-query/src/index.ts @@ -3,4 +3,4 @@ export * from "./tasks"; export * from "./worker"; export * from "./devices"; -export { BlueapiProvider } from "./provider"; +export { BlueapiProvider, useBlueapi } from "./provider"; From 78a005fdfe0c8366b256a5a8409103fd2e99f0bc Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Tue, 19 May 2026 15:00:46 +0000 Subject: [PATCH 12/40] Try to see if it wait for sleep --- apps/i15-1/src/components/RunPlanButton.tsx | 33 +++++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/apps/i15-1/src/components/RunPlanButton.tsx b/apps/i15-1/src/components/RunPlanButton.tsx index 65c05719..f2900bb4 100644 --- a/apps/i15-1/src/components/RunPlanButton.tsx +++ b/apps/i15-1/src/components/RunPlanButton.tsx @@ -46,24 +46,17 @@ const RunPlanButton = ({ const submitTask = useSubmitTask(); const startTask = useSetActiveTask(); - const submitAndRunTask = async ( - task: TaskRequest, - ): Promise => { - await submitTask.mutateAsync(task).then(async (response) => { - if (response) { - await runTask(response.task_id).catch((error) => { - throw new Error(error); - }); - } else { - throw new Error("Task couldn't be submitted"); - } - }); + const waitForIdle = async (ms: number): Promise => { + return new Promise((res) => setTimeout(res, ms)); }; const runTask = async (task_id: string) => { await startTask.mutateAsync(task_id).then(async (response) => { if (response) { - // TODO This needs to poll not return right away! + const status = workerState.data; + while (status !== "IDLE" && status !== "ABORTING") { + await waitForIdle(10); + } const data = await blueapi.tasks.get(task_id); if (data.is_complete) { if (data.outcome?.outcome === "success") { @@ -77,6 +70,20 @@ const RunPlanButton = ({ }); }; + const submitAndRunTask = async ( + task: TaskRequest, + ): Promise => { + await submitTask.mutateAsync(task).then(async (response) => { + if (response) { + await runTask(response.task_id).catch((error) => { + throw new Error(error); + }); + } else { + throw new Error("Task couldn't be submitted"); + } + }); + }; + const handleClick = async () => { setOpenSnackbar(true); setLoading(true); From c5738a49edc34ce6e723fec76e5a613bb3ac92c9 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Wed, 20 May 2026 16:20:26 +0000 Subject: [PATCH 13/40] An attempt at polling --- apps/i15-1/src/components/RunPlanButton.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/i15-1/src/components/RunPlanButton.tsx b/apps/i15-1/src/components/RunPlanButton.tsx index f2900bb4..0e363d4f 100644 --- a/apps/i15-1/src/components/RunPlanButton.tsx +++ b/apps/i15-1/src/components/RunPlanButton.tsx @@ -53,9 +53,10 @@ const RunPlanButton = ({ const runTask = async (task_id: string) => { await startTask.mutateAsync(task_id).then(async (response) => { if (response) { - const status = workerState.data; + let status = workerState.data; while (status !== "IDLE" && status !== "ABORTING") { await waitForIdle(10); + status = workerState.data; } const data = await blueapi.tasks.get(task_id); if (data.is_complete) { From a2ede88e3abba1da23b0b64579cbbfed46c02d67 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Thu, 28 May 2026 08:30:59 +0000 Subject: [PATCH 14/40] Fix typing in test --- packages/blueapi-query/src/tasks.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/blueapi-query/src/tasks.test.ts b/packages/blueapi-query/src/tasks.test.ts index a4cea6c9..228a7406 100644 --- a/packages/blueapi-query/src/tasks.test.ts +++ b/packages/blueapi-query/src/tasks.test.ts @@ -55,6 +55,7 @@ describe("Task hooks", () => { is_pending: true, is_complete: false, errors: [], + outcome: { outcome: "success" }, }; (api.tasks.get as any).mockResolvedValue(task); From 7a61e6a631e37debeaa6af39ce4f550472693358 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Wed, 10 Jun 2026 09:09:51 +0000 Subject: [PATCH 15/40] Fix lint --- apps/i15-1/src/components/BlueapiWorkerState.tsx | 14 ++++++++++---- apps/i15-1/src/mocks/handlers.ts | 3 +-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/i15-1/src/components/BlueapiWorkerState.tsx b/apps/i15-1/src/components/BlueapiWorkerState.tsx index 1a12bdbc..cfc1901c 100644 --- a/apps/i15-1/src/components/BlueapiWorkerState.tsx +++ b/apps/i15-1/src/components/BlueapiWorkerState.tsx @@ -1,8 +1,14 @@ import { useGetWorkerState } from "@atlas/blueapi-query"; -import { Card, CardContent, Stack, Typography, useTheme } from "@mui/material"; +import { + Card, + CardContent, + Stack, + Typography, + useTheme, + type Theme, +} from "@mui/material"; -function getStateColorMap() { - const theme = useTheme(); +function getStateColorMap(theme: Theme) { return { IDLE: theme.palette.info.main, RUNNING: theme.palette.success.main, @@ -20,7 +26,7 @@ function getStateColorMap() { export function BlueapiWorkerState() { const theme = useTheme(); const workerState = useGetWorkerState(); - const stateMap = getStateColorMap(); + const stateMap = getStateColorMap(theme); return ( { - // @ts-ignore - const { new_state } = await request.json(); + const { new_state } = (await request.json()) as { new_state: string }; if (new_state === "ABORTING") { setWorkerState(new_state); } From 8a74ce652cd9579db6f50f6d6f0ac23ed03b25d9 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Wed, 10 Jun 2026 09:48:49 +0000 Subject: [PATCH 16/40] Fix typecheck --- packages/blueapi/src/tasks.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/blueapi/src/tasks.test.ts b/packages/blueapi/src/tasks.test.ts index 17ef09dc..02d93f63 100644 --- a/packages/blueapi/src/tasks.test.ts +++ b/packages/blueapi/src/tasks.test.ts @@ -45,6 +45,7 @@ describe("createTasksApi", () => { is_complete: false, is_pending: true, errors: [], + outcome: { outcome: "success" }, }, ], }; From 509366285ce6290f7a25c548987743531f6032be Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Wed, 10 Jun 2026 14:32:48 +0100 Subject: [PATCH 17/40] Rename parameter --- packages/blueapi-ui/src/RunPlanButton.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.tsx b/packages/blueapi-ui/src/RunPlanButton.tsx index 736552bd..14f3e63d 100644 --- a/packages/blueapi-ui/src/RunPlanButton.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.tsx @@ -41,8 +41,8 @@ export function RunPlanButton({ const submitTask = useSubmitTask(); const startTask = useSetActiveTask(); - const waitForIdle = async (ms: number): Promise => { - return new Promise((res) => setTimeout(res, ms)); + const waitForIdle = async (timeoutInMs: number): Promise => { + return new Promise((res) => setTimeout(res, timeoutInMs)); }; const runTask = async (task_id: string) => { From c21fba7674c4b67eb0b46785b4fa5a7b89f6971a Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Wed, 10 Jun 2026 15:24:18 +0100 Subject: [PATCH 18/40] Pull idle and aborting into constants --- packages/blueapi-ui/src/RunPlanButton.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.tsx b/packages/blueapi-ui/src/RunPlanButton.tsx index 14f3e63d..e3d60a64 100644 --- a/packages/blueapi-ui/src/RunPlanButton.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.tsx @@ -23,6 +23,9 @@ export type RunPlanButtonProps = { type SeverityLevel = "success" | "info" | "warning" | "error"; +const idleState = "IDLE"; +const abortState = "ABORTING"; + export function RunPlanButton({ name, params, @@ -49,7 +52,7 @@ export function RunPlanButton({ await startTask.mutateAsync(task_id).then(async (response) => { if (response) { let status = workerState.data; - while (status !== "IDLE" && status !== "ABORTING") { + while (status !== idleState && status !== abortState) { await waitForIdle(10); status = workerState.data; } @@ -111,7 +114,7 @@ export function RunPlanButton({ const isButtonDisabled = () => { const workerState = useGetWorkerState(); - const disable = workerState.data !== "IDLE"; + const disable = workerState.data !== idleState; return disable; }; From 80630f9787125f6b6ebc54d00e5dff723433bc2e Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Thu, 16 Jul 2026 09:43:52 +0000 Subject: [PATCH 19/40] Fix issues from bad merge in Robot --- apps/i15-1/src/routes/Robot.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/apps/i15-1/src/routes/Robot.tsx b/apps/i15-1/src/routes/Robot.tsx index 4f773b8c..1052fa0b 100644 --- a/apps/i15-1/src/routes/Robot.tsx +++ b/apps/i15-1/src/routes/Robot.tsx @@ -5,7 +5,6 @@ import { NumberInput } from "../components/NumberInput"; import { RunPlanButton } from "@atlas/blueapi-ui"; import { ReadOnlyPv } from "@atlas/pvws-config"; import { StatusCard } from "../components/StatusCard"; -import { BlueapiWorkerState } from "../components/BlueapiWorkerState"; // import { AbortPlanButton } from "../components/AbortPlanButton"; import { WebcamStreamFromPv } from "../components/Webcam"; @@ -18,19 +17,6 @@ function StatusSidebar() { const theme = useTheme(); return ( - - - - - - Date: Thu, 16 Jul 2026 09:54:46 +0000 Subject: [PATCH 20/40] Put the abort button back in --- apps/i15-1/src/routes/Robot.tsx | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/apps/i15-1/src/routes/Robot.tsx b/apps/i15-1/src/routes/Robot.tsx index 1052fa0b..ad0c04db 100644 --- a/apps/i15-1/src/routes/Robot.tsx +++ b/apps/i15-1/src/routes/Robot.tsx @@ -5,7 +5,7 @@ import { NumberInput } from "../components/NumberInput"; import { RunPlanButton } from "@atlas/blueapi-ui"; import { ReadOnlyPv } from "@atlas/pvws-config"; import { StatusCard } from "../components/StatusCard"; -// import { AbortPlanButton } from "../components/AbortPlanButton"; +import { AbortPlanButton } from "../components/AbortPlanButton"; import { WebcamStreamFromPv } from "../components/Webcam"; type RobotSampleFormData = { @@ -97,17 +97,20 @@ function RobotControl() { }} /> - - + + + + + ); From 77b99cf04e636f9d56fc535ef716aed9272a3567 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Thu, 16 Jul 2026 10:04:08 +0000 Subject: [PATCH 21/40] Add more snackbars --- packages/blueapi-ui/src/RunPlanButton.tsx | 31 ++++------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.tsx b/packages/blueapi-ui/src/RunPlanButton.tsx index 1b68320c..85be059b 100644 --- a/packages/blueapi-ui/src/RunPlanButton.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.tsx @@ -74,19 +74,19 @@ export function RunPlanButton({ ): Promise => { await submitTask.mutateAsync(task).then(async (response) => { if (response) { + setSeverity("info"); + setMsg("Plan submission successful!"); await runTask(response.task_id).catch((error) => { throw new Error(error); }); } else { + setSeverity("error"); + setMsg("Plan submission failed!"); throw new Error("Task couldn't be submitted"); } }); }; - const [planSubmitted, setPlanSubmitted] = useState(false); - const [planSubmissionResult, setPlanSubmissionResult] = useState< - boolean | null - >(null); const handleClick = async () => { setOpenSnackbar(true); setLoading(true); @@ -114,28 +114,7 @@ export function RunPlanButton({ } setOpenSnackbar(false); - setPlanSubmitted(false); - setPlanSubmissionResult(null); }; - // MERGE AND THEN FIGURE OUT HOW TO HANDLE BOTH - // setLoading(true); - // try { - // await submitAndRunTask(taskRequest); - // setPlanSubmissionResult(true); - // setPlanSubmitted(true); - // } catch (error) { - // setPlanSubmissionResult(false); - // setPlanSubmitted(true); - // } finally { - // setLoading(false); - // } - // }; - - // const snackbarMessage = planSubmissionResult - // ? "Plan submission successful!" - // : planSubmissionResult === false - // ? "Plan submission failed!" - // : "Plan submission state unknown!"; const isButtonDisabled = () => { const workerState = useGetWorkerState(); @@ -156,7 +135,7 @@ export function RunPlanButton({ {buttonText} Date: Thu, 16 Jul 2026 14:12:35 +0000 Subject: [PATCH 22/40] Put the worker state back in --- .../src/components/BlueapiWorkerState.tsx | 14 +++++- apps/i15-1/src/routes/Robot.tsx | 2 + .../blueapi-ui/src/RunPlanButton.test.tsx | 47 +++++++++++++++---- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/apps/i15-1/src/components/BlueapiWorkerState.tsx b/apps/i15-1/src/components/BlueapiWorkerState.tsx index cfc1901c..9433ed44 100644 --- a/apps/i15-1/src/components/BlueapiWorkerState.tsx +++ b/apps/i15-1/src/components/BlueapiWorkerState.tsx @@ -33,16 +33,26 @@ export function BlueapiWorkerState() { variant="outlined" sx={{ minWidth: 250, - maxHeight: 100, + maxHeight: 200, bgcolor: theme.palette.background.paper, borderColor: theme.palette.text.primary, }} > - Blueapi worker state: + Blueapi worker state:{" "} + + {workerState.data} diff --git a/apps/i15-1/src/routes/Robot.tsx b/apps/i15-1/src/routes/Robot.tsx index ad0c04db..19f862f7 100644 --- a/apps/i15-1/src/routes/Robot.tsx +++ b/apps/i15-1/src/routes/Robot.tsx @@ -7,6 +7,7 @@ import { ReadOnlyPv } from "@atlas/pvws-config"; import { StatusCard } from "../components/StatusCard"; import { AbortPlanButton } from "../components/AbortPlanButton"; import { WebcamStreamFromPv } from "../components/Webcam"; +import { BlueapiWorkerState } from "../components/BlueapiWorkerState"; type RobotSampleFormData = { puck: number; @@ -54,6 +55,7 @@ function StatusSidebar() { pv="ca://BL15J-EA-LOC-01:SAMPLE:INDEX" /> + ); diff --git a/packages/blueapi-ui/src/RunPlanButton.test.tsx b/packages/blueapi-ui/src/RunPlanButton.test.tsx index 73285d6a..9486bbe6 100644 --- a/packages/blueapi-ui/src/RunPlanButton.test.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.test.tsx @@ -1,19 +1,43 @@ import { render, screen } from "@atlas/vitest-conf"; import { RunPlanButton } from "./RunPlanButton"; -import { useGetWorkerState, useSubmitTask } from "@atlas/blueapi-query"; +import { + useGetWorkerState, + useSubmitTask, + useSetActiveTask, + useBlueapi, +} from "@atlas/blueapi-query"; import type { Api, TaskResponse } from "@atlas/blueapi"; -vi.mock("@atlas/blueapi-query"); -const workerStateMock = vi.mocked(useGetWorkerState); -const submitTaskMock = vi.mocked(useSubmitTask); +// Mocks with starting return values const mockResponse: TaskResponse = { task_id: "92e6a0c3-52ff-4161-84ec-73096697e571", }; +const workerStateMock = vi.fn(() => ({ data: "IDLE" })); +const submitTaskMock = { + mutateAsync: vi.fn(() => Promise.resolve(mockResponse)), +}; +const setActiveTaskMock = { mutateAsync: vi.fn(() => Promise.resolve()) }; + +vi.mock("@atlas/blueapi-query", () => ({ + useGetWorkerState: () => workerStateMock(), + useSubmitTask: () => submitTaskMock, + useSetActiveTask: () => setActiveTaskMock, + useBlueapi: vi.fn(), + // useBlueapi: { tasks: { get: vi.fn() } } as unknown as Api, +})); describe("RunPlanButton", () => { + beforeEach(() => { + vi.clearAllMocks(); + workerStateMock.mockReset(); + workerStateMock.mockReturnValue({ data: "IDLE" }); + submitTaskMock.mutateAsync.mockClear(); + setActiveTaskMock.mutateAsync.mockClear(); + }); + it("renders default button with Run", () => { workerStateMock.mockReturnValue({ data: "IDLE" } as any); - submitTaskMock.mockReturnValue({ data: mockResponse } as any); + submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); render( { it("renders button with custom text", () => { workerStateMock.mockReturnValue({ data: "IDLE" } as any); - submitTaskMock.mockReturnValue({ data: mockResponse } as any); + submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); render( { it("success message appears when button is pressed with successful response", () => { workerStateMock.mockReturnValue({ data: "IDLE" } as any); - submitTaskMock.mockReturnValue({ data: mockResponse } as any); + submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); render( { }); it("failure message appears when button is pressed with failed response", () => { - submitTaskMock.mockRejectedValue; + submitTaskMock.mutateAsync.mockRejectedValue; render( { ); screen.getByText("Run").click(); expect(screen.findByTestId("Plan submission failed!")); + expect( + screen.findByText( + "Failed to run plan test_plan, see console and blueapi logs for full error.", + ), + ); }); + + it("Plan submission succeeds but plan fails during execution", () => {}); }); From b9e529f8d8149a7e4c54c521c6bef9b691c27997 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Thu, 16 Jul 2026 15:16:56 +0000 Subject: [PATCH 23/40] Get the polling to work --- packages/blueapi-ui/src/RunPlanButton.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.tsx b/packages/blueapi-ui/src/RunPlanButton.tsx index 85be059b..3c5e1d7d 100644 --- a/packages/blueapi-ui/src/RunPlanButton.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.tsx @@ -38,7 +38,6 @@ export function RunPlanButton({ const [loading, setLoading] = useState(false); - const workerState = useGetWorkerState(); const blueapi = useBlueapi(); const submitTask = useSubmitTask(); @@ -51,12 +50,15 @@ export function RunPlanButton({ const runTask = async (task_id: string) => { await startTask.mutateAsync(task_id).then(async (response) => { if (response) { - let status = workerState.data; + console.log("Running plan"); + let status = await blueapi.worker.getState(); while (status !== idleState && status !== abortState) { - await waitForIdle(10); - status = workerState.data; + console.log(`status: ${status}`); + await waitForIdle(100); + status = await blueapi.worker.getState(); } const data = await blueapi.tasks.get(task_id); + console.log(data); if (data.is_complete) { if (data.outcome?.outcome === "success") { setSeverity("success"); @@ -74,6 +76,7 @@ export function RunPlanButton({ ): Promise => { await submitTask.mutateAsync(task).then(async (response) => { if (response) { + console.log("Plan successfully submitted"); setSeverity("info"); setMsg("Plan submission successful!"); await runTask(response.task_id).catch((error) => { From 59a9248208636352eb302facf21a355034cb511d Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Thu, 16 Jul 2026 15:20:14 +0000 Subject: [PATCH 24/40] Fix the polling --- packages/blueapi-ui/src/RunPlanButton.test.tsx | 1 + packages/blueapi-ui/src/RunPlanButton.tsx | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.test.tsx b/packages/blueapi-ui/src/RunPlanButton.test.tsx index 9486bbe6..eda17f5c 100644 --- a/packages/blueapi-ui/src/RunPlanButton.test.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.test.tsx @@ -88,6 +88,7 @@ describe("RunPlanButton", () => { ); screen.getByText("Run").click(); expect(screen.findByText("Plan submission successful!")); + expect(screen.findByText("Running plan!")); }); it("failure message appears when button is pressed with failed response", () => { diff --git a/packages/blueapi-ui/src/RunPlanButton.tsx b/packages/blueapi-ui/src/RunPlanButton.tsx index 3c5e1d7d..482c90a7 100644 --- a/packages/blueapi-ui/src/RunPlanButton.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.tsx @@ -50,7 +50,8 @@ export function RunPlanButton({ const runTask = async (task_id: string) => { await startTask.mutateAsync(task_id).then(async (response) => { if (response) { - console.log("Running plan"); + setSeverity("info"); + setMsg("Running plan!"); let status = await blueapi.worker.getState(); while (status !== idleState && status !== abortState) { console.log(`status: ${status}`); @@ -76,9 +77,8 @@ export function RunPlanButton({ ): Promise => { await submitTask.mutateAsync(task).then(async (response) => { if (response) { - console.log("Plan successfully submitted"); setSeverity("info"); - setMsg("Plan submission successful!"); + setMsg("Running plan!"); await runTask(response.task_id).catch((error) => { throw new Error(error); }); From a373950038ec97a497d60478b58402fd7e6fc975 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Thu, 16 Jul 2026 15:52:48 +0000 Subject: [PATCH 25/40] Get mock for useBlueapi to work and add a test to check it --- .../blueapi-ui/src/RunPlanButton.test.tsx | 41 ++++++++++++++++--- packages/blueapi-ui/src/RunPlanButton.tsx | 2 +- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.test.tsx b/packages/blueapi-ui/src/RunPlanButton.test.tsx index eda17f5c..bf274f7d 100644 --- a/packages/blueapi-ui/src/RunPlanButton.test.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.test.tsx @@ -6,7 +6,7 @@ import { useSetActiveTask, useBlueapi, } from "@atlas/blueapi-query"; -import type { Api, TaskResponse } from "@atlas/blueapi"; +import type { Api, TaskResponse, TrackableTask } from "@atlas/blueapi"; // Mocks with starting return values const mockResponse: TaskResponse = { @@ -18,12 +18,26 @@ const submitTaskMock = { }; const setActiveTaskMock = { mutateAsync: vi.fn(() => Promise.resolve()) }; +const mockTask: TrackableTask = { + task_id: "92e6a0c3-52ff-4161-84ec-73096697e571", + task: { name: "test_plan", params: {}, metadata: {} }, + request_id: null, + is_complete: true, + is_pending: false, + errors: [], + outcome: { outcome: "success" }, +}; + +const api = { + worker: { get: vi.fn(() => Promise.resolve("IDLE")) }, + tasks: { get: vi.fn(() => Promise.resolve(mockTask)) }, +} as unknown as Api; + vi.mock("@atlas/blueapi-query", () => ({ useGetWorkerState: () => workerStateMock(), useSubmitTask: () => submitTaskMock, useSetActiveTask: () => setActiveTaskMock, - useBlueapi: vi.fn(), - // useBlueapi: { tasks: { get: vi.fn() } } as unknown as Api, + useBlueapi: () => api, })); describe("RunPlanButton", () => { @@ -101,7 +115,7 @@ describe("RunPlanButton", () => { />, ); screen.getByText("Run").click(); - expect(screen.findByTestId("Plan submission failed!")); + expect(screen.findByText("Plan submission failed!")); expect( screen.findByText( "Failed to run plan test_plan, see console and blueapi logs for full error.", @@ -109,5 +123,22 @@ describe("RunPlanButton", () => { ); }); - it("Plan submission succeeds but plan fails during execution", () => {}); + it("Plan submission succeeds but plan fails during execution", () => { + submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); + setActiveTaskMock.mutateAsync.mockRejectedValue; + render( + , + ); + screen.getByText("Run").click(); + expect(screen.findByText("Plan submission successful!")); + expect( + screen.findByText( + "Failed to run plan test_plan, see console and blueapi logs for full error.", + ), + ); + }); }); diff --git a/packages/blueapi-ui/src/RunPlanButton.tsx b/packages/blueapi-ui/src/RunPlanButton.tsx index 482c90a7..a1cebd53 100644 --- a/packages/blueapi-ui/src/RunPlanButton.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.tsx @@ -78,7 +78,7 @@ export function RunPlanButton({ await submitTask.mutateAsync(task).then(async (response) => { if (response) { setSeverity("info"); - setMsg("Running plan!"); + setMsg("Plan submission successful!"); await runTask(response.task_id).catch((error) => { throw new Error(error); }); From 83cc6cbaa625e90e036bcb378d51d99c5c8daa61 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Thu, 16 Jul 2026 16:06:40 +0000 Subject: [PATCH 26/40] One more test --- .../blueapi-ui/src/RunPlanButton.test.tsx | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.test.tsx b/packages/blueapi-ui/src/RunPlanButton.test.tsx index bf274f7d..bfb8c805 100644 --- a/packages/blueapi-ui/src/RunPlanButton.test.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.test.tsx @@ -105,7 +105,7 @@ describe("RunPlanButton", () => { expect(screen.findByText("Running plan!")); }); - it("failure message appears when button is pressed with failed response", () => { + it("failure message appears when button is pressed with failed response", async () => { submitTaskMock.mutateAsync.mockRejectedValue; render( { "Failed to run plan test_plan, see console and blueapi logs for full error.", ), ); + const alert = await screen.findByRole("alert"); + expect(alert).toBeVisible(); }); - it("Plan submission succeeds but plan fails during execution", () => { + it("Plan submission succeeds but plan fails on run", async () => { submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); setActiveTaskMock.mutateAsync.mockRejectedValue; render( @@ -140,5 +142,40 @@ describe("RunPlanButton", () => { "Failed to run plan test_plan, see console and blueapi logs for full error.", ), ); + const alert = await screen.findByRole("alert"); + expect(alert).toBeVisible(); + }); + + it("Plan submission succeeds but plan has errors during execution", async () => { + const mockFailedTask: TrackableTask = { + task_id: "92e6a0c3-52ff-4161-84ec-73096697e571", + task: { name: "test_plan", params: {}, metadata: {} }, + request_id: null, + is_complete: true, + is_pending: false, + errors: ["Some error"], + outcome: { outcome: "error" }, + }; + + submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); + setActiveTaskMock.mutateAsync.mockReturnValue({ + data: mockFailedTask, + } as any); + render( + , + ); + screen.getByText("Run").click(); + expect(screen.findByText("Plan submission successful!")); + expect( + screen.findByText( + "Failed to run plan test_plan, see console and blueapi logs for full error.", + ), + ); + const alert = await screen.findByRole("alert"); + expect(alert).toBeVisible(); }); }); From 94f04cff17232b46902a9b86b6ccfc7f298d7b3a Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Thu, 16 Jul 2026 16:08:27 +0000 Subject: [PATCH 27/40] Remove some extra comments --- packages/blueapi-ui/src/RunPlanButton.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.tsx b/packages/blueapi-ui/src/RunPlanButton.tsx index a1cebd53..e812a631 100644 --- a/packages/blueapi-ui/src/RunPlanButton.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.tsx @@ -50,16 +50,12 @@ export function RunPlanButton({ const runTask = async (task_id: string) => { await startTask.mutateAsync(task_id).then(async (response) => { if (response) { - setSeverity("info"); - setMsg("Running plan!"); let status = await blueapi.worker.getState(); while (status !== idleState && status !== abortState) { - console.log(`status: ${status}`); await waitForIdle(100); status = await blueapi.worker.getState(); } const data = await blueapi.tasks.get(task_id); - console.log(data); if (data.is_complete) { if (data.outcome?.outcome === "success") { setSeverity("success"); From 0ffbf99c1a20efb4f18124324acf7ab9f0e38ee8 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Thu, 16 Jul 2026 16:09:33 +0000 Subject: [PATCH 28/40] And maybe remember to update test --- packages/blueapi-ui/src/RunPlanButton.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.test.tsx b/packages/blueapi-ui/src/RunPlanButton.test.tsx index bfb8c805..03ba7a2e 100644 --- a/packages/blueapi-ui/src/RunPlanButton.test.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.test.tsx @@ -102,7 +102,7 @@ describe("RunPlanButton", () => { ); screen.getByText("Run").click(); expect(screen.findByText("Plan submission successful!")); - expect(screen.findByText("Running plan!")); + expect(screen.findByText("Plan succeeded")); }); it("failure message appears when button is pressed with failed response", async () => { From afd4c2ed7cc94362ed5efde1f6338ea1b3e8f86b Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Thu, 16 Jul 2026 16:16:10 +0000 Subject: [PATCH 29/40] Use correct utility --- packages/blueapi-ui/src/RunPlanButton.test.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.test.tsx b/packages/blueapi-ui/src/RunPlanButton.test.tsx index 03ba7a2e..9a1634b5 100644 --- a/packages/blueapi-ui/src/RunPlanButton.test.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.test.tsx @@ -101,8 +101,8 @@ describe("RunPlanButton", () => { />, ); screen.getByText("Run").click(); - expect(screen.findByText("Plan submission successful!")); - expect(screen.findByText("Plan succeeded")); + expect(screen.findByTestId("Plan submission successful!")); + expect(screen.findByTestId("Plan succeeded")); }); it("failure message appears when button is pressed with failed response", async () => { @@ -115,9 +115,9 @@ describe("RunPlanButton", () => { />, ); screen.getByText("Run").click(); - expect(screen.findByText("Plan submission failed!")); + expect(screen.findByTestId("Plan submission failed!")); expect( - screen.findByText( + screen.findByTestId( "Failed to run plan test_plan, see console and blueapi logs for full error.", ), ); @@ -136,9 +136,9 @@ describe("RunPlanButton", () => { />, ); screen.getByText("Run").click(); - expect(screen.findByText("Plan submission successful!")); + expect(screen.findByTestId("Plan submission successful!")); expect( - screen.findByText( + screen.findByTestId( "Failed to run plan test_plan, see console and blueapi logs for full error.", ), ); @@ -169,9 +169,9 @@ describe("RunPlanButton", () => { />, ); screen.getByText("Run").click(); - expect(screen.findByText("Plan submission successful!")); + expect(screen.findByTestId("Plan submission successful!")); expect( - screen.findByText( + screen.findByTestId( "Failed to run plan test_plan, see console and blueapi logs for full error.", ), ); From 3925bbda9b11490fed698a54304e1548dc4e63f6 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Thu, 16 Jul 2026 17:13:13 +0000 Subject: [PATCH 30/40] Use act for rendering --- .../blueapi-ui/src/RunPlanButton.test.tsx | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.test.tsx b/packages/blueapi-ui/src/RunPlanButton.test.tsx index 9a1634b5..fe866a98 100644 --- a/packages/blueapi-ui/src/RunPlanButton.test.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from "@atlas/vitest-conf"; +import { act, render, screen } from "@atlas/vitest-conf"; import { RunPlanButton } from "./RunPlanButton"; import { useGetWorkerState, @@ -90,15 +90,20 @@ describe("RunPlanButton", () => { expect(screen.getByText("Run")).toBeDisabled(); }); - it("success message appears when button is pressed with successful response", () => { + it("success message appears when button is pressed with successful response", async () => { workerStateMock.mockReturnValue({ data: "IDLE" } as any); submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); - render( - , + setActiveTaskMock.mutateAsync.mockReturnValue({ + data: mockTask, + } as any); + await act(async () => + render( + , + ), ); screen.getByText("Run").click(); expect(screen.findByTestId("Plan submission successful!")); From 9975ef5ceeaff7854ce3277b144f3eedd1f77da7 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Thu, 16 Jul 2026 17:21:32 +0000 Subject: [PATCH 31/40] Try to fix it --- .../blueapi-ui/src/RunPlanButton.test.tsx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.test.tsx b/packages/blueapi-ui/src/RunPlanButton.test.tsx index fe866a98..3bf1b5da 100644 --- a/packages/blueapi-ui/src/RunPlanButton.test.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.test.tsx @@ -16,7 +16,9 @@ const workerStateMock = vi.fn(() => ({ data: "IDLE" })); const submitTaskMock = { mutateAsync: vi.fn(() => Promise.resolve(mockResponse)), }; -const setActiveTaskMock = { mutateAsync: vi.fn(() => Promise.resolve()) }; +const setActiveTaskMock = { + mutateAsync: vi.fn(() => Promise.resolve(mockResponse)), +}; const mockTask: TrackableTask = { task_id: "92e6a0c3-52ff-4161-84ec-73096697e571", @@ -90,22 +92,21 @@ describe("RunPlanButton", () => { expect(screen.getByText("Run")).toBeDisabled(); }); - it("success message appears when button is pressed with successful response", async () => { + it("success message appears when button is pressed with successful response", () => { workerStateMock.mockReturnValue({ data: "IDLE" } as any); submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); setActiveTaskMock.mutateAsync.mockReturnValue({ data: mockTask, } as any); - await act(async () => - render( - , - ), - ); - screen.getByText("Run").click(); + (render( + , + ), + screen.getByText("Run").click()); + // Error here expect(screen.findByTestId("Plan submission successful!")); expect(screen.findByTestId("Plan succeeded")); }); @@ -120,6 +121,7 @@ describe("RunPlanButton", () => { />, ); screen.getByText("Run").click(); + // Error here expect(screen.findByTestId("Plan submission failed!")); expect( screen.findByTestId( From 89eccf69c8fc157a3db94de6bad84875b0a906cf Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Thu, 16 Jul 2026 17:24:37 +0000 Subject: [PATCH 32/40] Check if other tests actually pass --- .../blueapi-ui/src/RunPlanButton.test.tsx | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.test.tsx b/packages/blueapi-ui/src/RunPlanButton.test.tsx index 3bf1b5da..c0c6bb69 100644 --- a/packages/blueapi-ui/src/RunPlanButton.test.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.test.tsx @@ -92,45 +92,45 @@ describe("RunPlanButton", () => { expect(screen.getByText("Run")).toBeDisabled(); }); - it("success message appears when button is pressed with successful response", () => { - workerStateMock.mockReturnValue({ data: "IDLE" } as any); - submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); - setActiveTaskMock.mutateAsync.mockReturnValue({ - data: mockTask, - } as any); - (render( - , - ), - screen.getByText("Run").click()); - // Error here - expect(screen.findByTestId("Plan submission successful!")); - expect(screen.findByTestId("Plan succeeded")); - }); + // it("success message appears when button is pressed with successful response", () => { + // workerStateMock.mockReturnValue({ data: "IDLE" } as any); + // submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); + // setActiveTaskMock.mutateAsync.mockReturnValue({ + // data: mockTask, + // } as any); + // (render( + // , + // ), + // screen.getByText("Run").click()); + // // Error here + // expect(screen.findByTestId("Plan submission successful!")); + // expect(screen.findByTestId("Plan succeeded")); + // }); - it("failure message appears when button is pressed with failed response", async () => { - submitTaskMock.mutateAsync.mockRejectedValue; - render( - , - ); - screen.getByText("Run").click(); - // Error here - expect(screen.findByTestId("Plan submission failed!")); - expect( - screen.findByTestId( - "Failed to run plan test_plan, see console and blueapi logs for full error.", - ), - ); - const alert = await screen.findByRole("alert"); - expect(alert).toBeVisible(); - }); + // it("failure message appears when button is pressed with failed response", async () => { + // submitTaskMock.mutateAsync.mockRejectedValue; + // render( + // , + // ); + // screen.getByText("Run").click(); + // // Error here + // expect(screen.findByTestId("Plan submission failed!")); + // expect( + // screen.findByTestId( + // "Failed to run plan test_plan, see console and blueapi logs for full error.", + // ), + // ); + // const alert = await screen.findByRole("alert"); + // expect(alert).toBeVisible(); + // }); it("Plan submission succeeds but plan fails on run", async () => { submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); From 4c0658a273ba04c43fe6ea2397d438449ab046a5 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Thu, 16 Jul 2026 17:26:06 +0000 Subject: [PATCH 33/40] Check if other tests actually pass - part 2 --- .../blueapi-ui/src/RunPlanButton.test.tsx | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.test.tsx b/packages/blueapi-ui/src/RunPlanButton.test.tsx index c0c6bb69..297b9904 100644 --- a/packages/blueapi-ui/src/RunPlanButton.test.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.test.tsx @@ -132,26 +132,26 @@ describe("RunPlanButton", () => { // expect(alert).toBeVisible(); // }); - it("Plan submission succeeds but plan fails on run", async () => { - submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); - setActiveTaskMock.mutateAsync.mockRejectedValue; - render( - , - ); - screen.getByText("Run").click(); - expect(screen.findByTestId("Plan submission successful!")); - expect( - screen.findByTestId( - "Failed to run plan test_plan, see console and blueapi logs for full error.", - ), - ); - const alert = await screen.findByRole("alert"); - expect(alert).toBeVisible(); - }); + // it("Plan submission succeeds but plan fails on run", async () => { + // submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); + // setActiveTaskMock.mutateAsync.mockRejectedValue; + // render( + // , + // ); + // screen.getByText("Run").click(); + // expect(screen.findByTestId("Plan submission successful!")); + // expect( + // screen.findByTestId( + // "Failed to run plan test_plan, see console and blueapi logs for full error.", + // ), + // ); + // const alert = await screen.findByRole("alert"); + // expect(alert).toBeVisible(); + // }); it("Plan submission succeeds but plan has errors during execution", async () => { const mockFailedTask: TrackableTask = { From 0d8027be7b232507c062c65cae527df315e3d184 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Fri, 17 Jul 2026 09:43:44 +0000 Subject: [PATCH 34/40] Try to fix broken tests one by one --- .../blueapi-ui/src/RunPlanButton.test.tsx | 63 +++++++------------ 1 file changed, 22 insertions(+), 41 deletions(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.test.tsx b/packages/blueapi-ui/src/RunPlanButton.test.tsx index 297b9904..293c7352 100644 --- a/packages/blueapi-ui/src/RunPlanButton.test.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.test.tsx @@ -111,48 +111,29 @@ describe("RunPlanButton", () => { // expect(screen.findByTestId("Plan succeeded")); // }); - // it("failure message appears when button is pressed with failed response", async () => { - // submitTaskMock.mutateAsync.mockRejectedValue; - // render( - // , - // ); - // screen.getByText("Run").click(); - // // Error here - // expect(screen.findByTestId("Plan submission failed!")); - // expect( - // screen.findByTestId( - // "Failed to run plan test_plan, see console and blueapi logs for full error.", - // ), - // ); - // const alert = await screen.findByRole("alert"); - // expect(alert).toBeVisible(); - // }); - - // it("Plan submission succeeds but plan fails on run", async () => { - // submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); - // setActiveTaskMock.mutateAsync.mockRejectedValue; - // render( - // , - // ); - // screen.getByText("Run").click(); - // expect(screen.findByTestId("Plan submission successful!")); - // expect( - // screen.findByTestId( - // "Failed to run plan test_plan, see console and blueapi logs for full error.", - // ), - // ); - // const alert = await screen.findByRole("alert"); - // expect(alert).toBeVisible(); - // }); + it("failure message appears when button is pressed with failed response", async () => { + submitTaskMock.mutateAsync.mockRejectedValue; + setActiveTaskMock.mutateAsync.mockRejectedValue; + render( + , + ); + screen.getByText("Run").click(); + // Error here + expect(screen.findByTestId("Plan submission failed!")); + expect( + screen.findByTestId( + "Failed to run plan test_plan, see console and blueapi logs for full error.", + ), + ); + const alert = await screen.findByRole("alert"); + expect(alert).toBeVisible(); + }); + // This looks like the only one without errors, so will start again from this one! it("Plan submission succeeds but plan has errors during execution", async () => { const mockFailedTask: TrackableTask = { task_id: "92e6a0c3-52ff-4161-84ec-73096697e571", From b3880e99307fe7c518e5f0e1e97d961af6641eb6 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Fri, 17 Jul 2026 09:46:31 +0000 Subject: [PATCH 35/40] Try to fix broken tests one by one - maybe mocking an actual missing response as it's not a rejection --- packages/blueapi-ui/src/RunPlanButton.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.test.tsx b/packages/blueapi-ui/src/RunPlanButton.test.tsx index 293c7352..1debd5f7 100644 --- a/packages/blueapi-ui/src/RunPlanButton.test.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.test.tsx @@ -112,7 +112,7 @@ describe("RunPlanButton", () => { // }); it("failure message appears when button is pressed with failed response", async () => { - submitTaskMock.mutateAsync.mockRejectedValue; + submitTaskMock.mutateAsync.mockReturnValue({ data: null } as any); setActiveTaskMock.mutateAsync.mockRejectedValue; render( Date: Fri, 17 Jul 2026 09:48:56 +0000 Subject: [PATCH 36/40] See happy test --- .../blueapi-ui/src/RunPlanButton.test.tsx | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.test.tsx b/packages/blueapi-ui/src/RunPlanButton.test.tsx index 1debd5f7..2a7ddd45 100644 --- a/packages/blueapi-ui/src/RunPlanButton.test.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.test.tsx @@ -92,24 +92,24 @@ describe("RunPlanButton", () => { expect(screen.getByText("Run")).toBeDisabled(); }); - // it("success message appears when button is pressed with successful response", () => { - // workerStateMock.mockReturnValue({ data: "IDLE" } as any); - // submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); - // setActiveTaskMock.mutateAsync.mockReturnValue({ - // data: mockTask, - // } as any); - // (render( - // , - // ), - // screen.getByText("Run").click()); - // // Error here - // expect(screen.findByTestId("Plan submission successful!")); - // expect(screen.findByTestId("Plan succeeded")); - // }); + it("success message appears when button is pressed with successful response", () => { + workerStateMock.mockReturnValue({ data: "IDLE" } as any); + submitTaskMock.mutateAsync.mockReturnValue({ data: mockResponse } as any); + setActiveTaskMock.mutateAsync.mockReturnValue({ + data: mockTask, + } as any); + (render( + , + ), + screen.getByText("Run").click()); + // Error here + expect(screen.findByTestId("Plan submission successful!")); + expect(screen.findByTestId("Plan succeeded")); + }); it("failure message appears when button is pressed with failed response", async () => { submitTaskMock.mutateAsync.mockReturnValue({ data: null } as any); @@ -122,7 +122,6 @@ describe("RunPlanButton", () => { />, ); screen.getByText("Run").click(); - // Error here expect(screen.findByTestId("Plan submission failed!")); expect( screen.findByTestId( @@ -133,7 +132,6 @@ describe("RunPlanButton", () => { expect(alert).toBeVisible(); }); - // This looks like the only one without errors, so will start again from this one! it("Plan submission succeeds but plan has errors during execution", async () => { const mockFailedTask: TrackableTask = { task_id: "92e6a0c3-52ff-4161-84ec-73096697e571", From a2eb7afb59ce4de8a21b3a8ebfaa0eef6250ea03 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Fri, 17 Jul 2026 09:52:15 +0000 Subject: [PATCH 37/40] Tidy up --- packages/blueapi-ui/src/RunPlanButton.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/blueapi-ui/src/RunPlanButton.test.tsx b/packages/blueapi-ui/src/RunPlanButton.test.tsx index 2a7ddd45..bd730643 100644 --- a/packages/blueapi-ui/src/RunPlanButton.test.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.test.tsx @@ -106,7 +106,6 @@ describe("RunPlanButton", () => { />, ), screen.getByText("Run").click()); - // Error here expect(screen.findByTestId("Plan submission successful!")); expect(screen.findByTestId("Plan succeeded")); }); From d94080bf578724f10d6b1bf98124288bc5f95507 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Fri, 17 Jul 2026 14:18:31 +0000 Subject: [PATCH 38/40] Add tests for abort button and tidy up --- .../src/components/AbortPlanButton.test.tsx | 37 +++++++++++++++++++ .../blueapi-ui/src/RunPlanButton.test.tsx | 8 +--- 2 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 apps/i15-1/src/components/AbortPlanButton.test.tsx diff --git a/apps/i15-1/src/components/AbortPlanButton.test.tsx b/apps/i15-1/src/components/AbortPlanButton.test.tsx new file mode 100644 index 00000000..69d47abd --- /dev/null +++ b/apps/i15-1/src/components/AbortPlanButton.test.tsx @@ -0,0 +1,37 @@ +import { render, screen, userEvent } from "@atlas/vitest-conf"; +import { AbortPlanButton } from "./AbortPlanButton"; +import { useSetWorkerState } from "@atlas/blueapi-query"; +import type { WorkerStateRequest } from "@atlas/blueapi"; + +describe("AbortPlanButton", () => { + vi.mock("@atlas/blueapi-query"); + const mockedHook = vi.mocked(useSetWorkerState); + + const mutate = vi.fn(); + + mockedHook.mockReturnValue({ mutate } as any); + + it("renders default Abort button", () => { + render(); + + expect(screen.getByText("Abort")); + }); + + it("when abort clicked the worker state changes and alert comes on screen", async () => { + const expectedRequest: WorkerStateRequest = { + new_state: "ABORTING", + reason: "Abort button pressed", + }; + const user = userEvent.setup(); + render(); + + const button = screen.getByText("Abort"); + await user.click(button); + expect(mutate).toHaveBeenCalledWith(expectedRequest); + expect( + screen.findByTestId("Abort button pressed, will abort current plan ..."), + ); + const alert = await screen.findByRole("alert"); + expect(alert).toBeVisible(); + }); +}); diff --git a/packages/blueapi-ui/src/RunPlanButton.test.tsx b/packages/blueapi-ui/src/RunPlanButton.test.tsx index bd730643..00bb8435 100644 --- a/packages/blueapi-ui/src/RunPlanButton.test.tsx +++ b/packages/blueapi-ui/src/RunPlanButton.test.tsx @@ -1,11 +1,5 @@ -import { act, render, screen } from "@atlas/vitest-conf"; +import { render, screen } from "@atlas/vitest-conf"; import { RunPlanButton } from "./RunPlanButton"; -import { - useGetWorkerState, - useSubmitTask, - useSetActiveTask, - useBlueapi, -} from "@atlas/blueapi-query"; import type { Api, TaskResponse, TrackableTask } from "@atlas/blueapi"; // Mocks with starting return values From df555b56048cec79483dba62b29a1c0863792f61 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Fri, 17 Jul 2026 14:58:28 +0000 Subject: [PATCH 39/40] Fix lint --- apps/i15-1/src/components/AbortPlanButton.test.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/i15-1/src/components/AbortPlanButton.test.tsx b/apps/i15-1/src/components/AbortPlanButton.test.tsx index 69d47abd..bab4c962 100644 --- a/apps/i15-1/src/components/AbortPlanButton.test.tsx +++ b/apps/i15-1/src/components/AbortPlanButton.test.tsx @@ -1,7 +1,8 @@ import { render, screen, userEvent } from "@atlas/vitest-conf"; import { AbortPlanButton } from "./AbortPlanButton"; import { useSetWorkerState } from "@atlas/blueapi-query"; -import type { WorkerStateRequest } from "@atlas/blueapi"; +import type { WorkerState, WorkerStateRequest } from "@atlas/blueapi"; +import type { UseMutationResult } from "@tanstack/react-query"; describe("AbortPlanButton", () => { vi.mock("@atlas/blueapi-query"); @@ -9,7 +10,11 @@ describe("AbortPlanButton", () => { const mutate = vi.fn(); - mockedHook.mockReturnValue({ mutate } as any); + mockedHook.mockReturnValue({ mutate } as any as UseMutationResult< + WorkerState, + Error, + WorkerStateRequest + >); it("renders default Abort button", () => { render(); From 4adf11d189a17469486ed9c468c52c6b7c533986 Mon Sep 17 00:00:00 2001 From: Noemi Frisina Date: Fri, 17 Jul 2026 15:09:45 +0000 Subject: [PATCH 40/40] Try to disable warning since linter does not like the cast --- apps/i15-1/src/components/AbortPlanButton.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/i15-1/src/components/AbortPlanButton.test.tsx b/apps/i15-1/src/components/AbortPlanButton.test.tsx index bab4c962..36ae4052 100644 --- a/apps/i15-1/src/components/AbortPlanButton.test.tsx +++ b/apps/i15-1/src/components/AbortPlanButton.test.tsx @@ -10,6 +10,7 @@ describe("AbortPlanButton", () => { const mutate = vi.fn(); + /* eslint-disable @typescript-eslint/no-explicit-any */ mockedHook.mockReturnValue({ mutate } as any as UseMutationResult< WorkerState, Error,