Skip to content

Commit

Permalink
feat(frontend/rest): add batch actions for workflows
Browse files Browse the repository at this point in the history
  • Loading branch information
tdari committed Jul 16, 2024
1 parent ae78a80 commit 959cd5d
Show file tree
Hide file tree
Showing 16 changed files with 942 additions and 143 deletions.
45 changes: 45 additions & 0 deletions frontend/src/features/myWorkflows/api/runs/useStartRuns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { type MutationConfig } from "@services/clients/react-query.client";
import { useMutation } from "@tanstack/react-query";
import { type AxiosError } from "axios";
import { type IBatchWorkflowActionResponse } from "features/myWorkflows/types/workflow";
import { toast } from "react-toastify";
import { dominoApiClient } from "services/clients/domino.client";

interface StartRunsParams {
workflowIds: number[];
}

interface UseStartRuns {
workspaceId?: string;
}

export const useStartRuns = (
{ workspaceId }: UseStartRuns,
config: MutationConfig<StartRunsParams, IBatchWorkflowActionResponse> = {},
) => {
return useMutation({
mutationFn: async ({ workflowIds }) => {
if (!workflowIds) throw new Error("No workflow selected");
return await postWorkflowRunIds({ workflowIds, workspaceId });
},
onError: (e: AxiosError<{ detail: string }>) => {
const message =
(e.response?.data?.detail ?? e?.message) || "Something went wrong";

toast.error(message, {
toastId: message,
});
},
...config,
});
};

const postWorkflowRunIds = async ({
workflowIds,
workspaceId,
}: StartRunsParams & UseStartRuns): Promise<IBatchWorkflowActionResponse> => {
return await dominoApiClient.post(
`/batch/workspaces/${workspaceId}/workflows/runs`,
workflowIds,
);
};
45 changes: 45 additions & 0 deletions frontend/src/features/myWorkflows/api/runs/useStopRuns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { type MutationConfig } from "@services/clients/react-query.client";
import { useMutation } from "@tanstack/react-query";
import { type AxiosError } from "axios";
import { type IBatchWorkflowActionResponse } from "features/myWorkflows/types/workflow";
import { toast } from "react-toastify";
import { dominoApiClient } from "services/clients/domino.client";

interface StopRunsParams {
workflowIds: number[];
}

interface UseStopRuns {
workspaceId?: string;
}

export const useStopRuns = (
{ workspaceId }: UseStopRuns,
config: MutationConfig<StopRunsParams, IBatchWorkflowActionResponse> = {},
) => {
return useMutation({
mutationFn: async ({ workflowIds }) => {
if (!workflowIds) throw new Error("No workflow selected");
return await postWorkflowStopIds({ workflowIds, workspaceId });
},
onError: (e: AxiosError<{ detail: string }>) => {
const message =
(e.response?.data?.detail ?? e?.message) || "Something went wrong";

toast.error(message, {
toastId: message,
});
},
...config,
});
};

const postWorkflowStopIds = async ({
workflowIds,
workspaceId,
}: StopRunsParams & UseStopRuns): Promise<IBatchWorkflowActionResponse> => {
return await dominoApiClient.patch(
`/batch/workspaces/${workspaceId}/workflows/runs`,
workflowIds,
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { type MutationConfig } from "@services/clients/react-query.client";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { type AxiosError } from "axios";
import { type IBatchWorkflowActionResponse } from "features/myWorkflows/types/workflow";
import { toast } from "react-toastify";
import { dominoApiClient } from "services/clients/domino.client";

interface DeleteWorkflowsParams {
workflowIds: number[];
}

interface UseDeleteWorkflows {
workspaceId?: string;
}

export const useDeleteWorkflows = (
{ workspaceId }: UseDeleteWorkflows,
config: MutationConfig<
DeleteWorkflowsParams,
IBatchWorkflowActionResponse
> = {},
) => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: async ({ workflowIds }) => {
if (!workspaceId) throw new Error("No workspace selected");
return await deleteWorkflowByIds({ workflowIds, workspaceId });
},
onSuccess: async (_, { workflowIds }) => {
await queryClient.invalidateQueries({
queryKey: ["WORKFLOWS", workspaceId],
});
await queryClient.invalidateQueries({
queryKey: ["WORKFLOW", workspaceId, workflowIds],
});
},
onError: (e: AxiosError<{ detail: string }>) => {
const message =
(e.response?.data?.detail ?? e?.message) || "Something went wrong";

toast.error(message, {
toastId: message,
});
},
...config,
});
};

