From 3c77866cc4db5904ec329f8f7600d0d19e590036 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Wed, 28 Aug 2024 19:42:38 +0530 Subject: [PATCH] VTAdmin(web): Add workflow start/stop actions Signed-off-by: Noble Mittal --- web/vtadmin/src/api/http.ts | 22 ++++++ .../src/components/routes/Workflows.tsx | 20 +++-- .../routes/workflows/WorkflowAction.tsx | 77 +++++++++++++++++++ .../routes/workflows/WorkflowActions.tsx | 68 ++++++++++++++++ web/vtadmin/src/hooks/api.ts | 26 +++++++ 5 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 web/vtadmin/src/components/routes/workflows/WorkflowAction.tsx create mode 100644 web/vtadmin/src/components/routes/workflows/WorkflowActions.tsx diff --git a/web/vtadmin/src/api/http.ts b/web/vtadmin/src/api/http.ts index 64d75b23a42..030455c652d 100644 --- a/web/vtadmin/src/api/http.ts +++ b/web/vtadmin/src/api/http.ts @@ -448,6 +448,28 @@ export const fetchWorkflowStatus = async (params: { clusterID: string; keyspace: return vtctldata.WorkflowStatusResponse.create(result); }; +export interface WorkflowActionParams { + clusterID: string; + keyspace: string; + name: string; +} + +export const startWorkflow = async ({ clusterID, keyspace, name }: WorkflowActionParams) => { + const { result } = await vtfetch(`/api/workflow/${clusterID}/${keyspace}/${name}/start`); + const err = vtctldata.WorkflowUpdateResponse.verify(result); + if (err) throw Error(err); + + return vtctldata.WorkflowUpdateResponse.create(result); +}; + +export const stopWorkflow = async ({ clusterID, keyspace, name }: WorkflowActionParams) => { + const { result } = await vtfetch(`/api/workflow/${clusterID}/${keyspace}/${name}/stop`); + const err = vtctldata.WorkflowUpdateResponse.verify(result); + if (err) throw Error(err); + + return vtctldata.WorkflowUpdateResponse.create(result); +}; + export const fetchVTExplain = async ({ cluster, keyspace, sql }: R) => { // As an easy enhancement for later, we can also validate the request parameters on the front-end // instead of defaulting to '', to save a round trip. diff --git a/web/vtadmin/src/components/routes/Workflows.tsx b/web/vtadmin/src/components/routes/Workflows.tsx index 32ddfcfb825..477d8bcb582 100644 --- a/web/vtadmin/src/components/routes/Workflows.tsx +++ b/web/vtadmin/src/components/routes/Workflows.tsx @@ -35,9 +35,13 @@ import { Tooltip } from '../tooltip/Tooltip'; import { KeyspaceLink } from '../links/KeyspaceLink'; import { QueryLoadingPlaceholder } from '../placeholders/QueryLoadingPlaceholder'; import { UseQueryResult } from 'react-query'; +import { ReadOnlyGate } from '../ReadOnlyGate'; +import WorkflowActions from './workflows/WorkflowActions'; export const ThrottleThresholdSeconds = 60; +const COLUMNS = ['Workflow', 'Source', 'Target', 'Streams', 'Last Updated', 'Actions']; + export const Workflows = () => { useDocumentTitle('Workflows'); const workflowsQuery = useWorkflows(); @@ -180,6 +184,16 @@ export const Workflows = () => {
{formatDateTime(row.timeUpdated)}
{formatRelativeTime(row.timeUpdated)}
+ + + + + + ); }); @@ -198,11 +212,7 @@ export const Workflows = () => { value={filter || ''} /> - + diff --git a/web/vtadmin/src/components/routes/workflows/WorkflowAction.tsx b/web/vtadmin/src/components/routes/workflows/WorkflowAction.tsx new file mode 100644 index 00000000000..707ae6a3f7b --- /dev/null +++ b/web/vtadmin/src/components/routes/workflows/WorkflowAction.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { Icon, Icons } from '../../Icon'; +import Dialog from '../../dialog/Dialog'; +import { UseMutationResult } from 'react-query'; + +interface WorkflowActionProps { + isOpen: boolean; + mutation: UseMutationResult; + title: string; + confirmText: string; + successText: string; + errorText: string; + loadingText: string; + description?: string; + body?: JSX.Element; + successBody?: JSX.Element; + closeDialog: () => void; +} + +const WorkflowAction: React.FC = ({ + isOpen, + closeDialog, + mutation, + title, + confirmText, + description, + successText, + successBody, + loadingText, + errorText, + body, +}) => { + const onCloseDialog = () => { + setTimeout(mutation.reset, 500); + closeDialog(); + }; + + const hasRun = mutation.data || mutation.error; + return ( + +
+ {!hasRun && body} + {mutation.data && !mutation.error && ( +
+ + + +
{successText}
+ {successBody} +
+ )} + {mutation.error && ( +
+ + + +
{errorText}
+
+ )} +
+
+ ); +}; + +export default WorkflowAction; diff --git a/web/vtadmin/src/components/routes/workflows/WorkflowActions.tsx b/web/vtadmin/src/components/routes/workflows/WorkflowActions.tsx new file mode 100644 index 00000000000..7edb632327b --- /dev/null +++ b/web/vtadmin/src/components/routes/workflows/WorkflowActions.tsx @@ -0,0 +1,68 @@ +import React, { useState } from 'react'; +import Dropdown from '../../dropdown/Dropdown'; +import MenuItem from '../../dropdown/MenuItem'; +import { Icons } from '../../Icon'; +import WorkflowAction from './WorkflowAction'; +import { useStartWorkflow, useStopWorkflow } from '../../../hooks/api'; + +interface WorkflowActionsProps { + keyspace: string; + clusterID: string; + name: string; +} + +const WorkflowActions: React.FC = ({ keyspace, clusterID, name }) => { + const [currentDialog, setCurrentDialog] = useState(''); + const closeDialog = () => setCurrentDialog(''); + + const startWorkflowMutation = useStartWorkflow({ keyspace, clusterID, name }); + + const stopWorkflowMutation = useStopWorkflow({ keyspace, clusterID, name }); + + return ( +
+ + setCurrentDialog('Start Workflow')}>Start Workflow + setCurrentDialog('Stop Workflow')}>Stop Workflow + + + {startWorkflowMutation.data && startWorkflowMutation.data.summary && ( +
{startWorkflowMutation.data.summary}
+ )} +
+ } + /> + + {stopWorkflowMutation.data && stopWorkflowMutation.data.summary && ( +
{stopWorkflowMutation.data.summary}
+ )} + + } + /> + + ); +}; + +export default WorkflowActions; diff --git a/web/vtadmin/src/hooks/api.ts b/web/vtadmin/src/hooks/api.ts index 536293702c1..f3772ed4ab0 100644 --- a/web/vtadmin/src/hooks/api.ts +++ b/web/vtadmin/src/hooks/api.ts @@ -79,6 +79,8 @@ import { GetFullStatusParams, validateVersionShard, ValidateVersionShardParams, + startWorkflow, + stopWorkflow, } from '../api/http'; import { vtadmin as pb, vtctldata } from '../proto/vtadmin'; import { formatAlias } from '../util/tablets'; @@ -460,6 +462,30 @@ export const useWorkflowStatus = ( return useQuery(['workflow_status', params], () => fetchWorkflowStatus(params)); }; +/** + * useStartWorkflow is a mutate hook that starts a workflow. + */ +export const useStartWorkflow = ( + params: Parameters[0], + options?: UseMutationOptions>, Error> +) => { + return useMutation>, Error>(() => { + return startWorkflow(params); + }, options); +}; + +/** + * useStopWorkflow is a mutate hook that stops a workflow. + */ +export const useStopWorkflow = ( + params: Parameters[0], + options?: UseMutationOptions>, Error> +) => { + return useMutation>, Error>(() => { + return stopWorkflow(params); + }, options); +}; + /** * useReloadSchema is a mutate hook that reloads schemas in one or more * keyspaces, shards, or tablets in the cluster, depending on the request parameters.