github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/ui/src/components/CreateOrUpdateTask/index.tsx (about) 1 import React, { useState, useCallback, useEffect } from 'react' 2 import { useTranslation } from 'react-i18next' 3 import { omit } from 'lodash-es' 4 import { useNavigate, useLocation } from 'react-router-dom' 5 6 import { Card, Form, Steps, message } from '~/uikit' 7 import { 8 OnDuplicateBehavior, 9 Task, 10 TaskFormData, 11 TaskMode, 12 TaskShardMode, 13 useDmapiCreateTaskMutation, 14 useDmapiStartTaskMutation, 15 useDmapiUpdateTaskMutation, 16 } from '~/models/task' 17 import BasicInfo from '~/components/CreateOrUpdateTask/BasicInfo' 18 import SourceInfo from '~/components/CreateOrUpdateTask/SourceInfo' 19 import TargetInfo from '~/components/CreateOrUpdateTask/TargetInfo' 20 import EventFilters from '~/components/CreateOrUpdateTask/EventFilter' 21 import MigrateRule from '~/components/CreateOrUpdateTask/MigrateRule' 22 import CreateTaskEditorMode from '~/components/CreateOrUpdateTask/CreateTaskEditorMode' 23 import { isEmptyObject } from '~/utils/isEmptyObject' 24 25 const { Step } = Steps 26 27 const defaultValue: Partial<TaskFormData> = { 28 name: '', 29 task_mode: TaskMode.ALL, 30 shard_mode: TaskShardMode.PESSIMISTIC, 31 meta_schema: '', 32 enhance_online_schema_change: true, 33 on_duplicate: OnDuplicateBehavior.REPLACE, 34 target_config: { 35 host: '', 36 port: 3306, 37 user: '', 38 password: '', 39 }, 40 source_config: { 41 source_conf: [], 42 }, 43 table_migrate_rule: [], 44 } 45 46 const stepComponents = [ 47 BasicInfo, 48 SourceInfo, 49 TargetInfo, 50 EventFilters, 51 MigrateRule, 52 ] 53 54 const CreateTaskConfig: React.FC<{ 55 data?: Task | null 56 className?: string 57 }> = ({ data, className }) => { 58 const [t] = useTranslation() 59 const loc = useLocation() 60 const navigate = useNavigate() 61 const [loading, setLoading] = useState(true) 62 const [currentStep, setCurrentStep] = useState(0) 63 const [taskData, setTaskData] = useState<TaskFormData>( 64 defaultValue as TaskFormData 65 ) 66 const [createTask] = useDmapiCreateTaskMutation() 67 const [updateTask] = useDmapiUpdateTaskMutation() 68 const [startTask] = useDmapiStartTaskMutation() 69 const desciptions = [ 70 t('create task basic info desc'), 71 t('create task source info desc'), 72 t('create task target info desc'), 73 t('create task event filters desc'), 74 t('create task migrate rule desc'), 75 ] 76 77 const goNextStep = useCallback(() => { 78 setCurrentStep(c => c + 1) 79 }, []) 80 const goPrevStep = useCallback(() => { 81 setCurrentStep(c => c - 1) 82 }, []) 83 84 const handleSubmit = async (taskData: TaskFormData) => { 85 const isEditing = Boolean(data) 86 const payload = { ...taskData } 87 const startAfterSaved = payload.start_after_saved 88 if (payload.shard_mode === TaskShardMode.NONE) { 89 delete payload.shard_mode 90 } 91 if ('binlog_filter_rule_array' in payload) { 92 delete payload.binlog_filter_rule_array 93 } 94 if (isEmptyObject(payload.target_config.security)) { 95 delete payload.target_config.security 96 } 97 if ('start_after_saved' in payload) { 98 delete payload.start_after_saved 99 } 100 if (payload.table_migrate_rule.length > 0) { 101 payload.table_migrate_rule.forEach(i => 102 isEmptyObject(i.target) ? delete i.target : null 103 ) 104 } 105 const handler = isEditing ? updateTask : createTask 106 const key = 'createTask-' + Date.now() 107 message.loading({ content: t('requesting'), key }) 108 try { 109 const data = await handler({ task: payload as Task }).unwrap() 110 if (startAfterSaved) { 111 await startTask({ taskName: payload.name }).unwrap() 112 } 113 if (data.check_result) { 114 message.success({ content: data.check_result, key }) 115 } else { 116 message.success({ content: t('request success'), key }) 117 } 118 navigate('/migration/task') 119 } catch (e) { 120 message.destroy(key) 121 } 122 } 123 124 const getStepComponent = () => { 125 const Comp = stepComponents[currentStep] 126 return React.cloneElement(<Comp />, { 127 prev: goPrevStep, 128 next: goNextStep, 129 submit: handleSubmit, 130 initialValues: taskData, 131 isEditing: Boolean(data), 132 }) 133 } 134 135 useEffect(() => { 136 if (data) { 137 setTaskData({ 138 ...omit(data, 'status_list'), 139 binlog_filter_rule_array: Object.entries( 140 data?.binlog_filter_rule ?? {} 141 ).map(([name, value]) => ({ name, ...value })), 142 }) 143 } 144 setLoading(false) 145 }, [data]) 146 147 if (loc.hash === '#configFile') { 148 return ( 149 <div className={className}> 150 <CreateTaskEditorMode 151 initialValues={taskData} 152 onSubmit={data => handleSubmit(data)} 153 /> 154 </div> 155 ) 156 } 157 158 return ( 159 <Card className="!m-4" loading={loading}> 160 <div className="mb-8 p-4 rounded bg-white border-1 border-gray-300 border-dashed whitespace-pre-line"> 161 {desciptions[currentStep]} 162 </div> 163 164 <Steps current={currentStep}> 165 <Step title={t('basic info')} /> 166 <Step title={t('source info')} /> 167 <Step title={t('target info')} /> 168 <Step title={t('event filter')} /> 169 <Step title={t('migrate rules')} /> 170 </Steps> 171 <div className="mt-8"> 172 <Form.Provider 173 onFormFinish={(formName, { values }) => { 174 const nextTaskData = { ...taskData, ...values } 175 setTaskData(nextTaskData) 176 177 if (currentStep === stepComponents.length - 1) { 178 handleSubmit(nextTaskData) 179 } else { 180 goNextStep() 181 } 182 }} 183 > 184 {getStepComponent()} 185 </Form.Provider> 186 </div> 187 </Card> 188 ) 189 } 190 191 export default CreateTaskConfig