const deleteWorkflowByIds = async (
params: DeleteWorkflowsParams & UseDeleteWorkflows,
): Promise<IBatchWorkflowActionResponse> => {
return await dominoApiClient.delete(
`/batch/workspaces/${params.workspaceId}/workflows`,
{
data: params.workflowIds,
},
);
};
106 changes: 41 additions & 65 deletions frontend/src/features/myWorkflows/components/WorkflowsList/Actions.tsx
Original file line number Diff line number Diff line change
@@ -1,94 +1,70 @@
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
import PlayCircleOutlineIcon from "@mui/icons-material/PlayCircleOutline";
import { IconButton, Tooltip, useTheme } from "@mui/material";
import {
DeleteOutlined,
PlayCircleOutlined,
StopCircleOutlined,
} from "@mui/icons-material";
import { Button, Divider } from "@mui/material";
import { type CommonProps } from "@mui/material/OverridableComponent";
import { Modal, type ModalRef } from "components/Modal";
import { GridToolbarContainer } from "@mui/x-data-grid";
import { type IWorkflow } from "features/myWorkflows/types";
import React, { useRef, useState } from "react";
import React, { useState } from "react";

import { ConfirmDeleteModal } from "./ConfirmDeleteModal";

interface Props extends CommonProps {
id: IWorkflow["id"];
deleteFn: () => void;
ids: Array<IWorkflow["id"]>;
runFn: () => void;
pauseFn: () => void;
stopFn: () => void;
deleteFn: () => void;
disabled: boolean;
}

export const Actions: React.FC<Props> = ({
ids,
runFn,
stopFn,
deleteFn,
className,
disabled = false,
disabled,
}) => {
const theme = useTheme();

const [deleteModalOpen, setDeleteModalOpen] = useState(false);
const newFeatureModal = useRef<ModalRef>(null);

return (
<>
{disabled ? (
<Tooltip title="Can't run future workflows." arrow>
<span>
<IconButton
className={className}
onClick={runFn}
disabled={disabled}
>
<PlayCircleOutlineIcon
style={{
pointerEvents: "none",
color: theme.palette.grey[500],
}}
/>
</IconButton>
</span>
</Tooltip>
) : (
<IconButton className={className} onClick={runFn}>
<PlayCircleOutlineIcon
style={{
pointerEvents: "none",
color: theme.palette.success.main,
}}
/>
</IconButton>
)}
<IconButton
className={className}
onClick={() => {
newFeatureModal.current?.open();
}}
<GridToolbarContainer sx={{ borderBottom: 1 }}>
<Button
color="primary"
startIcon={<PlayCircleOutlined />}
onClick={runFn}
disabled={disabled}
>
<PauseCircleOutlineIcon
style={{ pointerEvents: "none", color: theme.palette.info.main }}
/>
</IconButton>
<IconButton
className={className}
Start
</Button>
<Divider orientation="vertical" variant="middle" flexItem />
<Button
color="primary"
startIcon={<StopCircleOutlined />}
onClick={stopFn}
disabled={disabled}
>
Stop
</Button>
<Divider orientation="vertical" variant="middle" flexItem />
<Button
color="error"
startIcon={<DeleteOutlined />}
onClick={() => {
setDeleteModalOpen(true);
}}
disabled={disabled}
>
<DeleteOutlineIcon
style={{ pointerEvents: "none", color: theme.palette.error.main }}
/>
</IconButton>
<Modal
title="New Feature"
content="This feature is not ready yet! We launch new versions every time,
check out our changelog for more information !"
ref={newFeatureModal}
/>
Delete
</Button>
<ConfirmDeleteModal
isOpen={deleteModalOpen}
title="Confirm Workflow Deletion"
content={
<span>
Are you sure you want to delete this workflow? This action{" "}
Are you sure you want to delete selected {ids.length} workflows?
This action{" "}
<span style={{ fontWeight: "bold" }}>cannot be undone</span>.
</span>
}
Expand All @@ -101,6 +77,6 @@ export const Actions: React.FC<Props> = ({
}}
confirmText="Delete"
/>
</>
</GridToolbarContainer>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { type IBatchWorkflowActionDetail } from "@features/myWorkflows/types";
import { Dialog, DialogContent, DialogTitle } from "@mui/material";
import { DataGrid, type GridColDef } from "@mui/x-data-grid";
import { useCallback } from "react";

interface Props {
isOpen: boolean;
title: string;
data: IBatchWorkflowActionDetail[];
cancelCb: () => void;
}

const columns: Array<GridColDef<IBatchWorkflowActionDetail>> = [
{ field: "id", headerName: "ID", width: 90 },
{
field: "message",
headerName: "Message",
width: 400,
},
];

export const FailureDetailsModal: React.FC<Props> = ({
isOpen,
title,
data,
cancelCb,
}) => {
const cancel = useCallback(() => {
cancelCb();
}, [cancelCb]);
return (
<Dialog
open={isOpen}
onClose={cancel}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{title}</DialogTitle>
<DialogContent>
<DataGrid
density="compact"
columns={columns}
rows={data}
disableDensitySelector
disableRowSelectionOnClick
hideFooterSelectedRowCount
disableColumnMenu
disableColumnSelector
/>
</DialogContent>
</Dialog>
);
};
Loading

0 comments on commit 959cd5d

Please sign in to comment.