Skip to content

Commit

Permalink
VTAdmin(web): Add workflow start/stop actions (#16675)
Browse files Browse the repository at this point in the history
Signed-off-by: Noble Mittal <noblemittal@outlook.com>
  • Loading branch information
beingnoble03 authored Sep 7, 2024
1 parent 5d562c7 commit c768b62
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 1 deletion.
22 changes: 22 additions & 0 deletions web/vtadmin/src/api/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <R extends pb.IVTExplainRequest>({ 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.
Expand Down
19 changes: 18 additions & 1 deletion web/vtadmin/src/components/routes/Workflows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,15 @@ 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';
import { isReadOnlyMode } from '../../util/env';

export const ThrottleThresholdSeconds = 60;

const COLUMNS = ['Workflow', 'Source', 'Target', 'Streams', 'Last Updated', 'Actions'];
const READ_ONLY_COLUMNS = ['Workflow', 'Source', 'Target', 'Streams', 'Last Updated'];

export const Workflows = () => {
useDocumentTitle('Workflows');
const workflowsQuery = useWorkflows();
Expand Down Expand Up @@ -180,6 +186,17 @@ export const Workflows = () => {
<div className="font-sans whitespace-nowrap">{formatDateTime(row.timeUpdated)}</div>
<div className="font-sans text-sm text-secondary">{formatRelativeTime(row.timeUpdated)}</div>
</DataCell>

<ReadOnlyGate>
<DataCell>
<WorkflowActions
refetchWorkflows={workflowsQuery.refetch}
keyspace={row.keyspace as string}
clusterID={row.clusterID as string}
name={row.name as string}
/>
</DataCell>
</ReadOnlyGate>
</tr>
);
});
Expand All @@ -199,7 +216,7 @@ export const Workflows = () => {
/>

<DataTable
columns={['Workflow', 'Source', 'Target', 'Streams', 'Last Updated']}
columns={isReadOnlyMode() ? READ_ONLY_COLUMNS : COLUMNS}
data={sortedData}
renderRows={renderRows}
/>
Expand Down
89 changes: 89 additions & 0 deletions web/vtadmin/src/components/routes/workflows/WorkflowAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
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<any, any, any>;
title: string;
confirmText: string;
successText: string;
errorText: string;
loadingText: string;
description?: string;
body?: JSX.Element;
successBody?: JSX.Element;
refetchWorkflows: Function;
closeDialog: () => void;
}

const WorkflowAction: React.FC<WorkflowActionProps> = ({
isOpen,
closeDialog,
mutation,
title,
confirmText,
description,
successText,
successBody,
loadingText,
errorText,
refetchWorkflows,
body,
}) => {
const onCloseDialog = () => {
setTimeout(mutation.reset, 500);
closeDialog();
};

const hasRun = mutation.data || mutation.error;
const onConfirm = () => {
mutation.mutate(
{},
{
onSuccess: () => {
refetchWorkflows();
},
}
);
};
return (
<Dialog
isOpen={isOpen}
confirmText={hasRun ? 'Close' : confirmText}
cancelText="Cancel"
onConfirm={hasRun ? onCloseDialog : onConfirm}
loadingText={loadingText}
loading={mutation.isLoading}
onCancel={onCloseDialog}
onClose={onCloseDialog}
hideCancel={hasRun}
title={hasRun ? undefined : title}
description={hasRun ? undefined : description}
>
<div className="w-full">
{!hasRun && body}
{mutation.data && !mutation.error && (
<div className="w-full flex flex-col justify-center items-center">
<span className="flex h-12 w-12 relative items-center justify-center">
<Icon className="fill-current text-green-500" icon={Icons.checkSuccess} />
</span>
<div className="text-lg mt-3 font-bold text-center">{successText}</div>
{successBody}
</div>
)}
{mutation.error && (
<div className="w-full flex flex-col justify-center items-center">
<span className="flex h-12 w-12 relative items-center justify-center">
<Icon className="fill-current text-red-500" icon={Icons.alertFail} />
</span>
<div className="text-lg mt-3 font-bold text-center">{errorText}</div>
</div>
)}
</div>
</Dialog>
);
};

export default WorkflowAction;
79 changes: 79 additions & 0 deletions web/vtadmin/src/components/routes/workflows/WorkflowActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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 {
refetchWorkflows: Function;
keyspace: string;
clusterID: string;
name: string;
}

const WorkflowActions: React.FC<WorkflowActionsProps> = ({ refetchWorkflows, keyspace, clusterID, name }) => {
const [currentDialog, setCurrentDialog] = useState<string>('');
const closeDialog = () => setCurrentDialog('');

const startWorkflowMutation = useStartWorkflow({ keyspace, clusterID, name });

const stopWorkflowMutation = useStopWorkflow({ keyspace, clusterID, name });

return (
<div className="w-min inline-block">
<Dropdown dropdownButton={Icons.info} position="bottom-right">
<MenuItem onClick={() => setCurrentDialog('Start Workflow')}>Start Workflow</MenuItem>
<MenuItem onClick={() => setCurrentDialog('Stop Workflow')}>Stop Workflow</MenuItem>
</Dropdown>
<WorkflowAction
title="Start Workflow"
confirmText="Start"
loadingText="Starting"
mutation={startWorkflowMutation}
successText="Started workflow"
errorText={`Error occured while starting workflow ${name}`}
closeDialog={closeDialog}
isOpen={currentDialog === 'Start Workflow'}
refetchWorkflows={refetchWorkflows}
successBody={
<div className="text-sm">
{startWorkflowMutation.data && startWorkflowMutation.data.summary && (
<div className="text-sm">{startWorkflowMutation.data.summary}</div>
)}
</div>
}
body={
<div className="text-sm mt-3">
Start the <span className="font-mono bg-gray-300">{name}</span> workflow.
</div>
}
/>
<WorkflowAction
title="Stop Workflow"
confirmText="Stop"
loadingText="Stopping"
mutation={stopWorkflowMutation}
successText="Stopped workflow"
errorText={`Error occured while stopping workflow ${name}`}
closeDialog={closeDialog}
isOpen={currentDialog === 'Stop Workflow'}
refetchWorkflows={refetchWorkflows}
successBody={
<div className="text-sm">
{stopWorkflowMutation.data && stopWorkflowMutation.data.summary && (
<div className="text-sm">{stopWorkflowMutation.data.summary}</div>
)}
</div>
}
body={
<div className="text-sm mt-3">
Stop the <span className="font-mono bg-gray-300">{name}</span> workflow.
</div>
}
/>
</div>
);
};

export default WorkflowActions;
26 changes: 26 additions & 0 deletions web/vtadmin/src/hooks/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<typeof startWorkflow>[0],
options?: UseMutationOptions<Awaited<ReturnType<typeof startWorkflow>>, Error>
) => {
return useMutation<Awaited<ReturnType<typeof startWorkflow>>, Error>(() => {
return startWorkflow(params);
}, options);
};

/**
* useStopWorkflow is a mutate hook that stops a workflow.
*/
export const useStopWorkflow = (
params: Parameters<typeof stopWorkflow>[0],
options?: UseMutationOptions<Awaited<ReturnType<typeof stopWorkflow>>, Error>
) => {
return useMutation<Awaited<ReturnType<typeof stopWorkflow>>, 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.
Expand Down

0 comments on commit c768b62

Please sign in to comment.