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

This commit is contained in:
wangyizhi 2022-02-13 20:16:58 +08:00 committed by GitHub
parent 9e9edbfe27
commit 4b06b76068
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 503 additions and 152 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -19,37 +19,71 @@ import { defineComponent, PropType, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Modal from '@/components/modal'
import Detail from './detail'
import type { IDataNode, ITask } from './types'
import type { NodeData } from '@/views/projects/workflow/components/dag/types'
const props = {
show: {
type: Boolean as PropType<boolean>,
default: false
},
nodeData: {
type: Object as PropType<IDataNode>,
default: {
taskType: 'SHELL'
}
},
type: {
type: String as PropType<string>,
default: ''
},
taskDefinition: {
type: Object as PropType<ITask>
type: Object as PropType<NodeData>,
default: { code: 0, taskType: 'SHELL', name: '' }
},
projectCode: {
type: Number as PropType<number>,
required: true
},
readonly: {
type: Boolean as PropType<boolean>,
default: false
}
}
const NodeDetailModal = defineComponent({
name: 'NodeDetailModal',
props,
emits: ['cancel', 'update'],
emits: ['cancel', 'submit'],
setup(props, { emit }) {
const { t } = useI18n()
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 = () => {
detailRef.value.onSubmit()
emit('submit', {
formRef: detailRef.value.formRef,
form: mapFormToTaskDefinition(detailRef.value.form)
})
}
const onCancel = () => {
emit('cancel')
@ -63,7 +97,15 @@ const NodeDetailModal = defineComponent({
}
},
render() {
const { t, show, onConfirm, onCancel } = this
const {
t,
show,
onConfirm,
onCancel,
projectCode,
taskDefinition,
readonly
} = this
return (
<Modal
show={show}
@ -72,7 +114,11 @@ const NodeDetailModal = defineComponent({
confirmLoading={false}
onCancel={onCancel}
>
<Detail ref='detailRef' taskType='SHELL' projectCode={111} />
<Detail
ref='detailRef'
taskType={taskDefinition.taskType}
projectCode={projectCode}
/>
</Modal>
)
}

View File

@ -15,7 +15,7 @@
* 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 { useTask } from './use-task'
import { useDetail } from './use-detail'
@ -40,14 +40,15 @@ const NodeDetail = defineComponent({
const { taskType, projectCode } = props
const { json, model } = useTask({ taskType, projectCode })
const { state, onSubmit } = useDetail()
const { state } = useDetail()
const jsonRef = ref(json)
const { rules, elements } = getElementByJson(jsonRef.value, model)
expose({
onSubmit: () => void onSubmit(model)
formRef: toRef(state, 'formRef'),
form: model
})
return { rules, elements, model, ...toRefs(state) }

View File

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

View File

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

View File

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

View File

@ -16,7 +16,10 @@
*/
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'
export default defineComponent({
@ -35,7 +38,7 @@ export default defineComponent({
class={Styles.draggable}
draggable='true'
onDragstart={(e) => {
context.emit('dragStart', e, task.type)
context.emit('dragStart', e, task.type as TaskType)
}}
>
<em

View File

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

View File

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

View File

@ -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
}
}

View File

@ -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
}
}

View File

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

View File

@ -18,9 +18,7 @@
import type { Node, Edge } from '@antv/x6'
import { X6_NODE_NAME, X6_EDGE_NAME } from './dag-config'
import utils from '@/utils'
import { WorkflowDefinition } from './types'
export type Coordinate = { x: number; y: number }
import { WorkflowDefinition, Coordinate } from './types'
export function useCustomCellBuilder() {
/**
@ -110,7 +108,7 @@ export function useCustomCellBuilder() {
tasks.forEach((task) => {
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,
y: location.y
})

View File

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

View File

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

View File

@ -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
}
}

View File

@ -16,15 +16,71 @@
*/
import { defineComponent } from 'vue'
import { useMessage } from 'naive-ui'
import Dag from '../../components/dag'
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'
interface SaveData {
saveForm: SaveForm
taskDefinitions: TaskDefinition[]
connects: Connect[]
locations: Location[]
}
export default defineComponent({
name: 'WorkflowDefinitionCreate',
setup() {
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 () => (
<div
class={[
@ -32,7 +88,7 @@ export default defineComponent({
theme.darkTheme ? Styles['dark'] : Styles['light']
]}
>
<Dag />
<Dag projectCode={projectCode} onSave={onSave} />
</div>
)
}

View File

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