You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dolphinscheduler.apache.org by ca...@apache.org on 2021/12/29 03:40:50 UTC

[dolphinscheduler] branch dev updated: [Fix-7345] Support the whole operations for the task group and the task group queue. (#7678)

This is an automated email from the ASF dual-hosted git repository.

caishunfeng pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler.git


The following commit(s) were added to refs/heads/dev by this push:
     new 5ab0135  [Fix-7345] Support the whole operations for the task group and the task group queue.  (#7678)
5ab0135 is described below

commit 5ab0135d1831c9156388d8b593417e775e2b2b84
Author: calvin <ji...@163.com>
AuthorDate: Wed Dec 29 11:40:44 2021 +0800

    [Fix-7345] Support the whole operations for the task group and the task group queue.  (#7678)
    
    * add task group
    
    * modify task group
    
    * add task group queue
    
    * add task group queue
    
    * add task group queue
    
    * add task group queue
    
    * add task group priority
    
    * fix a few issue with api
    
    * add task group queue
    
    * modify task group queue
    
    * modify task group
    
    * fix a little bit issue
    
    * fix a little bug in the page of managing task group
---
 .../api/service/TaskGroupService.java              |   2 +-
 .../api/service/impl/TaskGroupServiceImpl.java     |  21 +-
 .../api/service/TaskGroupServiceTest.java          |   2 +-
 .../dao/entity/TaskDefinition.java                 |   6 +-
 .../dolphinscheduler/dao/entity/TaskGroup.java     |   2 +-
 .../dao/mapper/TaskDefinitionLogMapper.xml         |   2 +-
 .../dao/mapper/TaskDefinitionMapper.xml            |   2 +-
 .../dao/mapper/TaskGroupMapper.xml                 |   2 +-
 .../dao/mapper/TaskGroupQueueMapper.xml            |  14 +-
 .../src/js/conf/home/pages/dag/_source/dag.vue     |   1 +
 .../dag/_source/formModel/_source/taskGroups.vue   |  86 +++++++++
 .../home/pages/dag/_source/formModel/formModel.vue |  55 +++++-
 .../home/pages/resource/pages/taskGroups/index.vue |  24 +++
 .../taskGroupOption/_source/createTaskGroup.vue}   | 160 +++++++---------
 .../taskGroups/taskGroupOption/_source/list.vue    | 135 +++++++++++++
 .../pages/taskGroups/taskGroupOption/index.vue     | 176 +++++++++++++++++
 .../taskGroups/taskGroupQueue/_source/list.vue     | 154 +++++++++++++++
 .../pages/taskGroups/taskGroupQueue/index.vue      | 211 +++++++++++++++++++++
 .../environment/_source/createEnvironment.vue      |   2 -
 .../src/js/conf/home/router/module/resource.js     |  29 +++
 .../src/js/conf/home/store/dag/actions.js          |   4 +-
 .../src/js/conf/home/store/resource/actions.js     | 108 +++++++++++
 .../components/secondaryMenu/_source/menu.js       |  22 +++
 .../src/js/module/i18n/locale/en_US.js             |  32 +++-
 .../src/js/module/i18n/locale/zh_CN.js             |  33 +++-
 25 files changed, 1166 insertions(+), 119 deletions(-)

diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/TaskGroupService.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/TaskGroupService.java
index 465f2ef..e950589 100644
--- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/TaskGroupService.java
+++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/TaskGroupService.java
@@ -35,7 +35,7 @@ public interface TaskGroupService {
      * @param groupSize   task group total size
      * @return the result code and msg
      */
-    Map<String, Object> createTaskGroup(User loginUser, long projectcode,String name,
+    Map<String, Object> createTaskGroup(User loginUser, Long projectCode, String name,
                                         String description, int groupSize);
 
     /**
diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/TaskGroupServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/TaskGroupServiceImpl.java
index 28da3e1..c732494 100644
--- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/TaskGroupServiceImpl.java
+++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/TaskGroupServiceImpl.java
@@ -17,8 +17,10 @@
 
 package org.apache.dolphinscheduler.api.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.dolphinscheduler.api.dto.gantt.Task;
 import org.apache.dolphinscheduler.api.enums.Status;
 import org.apache.dolphinscheduler.api.service.TaskGroupQueueService;
 import org.apache.dolphinscheduler.api.service.TaskGroupService;
@@ -68,7 +70,7 @@ public class TaskGroupServiceImpl extends BaseServiceImpl implements TaskGroupSe
      * @return the result code and msg
      */
     @Override
-    public Map<String, Object> createTaskGroup(User loginUser, long projectcode, String name, String description, int groupSize) {
+    public Map<String, Object> createTaskGroup(User loginUser, Long projectCode, String name, String description, int groupSize) {
         Map<String, Object> result = new HashMap<>();
         if (isNotAdmin(loginUser, result)) {
             return result;
@@ -86,7 +88,7 @@ public class TaskGroupServiceImpl extends BaseServiceImpl implements TaskGroupSe
             putMsg(result, Status.TASK_GROUP_NAME_EXSIT);
             return result;
         }
-        TaskGroup taskGroup = new TaskGroup(name, projectcode, description,
+        TaskGroup taskGroup = new TaskGroup(name, projectCode, description,
             groupSize, loginUser.getId(), Flag.YES.getCode());
 
         taskGroup.setCreateTime(new Date());
@@ -116,6 +118,19 @@ public class TaskGroupServiceImpl extends BaseServiceImpl implements TaskGroupSe
         if (isNotAdmin(loginUser, result)) {
             return result;
         }
+        if (name == null) {
+            putMsg(result, Status.NAME_NULL);
+            return result;
+        }
+        if (groupSize <= 0) {
+            putMsg(result, Status.TASK_GROUP_SIZE_ERROR);
+            return result;
+        }
+        Integer exists = taskGroupMapper.selectCount(new QueryWrapper<TaskGroup>().lambda().eq(TaskGroup::getName, name).ne(TaskGroup::getId, id));
+        if (exists > 0) {
+            putMsg(result, Status.TASK_GROUP_NAME_EXSIT);
+            return result;
+        }
         TaskGroup taskGroup = taskGroupMapper.selectById(id);
         if (taskGroup.getStatus() != Flag.YES.getCode()) {
             putMsg(result, Status.TASK_GROUP_STATUS_ERROR);
@@ -177,7 +192,7 @@ public class TaskGroupServiceImpl extends BaseServiceImpl implements TaskGroupSe
      * @param loginUser login user
      * @param pageNo    page no
      * @param pageSize  page size
-     * @param name      name
+     * @param projectCode project code
      * @return the result code and msg
      */
     @Override
diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/TaskGroupServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/TaskGroupServiceTest.java
index c3084f0..69bc3cb 100644
--- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/TaskGroupServiceTest.java
+++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/TaskGroupServiceTest.java
@@ -109,7 +109,7 @@ public class TaskGroupServiceTest {
         TaskGroup taskGroup = getTaskGroup();
         Mockito.when(taskGroupMapper.insert(taskGroup)).thenReturn(1);
         Mockito.when(taskGroupMapper.queryByName(loginUser.getId(), taskGroupName)).thenReturn(null);
-        Map<String, Object> result = taskGroupService.createTaskGroup(loginUser,0, taskGroupName, taskGroupDesc, 100);
+        Map<String, Object> result = taskGroupService.createTaskGroup(loginUser,0L, taskGroupName, taskGroupDesc, 100);
         Assert.assertNotNull(result);
 
     }
diff --git a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/TaskDefinition.java b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/TaskDefinition.java
index 5841fa3..699b2ec 100644
--- a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/TaskDefinition.java
+++ b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/TaskDefinition.java
@@ -472,7 +472,9 @@ public class TaskDefinition {
                 && timeoutFlag == that.timeoutFlag
                 && timeoutNotifyStrategy == that.timeoutNotifyStrategy
                 && Objects.equals(resourceIds, that.resourceIds)
-                && environmentCode == that.environmentCode;
+                && environmentCode == that.environmentCode
+                && taskGroupId == that.taskGroupId
+                && taskGroupPriority == that.taskGroupPriority;
     }
 
     @Override
@@ -496,6 +498,8 @@ public class TaskDefinition {
                 + ", workerGroup='" + workerGroup + '\''
                 + ", failRetryTimes=" + failRetryTimes
                 + ", environmentCode='" + environmentCode + '\''
+                + ", taskGroupId='" + taskGroupId + '\''
+                + ", taskGroupPriority='" + taskGroupPriority + '\''
                 + ", failRetryInterval=" + failRetryInterval
                 + ", timeoutFlag=" + timeoutFlag
                 + ", timeoutNotifyStrategy=" + timeoutNotifyStrategy
diff --git a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/TaskGroup.java b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/TaskGroup.java
index 3c2e021..7994f6c 100644
--- a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/TaskGroup.java
+++ b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/TaskGroup.java
@@ -56,7 +56,7 @@ public class TaskGroup implements Serializable {
     /**
      * 0 not available, 1 available
      */
-    private int status;
+    private Integer status;
     /**
      * create time
      */
diff --git a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskDefinitionLogMapper.xml b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskDefinitionLogMapper.xml
index 7caddc1..212b213 100644
--- a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskDefinitionLogMapper.xml
+++ b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskDefinitionLogMapper.xml
@@ -21,7 +21,7 @@
     <sql id="baseSql">
         id, code, name, version, description, project_code, user_id, task_type, task_params, flag, task_priority,
         worker_group, environment_code, fail_retry_times, fail_retry_interval, timeout_flag, timeout_notify_strategy, timeout, delay_time,
-        resource_ids, operator, operate_time, create_time, update_time,task_group_id
+        resource_ids, operator, operate_time, create_time, update_time,task_group_id,task_group_priority
     </sql>
     <select id="queryMaxVersionForDefinition" resultType="java.lang.Integer">
         select max(version)
diff --git a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskDefinitionMapper.xml b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskDefinitionMapper.xml
index 75d22da..f6185c8 100644
--- a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskDefinitionMapper.xml
+++ b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskDefinitionMapper.xml
@@ -21,7 +21,7 @@
     <sql id="baseSql">
         id, code, name, version, description, project_code, user_id, task_type, task_params, flag, task_priority,
         worker_group, environment_code, fail_retry_times, fail_retry_interval, timeout_flag, timeout_notify_strategy, timeout, delay_time,
-        resource_ids, create_time, update_time, task_group_id
+        resource_ids, create_time, update_time, task_group_id,task_group_priority
     </sql>
     <select id="queryByName" resultType="org.apache.dolphinscheduler.dao.entity.TaskDefinition">
         select
diff --git a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskGroupMapper.xml b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskGroupMapper.xml
index 3dd654a..7db5607 100644
--- a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskGroupMapper.xml
+++ b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskGroupMapper.xml
@@ -32,7 +32,7 @@
     </resultMap>
 
     <sql id = "baseSql">
-        id,name,description,project_code,group_size,use_size,create_time,update_time
+        id,name,description,project_code,group_size,use_size,status,create_time,update_time
     </sql>
 
     <select id="queryTaskGroupPaging" resultType="org.apache.dolphinscheduler.dao.entity.TaskGroup">
diff --git a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskGroupQueueMapper.xml b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskGroupQueueMapper.xml
index b9ff9b1..0d4a6be 100644
--- a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskGroupQueueMapper.xml
+++ b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/TaskGroupQueueMapper.xml
@@ -125,14 +125,14 @@
 
     <select id="queryTaskGroupQueueByTaskGroupIdPaging" resultType="org.apache.dolphinscheduler.dao.entity.TaskGroupQueue">
         select
-            queue.id, queue.task_name, queue.group_id, queue.process_id, queue.priority, queue.status
-             , queue.force_start, queue.create_time, queue.update_time,
-               process.name as processInstanceName,p.name as projectName,p.code as projectCode
+        queue.id, queue.task_name, queue.group_id, queue.process_id, queue.priority, queue.status
+        , queue.force_start, queue.create_time, queue.update_time,
+        process.name as processInstanceName,p.name as projectName,p.code as projectCode
         from t_ds_task_group_queue queue
-            left join t_ds_process_instance process on queue.process_id = process.id
-            left join t_ds_process_definition p_f on process.process_definition_code = p_f.code
-                                                         and process.process_definition_version = p_f.version
-            join t_ds_project as p on p_f.project_code = p.code
+        left join t_ds_process_instance process on queue.process_id = process.id
+        left join t_ds_process_definition p_f on process.process_definition_code = p_f.code
+        and process.process_definition_version = p_f.version
+        join t_ds_project as p on p_f.project_code = p.code
         where queue.group_id = #{groupId}
         <if test="taskName != null and taskName != ''">
             and task_name =#{taskName}
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue
index 9130b08..a1f5e05 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue
@@ -32,6 +32,7 @@
       <m-form-model
         v-if="taskDrawer"
         :nodeData="nodeData"
+        :project-code="projectCode"
         @seeHistory="seeHistory"
         @addTaskInfo="addTaskInfo"
         @close="closeTaskDrawer"
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/_source/taskGroups.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/_source/taskGroups.vue
new file mode 100644
index 0000000..75b042c
--- /dev/null
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/_source/taskGroups.vue
@@ -0,0 +1,86 @@
+/*
+ * 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>
+  <el-select
+          :disabled="isDetails"
+          @change="_onChange"
+          v-model="selectedValue"
+          size="small"
+          clearable
+          style="width: 180px">
+    <el-option
+            v-for="item in taskGroupList"
+            :key="item.id"
+            :value="item.id"
+            :label="item.name">
+    </el-option>
+  </el-select>
+</template>
+<script>
+  import disabledState from '@/module/mixin/disabledState'
+  export default {
+    name: 'form-task-group',
+    data () {
+      return {
+        selectedValue: this.value,
+        taskGroupList: []
+      }
+    },
+    mixins: [disabledState],
+    props: {
+      projectCode: {
+        type: Number
+      },
+      value: {
+        type: Number
+      }
+    },
+    model: {
+      prop: 'value',
+      event: 'taskGroupIdEvent'
+    },
+    methods: {
+      _onChange (o) {
+        this.$emit('taskGroupIdEvent', o)
+      }
+    },
+    watch: {
+      value (val) {
+        this.selectedValue = val
+      }
+    },
+    created () {
+      let stateTaskGroupList = this.store.state.resource.taskGroupListAll || []
+      if (stateTaskGroupList.length) {
+        this.taskGroupList = stateTaskGroupList
+      } else {
+        let params = {
+          pageNo: 1,
+          pageSize: 2147483647,
+          projectCode: this.projectCode
+        }
+        this.store.dispatch('resource/getTaskGroupListPagingByProjectCode', params).then(res => {
+          this.$nextTick(() => {
+            if (res.totalList) {
+              this.taskGroupList = res.totalList
+            }
+          })
+        })
+      }
+    }
+  }
+</script>
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue
index 1c6e1f2..24e2640 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue
@@ -151,6 +151,30 @@
           </div>
         </m-list-box>
 
+        <!-- Worker group and environment -->
+        <m-list-box>
+          <div slot="text">{{ $t("Task group name") }}</div>
+          <div slot="content">
+            <span class="label-box" style="width: 193px; display: inline-block">
+              <m-task-groups
+                v-model="taskGroupId"
+                :project-code="this.projectCode"
+                v-on:taskGroupIdEvent="_onUpdateTaskGroupId"
+              ></m-task-groups>
+            </span>
+            <span class="text-b">{{ $t("Task group queue priority") }}</span>
+            <el-input
+              :disabled="taskGroupId===''"
+              style="width: 166px;"
+              type="input"
+              v-model="taskGroupPriority"
+              maxlength="60"
+              v-on:input="_onUpdateTaskGroupPriority"
+              size="small">
+            </el-input>
+          </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>
@@ -478,6 +502,7 @@
   import mDependentTimeout from './_source/dependentTimeout'
   import mWorkerGroups from './_source/workerGroups'
   import mRelatedEnvironment from './_source/relatedEnvironment'
+  import mTaskGroups from './_source/taskGroups'
   import mPreTasks from './tasks/pre_tasks'
   import clickoutside from '@/module/util/clickoutside'
   import disabledState from '@/module/mixin/disabledState'
@@ -539,6 +564,8 @@
         // selected environment
         environmentCode: '',
         selectedWorkerGroup: '',
+        taskGroupId: '',
+        taskGroupPriority: 0,
         stateList: [
           {
             value: 'success',
@@ -575,7 +602,10 @@
         type: String,
         default: ''
       },
-      taskDefinition: Object
+      taskDefinition: Object,
+      projectCode: {
+        type: Number
+      }
     },
     inject: ['dagChart'],
     methods: {
@@ -619,7 +649,9 @@
           type: task.taskType,
           waitStartTimeout: task.taskParams.waitStartTimeout,
           workerGroup: task.workerGroup,
-          environmentCode: task.environmentCode
+          environmentCode: task.environmentCode,
+          taskGroupId: task.taskGroupId,
+          taskGroupPriority: task.taskGroupPriority
         }
       },
       /**
@@ -723,6 +755,15 @@
       _onUpdateEnvironmentCode (o) {
         this.environmentCode = o
       },
+      _onUpdateTaskGroupId (o) {
+        this.taskGroupId = o
+        if (this.taskGroupId === '') {
+          this.taskGroupPriority = 0
+        }
+      },
+      _onUpdateTaskGroupPriority (o) {
+        this.taskGroupPriority = o
+      },
       /**
        * _onCacheParams is reserved
        */
@@ -837,7 +878,9 @@
             timeoutNotifyStrategy: this.timeout.strategy,
             timeout: this.timeout.interval || 0,
             delayTime: this.delayTime,
-            environmentCode: this.environmentCode || -1
+            environmentCode: this.environmentCode || -1,
+            taskGroupId: this.taskGroupId,
+            taskGroupPriority: this.taskGroupPriority
           },
           fromThis: this
         })
@@ -952,6 +995,8 @@
           this.params = o.params || {}
           this.dependence = o.dependence || {}
           this.cacheDependence = o.dependence || {}
+          this.taskGroupId = o.taskGroupId
+          this.taskGroupPriority = o.taskGroupPriority
         } else {
           this.workerGroup = this.store.state.security.workerGroupsListAll[0].id
         }
@@ -966,7 +1011,6 @@
       // Backfill data
       let o = {}
       this.code = this.nodeData.id
-
       if (this.fromTaskDefinition) {
         if (this.taskDefinition) {
           const backfillItem = this.taskToBackfillItem(this.taskDefinition)
@@ -1061,7 +1105,8 @@
       mPriority,
       mWorkerGroups,
       mRelatedEnvironment,
-      mPreTasks
+      mPreTasks,
+      mTaskGroups
     // ReferenceFromTask
     }
   }
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/index.vue b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/index.vue
new file mode 100644
index 0000000..02e700e
--- /dev/null
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/index.vue
@@ -0,0 +1,24 @@
+/*
+ * 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>
+  <router-view></router-view>
+</template>
+<script>
+  export default {
+    name: 'task-group-option-index'
+  }
+</script>
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/security/pages/environment/_source/createEnvironment.vue b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/taskGroupOption/_source/createTaskGroup.vue
similarity index 50%
copy from dolphinscheduler-ui/src/js/conf/home/pages/security/pages/environment/_source/createEnvironment.vue
copy to dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/taskGroupOption/_source/createTaskGroup.vue
index 8e5ee8d..3fea8b7 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/security/pages/environment/_source/createEnvironment.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/taskGroupOption/_source/createTaskGroup.vue
@@ -17,9 +17,9 @@
 <template>
   <m-popover ref="popover" :ok-text="item && item.name ? $t('Edit') : $t('Submit')" @ok="_ok" @close="close">
     <template slot="content">
-      <div class="create-environment-model">
+      <div class="create-task-group-model">
         <m-list-box-f>
-          <template slot="name"><strong>*</strong>{{$t('Environment Name')}}</template>
+          <template slot="name"><strong>*</strong>{{$t('Task group name')}}</template>
           <template slot="content">
             <el-input
                     type="input"
@@ -30,48 +30,49 @@
             </el-input>
           </template>
         </m-list-box-f>
-       <m-list-box-f>
-        <template slot="name"><strong>*</strong>{{$t('Environment Config')}}</template>
-        <template slot="content">
-          <el-input
-                  type="textarea"
-                  :autosize="{ minRows: 10, maxRows: 20 }"
-                  v-model="config"
-                  :placeholder="configExample">
-          </el-input>
-        </template>
-        </m-list-box-f>
-        <m-list-box-f>
-          <template slot="name"><strong>*</strong>{{$t('Environment Desc')}}</template>
-          <template slot="content">
-            <el-input
-                    type="input"
-                    v-model="description"
-                    maxlength="60"
-                    size="mini"
-                    :placeholder="$t('Please enter environment desc')">
-            </el-input>
-          </template>
-        </m-list-box-f>
         <m-list-box-f>
-          <template slot="name">{{$t('Environment Worker Group')}}</template>
+          <template slot="name"><strong>*</strong>{{$t('Project Name')}}</template>
           <template slot="content">
             <el-select
-              v-model="workerGroups"
+              :disabled="item.modalType==='edit'"
+              v-model="projectCode"
               size="mini"
-              multiple
               collapse-tags
               style="display: block;"
-              :placeholder="$t('Please select worker groups')">
+              :placeholder="$t('Please select project')">
               <el-option
-                v-for="item in workerGroupOptions"
-                :key="item.id"
-                :label="item.id"
-                :value="item.name">
+                v-for="item in projectOptions"
+                :key="item.code"
+                :label="item.name"
+                :value="item.code">
               </el-option>
             </el-select>
           </template>
         </m-list-box-f>
+        <m-list-box-f>
+          <template slot="name"><strong>*</strong>{{$t('Task group resource pool size')}}</template>
+          <template slot="content">
+            <el-input
+              type="input"
+              v-model="groupSize"
+              maxlength="60"
+              size="mini"
+              :placeholder="$t('Please enter task group resource pool size')">
+            </el-input>
+          </template>
+        </m-list-box-f>
+        <m-list-box-f>
+          <template slot="name"><strong>*</strong>{{$t('Task group desc')}}</template>
+          <template slot="content">
+            <el-input
+                    type="input"
+                    v-model="description"
+                    maxlength="60"
+                    size="mini"
+                    :placeholder="$t('Please enter task group desc')">
+            </el-input>
+          </template>
+        </m-list-box-f>
       </div>
     </template>
   </m-popover>
@@ -85,39 +86,27 @@
   import mListBoxF from '@/module/components/listBoxF/listBoxF'
 
   export default {
-    name: 'create-environment',
+    name: 'create-task-group',
     data () {
       return {
         store,
-        workerGroups: [],
-        workerGroupOptions: [],
-        environment: '',
         name: '',
-        config: '',
-        description: '',
-        configExample: 'export HADOOP_HOME=/opt/hadoop-2.6.5\n' +
-          'export HADOOP_CONF_DIR=/etc/hadoop/conf\n' +
-          'export SPARK_HOME=/opt/soft/spark\n' +
-          'export PYTHON_HOME=/opt/soft/python\n' +
-          'export JAVA_HOME=/opt/java/jdk1.8.0_181-amd64\n' +
-          'export HIVE_HOME=/opt/soft/hive\n' +
-          'export FLINK_HOME=/opt/soft/flink\n' +
-          'export DATAX_HOME=/opt/soft/datax\n' +
-          'export YARN_CONF_DIR=/etc/hadoop/conf\n' +
-          'export PATH=$HADOOP_HOME/bin:$SPARK_HOME/bin:$PYTHON_HOME/bin:$JAVA_HOME/bin:$HIVE_HOME/bin:$FLINK_HOME/bin:$DATAX_HOME/bin:$PATH\n' +
-          'export HADOOP_CLASSPATH=`hadoop classpath`\n'
+        projectCode: '',
+        groupSize: 10,
+        projects: [],
+        project: [],
+        projectOptions: [],
+        description: ''
       }
     },
     props: {
       item: Object
     },
     methods: {
-      ...mapActions('security', ['getWorkerGroupsAll']),
-      _getWorkerGroupList () {
-        this.getWorkerGroupsAll().then(res => {
-          this.workerGroups = res
-          console.log('get Worker Group List')
-          console.log(this.workerGroups)
+      ...mapActions('projects', ['getProjectsList']),
+      _getProjectList () {
+        this.getProjectsList().then(res => {
+          this.projects = res
         })
       },
       _ok () {
@@ -127,9 +116,9 @@
 
         let param = {
           name: _.trim(this.name),
-          config: _.trim(this.config),
-          description: _.trim(this.description),
-          workerGroups: JSON.stringify(this.workerGroups)
+          projectCode: this.projectCode,
+          groupSize: _.trim(this.groupSize),
+          description: _.trim(this.description)
         }
 
         let $then = (res) => {
@@ -146,54 +135,40 @@
         if (this.item && this.item.name) {
           this.$refs.popover.spinnerLoading = true
           let updateParam = {
-            code: this.item.code,
+            id: this.item.id,
             name: _.trim(this.name),
-            config: _.trim(this.config),
-            description: _.trim(this.description),
-            workerGroups: JSON.stringify(this.workerGroups)
+            groupSize: _.trim(this.groupSize),
+            description: _.trim(this.description)
           }
-          this.store.dispatch('security/updateEnvironment', updateParam).then(res => {
+          this.store.dispatch('resource/updateTaskGroup', updateParam).then(res => {
             $then(res)
           }).catch(e => {
             $catch(e)
           })
         } else {
-          this._verifyName(param).then(() => {
-            this.$refs.popover.spinnerLoading = true
-            this.store.dispatch('security/createEnvironment', param).then(res => {
-              $then(res)
-            }).catch(e => {
-              $catch(e)
-            })
+          this.$refs.popover.spinnerLoading = true
+          this.store.dispatch('resource/createTaskGroup', param).then(res => {
+            $then(res)
           }).catch(e => {
-            this.$message.error(e.msg || '')
+            $catch(e)
           })
         }
       },
       _verification () {
-        if (!this.name.replace(/\s*/g, '')) {
+        if (!this.name || !this.name.replace(/\s*/g, '')) {
           this.$message.warning(`${i18n.$t('Please enter name')}`)
           return false
         }
-        if (!this.config.replace(/\s*/g, '')) {
-          this.$message.warning(`${i18n.$t('Please enter environment config')}`)
+        if (this.groupSize < 1) {
+          this.$message.warning(`${i18n.$t('Task group resource pool size be a number')}`)
           return false
         }
-        if (!this.description.replace(/\s*/g, '')) {
-          this.$message.warning(`${i18n.$t('Please enter environment desc')}`)
+        if (!this.description || !this.description.replace(/\s*/g, '')) {
+          this.$message.warning(`${i18n.$t('Please enter task group desc')}`)
           return false
         }
         return true
       },
-      _verifyName (param) {
-        return new Promise((resolve, reject) => {
-          this.store.dispatch('security/verifyEnvironment', { environmentName: param.name }).then(res => {
-            resolve()
-          }).catch(e => {
-            reject(e)
-          })
-        })
-      },
       close () {
         this.$emit('close')
       }
@@ -202,10 +177,11 @@
       item: {
         handler (val, oldVal) {
           this.name = val.name
-          this.config = val.config
+          this.projectCode = val.projectCode
+          this.groupSize = val.groupSize
           this.description = val.description
-          this.workerGroups = val.workerGroups
-          this.workerGroupOptions = val.workerGroupOptions
+          this.projectOptions = val.projectOptions
+          this.modalType = val.modalType
         },
         deep: true
       }
@@ -213,11 +189,11 @@
     created () {
       if (this.item && this.item.name) {
         this.name = this.item.name
-        this.config = this.item.config
+        this.projectCode = this.item.projectCode
+        this.groupSize = this.item.groupSize
         this.description = this.item.description
-        this.workerGroups = this.item.workerGroups
       }
-      this.workerGroupOptions = this.item.workerGroupOptions
+      this.projectOptions = this.item.projectOptions
     },
     mounted () {
     },
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/taskGroupOption/_source/list.vue b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/taskGroupOption/_source/list.vue
new file mode 100644
index 0000000..0b57bcd
--- /dev/null
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/taskGroupOption/_source/list.vue
@@ -0,0 +1,135 @@
+/*
+ * 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>
+  <div class="list-model">
+    <div class="table-box">
+      <el-table :data="list" size="mini" style="width: 100%">
+        <el-table-column type="index" :label="$t('#')" width="50"></el-table-column>
+        <el-table-column prop="name" :label="$t('Task group name')" width="150"></el-table-column>
+        <el-table-column prop="projectName" :label="$t('Project Name')"></el-table-column>
+        <el-table-column prop="groupSize" :label="$t('Task group resource pool size')" min-width="50"></el-table-column>
+        <el-table-column prop="useSize" :label="$t('Task group resource used pool size')" min-width="50"></el-table-column>
+        <el-table-column prop="description" :label="$t('Task group desc')" min-width="50"></el-table-column>
+        <el-table-column :label="$t('Create Time')" min-width="50">
+          <template slot-scope="scope">
+            <span>{{scope.row.createTime | formatDate}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column :label="$t('Update Time')" min-width="50">
+          <template slot-scope="scope">
+            <span>{{scope.row.updateTime | formatDate}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="status" :label="$t('Task group status')" min-width="50">
+          <template slot-scope="scope">
+            <el-tooltip :content="scope.row.status? $t('Task group enable status'):$t('Task group disable status')" placement="top">
+              <el-switch
+                v-model="scope.row.status"
+                active-color="#13ce66"
+                inactive-color="#ff4949"
+                @change="_switchTaskGroupStatus(scope.row)"/>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+        <el-table-column :label="$t('Operation')" width="100">
+          <template slot-scope="scope">
+          <el-tooltip :content="$t('Edit')" placement="top">
+             <el-button type="primary" size="mini" icon="el-icon-edit-outline" @click="_edit(scope.row)" circle></el-button>
+           </el-tooltip>
+           <el-tooltip :content="$t('View task group queue')" placement="top">
+             <el-button type="success" size="mini" icon="el-icon-tickets" @click="_switchTaskGroupQueue(scope.row)" circle></el-button>
+           </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+  </div>
+</template>
+<script>
+  import { mapActions } from 'vuex'
+
+  export default {
+    name: 'task-group-list',
+    data () {
+      return {
+        list: [],
+        switchValue: true
+      }
+    },
+    props: {
+      taskGroupList: Array,
+      pageNo: Number,
+      pageSize: Number
+    },
+    methods: {
+      ...mapActions('resource', ['closeTaskGroup', 'startTaskGroup']),
+      _switchTaskGroupStatus (item, i) {
+        if (item.status) {
+          this.startTaskGroup({
+            id: item.id
+          }).then(res => {
+            let newList = []
+            this.list.forEach(item => {
+              if (item.id !== i) {
+                newList.push(item)
+              }
+            })
+            this.list = newList
+            this.$message.success(res.msg)
+          }).catch(e => {
+            this.$message.error(e.msg || '')
+          })
+        } else {
+          this.closeTaskGroup({
+            id: item.id
+          }).then(res => {
+            let newList = []
+            this.list.forEach(item => {
+              if (item.id !== i) {
+                newList.push(item)
+              }
+            })
+            this.list = newList
+            this.$message.success(res.msg)
+          }).catch(e => {
+            this.$message.error(e.msg || '')
+          })
+        }
+      },
+      _switchTaskGroupQueue (item) {
+        this.$router.push({ path: `/resource/task-group-queue?id=${item.id}` })
+      },
+      _edit (item) {
+        this.$emit('on-edit', item)
+      }
+    },
+    watch: {
+      taskGroupList (a) {
+        this.list = []
+        setTimeout(() => {
+          this.list = a
+        })
+      }
+    },
+    created () {
+      this.list = this.taskGroupList
+    },
+    mounted () {
+    },
+    components: { }
+  }
+</script>
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/taskGroupOption/index.vue b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/taskGroupOption/index.vue
new file mode 100644
index 0000000..76cbc3a
--- /dev/null
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/taskGroupOption/index.vue
@@ -0,0 +1,176 @@
+/*
+ * 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>
+  <m-list-construction :title="$t('Task group option')">
+    <template slot="conditions">
+      <m-conditions @on-conditions="_onConditions">
+        <template slot="button-group" v-if="isADMIN">
+          <el-button size="mini" @click="_create()">{{$t('Create task group')}}</el-button>
+          <el-dialog
+            :title="item && item.name ? $t('Edit task group') : $t('Create task group')"
+            :v-if="createTaskGroupDialog"
+            :visible.sync="createTaskGroupDialog"
+            width="auto">
+            <m-create-task-group :item="item" @onUpdate="onUpdate" @close="close"></m-create-task-group>
+          </el-dialog>
+        </template>
+      </m-conditions>
+    </template>
+
+    <template slot="content">
+      <template v-if="taskGroupList.length || total>0">
+        <m-list @on-edit="_onEdit"
+                :task-group-list="taskGroupList"
+                :page-no="searchParams.pageNo"
+                :page-size="searchParams.pageSize">
+
+        </m-list>
+        <div class="page-box">
+          <el-pagination
+            background
+            @current-change="_page"
+            @size-change="_pageSize"
+            :page-size="searchParams.pageSize"
+            :current-page.sync="searchParams.pageNo"
+            :page-sizes="[10, 30, 50]"
+            layout="sizes, prev, pager, next, jumper"
+            :total="total">
+          </el-pagination>
+        </div>
+      </template>
+      <template v-if="!taskGroupList.length && total<=0">
+        <m-no-data></m-no-data>
+      </template>
+      <m-spin :is-spin="isLoading" :is-left="isLeft"></m-spin>
+    </template>
+  </m-list-construction>
+</template>
+<script>
+  import _ from 'lodash'
+  import { mapActions } from 'vuex'
+  import mList from './_source/list'
+  import store from '@/conf/home/store'
+  import mSpin from '@/module/components/spin/spin'
+  import mCreateTaskGroup from './_source/createTaskGroup'
+  import mNoData from '@/module/components/noData/noData'
+  import listUrlParamHandle from '@/module/mixin/listUrlParamHandle'
+  import mConditions from '@/module/components/conditions/conditions'
+  import mListConstruction from '@/module/components/listConstruction/listConstruction'
+
+  export default {
+    name: 'task-group-index',
+    data () {
+      return {
+        total: null,
+        isLoading: true,
+        modalType: 'create',
+        taskGroupList: [],
+        projectList: [],
+        environmentWorkerGroupRelationList: [],
+        searchParams: {
+          pageSize: 10,
+          pageNo: 1,
+          searchVal: ''
+        },
+        isLeft: true,
+        isADMIN: store.state.user.userInfo.userType === 'ADMIN_USER',
+        item: {},
+        createTaskGroupDialog: false
+      }
+    },
+    mixins: [listUrlParamHandle],
+    props: {},
+    methods: {
+      ...mapActions('projects', ['getProjectsList']),
+      ...mapActions('resource', ['getTaskGroupListPaging']),
+      /**
+       * Query
+       */
+      _onConditions (o) {
+        this.searchParams = _.assign(this.searchParams, o)
+        this.searchParams.pageNo = 1
+        this.searchParams.name = this.searchParams.searchVal
+        this._getList(false)
+      },
+      _page (val) {
+        this.searchParams.pageNo = val
+      },
+      _pageSize (val) {
+        this.searchParams.pageSize = val
+      },
+      _onEdit (item) {
+        this.item = item
+        this.item.modalType = 'edit'
+        this.createTaskGroupDialog = true
+      },
+      _create () {
+        this.item = { projectOptions: this.projectList, modalType: 'create' }
+        this.createTaskGroupDialog = true
+      },
+      onUpdate () {
+        this._debounceGET('false')
+        this.createTaskGroupDialog = false
+      },
+      close () {
+        this.createTaskGroupDialog = false
+      },
+      _getList (flag) {
+        const projectSearchParams = {
+          pageNo: 1,
+          pageSize: 2147483647
+        }
+        if (sessionStorage.getItem('isLeft') === 0) {
+          this.isLeft = false
+        } else {
+          this.isLeft = true
+        }
+        this.isLoading = !flag
+        Promise.all([this.getTaskGroupListPaging(this.searchParams), this.getProjectsList(projectSearchParams)]).then((values) => {
+          if (this.searchParams.pageNo > 1 && values[0].totalList.length === 0) {
+            this.searchParams.pageNo = this.searchParams.pageNo - 1
+          } else {
+            this.taskGroupList = []
+            this.taskGroupList = values[0].totalList
+            this.total = values[0].total
+            this.isLoading = false
+          }
+          if (values[1] && values[1].totalList) {
+            this.projectList = values[1].totalList
+          }
+          this.taskGroupList.forEach(item => {
+            item.status = item.status === 1
+            item.projectOptions = this.projectList
+            item.projectName = _.find(this.projectList, { code: item.projectCode }).name
+          })
+        }).catch(e => {
+          this.isLoading = false
+        })
+      }
+    },
+    watch: {
+      // router
+      '$route' (a) {
+        // url no params get instance list
+        this.searchParams.pageNo = _.isEmpty(a.query) ? 1 : a.query.pageNo
+      }
+    },
+    beforeDestroy () {
+      sessionStorage.setItem('isLeft', 1)
+    },
+    components: { mList, mListConstruction, mConditions, mSpin, mNoData, mCreateTaskGroup }
+  }
+</script>
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/taskGroupQueue/_source/list.vue b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/taskGroupQueue/_source/list.vue
new file mode 100644
index 0000000..30f8c69
--- /dev/null
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/taskGroupQueue/_source/list.vue
@@ -0,0 +1,154 @@
+/*
+ * 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>
+  <div class="list-model">
+    <div class="table-box">
+      <el-table :data="list" size="mini" style="width: 100%">
+        <el-table-column type="index" :label="$t('#')" width="50"></el-table-column>
+        <el-table-column prop="projectName" :label="$t('Project Name')" width="120"></el-table-column>
+        <el-table-column prop="taskName" :label="$t('Task Name')" width="120"></el-table-column>
+        <el-table-column prop="processInstanceName" :label="$t('Process Instance')" min-width="120"></el-table-column>
+        <el-table-column prop="taskGroupName" :label="$t('Task group name')" width="120"></el-table-column>
+        <el-table-column prop="priority" :label="$t('Task group queue priority')" min-width="70"></el-table-column>
+        <el-table-column prop="forceStart" :label="$t('Task group queue force starting status')" min-width="100"></el-table-column>
+        <el-table-column prop="inQueue" :label="$t('Task group in queue')" min-width="100"></el-table-column>
+        <el-table-column prop="status" :label="$t('Task group queue status')" min-width="70"></el-table-column>
+        <el-table-column :label="$t('Create Time')" min-width="50">
+          <template slot-scope="scope">
+            <span>{{scope.row.createTime | formatDate}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column :label="$t('Update Time')" min-width="50">
+          <template slot-scope="scope">
+            <span>{{scope.row.updateTime | formatDate}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column :label="$t('Operation')" width="100">
+          <template slot-scope="scope">
+          <el-tooltip :content="$t('Modify task group queue priority')" placement="top">
+             <el-button type="primary" size="mini" icon="el-icon-edit-outline" @click="_edit(scope.row)" circle></el-button>
+           </el-tooltip>
+           <el-tooltip :content="$t('Force to start task')" placement="top">
+             <el-button type="primary" size="mini" icon="el-icon-video-play" @click="_forceStart(scope.row)" circle></el-button>
+           </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-dialog
+        v-if="dialogVisible"
+        :title="$t('Modify task group queue priority')"
+        :visible.sync="dialogVisible"
+        width="25%"
+        center
+        modal>
+        <el-form :model="priorityForm" ref="priorityForm">
+          <el-form-item prop="priority" :label="$t('Task group queue priority')"  :rules="[{ required: true, message: notEmptyMessage }, { type: 'number', message: mustBeNumberMessage}]">
+            <el-input
+              type="input"
+              v-model.number="priorityForm.priority"
+              maxlength="60"
+              autocomplete="off"
+              size="mini">
+            </el-input>
+          </el-form-item>
+        </el-form>
+        <span slot="footer" class="dialog-footer">
+        <el-button @click="dialogVisible = false">{{ $t('Cancel') }}</el-button>
+        <el-button type="primary" @click="_editPriority()">{{ $t('Confirm') }}</el-button>
+        </span>
+      </el-dialog>
+    </div>
+  </div>
+</template>
+<script>
+  import { mapActions } from 'vuex'
+  import _ from 'lodash'
+  import i18n from '@/module/i18n'
+
+  export default {
+    name: 'task-group-list',
+    data () {
+      return {
+        list: [],
+        switchValue: true,
+        dialogVisible: false,
+        notEmptyMessage: $t('Priority not empty'),
+        mustBeNumberMessage: $t('Priority must be number'),
+        priorityForm: {
+          priority: 0,
+          queueId: 0
+        }
+      }
+    },
+    props: {
+      taskGroupQueue: Array,
+      pageNo: Number,
+      pageSize: Number
+    },
+    methods: {
+      ...mapActions('resource', ['modifyPriority', 'forceStartTaskInQueue']),
+      ...mapActions('resource', ['getTaskListInTaskGroupQueueById']),
+      _edit (item) {
+        this.priorityForm.priority = item.priority
+        this.priorityForm.queueId = item.id
+        this.dialogVisible = true
+        this.$emit('on-edit', item)
+      },
+      _editPriority () {
+        if (this.priorityForm.priority >= 0 || _.parseInt(this.priorityForm.priority) >= 0) {
+          const params = {
+            queueId: this.priorityForm.queueId,
+            priority: this.priorityForm.priority
+          }
+          this.modifyPriority(params).then(res => {
+            this.$emit('on-edit-priority')
+            this.$message.success(res.msg)
+          }).catch(e => {
+            this.$message.error(e.msg || '')
+          })
+        } else {
+          this.$message.warning(`${i18n.$t('Task group queue priority be a number')}`)
+        }
+      },
+      _forceStart (item) {
+        const params = {
+          queueId: item.id
+        }
+        this.forceStartTaskInQueue(params).then(res => {
+          this.$emit('on-force-start', item)
+          this.$message.success(res.msg)
+        }).catch(e => {
+          this.$message.error(e.msg || '')
+        })
+      }
+    },
+    watch: {
+      taskGroupList (a) {
+        this.list = []
+        setTimeout(() => {
+          this.list = a
+        })
+      }
+    },
+    created () {
+      this.list = this.taskGroupQueue
+    },
+    mounted () {
+    },
+    components: { }
+  }
+</script>
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/taskGroupQueue/index.vue b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/taskGroupQueue/index.vue
new file mode 100644
index 0000000..3f2952f
--- /dev/null
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/taskGroups/taskGroupQueue/index.vue
@@ -0,0 +1,211 @@
+/*
+ * 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>
+  <m-list-construction :title="$t('Task group queue')">
+    <template slot="conditions">
+      <m-conditions @on-conditions="_onConditions">
+        <template slot="search-group">
+          <div class="list">
+            <el-button size="mini" @click="_ckQuery" icon="el-icon-search"></el-button>
+          </div>
+          <div class="list">
+            <el-input v-model="instanceName" style="width: 140px;" size="mini" :placeholder="$t('Process Instance')" clearable></el-input>
+          </div>
+          <div class="list">
+            <el-input v-model="processName" style="width: 160px;" size="mini" :placeholder="$t('Process Name')" clearable></el-input>
+          </div>
+          <div class="list">
+            <el-select style="width: 140px;" v-model="groupId" :placeholder="$t('Task group name')" size="mini" clearable>
+              <el-option
+                v-for="taskGroup in taskGroupList"
+                :key="taskGroup.id"
+                :value="taskGroup.id"
+                :label="taskGroup.name">
+              </el-option>
+            </el-select>
+          </div>
+        </template>
+      </m-conditions>
+    </template>
+    <template slot="content">
+      <template v-if="taskGroupQueue.length || total>0">
+        <m-list @on-edit="_onEdit"
+                @on-force-start="_onForceStart"
+                @on-edit-priority="_onEditPriority"
+                :task-group-queue="taskGroupQueue"
+                :page-no="searchParams.pageNo"
+                :page-size="searchParams.pageSize">
+
+        </m-list>
+        <div class="page-box">
+          <el-pagination
+            background
+            @current-change="_page"
+            @size-change="_pageSize"
+            :page-size="searchParams.pageSize"
+            :current-page.sync="searchParams.pageNo"
+            :page-sizes="[10, 30, 50]"
+            layout="sizes, prev, pager, next, jumper"
+            :total="total">
+          </el-pagination>
+        </div>
+      </template>
+      <template v-if="!taskGroupList.length && total<=0">
+        <m-no-data></m-no-data>
+      </template>
+      <m-spin :is-spin="isLoading" :is-left="isLeft"></m-spin>
+    </template>
+  </m-list-construction>
+</template>
+<script>
+  import _ from 'lodash'
+  import { mapActions } from 'vuex'
+  import mList from './_source/list'
+  import store from '@/conf/home/store'
+  import mSpin from '@/module/components/spin/spin'
+  import mNoData from '@/module/components/noData/noData'
+  import listUrlParamHandle from '@/module/mixin/listUrlParamHandle'
+  import mConditions from '@/module/components/conditions/conditions'
+  import mListConstruction from '@/module/components/listConstruction/listConstruction'
+
+  export default {
+    name: 'task-group-queue-index',
+    data () {
+      return {
+        total: null,
+        isLoading: true,
+        modalType: 'create',
+        taskGroupList: [],
+        taskGroupQueue: [],
+        groupId: '',
+        instanceName: '',
+        processName: '',
+        searchParams: {
+          pageSize: 10,
+          pageNo: 1
+        },
+        isLeft: true,
+        isADMIN: store.state.user.userInfo.userType === 'ADMIN_USER',
+        item: {},
+        createTaskGroupDialog: false
+      }
+    },
+    mixins: [listUrlParamHandle],
+    props: {},
+    methods: {
+      ...mapActions('resource', ['getTaskListInTaskGroupQueueById']),
+      ...mapActions('resource', ['getTaskGroupListPaging']),
+      /**
+       * Query
+       */
+      _onConditions (o) {
+        this.searchParams = _.assign(this.searchParams, o)
+        this.searchParams.pageNo = 1
+      },
+      _ckQuery () {
+        console.log(this.groupId)
+        this.searchParams.groupId = this.groupId
+        this.searchParams.instanceName = this.instanceName
+        this.searchParams.processName = this.processName
+        this._getList(false)
+      },
+      _page (val) {
+        this.searchParams.pageNo = val
+      },
+      _pageSize (val) {
+        this.searchParams.pageSize = val
+      },
+      _onEdit (item) {
+        this.item = item
+        this.item.modalType = 'edit'
+        this.createTaskGroupDialog = true
+      },
+      _onForceStart (item) {
+        this._getList(false)
+      },
+      _onEditPriority () {
+        this._getList(false)
+      },
+      _create () {
+        this.item = { projectOptions: this.projectList, modalType: 'create' }
+        this.createTaskGroupDialog = true
+      },
+      onUpdate () {
+        this._debounceGET('false')
+        this.createTaskGroupDialog = false
+      },
+      close () {
+        this.createTaskGroupDialog = false
+      },
+      _getList (flag) {
+        const taskGroupSearchParams = {
+          pageNo: 1,
+          pageSize: 2147483647
+        }
+        if (sessionStorage.getItem('isLeft') === 0) {
+          this.isLeft = false
+        } else {
+          this.isLeft = true
+        }
+        this.isLoading = !flag
+        this.getTaskGroupListPaging(taskGroupSearchParams).then((values) => {
+          this.taskGroupList = []
+          this.taskGroupList = values.totalList
+          if (this.taskGroupList) {
+            if (this.searchParams.id) {
+              this.groupId = _.parseInt(this.searchParams.id)
+              this.searchParams.groupId = _.parseInt(this.searchParams.id)
+            }
+            this.getTaskListInTaskGroupQueueById(this.searchParams).then((res) => {
+              if (this.searchParams.pageNo > 1 && values.totalList.length === 0) {
+                this.searchParams.pageNo = this.searchParams.pageNo - 1
+              } else {
+                this.taskGroupQueue = []
+                if (res.data.totalList) {
+                  this.taskGroupQueue = res.data.totalList
+                }
+                this.taskGroupQueue.forEach(item => {
+                  const taskGroup = _.find(this.taskGroupList, { id: item.groupId })
+                  if (taskGroup) {
+                    item.taskGroupName = taskGroup.name
+                  }
+                })
+                this.total = res.data.total
+                this.isLoading = false
+              }
+            }).catch(e => {
+              this.isLoading = false
+            })
+          }
+        }).catch(e => {
+          this.isLoading = false
+        })
+      }
+    },
+    watch: {
+      // router
+      '$route' (a) {
+        // url no params get instance list
+        this.searchParams.pageNo = _.isEmpty(a.query) ? 1 : a.query.pageNo
+      }
+    },
+    beforeDestroy () {
+      sessionStorage.setItem('isLeft', 1)
+    },
+    components: { mList, mListConstruction, mConditions, mSpin, mNoData }
+  }
+</script>
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/security/pages/environment/_source/createEnvironment.vue b/dolphinscheduler-ui/src/js/conf/home/pages/security/pages/environment/_source/createEnvironment.vue
index 8e5ee8d..3b137ff 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/security/pages/environment/_source/createEnvironment.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/security/pages/environment/_source/createEnvironment.vue
@@ -116,8 +116,6 @@
       _getWorkerGroupList () {
         this.getWorkerGroupsAll().then(res => {
           this.workerGroups = res
-          console.log('get Worker Group List')
-          console.log(this.workerGroups)
         })
       },
       _ok () {
diff --git a/dolphinscheduler-ui/src/js/conf/home/router/module/resource.js b/dolphinscheduler-ui/src/js/conf/home/router/module/resource.js
index 6543995..0d262ce 100644
--- a/dolphinscheduler-ui/src/js/conf/home/router/module/resource.js
+++ b/dolphinscheduler-ui/src/js/conf/home/router/module/resource.js
@@ -152,6 +152,35 @@ const resource = [
             }
           }
         ]
+      },
+      {
+        path: '/resource/task-group',
+        name: 'task-group-manage',
+        component: resolve => require(['../../pages/resource/pages/taskGroups'], resolve),
+        meta: {
+          title: `${i18n.$t('Task group manage')}`,
+          refreshInSwitchedTab: config.refreshInSwitchedTab
+        },
+        children: [
+          {
+            path: '/resource/task-group',
+            name: 'task-group-option',
+            component: resolve => require(['../../pages/resource/pages/taskGroups/taskGroupOption'], resolve),
+            meta: {
+              title: `${i18n.$t('Task group option')}`,
+              refreshInSwitchedTab: config.refreshInSwitchedTab
+            }
+          },
+          {
+            path: '/resource/task-group-queue',
+            name: 'task-group-queue',
+            component: resolve => require(['../../pages/resource/pages/taskGroups/taskGroupQueue'], resolve),
+            meta: {
+              title: `${i18n.$t('Task group queue')}`,
+              refreshInSwitchedTab: config.refreshInSwitchedTab
+            }
+          }
+        ]
       }
     ]
   }
