Browse Source

[Feature-7018][UI] Add list edit view from task definition (#7852)

* Performance optimization of DEPENDENT task

* fix eslint

* [Feature] Improve task definition list

* feat: Task definition list

* change update interface
3.0.0/version-upgrade
wangyizhi 3 years ago committed by GitHub
parent
commit
984665a5ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 118
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue
  2. 59
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/pre_tasks.vue
  3. 200
      dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/taskDefinition/_source/list.vue
  4. 91
      dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/taskDefinition/_source/taskDeleteModal.vue
  5. 107
      dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/taskDefinition/_source/taskMoveModel.vue
  6. 253
      dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/taskDefinition/_source/versions.vue
  7. 313
      dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/taskDefinition/index.vue
  8. 138
      dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js
  9. 39
      dolphinscheduler-ui/src/js/module/components/conditions/conditions.vue
  10. 13
      dolphinscheduler-ui/src/js/module/components/secondaryMenu/_source/menu.js
  11. 11
      dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js
  12. 11
      dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js

118
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue

@ -17,11 +17,17 @@
<template>
<div class="form-model-wrapper" v-clickoutside="_handleClose">
<div class="title-box">
<span class="name">{{ $t("Current node settings") }}
<a v-if="helpUrlEnable(nodeData.taskType)" class="helper-link" target="_blank"
:href="helpUrl(nodeData.taskType)">
<span class="name"
>{{ $t("Current node settings") }}
<a
v-if="helpUrlEnable(nodeData.taskType)"
class="helper-link"
target="_blank"
:href="helpUrl(nodeData.taskType)"
>
<i class="el-icon-question" />
{{nodeData.taskType}} {{ $t('Instructions') }}</a>
{{ nodeData.taskType }} {{ $t("Instructions") }}</a
>
</span>
<span class="go-subtask">
<!-- Component can't pop up box to do component processing -->
@ -94,6 +100,26 @@
</div>
</m-list-box>
<m-list-box v-if="fromTaskDefinition">
<div slot="text">{{ $t("Process Name") }}</div>
<div slot="content">
<el-select
@change="changeProcessCode"
:value="processCode"
size="small"
style="width: 100%"
:disabled="isDetails || taskDefinition"
>
<el-option
v-for="process in processListS"
:key="process.code"
:label="process.name"
:value="process.code"
/>
</el-select>
</div>
</m-list-box>
<!-- Running sign -->
<m-list-box>
<div slot="text">{{ $t("Run flag") }}</div>
@ -164,13 +190,14 @@
</span>
<span class="text-b">{{ $t("Task group queue priority") }}</span>
<el-input
:disabled="taskGroupId===''"
style="width: 166px;"
:disabled="taskGroupId === ''"
style="width: 166px"
type="input"
v-model="taskGroupPriority"
maxlength="60"
v-on:input="_onUpdateTaskGroupPriority"
size="small">
size="small"
>
</el-input>
</div>
</m-list-box>
@ -448,11 +475,7 @@
</m-waterdrop>
</div>
<!-- Pre-tasks in workflow -->
<m-pre-tasks
ref="preTasks"
v-if="!fromTaskDefinition"
:code="code"
/>
<m-pre-tasks ref="preTasks" :code="code" :fromTaskDefinition="fromTaskDefinition" :prevTasks="prevTasks" :processDefinition="processDefinition"/>
</div>
</div>
<div class="bottom-box">
@ -583,7 +606,10 @@
backfillRefresh: true,
// whether this is a new Task
isNewCreate: true,
tasksTypeList: Object.keys(tasksType)
tasksTypeList: Object.keys(tasksType),
// processCode
processCode: undefined,
processDefinition: null
}
},
provide () {
@ -609,7 +635,7 @@
},
inject: ['dagChart'],
methods: {
...mapActions('dag', ['getTaskInstanceList']),
...mapActions('dag', ['getTaskInstanceList', 'getProcessDefinition']),
helpUrlEnable (typekey) {
const type = tasksType[typekey]
if (!type) return false
@ -850,10 +876,16 @@
if (!this.$refs[this.nodeData.taskType]._verification()) {
return
}
// set preTask
if (this.$refs.preTasks) {
this.$refs.preTasks.setPreNodes()
// set dag preTask
if (this.dagChart && this.$refs.preTasks) {
this.$refs.preTasks.setDagPreNodes()
}
// set edge label
if (this.dagChart) {
this._setEdgeLabel()
}
this.successBranch && (this.conditionResult.successNode[0] = this.successBranch)
this.failedBranch && (this.conditionResult.failedNode[0] = this.failedBranch)
this.$emit('addTaskInfo', {
@ -882,11 +914,12 @@
taskGroupId: this.taskGroupId,
taskGroupPriority: this.taskGroupPriority
},
fromThis: this
fromThis: this,
...(this.fromTaskDefinition ? {
prevTasks: this.$refs.preTasks ? this.$refs.preTasks.preTasks : [],
processCode: this.processCode
} : {})
})
// set edge label
this._setEdgeLabel()
},
/**
* Sub-workflow selected node echo name
@ -1005,6 +1038,44 @@
},
changeTaskType (value) {
this.$emit('changeTaskType', value)
},
calculateRelatedTasks () {
if (this.processDefinition && this.taskDefinition) {
const relations = this.processDefinition.processTaskRelationList || []
const tasks = this.processDefinition.taskDefinitionList || []
const tasksMap = {}
tasks.forEach(task => {
tasksMap[task.code] = task
})
const taskCode = this.taskDefinition.code
const buildTask = (task) => ({
code: task.code,
name: task.name,
type: task.taskType
})
// Downstream tasks
const postTasks = relations
.filter(relation => relation.preTaskCode === taskCode)
.map(relation => buildTask(tasksMap[relation.postTaskCode]))
// Upstream tasks
const prevTasks = relations
.filter(relation => relation.postTaskCode === taskCode && relation.preTaskCode !== 0)
.map(relation => buildTask(tasksMap[relation.preTaskCode]))
this.postTasks = postTasks
this.prevTasks = prevTasks
}
},
getProcessDetails () {
this.getProcessDefinition(this.processCode).then(res => {
this.processDefinition = res
this.calculateRelatedTasks()
})
},
changeProcessCode (code) {
this.processCode = code
this.getProcessDetails()
}
},
created () {
@ -1046,6 +1117,11 @@
this.postTasks = postNodes.map(buildTask)
this.prevTasks = prevNodes.map(buildTask)
}
if (this.fromTaskDefinition && this.taskDefinition) {
this.processCode = this.taskDefinition.processCode
this.getProcessDetails()
}
},
mounted () {
let self = this

59
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/pre_tasks.vue

@ -53,7 +53,11 @@
code: {
type: Number,
default: 0
}
},
processDefinition: {
type: Object
},
prevTasks: Array
},
data () {
return {
@ -61,20 +65,45 @@
preTasks: []
}
},
mounted () {
const canvas = this.getDagCanvasRef()
const edges = canvas.getEdges()
this.preTasks = canvas.getPrevNodes(this.code).map(node => node.id)
this.options = this.tasks.filter((task) => {
// The current node cannot be used as the prev node
if (task.code === this.code) return false
if (this.preTasks.includes(task.code)) return true
// The number of edges start with CONDITIONS task cannot be greater than 2
if (task.taskType === 'CONDITIONS') {
return edges.filter((e) => e.sourceId === task.code).length < 2
watch: {
processDefinition (def) {
if (def) {
this.preTasks = []
const relations = def.processTaskRelationList
this.options = def.taskDefinitionList.filter((task) => {
// The current node cannot be used as the prev node
if (task.code === this.code) return false
// The number of edges start with CONDITIONS task cannot be greater than 2
if (task.taskType === 'CONDITIONS') {
return relations.filter((e) => e.preTaskCode === task.code).length < 2
}
return true
})
}
},
prevTasks (prevTasks) {
if (prevTasks) {
this.preTasks = prevTasks.map(task => task.code)
}
return true
})
}
},
mounted () {
// Called by dag
if (this.dagChart) {
const canvas = this.getDagCanvasRef()
const edges = canvas.getEdges()
this.preTasks = canvas.getPrevNodes(this.code).map(node => node.id)
this.options = this.tasks.filter((task) => {
// The current node cannot be used as the prev node
if (task.code === this.code) return false
if (this.preTasks.includes(task.code)) return true
// The number of edges start with CONDITIONS task cannot be greater than 2
if (task.taskType === 'CONDITIONS') {
return edges.filter((e) => e.sourceId === task.code).length < 2
}
return true
})
}
},
computed: {
...mapState('dag', ['tasks'])
@ -89,7 +118,7 @@
return canvas
}
},
setPreNodes () {
setDagPreNodes () {
const canvas = this.getDagCanvasRef()
canvas.setPreNodes(this.code, this.preTasks, true)
}

200
dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/taskDefinition/_source/list.vue

@ -23,61 +23,106 @@
style="width: 100%"
@selection-change="select"
>
<el-table-column
prop="id"
:label="$t('#')"
min-width="50"
></el-table-column>
<el-table-column :label="$t('Task Name')" min-width="200">
<template v-slot="scope">
<el-popover trigger="hover" placement="top">
<p>{{ scope.row.name }}</p>
<div>{{ scope.row.taskName }}</div>
<div slot="reference" class="name-wrapper">
<a
href="javascript:"
class="links"
@click="viewTaskDetail(scope.row)"
>
{{ scope.row.name }}
<span class="ellipsis name">{{ scope.row.taskName }}</span>
</a>
</div>
</el-popover>
</template>
</el-table-column>
<el-table-column :label="$t('Task Type')" prop="taskType" min-width="135">
<el-table-column
:label="$t('Process Name')"
prop="processDefinitionName"
min-width="135"
>
</el-table-column>
<el-table-column
:label="$t('Process State')"
prop="processReleaseState"
min-width="135"
>
</el-table-column>
<el-table-column
:label="$t('User Name')"
prop="userName"
width="135"
></el-table-column>
:label="$t('Task Type')"
prop="taskType"
min-width="135"
>
</el-table-column>
<el-table-column :label="$t('Version Info')" min-width="135">
<template v-slot="scope">
<span>
{{ 'V' + scope.row.version }}
{{ "V" + scope.row.taskVersion }}
</span>
</template>
</el-table-column>
<el-table-column :label="$t('Upstream Tasks')" min-width="300">
<template v-slot="scope">
<div class="upstream-tasks">
<el-popover
trigger="hover"
placement="top"
v-for="task in scope.row.upstreamTasks.slice(0, 3)"
:key="task.taskCode"
>
<div>{{ task.taskName }}</div>
<el-tag class="pre-task-tag" size="mini" slot="reference">
{{ task.taskName }}
</el-tag>
</el-popover>
<!-- more popover -->
<el-popover
v-if="scope.row.upstreamTasks.length > 3"
trigger="hover"
:title="$t('Upstream Tasks')"
placement="top"
>
<div class="task-definition-upstreams-popover">
<el-tag
size="mini"
slot="reference"
class="popover-tag"
v-for="task in scope.row.upstreamTasks"
:key="task.taskCode"
>
{{ task.taskName }}
</el-tag>
</div>
<el-tag class="pre-task-tag" size="mini" slot="reference">
{{
$t("and {n} more", {
n: scope.row.upstreamTasks.length - 3,
})
}}
</el-tag>
</el-popover>
<span v-if="scope.row.upstreamTasks.length === 0">-</span>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('Create Time')" min-width="135">
<template v-slot="scope">
<span>
{{ scope.row.createTime | formatDate }}
{{ scope.row.taskCreateTime | formatDate }}
</span>
</template>
</el-table-column>
<el-table-column :label="$t('Update Time')" min-width="135">
<template v-slot="scope">
<span>
{{ scope.row.updateTime | formateDate }}
{{ scope.row.taskUpdateTime | formateDate }}
</span>
</template>
</el-table-column>
<el-table-column :label="$t('Description')" min-width="100">
<template v-slot="scope">
<span>{{ scope.row.description | filterNull }} </span>
</template>
</el-table-column>
<el-table-column :label="$t('Operation')" width="100" fixed="right">
<el-table-column :label="$t('Operation')" width="150" fixed="right">
<template v-slot="scope">
<el-tooltip
:content="$t('Edit')"
@ -90,33 +135,68 @@
size="mini"
icon="el-icon-edit-outline"
circle
:disabled="scope.row.taskType === 'CONDITIONS' || scope.row.taskType ==='SWITCH' "
:disabled="
['CONDITIONS', 'SWITCH'].includes(scope.row.taskType) ||
(scope.row.processDefinitionCode &&
scope.row.processReleaseState === 'ONLINE')
"
@click="editTask(scope.row)"
></el-button>
</span>
</el-tooltip>
<el-tooltip
:content="$t('Move task')"
placement="top"
:enterable="false"
>
<span>
<el-button
type="primary"
size="mini"
icon="el-icon-rank"
circle
:disabled="
scope.row.processDefinitionCode &&
scope.row.processReleaseState === 'ONLINE'
"
@click="showMoveModal(scope.row)"
></el-button>
</span>
</el-tooltip>
<el-tooltip
:content="$t('Delete')"
placement="top"
:enterable="false"
>
<el-popconfirm
:confirmButtonText="$t('Confirm')"
:cancelButtonText="$t('Cancel')"
icon="el-icon-info"
iconColor="red"
:title="$t('Delete?')"
@onConfirm="deleteTask(scope.row.code, scope.row.projectCode)"
>
<span>
<el-button
type="danger"
size="mini"
icon="el-icon-delete"
slot="reference"
circle
>
</el-button>
</el-popconfirm>
:disabled="
scope.row.processDefinitionCode &&
scope.row.processReleaseState === 'ONLINE'
"
@click="showDeleteModal(scope.row)"
></el-button>
</span>
</el-tooltip>
<el-tooltip
:content="$t('Version Info')"
placement="top"
:enterable="false"
>
<span
><el-button
type="primary"
size="mini"
icon="el-icon-info"
@click="viewTaskVersions(scope.row)"
circle
></el-button
></span>
</el-tooltip>
</template>
</el-table-column>
@ -127,7 +207,6 @@
<script>
import _ from 'lodash'
import { mapActions } from 'vuex'
export default {
name: 'task-list',
@ -151,47 +230,48 @@
deep: true
}
},
created () {
// this.list = this.tasksList
},
created () {},
methods: {
...mapActions('dag', ['deleteTaskDefinition']),
/**
* onUpdate
* Delete task
*/
_onUpdate () {
this.$emit('on-update')
showDeleteModal (taskRow) {
this.$emit('showDeleteModal', taskRow)
},
/**
* deleteTaskDefinition
* View task detail
*/
deleteTask (code) {
this.deleteTaskDefinition({
code: code
})
.then((res) => {
this._onUpdate()
this.$message.success(res.msg)
})
.catch((e) => {
this.$message.error(e.msg || '')
})
viewTaskDetail (taskRow) {
this.$emit('viewTaskDetail', taskRow)
},
/**
* taskdefinition detail
* Edit task
*/
viewTaskDetail (task) {
this.$emit('viewTaskDetail', task)
editTask (taskRow) {
this.$emit('editTask', taskRow)
},
/**
* task edit
* View task versions
*/
editTask (task) {
this.$emit('editTask', task)
viewTaskVersions (taskRow) {
this.$emit('viewTaskVersions', taskRow)
},
/**
* Move Task
*/
showMoveModal (taskRow) {
this.$emit('showMoveModal', taskRow)
}
}
}
</script>
<style>
<style lang="scss">
.task-definition-upstreams-popover {
max-width: 500px;
.popover-tag {
margin-right: 10px;
margin-bottom: 10px;
}
}
</style>

91
dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/taskDefinition/_source/taskDeleteModal.vue

@ -0,0 +1,91 @@
/*
* 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>
<el-dialog :title="$t('Delete')" :visible.sync="visible" width="500px">
<div class="content" v-if="taskRow">
<template v-if="taskRow.processDefinitionCode">
<span>{{
$t("Delete task {taskName} from process {processName}?", {
processName: taskRow.processDefinitionName,
taskName: taskRow.taskName,
})
}}</span>
<el-checkbox class="remove-checkbox" v-model="removeCompletely">{{
$t("Delete task completely")
}}</el-checkbox>
</template>
<template v-else>
<span>{{
$t("Delete {taskName}?", {
taskName: taskRow.taskName,
})
}}</span>
</template>
</div>
<span slot="footer" class="dialog-footer">
<el-button size="small" @click="close">{{ $t("Cancel") }}</el-button>
<el-button size="small" type="primary" @click="submit">{{
$t("Confirm")
}}</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
props: {
taskRow: Object
},
data () {
return {
visible: false,
// Whether to delete the task completely
removeCompletely: false
}
},
methods: {
show () {
this.visible = true
},
close () {
this.visible = false
},
submit () {
this.$emit('deleteTask', {
completely: this.taskRow.processDefinitionCode ? this.removeCompletely : true,
taskCode: this.taskRow.taskCode,
processDefinitionCode: this.taskRow.processDefinitionCode
})
}
}
}
</script>
<style lang="scss" scoped>
.content {
margin: 20px;
color: #333;
display: flex;
flex-direction: column;
.remove-checkbox {
margin-top: 20px;
}
}
</style>

107
dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/taskDefinition/_source/taskMoveModel.vue

@ -0,0 +1,107 @@
/*
* 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>
<el-dialog :title="$t('Move task')" :visible.sync="visible" width="500px">
<div class="content" v-if="taskRow">
<el-form ref="form" :model="form" label-width="100px" size="mini">
<el-form-item :label="$t('Process Name')">
<el-select v-model="form.processCode">
<el-option
:label="process.name"
:value="process.code"
v-for="process in processListS"
:key="process.code"
>{{ process.name }}</el-option
>
</el-select>
</el-form-item>
</el-form>
</div>
<span slot="footer" class="dialog-footer">
<el-button size="small" @click="close">{{ $t("Cancel") }}</el-button>
<el-button size="small" type="primary" @click="submit">{{
$t("Confirm")
}}</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
props: {
taskRow: Object
},
data () {
return {
visible: false,
form: {
processCode: -1
}
}
},
computed: {
...mapState('dag', ['processListS'])
},
methods: {
show () {
this.visible = true
},
close () {
this.visible = false
},
submit () {
if (this.taskRow.processDefinitionCode === this.form.processCode) {
this.visible = false
return
}
if (!this.form.processCode) {
this.$message.error(this.$t('Please select a process (required)'))
return
}
this.$emit('moveTask', {
taskCode: this.taskRow.taskCode,
processDefinitionCode: this.taskRow.processDefinitionCode,
targetProcessDefinitionCode: this.form.processCode
})
}
},
watch: {
taskRow (val) {
if (val) {
this.form.processCode = val.processDefinitionCode || ''
}
}
}
}
</script>
<style lang="scss" scoped>
.content {
margin: 20px;
color: #333;
display: flex;
flex-direction: column;
.remove-checkbox {
margin-top: 20px;
}
}
</style>

253
dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/taskDefinition/_source/versions.vue

@ -0,0 +1,253 @@
/*
* 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-drawer
:title="$t('Version Info')"
:visible.sync="visible"
:with-header="false"
size=""
>
<!-- fix the bug that Element-ui(2.13.2) auto focus on the first input -->
<div style="width: 0px; height: 0px; overflow: hidden">
<el-input type="text" />
</div>
<div class="container">
<div class="versions-header">
<span class="name">{{ $t("Version Info") }}</span>
</div>
<div class="table-box" v-if="taskVersions.length > 0">
<el-table :data="taskVersions" size="mini" style="width: 100%">
<el-table-column
type="index"
:label="$t('#')"
width="50"
></el-table-column>
<el-table-column prop="userName" :label="$t('Version')">
<template slot-scope="scope">
<span v-if="scope.row.version">
<span
v-if="scope.row.version === taskRow.taskVersion"
style="color: green"
><strong
>V{{ scope.row.version }}
{{ $t("Current Version") }}</strong
></span
>
<span v-else>V{{ scope.row.version }}</span>
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column
prop="description"
:label="$t('Description')"
></el-table-column>
<el-table-column :label="$t('Create Time')" min-width="120">
<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('Switch To This Version')"
placement="top"
>
<el-popconfirm
:confirmButtonText="$t('Confirm')"
:cancelButtonText="$t('Cancel')"
icon="el-icon-info"
iconColor="red"
:title="$t('Confirm Switch To This Version?')"
@onConfirm="swtichVersion(scope.row)"
>
<el-button
:disabled="
taskRow.processReleaseState === 'ONLINE' ||
scope.row.version === taskRow.taskVersion
"
type="primary"
size="mini"
icon="el-icon-warning"
circle
slot="reference"
></el-button>
</el-popconfirm>
</el-tooltip>
<el-tooltip :content="$t('Delete')" placement="top">
<el-popconfirm
:confirmButtonText="$t('Confirm')"
:cancelButtonText="$t('Cancel')"
icon="el-icon-info"
iconColor="red"
:title="$t('Delete?')"
@onConfirm="deleteVersion(scope.row)"
>
<el-button
:disabled="scope.row.version === taskRow.taskVersion"
type="danger"
size="mini"
icon="el-icon-delete"
circle
slot="reference"
></el-button>
</el-popconfirm>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</div>
<div v-if="taskVersions.length === 0">
<m-no-data />
</div>
<div v-if="taskVersions.length > 0">
<div class="versions-footer">
<el-button size="mini" @click="close()">{{ $t("Cancel") }}</el-button>
<el-pagination
background
@current-change="changePageNo"
layout="prev, pager, next"
:total="total"
:page-size="pageSize"
>
</el-pagination>
</div>
</div>
</div>
</el-drawer>
</template>
<script>
import mNoData from '@/module/components/noData/noData'
import { mapActions } from 'vuex'
export default {
name: 'task-definition-versions',
data () {
return {
visible: false,
taskVersions: [],
pageNo: 1,
pageSize: 10,
total: 0
}
},
props: {
taskRow: Object
},
methods: {
...mapActions('dag', [
'getTaskVersions',
'switchTaskVersion',
'deleteTaskVersion'
]),
show () {
this.visible = true
},
close () {
this.visible = false
this.taskVersions = []
},
changePageNo (val) {
this.pageNo = val
this.reload()
},
reload () {
this.getTaskVersions({
taskCode: this.taskRow.taskCode,
pageNo: this.pageNo,
pageSize: this.pageSize
}).then((res) => {
this.taskVersions = res.totalList
this.total = res.total
})
},
swtichVersion (row) {
this.switchTaskVersion({ taskCode: row.code, version: row.version })
.then((res) => {
this.$message.success(res.msg)
this.$emit('reloadList')
this.close()
})
.catch((err) => {
this.$message.error(err.msg || '')
})
},
deleteVersion (row) {
this.deleteTaskVersion({ taskCode: row.code, version: row.version })
.then((res) => {
this.$message.success(res.msg)
this.$emit('reloadList')
this.close()
})
.catch((err) => {
this.$message.error(err.msg || '')
})
}
},
components: { mNoData },
watch: {
visible (bool, a, b) {
if (bool && this.taskRow) {
this.reload()
}
}
}
}
</script>
<style lang="scss" rel="stylesheet/scss">
.container {
width: 500px;
position: relative;
.versions-header {
height: 60px;
position: relative;
line-height: 60px;
.name {
font-size: 16px;
}
}
.versions-footer {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
border-top: 1px solid #dcdedc;
background: #fff;
display: flex;
padding: 20px;
align-items: center;
justify-content: flex-end;
.ans-page {
display: inline-block;
}
}
.table-box {
overflow-y: scroll;
height: calc(100vh - 61px);
padding-bottom: 60px;
}
}
</style>

313
dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/taskDefinition/index.vue

@ -18,12 +18,61 @@
<div class="task-definition" v-if="!isLoading">
<m-list-construction :title="$t('Task Definition')">
<template slot="conditions">
<m-conditions @on-conditions="_onConditions" :taskTypeShow="true">
<m-conditions>
<template v-slot:button-group>
<el-button size="mini" @click="createTask">
{{ $t("Create task") }}
</el-button>
</template>
<template v-slot:search-group>
<div class="list">
<el-button
size="mini"
@click="_ckQuery"
icon="el-icon-search"
></el-button>
</div>
<div class="list">
<el-select
size="mini"
style="width: 140px"
:placeholder="$t('type')"
:value="tempParams.taskType"
@change="_onChangeTaskType"
clearable
>
<el-option
v-for="taskType in tasksTypeList"
:key="taskType"
:value="taskType"
:label="taskType"
>
</el-option>
</el-select>
</div>
<div class="list">
<el-input
v-model="tempParams.processName"
@keyup.enter.native="_ckQuery"
size="mini"
:placeholder="$t('Process Name')"
type="text"
style="width: 180px"
clearable
/>
</div>
<div class="list">
<el-input
v-model="tempParams.taskName"
@keyup.enter.native="_ckQuery"
size="mini"
:placeholder="$t('Task Name')"
type="text"
style="width: 180px"
clearable
/>
</div>
</template>
</m-conditions>
</template>
<template v-slot:content>
@ -32,7 +81,10 @@
:tasksList="tasksList"
@on-update="_onUpdate"
@editTask="editTask"
@showDeleteModal="showDeleteModal"
@showMoveModal="showMoveModal"
@viewTaskDetail="viewTaskDetail"
@viewTaskVersions="viewTaskVersions"
></m-list>
<div class="page-box">
<el-pagination
@ -75,6 +127,21 @@
>
</m-form-model>
</el-drawer>
<task-delete-modal
ref="taskDeleteModal"
:taskRow="deletingTaskRow"
@deleteTask="deleteTask"
/>
<task-move-modal
ref="taskMoveModal"
:taskRow="movingTaskRow"
@moveTask="moveTask"
/>
<version-drawer
ref="versionDrawer"
:taskRow="versionTaskRow"
@reloadList="_onUpdate"
/>
</div>
</template>
<script>
@ -86,10 +153,11 @@
import { mapActions, mapMutations } from 'vuex'
import listUrlParamHandle from '@/module/mixin/listUrlParamHandle'
import mFormModel from '@/conf/home/pages/dag/_source/formModel/formModel.vue'
/**
* tasksType
*/
import { tasksType } from '@/conf/home/pages/dag/_source/config.js'
import TaskDeleteModal from './_source/taskDeleteModal.vue'
import TaskMoveModal from './_source/taskMoveModel.vue'
import VersionDrawer from './_source/versions.vue'
import _ from 'lodash'
const DEFAULT_NODE_DATA = {
id: -1,
@ -108,9 +176,14 @@
searchParams: {
pageSize: 10,
pageNo: 1,
searchVal: '',
taskType: '',
userId: ''
processName: '',
taskName: '',
taskType: ''
},
tempParams: {
processName: '',
taskName: '',
taskType: ''
},
// whether the task config drawer is visible
taskDrawer: false,
@ -119,7 +192,14 @@
// tasksType
tasksTypeList,
// editing task definition
editingTask: null
editingTask: null,
editingProcess: null,
// task to be deleted
deletingTaskRow: null,
// task ready to move
movingTaskRow: null,
// the current browse task
versionTaskRow: null
}
},
mixins: [listUrlParamHandle],
@ -127,8 +207,12 @@
...mapActions('dag', [
'getTaskDefinitionsList',
'genTaskCodeList',
'saveTaskDefinition',
'updateTaskDefinition'
'saveTaskDefinitionWithUpstreams',
'updateTaskDefinition',
'deleteTaskDefinition',
'getTaskDefinition',
'moveTaskToProcess',
'deleteRelation'
]),
...mapActions('dag', [
'getProcessList',
@ -142,7 +226,7 @@
'getAlarmGroupsAll'
]),
/**
* Toggle task drawer
* Toggle task form-model drawer
*/
showTaskDrawer () {
this.taskDrawer = true
@ -151,10 +235,16 @@
this.setIsDetails(false)
this.taskDrawer = false
},
saveTask ({ item }) {
/**
* Save task
*/
saveTask ({ item: taskDefinition, prevTasks, processCode }) {
const isEditing = !!this.editingTask
if (isEditing) {
this.updateTaskDefinition(item)
this.updateTaskDefinition({
prevTasks: prevTasks,
taskDefinition: taskDefinition
})
.then((res) => {
this.$message.success(res.msg)
this._onUpdate()
@ -172,13 +262,13 @@
return code
})
.then((code) => {
return this.saveTaskDefinition({
taskDefinitionJson: [
{
...item,
code
}
]
return this.saveTaskDefinitionWithUpstreams({
taskDefinition: {
...taskDefinition,
code
},
prevTasks: prevTasks,
processDefinitionCode: processCode
})
})
.then((res) => {
@ -191,20 +281,101 @@
})
}
},
/**
* Show task creation modal
*/
createTask () {
this.editingTask = null
this.nodeData.taskType = DEFAULT_NODE_DATA.taskType
this.showTaskDrawer()
},
editTask (task) {
this.editingTask = task
this.nodeData.id = task.code
this.nodeData.taskType = task.taskType
this.showTaskDrawer()
/**
* Show task edit modal
*/
editTask (taskRow) {
this.getTaskDefinition(taskRow.taskCode).then((taskDefinition) => {
this.editingTask = {
...taskDefinition,
processCode: taskRow.processDefinitionCode
}
this.nodeData.id = taskDefinition.code
this.nodeData.taskType = taskDefinition.taskType
this.showTaskDrawer()
})
},
viewTaskDetail (task) {
/**
* Show task detail modal
*/
viewTaskDetail (taskRow) {
this.setIsDetails(true)
this.editTask(task)
this.editTask(taskRow)
},
/**
* Show delete task modal
*/
showDeleteModal (taskRow) {
this.deletingTaskRow = taskRow
if (this.$refs.taskDeleteModal) {
this.$refs.taskDeleteModal.show()
}
},
/**
* Show Move Modal
*/
showMoveModal (taskRow) {
this.movingTaskRow = taskRow
if (this.$refs.taskMoveModal) {
this.$refs.taskMoveModal.show()
}
},
/**
* Delete task
* @param {Boolean} completely Whether to delete the task completely
*/
deleteTask ({ completely, taskCode, processDefinitionCode }) {
const completelyDelete = this.deleteTaskDefinition
const deleteRelation = this.deleteRelation
const delRequest = completely ? completelyDelete : deleteRelation
const params = completely
? { taskCode }
: {
taskCode,
processDefinitionCode
}
delRequest(params)
.then((res) => {
this.$message.success(res.msg)
this.$refs.taskDeleteModal.close()
this.deletingTaskRow = null
this._onUpdate()
})
.catch((err) => {
this.$message.error(err.msg || '')
})
},
/**
* Move task to another workflow
*/
moveTask (params) {
this.moveTaskToProcess(params)
.then((res) => {
this.$message.success(res.msg)
this.$refs.taskMoveModal.close()
this.movingTaskRow = null
this._onUpdate()
})
.catch((err) => {
this.$message.error(err.msg || '')
})
},
/**
* ViewTaskVersion
*/
viewTaskVersions (taskRow) {
if (this.$refs.versionDrawer) {
this.versionTaskRow = taskRow
this.$refs.versionDrawer.show()
}
},
/**
* pageNo
@ -216,25 +387,52 @@
this.searchParams.pageSize = val
},
/**
* conditions
* query tasks
*/
_onConditions (o) {
this.searchParams.searchVal = o.searchVal
this.searchParams.taskType = o.taskType
_ckQuery (o) {
this.searchParams.processName = this.tempParams.processName
this.searchParams.taskType = this.tempParams.taskType
this.searchParams.taskName = this.tempParams.taskName
this.searchParams.pageNo = 1
},
/**
* filter tasks by taskType
*/
_onChangeTaskType (val) {
this.tempParams.taskType = val
},
/**
* get task definition list
*/
_getList (flag) {
this.isLoading = !flag
this.getTaskDefinitionsList(this.searchParams)
this.getTaskDefinitionsList({
pageNo: this.searchParams.pageNo,
pageSize: this.searchParams.pageSize,
taskType: this.searchParams.taskType,
searchTaskName: this.searchParams.taskName,
searchWorkflowName: this.searchParams.processName
})
.then((res) => {
if (this.searchParams.pageNo > 1 && res.totalList.length === 0) {
this.searchParams.pageNo = this.searchParams.pageNo - 1
} else {
this.tasksList = []
this.tasksList = res.totalList
this.tasksList = res.totalList.map((task) => {
const upstreamTaskMap = task.upstreamTaskMap || {}
const upstreamTasks = Object.keys(upstreamTaskMap).map((code) => {
return {
taskCode: code,
taskName: upstreamTaskMap[code]
}
})
return {
...task,
upstreamTasks,
upstreamTaskNames: upstreamTasks
.map((u) => u.taskName)
.join(',')
}
})
this.total = res.total
this.isLoading = false
}
@ -280,15 +478,23 @@
.catch(() => {
this.isLoading = false
})
// Routing parameter merging
if (!_.isEmpty(this.$route.query)) {
this.tempParams.processName = this.$route.query.processName || ''
this.tempParams.taskType = this.$route.query.taskType || ''
this.tempParams.taskName = this.$route.query.taskName || ''
}
},
mounted () {},
components: {
mListConstruction,
mConditions,
mList,
mNoData,
mSpin,
mFormModel
mFormModel,
TaskMoveModal,
TaskDeleteModal,
VersionDrawer
}
}
</script>
@ -297,5 +503,40 @@
.taskGroupBtn {
width: 300px;
}
::v-deep .table-box {
table {
tr {
th:first-child,
td:first-child {
text-align: left;
padding-left: 20px;
}
td:first-child span {
text-align: left;
}
}
td,
th.is-leaf {
padding-left: 10px;
}
}
.pre-task-tag {
margin-right: 10px;
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
}
.upstream-tasks {
display: flex;
flex-wrap: wrap;
}
}
::v-deep .el-dialog__header {
.el-dialog__headerbtn {
right: 20px;
}
}
}
</style>

138
dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js

@ -24,7 +24,7 @@ const convertLocations = (locationStr) => {
if (!locationStr) return locations
try {
locations = JSON.parse(locationStr)
} catch (error) {}
} catch (error) { }
return Array.isArray(locations) ? locations : null
}
@ -870,7 +870,7 @@ export default {
*/
deleteTaskDefinition ({ state }, payload) {
return new Promise((resolve, reject) => {
io.delete(`projects/${state.projectCode}/task-definition/${payload.code}`, payload, res => {
io.delete(`projects/${state.projectCode}/task-definition/${payload.taskCode}`, res => {
resolve(res)
}).catch(e => {
reject(e)
@ -891,15 +891,143 @@ export default {
})
})
},
updateTaskDefinition ({ state }, taskDefinition) {
/**
* Save Task Definition with upstreams
* @param {Object} taskDefinition
* @param {number[]} prevTasks
* @param {number} processDefinitionCode
*/
saveTaskDefinitionWithUpstreams ({ state }, payload) {
return new Promise((resolve, reject) => {
io.post(`projects/${state.projectCode}/task-definition/save-single`, {
taskDefinitionJsonObj: JSON.stringify(payload.taskDefinition),
upstreamCodes: payload.prevTasks.join(','),
processDefinitionCode: payload.processDefinitionCode
}, res => {
resolve(res)
}).catch(e => {
reject(e)
})
})
},
/**
*
* @param {Object} taskDefinition
* @param {number[]} taskDefinition
* @returns
*/
updateTaskDefinition ({ state }, payload) {
return new Promise((resolve, reject) => {
io.put(`projects/${state.projectCode}/task-definition/${payload.taskDefinition.code}/with-upstream`, {
taskDefinitionJsonObj: JSON.stringify(payload.taskDefinition),
upstreamCodes: payload.prevTasks.join(',')
}, res => {
resolve(res)
}).catch(e => {
reject(e)
})
})
},
/**
* Query taskDefinition by code
* @param {*} param0
*/
getTaskDefinition ({ state }, taskDefinitionCode) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectCode}/task-definition/${taskDefinitionCode}`, res => {
resolve(res.data)
}).catch(e => {
reject(e)
})
})
},
/**
* Get process definition detail
* @param {numbetr} code
*/
getProcessDefinition ({ state }, code) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectCode}/process-definition/${code}`, res => {
resolve(res.data)
}).catch(res => {
reject(res)
})
})
},
/**
* Move task
*/
moveTaskToProcess ({ state }, payload) {
return new Promise((resolve, reject) => {
io.post(`projects/${state.projectCode}/process-task-relation/move`, {
processDefinitionCode: payload.processDefinitionCode,
targetProcessDefinitionCode: payload.targetProcessDefinitionCode,
taskCode: payload.taskCode
}, res => {
resolve(res)
}).catch(e => {
reject(e)
})
})
},
/**
* Delete relation
*/
deleteRelation ({ state }, payload) {
return new Promise((resolve, reject) => {
io.put(`projects/${state.projectCode}/task-definition/${taskDefinition.code}`, {
taskDefinitionJsonObj: JSON.stringify(taskDefinition)
io.delete(`projects/${state.projectCode}/process-task-relation/${payload.taskCode}`, {
processDefinitionCode: payload.processDefinitionCode
}, res => {
resolve(res)
}).catch(e => {
reject(e)
})
})
},
/**
* Query task versions
* @param {number} taskCode
* @param {number} pageNo
* @param {number} pageSize
*/
getTaskVersions ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectCode}/task-definition/${payload.taskCode}/versions`, {
pageNo: payload.pageNo,
pageSize: payload.pageSize
}, res => {
resolve(res.data)
}).catch(res => {
reject(res)
})
})
},
/**
* Switch task version
* @param {number} taskCode
* @param {number} version
*/
switchTaskVersion ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`projects/${state.projectCode}/task-definition/${payload.taskCode}/versions/${payload.version}`, res => {
resolve(res)
}).catch(res => {
reject(res)
})
})
},
/**
* Delete task version
* @param {number} taskCode
* @param {number} version
*/
deleteTaskVersion ({ state }, payload) {
return new Promise((resolve, reject) => {
io.delete(`projects/${state.projectCode}/task-definition/${payload.taskCode}/versions/${payload.version}`, res => {
resolve(res)
}).catch(e => {
reject(e)
})
})
}
}

39
dolphinscheduler-ui/src/js/module/components/conditions/conditions.vue

@ -42,24 +42,6 @@
>
</el-input>
</div>
<div class="list" v-if="taskTypeShow">
<el-select
size="mini"
style="width: 140px"
:placeholder="$t('type')"
:value="taskType"
@change="_onChangeTaskType"
clearable
>
<el-option
v-for="(task, index) in taskTypeList"
:key="index"
:value="task.desc"
:label="index"
>
</el-option>
</el-select>
</div>
</template>
</div>
</div>
@ -67,40 +49,24 @@
</template>
<script>
import _ from 'lodash'
/**
* taskType list
*/
import { tasksType } from '@/conf/home/pages/dag/_source/config.js'
export default {
name: 'conditions',
data () {
return {
// taskType list
taskTypeList: tasksType,
// search value
searchVal: '',
// taskType switch
taskType: ''
searchVal: ''
}
},
props: {
taskTypeShow: Boolean,
operation: Array
},
methods: {
/**
* switch taskType
*/
_onChangeTaskType (val) {
this.taskType = val
},
/**
* emit Query parameter
*/
_ckQuery () {
this.$emit('on-conditions', {
searchVal: _.trim(this.searchVal),
taskType: this.taskType
searchVal: _.trim(this.searchVal)
})
}
},
@ -114,7 +80,6 @@
// Routing parameter merging
if (!_.isEmpty(this.$route.query)) {
this.searchVal = this.$route.query.searchVal || ''
this.taskType = this.$route.query.taskType || ''
}
},
components: {}

13
dolphinscheduler-ui/src/js/module/components/secondaryMenu/_source/menu.js

@ -54,6 +54,12 @@ const menu = {
enabled: true,
classNames: 'tab-process-definition'
},
{
name: `${i18n.$t('Task Definition')}`,
path: 'task-definition',
id: 5,
enabled: true
},
{
name: `${i18n.$t('Process Instance')}`,
path: 'instance',
@ -78,17 +84,10 @@ const menu = {
path: 'history-task-record',
id: 4,
enabled: config.recordSwitch
},
{
name: `${i18n.$t('Task Definition')}`,
path: 'task-definition',
id: 5,
enabled: true
}
]
}
],
security: [
{
name: `${i18n.$t('Tenant Manage')}`,

11
dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js

@ -797,5 +797,14 @@ export default {
'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',
'Please select task name': 'Please select a task name'
'Please select task name': 'Please select a task name',
'Process State': 'Process State',
'Upstream Tasks': 'Upstream Tasks',
'and {n} more': ' and {n} more',
'Move task': 'Move task',
'Delete task {taskName} from process {processName}?': 'Delete task {taskName} from process {processName}?',
'Delete task completely': 'Delete task completely',
'Please select a process': 'Please select a process',
'Delete {taskName}?': 'Delete {taskName}?',
'Please select a process (required)': 'Please select a process (required)'
}

11
dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js

@ -799,5 +799,14 @@ export default {
'Force to start task': '强制启动',
'Priority not empty': '优先级不能为空',
'Priority must be number': '优先级必须是数值',
'Please select task name': '请选择节点名称'
'Please select task name': '请选择节点名称',
'Process State': '工作流状态',
'Upstream Tasks': '上游任务',
'and {n} more': '{n}',
'Move task': '移动任务',
'Delete task {taskName} from process {processName}?': '将任务 {taskName} 从工作流 {processName} 中删除',
'Delete task completely': '彻底删除任务',
'Please select a process': '请选择工作流',
'Delete {taskName}?': '确定删除 {taskName} ?',
'Please select a process (required)': '请选择工作流必选'
}

Loading…
Cancel
Save