Browse Source

feat:作业修改

Leo 2 years ago
parent
commit
9ca605e475

+ 34 - 10
src/component/CronSelect.jsx

@@ -1,4 +1,5 @@
 import { Input, Radio, Select, Space } from 'antd'
+import { set } from 'lodash'
 import React, {
   useState,
   useImperativeHandle,
@@ -34,16 +35,40 @@ const CronSelect = ({ cron_data }, ref) => {
   })
 
   useEffect(() => {
-    if (cron_data?.cron_select_type === 0) {
-      setCronType(String(cron_data.cron_select_type))
-      const keys = Object.keys(cron_data)
-      if (keys.includes('hour')) {
+    const cron_type = cron_data?.cron_select_type
+    let keys = []
+    if (cron_type) keys = Object.keys(cron_data)
+    console.log(keys, cron_data['a'])
+    setCronType(String(cron_type))
+    switch (cron_type) {
+      case 0:
+        if (keys.includes('hour')) {
+          setHourVal(cron_data['hour'])
+          onChangeHourType('hour')
+        } else {
+          setHourVal(cron_data['minute'])
+          onChangeHourType('min')
+        }
+        break
+      case 1:
+        setHourVal(cron_data['hour'])
+        setMinVal(cron_data['minute'])
+        break
+      case 2:
+        setWeekVal(cron_data['week'])
+        setHourVal(cron_data['hour'])
+        setMinVal(cron_data['minute'])
+        break
+      case 3:
+        setMonthVal(cron_data['month'])
         setHourVal(cron_data['hour'])
-        onChangeHourType('hour')
-      } else {
-        setHourVal(cron_data['minute'])
-        onChangeHourType('min')
-      }
+        setMinVal(cron_data['minute'])
+        break
+      case 4:
+        setMinVal(cron_data['cron_expression'])
+        break
+      default:
+        break
     }
   }, [cron_data])
 
@@ -76,7 +101,6 @@ const CronSelect = ({ cron_data }, ref) => {
         obj = {
           cron_select_type: Number(cronType),
           month: monthVal,
-          week: weekVal,
           hour: hourVal,
           minute: minVal,
         }

+ 117 - 83
src/module/tasklog/component/TaskLogWatcher.jsx

@@ -1,22 +1,9 @@
-import { Card, Table} from 'antd'
+import { Card, Table, Tree } from 'antd'
 import React, { useEffect, useState } from 'react'
 import { useLocation } from 'react-router-dom'
 import styled from 'styled-components'
-// import { getOnceJoblog } from '../services/index'
+import { getLogInfo, getJobLog } from '../services/index'
 
-/* const LogWrapper = styled.div`
-  padding: 30px;
-  width: 100%;
-  height: 800px;
-  background-color: #fff;
-  .log {
-    background-color: #012b36;
-    height: 100%;
-    overflow-y: scroll;
-    color: #638691;
-    padding: 8px 10px;
-  }
-` */
 const LogWrapper = styled.div`
   padding: 20px;
   display: flex;
@@ -26,7 +13,7 @@ const LogWrapper = styled.div`
     flex: 1;
   }
   .log-info {
-    width: 920px
+    width: 920px;
   }
   .log {
     background-color: #012b36;
@@ -35,31 +22,51 @@ const LogWrapper = styled.div`
     overflow-y: scroll;
     color: #638691;
     padding: 8px 10px;
+    ::-webkit-scrollbar {
+      display: none;
+    }
+  }
+
+  .node_list {
+    width: 100%;
+    height: 100%;
+  }
+  .node_table {
+    overflow: auto;
+    height: 650px
+    ::-webkit-scrollbar {
+      display: none;
+    }
   }
   .ant-table-selection-column {
     display: none !important;
   }
 `
 
+const { DirectoryTree } = Tree
 
 const TaskLogWatcher = () => {
   const { state } = useLocation()
-  const [logData, setLogData] = useState(null)
-  // 初始化作业列表
+  const [logData, setLogData] = useState('请选择节点查看任务')
+  // 初始化节点列表
   const [jobList, setJobList] = useState([])
   // 选中的作业
   const [selectJob, setSelectJob] = useState(null)
+  // 单/多作业类型
+  const [taskType, setTaskType] = useState(null)
+
+  const [treeData, setTreeData] = useState([])
 
   const columns = [
     {
       title: '序号',
       dataIndex: 'index',
-      key: 'index'
+      key: 'index',
     },
     {
       title: '作业名称',
       dataIndex: 'jobName',
-      key: 'jobName'
+      key: 'jobName',
     },
     {
       title: '执行结果',
@@ -77,84 +84,111 @@ const TaskLogWatcher = () => {
   ]
 
   const fetchOnceJoblog = async () => {
-    // const { data } = await getOnceJoblog(state.id)
-    // if (data.code === 200) {
-    //   setLogData(data.data.handle_msg)
-    // }
-    setLogData(`2022-08-17 09:28:30,1761 "/opt/conda/envs/py38/lib/python3.8/site-packages/pyhive/hive.py",line 449, INFO:USE default
-2022-08-17 09:28:30,1871 "/opt/conda/envs/py38/lib/python3.8/site-packages/pyhive/hive.py",line 449, INFO:USE default
-2022-08-17 09:28:30,2161 "/opt/conda/envs/py38/lib/python3.8/site-packages/pyhive/hive.py",line 449, INFO:describe default.my_test_p
-2022-08-17 09:28:30,2401 "/opt/conda/envs/py38/lib/python3.8/site-packages/pyhive/hive.py",line 449, INFO:describe default.my_test_p
-`)
-    setJobList([
-      {
-        key: '1',
-        index: '1',
-        jobName: '作业名称1',
-        handleResult: 1
-      },
-      {
-        key: '2',
-        index: '2',
-        jobName: '作业名称2',
-        handleResult: 0
-      },
-      {
-        key: '3',
-        index: '3',
-        jobName: '作业名称3',
-        handleResult: 1
-      },
-      {
-        key: '4',
-        index: '4',
-        jobName: '作业名称4',
-        handleResult: 1
-      },
-    ])
+    const { data } = await getLogInfo(state.id)
+    if (data.code === 200) {
+      setTaskType(data.data.job_type)
+      if (data.data.job_type === '单作业离线任务')
+        formatSingleTask(data.data.logs)
+      if (data.data.job_type === '多作业离线任务')
+        formatMultiTask(data.data.logs)
+    }
+  }
+
+  const formatSingleTask = data => {
+    const list = data.map((item, index) => ({
+      key: item.id,
+      index: index + 1,
+      jobName: item.node_name,
+      job_log_uri: item.job_log_uri,
+      handleResult: item.executor_result,
+    }))
+    setJobList(list)
+    onClickTableNode(list[0])
+  }
+
+  const formatMultiTask = data => {
+    const list = data.map((item, index) => {
+      const children = item.nodes.map((node, i) => {
+        return {
+          title: node.node_name,
+          key: `${index}-${i}`,
+          uri: node.job_log_uri,
+          isLeaf: true,
+        }
+      })
+      return {
+        title: item.homework_name,
+        key: String(index),
+        children,
+      }
+    })
+    setTreeData(list)
+  }
+
+  const fetchJobLogInfo = async uri => {
+    const { data } = await getJobLog(uri)
+    setLogData(data)
+  }
+
+  const onClickTableNode = record => {
+    setSelectJob(record.key)
+    fetchJobLogInfo(record.job_log_uri)
+  }
+
+  const onClickTreeNode = (_, info) => {
+    console.log('$$$', _, info)
+    if (info.node.uri) fetchJobLogInfo(info.node.uri)
   }
 
   useEffect(() => {
     fetchOnceJoblog()
   }, [state])
 
+  const singleTable = (
+    <Table
+      columns={columns}
+      dataSource={jobList}
+      showHeader={false}
+      className="node_table"
+      pagination={{ position: ['none', 'none'] }}
+      onRow={record => {
+        return {
+          onClick: () => onClickTableNode(record),
+        }
+      }}
+      rowSelection={{
+        type: 'radio',
+        columnWidth: '0',
+        selectedRowKeys: [selectJob],
+      }}
+    />
+  )
+  const multiTree = (
+    <DirectoryTree onSelect={onClickTreeNode} treeData={treeData} />
+  )
+
   return (
     <LogWrapper>
       {/* <pre className="log">{logData}</pre> */}
-      <div className='job-list'>
+      <div className="job-list">
         <Card
-          size='small'
-          title='作业列表'
+          size="small"
+          title="节点列表"
           bordered={false}
-          style={{height: '100%', width: '100%'}}
-          headStyle={{padding: '6px 10px', fontSize: '13px'}}
-        >
-          <Table
-            columns={columns}
-            dataSource={jobList}
-            showHeader={false}
-            onRow={record => {
-              return {
-                onClick: e => {
-                  setSelectJob(record.key)
-                  setLogData('test: ' + record.jobName)
-                }
-              }
-            }}
-            rowSelection={{
-              type: 'radio',
-              columnWidth: '0',
-              selectedRowKeys: [selectJob]
-            }}
-          />
+          className="node_list"
+          headStyle={{
+            padding: '10px 15px',
+            fontSize: '14px',
+            fontWeight: '700',
+          }}>
+          {taskType === '单作业离线任务' ? singleTable : multiTree}
         </Card>
       </div>
-      <div className='log-info'>
+      <div className="log-info">
         <Card
           bordered={false}
-          style={{height: '100%', width: '100%'}}
-          bodyStyle={{height: '100%', width: '100%'}}
-        >
+          style={{ height: '100%', width: '100%' }}
+          bodyStyle={{ height: '100%', width: '100%' }}>
           <pre className="log">{logData}</pre>
         </Card>
       </div>

+ 2 - 4
src/module/tasklog/services/index.js

@@ -8,17 +8,15 @@ export const getLogList = () =>
   })
 
 // 定时任务日志详情
-export const getLogInfo = id => {
+export const getLogInfo = id =>
   request({
     url: `/jpt/jm_job_log/logs?job_history_id=${id}`,
     method: 'get',
   })
-}
 
 // 日志文件
-export const getJobLog = uri => {
+export const getJobLog = uri =>
   request({
     url: `/jpt/files/jm_job_log/?uri=${uri}`,
     method: 'get',
   })
-}

+ 59 - 83
src/module/workmgmt/component/JobCreate.jsx

@@ -31,18 +31,7 @@ const JobCre = styled.div`
 `
 const CreContent = styled.div`
   background-color: #ffffff;
-  margin-left: 24px;
-  .ant-form {
-    display: flex;
-    flex-direction: column;
-    align-content: flex-start;
-    justify-content: center;
-    flex-wrap: wrap;
-    align-items: stretch;
-  }
-  .script_file {
-    margin-left: -29px !important;
-  }
+  padding: 20px 10px;
   .creTitle {
     width: 120px;
     height: 60px;
@@ -69,54 +58,12 @@ const CreContent = styled.div`
     line-height: 67px;
     font-weight: 800;
   }
-  .JobName {
-    margin-left: 25px;
-  }
-  .MMir {
-    margin-left: -17px !important;
-  }
-  .ant-select-arrow {
-    top: 36%;
-    padding-left: 7px;
-    height: 20px;
-    border-left: 1px solid;
-  }
-  .ant-col {
-    flex: unset;
-  }
-
-  .ant-select {
-    width: 295px;
-    height: 32px;
-  }
   .ant-form-item-control-input {
     width: 295px;
   }
-  .ant-row {
-    margin-left: 60px;
-  }
   .JaPyInfo {
-    width: 464px;
-    .ant-form {
-      align-content: end;
-    }
-    .choseJa {
-      .ant-row {
-        margin-left: 43px;
-      }
-      ,
-      .ant-form-item-control-input {
-        left: 11px;
-      }
-      .upButton {
-        width: 88px;
-        height: 32px;
-        margin-left: 7px;
-      }
-    }
     .JaPySubmit {
       display: flex;
-      margin-left: 60px;
       .Submit {
         margin-left: 20px;
         .ant-btn {
@@ -132,16 +79,6 @@ const CreContent = styled.div`
       }
     }
   }
-  .mirrorLeft {
-    margin-left: -25px;
-  }
-  .ant-radio-wrapper {
-    display: inline-block;
-  }
-  .javaSelect {
-    width: 255px;
-    left: 8px;
-  }
   .DagInfo {
     .ant-steps-item-icon {
       width: 42px;
@@ -164,10 +101,17 @@ const RadioGroup = Radio.Group
 
 const layout = {
   labelCol: {
+    span: 4,
+  },
+  wrapperCol: {
     span: 8,
   },
+}
+
+const tailLayout = {
   wrapperCol: {
-    span: 16,
+    offset: 4,
+    span: 8,
   },
 }
 
@@ -225,6 +169,8 @@ export default function JobCreate() {
 
   const [dagData, setDagData] = useState(null)
 
+  const [syncState, setSyncState] = useState(false)
+
   const [fileList, setFileList] = useState([])
 
   useEffect(() => {
@@ -245,6 +191,7 @@ export default function JobCreate() {
             tag: data.data.tag,
             image_url: data.data.image_url,
             script_file: 'fromList',
+            execute_command: data.data.execute_command,
           })
           setRadioValue('fromList')
           setSriptFile(data.data.script_file)
@@ -255,6 +202,7 @@ export default function JobCreate() {
             tag: data.data.tag,
             image_url: data.data.image_url,
             script_file: 'fromList',
+            execute_command: data.data.execute_command,
           })
           setRadioValue('fromList')
           setSriptFile(data.data.script_file)
@@ -401,6 +349,7 @@ export default function JobCreate() {
         // 镜像
         image_url: '/test/images/exa',
         relation_list,
+        execute_command: '',
       }
       let resp = {}
       if (state?.id) {
@@ -421,14 +370,19 @@ export default function JobCreate() {
     currentForm
       .validateFields()
       .then(() => {
-        changeStep(1)
+        if (currentStep === 0 || syncState) {
+          if (currentStep === 2) {
+            DagSubmit()
+          } else {
+            changeStep(1)
+          }
+        } else {
+          message.error('请检查字段对应关系')
+        }
       })
       .catch(err => {
         message.error('请检查表单数据是否完整')
       })
-    if (currentStep === 2) {
-      DagSubmit()
-    }
   }
 
   useEffect(() => {
@@ -499,7 +453,6 @@ export default function JobCreate() {
 
   //java or python提交
   const JaPySubmit = () => {
-    const Jay = JavaDataForm.getFieldValue()
     currentForm
       .validateFields()
       .then(() => {
@@ -532,7 +485,7 @@ export default function JobCreate() {
   return (
     <JobCre>
       <CreContent>
-        <div className="choseJob">
+        <div>
           <div className="creTitle">
             <div className="bluetTitle" />
             <span className="creText">配置作业类型</span>
@@ -546,6 +499,7 @@ export default function JobCreate() {
               rules={[
                 {
                   required: true,
+                  message: '请选择作业类型',
                 },
               ]}>
               <Select
@@ -579,12 +533,20 @@ export default function JobCreate() {
 
           {/* 第二步 */}
           {currentStep === 1 && (
-            <JobStepTwo InputDataForm={InputDataForm} dagData={dagData} />
+            <JobStepTwo
+              InputDataForm={InputDataForm}
+              dagData={dagData}
+              checkSync={val => setSyncState(val)}
+            />
           )}
 
           {/* 第三步 */}
           {currentStep === 2 && (
-            <JobStepThree OutputDataForm={OutputDataForm} dagData={dagData} />
+            <JobStepThree
+              OutputDataForm={OutputDataForm}
+              dagData={dagData}
+              checkSync={val => setSyncState(val)}
+            />
           )}
 
           <Space style={{ margin: '20px' }}>
@@ -616,7 +578,6 @@ export default function JobCreate() {
             <FormItem
               name="name"
               label="作业名称"
-              className="JobName"
               rules={[
                 {
                   required: true,
@@ -631,7 +592,6 @@ export default function JobCreate() {
               shouldUpdate
               name="tag"
               label="作业标签"
-              className="JobName"
               rules={[
                 {
                   required: true,
@@ -682,7 +642,6 @@ export default function JobCreate() {
 
             {/* 选择执行镜像 */}
             <FormItem
-              className="mirrorLeft MMir"
               name="image_url"
               label="配置执行的镜像"
               rules={[
@@ -698,7 +657,6 @@ export default function JobCreate() {
 
             {/* 选择执行的Java脚本 */}
             <FormItem
-              className="mirrorLeft choseJa script_file"
               label="配置执行的Java脚本"
               name="script_file"
               rules={[
@@ -734,7 +692,18 @@ export default function JobCreate() {
                 </Space>
               </RadioGroup>
             </FormItem>
-            <FormItem>
+            <FormItem
+              label="执行命令"
+              name="execute_command"
+              rules={[
+                {
+                  required: true,
+                  message: '请配置执行命令',
+                },
+              ]}>
+              <Input placeholder="请输入执行命令..." allowClear></Input>
+            </FormItem>
+            <FormItem {...tailLayout}>
               <div className="JaPySubmit">
                 <div className="Cancel">
                   <Button onClick={cancel}>取消</Button>
@@ -759,7 +728,6 @@ export default function JobCreate() {
             <FormItem
               name="name"
               label="作业名称"
-              className="JobName"
               rules={[
                 {
                   required: true,
@@ -772,7 +740,6 @@ export default function JobCreate() {
             <FormItem
               name="tag"
               label="作业标签"
-              className="JobName"
               rules={[
                 {
                   required: true,
@@ -822,7 +789,6 @@ export default function JobCreate() {
             </FormItem>
             {/* 选择执行镜像 */}
             <FormItem
-              className="mirrorLeft MMir"
               name="image_url"
               label="配置执行的镜像"
               rules={[
@@ -838,7 +804,6 @@ export default function JobCreate() {
 
             {/* 选择执行的Python脚本 */}
             <FormItem
-              className="mirrorLeft choseJa script_file"
               label="配置执行的Python脚本"
               name="script_file"
               rules={[
@@ -873,7 +838,18 @@ export default function JobCreate() {
                 </Space>
               </RadioGroup>
             </FormItem>
-            <FormItem>
+            <FormItem
+              label="执行命令"
+              name="execute_command"
+              rules={[
+                {
+                  required: true,
+                  message: '请配置执行命令',
+                },
+              ]}>
+              <Input placeholder="请输入执行命令..." allowClear></Input>
+            </FormItem>
+            <FormItem {...tailLayout}>
               <div className="JaPySubmit">
                 <div className="Cancel">
                   <Button onClick={cancel}>取消</Button>

+ 2 - 2
src/module/workmgmt/component/JobStepOne.jsx

@@ -7,10 +7,10 @@ const FormItem = Form.Item
 
 const layout = {
   labelCol: {
-    span: 8,
+    span: 4,
   },
   wrapperCol: {
-    span: 16,
+    span: 8,
   },
 }
 

+ 54 - 10
src/module/workmgmt/component/JobStepThree.jsx

@@ -1,4 +1,4 @@
-import { Alert, Button, Form, Select, Row } from 'antd'
+import { Alert, Button, Form, Select, Row, Tooltip } from 'antd'
 import styled from 'styled-components'
 import {
   getDagInfomation,
@@ -19,6 +19,9 @@ const StepTwo = styled.div`
     overflow-y: auto;
     border: 1px solid #c8d3e9;
     .dtsource {
+      .ant-form-item-label {
+        width: 120px;
+      }
       .ant-select {
         width: 80%;
       }
@@ -92,6 +95,10 @@ const StepTwo = styled.div`
         text-align: center;
         background-color: #eef5fe;
         border-radius: 2px;
+        text-overflow: ellipsis;
+        overflow: hidden;
+        white-space: nowrap;
+        padding: 0 5px;
       }
       .Dagdatasource {
         margin-left: 10px;
@@ -102,7 +109,7 @@ const StepTwo = styled.div`
 const FormItem = Form.Item
 const { Option } = Select
 
-export default function JobStepThree({ OutputDataForm, dagData }) {
+export default function JobStepTwo({ OutputDataForm, dagData, checkSync }) {
   //获取数据源节点
   const [nodes, setNodes] = useState([])
   //数据源名称
@@ -116,14 +123,18 @@ export default function JobStepThree({ OutputDataForm, dagData }) {
   // 所有预览map
   const [nodeSchemas, setNodeSchemas] = useState({})
 
+  const [nodeSchema, setNodeSchema] = useState({})
+
   //所有节点预览状态
   const [nodeStates, setNodeStates] = useState({})
 
+  const [nodeState, setNodeState] = useState({})
+
   const getDagInfo = async dag_url => {
     const data = await getDagInfomation(dag_url)
     if (data.status === 200) {
-      const nodes = data.data.nodes.filter(item => item.op === 'datasource')
-      setNodes(nodes)
+      const dag_nodes = data.data.nodes.filter(item => item.op === 'datasource')
+      setNodes(dag_nodes)
     }
   }
 
@@ -197,13 +208,12 @@ export default function JobStepThree({ OutputDataForm, dagData }) {
       })
     }
     if (list.length === 0) {
-      nodeStates[index] = true
+      setNodeState({ index, state: true })
     } else {
-      nodeStates[index] = false
+      setNodeState({ index, state: false })
     }
-    setNodeStates(nodeStates)
-    nodeSchemas[index] = syncList
-    setNodeSchemas(nodeSchemas)
+
+    setNodeSchema({ index, list: syncList })
   }
 
   const showStructure = index => {
@@ -216,6 +226,37 @@ export default function JobStepThree({ OutputDataForm, dagData }) {
   useEffect(() => {
     getDagInfo(dagData)
   }, [dagData])
+
+  useEffect(() => {
+    nodes.forEach((_, index) => {
+      const datasource = OutputDataForm.getFieldValue(`datasource${index}`)
+      const table = OutputDataForm.getFieldValue(`sourceTable${index}`)
+      onDatasourcesSelect(datasource)
+      onTableSelect(table, index)
+    })
+  }, [nodes])
+
+  useEffect(() => {
+    const states = { ...nodeStates }
+    states[nodeState.index] = nodeState.state
+    setNodeStates(states)
+  }, [nodeState])
+
+  useEffect(() => {
+    const schemas = { ...nodeSchemas }
+    schemas[nodeSchema.index] = nodeSchema.list
+    setNodeSchemas(schemas)
+  }, [nodeSchema])
+
+  useEffect(() => {
+    let result = true
+    const keys = Object.keys(nodeStates).filter(item => item !== 'undefined')
+    keys.forEach(key => {
+      if (!nodeStates[key]) result = false
+    })
+    checkSync(result)
+  }, [nodeStates])
+
   return (
     <StepTwo>
       <div className="StepTwo-content">
@@ -289,7 +330,10 @@ export default function JobStepThree({ OutputDataForm, dagData }) {
               style={{
                 display: item?.datasource?.dataField ? 'block' : 'none',
               }}>
-              <span>{`${item?.datasource?.dataField}.${item?.datasource?.dataType}`}</span>
+              <Tooltip
+                title={`${item?.datasource?.dataField}.${item?.datasource?.dataType}`}>
+                <span>{`${item?.datasource?.dataField}.${item?.datasource?.dataType}`}</span>
+              </Tooltip>
             </div>
             <span className="icon">
               <img

+ 44 - 8
src/module/workmgmt/component/JobStepTwo.jsx

@@ -19,6 +19,9 @@ const StepTwo = styled.div`
     overflow-y: auto;
     border: 1px solid #c8d3e9;
     .dtsource {
+      .ant-form-item-label {
+        width: 120px;
+      }
       .ant-select {
         width: 80%;
       }
@@ -106,7 +109,7 @@ const StepTwo = styled.div`
 const FormItem = Form.Item
 const { Option } = Select
 
-export default function JobStepTwo({ InputDataForm, dagData }) {
+export default function JobStepTwo({ InputDataForm, dagData, checkSync }) {
   //获取数据源节点
   const [nodes, setNodes] = useState([])
   //数据源名称
@@ -120,14 +123,18 @@ export default function JobStepTwo({ InputDataForm, dagData }) {
   // 所有预览map
   const [nodeSchemas, setNodeSchemas] = useState({})
 
+  const [nodeSchema, setNodeSchema] = useState({})
+
   //所有节点预览状态
   const [nodeStates, setNodeStates] = useState({})
 
+  const [nodeState, setNodeState] = useState({})
+
   const getDagInfo = async dag_url => {
     const data = await getDagInfomation(dag_url)
     if (data.status === 200) {
-      const nodes = data.data.nodes.filter(item => item.op === 'datasource')
-      setNodes(nodes)
+      const dag_nodes = data.data.nodes.filter(item => item.op === 'datasource')
+      setNodes(dag_nodes)
     }
   }
 
@@ -201,13 +208,12 @@ export default function JobStepTwo({ InputDataForm, dagData }) {
       })
     }
     if (list.length === 0) {
-      nodeStates[index] = true
+      setNodeState({ index, state: true })
     } else {
-      nodeStates[index] = false
+      setNodeState({ index, state: false })
     }
-    setNodeStates(nodeStates)
-    nodeSchemas[index] = syncList
-    setNodeSchemas(nodeSchemas)
+
+    setNodeSchema({ index, list: syncList })
   }
 
   const showStructure = index => {
@@ -221,6 +227,36 @@ export default function JobStepTwo({ InputDataForm, dagData }) {
     getDagInfo(dagData)
   }, [dagData])
 
+  useEffect(() => {
+    nodes.forEach((_, index) => {
+      const datasource = InputDataForm.getFieldValue(`datasource${index}`)
+      const table = InputDataForm.getFieldValue(`sourceTable${index}`)
+      onDatasourcesSelect(datasource)
+      onTableSelect(table, index)
+    })
+  }, [nodes])
+
+  useEffect(() => {
+    const states = { ...nodeStates }
+    states[nodeState.index] = nodeState.state
+    setNodeStates(states)
+  }, [nodeState])
+
+  useEffect(() => {
+    const schemas = { ...nodeSchemas }
+    schemas[nodeSchema.index] = nodeSchema.list
+    setNodeSchemas(schemas)
+  }, [nodeSchema])
+
+  useEffect(() => {
+    let result = true
+    const keys = Object.keys(nodeStates).filter(item => item !== 'undefined')
+    keys.forEach(key => {
+      if (!nodeStates[key]) result = false
+    })
+    checkSync(result)
+  }, [nodeStates])
+
   return (
     <StepTwo>
       <div className="StepTwo-content">