Browse Source

[Feature][UI Next] Workflow create (#8123)

3.0.0/version-upgrade
wangyizhi 2 years ago committed by GitHub
parent
commit
45f6c4197b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      dolphinscheduler-ui-next/package.json
  2. 30
      dolphinscheduler-ui-next/pnpm-lock.yaml
  3. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/conditions.png
  4. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/conditions_hover.png
  5. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/datax.png
  6. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/datax_hover.png
  7. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/dependent.png
  8. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/dependent_hover.png
  9. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/flink.png
  10. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/flink_hover.png
  11. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/http.png
  12. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/http_hover.png
  13. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/mr.png
  14. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/mr_hover.png
  15. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/pigeon.png
  16. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/pigeon_hover.png
  17. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/procedure.png
  18. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/procedure_hover.png
  19. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/python.png
  20. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/python_hover.png
  21. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/seatunnel.png
  22. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/seatunnel_hover.png
  23. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/shell.png
  24. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/shell_hover.png
  25. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/spark.png
  26. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/spark_hover.png
  27. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/sql.png
  28. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/sql_hover.png
  29. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/sqoop.png
  30. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/sqoop_hover.png
  31. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/sub_process.png
  32. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/sub_process_hover.png
  33. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/switch.png
  34. BIN
      dolphinscheduler-ui-next/src/assets/images/task-icons/switch_hover.png
  35. 11
      dolphinscheduler-ui-next/src/locales/modules/en_US.ts
  36. 11
      dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts
  37. 16
      dolphinscheduler-ui-next/src/router/modules/projects.ts
  38. 13
      dolphinscheduler-ui-next/src/service/modules/task-definition/index.ts
  39. 4
      dolphinscheduler-ui-next/src/service/modules/task-definition/types.ts
  40. 4
      dolphinscheduler-ui-next/src/utils/index.ts
  41. 51
      dolphinscheduler-ui-next/src/utils/truncate-text.ts
  42. 27
      dolphinscheduler-ui-next/src/views/projects/index.tsx
  43. 67
      dolphinscheduler-ui-next/src/views/projects/task/config.ts
  44. 27
      dolphinscheduler-ui-next/src/views/projects/task/task-config.tsx
  45. 60
      dolphinscheduler-ui-next/src/views/projects/workflow/dag-canvas.tsx
  46. 339
      dolphinscheduler-ui-next/src/views/projects/workflow/dag-config.ts
  47. 32
      dolphinscheduler-ui-next/src/views/projects/workflow/dag-hooks.ts
  48. 68
      dolphinscheduler-ui-next/src/views/projects/workflow/dag-sidebar.tsx
  49. 180
      dolphinscheduler-ui-next/src/views/projects/workflow/dag-toolbar.tsx
  50. 217
      dolphinscheduler-ui-next/src/views/projects/workflow/dag.module.scss
  51. 67
      dolphinscheduler-ui-next/src/views/projects/workflow/dag.tsx
  52. 40
      dolphinscheduler-ui-next/src/views/projects/workflow/hook-demo.ts
  53. 70
      dolphinscheduler-ui-next/src/views/projects/workflow/use-canvas-drop.ts
  54. 177
      dolphinscheduler-ui-next/src/views/projects/workflow/use-canvas-init.ts
  55. 154
      dolphinscheduler-ui-next/src/views/projects/workflow/use-cell-active.ts
  56. 157
      dolphinscheduler-ui-next/src/views/projects/workflow/use-graph-operations.ts
  57. 60
      dolphinscheduler-ui-next/src/views/projects/workflow/use-node-search.ts
  58. 48
      dolphinscheduler-ui-next/src/views/projects/workflow/use-sidebar-drag.ts
  59. 22
      dolphinscheduler-ui-next/src/views/projects/workflow/workflow-definition-create.module.scss
  60. 38
      dolphinscheduler-ui-next/src/views/projects/workflow/workflow-definition-create.tsx
  61. 27
      dolphinscheduler-ui-next/src/views/projects/workflow/workflow-definition-details.tsx
  62. 27
      dolphinscheduler-ui-next/src/views/projects/workflow/workflow-definition-list.tsx
  63. 27
      dolphinscheduler-ui-next/src/views/projects/workflow/workflow-instance-details.tsx
  64. 27
      dolphinscheduler-ui-next/src/views/projects/workflow/workflow-instance-list.tsx
  65. 35
      dolphinscheduler-ui-next/src/views/projects/workflow/x6-style.scss

1
dolphinscheduler-ui-next/package.json

@ -10,6 +10,7 @@
"prettier": "prettier --write \"src/**/*.{vue,ts,tsx}\""
},
"dependencies": {
"@antv/x6": "^1.29.5",
"@vueuse/core": "^7.5.3",
"axios": "^0.24.0",
"date-fns": "^2.27.0",

30
dolphinscheduler-ui-next/pnpm-lock.yaml

@ -18,6 +18,7 @@
lockfileVersion: 5.3
specifiers:
'@antv/x6': ^1.29.5
'@types/node': ^16.11.19
'@types/nprogress': ^0.2.0
'@types/qs': ^6.9.7
@ -56,6 +57,7 @@ specifiers:
vue-tsc: ^0.28.10
dependencies:
'@antv/x6': 1.29.5
'@vueuse/core': 7.5.3_vue@3.2.26
axios: 0.24.0
date-fns: 2.28.0
@ -96,6 +98,18 @@ devDependencies:
vue-tsc: 0.28.10_typescript@4.5.4
packages:
/@antv/x6/1.29.5:
resolution: {integrity: sha512-U5gg40jo+UtzjdX/7QFenVZgGKOtDFDo60AMNGEvIEzGnixV+2zV0EBJ2f8We4Fp1ZVP0G2pm6uS7ajuuuLsvg==}
dependencies:
csstype: 3.0.10
jquery: 3.6.0
jquery-mousewheel: 3.1.13
lodash-es: 4.17.21
mousetrap: 1.6.5
utility-types: 3.10.0
dev: false
/@babel/code-frame/7.16.7:
resolution:
{
@ -2792,6 +2806,14 @@ packages:
engines: { node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0 }
dev: false
/jquery-mousewheel/3.1.13:
resolution: {integrity: sha1-BvAzXxbjU6aV5yBr9QUDy1I6buU=}
dev: false
/jquery/3.6.0:
resolution: {integrity: sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==}
dev: false
/js-stringify/1.0.2:
resolution: { integrity: sha1-Fzb939lyTyijaCrcYjCufk6Weds= }
dev: true
@ -3064,6 +3086,9 @@ packages:
{
integrity: sha512-FYPwxGZAeP6mRRyrr5XTGHD9gRXVjy7GUzF4IPChnyt3fS5WrNxIkS8DNujWf6EQy0Zlzpxw8oTVE+mWI2/D1Q==
}
/mousetrap/1.6.5:
resolution: {integrity: sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==}
dev: false
/ms/2.0.0:
@ -4117,6 +4142,11 @@ packages:
deprecated: Please see https://github.com/lydell/urix#deprecated
dev: true
/utility-types/3.10.0:
resolution: {integrity: sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==}
engines: {node: '>= 4'}
dev: false
/v8-compile-cache/2.3.0:
resolution:
{

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/conditions.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/conditions_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/datax.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/datax_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/dependent.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/dependent_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/flink.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/flink_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/http.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/http_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 B

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/mr.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/mr_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/pigeon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/pigeon_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/procedure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/procedure_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/python.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/python_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/seatunnel.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/seatunnel_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/shell.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/shell_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/spark.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/spark_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/sql.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/sql_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/sqoop.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/sqoop_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/sub_process.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/sub_process_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/switch.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

BIN
dolphinscheduler-ui-next/src/assets/images/task-icons/switch_hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 825 B

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

@ -286,6 +286,17 @@ const project = {
confirm: 'Confirm',
cancel: 'Cancel',
delete_confirm: 'Delete?'
},
dag: {
createWorkflow: "Create Workflow",
search: 'Search',
download_png: 'Download PNG',
fullscreen_open: 'Open Fullscreen',
fullscreen_close: 'Close Fullscreen',
workflow_version: 'Workflow Version Info',
save: 'Save',
close: 'Close',
format: 'Format'
}
}

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

@ -286,6 +286,17 @@ const project = {
confirm: '确定',
cancel: '取消',
delete_confirm: '确定删除吗?'
},
dag: {
createWorkflow: "创建工作流",
search: '搜索',
download_png: '下载工作流图片',
fullscreen_open: '全屏',
fullscreen_close: '退出全屏',
workflow_version: '工作流版本信息',
save: '保存',
close: '关闭',
format: '格式化'
}
}

16
dolphinscheduler-ui-next/src/router/modules/projects.ts

@ -46,6 +46,22 @@ export default {
title: '工作流监控',
showSide: true
}
},
{
path: '/projects/:projectCode/workflow/definitions/create',
name: 'workflow-definition-create',
component: components['workflow-definition-create'],
meta: {
title: '创建工作流定义',
},
},
{
path: '/projects/:projectCode/workflow/definitions/:code',
name: 'workflow-definition-details',
component: components['workflow-definition-details'],
meta: {
title: '工作流定义详情',
},
}
]
}

