From 6ccd0e42aab6ac8aed95eece0de7508713536863 Mon Sep 17 00:00:00 2001 From: James Gilbert Date: Wed, 26 Aug 2026 07:34:20 +0000 Subject: [PATCH 1/2] feat(frontend): add basic trigger list --- frontend/dashboard/src/main.tsx | 5 ++ .../dashboard/src/routes/TriggersListPage.tsx | 18 +++++ .../lib/components/TriggersListContent.tsx | 78 +++++++++++++++++++ .../lib/components/WorkflowsNavbar.tsx | 3 + frontend/relay-workflows-lib/lib/main.ts | 1 + .../lib/views/TriggersListView.tsx | 70 +++++++++++++++++ .../lib/components/triggers/TriggerList.tsx | 0 7 files changed, 175 insertions(+) create mode 100644 frontend/dashboard/src/routes/TriggersListPage.tsx create mode 100644 frontend/relay-workflows-lib/lib/components/TriggersListContent.tsx create mode 100644 frontend/relay-workflows-lib/lib/views/TriggersListView.tsx create mode 100644 frontend/workflows-lib/lib/components/triggers/TriggerList.tsx diff --git a/frontend/dashboard/src/main.tsx b/frontend/dashboard/src/main.tsx index c270a4a27..5b9b63b57 100644 --- a/frontend/dashboard/src/main.tsx +++ b/frontend/dashboard/src/main.tsx @@ -10,6 +10,7 @@ import SingleTemplatePage from "./routes/SingleTemplatePage"; import SingleWorkflowPage from "./routes/SingleWorkflowPage"; import ErrorPage from "./ErrorPage"; import { ThemeProvider, DiamondTheme } from "@diamondlightsource/sci-react-ui"; +import TriggersListPage from "./routes/TriggersListPage"; const router = createBrowserRouter([ { @@ -45,6 +46,10 @@ const router = createBrowserRouter([ path: "workflows/:visitid/:workflowId/:tasknames", element: , }, + { + path: "triggers", + element: , + }, ]); async function startMockingIfConfigured() { diff --git a/frontend/dashboard/src/routes/TriggersListPage.tsx b/frontend/dashboard/src/routes/TriggersListPage.tsx new file mode 100644 index 000000000..2d67f9f70 --- /dev/null +++ b/frontend/dashboard/src/routes/TriggersListPage.tsx @@ -0,0 +1,18 @@ +import { Breadcrumbs } from "@diamondlightsource/sci-react-ui"; +import { Box, Container } from "@mui/material"; +import { Link } from "react-router-dom"; +import { TriggersListView, WorkflowsNavbar } from "relay-workflows-lib"; + +export default function TriggersListPage() { + return ( + <> + + + + + + + + + ); +} diff --git a/frontend/relay-workflows-lib/lib/components/TriggersListContent.tsx b/frontend/relay-workflows-lib/lib/components/TriggersListContent.tsx new file mode 100644 index 000000000..fe884d357 --- /dev/null +++ b/frontend/relay-workflows-lib/lib/components/TriggersListContent.tsx @@ -0,0 +1,78 @@ +import { useEffect } from "react"; +import { PreloadedQuery, usePreloadedQuery } from "react-relay"; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Box, + Typography, +} from "@mui/material"; +import { PaginationControls } from "workflows-lib"; +import { TriggersListViewQuery } from "../views/TriggersListView"; +import type { TriggersListViewQuery as TriggersListViewQueryType } from "../views/__generated__/TriggersListViewQuery.graphql"; + +interface TriggersListContentProps { + queryRef: PreloadedQuery; + currentPage: number; + totalPages: number; + selectedLimit: number; + onPageChange: ( + page: number, + endCursor?: string | null, + hasNextPage?: boolean, + ) => void; + onLimitChange: (limit: number) => void; + updatePageInfo: (hasNextPage: boolean, endCursor: string | null) => void; +} + +export default function TriggersListContent({ + queryRef, + currentPage, + totalPages, + selectedLimit, + onPageChange, + onLimitChange, + updatePageInfo, +}: TriggersListContentProps) { + const queryData = usePreloadedQuery(TriggersListViewQuery, queryRef); + const { nodes, pageInfo } = queryData.triggers; + + useEffect(() => { + updatePageInfo(pageInfo.hasNextPage, pageInfo.endCursor ?? null); + }, [pageInfo.hasNextPage, pageInfo.endCursor, updatePageInfo]); + + return ( + + + {nodes.map((node, index) => ( + + + + + {node.beamline} + + {node.name} + + + + + Created from the {node.templateRef} template + + + + ))} + + { + onPageChange(page, pageInfo.endCursor, pageInfo.hasNextPage); + }} + selectedLimit={selectedLimit} + onLimitChange={onLimitChange} + /> + + ); +} diff --git a/frontend/relay-workflows-lib/lib/components/WorkflowsNavbar.tsx b/frontend/relay-workflows-lib/lib/components/WorkflowsNavbar.tsx index 6e7b19a8b..b41fe353d 100644 --- a/frontend/relay-workflows-lib/lib/components/WorkflowsNavbar.tsx +++ b/frontend/relay-workflows-lib/lib/components/WorkflowsNavbar.tsx @@ -67,6 +67,9 @@ const WorkflowsNavbar: React.FC = ({ sessionInfo }) => { Templates + + Triggers + } diff --git a/frontend/relay-workflows-lib/lib/main.ts b/frontend/relay-workflows-lib/lib/main.ts index 9f839ecfd..c06c3279d 100644 --- a/frontend/relay-workflows-lib/lib/main.ts +++ b/frontend/relay-workflows-lib/lib/main.ts @@ -11,6 +11,7 @@ export { export { default as TemplateView } from "./views/TemplateView"; export { default as TemplateViewRetrigger } from "./views/TemplateViewRetrigger"; export { default as SingleWorkflowView } from "./views/SingleWorkflowView"; +export { default as TriggersListView } from "./views/TriggersListView"; export { default as RepositoryLink } from "./query-components/RepositoryLink"; export { default as WorkflowsNavbar } from "./components/WorkflowsNavbar"; export { diff --git a/frontend/relay-workflows-lib/lib/views/TriggersListView.tsx b/frontend/relay-workflows-lib/lib/views/TriggersListView.tsx new file mode 100644 index 000000000..a53d410df --- /dev/null +++ b/frontend/relay-workflows-lib/lib/views/TriggersListView.tsx @@ -0,0 +1,70 @@ +import { graphql } from "relay-runtime"; +import { type TriggersListViewQuery as TriggersListViewQueryType } from "./__generated__/TriggersListViewQuery.graphql"; +import { useQueryLoader } from "react-relay"; +import { useCallback, useEffect } from "react"; +import TriggersListContent from "../components/TriggersListContent"; +import { Box } from "@mui/material"; +import { useServerSidePagination } from "../utils/useServerSidePagination"; + +export const TriggersListViewQuery = graphql` + query TriggersListViewQuery($limit: Int, $cursor: String) { + triggers(limit: $limit, cursor: $cursor) { + pageInfo { + hasNextPage + endCursor + } + nodes { + name + beamline + templateRef + } + } + } +`; + +export default function TriggersListView() { + const [queryReference, loadQuery] = useQueryLoader( + TriggersListViewQuery, + ); + + const { + cursor, + currentPage, + totalPages, + selectedLimit, + goToPage, + changeLimit, + updatePageInfo, + } = useServerSidePagination(); + + const load = useCallback(() => { + loadQuery( + { limit: selectedLimit, cursor }, + { fetchPolicy: "store-and-network" }, + ); + }, [selectedLimit, cursor, loadQuery]); + + useEffect(() => { + load(); + const interval = setInterval(load, 5000); + return () => { + clearInterval(interval); + }; + }, [load]); + + return ( + + {queryReference && ( + + )} + + ); +} diff --git a/frontend/workflows-lib/lib/components/triggers/TriggerList.tsx b/frontend/workflows-lib/lib/components/triggers/TriggerList.tsx new file mode 100644 index 000000000..e69de29bb From add1bab8e0aa91749730584118aa0feb967243ee Mon Sep 17 00:00:00 2001 From: James Gilbert Date: Thu, 27 Aug 2026 08:16:58 +0000 Subject: [PATCH 2/2] feat(frontend): remove session ID from navbar --- .../lib/components/WorkflowsNavbar.tsx | 27 +++---------------- .../tests/components/WorkflowsNavbar.test.tsx | 25 ----------------- 2 files changed, 3 insertions(+), 49 deletions(-) diff --git a/frontend/relay-workflows-lib/lib/components/WorkflowsNavbar.tsx b/frontend/relay-workflows-lib/lib/components/WorkflowsNavbar.tsx index b41fe353d..69382915f 100644 --- a/frontend/relay-workflows-lib/lib/components/WorkflowsNavbar.tsx +++ b/frontend/relay-workflows-lib/lib/components/WorkflowsNavbar.tsx @@ -1,8 +1,7 @@ -import { Box, Typography } from "@mui/material"; +import { Box } from "@mui/material"; import { NavLink as Link } from "react-router-dom"; import { Navbar, - DiamondTheme, NavLinks, NavLink, User, @@ -32,7 +31,7 @@ const handleLogout = () => { } }; -const WorkflowsNavbar: React.FC = ({ sessionInfo }) => { +const WorkflowsNavbar: React.FC = () => { const [user, setUser] = useState(null); useEffect(() => { @@ -74,27 +73,7 @@ const WorkflowsNavbar: React.FC = ({ sessionInfo }) => { } rightSlot={ - <> - {sessionInfo && ( - - {sessionInfo} - - )} - - + } /> ); diff --git a/frontend/relay-workflows-lib/tests/components/WorkflowsNavbar.test.tsx b/frontend/relay-workflows-lib/tests/components/WorkflowsNavbar.test.tsx index 63f535c5c..5ac0896fc 100644 --- a/frontend/relay-workflows-lib/tests/components/WorkflowsNavbar.test.tsx +++ b/frontend/relay-workflows-lib/tests/components/WorkflowsNavbar.test.tsx @@ -26,31 +26,6 @@ describe("WorkflowsNavbar", () => { vi.restoreAllMocks(); }); - it("renders with title and sessionInfo", () => { - const { getByText } = render( - - - - - , - ); - expect(getByText("cm12345-6")).toBeInTheDocument(); - }); - - it("applies the correct styles", () => { - const { getByText } = render( - - - - - , - ); - const sessionElement = getByText("cm34567-8"); - expect(sessionElement).toHaveStyle( - `color: ${DiamondTheme.palette.primary.contrastText}`, - ); - }); - it("displays the logged in user", async () => { vi.mocked(getUser).mockReturnValue(Promise.resolve(testUser)); render(