You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dolphinscheduler.apache.org by so...@apache.org on 2022/02/22 07:10:51 UTC
[dolphinscheduler] branch dev updated: [Feature][UI Next] Add dag menu (#8481)
This is an automated email from the ASF dual-hosted git repository.
songjian pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler.git
The following commit(s) were added to refs/heads/dev by this push:
new 66241fd [Feature][UI Next] Add dag menu (#8481)
66241fd is described below
commit 66241fd5c27e7463707ff94ed55d3fce350a3f49
Author: Devosend <de...@gmail.com>
AuthorDate: Tue Feb 22 15:10:45 2022 +0800
[Feature][UI Next] Add dag menu (#8481)
* add dag menu
* add dag menu click event
* workflow online edit not allowed
---
.../src/locales/modules/en_US.ts | 6 +-
.../src/locales/modules/zh_CN.ts | 6 +-
dolphinscheduler-ui-next/src/utils/common.ts | 17 +++
.../projects/workflow/components/dag/dag-config.ts | 7 +-
.../workflow/components/dag/dag-context-menu.tsx | 163 +++++++++++++++++++++
.../projects/workflow/components/dag/dag-hooks.ts | 4 +-
.../projects/workflow/components/dag/index.tsx | 38 ++++-
.../dag/{dag-hooks.ts => menu.module.scss} | 47 +++---
.../workflow/components/dag/use-canvas-init.ts | 3 +
.../workflow/components/dag/use-node-menu.ts | 75 ++++++++++
.../workflow/components/dag/use-task-edit.ts | 43 +++++-
11 files changed, 376 insertions(+), 33 deletions(-)
diff --git a/dolphinscheduler-ui-next/src/locales/modules/en_US.ts b/dolphinscheduler-ui-next/src/locales/modules/en_US.ts
index 6c91e5c..7597e4a 100644
--- a/dolphinscheduler-ui-next/src/locales/modules/en_US.ts
+++ b/dolphinscheduler-ui-next/src/locales/modules/en_US.ts
@@ -684,7 +684,11 @@ const project = {
sql_input_placeholder: 'Please enter non-query sql.',
sql_empty_tips: 'The sql can not be empty.',
procedure_method: 'SQL Statement',
- procedure_method_tips: 'Please enter the procedure script'
+ procedure_method_tips: 'Please enter the procedure script',
+ start: 'Start',
+ edit: 'Edit',
+ copy: 'Copy',
+ delete: 'Delete'
}
}
diff --git a/dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts b/dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts
index a503c76..10b5a39 100644
--- a/dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts
+++ b/dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts
@@ -676,7 +676,11 @@ const project = {
sql_input_placeholder: '请输入非查询SQL语句',
sql_empty_tips: '语句不能为空',
procedure_method: 'SQL语句',
- procedure_method_tips: '请输入存储脚本'
+ procedure_method_tips: '请输入存储脚本',
+ start: '运行',
+ edit: '编辑',
+ copy: '复制节点',
+ delete: '删除'
}
}
diff --git a/dolphinscheduler-ui-next/src/utils/common.ts b/dolphinscheduler-ui-next/src/utils/common.ts
index 68766c9..23ca932 100644
--- a/dolphinscheduler-ui-next/src/utils/common.ts
+++ b/dolphinscheduler-ui-next/src/utils/common.ts
@@ -314,3 +314,20 @@ export const tasksState = (t: any): ITaskState => ({
isSpin: false
}
})
+
+/**
+ * A simple uuid generator, support prefix and template pattern.
+ *
+ * @example
+ *
+ * uuid('v-') // -> v-xxx
+ * uuid('v-ani-%{s}-translate') // -> v-ani-xxx
+ */
+export function uuid(prefix: string) {
+ const id = Math.floor(Math.random() * 10000).toString(36)
+ return prefix
+ ? ~prefix.indexOf('%{s}')
+ ? prefix.replace(/%\{s\}/g, id)
+ : prefix + id
+ : id
+}
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-config.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-config.ts
index 5f73e2f..b84adea 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-config.ts
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-config.ts
@@ -200,7 +200,12 @@ export const NODE = {
group: X6_PORT_OUT_NAME
}
]
- }
+ },
+ tools: [
+ {
+ name: 'contextmenu'
+ }
+ ]
}
export const NODE_HOVER = {
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-context-menu.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-context-menu.tsx
new file mode 100644
index 0000000..0681d54
--- /dev/null
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-context-menu.tsx
@@ -0,0 +1,163 @@
+/*
+ * 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 { genTaskCodeList } from '@/service/modules/task-definition'
+import type { Cell } from '@antv/x6'
+import {
+ defineComponent,
+ onMounted,
+ PropType,
+ inject,
+ ref,
+ computed
+} from 'vue'
+import { useI18n } from 'vue-i18n'
+import { useRoute } from 'vue-router'
+import styles from './menu.module.scss'
+import { uuid } from '@/utils/common'
+
+const props = {
+ cell: {
+ type: Object as PropType<Cell>,
+ require: true
+ },
+ visible: {
+ type: Boolean as PropType<boolean>,
+ default: true
+ },
+ left: {
+ type: Number as PropType<number>,
+ default: 0
+ },
+ top: {
+ type: Number as PropType<number>,
+ default: 0
+ },
+ releaseState: {
+ type: String as PropType<string>,
+ default: 'OFFLINE'
+ }
+}
+
+export default defineComponent({
+ name: 'dag-context-menu',
+ props,
+ emits: ['hide', 'start', 'edit', 'copyTask', 'removeTasks'],
+ setup(props, ctx) {
+ const graph = inject('graph', ref())
+ const route = useRoute()
+ const projectCode = Number(route.params.projectCode)
+
+ const startAvailable = computed(
+ () =>
+ route.name === 'workflow-definition-detail' &&
+ props.releaseState !== 'NOT_RELEASE'
+ )
+
+ const hide = () => {
+ ctx.emit('hide', false)
+ }
+
+ const startRunning = () => {
+ ctx.emit('start')
+ }
+
+ const handleEdit = () => {
+ ctx.emit('edit', Number(props.cell?.id))
+ }
+
+ const handleCopy = () => {
+ const genNums = 1
+ const type = props.cell?.data.taskType
+ const taskName = uuid(props.cell?.data.taskName + '_')
+ const targetCode = Number(props.cell?.id)
+
+ genTaskCodeList(genNums, projectCode).then((res) => {
+ const [code] = res
+ ctx.emit('copyTask', taskName, code, targetCode, type, {
+ x: props.left + 100,
+ y: props.top + 100
+ })
+ })
+ }
+
+ const handleDelete = () => {
+ graph.value?.removeCell(props.cell)
+ ctx.emit('removeTasks', [Number(props.cell?.id)])
+ }
+
+ onMounted(() => {
+ document.addEventListener('click', () => {
+ hide()
+ })
+ })
+
+ return {
+ startAvailable,
+ startRunning,
+ handleEdit,
+ handleCopy,
+ handleDelete
+ }
+ },
+ render() {
+ const { t } = useI18n()
+
+ return (
+ this.visible && (
+ <div
+ class={styles['dag-context-menu']}
+ style={{ left: `${this.left}px`, top: `${this.top}px` }}
+ >
+ <div
+ class={`${styles['menu-item']} ${
+ !this.startAvailable ? styles['disabled'] : ''
+ } `}
+ onClick={this.startRunning}
+ >
+ {t('project.node.start')}
+ </div>
+ <div
+ class={`${styles['menu-item']} ${
+ this.releaseState === 'ONLINE' ? styles['disabled'] : ''
+ } `}
+ onClick={this.handleEdit}
+ >
+ {t('project.node.edit')}
+ </div>
+ <div
+ class={`${styles['menu-item']} ${
+ this.releaseState === 'ONLINE' ? styles['disabled'] : ''
+ } `}
+ onClick={this.handleCopy}
+ >
+ {t('project.node.copy')}
+ </div>
+ <div
+ class={`${styles['menu-item']} ${
+ this.releaseState === 'ONLINE' ? styles['disabled'] : ''
+ } `}
+ onClick={this.handleDelete}
+ >
+ {t('project.node.delete')}
+ </div>
+ {/* TODO: view log */}
+ </div>
+ )
+ )
+ }
+})
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-hooks.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-hooks.ts
index 3c69127..55b6e42 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-hooks.ts
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-hooks.ts
@@ -26,6 +26,7 @@ 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'
+import { useNodeMenu } from './use-node-menu'
export {
useCanvasInit,
@@ -38,5 +39,6 @@ export {
useGraphBackfill,
useCellUpdate,
useDagDragAndDrop,
- useTaskEdit
+ useTaskEdit,
+ useNodeMenu
}
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/index.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/index.tsx
index 9fbbc30..e43076d 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/index.tsx
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/index.tsx
@@ -27,13 +27,16 @@ import {
useGraphBackfill,
useDagDragAndDrop,
useTaskEdit,
- useBusinessMapper
+ useBusinessMapper,
+ useNodeMenu
} 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/task/components/node/detail-modal'
+import StartModal from '@/views/projects/workflow/definition/components/start-modal'
+import ContextMenuItem from './dag-context-menu'
import './x6-style.scss'
const props = {
@@ -82,10 +85,25 @@ export default defineComponent({
currTask,
taskCancel,
appendTask,
+ editTask,
+ copyTask,
taskDefinitions,
removeTasks
} = useTaskEdit({ graph, definition: toRef(props, 'definition') })
+ // Right click cell
+ const {
+ menuCell,
+ pageX,
+ pageY,
+ menuVisible,
+ startModalShow,
+ menuHide,
+ menuStart
+ } = useNodeMenu({
+ graph
+ })
+
const { onDragStart, onDrop } = useDagDragAndDrop({
graph,
readonly: toRef(props, 'readonly'),
@@ -177,6 +195,24 @@ export default defineComponent({
onSubmit={taskConfirm}
onCancel={taskCancel}
/>
+ <ContextMenuItem
+ cell={menuCell.value}
+ visible={menuVisible.value}
+ left={pageX.value}
+ top={pageY.value}
+ releaseState={props.definition?.processDefinition.releaseState}
+ onHide={menuHide}
+ onStart={menuStart}
+ onEdit={editTask}
+ onCopyTask={copyTask}
+ onRemoveTasks={removeTasks}
+ />
+ {!!props.definition && (
+ <StartModal
+ v-model:row={props.definition.processDefinition}
+ v-model:show={startModalShow.value}
+ />
+ )}
</div>
)
}
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-hooks.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/menu.module.scss
similarity index 50%
copy from dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-hooks.ts
copy to dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/menu.module.scss
index 3c69127..b4d5ce1 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-hooks.ts
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/menu.module.scss
@@ -15,28 +15,29 @@
* limitations under the License.
*/
-import { useCanvasInit } from './use-canvas-init'
-import { useBusinessMapper } from './use-business-mapper'
-import { useCellActive } from './use-cell-active'
-import { useCellUpdate } from './use-cell-update'
-import { useNodeSearch } from './use-node-search'
-import { useGraphAutoLayout } from './use-graph-auto-layout'
-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'
+ .dag-context-menu{
+ position: fixed;
+ left: 0;
+ top: 0;
+ width: 100px;
+ background-color: #ffffff;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.12);
-export {
- useCanvasInit,
- useBusinessMapper,
- useCellActive,
- useNodeSearch,
- useGraphAutoLayout,
- useTextCopy,
- useCustomCellBuilder,
- useGraphBackfill,
- useCellUpdate,
- useDagDragAndDrop,
- useTaskEdit
+ .menu-item{
+ padding: 5px 10px;
+ border-bottom: solid 1px #f2f3f7;
+ cursor: pointer;
+ color: rgb(89, 89, 89);
+ font-size: 12px;
+
+ &:hover:not(.disabled){
+ color: #262626;
+ background-color: #f5f5f5;
+ }
+
+ &.disabled{
+ cursor: not-allowed;
+ color: rgba(89, 89, 89, .4);
+ }
+ }
}
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-canvas-init.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-canvas-init.ts
index ebba1d8..6282acf 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-canvas-init.ts
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-canvas-init.ts
@@ -21,6 +21,7 @@ import { Graph } from '@antv/x6'
import { NODE, EDGE, X6_NODE_NAME, X6_EDGE_NAME } from './dag-config'
import { debounce } from 'lodash'
import { useResizeObserver } from '@vueuse/core'
+import ContextMenuTool from './dag-context-menu'
interface Options {
readonly: Ref<boolean>
@@ -45,6 +46,8 @@ export function useCanvasInit(options: Options) {
* Graph Init, bind graph to the dom
*/
function graphInit() {
+ Graph.registerNodeTool('contextmenu', ContextMenuTool, true)
+
return new Graph({
container: paper.value,
selecting: {
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-node-menu.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-node-menu.ts
new file mode 100644
index 0000000..df66c3e
--- /dev/null
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-node-menu.ts
@@ -0,0 +1,75 @@
+/*
+ * 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 { Graph, Cell } from '@antv/x6'
+
+interface Options {
+ graph: Ref<Graph | undefined>
+}
+
+/**
+ * Get position of the right-clicked Cell.
+ */
+export function useNodeMenu(options: Options) {
+ const { graph } = options
+ const startModalShow = ref(false)
+ const menuVisible = ref(false)
+ const pageX = ref()
+ const pageY = ref()
+ const menuCell = ref<Cell>()
+
+ const menuHide = () => {
+ menuVisible.value = false
+
+ // unlock scroller
+ graph.value?.unlockScroller()
+ }
+
+ const menuStart = () => {
+ startModalShow.value = true
+ }
+
+ onMounted(() => {
+ if (graph.value) {
+ // contextmenu
+ graph.value.on('node:contextmenu', ({ cell, x, y }) => {
+ menuCell.value = cell
+ const data = graph.value?.localToPage(x, y)
+ pageX.value = data?.x
+ pageY.value = data?.y
+
+ // show menu
+ menuVisible.value = true
+
+ // lock scroller
+ graph.value?.lockScroller()
+ })
+ }
+ })
+
+ return {
+ pageX,
+ pageY,
+ startModalShow,
+ menuVisible,
+ menuCell,
+ menuHide,
+ menuStart
+ }
+}
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-task-edit.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-task-edit.ts
index d4f91c0..d2bc42e 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-task-edit.ts
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-task-edit.ts
@@ -15,6 +15,7 @@
* limitations under the License.
*/
+import _ from 'lodash'
import { ref, onMounted, watch } from 'vue'
import type { Ref } from 'vue'
import type { Graph } from '@antv/x6'
@@ -61,6 +62,28 @@ export function useTaskEdit(options: Options) {
}
/**
+ * Copy a task
+ */
+ function copyTask(
+ name: string,
+ code: number,
+ targetCode: number,
+ type: TaskType,
+ coordinate: Coordinate
+ ) {
+ addNode(code + '', type, name, coordinate)
+ const definition = taskDefinitions.value.find((t) => t.code === targetCode)
+
+ const newDefinition = {
+ ...definition,
+ code,
+ name
+ } as NodeData
+
+ taskDefinitions.value.push(newDefinition)
+ }
+
+ /**
* Remove task
* @param {number} code
*/
@@ -76,6 +99,18 @@ export function useTaskEdit(options: Options) {
}
/**
+ * Edit task
+ * @param {number} code
+ */
+ function editTask(code: number) {
+ const definition = taskDefinitions.value.find((t) => t.code === code)
+ if (definition) {
+ currTask.value = definition
+ }
+ taskModalVisible.value = true
+ }
+
+ /**
* The confirm event in task config modal
* @param formRef
* @param from
@@ -108,11 +143,7 @@ export function useTaskEdit(options: Options) {
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
+ editTask(code)
})
}
})
@@ -127,6 +158,8 @@ export function useTaskEdit(options: Options) {
taskConfirm,
taskCancel,
appendTask,
+ editTask,
+ copyTask,
taskDefinitions,
removeTasks
}