13
dolphinscheduler-ui-next/src/service/modules/task-definition/index.ts

@ -21,7 +21,6 @@ import {
ProjectCodeReq,
TaskDefinitionListReq,
TaskDefinitionJsonReq,
GenNumReq,
CodeReq,
TaskDefinitionJsonObjReq,
ReleaseStateReq,
@ -51,13 +50,15 @@ export function save(
}
export function genTaskCodeList(
params: GenNumReq,
projectCode: ProjectCodeReq
): any {
return axios({
num: number,
projectCode: number
) {
return axios.request<unknown, number[]>({
url: `/projects/${projectCode}/task-definition/gen-task-codes`,
method: 'get',
params
params: {
genNum: num
}
})
}

4
dolphinscheduler-ui-next/src/service/modules/task-definition/types.ts

@ -37,10 +37,6 @@ interface TaskDefinitionJsonReq {
taskDefinitionJson: string
}
interface GenNumReq {
genNum: number
}
interface CodeReq {
code: number
}

4
dolphinscheduler-ui-next/src/utils/index.ts

@ -17,10 +17,12 @@
import mapping from './mapping'
import regex from './regex'
import truncateText from './truncate-text'
const utils = {
mapping,
regex
regex,
truncateText,
}
export default utils

51
dolphinscheduler-ui-next/src/utils/truncate-text.ts

@ -0,0 +1,51 @@
/*
* 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.
*/
/**
* truncateText('ALongText', 4) => 'ALon...'
* @param {number} limit
* @param {string} text
* Each Chinese character is equal to two chars
*/
export default function truncateText(text: string, n: number) {
const exp = /[\u4E00-\u9FA5]/
let res = ''
let len = text.length
let chinese = text.match(new RegExp(exp, 'g'))
if (chinese) {
len += chinese.length
}
if (len > n) {
let i = 0
let acc = 0
while (true) {
let char = text[i]
if (exp.test(char)) {
acc += 2
} else {
acc++
}
if (acc > n) break
res += char
i++
}
res += '...'
} else {
res = text
}
return res
}

27
dolphinscheduler-ui-next/src/views/projects/index.tsx

@ -0,0 +1,27 @@
/*
* 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 } from 'vue'
export default defineComponent({
name: "Projects",
setup() {
return () => (
<div>Projects</div>
)
}
})

67
dolphinscheduler-ui-next/src/views/projects/task/config.ts

@ -0,0 +1,67 @@
/*
* 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.
*/
export const ALL_TASK_TYPES:any = {
SHELL: {
alias: 'SHELL',
},
SUB_PROCESS: {
alias: 'SUB_PROCESS',
},
PROCEDURE: {
alias: 'PROCEDURE',
},
SQL: {
alias: 'SQL',
},
SPARK: {
alias: 'SPARK',
},
FLINK: {
alias: 'FLINK',
},
MR: {
alias: 'MapReduce',
},
PYTHON: {
alias: 'PYTHON',
},
DEPENDENT: {
alias: 'DEPENDENT',
},
HTTP: {
alias: 'HTTP',
},
DATAX: {
alias: 'DataX',
},
PIGEON: {
alias: 'PIGEON',
},
SQOOP: {
alias: 'SQOOP',
},
CONDITIONS: {
alias: 'CONDITIONS',
},
SWITCH: {
alias: 'SWITCH',
},
SEATUNNEL: {
alias: 'WATERDROP',
}
};

27
dolphinscheduler-ui-next/src/views/projects/task/task-config.tsx

@ -0,0 +1,27 @@
/*
* 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 } from 'vue'
export default defineComponent({
name: "TaskConfigModal",
setup() {
return () => (
<div>TaskConfigModal</div>
)
}
})

60
dolphinscheduler-ui-next/src/views/projects/workflow/dag-canvas.tsx

@ -0,0 +1,60 @@
/*
* 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, ref, inject } from 'vue'
import Styles from './dag.module.scss'
import type { PropType, Ref } from 'vue'
import type { Dragged } from './dag'
import { useCanvasInit, useGraphOperations, useCellActive, useCanvasDrop } from './dag-hooks';
import { useRoute } from 'vue-router'
const props = {
dragged: {
type: Object as PropType<Ref<Dragged>>,
default: ref({
x: 0,
y: 0,
type: ''
}),
}
}
export default defineComponent({
name: "workflow-dag-canvas",
props,
setup(props, context) {
const readonly = inject('readonly', ref(false));
const graph = inject('graph', ref());
const route = useRoute();
const projectCode = route.params.projectCode as string;
const { paper, minimap, container } = useCanvasInit({ readonly, graph });
// Change the style on cell hover and select
useCellActive({ graph });
// Drop sidebar item in canvas
const { onDrop, onDragenter, onDragover, onDragleave } = useCanvasDrop({ readonly, dragged: props.dragged, graph, container, projectCode });
return () => (
<div ref={container} class={Styles.canvas} onDrop={onDrop} onDragenter={onDragenter} onDragover={onDragover} onDragleave={onDragleave}>
<div ref={paper} class={Styles.paper}></div>
<div ref={minimap} class={Styles.minimap}></div>
</div>
);
}
})

339
dolphinscheduler-ui-next/src/views/projects/workflow/dag-config.ts

@ -0,0 +1,339 @@
/*
* 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.
*/
export const X6_NODE_NAME = 'dag-task'
export const X6_EDGE_NAME = 'dag-edge'
export const X6_PORT_OUT_NAME = 'dag-port-out'
const EDGE_COLOR = '#999999'
const BG_BLUE = '#DFE9F7'
const BG_WHITE = '#FFFFFF'
const NODE_BORDER = '#CCCCCC'
const TITLE = '#333333'
const STROKE_BLUE = '#288FFF'
const NODE_SHADOW = 'drop-shadow(3px 3px 4px rgba(0, 0, 0, 0.2))'
const EDGE_SHADOW = 'drop-shadow(3px 3px 2px rgba(0, 0, 0, 0.2))'
export const PORT = {
groups: {
[X6_PORT_OUT_NAME]: {
position: {
name: 'absolute',
args: {
x: 200,
y: 24
}
},
markup: [
{
tagName: 'g',
selector: 'body',
children: [
{
tagName: 'circle',
selector: 'circle-outer'
},
{
tagName: 'text',
selector: 'plus-text'
},
{
tagName: 'circle',
selector: 'circle-inner'
}
]
}
],
attrs: {
body: {
magnet: true
},
'plus-text': {
fontSize: 12,
fill: NODE_BORDER,
text: '+',
textAnchor: 'middle',
x: 0,
y: 3
},
'circle-outer': {
stroke: NODE_BORDER,
strokeWidth: 1,
r: 6,
fill: BG_WHITE
},
'circle-inner': {
r: 4,
fill: 'transparent'
}
}
}
}
}
export const PORT_HOVER = {
groups: {
[X6_PORT_OUT_NAME]: {
attrs: {
'circle-outer': {
stroke: STROKE_BLUE,
fill: BG_BLUE,
r: 8
},
'circle-inner': {
fill: STROKE_BLUE,
r: 6
}
}
}
}
}
export const PORT_SELECTED = {
groups: {
[X6_PORT_OUT_NAME]: {
attrs: {
'plus-text': {
fill: STROKE_BLUE
},
'circle-outer': {
stroke: STROKE_BLUE,
fill: BG_WHITE
}
}
}
}
}
export const NODE_STATUS_MARKUP = [{
tagName: 'foreignObject',
selector: 'fo',
children: [
{
tagName: 'body',
selector: 'fo-body',
ns: 'http://www.w3.org/1999/xhtml',
children: [{
tagName: 'div',
selector: 'status'
}]
}
]
}]
export const NODE = {
width: 220,
height: 48,
markup: [
{
tagName: 'rect',
selector: 'body',
className: 'dag-task-body'
},
{
tagName: 'image',
selector: 'image'
},
{
tagName: 'text',
selector: 'title'
}
],
attrs: {
body: {
refWidth: '100%',
refHeight: '100%',
rx: 6,
ry: 6,
pointerEvents: 'visiblePainted',
fill: BG_WHITE,
stroke: NODE_BORDER,
strokeWidth: 1,
strokeDasharray: 'none',
filter: 'none'
},
image: {
width: 30,
height: 30,
refX: 12,
refY: 9
},
title: {
refX: 45,
refY: 18,
fontFamily: 'Microsoft Yahei',
fontSize: 12,
fontWeight: 'bold',
fill: TITLE,
strokeWidth: 0
},
fo: {
refX: '46%',
refY: -25,
width: 18,
height: 18
}
},
ports: {
...PORT,
items: [
{
id: X6_PORT_OUT_NAME,
group: X6_PORT_OUT_NAME
}
]
}
}
export const NODE_HOVER = {
attrs: {
body: {
fill: BG_BLUE,
stroke: STROKE_BLUE,
strokeDasharray: '5,2'
},
title: {
fill: STROKE_BLUE
}
}
}
export const NODE_SELECTED = {
attrs: {
body: {
filter: NODE_SHADOW,
fill: BG_WHITE,
stroke: STROKE_BLUE,
strokeDasharray: '5,2',
strokeWidth: '1.5'
},
title: {
fill: STROKE_BLUE
}
}
}
export const EDGE = {
attrs: {
line: {
stroke: EDGE_COLOR,
strokeWidth: 1,
targetMarker: {
tagName: 'path',
fill: EDGE_COLOR,
strokeWidth: 0,
d: 'M 6 -3 0 0 6 3 Z'
},
filter: 'none'
}
},
connector: {
name: 'rounded'
},
router: {
name: 'manhattan',
args: {
endDirections: ['top', 'bottom', 'left']
}
},
defaultLabel: {
markup: [
{
tagName: 'rect',
selector: 'body'
},
{
tagName: 'text',
selector: 'label'
}
],
attrs: {
label: {
fill: EDGE_COLOR,
fontSize: 14,
textAnchor: 'middle',
textVerticalAnchor: 'middle',
pointerEvents: 'none'
},
body: {
ref: 'label',
fill: BG_WHITE,
stroke: EDGE_COLOR,
strokeWidth: 1,
rx: 4,
ry: 4,
refWidth: '140%',
refHeight: '140%',
refX: '-20%',
refY: '-20%'
}
},
position: {
distance: 0.5,
options: {
absoluteDistance: true,
reverseDistance: true
}
}
}
}
export const EDGE_HOVER = {
attrs: {
line: {
stroke: STROKE_BLUE,
targetMarker: {
fill: STROKE_BLUE
}
}
},
defaultLabel: {
attrs: {
label: {
fill: STROKE_BLUE
},
body: {
fill: BG_WHITE,
stroke: STROKE_BLUE
}
}
}
}
export const EDGE_SELECTED = {
attrs: {
line: {
stroke: STROKE_BLUE,
targetMarker: {
fill: STROKE_BLUE
},
strokeWidth: 2,
filter: EDGE_SHADOW
}
},
defaultLabel: {
attrs: {
label: {
fill: STROKE_BLUE
},
body: {
fill: BG_WHITE,
stroke: STROKE_BLUE
}
}
}
}

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

@ -0,0 +1,32 @@
/*
* 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 { useCanvasInit } from './use-canvas-init';
import { useGraphOperations } from './use-graph-operations';
import { useCellActive } from './use-cell-active';
import { useSidebarDrag } from './use-sidebar-drag';
import { useCanvasDrop } from './use-canvas-drop';
import { useNodeSearch } from './use-node-search';
export {
useCanvasInit,
useGraphOperations,
useCellActive,
useSidebarDrag,
useCanvasDrop,
useNodeSearch,
}

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

@ -0,0 +1,68 @@
/*
* 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 { PropType, Ref } from 'vue';
import type { Dragged } from './dag';
import { defineComponent, ref, inject } from 'vue'
import { ALL_TASK_TYPES } from '../task/config';
import { useSidebarDrag } from './dag-hooks';
import Styles from './dag.module.scss';
const props = {
dragged: {
type: Object as PropType<Ref<Dragged>>,
default: ref({
x: 0,
y: 0,
type: ''
}),
},
}
export default defineComponent({
name: "workflow-dag-sidebar",
props,
setup(props) {
const readonly = inject('readonly', ref(false))
const dragged = props.dragged;
const { onDragStart } = useSidebarDrag({
readonly,
dragged
});
const allTaskTypes = Object.keys(ALL_TASK_TYPES).map(type => ({
type,
...ALL_TASK_TYPES[type]
}));
return () => (
<div class={Styles.sidebar}>
{
allTaskTypes.map(task => (
<div
class={Styles.draggable}
draggable="true"
onDragstart={(e) => onDragStart(e, task.type)}
>
<em class={`${Styles['sidebar-icon']} ${Styles['icon-' + task.type.toLocaleLowerCase()]}`}></em>
<span>{task.alias}</span>
</div>
))
}
</div>
)
}
})

180
dolphinscheduler-ui-next/src/views/projects/workflow/dag-toolbar.tsx

@ -0,0 +1,180 @@
/*
* 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, ref, inject } from 'vue'
import { useI18n } from 'vue-i18n'
import Styles from './dag.module.scss'
import { NTooltip, NIcon, NButton, NSelect } from 'naive-ui';
import { SearchOutlined, DownloadOutlined, FullscreenOutlined, FullscreenExitOutlined, InfoCircleOutlined, FormatPainterOutlined } from '@vicons/antd';
import { useNodeSearch } from './dag-hooks';
import { DataUri } from '@antv/x6'
import { useFullscreen } from '@vueuse/core';
import { useRouter } from 'vue-router';
export default defineComponent({
name: "workflow-dag-toolbar",
setup(props, context) {
const { t } = useI18n();
const graph = inject('graph', ref());
const router = useRouter();
/**
* Node search and navigate
*/
const {
searchNode,
getAllNodes,
allNodes,
toggleSearchInput,
searchInputVisible
} = useNodeSearch({ graph });
/**
* Download Workflow Image
* @param {string} fileName
* @param {string} bgColor
*/
const downloadPNG = (options = { fileName: 'dag', bgColor: '#f2f3f7' }) => {
const { fileName, bgColor } = options;
graph.value?.toPNG(
(dataUri: string) => {
DataUri.downloadDataUri(dataUri, `${fileName}.png`)
},
{
padding: {
top: 50,
right: 50,
bottom: 50,
left: 50
},
backgroundColor: bgColor
}
)
}
/**
* Toggle fullscreen
*/
const { isFullscreen, toggle } = useFullscreen();
/**
* Open workflow version modal
*/
const openVersionModal = () => {
//TODO, same as the version popup in the workflow list page
}
/**
* Open DAG format modal
*/
const openDagFormatModal = () => {
}
const onClose = () => {
router.go(-1)
}
return () => (
<div class={Styles.toolbar}>
<span class={Styles['workflow-name']}>{t("project.dag.createWorkflow")}</span>
<div class={Styles['toolbar-right-part']}>
{/* Search node */}
<NTooltip v-slots={{
trigger: () => (
<NButton class={Styles['toolbar-right-item']} strong secondary circle type="info" onClick={toggleSearchInput} v-slots={{
icon: () => (
<NIcon>
<SearchOutlined />
</NIcon>
)
}} />
),
default: () => t('project.dag.search')
}}>
</NTooltip>
<div
class={`${Styles['toolbar-right-item']} ${Styles['node-selector']} ${searchInputVisible.value ? Styles['visible'] : ''}`}
>
<NSelect size="small" options={allNodes.value} onFocus={getAllNodes} onUpdateValue={searchNode} filterable />
</div>
{/* Download workflow PNG */}
<NTooltip v-slots={{
trigger: () => (
<NButton class={Styles['toolbar-right-item']} strong secondary circle type="info" onClick={() => downloadPNG()} v-slots={{
icon: () => (
<NIcon>
<DownloadOutlined />
</NIcon>
)
}} />
),
default: () => t('project.dag.download_png')
}}>
</NTooltip>
{/* Toggle fullscreen */}
<NTooltip v-slots={{
trigger: () => (
<NButton class={Styles['toolbar-right-item']} strong secondary circle type="info" onClick={toggle} v-slots={{
icon: () => (
<NIcon>
{isFullscreen.value ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
</NIcon>
)
}} />
),
default: () => isFullscreen.value ? t('project.dag.fullscreen_close') : t('project.dag.fullscreen_open')
}}>
</NTooltip>
{/* DAG Format */}
<NTooltip v-slots={{
trigger: () => (
<NButton class={Styles['toolbar-right-item']} strong secondary circle type="info" onClick={openDagFormatModal} v-slots={{
icon: () => (
<NIcon>
<FormatPainterOutlined />
</NIcon>
)
}} />
),
default: () => t('project.dag.format')
}}>
</NTooltip>
{/* Version info */}
<NTooltip v-slots={{
trigger: () => (
<NButton class={Styles['toolbar-right-item']} strong secondary circle type="info" onClick={openVersionModal} v-slots={{
icon: () => (
<NIcon>
<InfoCircleOutlined />
</NIcon>
)
}} />
),
default: () => t('project.dag.workflow_version')
}}>
</NTooltip>
{/* Save workflow */}
<NButton class={Styles['toolbar-right-item']} type="info" secondary round>{t('project.dag.save')}</NButton>
{/* Return to previous page */}
<NButton secondary round onClick={onClose}>{t('project.dag.close')}</NButton>
</div>
</div>
)
}
})

217
dolphinscheduler-ui-next/src/views/projects/workflow/dag.module.scss

@ -0,0 +1,217 @@
/*
* 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.
*/
$blue: #288fff;
$blueBg: rgba(40, 143, 255, 0.1);
$toolbarHeight: 50px;
.dag {
height: 100%;
}
.content {
display: flex;
height: calc(100% - $toolbarHeight - 20px);
margin-top: 20px;
}
.toolbar {
height: $toolbarHeight;
display: flex;
align-items: center;
padding: 0 20px;
border: 1px solid var(--n-border-color);
border-radius: 4px;
justify-content: space-between;
}
.canvas {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
display: flex;
}
.paper {
width: 100%;
height: 100%;
}
.sidebar {
width: 190px;
height: 100%;
margin-right: 20px;
}
.workflow-name {
font-size: 14px;
}
.draggable {
display: flex;
width: 100%;
height: 32px;
margin-bottom: 10px;
align-items: center;
border: 1px solid var(--n-border-color);
padding: 0 10px;
border-radius: 4px;
transform: translate(0, 0);
box-sizing: border-box;
cursor: move;
font-size: 12px;
.sidebar-icon {
display: block;
width: 18px;
height: 18px;
background-size: 100% 100%;
margin-right: 10px;
&.icon-shell {
background-image: url("../../../assets/images/task-icons/shell.png");
}
&.icon-sub_process {
background-image: url("../../../assets/images/task-icons/sub_process.png");
}
&.icon-procedure {
background-image: url("../../../assets/images/task-icons/procedure.png");
}
&.icon-sql {
background-image: url("../../../assets/images/task-icons/sql.png");
}
&.icon-flink {
background-image: url("../../../assets/images/task-icons/flink.png");
}
&.icon-mr {
background-image: url("../../../assets/images/task-icons/mr.png");
}
&.icon-python {
background-image: url("../../../assets/images/task-icons/python.png");
}
&.icon-dependent {
background-image: url("../../../assets/images/task-icons/dependent.png");
}
&.icon-http {
background-image: url("../../../assets/images/task-icons/http.png");
}
&.icon-datax {
background-image: url("../../../assets/images/task-icons/datax.png");
}
&.icon-pigeon {
background-image: url("../../../assets/images/task-icons/pigeon.png");
}
&.icon-sqoop {
background-image: url("../../../assets/images/task-icons/sqoop.png");
}
&.icon-conditions {
background-image: url("../../../assets/images/task-icons/conditions.png");
}
&.icon-seatunnel {
background-image: url("../../../assets/images/task-icons/seatunnel.png");
}
&.icon-spark {
background-image: url("../../../assets/images/task-icons/spark.png");
}
&.icon-switch {
background-image: url("../../../assets/images/task-icons/switch.png");
}
}
&:hover {
color: $blue;
border: 1px dashed $blue;
background-color: $blueBg;
.sidebar-icon {
&.icon-shell {
background-image: url("../../../assets/images/task-icons/shell_hover.png");
}
&.icon-sub_process {
background-image: url("../../../assets/images/task-icons/sub_process_hover.png");
}
&.icon-procedure {
background-image: url("../../../assets/images/task-icons/procedure_hover.png");
}
&.icon-sql {
background-image: url("../../../assets/images/task-icons/sql_hover.png");
}
&.icon-flink {
background-image: url("../../../assets/images/task-icons/flink_hover.png");
}
&.icon-mr {
background-image: url("../../../assets/images/task-icons/mr_hover.png");
}
&.icon-python {
background-image: url("../../../assets/images/task-icons/python_hover.png");
}
&.icon-dependent {
background-image: url("../../../assets/images/task-icons/dependent_hover.png");
}
&.icon-http {
background-image: url("../../../assets/images/task-icons/http_hover.png");
}
&.icon-datax {
background-image: url("../../../assets/images/task-icons/datax_hover.png");
}
&.icon-pigeon {
background-image: url("../../../assets/images/task-icons/pigeon_hover.png");
}
&.icon-sqoop {
background-image: url("../../../assets/images/task-icons/sqoop_hover.png");
}
&.icon-conditions {
background-image: url("../../../assets/images/task-icons/conditions_hover.png");
}
&.icon-seatunnel {
background-image: url("../../../assets/images/task-icons/seatunnel_hover.png");
}
&.icon-spark {
background-image: url("../../../assets/images/task-icons/spark_hover.png");
}
&.icon-switch {
background-image: url("../../../assets/images/task-icons/switch_hover.png");
}
}
}
}
.minimap {
position: absolute;
right: 0px;
bottom: 0px;
border: dashed 1px #e4e4e4;
z-index: 9;
}
.toolbar-right-part {
display: flex;
align-items: center;
.toolbar-right-item {
margin-right: 10px;
}
.node-selector {
width: 0;
overflow: hidden;
transition: all 0.5s;
margin-right: 0;
&.visible {
width: 200px;
margin-right: 10px;
}
}
}

67
dolphinscheduler-ui-next/src/views/projects/workflow/dag.tsx

@ -0,0 +1,67 @@
/*
* 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 { Graph } from '@antv/x6';
import { defineComponent, ref, provide } from 'vue'
import DagToolbar from './dag-toolbar';
import DagCanvas from './dag-canvas';
import DagSidebar from './dag-sidebar';
import Styles from './dag.module.scss';
import "./x6-style.scss";
export interface Dragged {
x: number;
y: number;
type: string;
}
export default defineComponent({
name: "workflow-dag",
setup(props, context) {
// Whether the graph can be operated
const readonly = ref(false);
provide('readonly', readonly);
const graph = ref<Graph>();
provide('graph', graph);
// The sidebar slots
const toolbarSlots = {
left: context.slots.toolbarLeft,
right: context.slots.toolbarRight
}
// The element currently being dragged up
const dragged = ref<Dragged>({
x: 0,
y: 0,
type: ''
});
return () => (
<div class={Styles.dag}>
<DagToolbar v-slots={toolbarSlots} />
<div class={Styles.content}>
<DagSidebar dragged={dragged} />
<DagCanvas dragged={dragged} />
</div>
</div>
)
}
})

40
dolphinscheduler-ui-next/src/views/projects/workflow/hook-demo.ts

@ -0,0 +1,40 @@
/*
* 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, Ref, onUnmounted } from 'vue'
interface Options {
// readonly: Ref<boolean>;
// canvas: Ref<HTMLElement | undefined>;
}
/**
* Canvas Init
* 1. Bind the graph to the dom
* 2. Redraw when the page is resized
* 3. Register custom graphics
*/
export function useCanvasInit(options: Options) {
// Whether the graph can be operated
const { } = options;
return {
}
}

70
dolphinscheduler-ui-next/src/views/projects/workflow/use-canvas-drop.ts

@ -0,0 +1,70 @@
/*
* 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 type { Dragged } from './dag'
import { genTaskCodeList } from '@/service/modules/task-definition';
import { useGraphOperations } from './dag-hooks';
interface Options {
readonly: Ref<boolean>;
graph: Ref<Graph | undefined>;
container: Ref<HTMLElement | undefined>;
dragged: Ref<Dragged>;
projectCode: string;
}
/**
* Drop sidebar item in canvas
*/
export function useCanvasDrop(options: Options) {
const { readonly, graph, container, dragged, projectCode } = options;
const { addNode } = useGraphOperations({ graph });
const onDrop = (e: DragEvent) => {
e.stopPropagation();
e.preventDefault();
if (readonly.value) {
return;
}
if (dragged.value && graph.value && container.value && projectCode) {
const { type, x: eX, y: eY } = dragged.value;
const { x, y } = graph.value.clientToLocal(e.clientX, e.clientY);
const genNums = 1;
genTaskCodeList(genNums, Number(projectCode))
.then((res) => {
const [code] = res
addNode(code + '', type, { x: x - eX, y: y - eY })
// openTaskConfigModel(code, type)
})
}
}
const preventDefault = (e: DragEvent) => {
e.preventDefault();
}
return {
onDrop,
onDragenter: preventDefault,
onDragover: preventDefault,
onDragleave: preventDefault,
}
}

177
dolphinscheduler-ui-next/src/views/projects/workflow/use-canvas-init.ts

@ -0,0 +1,177 @@
/*
* 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 } from '@antv/x6';
import { ref, onMounted, Ref, onUnmounted } from 'vue'
import { Graph } from '@antv/x6'
import {
NODE,
EDGE,
X6_NODE_NAME,
X6_EDGE_NAME,
} from './dag-config'
import { debounce } from 'lodash';
interface Options {
readonly: Ref<boolean>;
graph: Ref<Graph | undefined>;
}
/**
* Canvas Init
* 1. Bind the graph to the dom
* 2. Redraw when the page is resized
* 3. Register custom graphics
*/
export function useCanvasInit(options: Options) {
// Whether the graph can be operated
const { readonly, graph } = options;
const paper = ref<HTMLElement>(); // The graph mount HTMLElement
const minimap = ref<HTMLElement>(); // The minimap mount HTMLElement
const container = ref<HTMLElement>(); // The container of paper and minimap
/**
* Graph Init, bind graph to the dom
*/
function graphInit() {
return new Graph({
container: paper.value,
selecting: {
enabled: true,
multiple: true,
rubberband: true,
rubberEdge: true,
movable: true,
showNodeSelectionBox: false
},
scaling: {
min: 0.2,
max: 2
},
mousewheel: {
enabled: true,
modifiers: ['ctrl', 'meta']
},
scroller: true,
grid: {
size: 10,
visible: true
},
snapline: true,
minimap: {
enabled: true,
container: minimap.value,
scalable: false,
width: 200,
height: 120
},
interacting: {
edgeLabelMovable: false,
nodeMovable: !readonly.value,
magnetConnectable: !readonly.value
},
connecting: {
// Whether multiple edges can be created between the same start node and end
allowMulti: false,
// Whether a point is allowed to connect to a blank position on the canvas
allowBlank: false,
// The start node and the end node are the same node
allowLoop: false,
// Whether an edge is allowed to link to another edge
allowEdge: false,
// Whether edges are allowed to link to nodes
allowNode: true,
// Whether to allow edge links to ports
allowPort: false,
// Whether all available ports or nodes are highlighted when you drag the edge
highlight: true,
createEdge() {
return graph.value?.createEdge({ shape: X6_EDGE_NAME })
}
},
highlighting: {
nodeAvailable: {
name: 'className',
args: {
className: 'available'
}
},
magnetAvailable: {
name: 'className',
args: {
className: 'available'
}
},
magnetAdsorbed: {
name: 'className',
args: {
className: 'adsorbed'
}
}
}
})
}
onMounted(() => {
graph.value = graphInit();
// Make sure the edge starts with node, not port
graph.value.on('edge:connected', ({ isNew, edge }) => {
if (isNew) {
const sourceNode = edge.getSourceNode() as Node
edge.setSource(sourceNode)
}
})
})
/**
* Redraw when the page is resized
*/
const paperResize = debounce(() => {
if (!container.value) return;
const w = container.value.offsetWidth
const h = container.value.offsetHeight
graph.value?.resize(w, h);
}, 200)
onMounted(() => {
window.addEventListener('resize', paperResize)
})
onUnmounted(() => {
window.removeEventListener('resize', paperResize)
})
/**
* Register custom cells
*/
function registerCustomCells() {
Graph.unregisterNode(X6_NODE_NAME)
Graph.unregisterEdge(X6_EDGE_NAME)
Graph.registerNode(X6_NODE_NAME, { ...NODE })
Graph.registerEdge(X6_EDGE_NAME, { ...EDGE })
}
onMounted(() => {
registerCustomCells()
})
return {
graph,
paper,
minimap,
container
}
}

154
dolphinscheduler-ui-next/src/views/projects/workflow/use-cell-active.ts

@ -0,0 +1,154 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Ref } from 'vue'
import { onMounted, ref } from 'vue';
import type { Node, Graph, Edge, Cell } from '@antv/x6'
import _ from 'lodash';
import {
X6_PORT_OUT_NAME,
PORT_HOVER,
PORT_SELECTED,
PORT,
NODE,
NODE_HOVER,
NODE_SELECTED,
EDGE,
EDGE_SELECTED,
EDGE_HOVER
} from './dag-config';
interface Options {
graph: Ref<Graph | undefined>
}
/**
* Change the style on cell hover and select
*/
export function useCellActive(options: Options) {
const { graph } = options;
const hoverCell = ref();
const isStatusIcon = (tagName: string) => {
if (!tagName) return false;
return tagName.toLocaleLowerCase() === 'em' || tagName.toLocaleLowerCase() === 'body'
}
function setEdgeStyle(edge: Edge) {
const isHover = edge === hoverCell.value;
const isSelected = graph.value?.isSelected(edge)
// TODO
// const labelName = this.getEdgeLabelName ? this.getEdgeLabelName(edge) : ''
let edgeProps = null
if (isHover) {
edgeProps = _.merge(_.cloneDeep(EDGE), EDGE_HOVER)
} else if (isSelected) {
edgeProps = _.merge(_.cloneDeep(EDGE), EDGE_SELECTED)
} else {
edgeProps = _.cloneDeep(EDGE)
}
edge.setAttrs(edgeProps.attrs)
edge.setLabels([
{
..._.merge(
{
attrs: _.cloneDeep(edgeProps.defaultLabel.attrs)
},
// {
// attrs: { label: { text: labelName } }
// }
)
}
])
}
function setNodeStyle(node: Node) {
const isHover = node === hoverCell.value
const isSelected = graph.value?.isSelected(node)
const portHover = _.cloneDeep(PORT_HOVER.groups[X6_PORT_OUT_NAME].attrs)
const portSelected = _.cloneDeep(PORT_SELECTED.groups[X6_PORT_OUT_NAME].attrs)
const portDefault = _.cloneDeep(PORT.groups[X6_PORT_OUT_NAME].attrs)
const nodeHover = _.merge(_.cloneDeep(NODE.attrs), NODE_HOVER.attrs)
const nodeSelected = _.merge(_.cloneDeep(NODE.attrs), NODE_SELECTED.attrs)
let img = null
let nodeAttrs = null
let portAttrs = null
if (isHover || isSelected) {
img = `/src/assets/images/task-icons/${node.data.taskType.toLocaleLowerCase()}_hover.png`
if (isHover) {
nodeAttrs = nodeHover
portAttrs = _.merge(portDefault, portHover)
} else {
nodeAttrs = nodeSelected
portAttrs = _.merge(portDefault, portSelected)
}
} else {
img = `/src/assets/images/task-icons/${node.data.taskType.toLocaleLowerCase()}.png`
nodeAttrs = NODE.attrs
portAttrs = portDefault
}
node.setAttrByPath('image/xlink:href', img)
node.setAttrs(nodeAttrs)
node.setPortProp(
X6_PORT_OUT_NAME,
'attrs',
portAttrs
)
}
function updateCellStyle(cell: Cell) {
if (cell.isEdge()) {
setEdgeStyle(cell)
} else if (cell.isNode()) {
setNodeStyle(cell)
}
}
onMounted(() => {
if (graph.value) {
// hover
graph.value.on('cell:mouseenter', (data) => {
const { cell, e } = data
if (!isStatusIcon(e.target.tagName)) {
hoverCell.value = cell
updateCellStyle(cell)
}
})
graph.value.on('cell:mouseleave', ({ cell }) => {
hoverCell.value = undefined
updateCellStyle(cell)
})
// select
graph.value.on('cell:selected', ({ cell }) => {
updateCellStyle(cell)
})
graph.value.on('cell:unselected', ({ cell }) => {
updateCellStyle(cell)
})
}
})
return {
hoverCell
}
}

