Browse Source

[Improvement-15603][API] When removing or modifying a workflow the system can check if there any tasks depend on it. (#15681)

* modify the function of queryTaskSubProcessDepOnProcess

* add a function to query downstream dependent tasks

* confirm to make the workflow offline

* query dependent tasks

* check dependencies

* done

* done

* done

* done

* improve the test case

* improve the test case

* improve codes

* prettier the codes

* prettier the codes
dev_wenjun_refactorMaster
calvin 9 months ago committed by GitHub
parent
commit
c61b81e0c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ProjectWorkerGroupController.java
  2. 16
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/WorkFlowLineageController.java
  3. 9
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/WorkFlowLineageService.java
  4. 1
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProcessDefinitionServiceImpl.java
  5. 21
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkFlowLineageServiceImpl.java
  6. 13
      dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/controller/WorkFlowLineageControllerTest.java
  7. 5
      dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/TaskMainInfo.java
  8. 6
      dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/WorkFlowLineageMapper.java
  9. 9
      dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/WorkFlowLineageMapper.xml
  10. 10
      dolphinscheduler-ui/src/locales/en_US/project.ts
  11. 10
      dolphinscheduler-ui/src/locales/zh_CN/project.ts
  12. 10
      dolphinscheduler-ui/src/service/modules/lineages/index.ts
  13. 5
      dolphinscheduler-ui/src/service/modules/lineages/types.ts
  14. 129
      dolphinscheduler-ui/src/views/projects/components/dependencies/dependencies-modal.tsx
  15. 122
      dolphinscheduler-ui/src/views/projects/components/dependencies/use-dependencies.ts
  16. 8
      dolphinscheduler-ui/src/views/projects/task/definition/batch-task.tsx
  17. 44
      dolphinscheduler-ui/src/views/projects/task/definition/use-table.ts
  18. 33
      dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-context-menu.tsx
  19. 24
      dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-toolbar.tsx
  20. 19
      dolphinscheduler-ui/src/views/projects/workflow/components/dag/index.tsx
  21. 12
      dolphinscheduler-ui/src/views/projects/workflow/definition/components/table-action.tsx
  22. 11
      dolphinscheduler-ui/src/views/projects/workflow/definition/index.tsx
  23. 8
      dolphinscheduler-ui/src/views/projects/workflow/definition/timing/index.tsx
  24. 88
      dolphinscheduler-ui/src/views/projects/workflow/definition/timing/use-table.ts
  25. 157
      dolphinscheduler-ui/src/views/projects/workflow/definition/use-table.ts
  26. 12
      dolphinscheduler-ui/src/views/projects/workflow/timing/index.tsx

2
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ProjectWorkerGroupController.java

@ -68,7 +68,7 @@ public class ProjectWorkerGroupController extends BaseController {
@ @RequestParam(value = "workerGroups", required = false) String workerGroups
* @return create result code
*/
@Operation(summary = "assignWorkerGroups", description = "CREATE_PROCESS_DEFINITION_NOTES")
@Operation(summary = "assignWorkerGroups", description = "ASSIGN_WORKER_GROUPS_NOTES")
@Parameters({
@Parameter(name = "projectCode", description = "PROJECT_CODE", schema = @Schema(implementation = long.class, example = "123456")),
@Parameter(name = "workerGroups", description = "WORKER_GROUP_LIST", schema = @Schema(implementation = List.class))

16
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/WorkFlowLineageController.java

@ -135,4 +135,20 @@ public class WorkFlowLineageController extends BaseController {
putMsg(result, Status.SUCCESS);
return result;
}
@Operation(summary = "queryDownstreamDependentTaskList", description = "QUERY_DOWNSTREAM_DEPENDENT_TASK_NOTES")
@Parameters({
@Parameter(name = "workFlowCode", description = "PROCESS_DEFINITION_CODE", required = true, schema = @Schema(implementation = Long.class)),
@Parameter(name = "taskCode", description = "TASK_DEFINITION_CODE", required = false, schema = @Schema(implementation = Long.class, example = "123456789")),
})
@GetMapping(value = "/query-dependent-tasks")
@ResponseStatus(HttpStatus.OK)
@ApiException(QUERY_WORKFLOW_LINEAGE_ERROR)
public Result<Map<String, Object>> queryDownstreamDependentTaskList(@Parameter(hidden = true) @RequestAttribute(value = SESSION_USER) User loginUser,
@RequestParam(value = "workFlowCode") Long workFlowCode,
@RequestParam(value = "taskCode", required = false, defaultValue = "0") Long taskCode) {
Map<String, Object> result =
workFlowLineageService.queryDownstreamDependentTasks(workFlowCode, taskCode);
return returnDataList(result);
}
}

9
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/WorkFlowLineageService.java

@ -45,6 +45,15 @@ public interface WorkFlowLineageService {
*/
Set<TaskMainInfo> queryTaskDepOnProcess(long projectCode, long processDefinitionCode);
/**
* Query downstream tasks depend on a process definition or a task
*
* @param processDefinitionCode Process definition code want to query tasks dependence
* @param taskCode Task code want to query tasks dependence
* @return downstream dependent tasks
*/
Map<String, Object> queryDownstreamDependentTasks(Long processDefinitionCode, Long taskCode);
/**
* Query and return tasks dependence with string format, is a wrapper of queryTaskDepOnTask and task query method.
*

1
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProcessDefinitionServiceImpl.java

@ -2537,6 +2537,7 @@ public class ProcessDefinitionServiceImpl extends BaseServiceImpl implements Pro
// do nothing if the workflow is already offline
return;
}
workflowDefinition.setReleaseState(ReleaseState.OFFLINE);
processDefinitionDao.updateById(workflowDefinition);

21
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkFlowLineageServiceImpl.java

@ -49,6 +49,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@ -278,11 +279,29 @@ public class WorkFlowLineageServiceImpl extends BaseServiceImpl implements WorkF
public Set<TaskMainInfo> queryTaskDepOnProcess(long projectCode, long processDefinitionCode) {
Set<TaskMainInfo> taskMainInfos = new HashSet<>();
List<TaskMainInfo> taskDependents =
workFlowLineageMapper.queryTaskDependentDepOnProcess(projectCode, processDefinitionCode);
workFlowLineageMapper.queryTaskDependentOnProcess(processDefinitionCode, 0);
List<TaskMainInfo> taskSubProcess =
workFlowLineageMapper.queryTaskSubProcessDepOnProcess(projectCode, processDefinitionCode);
taskMainInfos.addAll(taskDependents);
taskMainInfos.addAll(taskSubProcess);
return taskMainInfos;
}
/**
* Query downstream tasks depend on a process definition or a task
*
* @param processDefinitionCode Process definition code want to query tasks dependence
* @param taskCode Task code want to query tasks dependence
* @return downstream dependent tasks
*/
@Override
public Map<String, Object> queryDownstreamDependentTasks(Long processDefinitionCode, Long taskCode) {
Map<String, Object> result = new HashMap<>();
List<TaskMainInfo> taskDependents =
workFlowLineageMapper.queryTaskDependentOnProcess(processDefinitionCode,
Objects.isNull(taskCode) ? 0 : taskCode.longValue());
result.put(Constants.DATA_LIST, taskDependents);
putMsg(result, Status.SUCCESS);
return result;
}
}

13
dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/controller/WorkFlowLineageControllerTest.java

@ -86,4 +86,17 @@ public class WorkFlowLineageControllerTest {
Mockito.when(workFlowLineageService.queryWorkFlowLineageByCode(projectCode, code)).thenReturn(new HashMap<>());
assertDoesNotThrow(() -> workFlowLineageController.queryWorkFlowLineageByCode(user, projectCode, code));
}
@Test
public void testQueryDownstreamDependentTaskList() {
long code = 1L;
long taskCode = 1L;
Map<String, Object> result = new HashMap<>();
result.put(Constants.STATUS, Status.SUCCESS);
Mockito.when(workFlowLineageService.queryDownstreamDependentTasks(code, taskCode))
.thenReturn(result);
assertDoesNotThrow(
() -> workFlowLineageController.queryDownstreamDependentTaskList(user, code, taskCode));
}
}

5
dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/TaskMainInfo.java

@ -62,6 +62,11 @@ public class TaskMainInfo {
*/
private Date taskUpdateTime;
/**
* projectCode
*/
private long projectCode;
/**
* processDefinitionCode
*/

6
dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/WorkFlowLineageMapper.java

@ -124,12 +124,12 @@ public interface WorkFlowLineageMapper {
* current method `queryTaskDepOnProcess`. Which mean with the same parameter processDefinitionCode, all tasks in
* `queryTaskDepOnTask` are in the result of method `queryTaskDepOnProcess`.
*
* @param projectCode Project code want to query tasks dependence
* @param processDefinitionCode Process definition code want to query tasks dependence
* @param taskCode Task code want to query tasks dependence
* @return List of TaskMainInfo
*/
List<TaskMainInfo> queryTaskDependentDepOnProcess(@Param("projectCode") long projectCode,
@Param("processDefinitionCode") long processDefinitionCode);
List<TaskMainInfo> queryTaskDependentOnProcess(@Param("processDefinitionCode") long processDefinitionCode,
@Param("taskCode") long taskCode);
/**
* Query all tasks depend on task, only downstream task support currently(from dependent task type).

9
dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/WorkFlowLineageMapper.xml

@ -191,12 +191,13 @@
</where>
</select>
<select id="queryTaskDependentDepOnProcess" resultType="org.apache.dolphinscheduler.dao.entity.TaskMainInfo">
<select id="queryTaskDependentOnProcess" resultType="org.apache.dolphinscheduler.dao.entity.TaskMainInfo">
select td.id
, td.name as taskName
, td.code as taskCode
, td.version as taskVersion
, td.task_type as taskType
, pd.project_code as projectCode
, ptr.process_definition_code as processDefinitionCode
, pd.name as processDefinitionName
, pd.version as processDefinitionVersion
@ -205,9 +206,6 @@
join t_ds_process_task_relation ptr on ptr.post_task_code = td.code and td.version = ptr.post_task_version
join t_ds_process_definition pd on pd.code = ptr.process_definition_code and pd.version = ptr.process_definition_version
<where>
<if test="projectCode != 0">
and ptr.project_code = #{projectCode}
</if>
<!-- ptr.process_definition_code != #{processDefinitionCode} query task not in current workflow -->
<!-- For dependnet task type, using `like concat('%"definitionCode":', #{processDefinitionCode}, '%')` -->
<if test="processDefinitionCode != 0">
@ -215,6 +213,9 @@
and ptr.process_definition_code != #{processDefinitionCode}
and td.task_params like concat('%"definitionCode":', #{processDefinitionCode}, '%')
</if>
<if test="taskCode != 0">
and (td.task_params like concat('%"depTaskCode":', #{taskCode}, '%') or td.task_params like concat('%"depTaskCode":-1%'))
</if>
</where>
</select>

10
dolphinscheduler-ui/src/locales/en_US/project.ts

@ -238,6 +238,13 @@ export default {
confirm_to_offline: 'Confirm to make the workflow offline?',
time_to_online: 'Confirm to make the Scheduler online?',
time_to_offline: 'Confirm to make the Scheduler offline?',
warning_dependent_tasks_title: 'Warning',
warning_dependent_tasks_desc: 'The downstream dependent tasks exists. Are you sure to make the workflow offline?',
warning_dependencies: 'Dependencies:',
delete_validate_dependent_tasks_desc: 'The downstream dependent tasks exists. You can not delete the workflow.',
warning_offline_scheduler_dependent_tasks_desc: 'The downstream dependent tasks exists. Are you sure to make the scheduler offline?',
delete_task_validate_dependent_tasks_desc: 'The downstream dependent tasks exists. You can not delete the task.',
warning_delete_scheduler_dependent_tasks_desc: 'The downstream dependent tasks exists. Are you sure to delete the scheduler?',
},
task: {
on_line: 'Online',
@ -306,7 +313,8 @@ export default {
startup_parameter: 'Startup Parameter',
whether_dry_run: 'Whether Dry-Run',
please_choose: 'Please Choose',
remove_task_cache: 'Clear cache'
remove_task_cache: 'Clear cache',
delete_validate_dependent_tasks_desc: 'The downstream dependent tasks exists. You can not delete the task.',
},
dag: {
create: 'Create Workflow',

10
dolphinscheduler-ui/src/locales/zh_CN/project.ts

@ -236,6 +236,13 @@ export default {
confirm_to_offline: '是否确定下线该工作流?',
time_to_online: '是否确定上线该定时?',
time_to_offline: '是否确定下线该定时?',
warning_dependent_tasks_title: '警告',
warning_dependent_tasks_desc: '下游存在依赖, 下线操作可能会对下游任务产生影响. 你确定要下线该工作流嘛?',
warning_dependencies: '依赖如下:',
delete_validate_dependent_tasks_desc: '下游存在依赖,你不能删除该工作流',
warning_offline_scheduler_dependent_tasks_desc: '下游存在依赖, 下线操作可能会对下游任务产生影响. 你确定要下线该定时嘛?',
delete_task_validate_dependent_tasks_desc: '下游存在依赖,你不能删除该任务.',
warning_delete_scheduler_dependent_tasks_desc: '下游存在依赖, 删除定时可能会对下游任务产生影响. 你确定要删除该定时嘛?',
},
task: {
on_line: '线上',
@ -304,7 +311,8 @@ export default {
startup_parameter: '启动参数',
whether_dry_run: '是否空跑',
please_choose: '请选择',
remove_task_cache: '清除缓存'
remove_task_cache: '清除缓存',
delete_validate_dependent_tasks_desc: '下游存在依赖,你不能删除该任务定义',
},
dag: {
create: '创建工作流',

10
dolphinscheduler-ui/src/service/modules/lineages/index.ts

@ -16,7 +16,7 @@
*/
import { axios } from '@/service/service'
import { ProjectCodeReq, WorkflowCodeReq } from './types'
import {DependentTaskReq, ProjectCodeReq, WorkflowCodeReq} from './types'
export function queryWorkFlowList(projectCode: ProjectCodeReq): any {
return axios({
@ -41,3 +41,11 @@ export function queryLineageByWorkFlowCode(
method: 'get'
})
}
export function queryDependentTasks(projectCode: number, params: DependentTaskReq): any {
return axios({
url: `/projects/${projectCode}/lineages/query-dependent-tasks`,
method: 'get',
params
})
}

5
dolphinscheduler-ui/src/service/modules/lineages/types.ts

@ -47,10 +47,15 @@ interface WorkflowRes {
workFlowRelationList: WorkFlowRelationList[]
}
interface DependentTaskReq extends WorkflowCodeReq {
taskCode?: number
}
export {
ProjectCodeReq,
WorkflowCodeReq,
WorkFlowNameReq,
DependentTaskReq,
WorkflowRes,
WorkFlowListRes
}

129
dolphinscheduler-ui/src/views/projects/components/dependencies/dependencies-modal.tsx

@ -0,0 +1,129 @@
/*
* 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.
*/
import {
defineComponent,
PropType,
h,
ref, watch
} from 'vue'
import { useI18n } from 'vue-i18n'
import {NEllipsis, NModal, NSpace} from 'naive-ui'
import {IDefinitionData} from "@/views/projects/workflow/definition/types";
import ButtonLink from "@/components/button-link";
const props = {
row: {
type: Object as PropType<IDefinitionData>,
default: {},
required: false
},
show: {
type: Boolean as PropType<boolean>,
default: false
},
required: {
type: Boolean as PropType<boolean>,
default: true
},
taskLinks: {
type: Array,
default: []
},
content: {
type: String,
default: ''
}
}
export default defineComponent({
name: 'dependenciesConfirm',
props,
emits: ['update:show', 'update:row', 'confirm'],
setup(props, ctx) {
const { t } = useI18n()
const showRef = ref(props.show)
const confirmToHandle = () => {
ctx.emit('confirm')
}
const cancelToHandle = () => {
ctx.emit('update:show', showRef)
}
const renderDownstreamDependencies = () => {
return h(
<NSpace vertical>
<div>{props.content}</div>
<div>{t('project.workflow.warning_dependencies')}</div>
{props.taskLinks.map((item: any) => {
return (
<ButtonLink
onClick={item.action}
disabled={false}
>
{{
default: () =>
h(NEllipsis,
{
style: 'max-width: 350px;line-height: 1.5'
},
() => item.text
)
}}
</ButtonLink>
)
})}
</NSpace>
)
}
watch(()=> props.show,
() => {
showRef.value = props.show
})
return {renderDownstreamDependencies, confirmToHandle, cancelToHandle, showRef}
},
render() {
const { t } = useI18n()
return (
<NModal
v-model:show={this.showRef}
preset={'dialog'}
type={this.$props.required? 'error':'warning'}
title={t('project.workflow.warning_dependent_tasks_title')}
positiveText={this.$props.required? '':t('project.workflow.confirm')}
negativeText={t('project.workflow.cancel')}
maskClosable={false}
onNegativeClick={this.cancelToHandle}
onPositiveClick={this.confirmToHandle}
onClose={this.cancelToHandle}
>
{{
default: () => (
this.renderDownstreamDependencies()
)
}}
</NModal>
)
}
})

122
dolphinscheduler-ui/src/views/projects/components/dependencies/use-dependencies.ts

@ -0,0 +1,122 @@
/*
* 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.
*/
import {DependentTaskReq} from "@/service/modules/lineages/types";
import {queryDependentTasks} from "@/service/modules/lineages";
import {TASK_TYPES_MAP} from "@/store/project";
export function useDependencies() {
const getDependentTasksBySingleTask = async (projectCode: any, workflowCode: any, taskCode: any) => {
let tasks = [] as any
if (workflowCode && taskCode) {
let dependentTaskReq = {workFlowCode: workflowCode, taskCode: taskCode} as DependentTaskReq
const res = await queryDependentTasks(projectCode, dependentTaskReq)
res.filter((item: any) => item.processDefinitionCode !== workflowCode && item.taskType === TASK_TYPES_MAP.DEPENDENT.alias)
.forEach((item: any) => {
tasks.push(item.processDefinitionName + '->' + item.taskName)
})
}
return tasks
}
const getDependentTasksByWorkflow = async (projectCode: any, workflowCode: any) => {
let tasks = [] as any
if (workflowCode) {
let dependentTaskReq = {workFlowCode: workflowCode} as DependentTaskReq
const res = await queryDependentTasks(projectCode, dependentTaskReq)
res.filter((item: any) => item.processDefinitionCode !== workflowCode && item.taskType === TASK_TYPES_MAP.DEPENDENT.alias)
.forEach((item: any) => {
tasks.push(item.processDefinitionName + '->' + item.taskName)
})
}
return tasks
}
const getDependentTasksByMultipleTasks = async (projectCode: any, workflowCode: any, taskCodes: any[]) => {
let tasks = [] as any
if (workflowCode && taskCodes?.length>0) {
for(const taskCode of taskCodes) {
const res = await getDependentTasksBySingleTask(projectCode, workflowCode, taskCode)
if (res?.length >0) {
tasks = tasks.concat(res)
}
}
}
return tasks
}
const getDependentTaskLinksByMultipleTasks = async (projectCode: any, workflowCode: any, taskCodes: any[]) => {
let dependentTaskLinks = [] as any
if (workflowCode && projectCode) {
for (const taskCode of taskCodes) {
await getDependentTaskLinksByTask(projectCode, workflowCode, taskCode).then((res: any) => {
dependentTaskLinks = dependentTaskLinks.concat(res)
})
}
}
return dependentTaskLinks
}
const getDependentTaskLinks = async (projectCode: any, workflowCode: any) => {
let dependentTaskReq = {workFlowCode: workflowCode} as DependentTaskReq
let dependentTaskLinks = [] as any
if (workflowCode && projectCode) {
await queryDependentTasks(projectCode, dependentTaskReq).then((res: any) => {
res.filter((item: any) => item.processDefinitionCode !== workflowCode && item.taskType === TASK_TYPES_MAP.DEPENDENT.alias)
.forEach((item: any) => {
dependentTaskLinks.push(
{
text: item.processDefinitionName + '->' + item.taskName,
show: true,
action: () => {
const url = `/projects/${item.projectCode}/workflow/definitions/${item.processDefinitionCode}`
window.open(url, '_blank')
},
}
)
})
})
}
return dependentTaskLinks
}
const getDependentTaskLinksByTask = async (projectCode: any, workflowCode: any, taskCode: any) => {
let dependentTaskReq = {workFlowCode: workflowCode, taskCode: taskCode} as DependentTaskReq
let dependentTaskLinks = [] as any
if (workflowCode && projectCode) {
await queryDependentTasks(projectCode, dependentTaskReq).then((res: any) => {
res.filter((item: any) => item.processDefinitionCode !== workflowCode && item.taskType === TASK_TYPES_MAP.DEPENDENT.alias)
.forEach((item: any) => {
dependentTaskLinks.push(
{
text: item.processDefinitionName + '->' + item.taskName,
show: true,
action: () => {
const url = `/projects/${item.projectCode}/workflow/definitions/${item.processDefinitionCode}`
window.open(url, '_blank')
},
}
)
})
})
}
return dependentTaskLinks
}
return { getDependentTasksBySingleTask, getDependentTasksByMultipleTasks, getDependentTaskLinks, getDependentTasksByWorkflow, getDependentTaskLinksByTask, getDependentTaskLinksByMultipleTasks }
}

8
dolphinscheduler-ui/src/views/projects/task/definition/batch-task.tsx

@ -41,6 +41,7 @@ import Card from '@/components/card'
import VersionModal from './components/version-modal'
import TaskModal from '@/views/projects/task/components/node/detail-modal'
import type { INodeData } from './types'
import DependenciesModal from "@/views/projects/components/dependencies/dependencies-modal";
const BatchTaskDefinition = defineComponent({
name: 'batch-task-definition',
@ -213,6 +214,13 @@ const BatchTaskDefinition = defineComponent({
readonly={this.taskReadonly}
saving={this.taskSaving}
/>
<DependenciesModal
v-model:show={this.dependenciesData.showRef}
v-model:taskLinks={this.dependenciesData.taskLinks}
required={this.dependenciesData.required}
content={this.dependenciesData.tip}
onConfirm={this.dependenciesData.action}
/>
</NSpace>
)
}

44
dolphinscheduler-ui/src/views/projects/task/definition/use-table.ts

@ -49,11 +49,15 @@ import type {
} from '@/service/modules/task-definition/types'
import type { IRecord } from './types'
import { useDependencies } from '../../components/dependencies/use-dependencies'
export function useTable(onEdit: Function) {
const { t } = useI18n()
const route = useRoute()
const projectCode = Number(route.params.projectCode)
const {getDependentTaskLinksByTask} = useDependencies()
const createColumns = (variables: any) => {
variables.columns = [
{
@ -260,22 +264,38 @@ export function useTable(onEdit: Function) {
totalPage: ref(1),
taskType: ref(null),
showVersionModalRef: ref(false),
dependentTasksShowRef: ref(false),
dependentTaskLinksRef: ref([]),
row: {},
loadingRef: ref(false)
loadingRef: ref(false),
dependenciesData: ref({showRef: ref(false), taskLinks: ref([]), required: ref(false), tip: ref(''), action:() => {}}),
})
const handleDelete = (row: any) => {
deleteTaskDefinition({ code: row.taskCode }, { projectCode }).then(() => {
getTableData({
pageSize: variables.pageSize,
pageNo:
variables.tableData.length === 1 && variables.page > 1
? variables.page - 1
: variables.page,
searchTaskName: variables.searchTaskName,
searchWorkflowName: variables.searchWorkflowName,
taskType: variables.taskType
})
variables.row = row
getDependentTaskLinksByTask(projectCode, row.processDefinitionCode, row.taskCode).then((res: any) =>{
if (res && res.length > 0) {
variables.dependenciesData = {
showRef: true,
taskLinks: res,
tip: t('project.workflow.delete_validate_dependent_tasks_desc'),
required: true,
action: () => {}
}
} else {
deleteTaskDefinition({ code: row.taskCode }, { projectCode }).then(() => {
getTableData({
pageSize: variables.pageSize,
pageNo:
variables.tableData.length === 1 && variables.page > 1
? variables.page - 1
: variables.page,
searchTaskName: variables.searchTaskName,
searchWorkflowName: variables.searchWorkflowName,
taskType: variables.taskType
})
})
}
})
}

33
dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-context-menu.tsx

@ -23,7 +23,8 @@ import { useRoute } from 'vue-router'
import styles from './menu.module.scss'
import { uuid } from '@/common/common'
import { IWorkflowTaskInstance } from './types'
import { NButton } from 'naive-ui'
import {NButton} from 'naive-ui'
import {useDependencies} from "@/views/projects/components/dependencies/use-dependencies"
const props = {
startDisplay: {
@ -57,6 +58,10 @@ const props = {
top: {
type: Number as PropType<number>,
default: 0
},
dependenciesData: {
type: Object as PropType<any>,
require: false
}
}
@ -77,6 +82,12 @@ export default defineComponent({
const graph = inject('graph', ref())
const route = useRoute()
const projectCode = Number(route.params.projectCode)
const workflowCode = Number(route.params.code)
const { t } = useI18n()
const { getDependentTaskLinksByTask } = useDependencies()
const dependenciesData = props.dependenciesData
const hide = () => {
ctx.emit('hide', false)
@ -134,9 +145,19 @@ export default defineComponent({
})
}
const handleDelete = () => {
graph.value?.removeCell(props.cell)
ctx.emit('removeTasks', [Number(props.cell?.id)])
const handleDelete = async () => {
let taskCode = props.cell?.id
let res = await getDependentTaskLinksByTask(projectCode, workflowCode, taskCode)
dependenciesData.showRef = false
if (res.length > 0) {
dependenciesData.showRef = true
dependenciesData.taskLinks = res
dependenciesData.tip = t('project.task.delete_validate_dependent_tasks_desc')
dependenciesData.required = true
} else {
graph.value?.removeCell(props.cell)
ctx.emit('removeTasks', [Number(props.cell?.id)])
}
}
onMounted(() => {
@ -189,8 +210,8 @@ export default defineComponent({
{t('project.node.copy')}
</NButton>
<NButton
class={`${styles['menu-item']}`}
onClick={this.handleDelete}
class={`${styles['menu-item']}`}
onClick={this.handleDelete}
>
{t('project.node.delete')}
</NButton>

24
dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-toolbar.tsx

@ -49,6 +49,7 @@ import type { Graph } from '@antv/x6'
import StartupParam from './dag-startup-param'
import VariablesView from '@/views/projects/workflow/instance/components/variables-view'
import { WorkflowDefinition, WorkflowInstance } from './types'
import { useDependencies } from "@/views/projects/components/dependencies/use-dependencies"
const props = {
layoutToggle: {
@ -64,6 +65,10 @@ const props = {
// The same as the structure responsed by the queryProcessDefinitionByCode api
type: Object as PropType<WorkflowDefinition>,
default: null
},
dependenciesData: {
type: Object as PropType<any>,
require: false
}
}
@ -79,6 +84,11 @@ export default defineComponent({
const graph = inject<Ref<Graph | undefined>>('graph', ref())
const router = useRouter()
const route = useRoute()
const projectCode = Number(route.params.projectCode)
const workflowCode = Number(route.params.code)
const { getDependentTaskLinksByMultipleTasks } = useDependencies()
const dependenciesData = props.dependenciesData
/**
* Node search and navigate
@ -164,15 +174,23 @@ export default defineComponent({
/**
* Delete selected edges and nodes
*/
const removeCells = () => {
const removeCells = async () => {
if (graph.value) {
const cells = graph.value.getSelectedCells()
if (cells) {
const codes = cells
.filter((cell) => cell.isNode())
.map((cell) => +cell.id)
context.emit('removeTasks', codes, cells)
graph.value?.removeCells(cells)
const res = await getDependentTaskLinksByMultipleTasks(projectCode, workflowCode, codes)
if (res.length > 0) {
dependenciesData.showRef = true
dependenciesData.taskLinks = res
dependenciesData.tip = t('project.task.delete_validate_dependent_tasks_desc')
dependenciesData.required = true
} else {
context.emit('removeTasks', codes, cells)
graph.value?.removeCells(cells)
}
}
}
}

19
dolphinscheduler-ui/src/views/projects/workflow/components/dag/index.tsx

@ -24,7 +24,7 @@ import {
toRef,
watch,
onBeforeUnmount,
computed
computed, reactive
} from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
@ -57,6 +57,7 @@ import utils from '@/utils'
import { useUISettingStore } from '@/store/ui-setting/ui-setting'
import { executeTask } from '@/service/modules/executors'
import { removeTaskInstanceCache } from '@/service/modules/task-instances'
import DependenciesModal from "@/views/projects/components/dependencies/dependencies-modal";
const props = {
// If this prop is passed, it means from definition detail
@ -333,6 +334,13 @@ export default defineComponent({
}
}
const dependenciesData = reactive({
showRef: ref(false),
taskLinks: ref([]),
required: ref(false),
tip: ref(''), action: () => {}
})
watch(
() => props.definition,
() => {
@ -373,6 +381,7 @@ export default defineComponent({
onSaveModelToggle={saveModelToggle}
onRemoveTasks={removeTasks}
onRefresh={refreshTaskStatus}
v-model:dependenciesData={dependenciesData}
/>
<div class={Styles.content}>
<DagSidebar onDragStart={onDragStart} />
@ -428,6 +437,14 @@ export default defineComponent({
onViewLog={handleViewLog}
onExecuteTask={handleExecuteTask}
onRemoveTaskInstanceCache={handleRemoveTaskInstanceCache}
v-model:dependenciesData={dependenciesData}
/>
<DependenciesModal
v-model:show={dependenciesData.showRef}
v-model:taskLinks={dependenciesData.taskLinks}
required={dependenciesData.required}
content={dependenciesData.tip}
onConfirm={dependenciesData.action}
/>
{!!props.definition && (
<StartModal

12
dolphinscheduler-ui/src/views/projects/workflow/definition/components/table-action.tsx

@ -33,6 +33,7 @@ import {
} from '@vicons/antd'
import { useI18n } from 'vue-i18n'
import { IDefinitionData } from '../types'
const props = {
row: {
type: Object as PropType<IDefinitionData>
@ -95,6 +96,7 @@ export default defineComponent({
const handleReleaseScheduler = () => {
ctx.emit('releaseScheduler')
}
return {
handleEditWorkflow,
handleStartWorkflow,
@ -114,6 +116,7 @@ export default defineComponent({
const releaseState = this.row?.releaseState
const scheduleReleaseState = this.row?.scheduleReleaseState
const schedule = this.row?.schedule
return (
<NSpace>
<NTooltip trigger={'hover'}>
@ -166,10 +169,7 @@ export default defineComponent({
trigger: () => (
<NPopconfirm onPositiveClick={this.handleReleaseWorkflow}>
{{
default: () =>
releaseState === 'ONLINE'
? t('project.workflow.confirm_to_offline')
: t('project.workflow.confirm_to_online'),
default: () => releaseState === 'OFFLINE' ? t('project.workflow.confirm_to_online'):t('project.workflow.confirm_to_offline'),
trigger: () => (
<NButton
size='small'
@ -220,9 +220,7 @@ export default defineComponent({
<NPopconfirm onPositiveClick={this.handleReleaseScheduler}>
{{
default: () =>
scheduleReleaseState === 'ONLINE'
? t('project.workflow.time_to_offline')
: t('project.workflow.time_to_online'),
scheduleReleaseState === 'OFFLINE' ? t('project.workflow.time_to_online'):t('project.workflow.time_to_offline'),
trigger: () => (
<NButton
size='small'

11
dolphinscheduler-ui/src/views/projects/workflow/definition/index.tsx

@ -24,7 +24,7 @@ import {
NSpace,
NTooltip,
NPopconfirm,
NModal
NModal,
} from 'naive-ui'
import {
defineComponent,
@ -45,6 +45,7 @@ import VersionModal from './components/version-modal'
import CopyModal from './components/copy-modal'
import type { Router } from 'vue-router'
import Search from '@/components/input-search'
import DependenciesModal from '@/views/projects/components/dependencies/dependencies-modal'
export default defineComponent({
name: 'WorkflowDefinitionList',
@ -318,6 +319,14 @@ export default defineComponent({
maskClosable={false}
onPositiveClick={this.confirmToSetWorkflowTiming}
/>
<DependenciesModal
v-model:row={this.row}
v-model:show={this.dependenciesData.showRef}
v-model:taskLinks={this.dependenciesData.taskLinks}
required={this.dependenciesData.required}
content={this.dependenciesData.tip}
onConfirm={this.dependenciesData.action}
/>
</NSpace>
)
}

8
dolphinscheduler-ui/src/views/projects/workflow/definition/timing/index.tsx

@ -24,6 +24,7 @@ import { useTable } from './use-table'
import Card from '@/components/card'
import TimingModal from '../components/timing-modal'
import type { Router } from 'vue-router'
import DependenciesModal from "@/views/projects/components/dependencies/dependencies-modal";
export default defineComponent({
name: 'WorkflowDefinitionTiming',
@ -115,6 +116,13 @@ export default defineComponent({
v-model:show={this.showRef}
onUpdateList={this.handleUpdateList}
/>
<DependenciesModal
v-model:show={this.dependenciesData.showRef}
v-model:taskLinks={this.dependenciesData.taskLinks}
required={this.dependenciesData.required}
content={this.dependenciesData.tip}
onConfirm={this.dependenciesData.action}
/>
</NSpace>
)
}

88
dolphinscheduler-ui/src/views/projects/workflow/definition/timing/use-table.ts

@ -39,15 +39,18 @@ import {
import { format } from 'date-fns-tz'
import { ISearchParam } from './types'
import type { Router } from 'vue-router'
import { useDependencies } from "@/views/projects/components/dependencies/use-dependencies"
export function useTable() {
const { t } = useI18n()
const router: Router = useRouter()
const {getDependentTaskLinks} = useDependencies()
const variables = reactive({
columns: [],
tableWidth: DefaultTableWidth,
row: {},
row: {} as any,
tableData: [],
projectCode: ref(Number(router.currentRoute.value.params.projectCode)),
page: ref(1),
@ -58,7 +61,8 @@ export function useTable() {
loadingRef: ref(false),
processDefinitionCode: router.currentRoute.value.params.definitionCode
? ref(Number(router.currentRoute.value.params.definitionCode))
: ref()
: ref(),
dependenciesData: ref({showRef: false, taskLinks: ref([]), required: ref(false), tip: ref(''), action:() => {}}),
})
const renderTime = (time: string, timeZone: string) => {
@ -329,7 +333,7 @@ export function useTable() {
NPopconfirm,
{
onPositiveClick: () => {
handleDelete(row.id)
handleDelete(row)
}
},
{
@ -344,7 +348,8 @@ export function useTable() {
{
circle: true,
type: 'error',
size: 'small'
size: 'small',
disabled: row.releaseState === 'ONLINE'
},
{
icon: () => h(DeleteOutlined)
@ -387,12 +392,43 @@ export function useTable() {
}
const handleReleaseState = (row: any) => {
let handle = online
if (row.releaseState === 'ONLINE') {
handle = offline
variables.row = row
getDependentTaskLinks(variables.projectCode, row.processDefinitionCode).then((res: any) =>{
if (res && res.length > 0) {
variables.dependenciesData.showRef = true
variables.dependenciesData.taskLinks = res
variables.dependenciesData.tip = t('project.workflow.warning_delete_scheduler_dependent_tasks_desc')
variables.dependenciesData.required = false
variables.dependenciesData.action = confirmToOfflineSchedule
} else {
offline(variables.projectCode, row.id).then(() => {
window.$message.success(t('project.workflow.success'))
getTableData({
pageSize: variables.pageSize,
pageNo: variables.page,
searchVal: variables.searchVal,
projectCode: variables.projectCode,
processDefinitionCode: variables.processDefinitionCode
})
})
}})
} else {
online(variables.projectCode, row.id).then(() => {
window.$message.success(t('project.workflow.success'))
getTableData({
pageSize: variables.pageSize,
pageNo: variables.page,
searchVal: variables.searchVal,
projectCode: variables.projectCode,
processDefinitionCode: variables.processDefinitionCode
})
})
}
}
handle(variables.projectCode, row.id).then(() => {
const confirmToOfflineSchedule = () => {
offline(variables.projectCode, variables.row.id).then(() => {
window.$message.success(t('project.workflow.success'))
getTableData({
pageSize: variables.pageSize,
@ -402,14 +438,11 @@ export function useTable() {
processDefinitionCode: variables.processDefinitionCode
})
})
variables.dependenciesData.showRef = false
}
const handleDelete = (id: number) => {
/* after deleting data from the current page, you need to jump forward when the page is empty. */
if (variables.tableData.length === 1 && variables.page > 1) {
variables.page -= 1
}
deleteScheduleById(id, variables.projectCode).then(() => {
const confirmToDeleteSchedule = () => {
deleteScheduleById(variables.row.id, variables.projectCode).then(() => {
window.$message.success(t('project.workflow.success'))
getTableData({
pageSize: variables.pageSize,
@ -419,6 +452,35 @@ export function useTable() {
processDefinitionCode: variables.processDefinitionCode
})
})
variables.dependenciesData.showRef = false
}
const handleDelete = (row: any) => {
/* after deleting data from the current page, you need to jump forward when the page is empty. */
if (variables.tableData.length === 1 && variables.page > 1) {
variables.page -= 1
}
variables.row = row
getDependentTaskLinks(variables.projectCode, row.processDefinitionCode).then((res: any) =>{
if (res && res.length > 0) {
variables.dependenciesData.showRef = true
variables.dependenciesData.taskLinks = res
variables.dependenciesData.tip = t('project.workflow.warning_delete_scheduler_dependent_tasks_desc')
variables.dependenciesData.required = false
variables.dependenciesData.action = confirmToDeleteSchedule
} else {
deleteScheduleById(row.id, variables.projectCode).then(() => {
window.$message.success(t('project.workflow.success'))
getTableData({
pageSize: variables.pageSize,
pageNo: variables.page,
searchVal: variables.searchVal,
projectCode: variables.projectCode,
processDefinitionCode: variables.processDefinitionCode
})
})
}
})
}
return {

157
dolphinscheduler-ui/src/views/projects/workflow/definition/use-table.ts

@ -43,11 +43,14 @@ import {
import type { IDefinitionParam } from './types'
import type { Router } from 'vue-router'
import type { TableColumns, RowKey } from 'naive-ui/es/data-table/src/interface'
import {useDependencies} from '../../components/dependencies/use-dependencies'
export function useTable() {
const { t } = useI18n()
const router: Router = useRouter()
const { copy } = useTextCopy()
const { getDependentTaskLinks } = useDependencies()
const variables = reactive({
columns: [],
tableWidth: DefaultTableWidth,
@ -67,7 +70,8 @@ export function useTable() {
versionShowRef: ref(false),
copyShowRef: ref(false),
loadingRef: ref(false),
setTimingDialogShowRef: ref(false)
setTimingDialogShowRef: ref(false),
dependenciesData: ref({showRef: false, taskLinks: ref([]), required: ref(false), tip: ref(''), action:() => {}}),
})
const createColumns = (variables: any) => {
@ -304,17 +308,6 @@ export function useTable() {
variables.row = row
}
const deleteWorkflow = (row: any) => {
deleteByCode(variables.projectCode, row.code).then(() => {
window.$message.success(t('project.workflow.success'))
getTableData({
pageSize: variables.pageSize,
pageNo: variables.page,
searchVal: variables.searchVal
})
})
}
const batchDeleteWorkflow = () => {
const data = {
codes: _.join(variables.checkedRowKeys, ',')
@ -354,48 +347,142 @@ export function useTable() {
const batchCopyWorkflow = () => {}
const releaseWorkflow = (row: any) => {
const confirmToOfflineWorkflow = () => {
const row: any = variables.row
const data = {
name: row.name,
releaseState: (row.releaseState === 'ONLINE' ? 'OFFLINE' : 'ONLINE') as
| 'OFFLINE'
| 'ONLINE'
| 'OFFLINE'
| 'ONLINE'
}
if (data.releaseState === 'OFFLINE') {
release(data, variables.projectCode, row.code).then(() => {
getTableData({
pageSize: variables.pageSize,
pageNo: variables.page,
searchVal: variables.searchVal
})
window.$message.success(t('project.workflow.success'))
})
}
variables.dependenciesData.showRef = false
}
const confirmToOfflineScheduler = () => {
const row: any = variables.row
offline(variables.projectCode, row.schedule.id).then(() => {
window.$message.success(t('project.workflow.success'))
getTableData({
pageSize: variables.pageSize,
pageNo: variables.page,
searchVal: variables.searchVal
})
})
variables.dependenciesData.showRef = false
}
release(data, variables.projectCode, row.code).then(() => {
if (data.releaseState === 'ONLINE') {
const releaseWorkflow = (row: any) => {
const data = {
name: row.name,
releaseState: (row.releaseState === 'ONLINE' ? 'OFFLINE' : 'ONLINE') as
| 'OFFLINE'
| 'ONLINE'
}
variables.row = row
if (data.releaseState === 'ONLINE') {
release(data, variables.projectCode, row.code).then(() => {
variables.setTimingDialogShowRef = true
variables.row = row
if (row?.schedule) {
variables.row = row.schedule
variables.timingType = 'update'
variables.timingState = row.scheduleReleaseState
}
getTableData({
pageSize: variables.pageSize,
pageNo: variables.page,
searchVal: variables.searchVal
})
})
} else {
getDependentTaskLinks(variables.projectCode, row.code).then((res: any) => {
if (res && res.length > 0) {
variables.dependenciesData = {
showRef: true,
taskLinks: res,
tip: t('project.workflow.warning_dependent_tasks_desc'),
required: false,
action: confirmToOfflineWorkflow
}
} else {
release(data, variables.projectCode, row.code).then(() => {
window.$message.success(t('project.workflow.success'))
getTableData({
pageSize: variables.pageSize,
pageNo: variables.page,
searchVal: variables.searchVal
})
})
}
})
}
}
const deleteWorkflow = (row: any) => {
getDependentTaskLinks(variables.projectCode, row.code).then((res: any) => {
if (res && res.length > 0) {
variables.dependenciesData = {
showRef: true,
taskLinks: res,
tip: t('project.workflow.delete_validate_dependent_tasks_desc'),
required: true,
action: () => {}
}
} else {
window.$message.success(t('project.workflow.success'))
deleteByCode(variables.projectCode, row.code).then(() => {
window.$message.success(t('project.workflow.success'))
getTableData({
pageSize: variables.pageSize,
pageNo: variables.page,
searchVal: variables.searchVal
})
})
}
getTableData({
pageSize: variables.pageSize,
pageNo: variables.page,
searchVal: variables.searchVal
})
})
}
const releaseScheduler = (row: any) => {
variables.row = row
if (row.schedule) {
let handle = online
if (row.schedule.releaseState === 'ONLINE') {
handle = offline
}
handle(variables.projectCode, row.schedule.id).then(() => {
window.$message.success(t('project.workflow.success'))
getTableData({
pageSize: variables.pageSize,
pageNo: variables.page,
searchVal: variables.searchVal
getDependentTaskLinks(variables.projectCode, row.code).then((res: any) => {
if (res && res.length > 0) {
variables.dependenciesData = {
showRef: true,
taskLinks: res,
tip: t('project.workflow.warning_offline_scheduler_dependent_tasks_desc'),
required: false,
action: confirmToOfflineScheduler
}
} else {
offline(variables.projectCode, row.schedule.id).then(() => {
window.$message.success(t('project.workflow.success'))
getTableData({
pageSize: variables.pageSize,
pageNo: variables.page,
searchVal: variables.searchVal
})
})
}})
} else {
online(variables.projectCode, row.schedule.id).then(() => {
window.$message.success(t('project.workflow.success'))
getTableData({
pageSize: variables.pageSize,
pageNo: variables.page,
searchVal: variables.searchVal
})
})
})
}
}
}
@ -479,6 +566,6 @@ export function useTable() {
getTableData,
batchDeleteWorkflow,
batchExportWorkflow,
batchCopyWorkflow
batchCopyWorkflow,
}
}

12
dolphinscheduler-ui/src/views/projects/workflow/timing/index.tsx

@ -15,14 +15,15 @@
* limitations under the License.
*/
import { NDataTable, NPagination, NSpace } from 'naive-ui'
import { defineComponent, onMounted, toRefs, watch } from 'vue'
import {NDataTable, NPagination, NSpace} from 'naive-ui'
import {defineComponent, onMounted, toRefs, watch} from 'vue'
import { useI18n } from 'vue-i18n'
import { useTable } from '../definition/timing/use-table'
import Card from '@/components/card'
import TimingModal from '../definition/components/timing-modal'
import TimingCondition from '@/views/projects/workflow/timing/components/timing-condition'
import { ITimingSearch } from '@/views/projects/workflow/timing/types'
import DependenciesModal from "@/views/projects/components/dependencies/dependencies-modal";
export default defineComponent({
name: 'WorkflowTimingList',
@ -110,6 +111,13 @@ export default defineComponent({
v-model:show={this.showRef}
onUpdateList={this.handleUpdateList}
/>
<DependenciesModal
v-model:show={this.dependenciesData.showRef}
v-model:taskLinks={this.dependenciesData.taskLinks}
required={this.dependenciesData.required}
content={this.dependenciesData.tip}
onConfirm={this.dependenciesData.action}
/>
</NSpace>
)
}

Loading…
Cancel
Save