github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/ui/src/components/CreateOrUpdateTask/MigrateRule.tsx (about)

     1  import React, { useEffect, useMemo, useState } from 'react'
     2  import { useTranslation } from 'react-i18next'
     3  
     4  import {
     5    Form,
     6    Button,
     7    Card,
     8    Select,
     9    Input,
    10    Cascader,
    11    message,
    12    Space,
    13    Checkbox,
    14  } from '~/uikit'
    15  import {
    16    FileAddOutlined,
    17    DoubleRightOutlined,
    18    CloseOutlined,
    19    DatabaseOutlined,
    20    LoadingOutlined,
    21  } from '~/uikit/icons'
    22  import { StepCompnent } from '~/components/CreateOrUpdateTask/shared'
    23  import {
    24    useDmapiGetSourceSchemaListQuery,
    25    useDmapiGetSourceTableListMutation,
    26  } from '~/models/source'
    27  
    28  const itemLayout = {
    29    labelCol: { span: 6 },
    30    wrapperCol: { span: 18 },
    31  }
    32  
    33  const createPattern = (name: string[]) => {
    34    if (name.length === 0) return '*'
    35    if (name.length === 1) return name[0]
    36    return '~' + name.map(i => `^${i}$`).join('|')
    37  }
    38  
    39  const MigrateRule: StepCompnent = ({ prev, initialValues }) => {
    40    const [t] = useTranslation()
    41    const [currentSource, setCurrentSource] = useState('')
    42    const [cascaderValue, setCascaderValue] = useState<string[][]>([])
    43    const [form] = Form.useForm()
    44    const [getSourceTable] = useDmapiGetSourceTableListMutation()
    45    const { data: schemas, isFetching } = useDmapiGetSourceSchemaListQuery(
    46      { sourceName: currentSource },
    47      { skip: !currentSource }
    48    )
    49    const selectedSource = useMemo(() => {
    50      return (
    51        initialValues?.source_config?.source_conf?.map(i => i.source_name) ?? []
    52      )
    53    }, [initialValues])
    54  
    55    const [cascaderOptions, setCascaderOptions] = useState(
    56      schemas?.map(item => ({
    57        value: item,
    58        label: item,
    59        isLeaf: false,
    60      })) ?? []
    61    )
    62  
    63    const loadCascaderData = async (selectedOptions: any) => {
    64      const targetOption = selectedOptions[selectedOptions.length - 1]
    65      targetOption.loading = true
    66      const res = await getSourceTable({
    67        sourceName: currentSource,
    68        schemaName: targetOption.value,
    69      }).unwrap()
    70      targetOption.loading = false
    71      targetOption.children = res.map(item => ({
    72        value: item,
    73        label: item,
    74        isLeaf: true,
    75      }))
    76      setCascaderOptions([...cascaderOptions])
    77    }
    78  
    79    const handelSchemaSelectInCascader = (value: string[][], field: any) => {
    80      setCascaderValue(value)
    81      const dbPattern = createPattern([
    82        ...new Set(value.map((item: string[]) => item[0])),
    83      ] as string[])
    84      const tablePattern = createPattern([
    85        ...new Set(value.map((item: string[]) => item[1]).filter(Boolean)),
    86      ] as string[])
    87      const newData = [...form.getFieldValue('table_migrate_rule')]
    88      newData[field.key] = {
    89        ...newData[field.key],
    90        source: {
    91          ...newData[field.key].source,
    92          schema: dbPattern,
    93          table: tablePattern,
    94        },
    95      }
    96      form.setFieldsValue({
    97        table_migrate_rule: newData,
    98      })
    99    }
   100  
   101    const handleSubmit = (startAfterSaved: boolean) => {
   102      const rules = form.getFieldValue('table_migrate_rule')
   103      if (rules.length === 0) {
   104        message.error({
   105          content: t('migrate rules is required'),
   106        })
   107        return
   108      }
   109      if (startAfterSaved) {
   110        form.setFieldsValue({
   111          ...form.getFieldsValue(),
   112          start_after_saved: true,
   113        })
   114      }
   115      form.submit()
   116    }
   117  
   118    useEffect(() => {
   119      if (schemas) {
   120        setCascaderOptions(
   121          schemas?.map(item => ({
   122            value: item,
   123            label: item,
   124            isLeaf: false,
   125          }))
   126        )
   127  
   128        setCascaderValue([])
   129      }
   130    }, [schemas])
   131  
   132    return (
   133      <Form initialValues={initialValues} name="migrateRule" form={form}>
   134        <div className="grid grid-cols-1 gap-4 auto-rows-fr my-4">
   135          <Form.List name="table_migrate_rule">
   136            {(fields, { add, remove }) => (
   137              <>
   138                {fields.map(field => (
   139                  <Card
   140                    hoverable
   141                    key={field.key}
   142                    className="min-h-200px relative !border-[#d9d9d9] group"
   143                  >
   144                    <CloseOutlined
   145                      onClick={() => remove(field.name)}
   146                      className="!text-gray-500 absolute top-2 right-2 group-hover:opacity-100 opacity-0 transition"
   147                    />
   148                    <div className="grid grid-cols-26">
   149                      <div className="col-span-8">
   150                        <h1 className="font-bold">{t('source')}</h1>
   151                        <Form.Item
   152                          label={t('source')}
   153                          name={[field.name, 'source', 'source_name']}
   154                          {...itemLayout}
   155                        >
   156                          <Select
   157                            onChange={val => setCurrentSource(val as string)}
   158                          >
   159                            {selectedSource.map(s => (
   160                              <Select.Option key={s} value={s}>
   161                                {s}
   162                              </Select.Option>
   163                            ))}
   164                          </Select>
   165                        </Form.Item>
   166                        <Form.Item
   167                          label={t('database')}
   168                          tooltip={t(
   169                            'create task migrate rule source schema tooltip'
   170                          )}
   171                          name={[field.name, 'source', 'schema']}
   172                          {...itemLayout}
   173                        >
   174                          <Input
   175                            placeholder="*"
   176                            addonAfter={
   177                              <Cascader
   178                                options={cascaderOptions}
   179                                loadData={loadCascaderData}
   180                                multiple
   181                                maxTagCount="responsive"
   182                                value={cascaderValue}
   183                                onChange={(val: any) =>
   184                                  handelSchemaSelectInCascader(val, field)
   185                                }
   186                              >
   187                                {isFetching ? (
   188                                  <LoadingOutlined />
   189                                ) : (
   190                                  <DatabaseOutlined />
   191                                )}
   192                              </Cascader>
   193                            }
   194                          />
   195                        </Form.Item>
   196                        <Form.Item
   197                          label={t('table')}
   198                          tooltip={t(
   199                            'create task migrate rule source table tooltip'
   200                          )}
   201                          name={[field.name, 'source', 'table']}
   202                          {...itemLayout}
   203                        >
   204                          <Input placeholder="tbl_*" />
   205                        </Form.Item>
   206                      </div>
   207  
   208                      <div className="flex items-center justify-center col-span-1">
   209                        <DoubleRightOutlined />
   210                      </div>
   211  
   212                      <div className="col-span-8">
   213                        <h1 className="font-bold">{t('event filter')}</h1>
   214                        <Form.Item
   215                          label={t('event filter')}
   216                          {...itemLayout}
   217                          name={[field.name, 'binlog_filter_rule']}
   218                          tooltip={t(
   219                            'create task migrate rule binlog_filter_rule tooltip'
   220                          )}
   221                        >
   222                          <Select
   223                            mode="multiple"
   224                            allowClear
   225                            placeholder="Please select filter"
   226                          >
   227                            {initialValues?.binlog_filter_rule_array?.map(
   228                              rule => (
   229                                <Select.Option key={rule.name} value={rule.name}>
   230                                  {rule.name}
   231                                </Select.Option>
   232                              )
   233                            )}
   234                          </Select>
   235                        </Form.Item>
   236                      </div>
   237  
   238                      <div className="flex items-center justify-center col-span-1">
   239                        <DoubleRightOutlined />
   240                      </div>
   241  
   242                      <div className="col-span-8">
   243                        <h1 className="font-bold">{t('target')}</h1>
   244                        <Form.Item label={t('target')} {...itemLayout}>
   245                          <Input
   246                            value={initialValues?.target_config.host}
   247                            disabled
   248                          />
   249                        </Form.Item>
   250                        <Form.Item
   251                          label={t('database')}
   252                          tooltip={t(
   253                            'create task migrate rule target schema tooltip'
   254                          )}
   255                          name={[field.name, 'target', 'schema']}
   256                          {...itemLayout}
   257                        >
   258                          <Input />
   259                        </Form.Item>
   260                        <Form.Item
   261                          label={t('table')}
   262                          tooltip={t(
   263                            'create task migrate rule target table tooltip'
   264                          )}
   265                          name={[field.name, 'target', 'table']}
   266                          {...itemLayout}
   267                        >
   268                          <Input />
   269                        </Form.Item>
   270                      </div>
   271                    </div>
   272                  </Card>
   273                ))}
   274  
   275                <Button
   276                  className="!h-200px"
   277                  type="dashed"
   278                  icon={<FileAddOutlined />}
   279                  onClick={() =>
   280                    add({
   281                      source: { schema: '', table: '' },
   282                      binlog_filter_rule: [],
   283                    })
   284                  }
   285                >
   286                  {t('add migrate rule')}
   287                </Button>
   288              </>
   289            )}
   290          </Form.List>
   291          <Form.Item name="start_after_saved" hidden>
   292            <Checkbox />
   293          </Form.Item>
   294        </div>
   295  
   296        <Form.Item>
   297          <Space>
   298            <Button onClick={prev}>{t('previous')}</Button>
   299            <Button type="primary" onClick={() => handleSubmit(false)}>
   300              {t('save')}
   301            </Button>
   302            <Button type="primary" onClick={() => handleSubmit(true)}>
   303              {t('save and run')}
   304            </Button>
   305          </Space>
   306        </Form.Item>
   307      </Form>
   308    )
   309  }
   310  
   311  export default MigrateRule