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..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(() => { @@ -67,31 +66,14 @@ const WorkflowsNavbar: React.FC = ({ sessionInfo }) => { Templates + + Triggers + } rightSlot={ - <> - {sessionInfo && ( - - {sessionInfo} - - )} - - + } /> ); 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/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( 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