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