157
dolphinscheduler-ui-next/src/views/projects/workflow/use-graph-operations.ts

@ -0,0 +1,157 @@
/*
* 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 { Node, Graph, Edge } from '@antv/x6'
import {
X6_NODE_NAME,
X6_EDGE_NAME,
} from './dag-config'
import { ALL_TASK_TYPES } from '../task/config';
import utils from '@/utils';
interface Options {
graph: Ref<Graph | undefined>
}
type Coordinate = { x: number; y: number; }
/**
* Expose some graph operation methods
* @param {Options} options
*/
export function useGraphOperations(options: Options) {
const { graph } = options;
/**
* Build edge metadata
* @param {string} sourceId
* @param {string} targetId
* @param {string} label
*/
function buildEdgeMetadata(sourceId: string, targetId: string, label: string = ''): Edge.Metadata {
return {
shape: X6_EDGE_NAME,
source: {
cell: sourceId
},
target: {
cell: targetId
},
labels: label ? [label] : undefined
}
}
/**
* Build node metadata
* @param {string} id
* @param {string} taskType
* @param {Coordinate} coordinate Default is { x: 100, y: 100 }
*/
function buildNodeMetadata(id: string, type: string, taskName: string, coordinate: Coordinate = { x: 100, y: 100 }): Node.Metadata {
const truncation = taskName ? utils.truncateText(taskName, 18) : id;
return {
id: id,
shape: X6_NODE_NAME,
x: coordinate.x,
y: coordinate.y,
data: {
taskType: type,
taskName: taskName || id
},
attrs: {
image: {
// Use href instead of xlink:href, you may lose the icon when downloadPNG
'xlink:href': `/src/assets/images/task-icons/${type.toLocaleLowerCase()}.png`
},
title: {
text: truncation
}
}
}
}
/**
* Add a node to the graph
* @param {string} id
* @param {string} taskType
* @param {Coordinate} coordinate Default is { x: 100, y: 100 }
*/
function addNode(id: string, type: string, coordinate: Coordinate = { x: 100, y: 100 }) {
if (!ALL_TASK_TYPES[type]) {
console.warn(`taskType:${type} is invalid!`)
return
}
const node = buildNodeMetadata(id, type, '', coordinate)
graph.value?.addNode(node)
}
/**
* Set node name by id
* @param {string} id
* @param {string} name
*/
function setNodeName(id: string, newName: string) {
const node = graph.value?.getCellById(id)
if (node) {
const truncation = utils.truncateText(newName, 18)
node.attr('title/text', truncation)
node.setData({ taskName: newName })
}
}
/**
* Get 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,
type: data.taskType
}
})
}
/**
* Navigate to cell
* @param {string} code
*/
function navigateTo(code: string) {
if (!graph.value) return;
const cell = graph.value.getCellById(code)
graph.value.scrollToCell(cell, { animation: { duration: 600 } })
graph.value.cleanSelection()
graph.value.select(cell)
};
return {
buildEdgeMetadata,
buildNodeMetadata,
addNode,
setNodeName,
getNodes,
navigateTo,
}
}

