# HG changeset patch # User Arnaud Vergnet <arnaud.vergnet@logilab.fr> # Date 1711030449 -3600 # Thu Mar 21 15:14:09 2024 +0100 # Node ID be74d7006c4eeb3db67b1dafe3e1887a754e7657 # Parent a6e73e34395ab574adb132f635b4926bf4556a20 feat(frontend): add button to manually run recipe diff --git a/frontend/src/api/cubicweb.ts b/frontend/src/api/cubicweb.ts --- a/frontend/src/api/cubicweb.ts +++ b/frontend/src/api/cubicweb.ts @@ -176,6 +176,18 @@ } } +export function useApiRunRecipe() { + const client = useClient(); + const insertImportProcess = + "INSERT ImportProcess X: X import_procedure %(import_procedure)s, X import_recipe %(import_recipe)s"; + return (recipeEid: number, projectEid: number) => { + client.execute(insertImportProcess, { + import_procedure: projectEid, + import_recipe: recipeEid, + }); + }; +} + export function useApiCreateRecipe() { const client = useClient(); const insertRql = diff --git a/frontend/src/app/project/[eid]/page.tsx b/frontend/src/app/project/[eid]/page.tsx --- a/frontend/src/app/project/[eid]/page.tsx +++ b/frontend/src/app/project/[eid]/page.tsx @@ -20,7 +20,7 @@ import AddIcon from "@mui/icons-material/Add"; import { RecipeModal } from "@/components/RecipeModal"; import { useGetProject } from "@/hooks/useGetProject"; -import { useApiDeleteRecipe } from "@/api/cubicweb"; +import { useApiDeleteRecipe, useApiRunRecipe } from "@/api/cubicweb"; export default function Project({ params: { eid }, @@ -33,13 +33,15 @@ const [selectedRecipeEid, setSelectedRecipeEid] = useState< number | undefined >(); - const [deleteModal, setDeleteModal] = useState<{ + const [modal, setModal] = useState<{ visible: boolean; text?: string; eid?: number; + type?: "delete" | "run"; }>({ visible: false }); const [recipeModalOpen, setRecipeModalOpen] = useState(false); const deleteRecipe = useApiDeleteRecipe(); + const runRecipe = useApiRunRecipe(); if (isNaN(parsedEid)) { return <ErrorScreen message={`Projet '${eid}' inconnu`} />; @@ -102,10 +104,19 @@ }} selected={selectedRecipeEid === d.eid} onDelete={() => - setDeleteModal({ + setModal({ visible: true, text: `Voulez vous vraiment supprimer la recette '${d.name}' ?`, eid: d.eid, + type: "delete", + }) + } + onRun={() => + setModal({ + visible: true, + text: `Voulez vous vraiment lancer la recette '${d.name}' ?`, + eid: d.eid, + type: "run", }) } /> @@ -120,17 +131,21 @@ /> </Stack> <ConfirmModal - open={deleteModal.visible} - text={deleteModal?.text ?? ""} - onClose={() => setDeleteModal((prev) => ({ ...prev, visible: false }))} + open={modal.visible} + text={modal?.text ?? ""} + onClose={() => setModal((prev) => ({ ...prev, visible: false }))} onAccept={async () => { - if (deleteModal.eid) { - await deleteRecipe(deleteModal.eid); - const recipeList = data.import_recipes; - recipeList?.splice( - recipeList?.findIndex((p) => p.eid === deleteModal.eid), - 1, - ); + if (modal.eid) { + if (modal.type === "delete") { + await deleteRecipe(modal.eid); + const recipeList = data.import_recipes; + recipeList?.splice( + recipeList?.findIndex((p) => p.eid === modal.eid), + 1, + ); + } else { + await runRecipe(modal.eid, parsedEid); + } } }} /> diff --git a/frontend/src/components/cards/RecipeCard.tsx b/frontend/src/components/cards/RecipeCard.tsx --- a/frontend/src/components/cards/RecipeCard.tsx +++ b/frontend/src/components/cards/RecipeCard.tsx @@ -1,5 +1,7 @@ import { Box, + Button, + CardActions, CardContent, IconButton, Stack, @@ -10,6 +12,7 @@ import { BaseCard, preventPropagation } from "./BaseCard"; import CheckIcon from "@mui/icons-material/Check"; import CloseIcon from "@mui/icons-material/Close"; +import PlayArrowIcon from "@mui/icons-material/PlayArrow"; interface RecipeCardProps { name: string; @@ -19,6 +22,7 @@ lastProcessSuccess?: boolean; selected: boolean; onOpen?: () => void; + onRun?: () => void; onDelete?: () => void; } @@ -31,6 +35,7 @@ selected, onOpen, onDelete, + onRun, }: RecipeCardProps) { const theme = useTheme(); return ( @@ -40,43 +45,66 @@ backgroundColor: selected ? theme.palette.action.selected : undefined, }} > - <CardContent> - <Stack direction={"row"}> - <Typography variant="h5" component={"p"} fontWeight={"bold"} flex={1}> - {name} - </Typography> - <Box> - <IconButton - onClick={(e) => { - preventPropagation(e); - if (onDelete) { - onDelete(); - } - }} - onMouseDown={preventPropagation} + <Box display={"flex"} flexDirection={"column"} height={"100%"}> + <CardContent style={{ flex: 1 }}> + <Stack direction={"row"}> + <Typography + variant="h5" + component={"p"} + fontWeight={"bold"} + flex={1} > - <DeleteIcon /> - </IconButton> - </Box> - </Stack> - {lastProcessDate && lastProcessSuccess !== undefined ? ( - <> - <Typography variant="body2">Dernier process</Typography> - <Stack direction={"row"} spacing={1}> - <Typography variant="body1"> - {lastProcessDate.toLocaleDateString()} - </Typography> - {lastProcessSuccess ? ( - <CheckIcon color="success" /> - ) : ( - <CloseIcon color="error" /> - )} - </Stack> - </> - ) : null} - <Typography variant="body2">Process type: {processType}</Typography> - <Typography variant="body2">Data Service: {dataserviceName}</Typography> - </CardContent> + {name} + </Typography> + <Box> + <IconButton + onClick={(e) => { + preventPropagation(e); + if (onDelete) { + onDelete(); + } + }} + onMouseDown={preventPropagation} + > + <DeleteIcon /> + </IconButton> + </Box> + </Stack> + {lastProcessDate && lastProcessSuccess !== undefined ? ( + <> + <Typography variant="body2">Dernier process</Typography> + <Stack direction={"row"} spacing={1}> + <Typography variant="body1"> + {lastProcessDate.toLocaleDateString()} + </Typography> + {lastProcessSuccess ? ( + <CheckIcon color="success" /> + ) : ( + <CloseIcon color="error" /> + )} + </Stack> + </> + ) : null} + <Typography variant="body2">Process type: {processType}</Typography> + <Typography variant="body2"> + Data Service: {dataserviceName} + </Typography> + </CardContent> + <CardActions style={{ justifyContent: "end" }}> + <Button + endIcon={<PlayArrowIcon />} + onClick={(e) => { + preventPropagation(e); + if (onRun) { + onRun(); + } + }} + onMouseDown={preventPropagation} + > + Lancer + </Button> + </CardActions> + </Box> </BaseCard> ); }