Ver Fonte

copy configs from taskDefinition (#6179)

Wangyizhi1 há 3 anos atrás
pai
commit
ca488fcfa6

+ 232 - 0
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/_source/copyFromTask.vue

@@ -0,0 +1,232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+<template>
+  <list-box>
+    <div slot="text">{{ $t("Copy from") }}</div>
+    <div slot="content" class="copy-from" ref="copyFrom">
+      <div class="copy-from-content">
+        <el-input
+          class="copy-from-input"
+          v-model="searchVal"
+          :placeholder="getTaskName(value) || $t('Please choose')"
+          @focus="inputFocus"
+          @input="searchValChange"
+          size="small"
+          :suffix-icon="
+            dropdownVisible ? 'el-icon-arrow-up' : 'el-icon-arrow-down'
+          "
+        ></el-input>
+        <div class="copy-from-dropdown" v-show="dropdownVisible">
+          <div class="scroll-box">
+            <ul v-infinite-scroll="load">
+              <li
+                v-for="taskDefinition in taskDefinitions"
+                :key="taskDefinition.code"
+                class="dropdown-item"
+                @click="itemClick(taskDefinition)"
+              >
+                {{ taskDefinition.name }}
+              </li>
+            </ul>
+            <p class="dropdown-msg" v-if="loading">{{ $t("Loading...") }}</p>
+            <p class="dropdown-msg" v-if="noMore">{{ $t("No more...") }}</p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </list-box>
+</template>
+
+<script>
+  import ListBox from '../tasks/_source/listBox'
+  import { mapActions } from 'vuex'
+
+  export default {
+    name: 'copy-from-task',
+    props: {
+      taskType: String
+    },
+    inject: ['formModel'],
+    data () {
+      return {
+        pageNo: 1,
+        pageSize: 10,
+        searchVal: '',
+        value: '',
+        loading: false,
+        noMore: false,
+        taskDefinitions: [],
+        dropdownVisible: false
+      }
+    },
+    mounted () {
+      document.addEventListener('click', this.outsideClick)
+      this.load()
+    },
+    destroyed () {
+      document.removeEventListener('click', this.outsideClick)
+    },
+    methods: {
+      ...mapActions('dag', ['getTaskDefinitions']),
+      outsideClick (e) {
+        const elem = this.$refs.copyFrom
+        if (!elem.contains(e.target) && this.dropdownVisible) {
+          this.dropdownVisible = false
+        }
+      },
+      searchValChange (val) {
+        this.load(true)
+      },
+      load (override) {
+        if (this.loading) return
+        if (override) {
+          this.noMore = false
+          this.pageNo = 1
+        }
+        if (this.noMore) return
+        this.loading = true
+        this.getTaskDefinitions({
+          pageNo: this.pageNo,
+          pageSize: this.pageSize,
+          searchVal: this.searchVal,
+          taskType: this.taskType
+        }).then((res) => {
+          this.taskDefinitions = override ? res.totalList : this.taskDefinitions.concat(res.totalList)
+          this.pageNo = res.currentPage + 1
+          this.noMore = this.taskDefinitions.length >= res.total
+          this.loading = false
+        })
+      },
+      itemClick (taskDefinition) {
+        this.value = taskDefinition.code
+        this.searchVal = taskDefinition.name
+        this.dropdownVisible = false
+
+        if (this.formModel) {
+          const backfillItem = this.formModel.taskToBackfillItem(taskDefinition)
+          this.formModel.backfillRefresh = false
+          this.$nextTick(() => {
+            this.formModel.backfillItem = backfillItem
+            this.formModel.backfill(backfillItem, true)
+            this.formModel.backfillRefresh = true
+          })
+        }
+      },
+      inputFocus () {
+        this.searchVal = ''
+        this.dropdownVisible = true
+      },
+      inputBlur () {
+        this.dropdownVisible = false
+      },
+      getTaskName (code) {
+        const taskDefinition = this.taskDefinitions.find(
+          (taskDefinition) => taskDefinition.code === code
+        )
+        return taskDefinition ? taskDefinition.name : ''
+      }
+    },
+    components: {
+      ListBox
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+.copy-from {
+  position: relative;
+  &-content {
+    width: 100%;
+  }
+  &-input {
+    width: 100%;
+  }
+  &-dropdown {
+    width: 100%;
+    position: absolute;
+    padding: 6px 0;
+    top: 42px;
+    left: 0;
+    z-index: 10;
+    border: 1px solid #e4e7ed;
+    border-radius: 4px;
+    background-color: #fff;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+    .scroll-box {
+      width: 100%;
+      max-height: 200px;
+      overflow: auto;
+    }
+
+    .dropdown-item {
+      font-size: 14px;
+      padding: 0 20px;
+      position: relative;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      color: #606266;
+      height: 34px;
+      line-height: 34px;
+      box-sizing: border-box;
+      cursor: pointer;
+      &:hover {
+        background-color: #f5f7fa;
+      }
+      &.selected {
+        color: #409eff;
+        font-weight: 700;
+      }
+    }
+
+    &:before,
+    &:after {
+      content: "";
+      position: absolute;
+      display: block;
+      width: 0;
+      height: 0;
+      top: -6px;
+      left: 35px;
+      border-width: 6px;
+      border-top-width: 0;
+      border-color: transparent;
+      border-bottom-color: #fff;
+      border-style: solid;
+      z-index: 10;
+    }
+
+    &:before {
+      top: -8px;
+      left: 33px;
+      border-width: 8px;
+      border-top-width: 0;
+      border-bottom-color: #ebeef5;
+      z-index: 9;
+    }
+
+    .dropdown-msg {
+      text-align: center;
+      color: #666;
+      font-size: 12px;
+      line-height: 34px;
+      margin: 0;
+    }
+  }
+}
+</style>

+ 446 - 264
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue

@@ -17,21 +17,38 @@
 <template>
   <div class="form-model-wrapper" v-clickoutside="_handleClose">
     <div class="title-box">
-      <span class="name">{{$t('Current node settings')}}</span>
+      <span class="name">{{ $t("Current node settings") }}</span>
       <span class="go-subtask">
         <!-- Component can't pop up box to do component processing -->
-        <m-log v-if="taskInstance" :item="backfillItem" :task-instance-id="taskInstance.id">
-          <template slot="history"><a href="javascript:" @click="_seeHistory" ><em class="ansicon el-icon-alarm-clock"></em><em>{{$t('View history')}}</em></a></template>
-          <template slot="log"><a href="javascript:"><em class="ansicon el-icon-document"></em><em>{{$t('View log')}}</em></a></template>
+        <m-log
+          v-if="taskInstance"
+          :item="backfillItem"
+          :task-instance-id="taskInstance.id"
+        >
+          <template slot="history"
+            ><a href="javascript:" @click="_seeHistory"
+              ><em class="ansicon el-icon-alarm-clock"></em
+              ><em>{{ $t("View history") }}</em></a
+            ></template
+          >
+          <template slot="log"
+            ><a href="javascript:"
+              ><em class="ansicon el-icon-document"></em
+              ><em>{{ $t("View log") }}</em></a
+            ></template
+          >
         </m-log>
-        <a href="javascript:" @click="_goSubProcess" v-if="_isGoSubProcess"><em class="ansicon ri-node-tree"></em><em>{{$t('Enter this child node')}}</em></a>
+        <a href="javascript:" @click="_goSubProcess" v-if="_isGoSubProcess"
+          ><em class="ansicon ri-node-tree"></em
+          ><em>{{ $t("Enter this child node") }}</em></a
+        >
       </span>
     </div>
     <div class="content-box" v-if="isContentBox">
       <div class="form-model">
         <!-- Node name -->
         <m-list-box>
-          <div slot="text">{{$t('Node name')}}</div>
+          <div slot="text">{{ $t("Node name") }}</div>
           <div slot="content">
             <el-input
               type="text"
@@ -40,41 +57,50 @@
               :disabled="isDetails"
               :placeholder="$t('Please enter name (required)')"
               maxlength="100"
-              @blur="_verifName()">
+              @blur="_verifName()"
+            >
             </el-input>
           </div>
         </m-list-box>
 
+        <!-- Copy from task -->
+        <copy-from-task v-if="!isDetails" :taskType="nodeData.taskType" />
+
         <!-- Running sign -->
         <m-list-box>
-          <div slot="text">{{$t('Run flag')}}</div>
+          <div slot="text">{{ $t("Run flag") }}</div>
           <div slot="content">
             <el-radio-group v-model="runFlag" size="small">
-              <el-radio :label="'YES'" :disabled="isDetails">{{$t('Normal')}}</el-radio>
-              <el-radio :label="'NO'" :disabled="isDetails">{{$t('Prohibition execution')}}</el-radio>
+              <el-radio :label="'YES'" :disabled="isDetails">{{
+                $t("Normal")
+              }}</el-radio>
+              <el-radio :label="'NO'" :disabled="isDetails">{{
+                $t("Prohibition execution")
+              }}</el-radio>
             </el-radio-group>
           </div>
         </m-list-box>
 
         <!-- description -->
         <m-list-box>
-          <div slot="text">{{$t('Description')}}</div>
+          <div slot="text">{{ $t("Description") }}</div>
           <div slot="content">
             <el-input
               :rows="2"
               type="textarea"
               :disabled="isDetails"
               v-model="desc"
-              :placeholder="$t('Please enter description')">
+              :placeholder="$t('Please enter description')"
+            >
             </el-input>
           </div>
         </m-list-box>
 
         <!-- Task priority -->
         <m-list-box>
-          <div slot="text">{{$t('Task priority')}}</div>
+          <div slot="text">{{ $t("Task priority") }}</div>
           <div slot="content">
-            <span class="label-box" style="width: 193px;display: inline-block;">
+            <span class="label-box" style="width: 193px; display: inline-block">
               <m-priority v-model="taskInstancePriority"></m-priority>
             </span>
           </div>
@@ -82,207 +108,305 @@
 
         <!-- Worker group and environment -->
         <m-list-box>
-          <div slot="text">{{$t('Worker group')}}</div>
+          <div slot="text">{{ $t("Worker group") }}</div>
           <div slot="content">
-            <span class="label-box" style="width: 193px;display: inline-block;">
+            <span class="label-box" style="width: 193px; display: inline-block">
               <m-worker-groups v-model="workerGroup"></m-worker-groups>
             </span>
-            <span class="text-b">{{$t('Environment Name')}}</span>
-            <m-related-environment v-model="environmentCode" :workerGroup="workerGroup" v-on:environmentCodeEvent="_onUpdateEnvironmentCode"></m-related-environment>
+            <span class="text-b">{{ $t("Environment Name") }}</span>
+            <m-related-environment
+              v-model="environmentCode"
+              :workerGroup="workerGroup"
+              v-on:environmentCodeEvent="_onUpdateEnvironmentCode"
+            ></m-related-environment>
           </div>
         </m-list-box>
 
         <!-- Number of failed retries -->
         <m-list-box v-if="nodeData.taskType !== 'SUB_PROCESS'">
-          <div slot="text">{{$t('Number of failed retries')}}</div>
+          <div slot="text">{{ $t("Number of failed retries") }}</div>
           <div slot="content">
-            <m-select-input v-model="maxRetryTimes" :list="[0,1,2,3,4]"></m-select-input>
-            <span>({{$t('Times')}})</span>
-            <span class="text-b">{{$t('Failed retry interval')}}</span>
-            <m-select-input v-model="retryInterval" :list="[1,10,30,60,120]"></m-select-input>
-            <span>({{$t('Minute')}})</span>
+            <m-select-input
+              v-model="maxRetryTimes"
+              :list="[0, 1, 2, 3, 4]"
+            ></m-select-input>
+            <span>({{ $t("Times") }})</span>
+            <span class="text-b">{{ $t("Failed retry interval") }}</span>
+            <m-select-input
+              v-model="retryInterval"
+              :list="[1, 10, 30, 60, 120]"
+            ></m-select-input>
+            <span>({{ $t("Minute") }})</span>
           </div>
         </m-list-box>
 
         <!-- Delay execution time -->
-        <m-list-box v-if="nodeData.taskType !== 'SUB_PROCESS' && nodeData.taskType !== 'CONDITIONS' && nodeData.taskType !== 'DEPENDENT'&& nodeData.taskType !== 'SWITCH'">
-          <div slot="text">{{$t('Delay execution time')}}</div>
+        <m-list-box
+          v-if="
+            nodeData.taskType !== 'SUB_PROCESS' &&
+            nodeData.taskType !== 'CONDITIONS' &&
+            nodeData.taskType !== 'DEPENDENT' &&
+            nodeData.taskType !== 'SWITCH'
+          "
+        >
+          <div slot="text">{{ $t("Delay execution time") }}</div>
           <div slot="content">
-            <m-select-input v-model="delayTime" :list="[0,1,5,10]"></m-select-input>
-            <span>({{$t('Minute')}})</span>
+            <m-select-input
+              v-model="delayTime"
+              :list="[0, 1, 5, 10]"
+            ></m-select-input>
+            <span>({{ $t("Minute") }})</span>
           </div>
         </m-list-box>
 
         <!-- Branch flow -->
         <m-list-box v-if="nodeData.taskType === 'CONDITIONS'">
-          <div slot="text">{{$t('State')}}</div>
+          <div slot="text">{{ $t("State") }}</div>
           <div slot="content">
-            <span class="label-box" style="width: 193px;display: inline-block;">
-              <el-select style="width: 157px;" size="small" v-model="successNode" :disabled="true">
-                <el-option v-for="item in stateList" :key="item.value" :value="item.value" :label="item.label"></el-option>
+            <span class="label-box" style="width: 193px; display: inline-block">
+              <el-select
+                style="width: 157px"
+                size="small"
+                v-model="successNode"
+                :disabled="true"
+              >
+                <el-option
+                  v-for="item in stateList"
+                  :key="item.value"
+                  :value="item.value"
+                  :label="item.label"
+                ></el-option>
               </el-select>
             </span>
-            <span class="text-b" style="padding-left: 38px">{{$t('Branch flow')}}</span>
-            <el-select style="width: 157px;" size="small" v-model="successBranch" clearable :disabled="isDetails">
-              <el-option v-for="item in postTasks" :key="item.code" :value="item.name" :label="item.name"></el-option>
+            <span class="text-b" style="padding-left: 38px">{{
+              $t("Branch flow")
+            }}</span>
+            <el-select
+              style="width: 157px"
+              size="small"
+              v-model="successBranch"
+              clearable
+              :disabled="isDetails"
+            >
+              <el-option
+                v-for="item in postTasks"
+                :key="item.code"
+                :value="item.name"
+                :label="item.name"
+              ></el-option>
             </el-select>
           </div>
         </m-list-box>
         <m-list-box v-if="nodeData.taskType === 'CONDITIONS'">
-          <div slot="text">{{$t('State')}}</div>
+          <div slot="text">{{ $t("State") }}</div>
           <div slot="content">
-            <span class="label-box" style="width: 193px;display: inline-block;">
-              <el-select style="width: 157px;" size="small" v-model="failedNode" :disabled="true">
-                <el-option v-for="item in stateList" :key="item.value" :value="item.value" :label="item.label"></el-option>
+            <span class="label-box" style="width: 193px; display: inline-block">
+              <el-select
+                style="width: 157px"
+                size="small"
+                v-model="failedNode"
+                :disabled="true"
+              >
+                <el-option
+                  v-for="item in stateList"
+                  :key="item.value"
+                  :value="item.value"
+                  :label="item.label"
+                ></el-option>
               </el-select>
             </span>
-            <span class="text-b" style="padding-left: 38px">{{$t('Branch flow')}}</span>
-            <el-select style="width: 157px;" size="small" v-model="failedBranch" clearable :disabled="isDetails">
-              <el-option v-for="item in postTasks" :key="item.code" :value="item.name" :label="item.name"></el-option>
+            <span class="text-b" style="padding-left: 38px">{{
+              $t("Branch flow")
+            }}</span>
+            <el-select
+              style="width: 157px"
+              size="small"
+              v-model="failedBranch"
+              clearable
+              :disabled="isDetails"
+            >
+              <el-option
+                v-for="item in postTasks"
+                :key="item.code"
+                :value="item.name"
+                :label="item.name"
+              ></el-option>
             </el-select>
           </div>
         </m-list-box>
 
-        <!-- Task timeout alarm -->
-        <m-timeout-alarm
-          v-if="nodeData.taskType !== 'DEPENDENT'"
-          ref="timeout"
-          :backfill-item="backfillItem"
-          @on-timeout="_onTimeout">
-        </m-timeout-alarm>
-        <!-- Dependent timeout alarm -->
-        <m-dependent-timeout
-          v-if="nodeData.taskType === 'DEPENDENT'"
-          ref="dependentTimeout"
-          :backfill-item="backfillItem"
-          @on-timeout="_onDependentTimeout">
-        </m-dependent-timeout>
+        <div v-if="backfillRefresh">
+          <!-- Task timeout alarm -->
+          <m-timeout-alarm
+            v-if="nodeData.taskType !== 'DEPENDENT'"
+            ref="timeout"
+            :backfill-item="backfillItem"
+            @on-timeout="_onTimeout"
+          >
+          </m-timeout-alarm>
+          <!-- Dependent timeout alarm -->
+          <m-dependent-timeout
+            v-if="nodeData.taskType === 'DEPENDENT'"
+            ref="dependentTimeout"
+            :backfill-item="backfillItem"
+            @on-timeout="_onDependentTimeout"
+          >
+          </m-dependent-timeout>
 
-        <!-- shell node -->
-        <m-shell
-          v-if="nodeData.taskType === 'SHELL'"
-          @on-params="_onParams"
-          @on-cache-params="_onCacheParams"
-          ref="SHELL"
-          :backfill-item="backfillItem">
-        </m-shell>
-        <!-- waterdrop node -->
-        <m-waterdrop
-          v-if="nodeData.taskType === 'WATERDROP'"
-          @on-params="_onParams"
-          @on-cache-params="_onCacheParams"
-          ref="WATERDROP"
-          :backfill-item="backfillItem">
-        </m-waterdrop>
-        <!-- sub_process node -->
-        <m-sub-process
-          v-if="nodeData.taskType === 'SUB_PROCESS'"
-          @on-params="_onParams"
-          @on-cache-params="_onCacheParams"
-          @on-set-process-name="_onSetProcessName"
-          ref="SUB_PROCESS"
-          :backfill-item="backfillItem">
-        </m-sub-process>
-        <!-- procedure node -->
-        <m-procedure
-          v-if="nodeData.taskType === 'PROCEDURE'"
-          @on-params="_onParams"
-          @on-cache-params="_onCacheParams"
-          ref="PROCEDURE"
-          :backfill-item="backfillItem">
-        </m-procedure>
-        <!-- sql node -->
-        <m-sql
-          v-if="nodeData.taskType === 'SQL'"
-          @on-params="_onParams"
-          @on-cache-params="_onCacheParams"
-          ref="SQL"
-          :create-node-id="nodeData.id"
-          :backfill-item="backfillItem">
-        </m-sql>
-        <!-- spark node -->
-        <m-spark
-          v-if="nodeData.taskType === 'SPARK'"
-          @on-params="_onParams"
-          @on-cache-params="_onCacheParams"
-          ref="SPARK"
-          :backfill-item="backfillItem">
-        </m-spark>
-        <m-flink
-          v-if="nodeData.taskType === 'FLINK'"
-          @on-params="_onParams"
-          @on-cache-params="_onCacheParams"
-          ref="FLINK"
-          :backfill-item="backfillItem">
-        </m-flink>
-        <!-- mr node -->
-        <m-mr
-          v-if="nodeData.taskType === 'MR'"
-          @on-params="_onParams"
-          @on-cache-params="_onCacheParams"
-          ref="MR"
-          :backfill-item="backfillItem">
-        </m-mr>
-        <!-- python node -->
-        <m-python
-          v-if="nodeData.taskType === 'PYTHON'"
-          @on-params="_onParams"
-          @on-cache-params="_onCacheParams"
-          ref="PYTHON"
-          :backfill-item="backfillItem">
-        </m-python>
-        <!-- dependent node -->
-        <m-dependent
-          v-if="nodeData.taskType === 'DEPENDENT'"
-          @on-dependent="_onDependent"
-          @on-cache-dependent="_onCacheDependent"
-          ref="DEPENDENT"
-          :backfill-item="backfillItem">
-        </m-dependent>
-        <m-http
-          v-if="nodeData.taskType === 'HTTP'"
-          @on-params="_onParams"
-          @on-cache-params="_onCacheParams"
-          ref="HTTP"
-          :backfill-item="backfillItem">
-        </m-http>
-        <m-datax
-          v-if="nodeData.taskType === 'DATAX'"
-          @on-params="_onParams"
-          @on-cache-params="_onCacheParams"
-          ref="DATAX"
-          :backfill-item="backfillItem">
-        </m-datax>
-        <m-sqoop
-          v-if="nodeData.taskType === 'SQOOP'"
-          @on-params="_onParams"
-          @on-cache-params="_onCacheParams"
-          ref="SQOOP"
-          :backfill-item="backfillItem">
-        </m-sqoop>
-        <m-conditions
-          v-if="nodeData.taskType === 'CONDITIONS'"
-          ref="CONDITIONS"
-          @on-dependent="_onDependent"
-          @on-cache-dependent="_onCacheDependent"
-          :backfill-item="backfillItem"
-          :prev-tasks="prevTasks">
-        </m-conditions>
-        <m-switch
-          v-if="nodeData.taskType === 'SWITCH'"
-          ref="SWITCH"
-          @on-switch-result="_onSwitchResult"
-          :backfill-item="backfillItem"
-          :nodeData="nodeData"
-        ></m-switch>
+          <!-- shell node -->
+          <m-shell
+            v-if="nodeData.taskType === 'SHELL'"
+            @on-params="_onParams"
+            @on-cache-params="_onCacheParams"
+            ref="SHELL"
+            :backfill-item="backfillItem"
+          >
+          </m-shell>
+          <!-- waterdrop node -->
+          <m-waterdrop
+            v-if="nodeData.taskType === 'WATERDROP'"
+            @on-params="_onParams"
+            @on-cache-params="_onCacheParams"
+            ref="WATERDROP"
+            :backfill-item="backfillItem"
+          >
+          </m-waterdrop>
+          <!-- sub_process node -->
+          <m-sub-process
+            v-if="nodeData.taskType === 'SUB_PROCESS'"
+            @on-params="_onParams"
+            @on-cache-params="_onCacheParams"
+            @on-set-process-name="_onSetProcessName"
+            ref="SUB_PROCESS"
+            :backfill-item="backfillItem"
+          >
+          </m-sub-process>
+          <!-- procedure node -->
+          <m-procedure
+            v-if="nodeData.taskType === 'PROCEDURE'"
+            @on-params="_onParams"
+            @on-cache-params="_onCacheParams"
+            ref="PROCEDURE"
+            :backfill-item="backfillItem"
+          >
+          </m-procedure>
+          <!-- sql node -->
+          <m-sql
+            v-if="nodeData.taskType === 'SQL'"
+            @on-params="_onParams"
+            @on-cache-params="_onCacheParams"
+            ref="SQL"
+            :create-node-id="nodeData.id"
+            :backfill-item="backfillItem"
+          >
+          </m-sql>
+          <!-- spark node -->
+          <m-spark
+            v-if="nodeData.taskType === 'SPARK'"
+            @on-params="_onParams"
+            @on-cache-params="_onCacheParams"
+            ref="SPARK"
+            :backfill-item="backfillItem"
+          >
+          </m-spark>
+          <m-flink
+            v-if="nodeData.taskType === 'FLINK'"
+            @on-params="_onParams"
+            @on-cache-params="_onCacheParams"
+            ref="FLINK"
+            :backfill-item="backfillItem"
+          >
+          </m-flink>
+          <!-- mr node -->
+          <m-mr
+            v-if="nodeData.taskType === 'MR'"
+            @on-params="_onParams"
+            @on-cache-params="_onCacheParams"
+            ref="MR"
+            :backfill-item="backfillItem"
+          >
+          </m-mr>
+          <!-- python node -->
+          <m-python
+            v-if="nodeData.taskType === 'PYTHON'"
+            @on-params="_onParams"
+            @on-cache-params="_onCacheParams"
+            ref="PYTHON"
+            :backfill-item="backfillItem"
+          >
+          </m-python>
+          <!-- dependent node -->
+          <m-dependent
+            v-if="nodeData.taskType === 'DEPENDENT'"
+            @on-dependent="_onDependent"
+            @on-cache-dependent="_onCacheDependent"
+            ref="DEPENDENT"
+            :backfill-item="backfillItem"
+          >
+          </m-dependent>
+          <m-http
+            v-if="nodeData.taskType === 'HTTP'"
+            @on-params="_onParams"
+            @on-cache-params="_onCacheParams"
+            ref="HTTP"
+            :backfill-item="backfillItem"
+          >
+          </m-http>
+          <m-datax
+            v-if="nodeData.taskType === 'DATAX'"
+            @on-params="_onParams"
+            @on-cache-params="_onCacheParams"
+            ref="DATAX"
+            :backfill-item="backfillItem"
+          >
+          </m-datax>
+          <m-sqoop
+            v-if="nodeData.taskType === 'SQOOP'"
+            @on-params="_onParams"
+            @on-cache-params="_onCacheParams"
+            ref="SQOOP"
+            :backfill-item="backfillItem"
+          >
+          </m-sqoop>
+          <m-conditions
+            v-if="nodeData.taskType === 'CONDITIONS'"
+            ref="CONDITIONS"
+            @on-dependent="_onDependent"
+            @on-cache-dependent="_onCacheDependent"
+            :backfill-item="backfillItem"
+            :prev-tasks="prevTasks"
+          >
+          </m-conditions>
+          <m-switch
+            v-if="nodeData.taskType === 'SWITCH'"
+            ref="SWITCH"
+            @on-switch-result="_onSwitchResult"
+            :backfill-item="backfillItem"
+            :nodeData="nodeData"
+          ></m-switch>
+        </div>
         <!-- Pre-tasks in workflow -->
-        <m-pre-tasks ref="preTasks" v-if="['SHELL', 'SUB_PROCESS'].indexOf(nodeData.taskType) > -1" :code="code"/>
+        <m-pre-tasks
+          ref="preTasks"
+          v-if="['SHELL', 'SUB_PROCESS'].indexOf(nodeData.taskType) > -1"
+          :code="code"
+        />
       </div>
     </div>
     <div class="bottom-box">
-      <div class="submit" style="background: #fff;">
-        <el-button type="text" size="small" id="cancelBtn"> {{$t('Cancel')}} </el-button>
-        <el-button type="primary" size="small" round :loading="spinnerLoading" @click="ok()" :disabled="isDetails">{{spinnerLoading ? $t('Loading...') : $t('Confirm add')}} </el-button>
+      <div class="submit" style="background: #fff">
+        <el-button type="text" size="small" id="cancelBtn">
+          {{ $t("Cancel") }}
+        </el-button>
+        <el-button
+          type="primary"
+          size="small"
+          round
+          :loading="spinnerLoading"
+          @click="ok()"
+          :disabled="isDetails"
+          >{{ spinnerLoading ? $t("Loading...") : $t("Confirm add") }}
+        </el-button>
       </div>
     </div>
   </div>
@@ -318,6 +442,7 @@
   import disabledState from '@/module/mixin/disabledState'
   import mPriority from '@/module/components/priority/priority'
   import { findComponentDownward } from '@/module/util/'
+  import CopyFromTask from './_source/copyFromTask.vue'
 
   export default {
     name: 'form-model',
@@ -384,7 +509,14 @@
         ],
         // for CONDITIONS
         postTasks: [],
-        prevTasks: []
+        prevTasks: [],
+        // refresh part of the formModel, after set backfillItem outside
+        backfillRefresh: true
+      }
+    },
+    provide () {
+      return {
+        formModel: this
       }
     },
     /**
@@ -408,7 +540,11 @@
           id: task.id,
           maxRetryTimes: task.failRetryTimes,
           name: task.name,
-          params: _.omit(task.taskParams, ['conditionResult', 'dependence']),
+          params: _.omit(task.taskParams, [
+            'conditionResult',
+            'dependence',
+            'waitStartTimeout'
+          ]),
           retryInterval: task.failRetryInterval,
           runFlag: task.flag,
           taskInstancePriority: task.taskPriority,
@@ -448,13 +584,17 @@
        */
       _onDependentTimeout (o) {
         this.timeout = Object.assign(this.timeout, {}, o.waitCompleteTimeout)
-        this.waitStartTimeout = Object.assign(this.waitStartTimeout, {}, o.waitStartTimeout)
+        this.waitStartTimeout = Object.assign(
+          this.waitStartTimeout,
+          {},
+          o.waitStartTimeout
+        )
       },
       /**
        * Click external to close the current component
        */
       _handleClose () {
-        // this.close()
+      // this.close()
       },
       /**
        * Jump to task instance
@@ -468,25 +608,39 @@
        */
       _goSubProcess () {
         if (_.isEmpty(this.backfillItem)) {
-          this.$message.warning(`${i18n.$t('The newly created sub-Process has not yet been executed and cannot enter the sub-Process')}`)
+          this.$message.warning(
+            `${i18n.$t(
+              'The newly created sub-Process has not yet been executed and cannot enter the sub-Process'
+            )}`
+          )
           return
         }
         if (this.router.history.current.name === 'projects-instance-details') {
           if (!this.taskInstance) {
-            this.$message.warning(`${i18n.$t('The task has not been executed and cannot enter the sub-Process')}`)
+            this.$message.warning(
+              `${i18n.$t(
+                'The task has not been executed and cannot enter the sub-Process'
+              )}`
+            )
             return
           }
-          this.store.dispatch('dag/getSubProcessId', { taskId: this.taskInstance.id }).then(res => {
-            this.$emit('onSubProcess', {
-              subProcessId: res.data.subProcessInstanceId,
-              fromThis: this
+          this.store
+            .dispatch('dag/getSubProcessId', { taskId: this.taskInstance.id })
+            .then((res) => {
+              this.$emit('onSubProcess', {
+                subProcessId: res.data.subProcessInstanceId,
+                fromThis: this
+              })
+            })
+            .catch((e) => {
+              this.$message.error(e.msg || '')
             })
-          }).catch(e => {
-            this.$message.error(e.msg || '')
-          })
         } else {
-          const processDefinitionId = this.backfillItem.params.processDefinitionId
-          const process = this.processListS.find(process => process.processDefinition.id === processDefinitionId)
+          const processDefinitionId =
+            this.backfillItem.params.processDefinitionId
+          const process = this.processListS.find(
+            (process) => process.processDefinition.id === processDefinitionId
+          )
           this.$emit('onSubProcess', {
             subProcessCode: process.processDefinition.code,
             fromThis: this
@@ -519,8 +673,16 @@
           this.$message.warning(`${i18n.$t('Please enter name (required)')}`)
           return false
         }
-        if (this.successBranch !== '' && this.successBranch !== null && this.successBranch === this.failedBranch) {
-          this.$message.warning(`${i18n.$t('Cannot select the same node for successful branch flow and failed branch flow')}`)
+        if (
+          this.successBranch !== '' &&
+          this.successBranch !== null &&
+          this.successBranch === this.failedBranch
+        ) {
+          this.$message.warning(
+            `${i18n.$t(
+              'Cannot select the same node for successful branch flow and failed branch flow'
+            )}`
+          )
           return false
         }
         if (this.name === this.backfillItem.name) {
@@ -528,7 +690,7 @@
         }
         // Name repeat depends on dom backfill dependent store
         const tasks = this.store.state.dag.tasks
-        const task = tasks.find(t => t.name === 'this.name')
+        const task = tasks.find((t) => t.name === 'this.name')
         if (task) {
           this.$message.warning(`${i18n.$t('Name already exists')}`)
           return false
@@ -536,11 +698,15 @@
         return true
       },
       _verifWorkGroup () {
-        let item = this.store.state.security.workerGroupsListAll.find(item => {
+        let item = this.store.state.security.workerGroupsListAll.find((item) => {
           return item.id === this.workerGroup
         })
         if (item === undefined) {
-          this.$message.warning(`${i18n.$t('The Worker group no longer exists, please select the correct Worker group!')}`)
+          this.$message.warning(
+            `${i18n.$t(
+              'The Worker group no longer exists, please select the correct Worker group!'
+            )}`
+          )
           return false
         }
         return true
@@ -619,20 +785,29 @@
        *  set run flag
        *  TODO
        */
-      _setRunFlag () {
-
-      },
-      /**
-       *
-       */
+      _setRunFlag () {},
       _setEdgeLabel () {
         if (this.successBranch || this.failedBranch) {
           const canvas = findComponentDownward(this.dagChart, 'dag-canvas')
           const edges = canvas.getEdges()
-          const successTask = this.postTasks.find(t => t.name === this.successBranch)
-          const failedTask = this.postTasks.find(t => t.name === this.failedBranch)
-          const sEdge = edges.find(edge => successTask && edge.sourceId === this.code && edge.targetId === successTask.code)
-          const fEdge = edges.find(edge => failedTask && edge.sourceId === this.code && edge.targetId === failedTask.code)
+          const successTask = this.postTasks.find(
+            (t) => t.name === this.successBranch
+          )
+          const failedTask = this.postTasks.find(
+            (t) => t.name === this.failedBranch
+          )
+          const sEdge = edges.find(
+            (edge) =>
+              successTask &&
+              edge.sourceId === this.code &&
+              edge.targetId === successTask.code
+          )
+          const fEdge = edges.find(
+            (edge) =>
+              failedTask &&
+              edge.sourceId === this.code &&
+              edge.targetId === failedTask.code
+          )
           sEdge && canvas.setEdgeLabel(sEdge.id, this.$t('Success'))
           fEdge && canvas.setEdgeLabel(fEdge.id, this.$t('Failed'))
         }
@@ -659,6 +834,57 @@
           flag: flag,
           fromThis: this
         })
+      },
+      backfill (backfillItem, copyFromTask) {
+        const o = backfillItem
+        // Non-null objects represent backfill
+        if (!_.isEmpty(o)) {
+          this.code = o.code
+          !copyFromTask && (this.name = o.name)
+          this.taskInstancePriority = o.taskInstancePriority
+          this.runFlag = o.runFlag || 'YES'
+          this.desc = o.desc
+          this.maxRetryTimes = o.maxRetryTimes
+          this.retryInterval = o.retryInterval
+          this.delayTime = o.delayTime
+          if (o.conditionResult) {
+            this.successBranch = o.conditionResult.successNode[0]
+            this.failedBranch = o.conditionResult.failedNode[0]
+          }
+          // If the workergroup has been deleted, set the default workergroup
+          for (
+            let i = 0;
+            i < this.store.state.security.workerGroupsListAll.length;
+            i++
+          ) {
+            let workerGroup = this.store.state.security.workerGroupsListAll[i].id
+            if (o.workerGroup === workerGroup) {
+              break
+            }
+          }
+          if (o.workerGroup === undefined) {
+            this.store
+              .dispatch('dag/getTaskInstanceList', {
+                pageSize: 10,
+                pageNo: 1,
+                processInstanceId: this.nodeData.instanceId,
+                name: o.name
+              })
+              .then((res) => {
+                this.workerGroup = res.totalList[0].workerGroup
+              })
+          } else {
+            this.workerGroup = o.workerGroup
+          }
+          this.environmentCode = o.environmentCode
+          this.params = o.params || {}
+          this.dependence = o.dependence || {}
+          this.cacheDependence = o.dependence || {}
+        } else {
+          this.workerGroup = this.store.state.security.workerGroupsListAll[0].id
+        }
+        this.cacheBackfillItem = JSON.parse(JSON.stringify(o))
+        this.isContentBox = true
       }
     },
     created () {
@@ -666,7 +892,7 @@
       let taskList = this.store.state.dag.tasks
       let o = {}
       if (taskList.length) {
-        taskList.forEach(task => {
+        taskList.forEach((task) => {
           if (task.code === this.nodeData.id) {
             const backfillItem = this.taskToBackfillItem(task)
             o = backfillItem
@@ -675,46 +901,7 @@
         })
       }
       this.code = this.nodeData.id
-      // Non-null objects represent backfill
-      if (!_.isEmpty(o)) {
-        this.code = o.code
-        this.name = o.name
-        this.taskInstancePriority = o.taskInstancePriority
-        this.runFlag = o.runFlag || 'YES'
-        this.desc = o.desc
-        this.maxRetryTimes = o.maxRetryTimes
-        this.retryInterval = o.retryInterval
-        this.delayTime = o.delayTime
-        if (o.conditionResult) {
-          this.successBranch = o.conditionResult.successNode[0]
-          this.failedBranch = o.conditionResult.failedNode[0]
-        }
-        // If the workergroup has been deleted, set the default workergroup
-        for (let i = 0; i < this.store.state.security.workerGroupsListAll.length; i++) {
-          let workerGroup = this.store.state.security.workerGroupsListAll[i].id
-          if (o.workerGroup === workerGroup) {
-            break
-          }
-        }
-
-        if (o.workerGroup === undefined) {
-          this.store.dispatch('dag/getTaskInstanceList', {
-            pageSize: 10, pageNo: 1, processInstanceId: this.nodeData.instanceId, name: o.name
-          }).then(res => {
-            this.workerGroup = res.totalList[0].workerGroup
-          })
-        } else {
-          this.workerGroup = o.workerGroup
-        }
-        this.environmentCode = o.environmentCode
-        this.params = o.params || {}
-        this.dependence = o.dependence || {}
-        this.cacheDependence = o.dependence || {}
-      } else {
-        this.workerGroup = this.store.state.security.workerGroupsListAll[0].id
-      }
-      this.cacheBackfillItem = JSON.parse(JSON.stringify(o))
-      this.isContentBox = true
+      this.backfill(o)
 
       if (this.dagChart) {
         const canvas = findComponentDownward(this.dagChart, 'dag-canvas')
@@ -736,17 +923,11 @@
         self.close()
       })
     },
-    updated () {
-    },
-    beforeDestroy () {
-    },
-    destroyed () {
-    },
+    updated () {},
+    beforeDestroy () {},
+    destroyed () {},
     computed: {
-      ...mapState('dag', [
-        'processListS',
-        'taskInstances'
-      ]),
+      ...mapState('dag', ['processListS', 'taskInstances']),
       /**
        * Child workflow entry show/hide
        */
@@ -786,16 +967,17 @@
       mPriority,
       mWorkerGroups,
       mRelatedEnvironment,
-      mPreTasks
+      mPreTasks,
+      CopyFromTask
     }
   }
 </script>
 
 <style lang="scss" rel="stylesheet/scss">
-  @import "./formModel";
-  .ans-radio-disabled {
-    .ans-radio-inner:after {
-      background-color: #6F8391
-    }
+@import "./formModel";
+.ans-radio-disabled {
+  .ans-radio-inner:after {
+    background-color: #6f8391;
   }
+}
 </style>

+ 9 - 0
dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js

@@ -829,5 +829,14 @@ export default {
         reject(e)
       })
     })
+  },
+  getTaskDefinitions ({ state }, payload) {
+    return new Promise((resolve, reject) => {
+      io.get(`projects/${state.projectCode}/task-definition`, payload, res => {
+        resolve(res.data)
+      }).catch(e => {
+        reject(e)
+      })
+    })
   }
 }

+ 3 - 1
dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js

@@ -706,5 +706,7 @@ export default {
   'Please enter environment desc': 'Please enter environment desc',
   'Please select worker groups': 'Please select worker groups',
   condition: 'condition',
-  'The condition content cannot be empty': 'The condition content cannot be empty'
+  'The condition content cannot be empty': 'The condition content cannot be empty',
+  'Copy from': 'Copy from',
+  'No more...': 'No more...'
 }

+ 3 - 1
dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js

@@ -705,5 +705,7 @@ export default {
   'Please enter environment desc': '请输入详细描述',
   'Please select worker groups': '请选择Worker分组',
   condition: '条件',
-  'The condition content cannot be empty': '条件内容不能为空'
+  'The condition content cannot be empty': '条件内容不能为空',
+  'Copy from': '从任务复制',
+  'No more...': '没有更多了...'
 }