60
dolphinscheduler-ui-next/src/views/projects/workflow/use-node-search.ts

@ -0,0 +1,60 @@
/*
* 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 { Graph } from '@antv/x6';
import { ref, Ref } from 'vue'
import { useGraphOperations } from './dag-hooks';
interface Options {
graph: Ref<Graph | undefined>;
}
/**
* Node search and navigate
*/
export function useNodeSearch(options: Options) {
const { graph } = options;
const searchInputVisible = ref(false);
const allNodes = ref<any>([]);
const toggleSearchInput = () => {
searchInputVisible.value = !searchInputVisible.value;
}
const { getNodes, navigateTo } = useGraphOperations({ graph });
const searchNode = (val: string) => {
navigateTo(val)
}
const getAllNodes = () => {
const nodes = getNodes();
allNodes.value = nodes.map(node => {
return {
label: node.name,
value: node.code
}
})
}
return {
searchNode,
getAllNodes,
allNodes,
toggleSearchInput,
searchInputVisible,
}
}

48
dolphinscheduler-ui-next/src/views/projects/workflow/use-sidebar-drag.ts

@ -0,0 +1,48 @@
/*
* 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 { Dragged } from './dag';
interface Options {
readonly: Ref<boolean>;
dragged: Ref<Dragged>;
}
/**
* Sidebar drag
*/
export function useSidebarDrag(options: Options) {
const { readonly, dragged } = options;
const onDragStart = (e: DragEvent, type: string) => {
if (readonly.value) {
e.preventDefault()
return
}
dragged.value = {
x: e.offsetX,
y: e.offsetY,
type: type
}
}
return {
onDragStart
}
}

