Browse Source

[Feature][UI Next] Create workflow (#8362)

3.0.0/version-upgrade
wangyizhi 2 years ago committed by GitHub
parent
commit
4b06b76068
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      dolphinscheduler-ui-next/src/locales/modules/en_US.ts
  2. 3
      dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts
  3. 6
      dolphinscheduler-ui-next/src/service/modules/process-definition/index.ts
  4. 3
      dolphinscheduler-ui-next/src/service/modules/process-definition/types.ts
  5. 76
      dolphinscheduler-ui-next/src/views/projects/node/detail-modal.tsx
  6. 7
      dolphinscheduler-ui-next/src/views/projects/node/detail.tsx
  7. 22
      dolphinscheduler-ui-next/src/views/projects/node/types.ts
  8. 8
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-hooks.ts
  9. 3
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-save-modal.tsx
  10. 7
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-sidebar.tsx
  11. 47
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/index.tsx
  12. 69
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/types.ts
  13. 118
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-business-mapper.ts
  14. 54
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-cell-query.ts
  15. 5
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-cell-update.ts
  16. 6
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-custom-cell-builder.ts
  17. 21
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-dag-drag-drop.ts
  18. 9
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-node-search.ts
  19. 119
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-task-edit.ts
  20. 58
      dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.tsx
  21. 9
      dolphinscheduler-ui-next/src/views/projects/workflow/definition/detail/index.tsx

3
dolphinscheduler-ui-next/src/locales/modules/en_US.ts

@ -545,7 +545,8 @@ const project = {
basic_info: 'Basic Information', basic_info: 'Basic Information',
minute: 'Minute', minute: 'Minute',
key: 'Key', key: 'Key',
value: 'Value' value: 'Value',
success: 'Success'
}, },
node: { node: {
current_node_settings: 'Current node settings', current_node_settings: 'Current node settings',

3
dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts

@ -543,7 +543,8 @@ const project = {
basic_info: '基本信息', basic_info: '基本信息',
minute: '分', minute: '分',
key: '键', key: '键',
value: '值' value: '值',
success: '成功'
}, },
node: { node: {
current_node_settings: '当前节点设置', current_node_settings: '当前节点设置',

6
dolphinscheduler-ui-next/src/service/modules/process-definition/index.ts

@ -39,11 +39,11 @@ export function queryListPaging(params: PageReq & ListReq, code: number): any {
} }
export function createProcessDefinition( export function createProcessDefinition(
data: ProcessDefinitionReq & NameReq, data: ProcessDefinitionReq,
code: CodeReq projectCode: number
): any { ): any {
return axios({ return axios({
url: `/projects/${code}/process-definition`, url: `/projects/${projectCode}/process-definition`,
method: 'post', method: 'post',
data data
}) })

3
dolphinscheduler-ui-next/src/service/modules/process-definition/types.ts

@ -53,7 +53,8 @@ interface ListReq extends PageReq {
userId?: number userId?: number
} }
interface ProcessDefinitionReq extends NameReq { interface ProcessDefinitionReq {
name: string
locations: string locations: string
taskDefinitionJson: string taskDefinitionJson: string
taskRelationJson: string taskRelationJson: string

76
dolphinscheduler-ui-next/src/views/projects/node/detail-modal.tsx

@ -19,37 +19,71 @@ import { defineComponent, PropType, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import Modal from '@/components/modal' import Modal from '@/components/modal'
import Detail from './detail' import Detail from './detail'
import type { IDataNode, ITask } from './types' import type { NodeData } from '@/views/projects/workflow/components/dag/types'
const props = { const props = {
show: { show: {
type: Boolean as PropType<boolean>, type: Boolean as PropType<boolean>,
default: false default: false
}, },
nodeData: { taskDefinition: {
type: Object as PropType<IDataNode>, type: Object as PropType<NodeData>,
default: { default: { code: 0, taskType: 'SHELL', name: '' }
taskType: 'SHELL'
}
}, },
type: { projectCode: {
type: String as PropType<string>, type: Number as PropType<number>,
default: '' required: true
}, },
taskDefinition: { readonly: {
type: Object as PropType<ITask> type: Boolean as PropType<boolean>,
default: false
} }
} }
const NodeDetailModal = defineComponent({ const NodeDetailModal = defineComponent({
name: 'NodeDetailModal', name: 'NodeDetailModal',
props, props,
emits: ['cancel', 'update'], emits: ['cancel', 'submit'],
setup(props, { emit }) { setup(props, { emit }) {
const { t } = useI18n() const { t } = useI18n()
const detailRef = ref() const detailRef = ref()
// TODO
const mapFormToTaskDefinition = (form: any) => {
return {
// "code": form.code,
name: form.name,
description: form.desc,
taskType: 'SHELL',
taskParams: {
resourceList: [],
localParams: form.localParams,
rawScript: form.shell,
dependence: {},
conditionResult: {
successNode: [],
failedNode: []
},
waitStartTimeout: {},
switchResult: {}
},
flag: form.runFlag,
taskPriority: 'MEDIUM',
workerGroup: form.workerGroup,
failRetryTimes: '0',
failRetryInterval: '1',
timeoutFlag: 'CLOSE',
timeoutNotifyStrategy: '',
timeout: 0,
delayTime: '0',
environmentCode: form.environmentCode
}
}
const onConfirm = () => { const onConfirm = () => {
detailRef.value.onSubmit() emit('submit', {
formRef: detailRef.value.formRef,
form: mapFormToTaskDefinition(detailRef.value.form)
})
} }
const onCancel = () => { const onCancel = () => {
emit('cancel') emit('cancel')
@ -63,7 +97,15 @@ const NodeDetailModal = defineComponent({
} }
}, },
render() { render() {
const { t, show, onConfirm, onCancel } = this const {
t,
show,
onConfirm,
onCancel,
projectCode,
taskDefinition,
readonly
} = this
return ( return (
<Modal <Modal
show={show} show={show}
@ -72,7 +114,11 @@ const NodeDetailModal = defineComponent({
confirmLoading={false} confirmLoading={false}
onCancel={onCancel} onCancel={onCancel}
> >
<Detail ref='detailRef' taskType='SHELL' projectCode={111} /> <Detail
ref='detailRef'
taskType={taskDefinition.taskType}
projectCode={projectCode}
/>
</Modal> </Modal>
) )
} }

7
dolphinscheduler-ui-next/src/views/projects/node/detail.tsx

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { defineComponent, PropType, ref, toRefs } from 'vue' import { defineComponent, PropType, ref, toRef, toRefs } from 'vue'
import Form from '@/components/form' import Form from '@/components/form'
import { useTask } from './use-task' import { useTask } from './use-task'
import { useDetail } from './use-detail' import { useDetail } from './use-detail'
@ -40,14 +40,15 @@ const NodeDetail = defineComponent({
const { taskType, projectCode } = props const { taskType, projectCode } = props
const { json, model } = useTask({ taskType, projectCode }) const { json, model } = useTask({ taskType, projectCode })
const { state, onSubmit } = useDetail() const { state } = useDetail()
const jsonRef = ref(json) const jsonRef = ref(json)
const { rules, elements } = getElementByJson(jsonRef.value, model) const { rules, elements } = getElementByJson(jsonRef.value, model)
expose({ expose({
onSubmit: () => void onSubmit(model) formRef: toRef(state, 'formRef'),
form: model
}) })
return { rules, elements, model, ...toRefs(state) } return { rules, elements, model, ...toRefs(state) }

22
dolphinscheduler-ui-next/src/views/projects/node/types.ts

@ -18,6 +18,7 @@
import { VNode } from 'vue' import { VNode } from 'vue'
import type { SelectOption } from 'naive-ui' import type { SelectOption } from 'naive-ui'
import type { IFormItem, IJsonItem } from '@/components/form/types' import type { IFormItem, IJsonItem } from '@/components/form/types'
import type { TaskType } from '@/views/projects/task/constants/task-type'
interface ITaskPriorityOption extends SelectOption { interface ITaskPriorityOption extends SelectOption {
icon: VNode icon: VNode
@ -46,27 +47,10 @@ interface ITimeout {
timeout?: number timeout?: number
strategy?: string strategy?: string
} }
type ITaskType = type ITaskType = TaskType
| 'SHELL'
| 'SUB_PROCESS'
| 'PROCEDURE'
| 'SQL'
| 'SPARK'
| 'FLINK'
| 'MapReduce'
| 'PYTHON'
| 'DEPENDENT'
| 'HTTP'
| 'DataX'
| 'PIGEON'
| 'SQOOP'
| 'CONDITIONS'
| 'DATA_QUALITY'
| 'SWITCH'
| 'SEATUNNEL'
interface ITask { interface ITask {
code?: string code: number
timeoutNotifyStrategy?: string timeoutNotifyStrategy?: string
taskParams: ITaskParams taskParams: ITaskParams
description?: string description?: string

8
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-hooks.ts

@ -16,7 +16,7 @@
*/ */
import { useCanvasInit } from './use-canvas-init' import { useCanvasInit } from './use-canvas-init'
import { useCellQuery } from './use-cell-query' import { useBusinessMapper } from './use-business-mapper'
import { useCellActive } from './use-cell-active' import { useCellActive } from './use-cell-active'
import { useCellUpdate } from './use-cell-update' import { useCellUpdate } from './use-cell-update'
import { useNodeSearch } from './use-node-search' import { useNodeSearch } from './use-node-search'
@ -25,10 +25,11 @@ import { useTextCopy } from './use-text-copy'
import { useCustomCellBuilder } from './use-custom-cell-builder' import { useCustomCellBuilder } from './use-custom-cell-builder'
import { useGraphBackfill } from './use-graph-backfill' import { useGraphBackfill } from './use-graph-backfill'
import { useDagDragAndDrop } from './use-dag-drag-drop' import { useDagDragAndDrop } from './use-dag-drag-drop'
import { useTaskEdit } from './use-task-edit'
export { export {
useCanvasInit, useCanvasInit,
useCellQuery, useBusinessMapper,
useCellActive, useCellActive,
useNodeSearch, useNodeSearch,
useGraphAutoLayout, useGraphAutoLayout,
@ -36,5 +37,6 @@ export {
useCustomCellBuilder, useCustomCellBuilder,
useGraphBackfill, useGraphBackfill,
useCellUpdate, useCellUpdate,
useDagDragAndDrop useDagDragAndDrop,
useTaskEdit
} }

3
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-save-modal.tsx

@ -28,6 +28,7 @@ import {
NDynamicInput NDynamicInput
} from 'naive-ui' } from 'naive-ui'
import { queryTenantList } from '@/service/modules/tenants' import { queryTenantList } from '@/service/modules/tenants'
import { SaveForm } from './types'
import './x6-style.scss' import './x6-style.scss'
const props = { const props = {
@ -67,7 +68,7 @@ export default defineComponent({
}) })
}) })
const formValue = ref({ const formValue = ref<SaveForm>({
name: '', name: '',
description: '', description: '',
tenantCode: 'default', tenantCode: 'default',

7
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-sidebar.tsx

@ -16,7 +16,10 @@
*/ */
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { TASK_TYPES_MAP, TaskType } from '../../../task/constants/task-type' import {
TaskType,
TASK_TYPES_MAP
} from '@/views/projects/task/constants/task-type'
import Styles from './dag.module.scss' import Styles from './dag.module.scss'
export default defineComponent({ export default defineComponent({
@ -35,7 +38,7 @@ export default defineComponent({
class={Styles.draggable} class={Styles.draggable}
draggable='true' draggable='true'
onDragstart={(e) => { onDragstart={(e) => {
context.emit('dragStart', e, task.type) context.emit('dragStart', e, task.type as TaskType)
}} }}
> >
<em <em

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

@ -25,12 +25,15 @@ import DagAutoLayoutModal from './dag-auto-layout-modal'
import { import {
useGraphAutoLayout, useGraphAutoLayout,
useGraphBackfill, useGraphBackfill,
useDagDragAndDrop useDagDragAndDrop,
useTaskEdit,
useBusinessMapper
} from './dag-hooks' } from './dag-hooks'
import { useThemeStore } from '@/store/theme/theme' import { useThemeStore } from '@/store/theme/theme'
import VersionModal from '../../definition/components/version-modal' import VersionModal from '../../definition/components/version-modal'
import { WorkflowDefinition } from './types' import { WorkflowDefinition } from './types'
import DagSaveModal from './dag-save-modal' import DagSaveModal from './dag-save-modal'
import TaskModal from '@/views/projects/node/detail-modal'
import './x6-style.scss' import './x6-style.scss'
const props = { const props = {
@ -42,13 +45,17 @@ const props = {
readonly: { readonly: {
type: Boolean as PropType<boolean>, type: Boolean as PropType<boolean>,
default: false default: false
},
projectCode: {
type: Number as PropType<number>,
default: 0
} }
} }
export default defineComponent({ export default defineComponent({
name: 'workflow-dag', name: 'workflow-dag',
props, props,
emits: ['refresh'], emits: ['refresh', 'save'],
setup(props, context) { setup(props, context) {
const theme = useThemeStore() const theme = useThemeStore()
@ -68,9 +75,20 @@ export default defineComponent({
cancel cancel
} = useGraphAutoLayout({ graph }) } = useGraphAutoLayout({ graph })
// Edit task
const {
taskConfirm,
taskModalVisible,
currTask,
taskCancel,
appendTask,
taskDefinitions
} = useTaskEdit({ graph })
const { onDragStart, onDrop } = useDagDragAndDrop({ const { onDragStart, onDrop } = useDagDragAndDrop({
graph, graph,
readonly: toRef(props, 'readonly') readonly: toRef(props, 'readonly'),
appendTask
}) })
// backfill // backfill
@ -99,9 +117,19 @@ export default defineComponent({
saveModalShow.value = !versionModalShow.value saveModalShow.value = !versionModalShow.value
} }
} }
const onSave = (form: any) => { const { getConnects, getLocations } = useBusinessMapper()
// TODO const onSave = (saveForm: any) => {
console.log(form) const edges = graph.value?.getEdges() || []
const nodes = graph.value?.getNodes() || []
const connects = getConnects(nodes, edges, taskDefinitions.value as any)
const locations = getLocations(nodes)
context.emit('save', {
taskDefinitions: taskDefinitions.value,
saveForm,
connects,
locations
})
saveModelToggle(false)
} }
return () => ( return () => (
@ -136,6 +164,13 @@ export default defineComponent({
/> />
)} )}
<DagSaveModal v-model:show={saveModalShow.value} onSave={onSave} /> <DagSaveModal v-model:show={saveModalShow.value} onSave={onSave} />
<TaskModal
show={taskModalVisible.value}
projectCode={props.projectCode}
taskDefinition={currTask.value}
onSubmit={taskConfirm}
onCancel={taskCancel}
/>
</div> </div>
) )
} }

69
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/types.ts

@ -15,6 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { TaskType } from '@/views/projects/task/constants/task-type'
export interface ProcessDefinition { export interface ProcessDefinition {
id: number id: number
code: number code: number
@ -41,37 +43,37 @@ export interface ProcessDefinition {
warningGroupId: number warningGroupId: number
} }
export interface ProcessTaskRelationList { export interface Connect {
id: number id?: number
name: string name: string
processDefinitionVersion: number processDefinitionVersion?: number
projectCode: any projectCode?: number
processDefinitionCode: any processDefinitionCode?: number
preTaskCode: number preTaskCode: number
preTaskVersion: number preTaskVersion: number
postTaskCode: any postTaskCode: number
postTaskVersion: number postTaskVersion: number
conditionType: string conditionType: string
conditionParams: any conditionParams: any
createTime: string createTime?: string
updateTime: string updateTime?: string
} }
export interface TaskDefinitionList { export interface TaskDefinition {
id: number id: number
code: any code: number
name: string name: string
version: number version: number
description: string description: string
projectCode: any projectCode: any
userId: number userId: number
taskType: string taskType: TaskType
taskParams: any taskParams: any
taskParamList: any[] taskParamList: any[]
taskParamMap: any taskParamMap: any
flag: string flag: string
taskPriority: string taskPriority: string
userName?: any userName: any
projectName?: any projectName?: any
workerGroup: string workerGroup: string
environmentCode: number environmentCode: number
@ -84,12 +86,49 @@ export interface TaskDefinitionList {
resourceIds: string resourceIds: string
createTime: string createTime: string
updateTime: string updateTime: string
modifyBy?: any modifyBy: any
dependence: string dependence: string
} }
export type NodeData = {
code: number
taskType: TaskType
name: string
} & Partial<TaskDefinition>
export interface WorkflowDefinition { export interface WorkflowDefinition {
processDefinition: ProcessDefinition processDefinition: ProcessDefinition
processTaskRelationList: ProcessTaskRelationList[] processTaskRelationList: Connect[]
taskDefinitionList: TaskDefinitionList[] taskDefinitionList: TaskDefinition[]
}
export interface Dragged {
x: number
y: number
type: TaskType
}
export interface Coordinate {
x: number
y: number
}
export interface GlobalParam {
key: string
value: string
}
export interface SaveForm {
name: string
description: string
tenantCode: string
timeoutFlag: boolean
timeout: number
globalParams: GlobalParam[]
}
export interface Location {
taskCode: number
x: number
y: number
} }

118
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-business-mapper.ts

@ -0,0 +1,118 @@
/*
* 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 type { Node, Edge } from '@antv/x6'
import { Connect, Location, TaskDefinition } from './types'
import { get } from 'lodash'
/**
* Handling business entity and x6 entity conversion
* @param {Options} options
*/
export function useBusinessMapper() {
/**
* Get connects, connects and processTaskRelationList are the same
* @param {Node[]} nodes
* @param {Edge[]} edges
* @param {TaskDefinition[]} taskDefinitions
* @returns {Connect[]}
*/
function getConnects(
nodes: Node[],
edges: Edge[],
taskDefinitions: TaskDefinition[]
): Connect[] {
interface TailNodes {
[code: string]: boolean
}
// Nodes in DAG whose in-degree is not 0
const tailNodes: TailNodes = {}
// If there is an edge target to a node, the node is tailNode
edges.forEach((edge) => {
const targetId = edge.getTargetCellId()
tailNodes[targetId] = true
})
const isHeadNode = (code: string) => !tailNodes[code]
interface TasksMap {
[code: string]: TaskDefinition
}
const tasksMap: TasksMap = {}
nodes.forEach((node) => {
const code = node.id
const task = taskDefinitions.find((t) => t.code === Number(code))
if (task) {
tasksMap[code] = task
}
})
const headConnects: Connect[] = nodes
.filter((node) => isHeadNode(node.id))
.map((node) => {
const task = tasksMap[node.id]
return {
name: '',
preTaskCode: 0,
preTaskVersion: 0,
postTaskCode: task.code,
postTaskVersion: task.version || 0,
// conditionType and conditionParams are reserved
conditionType: 'NONE',
conditionParams: {}
}
})
const tailConnects: Connect[] = edges.map((edge) => {
const labels = edge.getLabels()
const labelName = get(labels, ['0', 'attrs', 'label', 'text'], '')
const sourceId = edge.getSourceCellId()
const prevTask = tasksMap[sourceId]
const targetId = edge.getTargetCellId()
const task = tasksMap[targetId]
return {
name: labelName,
preTaskCode: prevTask.code,
preTaskVersion: prevTask.version || 0,
postTaskCode: task.code,
postTaskVersion: task.version || 0,
// conditionType and conditionParams are reserved
conditionType: 'NONE',
conditionParams: {}
}
})
return headConnects.concat(tailConnects)
}
function getLocations(nodes: Node[]): Location[] {
return nodes.map((node) => {
const code = +node.id
const { x, y } = node.getPosition()
return {
taskCode: code,
x,
y
}
})
}
return {
getLocations,
getConnects
}
}

54
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-cell-query.ts

@ -1,54 +0,0 @@
/*
* 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 type { Ref } from 'vue'
import type { Graph } from '@antv/x6'
import { TaskType } from '../../../task/constants/task-type'
interface Options {
graph: Ref<Graph | undefined>
}
/**
* Expose some cell-related query methods and refs
* @param {Options} options
*/
export function useCellQuery(options: Options) {
const { graph } = options
/**
* Get all nodes
*/
function getNodes() {
const nodes = graph.value?.getNodes()
if (!nodes) return []
return nodes.map((node) => {
const position = node.getPosition()
const data = node.getData()
return {
code: node.id,
position: position,
name: data.taskName as string,
type: data.taskType as TaskType
}
})
}
return {
getNodes
}
}

5
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-cell-update.ts

@ -18,9 +18,9 @@
import type { Ref } from 'vue' import type { Ref } from 'vue'
import type { Graph } from '@antv/x6' import type { Graph } from '@antv/x6'
import type { TaskType } from '@/views/projects/task/constants/task-type' import type { TaskType } from '@/views/projects/task/constants/task-type'
import type { Coordinate } from './types'
import { TASK_TYPES_MAP } from '@/views/projects/task/constants/task-type' import { TASK_TYPES_MAP } from '@/views/projects/task/constants/task-type'
import { useCustomCellBuilder } from './dag-hooks' import { useCustomCellBuilder } from './dag-hooks'
import type { Coordinate } from './use-custom-cell-builder'
import utils from '@/utils' import utils from '@/utils'
interface Options { interface Options {
@ -59,13 +59,14 @@ export function useCellUpdate(options: Options) {
function addNode( function addNode(
id: string, id: string,
type: string, type: string,
name: string,
coordinate: Coordinate = { x: 100, y: 100 } coordinate: Coordinate = { x: 100, y: 100 }
) { ) {
if (!TASK_TYPES_MAP[type as TaskType]) { if (!TASK_TYPES_MAP[type as TaskType]) {
console.warn(`taskType:${type} is invalid!`) console.warn(`taskType:${type} is invalid!`)
return return
} }
const node = buildNode(id, type, '', coordinate) const node = buildNode(id, type, name, coordinate)
graph.value?.addNode(node) graph.value?.addNode(node)
} }

6
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-custom-cell-builder.ts

@ -18,9 +18,7 @@
import type { Node, Edge } from '@antv/x6' import type { Node, Edge } from '@antv/x6'
import { X6_NODE_NAME, X6_EDGE_NAME } from './dag-config' import { X6_NODE_NAME, X6_EDGE_NAME } from './dag-config'
import utils from '@/utils' import utils from '@/utils'
import { WorkflowDefinition } from './types' import { WorkflowDefinition, Coordinate } from './types'
export type Coordinate = { x: number; y: number }
export function useCustomCellBuilder() { export function useCustomCellBuilder() {
/** /**
@ -110,7 +108,7 @@ export function useCustomCellBuilder() {
tasks.forEach((task) => { tasks.forEach((task) => {
const location = locations.find((l) => l.taskCode === task.code) || {} const location = locations.find((l) => l.taskCode === task.code) || {}
const node = buildNode(task.code, task.taskType, task.name, { const node = buildNode(task.code + '', task.taskType, task.name, {
x: location.x, x: location.x,
y: location.y y: location.y
}) })

21
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-dag-drag-drop.ts

@ -19,39 +19,33 @@ import { ref } from 'vue'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import type { Graph } from '@antv/x6' import type { Graph } from '@antv/x6'
import { genTaskCodeList } from '@/service/modules/task-definition' import { genTaskCodeList } from '@/service/modules/task-definition'
import { useCellUpdate } from './dag-hooks' import { Dragged } from './types'
import { TaskType } from '@/views/projects/task/constants/task-type'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
interface Options { interface Options {
readonly: Ref<boolean> readonly: Ref<boolean>
graph: Ref<Graph | undefined> graph: Ref<Graph | undefined>
} appendTask: (code: number, type: TaskType, coor: Coordinate) => void
interface Dragged {
x: number
y: number
type: string
} }
/** /**
* Sidebar item drag && drop in canvas * Sidebar item drag && drop in canvas
*/ */
export function useDagDragAndDrop(options: Options) { export function useDagDragAndDrop(options: Options) {
const { readonly, graph } = options const { readonly, graph, appendTask } = options
const route = useRoute() const route = useRoute()
const projectCode = Number(route.params.projectCode) const projectCode = Number(route.params.projectCode)
const { addNode } = useCellUpdate({ graph })
// The element currently being dragged up // The element currently being dragged up
const dragged = ref<Dragged>({ const dragged = ref<Dragged>({
x: 0, x: 0,
y: 0, y: 0,
type: '' type: 'SHELL'
}) })
function onDragStart(e: DragEvent, type: string) { function onDragStart(e: DragEvent, type: TaskType) {
if (readonly.value) { if (readonly.value) {
e.preventDefault() e.preventDefault()
return return
@ -75,8 +69,7 @@ export function useDagDragAndDrop(options: Options) {
const genNums = 1 const genNums = 1
genTaskCodeList(genNums, projectCode).then((res) => { genTaskCodeList(genNums, projectCode).then((res) => {
const [code] = res const [code] = res
addNode(code + '', type, { x: x - eX, y: y - eY }) appendTask(code, type, { x: x - eX, y: y - eY })
// openTaskConfigModel(code, type)
}) })
} }
} }

9
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-node-search.ts

@ -17,7 +17,6 @@
import type { Graph } from '@antv/x6' import type { Graph } from '@antv/x6'
import { ref, Ref } from 'vue' import { ref, Ref } from 'vue'
import { useCellQuery } from './dag-hooks'
interface Options { interface Options {
graph: Ref<Graph | undefined> graph: Ref<Graph | undefined>
@ -40,12 +39,12 @@ export function useNodeSearch(options: Options) {
/** /**
* Search dropdown control * Search dropdown control
*/ */
const { getNodes } = useCellQuery({ graph })
const nodesDropdown = ref<{ label: string; value: string }[]>([]) const nodesDropdown = ref<{ label: string; value: string }[]>([])
const reQueryNodes = () => { const reQueryNodes = () => {
nodesDropdown.value = getNodes().map((node) => ({ const nodes = graph.value?.getNodes() || []
label: node.name, nodesDropdown.value = nodes.map((node) => ({
value: node.code label: node.getData().taskName,
value: node.id
})) }))
} }

119
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-task-edit.ts

@ -0,0 +1,119 @@
/*
* 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 { ref, onMounted } from 'vue'
import type { Ref } from 'vue'
import type { Graph } from '@antv/x6'
import type { Coordinate, NodeData } from './types'
import { TaskType } from '@/views/projects/task/constants/task-type'
import { useCellUpdate } from './dag-hooks'
interface Options {
graph: Ref<Graph | undefined>
}
/**
* Edit task configuration when dbclick
* @param {Options} options
* @returns
*/
export function useTaskEdit(options: Options) {
const { graph } = options
const { addNode, setNodeName } = useCellUpdate({ graph })
const taskDefinitions = ref<NodeData[]>([])
const currTask = ref<NodeData>({
taskType: 'SHELL',
code: 0,
name: ''
})
const taskModalVisible = ref(false)
/**
* Append a new task
*/
function appendTask(code: number, type: TaskType, coordinate: Coordinate) {
addNode(code + '', type, '', coordinate)
taskDefinitions.value.push({
code,
taskType: type,
name: ''
})
openTaskModal({ code, taskType: type, name: '' })
}
function openTaskModal(task: NodeData) {
currTask.value = task
taskModalVisible.value = true
}
/**
* The confirm event in task config modal
* @param formRef
* @param from
*/
function taskConfirm({ formRef, form }: any) {
formRef.validate((errors: any) => {
if (!errors) {
// override target config
taskDefinitions.value = taskDefinitions.value.map((task) => {
if (task.code === currTask.value?.code) {
setNodeName(task.code + '', form.name)
console.log(form)
console.log(JSON.stringify(form))
return {
code: task.code,
...form
}
}
return task
})
taskModalVisible.value = false
}
})
}
/**
* The cancel event in task config modal
*/
function taskCancel() {
taskModalVisible.value = false
}
onMounted(() => {
if (graph.value) {
graph.value.on('cell:dblclick', ({ cell }) => {
const code = Number(cell.id)
const definition = taskDefinitions.value.find((t) => t.code === code)
if (definition) {
currTask.value = definition
}
taskModalVisible.value = true
})
}
})
return {
currTask,
taskModalVisible,
taskConfirm,
taskCancel,
appendTask,
taskDefinitions
}
}

58
dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.tsx

@ -16,15 +16,71 @@
*/ */
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { useMessage } from 'naive-ui'
import Dag from '../../components/dag' import Dag from '../../components/dag'
import { useThemeStore } from '@/store/theme/theme' import { useThemeStore } from '@/store/theme/theme'
import { useRoute, useRouter } from 'vue-router'
import {
SaveForm,
TaskDefinition,
Connect,
Location
} from '../../components/dag/types'
import { createProcessDefinition } from '@/service/modules/process-definition'
import { useI18n } from 'vue-i18n'
import Styles from './index.module.scss' import Styles from './index.module.scss'
interface SaveData {
saveForm: SaveForm
taskDefinitions: TaskDefinition[]
connects: Connect[]
locations: Location[]
}
export default defineComponent({ export default defineComponent({
name: 'WorkflowDefinitionCreate', name: 'WorkflowDefinitionCreate',
setup() { setup() {
const theme = useThemeStore() const theme = useThemeStore()
const message = useMessage()
const { t } = useI18n()
const route = useRoute()
const router = useRouter()
const projectCode = Number(route.params.projectCode)
const onSave = ({
taskDefinitions,
saveForm,
connects,
locations
}: SaveData) => {
const globalParams = saveForm.globalParams.map((p) => {
return {
prop: p.key,
value: p.value,
direct: 'IN',
type: 'VARCHAR'
}
})
createProcessDefinition(
{
taskDefinitionJson: JSON.stringify(taskDefinitions),
taskRelationJson: JSON.stringify(connects),
locations: JSON.stringify(locations),
name: saveForm.name,
tenantCode: saveForm.tenantCode,
description: saveForm.description,
globalParams: JSON.stringify(globalParams),
timeout: saveForm.timeoutFlag ? saveForm.timeout : 0
},
projectCode
).then((res: any) => {
message.success(t('project.dag.success'))
router.push({ path: `/projects/${projectCode}/workflow-definition` })
})
}
return () => ( return () => (
<div <div
class={[ class={[
@ -32,7 +88,7 @@ export default defineComponent({
theme.darkTheme ? Styles['dark'] : Styles['light'] theme.darkTheme ? Styles['dark'] : Styles['light']
]} ]}
> >
<Dag /> <Dag projectCode={projectCode} onSave={onSave} />
</div> </div>
) )
} }

9
dolphinscheduler-ui-next/src/views/projects/workflow/definition/detail/index.tsx

@ -39,6 +39,8 @@ export default defineComponent({
}) })
} }
const save = () => {}
onMounted(() => { onMounted(() => {
if (!code || !projectCode) return if (!code || !projectCode) return
refresh() refresh()
@ -51,7 +53,12 @@ export default defineComponent({
theme.darkTheme ? Styles['dark'] : Styles['light'] theme.darkTheme ? Styles['dark'] : Styles['light']
]} ]}
> >
<Dag definition={definition.value} onRefresh={refresh} /> <Dag
definition={definition.value}
onRefresh={refresh}
projectCode={projectCode}
onSave={save}
/>
</div> </div>
) )
} }

Loading…
Cancel
Save