diff --git a/dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js b/dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js
index 5147028..6341d78 100644
--- a/dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js
+++ b/dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js
@@ -183,7 +183,9 @@ export default {
           'timeoutFlag',
           'timeoutNotifyStrategy',
           'timeout',
-          'environmentCode'
+          'environmentCode',
+          'taskGroupId',
+          'taskGroupPriority'
         ]))
 
         resolve(res.data)
diff --git a/dolphinscheduler-ui/src/js/conf/home/store/resource/actions.js b/dolphinscheduler-ui/src/js/conf/home/store/resource/actions.js
index 0ab83e2..06e2835 100755
--- a/dolphinscheduler-ui/src/js/conf/home/store/resource/actions.js
+++ b/dolphinscheduler-ui/src/js/conf/home/store/resource/actions.js
@@ -192,6 +192,114 @@ export default {
         reject(e)
       })
     })
+  },
+  /**
+   * get task group list pages
+   */
+  getTaskGroupListPaging ({ state }, payload) {
+    return new Promise((resolve, reject) => {
+      io.get('task-group/list-paging', payload, res => {
+        resolve(res.data)
+      }).catch(e => {
+        reject(e)
+      })
+    })
+  },
+  /**
+   * get task group list pages
+   */
+  getTaskGroupListPagingByProjectCode ({ state }, payload) {
+    return new Promise((resolve, reject) => {
+      io.get('task-group/query-list-by-projectCode', payload, res => {
+        resolve(res.data)
+      }).catch(e => {
+        reject(e)
+      })
+    })
+  },
+  /**
+   * create task group
+   */
+  createTaskGroup ({ state }, payload) {
+    return new Promise((resolve, reject) => {
+      io.post('task-group/create', payload, res => {
+        resolve(res)
+      }).catch(e => {
+        reject(e)
+      })
+    })
+  },
+  /**
+   * update task group
+   */
+  updateTaskGroup ({ state }, payload) {
+    return new Promise((resolve, reject) => {
+      io.post('task-group/update', payload, res => {
+        resolve(res)
+      }).catch(e => {
+        reject(e)
+      })
+    })
+  },
+  /**
+   * close task group
+   */
+  closeTaskGroup ({ state }, payload) {
+    return new Promise((resolve, reject) => {
+      io.post('task-group/close-task-group', payload, res => {
+        resolve(res)
+      }).catch(e => {
+        reject(e)
+      })
+    })
+  },
+  /**
+   * start task group
+   */
+  startTaskGroup ({ state }, payload) {
+    return new Promise((resolve, reject) => {
+      io.post('task-group/start-task-group', payload, res => {
+        resolve(res)
+      }).catch(e => {
+        reject(e)
+      })
+    })
+  },
+  /**
+   * query the details of task group through group id
+   */
+  getTaskListInTaskGroupQueueById ({ state }, payload) {
+    return new Promise((resolve, reject) => {
+      io.get('task-group/query-list-by-group-id', payload, res => {
+        resolve(res)
+      }).catch(e => {
+        reject(e)
+      })
+    })
+  },
+  /**
+   * modify the priority of the task in the queue
+   */
+  modifyPriority ({ state }, payload) {
+    return new Promise((resolve, reject) => {
+      io.post('task-group/modifyPriority', payload, res => {
+        resolve(res)
+      }).catch(e => {
+        reject(e)
+      })
+    })
+  },
+  /**
+   * force to start the task in the queue
+   */
+  forceStartTaskInQueue ({ state }, payload) {
+    return new Promise((resolve, reject) => {
+      io.post('task-group/forceStart', payload, res => {
+        resolve(res)
+      }).catch(e => {
+        reject(e)
+      })
+    })
   }
 
 }
