Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
e997c63
Add a blueapi status card
noemifrisina May 12, 2026
38ff3ed
Add abort button
noemifrisina May 12, 2026
cb83b97
Tidy up
noemifrisina May 12, 2026
bec4ed7
Try adding a handler
noemifrisina May 12, 2026
2e2e94d
Add a first snackbar
noemifrisina May 13, 2026
d4b9912
Get the msw handler to more or less show abort
noemifrisina May 13, 2026
28b869f
Update the Trackable task from blueapi
noemifrisina May 13, 2026
de12c13
Add a snackbar to the run plan button
noemifrisina May 13, 2026
85d7ab4
Move snackbar back
noemifrisina May 13, 2026
b717540
Add more snackbar and try to mock
noemifrisina May 15, 2026
cb84630
Fix the snackbar - now just need to poll
noemifrisina May 18, 2026
78a005f
Try to see if it wait for sleep
noemifrisina May 19, 2026
c5738a4
An attempt at polling
noemifrisina May 20, 2026
a2ede88
Fix typing in test
noemifrisina May 28, 2026
fd020b7
Merge branch 'main' into 1539-i15_1-blueapi_feedback
jacob720 Jun 10, 2026
7a61e6a
Fix lint
jacob720 Jun 10, 2026
9e4c89d
Merge branch 'main' into 1539-i15_1-blueapi_feedback
jacob720 Jun 10, 2026
8a74ce6
Fix typecheck
jacob720 Jun 10, 2026
40377f0
Merge branch 'main' into 1539-i15_1-blueapi_feedback
noemifrisina Jun 10, 2026
5093662
Rename parameter
noemifrisina Jun 10, 2026
c21fba7
Pull idle and aborting into constants
noemifrisina Jun 10, 2026
b7c65ec
Merge branch 'main' into 1539-i15_1-blueapi_feedback
noemifrisina Jun 19, 2026
38ceb2e
Merge branch 'main' into 1539-i15_1-blueapi_feedback
noemifrisina Jul 3, 2026
fbd0420
Merge branch 'main' into 1539-i15_1-blueapi_feedback
noemifrisina Jul 16, 2026
80630f9
Fix issues from bad merge in Robot
noemifrisina Jul 16, 2026
f2c4e53
Put the abort button back in
noemifrisina Jul 16, 2026
77b99cf
Add more snackbars
noemifrisina Jul 16, 2026
1dda9b3
Put the worker state back in
noemifrisina Jul 16, 2026
b9e529f
Get the polling to work
noemifrisina Jul 16, 2026
59a9248
Fix the polling
noemifrisina Jul 16, 2026
a373950
Get mock for useBlueapi to work and add a test to check it
noemifrisina Jul 16, 2026
83cc6cb
One more test
noemifrisina Jul 16, 2026
94f04cf
Remove some extra comments
noemifrisina Jul 16, 2026
0ffbf99
And maybe remember to update test
noemifrisina Jul 16, 2026
afd4c2e
Use correct utility
noemifrisina Jul 16, 2026
3925bbd
Use act for rendering
noemifrisina Jul 16, 2026
9975ef5
Try to fix it
noemifrisina Jul 16, 2026
89eccf6
Check if other tests actually pass
noemifrisina Jul 16, 2026
4c0658a
Check if other tests actually pass - part 2
noemifrisina Jul 16, 2026
0d8027b
Try to fix broken tests one by one
noemifrisina Jul 17, 2026
b3880e9
Try to fix broken tests one by one - maybe mocking an actual missing …
noemifrisina Jul 17, 2026
2b45039
See happy test
noemifrisina Jul 17, 2026
a2eb7af
Tidy up
noemifrisina Jul 17, 2026
d94080b
Add tests for abort button and tidy up
noemifrisina Jul 17, 2026
df555b5
Fix lint
noemifrisina Jul 17, 2026
4adf11d
Try to disable warning since linter does not like the cast
noemifrisina Jul 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions apps/i15-1/src/components/AbortPlanButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { render, screen, userEvent } from "@atlas/vitest-conf";
import { AbortPlanButton } from "./AbortPlanButton";
import { useSetWorkerState } from "@atlas/blueapi-query";
import type { WorkerState, WorkerStateRequest } from "@atlas/blueapi";
import type { UseMutationResult } from "@tanstack/react-query";

