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"
+ />
+
+
+
+ setSwitchTrafficOptions((prevOptions) => ({
+ ...prevOptions,
+ enableReverseReplication: !prevOptions.enableReverseReplication,
+ }))
+ }
+ />
+
+ {initializeTargetSequences && (
+
+
+
+ setSwitchTrafficOptions((prevOptions) => ({
+ ...prevOptions,
+ initializeTargetSequences: !prevOptions.initializeTargetSequences,
+ }))
+ }
+ />
+
+ )}
+
+
+
+ setSwitchTrafficOptions((prevOptions) => ({
+ ...prevOptions,
+ force: !prevOptions.force,
+ }))
+ }
+ />
+
+
+ );
+ };
+
return (
{!isReverseWorkflow && (
<>
- {isMoveTablesWorkflow && isSwitched && (
-
+ {supportsComplete && isSwitched && (
+
)}
{isRunning && (
@@ -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}
);