diff --git a/dolphinscheduler-ui/src/js/module/components/secondaryMenu/_source/menu.js b/dolphinscheduler-ui/src/js/module/components/secondaryMenu/_source/menu.js
index 09395ca..f58f98d 100644
--- a/dolphinscheduler-ui/src/js/module/components/secondaryMenu/_source/menu.js
+++ b/dolphinscheduler-ui/src/js/module/components/secondaryMenu/_source/menu.js
@@ -196,6 +196,28 @@ const menu = {
           enabled: true
         }
       ]
+    },
+    {
+      name: `${i18n.$t('Task group manage')}`,
+      id: 2,
+      path: '',
+      isOpen: true,
+      enabled: true,
+      icon: 'el-icon-setting',
+      children: [
+        {
+          name: `${i18n.$t('Task group option')}`,
+          path: 'task-group-option',
+          id: 0,
+          enabled: true
+        },
+        {
+          name: `${i18n.$t('Task group queue')}`,
+          path: 'task-group-queue',
+          id: 1,
+          enabled: true
+        }
+      ]
     }
   ],
   user: [
diff --git a/dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js b/dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js
index 7f3c654..52473c8 100755
--- a/dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js
+++ b/dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js
@@ -764,5 +764,35 @@ export default {
   scheduleEndTime: 'Schedule End Time',
   crontabExpression: 'Crontab',
   workflowPublishStatus: 'Workflow Publish Status',
-  schedulePublishStatus: 'Schedule Publish Status'
+  schedulePublishStatus: 'Schedule Publish Status',
+  'Task group manage': 'Task group manage',
+  'Task group option': 'Task group option',
+  'Create task group': 'Create task group',
+  'Edit task group': 'Edit task group',
+  'Delete task group': 'Delete task group',
+  'Task group code': 'Task group code',
+  'Task group name': 'Task group name',
+  'Task group resource pool size': 'Resource pool size',
+  'Task group resource pool size be a number': 'The size of the task group resource pool should be more than 1',
+  'Task group resource used pool size': 'Used resource',
+  'Task group desc': 'Task group desc',
+  'Task group status': 'Task group status',
+  'Task group enable status': 'Enable',
+  'Task group disable status': 'Disable',
+  'Please enter task group desc': 'Please enter task group description',
+  'Please enter task group resource pool size': 'Please enter task group resource pool size',
+  'Please select project': 'Please select a project',
+  'Task group queue': 'Task group queue',
+  'Task group queue priority': 'Priority',
+  'Task group queue priority be a number': 'The priority of the task group queue should be a positive number',
+  'Task group queue force starting status': 'Starting status',
+  'Task group in queue': 'In queue',
+  'Task group queue status': 'Task status',
+  'View task group queue': 'View task group queue',
+  'Task group queue the status of waiting': 'Waiting into the queue',
+  'Task group queue the status of queuing': 'Queuing',
+  'Task group queue the status of releasing': 'Released',
+  'Modify task group queue priority': 'Edit the priority of the task group queue',
+  'Priority not empty': 'The value of priority can not be empty',
+  'Priority must be number': 'The value of priority should be number'
 }
diff --git a/dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js b/dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js
index 5171a00..e6f6349 100644
--- a/dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js
+++ b/dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js
@@ -765,5 +765,36 @@ export default {
   scheduleEndTime: '定时结束时间',
   crontabExpression: 'Crontab',
   workflowPublishStatus: '工作流上线状态',
-  schedulePublishStatus: '定时状态'
+  schedulePublishStatus: '定时状态',
+  'Task group manage': '任务组管理',
+  'Task group option': '任务组配置',
+  'Create task group': '创建任务组',
+  'Edit task group': '编辑任务组',
+  'Delete task group': '删除任务组',
+  'Task group code': '任务组编号',
+  'Task group name': '任务组名称',
+  'Task group resource pool size': '资源容量',
+  'Task group resource used pool size': '已用资源',
+  'Task group desc': '描述信息',
+  'Task group status': '任务组状态',
+  'Task group enable status': '启用',
+  'Task group disable status': '不可用',
+  'Please enter task group desc': '请输入任务组描述',
+  'Please enter task group resource pool size': '请输入资源容量大小',
+  'Task group resource pool size be a number': '资源容量大小必须大于等于1的数值',
+  'Please select project': '请选择项目',
+  'Task group queue': '任务组队列',
+  'Task group queue priority': '组内优先级',
+  'Task group queue priority be a number': '优先级必须是大于等于0的数值',
+  'Task group queue force starting status': '是否强制启动',
+  'Task group in queue': '是否排队中',
+  'Task group queue status': '任务状态',
+  'View task group queue': '查看任务组队列',
+  'Task group queue the status of waiting': '等待入队',
+  'Task group queue the status of queuing': '排队中',
+  'Task group queue the status of releasing': '已释放',
+  'Modify task group queue priority': '修改优先级',
+  'Force to start task': '强制启动',
+  'Priority not empty': '优先级不能为空',
+  'Priority must be number': '优先级必须是数值'
 }