22
dolphinscheduler-ui-next/src/views/projects/workflow/workflow-definition-create.module.scss

@ -0,0 +1,22 @@
/*
* 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.
*/
.container{
width: 100%;
box-sizing: border-box;
height: calc(100vh - 100px);
}

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

@ -0,0 +1,38 @@
/*
* 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 } from 'vue'
import Dag from './dag';
import { NCard } from 'naive-ui';
import styles from './workflow-definition-create.module.scss';
export default defineComponent({
name: "WorkflowDefinitionCreate",
setup() {
const slots = {
toolbarLeft: () => <span>left-operations</span>,
toolbarRight: () => <span>right-operations</span>
};
return () => (
<NCard class={styles.container}>
<Dag v-slots={slots} />
</NCard>
)
}
})

27
dolphinscheduler-ui-next/src/views/projects/workflow/workflow-definition-details.tsx

@ -0,0 +1,27 @@
/*
* 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 } from 'vue'
export default defineComponent({
name: "WorkflowDefinitionDetails",
setup() {
return () => (
<div>WorkflowDefinitionDetails</div>
)
}
})

27
dolphinscheduler-ui-next/src/views/projects/workflow/workflow-definition-list.tsx

@ -0,0 +1,27 @@
/*
* 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 } from 'vue'
export default defineComponent({
name: "WorkflowDefinitionList",
setup() {
return () => (
<div>WorkflowDefinitionList</div>
)
}
})

27
dolphinscheduler-ui-next/src/views/projects/workflow/workflow-instance-details.tsx

@ -0,0 +1,27 @@
/*
* 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 } from 'vue'
export default defineComponent({
name: "WorkflowInstanceDetails",
setup() {
return () => (
<div>WorkflowInstanceDetails</div>
)
}
})

27
dolphinscheduler-ui-next/src/views/projects/workflow/workflow-instance-list.tsx

@ -0,0 +1,27 @@
/*
* 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 } from 'vue'
export default defineComponent({
name: "WorkflowInstanceList",
setup() {
return () => (
<div>WorkflowInstanceList</div>
)
}
})

35
dolphinscheduler-ui-next/src/views/projects/workflow/x6-style.scss

@ -0,0 +1,35 @@
/*
* 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.
*/
$STROKE_BLUE: #288fff;
$BG_WHITE: #ffffff;
.x6-node[data-shape="dag-task"] {
&.available {
.dag-task-body {
stroke: $STROKE_BLUE;
stroke-width: 1;
stroke-dasharray: 5, 2;
}
&.adsorbed {
.dag-task-body {
stroke-width: 3;
}
}
}
}
Loading…
Cancel
Save