From afdb26b52749ecf9b5179fd639d162dfb592e2e6 Mon Sep 17 00:00:00 2001 From: Noble Mittal <62551163+beingnoble03@users.noreply.github.com> Date: Tue, 25 Feb 2025 15:00:51 +0530 Subject: [PATCH] VTAdmin: Add advanced workflow `switchtraffic` options (#17658) Signed-off-by: Noble Mittal --- go/vt/vtadmin/api.go | 10 - web/vtadmin/src/components/dialog/Dialog.tsx | 1 + .../src/components/dropdown/Dropdown.tsx | 1 + .../routes/workflows/WorkflowActions.test.tsx | 466 ++++++++++++++++++ .../routes/workflows/WorkflowActions.tsx | 229 +++++++-- web/vtadmin/src/components/toggle/Toggle.tsx | 4 +- .../src/components/tooltip/Tooltip.tsx | 2 +- 7 files changed, 667 insertions(+), 46 deletions(-) create mode 100644 web/vtadmin/src/components/routes/workflows/WorkflowActions.test.tsx diff --git a/go/vt/vtadmin/api.go b/go/vt/vtadmin/api.go index 1182d694e19..6ed5762f96e 100644 --- a/go/vt/vtadmin/api.go +++ b/go/vt/vtadmin/api.go @@ -2878,16 +2878,6 @@ func (api *API) WorkflowSwitchTraffic(ctx context.Context, req *vtadminpb.Workfl return nil, err } - // Set the default options which are not supported in VTAdmin Web. - req.Request.TabletTypes = []topodatapb.TabletType{ - topodatapb.TabletType_PRIMARY, - topodatapb.TabletType_REPLICA, - topodatapb.TabletType_RDONLY, - } - req.Request.Timeout = protoutil.DurationToProto(workflow.DefaultTimeout) - req.Request.MaxReplicationLagAllowed = protoutil.DurationToProto(vreplcommon.MaxReplicationLagDefault) - req.Request.EnableReverseReplication = true - return c.Vtctld.WorkflowSwitchTraffic(ctx, req.Request) } diff --git a/web/vtadmin/src/components/dialog/Dialog.tsx b/web/vtadmin/src/components/dialog/Dialog.tsx index ab1dcf44ef1..3181c1a4159 100644 --- a/web/vtadmin/src/components/dialog/Dialog.tsx +++ b/web/vtadmin/src/components/dialog/Dialog.tsx @@ -119,6 +119,7 @@ const Dialog: React.FC = ({ disabled={loading} type="button" className="btn" + data-testid="confirm-btn" onClick={() => { onConfirm?.(); }} diff --git a/web/vtadmin/src/components/dropdown/Dropdown.tsx b/web/vtadmin/src/components/dropdown/Dropdown.tsx index a0db3cdd313..e3e961a3e5d 100644 --- a/web/vtadmin/src/components/dropdown/Dropdown.tsx +++ b/web/vtadmin/src/components/dropdown/Dropdown.tsx @@ -32,6 +32,7 @@ export const DropdownButton: React.FC<{ icon: Icons; title?: string; className?: className )} id="menu-button" + data-testid="dropdown-btn" aria-haspopup="true" aria-label={title || 'change'} title={title || 'change'} diff --git a/web/vtadmin/src/components/routes/workflows/WorkflowActions.test.tsx b/web/vtadmin/src/components/routes/workflows/WorkflowActions.test.tsx new file mode 100644 index 00000000000..78416b8c113 --- /dev/null +++ b/web/vtadmin/src/components/routes/workflows/WorkflowActions.test.tsx @@ -0,0 +1,466 @@ +/** + * Copyright 2025 The Vitess Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { userEvent } from '@testing-library/user-event'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import WorkflowActions from './WorkflowActions'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { vtadmin } from '../../../proto/vtadmin'; + +import * as httpAPI from '../../../api/http'; + +describe('WorkflowActions', () => { + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, + }); + + const stoppedWorkflowProps = { + streamsByState: { + Stopped: [{}], + }, + workflows: [ + new vtadmin.Workflow({ + keyspace: 'test_keyspace', + }), + ], + refetchWorkflows: vi.fn(), + keyspace: 'test_keyspace', + clusterID: 'test_cluster', + name: 'test_workflow', + workflowType: 'MoveTables', + }; + + const runningWorkflowProps = { + streamsByState: { + Running: [{}], + }, + workflows: [ + new vtadmin.Workflow({ + keyspace: 'test_keyspace', + }), + ], + refetchWorkflows: vi.fn(), + keyspace: 'test_keyspace', + clusterID: 'test_cluster', + name: 'test_workflow', + workflowType: 'MoveTables', + }; + + const switchedWorkflowProps = { + streamsByState: { + Stopped: [{}], + }, + workflows: [ + new vtadmin.Workflow({ + keyspace: 'test_keyspace', + cluster: { + id: 'test_cluster', + }, + workflow: { + name: 'test_workflow', + }, + }), + new vtadmin.Workflow({ + keyspace: 'test_keyspace', + cluster: { + id: 'test_cluster', + }, + workflow: { + name: 'test_workflow_reverse', + }, + }), + ], + refetchWorkflows: vi.fn(), + keyspace: 'test_keyspace', + clusterID: 'test_cluster', + name: 'test_workflow', + workflowType: 'MoveTables', + }; + + beforeEach(() => { + const ResizeObserverMock = vi.fn(() => ({ + observe: vi.fn(), + unobserve: vi.fn(), + disconnect: vi.fn(), + })); + + vi.stubGlobal('ResizeObserver', ResizeObserverMock); + vi.restoreAllMocks(); + }); + + it('test Start Workflow dialog and API', async () => { + render( + + + + ); + + fireEvent.click(screen.getByTestId('dropdown-btn')); + fireEvent.click(screen.getByText('Start Workflow')); + // expect workflow name to be in the modal/dialog. + expect(screen.getByText('test_workflow')).toBeDefined(); + + // verify the API parameters, if "Start" button is clicked. + const startWorkflowSpy = vi.spyOn(httpAPI, 'startWorkflow'); + fireEvent.click(screen.getByText('Start')); + + await waitFor(() => { + expect(startWorkflowSpy).toHaveBeenCalledTimes(1); + }); + expect(startWorkflowSpy).toHaveBeenCalledWith({ + clusterID: 'test_cluster', + keyspace: 'test_keyspace', + name: 'test_workflow', + }); + }); + + it('test Stop Workflow dialog and API', async () => { + render( + + + + ); + fireEvent.click(screen.getByTestId('dropdown-btn')); + fireEvent.click(screen.getByText('Stop Workflow')); + // expect workflow name to be in the modal/dialog. + expect(screen.getByText('test_workflow')).toBeDefined(); + + // verify the API parameters, if "Stop" button is clicked. + const stopWorkflowSpy = vi.spyOn(httpAPI, 'stopWorkflow'); + fireEvent.click(screen.getByText('Stop')); + + await waitFor(() => { + expect(stopWorkflowSpy).toHaveBeenCalledTimes(1); + }); + expect(stopWorkflowSpy).toHaveBeenCalledWith({ + clusterID: 'test_cluster', + keyspace: 'test_keyspace', + name: 'test_workflow', + }); + }); + + it('test Complete Workflow dialog and API', async () => { + render( + + + + ); + fireEvent.click(screen.getByTestId('dropdown-btn')); + fireEvent.click(screen.getByText('Complete')); + // expect workflow name to be in the modal/dialog. + expect(screen.getByText(/test_workflow/)).toBeDefined(); + + // verify the API parameters, if "Complete" button is clicked. + const completeWorkflowSpy = vi.spyOn(httpAPI, 'completeMoveTables'); + fireEvent.click(screen.getByTestId('confirm-btn')); + + await waitFor(() => { + expect(completeWorkflowSpy).toHaveBeenCalledTimes(1); + }); + expect(completeWorkflowSpy).toHaveBeenCalledWith({ + clusterID: 'test_cluster', + request: { + workflow: 'test_workflow', + target_keyspace: 'test_keyspace', + keep_data: false, + keep_routing_rules: false, + rename_tables: false, + }, + }); + }); + + it('test Complete Workflow with toggled options', async () => { + render( + + + + ); + fireEvent.click(screen.getByTestId('dropdown-btn')); + fireEvent.click(screen.getByText('Complete')); + // expect workflow name to be in the modal/dialog. + expect(screen.getByText(/test_workflow/)).toBeDefined(); + + // verify the API parameters, if "Complete" button is clicked. + const completeWorkflowSpy = vi.spyOn(httpAPI, 'completeMoveTables'); + + // Toggle keep_data. + fireEvent.click(screen.getByTestId('toggle-keep-data')); + + // Toggle keep_routing_rules. + fireEvent.click(screen.getByTestId('toggle-routing-rules')); + + // Toggle rename_tables. + fireEvent.click(screen.getByTestId('toggle-rename-tables')); + + fireEvent.click(screen.getByTestId('confirm-btn')); + + await waitFor(() => { + expect(completeWorkflowSpy).toHaveBeenCalledTimes(1); + }); + expect(completeWorkflowSpy).toHaveBeenCalledWith({ + clusterID: 'test_cluster', + request: { + workflow: 'test_workflow', + target_keyspace: 'test_keyspace', + // Toggled options. + keep_data: true, + keep_routing_rules: true, + rename_tables: true, + }, + }); + }); + + it('test Cancel Workflow dialog and API', async () => { + render( + + + + ); + fireEvent.click(screen.getByTestId('dropdown-btn')); + fireEvent.click(screen.getByText('Cancel Workflow')); + // expect workflow name to be in the modal/dialog. + expect(screen.getByText(/test_workflow/)).toBeDefined(); + + // verify the API parameters, if "Confirm" button is clicked. + const cancelWorkflowSpy = vi.spyOn(httpAPI, 'workflowDelete'); + fireEvent.click(screen.getByTestId('confirm-btn')); + + await waitFor(() => { + expect(cancelWorkflowSpy).toHaveBeenCalledTimes(1); + }); + expect(cancelWorkflowSpy).toHaveBeenCalledWith({ + clusterID: 'test_cluster', + request: { + workflow: 'test_workflow', + keyspace: 'test_keyspace', + keep_data: false, + keep_routing_rules: false, + }, + }); + }); + + it('test Cancel Workflow dialog with toggled options', async () => { + render( + + + + ); + fireEvent.click(screen.getByTestId('dropdown-btn')); + fireEvent.click(screen.getByText('Cancel Workflow')); + // expect workflow name to be in the modal/dialog. + expect(screen.getByText(/test_workflow/)).toBeDefined(); + + // verify the API parameters, if "Confirm" button is clicked. + const cancelWorkflowSpy = vi.spyOn(httpAPI, 'workflowDelete'); + + // Toggle keep_data. + fireEvent.click(screen.getByTestId('toggle-keep-data')); + + // Toggle keep_routing_rules. + fireEvent.click(screen.getByTestId('toggle-routing-rules')); + + fireEvent.click(screen.getByTestId('confirm-btn')); + + await waitFor(() => { + expect(cancelWorkflowSpy).toHaveBeenCalledTimes(1); + }); + expect(cancelWorkflowSpy).toHaveBeenCalledWith({ + clusterID: 'test_cluster', + request: { + workflow: 'test_workflow', + keyspace: 'test_keyspace', + // Toggled options. + keep_data: true, + keep_routing_rules: true, + }, + }); + }); + + it('test Switch Traffic dialog with default options', async () => { + render( + + + + ); + fireEvent.click(screen.getByTestId('dropdown-btn')); + fireEvent.click(screen.getByText('Switch Traffic')); + // expect workflow name to be in the modal/dialog. + expect(screen.getByText(/Switch traffic for the test_workflow workflow/)).toBeDefined(); + + // verify the API parameters, if "Switch" button is clicked. + const switchTrafficWorkflowSpy = vi.spyOn(httpAPI, 'workflowSwitchTraffic'); + fireEvent.click(screen.getByText('Switch')); + + await waitFor(() => { + expect(switchTrafficWorkflowSpy).toHaveBeenCalledTimes(1); + }); + expect(switchTrafficWorkflowSpy).toHaveBeenCalledWith({ + clusterID: 'test_cluster', + request: { + keyspace: 'test_keyspace', + workflow: 'test_workflow', + direction: 0, + enable_reverse_replication: true, + force: false, + max_replication_lag_allowed: { seconds: 30 }, + timeout: { seconds: 30 }, + initialize_target_sequences: false, + tablet_types: [1, 2, 3], + }, + }); + }); + + it('test Switch Traffic with different options', async () => { + render( + + + + ); + fireEvent.click(screen.getByTestId('dropdown-btn')); + fireEvent.click(screen.getByText('Switch Traffic')); + // expect workflow name to be in the modal/dialog. + expect(screen.getByText(/Switch traffic for the test_workflow workflow/)).toBeDefined(); + + // Change timeout input. + await userEvent.clear(screen.getByTestId('input-timeout')); + await userEvent.type(screen.getByTestId('input-timeout'), '32'); + + // Change max_replication_lag_allowed input. + await userEvent.clear(screen.getByTestId('input-max-repl-lag-allowed')); + await userEvent.type(screen.getByTestId('input-max-repl-lag-allowed'), '54'); + + // Toggle enable_reverse_replication. + fireEvent.click(screen.getByTestId('toggle-enable-reverse-repl')); + + // Toggle force. + fireEvent.click(screen.getByTestId('toggle-force')); + + // Toggle initialize_target_sequences. + fireEvent.click(screen.getByTestId('toggle-init-target-seq')); + + const switchTrafficWorkflowSpy = vi.spyOn(httpAPI, 'workflowSwitchTraffic'); + fireEvent.click(screen.getByText('Switch')); + + await waitFor(() => { + expect(switchTrafficWorkflowSpy).toHaveBeenCalledTimes(1); + }); + expect(switchTrafficWorkflowSpy).toHaveBeenCalledWith({ + clusterID: 'test_cluster', + request: { + keyspace: 'test_keyspace', + workflow: 'test_workflow', + direction: 0, + // Expect enable_reverse_replication to be toggled. + enable_reverse_replication: false, + // Expect force to be toggled. + force: true, + // Expect timeout to be changed to '54' sec. + max_replication_lag_allowed: { seconds: 54 }, + // Expect timeout to be changed to '32' sec. + timeout: { seconds: 32 }, + // Expect initialize_target_sequences to be changed to true. + initialize_target_sequences: true, + tablet_types: [1, 2, 3], + }, + }); + }); + + it('test Reverse Traffic dialog with default options', async () => { + render( + + + + ); + fireEvent.click(screen.getByTestId('dropdown-btn')); + fireEvent.click(screen.getByText('Reverse Traffic')); + // expect workflow name to be in the modal/dialog. + expect(screen.getByText(/Reverse traffic for the test_workflow workflow/)).toBeDefined(); + + // verify the API parameters, if "Switch" button is clicked. + const switchTrafficWorkflowSpy = vi.spyOn(httpAPI, 'workflowSwitchTraffic'); + fireEvent.click(screen.getByText('Reverse')); + + await waitFor(() => { + expect(switchTrafficWorkflowSpy).toHaveBeenCalledTimes(1); + }); + expect(switchTrafficWorkflowSpy).toHaveBeenCalledWith({ + clusterID: 'test_cluster', + request: { + keyspace: 'test_keyspace', + workflow: 'test_workflow', + direction: 1, + enable_reverse_replication: true, + force: false, + max_replication_lag_allowed: { seconds: 30 }, + timeout: { seconds: 30 }, + initialize_target_sequences: false, + tablet_types: [1, 2, 3], + }, + }); + }); + + it('test Reverse Traffic with different options', async () => { + render( + + + + ); + fireEvent.click(screen.getByTestId('dropdown-btn')); + fireEvent.click(screen.getByText('Reverse Traffic')); + // expect workflow name to be in the modal/dialog. + expect(screen.getByText(/Reverse traffic for the test_workflow workflow/)).toBeDefined(); + + // Change timeout input. + await userEvent.clear(screen.getByTestId('input-timeout')); + await userEvent.type(screen.getByTestId('input-timeout'), '32'); + + // Change max_replication_lag_allowed input. + await userEvent.clear(screen.getByTestId('input-max-repl-lag-allowed')); + await userEvent.type(screen.getByTestId('input-max-repl-lag-allowed'), '54'); + + // Toggle enable_reverse_replication. + fireEvent.click(screen.getByTestId('toggle-enable-reverse-repl')); + + // Toggle force. + fireEvent.click(screen.getByTestId('toggle-force')); + + const switchTrafficWorkflowSpy = vi.spyOn(httpAPI, 'workflowSwitchTraffic'); + fireEvent.click(screen.getByText('Reverse')); + + await waitFor(() => { + expect(switchTrafficWorkflowSpy).toHaveBeenCalledTimes(1); + }); + expect(switchTrafficWorkflowSpy).toHaveBeenCalledWith({ + clusterID: 'test_cluster', + request: { + keyspace: 'test_keyspace', + workflow: 'test_workflow', + direction: 1, + // Expect enable_reverse_replication to be toggled. + enable_reverse_replication: false, + // Expect force to be toggled. + force: true, + // Expect timeout to be changed to '54' sec. + max_replication_lag_allowed: { seconds: 54 }, + // Expect timeout to be changed to '32' sec. + timeout: { seconds: 32 }, + initialize_target_sequences: false, + tablet_types: [1, 2, 3], + }, + }); + }); +}); diff --git a/web/vtadmin/src/components/routes/workflows/WorkflowActions.tsx b/web/vtadmin/src/components/routes/workflows/WorkflowActions.tsx index f8ac57162b4..226d4673562 100644 --- a/web/vtadmin/src/components/routes/workflows/WorkflowActions.tsx +++ b/web/vtadmin/src/components/routes/workflows/WorkflowActions.tsx @@ -12,8 +12,12 @@ import { } from '../../../hooks/api'; import Toggle from '../../toggle/Toggle'; import { success } from '../../Snackbar'; -import { vtadmin, vtctldata } from '../../../proto/vtadmin'; +import { topodata, vtadmin, vtctldata } from '../../../proto/vtadmin'; import { getReverseWorkflow } from '../../../util/workflows'; +import { Label } from '../../inputs/Label'; +import { TextInput } from '../../TextInput'; +import { MultiSelect } from '../../inputs/MultiSelect'; +import { TABLET_TYPES } from '../../../util/tablets'; interface WorkflowActionsProps { streamsByState: { @@ -29,24 +33,44 @@ interface WorkflowActionsProps { interface CompleteMoveTablesOptions { keepData: boolean; - keepRoutingRoules: boolean; + keepRoutingRules: boolean; renameTables: boolean; } const DefaultCompleteMoveTablesOptions: CompleteMoveTablesOptions = { keepData: false, - keepRoutingRoules: false, + keepRoutingRules: false, renameTables: false, }; interface CancelWorkflowOptions { keepData: boolean; - keepRoutingRoules: boolean; + keepRoutingRules: boolean; } +interface SwitchTrafficOptions { + enableReverseReplication: boolean; + force: boolean; + initializeTargetSequences: boolean; + maxReplicationLagAllowed: number; + timeout: number; + tabletTypes: topodata.TabletType[]; +} + +const TABLET_OPTIONS = [topodata.TabletType['PRIMARY'], topodata.TabletType['REPLICA'], topodata.TabletType['RDONLY']]; + +const DefaultSwitchTrafficOptions: SwitchTrafficOptions = { + enableReverseReplication: true, + force: false, + initializeTargetSequences: false, + maxReplicationLagAllowed: 30, + timeout: 30, + tabletTypes: TABLET_OPTIONS, +}; + const DefaultCancelWorkflowOptions: CancelWorkflowOptions = { keepData: false, - keepRoutingRoules: false, + keepRoutingRules: false, }; const WorkflowActions: React.FC = ({ @@ -60,13 +84,15 @@ const WorkflowActions: React.FC = ({ }) => { const [currentDialog, setCurrentDialog] = useState(''); - const [completeMoveTablesOptions, SetCompleteMoveTablesOptions] = useState( + const [completeMoveTablesOptions, setCompleteMoveTablesOptions] = useState( DefaultCompleteMoveTablesOptions ); - const [cancelWorkflowOptions, SetCancelWorkflowOptions] = + const [cancelWorkflowOptions, setCancelWorkflowOptions] = useState(DefaultCancelWorkflowOptions); + const [switchTrafficOptions, setSwitchTrafficOptions] = useState(DefaultSwitchTrafficOptions); + const closeDialog = () => setCurrentDialog(''); const startWorkflowMutation = useStartWorkflow({ keyspace, clusterID, name }); @@ -79,6 +105,16 @@ const WorkflowActions: React.FC = ({ keyspace: keyspace, workflow: name, direction: 0, + enable_reverse_replication: switchTrafficOptions.enableReverseReplication, + force: switchTrafficOptions.force, + max_replication_lag_allowed: { + seconds: switchTrafficOptions.maxReplicationLagAllowed, + }, + timeout: { + seconds: switchTrafficOptions.timeout, + }, + initialize_target_sequences: switchTrafficOptions.initializeTargetSequences, + tablet_types: switchTrafficOptions.tabletTypes, }, }); @@ -88,6 +124,16 @@ const WorkflowActions: React.FC = ({ keyspace: keyspace, workflow: name, direction: 1, + enable_reverse_replication: switchTrafficOptions.enableReverseReplication, + force: switchTrafficOptions.force, + max_replication_lag_allowed: { + seconds: switchTrafficOptions.maxReplicationLagAllowed, + }, + timeout: { + seconds: switchTrafficOptions.timeout, + }, + initialize_target_sequences: switchTrafficOptions.initializeTargetSequences, + tablet_types: switchTrafficOptions.tabletTypes, }, }); @@ -98,7 +144,7 @@ const WorkflowActions: React.FC = ({ keyspace: keyspace, workflow: name, keep_data: cancelWorkflowOptions.keepData, - keep_routing_rules: cancelWorkflowOptions.keepRoutingRoules, + keep_routing_rules: cancelWorkflowOptions.keepRoutingRules, }, }, { @@ -115,7 +161,7 @@ const WorkflowActions: React.FC = ({ workflow: name, target_keyspace: keyspace, keep_data: completeMoveTablesOptions.keepData, - keep_routing_rules: completeMoveTablesOptions.keepRoutingRoules, + keep_routing_rules: completeMoveTablesOptions.keepRoutingRules, rename_tables: completeMoveTablesOptions.renameTables, }, }, @@ -126,7 +172,7 @@ const WorkflowActions: React.FC = ({ } ); - const isMoveTablesWorkflow = workflowType === 'MoveTables'; + const supportsComplete = workflowType.toLowerCase() === 'movetables' || workflowType.toLowerCase() === 'reshard'; const isRunning = !(streamsByState['Error'] && streamsByState['Error'].length) && @@ -148,13 +194,129 @@ const WorkflowActions: React.FC = ({ const isReverseWorkflow = name.endsWith('_reverse'); + const switchTrafficDialogBody = (initializeTargetSequences: boolean = true) => { + return ( +
+ + + TABLET_TYPES[tt]} + selectedItems={switchTrafficOptions.tabletTypes} + label="Tablet Types" + helpText={'Tablet types to switch traffic for.'} + onChange={(types) => { + setSwitchTrafficOptions({ ...switchTrafficOptions, tabletTypes: types }); + }} + placeholder="Select tablet types" + /> +
+
+ {initializeTargetSequences && ( +
+
+ )} +
+
+
+ ); + }; + return (
{!isReverseWorkflow && ( <> - {isMoveTablesWorkflow && isSwitched && ( - setCurrentDialog('Complete MoveTables')}>Complete + {supportsComplete && isSwitched && ( + setCurrentDialog('Complete Workflow')}>Complete )} {isRunning && ( setCurrentDialog('Switch Traffic')}>Switch Traffic @@ -219,10 +381,12 @@ const WorkflowActions: React.FC = ({ } /> = ({ )}
} - body={ -
- Switch traffic for the {name} workflow. -
- } + body={switchTrafficDialogBody(workflowType.toLowerCase() !== 'reshard')} /> = ({ )} } - body={ -
- Reverse traffic for the {name} workflow. -
- } + body={switchTrafficDialogBody(false)} /> = ({ - SetCancelWorkflowOptions((prevOptions) => ({ + setCancelWorkflowOptions((prevOptions) => ({ ...prevOptions, keepData: !prevOptions.keepData, })) @@ -314,11 +473,12 @@ const WorkflowActions: React.FC = ({

- SetCancelWorkflowOptions((prevOptions) => ({ + setCancelWorkflowOptions((prevOptions) => ({ ...prevOptions, - keepRoutingRoules: !prevOptions.keepRoutingRoules, + keepRoutingRules: !prevOptions.keepRoutingRules, })) } /> @@ -328,7 +488,7 @@ const WorkflowActions: React.FC = ({ /> = ({ errorText={`Error occured while completing workflow ${name}`} errorDescription={completeMoveTablesMutation.error ? completeMoveTablesMutation.error.message : ''} closeDialog={closeDialog} - isOpen={currentDialog === 'Complete MoveTables'} + isOpen={currentDialog === 'Complete Workflow'} refetchWorkflows={refetchWorkflows} body={
@@ -350,8 +510,9 @@ const WorkflowActions: React.FC = ({
- SetCompleteMoveTablesOptions((prevOptions) => ({ + setCompleteMoveTablesOptions((prevOptions) => ({ ...prevOptions, keepData: !prevOptions.keepData, })) @@ -367,11 +528,12 @@ const WorkflowActions: React.FC = ({

- SetCompleteMoveTablesOptions((prevOptions) => ({ + setCompleteMoveTablesOptions((prevOptions) => ({ ...prevOptions, - keepRoutingRoules: !prevOptions.keepRoutingRoules, + keepRoutingRules: !prevOptions.keepRoutingRules, })) } /> @@ -387,8 +549,9 @@ const WorkflowActions: React.FC = ({ - SetCompleteMoveTablesOptions((prevOptions) => ({ + setCompleteMoveTablesOptions((prevOptions) => ({ ...prevOptions, renameTables: !prevOptions.renameTables, })) diff --git a/web/vtadmin/src/components/toggle/Toggle.tsx b/web/vtadmin/src/components/toggle/Toggle.tsx index b1533af8f01..333d4029494 100644 --- a/web/vtadmin/src/components/toggle/Toggle.tsx +++ b/web/vtadmin/src/components/toggle/Toggle.tsx @@ -6,10 +6,10 @@ interface ToggleParams { className?: string; } -const Toggle: React.FC = ({ enabled, className, onChange }) => { +const Toggle: React.FC = ({ enabled, className, onChange, ...props }) => { return (
- + Use setting diff --git a/web/vtadmin/src/components/tooltip/Tooltip.tsx b/web/vtadmin/src/components/tooltip/Tooltip.tsx index c74184fd060..399a4dc46de 100644 --- a/web/vtadmin/src/components/tooltip/Tooltip.tsx +++ b/web/vtadmin/src/components/tooltip/Tooltip.tsx @@ -72,7 +72,7 @@ export const Tooltip = ({ children, text }: TooltipProps) => { }); return ( - + {cloneChildren} );