describe("AbortPlanButton", () => {
vi.mock("@atlas/blueapi-query");
const mockedHook = vi.mocked(useSetWorkerState);

const mutate = vi.fn();

/* eslint-disable @typescript-eslint/no-explicit-any */
mockedHook.mockReturnValue({ mutate } as any as UseMutationResult<
WorkerState,
Error,
WorkerStateRequest
>);

it("renders default Abort button", () => {
render(<AbortPlanButton />);

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(<AbortPlanButton />);

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();
});
});
65 changes: 65 additions & 0 deletions apps/i15-1/src/components/AbortPlanButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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<boolean>(false);

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 (
<React.Fragment>
<Tooltip title="Abort current blueapi operation" placement="bottom">
<Button
variant="contained"
color="error"
sx={{ width: "150px" }}
onClick={handleClick}
>
Abort
</Button>
</Tooltip>
<Snackbar
open={openSnackbar}
autoHideDuration={5000}
onClose={handleSnackbarClose}
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
>
<Alert onClose={handleSnackbarClose} severity="warning">
Abort button pressed, will abort current plan ...
</Alert>
</Snackbar>
</React.Fragment>
);
}
64 changes: 64 additions & 0 deletions apps/i15-1/src/components/BlueapiWorkerState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useGetWorkerState } from "@atlas/blueapi-query";
import {
Card,
CardContent,
Stack,
Typography,
useTheme,
type Theme,
} from "@mui/material";

function getStateColorMap(theme: Theme) {
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(theme);

return (
<Card
variant="outlined"
sx={{
minWidth: 250,
maxHeight: 200,
bgcolor: theme.palette.background.paper,
borderColor: theme.palette.text.primary,
}}
>
<CardContent>
<Stack direction={"column"} spacing={"1"}>
<Typography
variant="body1"
sx={{
fontSize: 16,
fontStyle: "italic",
fontWeight: "bold",
}}
>
Blueapi worker state:{" "}
</Typography>
<Typography
variant="body1"
sx={{ fontSize: 18, fontWeight: "bold" }}
color={stateMap[workerState.data ? workerState.data : "UNKNOWN"]}
>
{workerState.data}
</Typography>
</Stack>
</CardContent>
</Card>
);
}
26 changes: 23 additions & 3 deletions apps/i15-1/src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ const fakeExperiments = {
},
};

function setWorkerState(new_state: string) {
workerStatus.status = new_state;
}

const fakePvws = ws.link("wss://pvws.diamond.ac.uk/pvws/pv");

const fakeHistory = [
Expand Down Expand Up @@ -522,7 +526,7 @@ export const handlers = [
: []),

http.put("/api/blueapi/worker/task", () => {
workerStatus.status = "RUNNING";
setWorkerState("RUNNING");
return HttpResponse.json({
task_id: fakeTaskId,
});
Expand All @@ -534,8 +538,24 @@ export const handlers = [
});
}),

http.put("/api/blueapi/worker/state", () => {
return HttpResponse.json("IDLE");
http.get("/api/blueapi/tasks/:task_id", () => {
return HttpResponse.json({
task_id: fakeTaskId,
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/blueapi/worker/state", async ({ request }) => {
const { new_state } = (await request.json()) as { new_state: string };
if (new_state === "ABORTING") {
setWorkerState(new_state);
}
return HttpResponse.json(workerStatus.status);
}),

http.get("/oauth2/userinfo", () => {
Expand Down
28 changes: 17 additions & 11 deletions apps/i15-1/src/routes/Robot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ 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 { WebcamStreamFromPv } from "../components/Webcam";
import { BlueapiWorkerState } from "../components/BlueapiWorkerState";

type RobotSampleFormData = {
puck: number;
Expand Down Expand Up @@ -53,6 +55,7 @@ function StatusSidebar() {
pv="ca://BL15J-EA-LOC-01:SAMPLE:INDEX"
/>
</StatusCard>
<BlueapiWorkerState />
</Stack>
</Box>
);
Expand Down Expand Up @@ -96,17 +99,20 @@ function RobotControl() {
}}
/>
</Stack>
<RunPlanButton
name="robot_load"
params={formData}
instrumentSession={instrumentSession}
buttonText="Load Sample"
/>
<RunPlanButton
name="robot_unload"
instrumentSession={instrumentSession}
buttonText="Unload Sample"
/>
<Stack direction={"row"} spacing={3} alignItems={"center"}>
<RunPlanButton
name="robot_load"
params={formData}
instrumentSession={instrumentSession}
buttonText="Load Sample"
/>
<RunPlanButton
name="robot_unload"
instrumentSession={instrumentSession}
buttonText="Unload Sample"
/>
</Stack>
<AbortPlanButton />
</Stack>
</Box>
);
Expand Down
Loading
Loading