You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dolphinscheduler.apache.org by zh...@apache.org on 2022/02/09 15:32:21 UTC

[dolphinscheduler] branch dev updated: [Feature][UI Next] DAG backfill (#8325)

This is an automated email from the ASF dual-hosted git repository.

zhongjiajie 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 480492d  [Feature][UI Next] DAG backfill (#8325)
480492d is described below

commit 480492db732fc4511f50be9708727fdf040501e0
Author: wangyizhi <wa...@cmss.chinamobile.com>
AuthorDate: Wed Feb 9 23:32:11 2022 +0800

    [Feature][UI Next] DAG backfill (#8325)
    
    * [Feature][UI Next] Dag backfill
    
    * [Feature][UI Next] Add license header
---
 .../src/locales/modules/en_US.ts                   |   3 +-
 .../src/locales/modules/zh_CN.ts                   |   3 +-
 .../service/modules/process-definition/index.ts    |   6 +-
 .../src/views/projects/task/constants/task-type.ts |   4 +-
 .../workflow/components/dag/dag-canvas.tsx         |  42 ++-----
 .../projects/workflow/components/dag/dag-hooks.ts  |  20 ++--
 .../workflow/components/dag/dag-sidebar.tsx        |  36 ++----
 .../workflow/components/dag/dag-toolbar.tsx        | 111 ++++++++++++------
 .../workflow/components/dag/dag.module.scss        |   4 +
 .../projects/workflow/components/dag/index.tsx     |  83 ++++++++++----
 .../projects/workflow/components/dag/types.ts      |  95 ++++++++++++++++
 .../workflow/components/dag/use-canvas-init.ts     |   2 +-
 .../dag/{use-node-search.ts => use-cell-query.ts}  |  42 ++++---
 .../workflow/components/dag/use-cell-update.ts     |  76 +++++++++++++
 ...ph-operations.ts => use-custom-cell-builder.ts} | 126 ++++++++-------------
 .../{use-canvas-drop.ts => use-dag-drag-drop.ts}   |  46 ++++++--
 .../{use-sidebar-drag.ts => use-graph-backfill.ts} |  36 +++---
 .../workflow/components/dag/use-node-search.ts     |  47 +++++---
 .../dag/{use-sidebar-drag.ts => use-text-copy.ts}  |  36 +++---
 .../definition/components/version-modal.tsx        |   6 +-
 .../projects/workflow/definition/create/index.tsx  |   7 +-
 .../detail/{index.tsx => index.module.scss}        |  32 ++++--
 .../projects/workflow/definition/detail/index.tsx  |  37 +++++-
 .../views/projects/workflow/definition/index.tsx   |  15 ++-
 .../definition/{use-table.ts => use-table.tsx}     |  25 ++--
 25 files changed, 608 insertions(+), 332 deletions(-)

diff --git a/dolphinscheduler-ui-next/src/locales/modules/en_US.ts b/dolphinscheduler-ui-next/src/locales/modules/en_US.ts
index 206684b..1bd8caa 100644
--- a/dolphinscheduler-ui-next/src/locales/modules/en_US.ts
+++ b/dolphinscheduler-ui-next/src/locales/modules/en_US.ts
@@ -495,7 +495,8 @@ const project = {
     grid_layout: 'Grid',
     dagre_layout: 'Dagre',
     rows: 'Rows',
-    cols: 'Cols'
+    cols: 'Cols',
+    copy_success: 'Copy Success'
   }
 }
 
diff --git a/dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts b/dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts
index d9fe1bc..00cefaa 100644
--- a/dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts
+++ b/dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts
@@ -494,7 +494,8 @@ const project = {
     grid_layout: '网格布局',
     dagre_layout: '层次布局',
     rows: '行数',
-    cols: '列数'
+    cols: '列数',
+    copy_success: '复制成功'
   }
 }
 
diff --git a/dolphinscheduler-ui-next/src/service/modules/process-definition/index.ts b/dolphinscheduler-ui-next/src/service/modules/process-definition/index.ts
index c2a8885..a6d1313 100644
--- a/dolphinscheduler-ui-next/src/service/modules/process-definition/index.ts
+++ b/dolphinscheduler-ui-next/src/service/modules/process-definition/index.ts
@@ -148,11 +148,11 @@ export function verifyName(params: NameReq, code: CodeReq): any {
 }
 
 export function queryProcessDefinitionByCode(
-  code: CodeReq,
-  processCode: CodeReq
+  code: number,
+  projectCode: number
 ): any {
   return axios({
-    url: `/projects/${code}/process-definition/${processCode}`,
+    url: `/projects/${projectCode}/process-definition/${code}`,
     method: 'get'
   })
 }
diff --git a/dolphinscheduler-ui-next/src/views/projects/task/constants/task-type.ts b/dolphinscheduler-ui-next/src/views/projects/task/constants/task-type.ts
index 260cbc9..52878ab 100644
--- a/dolphinscheduler-ui-next/src/views/projects/task/constants/task-type.ts
+++ b/dolphinscheduler-ui-next/src/views/projects/task/constants/task-type.ts
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-export const ALL_TASK_TYPES: any = {
+export const TASK_TYPES_MAP = {
   SHELL: {
     alias: 'SHELL'
   },
@@ -65,3 +65,5 @@ export const ALL_TASK_TYPES: any = {
     alias: 'WATERDROP'
   }
 }
+
+export type TaskType = keyof typeof TASK_TYPES_MAP
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-canvas.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-canvas.tsx
index 221bc8e..b6771f1 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-canvas.tsx
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-canvas.tsx
@@ -17,53 +17,33 @@
 
 import { defineComponent, ref, inject } from 'vue'
 import Styles from './dag.module.scss'
-import type { PropType, Ref } from 'vue'
-import type { Dragged } from './index'
-import { useCanvasInit, 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: ''
-    })
-  }
-}
+import { useCanvasInit, useCellActive } from './dag-hooks'
 
 export default defineComponent({
   name: 'workflow-dag-canvas',
-  props,
+  emits: ['drop'],
   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
-    })
+    const preventDefault = (e: DragEvent) => {
+      e.preventDefault()
+    }
 
     return () => (
       <div
         ref={container}
         class={Styles.canvas}
-        onDrop={onDrop}
-        onDragenter={onDragenter}
-        onDragover={onDragover}
-        onDragleave={onDragleave}
+        onDrop={(e) => {
+          context.emit('drop', e)
+        }}
+        onDragenter={preventDefault}
+        onDragover={preventDefault}
+        onDragleave={preventDefault}
       >
         <div ref={paper} class={Styles.paper}></div>
         <div ref={minimap} class={Styles.minimap}></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 a2d57e2..303ae1f 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
@@ -16,19 +16,25 @@
  */
 
 import { useCanvasInit } from './use-canvas-init'
-import { useGraphOperations } from './use-graph-operations'
+import { useCellQuery } from './use-cell-query'
 import { useCellActive } from './use-cell-active'
-import { useSidebarDrag } from './use-sidebar-drag'
-import { useCanvasDrop } from './use-canvas-drop'
+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'
 
 export {
   useCanvasInit,
-  useGraphOperations,
+  useCellQuery,
   useCellActive,
-  useSidebarDrag,
-  useCanvasDrop,
   useNodeSearch,
-  useGraphAutoLayout
+  useGraphAutoLayout,
+  useTextCopy,
+  useCustomCellBuilder,
+  useGraphBackfill,
+  useCellUpdate,
+  useDagDragAndDrop
 }
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-sidebar.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-sidebar.tsx
index 420d4eb..2ecd498 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-sidebar.tsx
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-sidebar.tsx
@@ -15,37 +15,17 @@
  * limitations under the License.
  */
 
-import type { PropType, Ref } from 'vue'
-import type { Dragged } from './index'
-import { defineComponent, ref, inject } from 'vue'
-import { ALL_TASK_TYPES } from '../../../task/constants/task-type'
-import { useSidebarDrag } from './dag-hooks'
+import { defineComponent } from 'vue'
+import { TASK_TYPES_MAP, TaskType } from '../../../task/constants/task-type'
 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) => ({
+  emits: ['dragStart'],
+  setup(props, context) {
+    const allTaskTypes = Object.keys(TASK_TYPES_MAP).map((type) => ({
       type,
-      ...ALL_TASK_TYPES[type]
+      ...TASK_TYPES_MAP[type as TaskType]
     }))
 
     return () => (
@@ -54,7 +34,9 @@ export default defineComponent({
           <div
             class={Styles.draggable}
             draggable='true'
-            onDragstart={(e) => onDragStart(e, task.type)}
+            onDragstart={(e) => {
+              context.emit('dragStart', e, task.type)
+            }}
           >
             <em
               class={[
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-toolbar.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-toolbar.tsx
index b1081ce..1edcf19 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-toolbar.tsx
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-toolbar.tsx
@@ -15,19 +15,29 @@
  * limitations under the License.
  */
 
-import { defineComponent, ref, inject, PropType } from 'vue'
+import {
+  defineComponent,
+  ref,
+  inject,
+  PropType,
+  onMounted,
+  watch,
+  computed
+} from 'vue'
+import type { Ref } from 'vue'
 import { useI18n } from 'vue-i18n'
 import Styles from './dag.module.scss'
-import { NTooltip, NIcon, NButton, NSelect } from 'naive-ui'
+import { NTooltip, NIcon, NButton, NSelect, useMessage } from 'naive-ui'
 import {
   SearchOutlined,
   DownloadOutlined,
   FullscreenOutlined,
   FullscreenExitOutlined,
   InfoCircleOutlined,
-  FormatPainterOutlined
+  FormatPainterOutlined,
+  CopyOutlined
 } from '@vicons/antd'
-import { useNodeSearch } from './dag-hooks'
+import { useNodeSearch, useTextCopy, useCellQuery } from './dag-hooks'
 import { DataUri } from '@antv/x6'
 import { useFullscreen } from '@vueuse/core'
 import { useRouter } from 'vue-router'
@@ -37,12 +47,19 @@ const props = {
   layoutToggle: {
     type: Function as PropType<(bool?: boolean) => void>,
     default: () => {}
+  },
+  // If this prop is passed, it means from definition detail
+  definition: {
+    // The same as the structure responsed by the queryProcessDefinitionByCode api
+    type: Object as PropType<any>,
+    default: null
   }
 }
 
 export default defineComponent({
   name: 'workflow-dag-toolbar',
   props,
+  emits: ['versionToggle'],
   setup(props, context) {
     const { t } = useI18n()
 
@@ -55,11 +72,11 @@ export default defineComponent({
      * Node search and navigate
      */
     const {
-      searchNode,
-      getAllNodes,
-      allNodes,
+      navigateTo,
       toggleSearchInput,
-      searchInputVisible
+      searchInputVisible,
+      reQueryNodes,
+      nodesDropdown
     } = useNodeSearch({ graph })
 
     /**
@@ -94,7 +111,7 @@ export default defineComponent({
      * Open workflow version modal
      */
     const openVersionModal = () => {
-      //TODO, same as the version popup in the workflow list page
+      context.emit('versionToggle', true)
     }
 
     /**
@@ -111,6 +128,11 @@ export default defineComponent({
       router.go(-1)
     }
 
+    /**
+     *  Copy workflow name
+     */
+    const { copy } = useTextCopy()
+
     return () => (
       <div
         class={[
@@ -118,7 +140,24 @@ export default defineComponent({
           Styles[themeStore.darkTheme ? 'toolbar-dark' : 'toolbar-light']
         ]}
       >
-        <span class={Styles['workflow-name']}>{t('project.dag.create')}</span>
+        <div>
+          <span class={Styles['workflow-name']}>
+            {props.definition?.processDefinition?.name ||
+              t('project.dag.create')}
+          </span>
+          {props.definition?.processDefinition?.name && (
+            <NButton
+              quaternary
+              circle
+              onClick={() => copy(props.definition?.processDefinition?.name)}
+              class={Styles['copy-btn']}
+            >
+              <NIcon>
+                <CopyOutlined />
+              </NIcon>
+            </NButton>
+          )}
+        </div>
         <div class={Styles['toolbar-right-part']}>
           {/* Search node */}
           <NTooltip
@@ -150,9 +189,9 @@ export default defineComponent({
           >
             <NSelect
               size='small'
-              options={allNodes.value}
-              onFocus={getAllNodes}
-              onUpdateValue={searchNode}
+              options={nodesDropdown.value}
+              onFocus={reQueryNodes}
+              onUpdateValue={navigateTo}
               filterable
             />
           </div>
@@ -233,28 +272,30 @@ export default defineComponent({
             }}
           ></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.workflow.version_info')
-            }}
-          ></NTooltip>
+          {!!props.definition && (
+            <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.workflow.version_info')
+              }}
+            ></NTooltip>
+          )}
           {/* Save workflow */}
           <NButton
             class={Styles['toolbar-right-item']}
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag.module.scss b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag.module.scss
index 7fae7ae..d338520 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag.module.scss
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag.module.scss
@@ -76,6 +76,10 @@ $bgLight: #ffffff;
   font-size: 14px;
 }
 
+.copy-btn {
+  margin-left: 5px;
+}
+
 .draggable {
   display: flex;
   width: 100%;
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 fc4665e..6c54a8c 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
@@ -16,48 +16,48 @@
  */
 
 import type { Graph } from '@antv/x6'
-import { defineComponent, ref, provide } from 'vue'
+import { defineComponent, ref, provide, PropType, toRef } from 'vue'
 import DagToolbar from './dag-toolbar'
 import DagCanvas from './dag-canvas'
 import DagSidebar from './dag-sidebar'
 import Styles from './dag.module.scss'
 import DagAutoLayoutModal from './dag-auto-layout-modal'
-import { useGraphAutoLayout } from './dag-hooks'
+import {
+  useGraphAutoLayout,
+  useGraphBackfill,
+  useDagDragAndDrop
+} from './dag-hooks'
 import { useThemeStore } from '@/store/theme/theme'
+import VersionModal from '../../definition/components/version-modal'
+import { WorkflowDefinition } from './types'
 import './x6-style.scss'
 
-export interface Dragged {
-  x: number
-  y: number
-  type: string
+const props = {
+  // If this prop is passed, it means from definition detail
+  definition: {
+    type: Object as PropType<WorkflowDefinition>,
+    default: undefined
+  },
+  readonly: {
+    type: Boolean as PropType<boolean>,
+    default: false
+  }
 }
 
 export default defineComponent({
   name: 'workflow-dag',
+  props,
+  emits: ['refresh'],
   setup(props, context) {
     const theme = useThemeStore()
 
     // Whether the graph can be operated
-    const readonly = ref(false)
-    provide('readonly', readonly)
+    provide('readonly', toRef(props, '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: ''
-    })
-
-    // Auto layout
+    // Auto layout modal
     const {
       visible: layoutVisible,
       toggle: layoutToggle,
@@ -67,6 +67,28 @@ export default defineComponent({
       cancel
     } = useGraphAutoLayout({ graph })
 
+    const { onDragStart, onDrop } = useDagDragAndDrop({
+      graph,
+      readonly: toRef(props, 'readonly')
+    })
+
+    // backfill
+    useGraphBackfill({ graph, definition: toRef(props, 'definition') })
+
+    // version modal
+    const versionModalShow = ref(false)
+    const versionToggle = (bool: boolean) => {
+      if (typeof bool === 'boolean') {
+        versionModalShow.value = bool
+      } else {
+        versionModalShow.value = !versionModalShow.value
+      }
+    }
+    const refreshDetail = () => {
+      context.emit('refresh')
+      versionModalShow.value = false
+    }
+
     return () => (
       <div
         class={[
@@ -74,10 +96,14 @@ export default defineComponent({
           Styles[`dag-${theme.darkTheme ? 'dark' : 'light'}`]
         ]}
       >
-        <DagToolbar v-slots={toolbarSlots} layoutToggle={layoutToggle} />
+        <DagToolbar
+          layoutToggle={layoutToggle}
+          definition={props.definition}
+          onVersionToggle={versionToggle}
+        />
         <div class={Styles.content}>
-          <DagSidebar dragged={dragged} />
-          <DagCanvas dragged={dragged} />
+          <DagSidebar onDragStart={onDragStart} />
+          <DagCanvas onDrop={onDrop} />
         </div>
         <DagAutoLayoutModal
           visible={layoutVisible.value}
@@ -86,6 +112,13 @@ export default defineComponent({
           formValue={formValue}
           formRef={formRef}
         />
+        {!!props.definition && (
+          <VersionModal
+            v-model:row={props.definition.processDefinition}
+            v-model:show={versionModalShow.value}
+            onUpdateList={refreshDetail}
+          />
+        )}
       </div>
     )
   }
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/types.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/types.ts
new file mode 100644
index 0000000..77f97db
--- /dev/null
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/types.ts
@@ -0,0 +1,95 @@
+/*
+ * 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 interface ProcessDefinition {
+  id: number
+  code: number
+  name: string
+  version: number
+  releaseState: string
+  projectCode: number
+  description: string
+  globalParams: string
+  globalParamList: any[]
+  globalParamMap: any
+  createTime: string
+  updateTime: string
+  flag: string
+  userId: number
+  userName?: any
+  projectName?: any
+  locations: string
+  scheduleReleaseState?: any
+  timeout: number
+  tenantId: number
+  tenantCode: string
+  modifyBy?: any
+  warningGroupId: number
+}
+
+export interface ProcessTaskRelationList {
+  id: number
+  name: string
+  processDefinitionVersion: number
+  projectCode: any
+  processDefinitionCode: any
+  preTaskCode: number
+  preTaskVersion: number
+  postTaskCode: any
+  postTaskVersion: number
+  conditionType: string
+  conditionParams: any
+  createTime: string
+  updateTime: string
+}
+
+export interface TaskDefinitionList {
+  id: number
+  code: any
+  name: string
+  version: number
+  description: string
+  projectCode: any
+  userId: number
+  taskType: string
+  taskParams: any
+  taskParamList: any[]
+  taskParamMap: any
+  flag: string
+  taskPriority: string
+  userName?: any
+  projectName?: any
+  workerGroup: string
+  environmentCode: number
+  failRetryTimes: number
+  failRetryInterval: number
+  timeoutFlag: string
+  timeoutNotifyStrategy: string
+  timeout: number
+  delayTime: number
+  resourceIds: string
+  createTime: string
+  updateTime: string
+  modifyBy?: any
+  dependence: string
+}
+
+export interface WorkflowDefinition {
+  processDefinition: ProcessDefinition
+  processTaskRelationList: ProcessTaskRelationList[]
+  taskDefinitionList: TaskDefinitionList[]
+}
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 8ec8d12..ffd5dc2 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
@@ -16,7 +16,7 @@
  */
 
 import type { Node } from '@antv/x6'
-import { ref, onMounted, Ref, onUnmounted } from 'vue'
+import { ref, onMounted, Ref } from 'vue'
 import { Graph } from '@antv/x6'
 import { NODE, EDGE, X6_NODE_NAME, X6_EDGE_NAME } from './dag-config'
 import { debounce } from 'lodash'
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-node-search.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-cell-query.ts
similarity index 58%
copy from dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-node-search.ts
copy to dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-cell-query.ts
index 59d96e8..243e6c7 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-node-search.ts
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-cell-query.ts
@@ -15,44 +15,40 @@
  * limitations under the License.
  */
 
+import type { Ref } from 'vue'
 import type { Graph } from '@antv/x6'
-import { ref, Ref } from 'vue'
-import { useGraphOperations } from './dag-hooks'
+import { TaskType } from '../../../task/constants/task-type'
 
 interface Options {
   graph: Ref<Graph | undefined>
 }
 
 /**
- * Node search and navigate
+ * Expose some cell-related query methods and refs
+ * @param {Options} options
  */
-export function useNodeSearch(options: Options) {
+export function useCellQuery(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) => {
+  /**
+   * 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 {
-        label: node.name,
-        value: node.code
+        code: node.id,
+        position: position,
+        name: data.taskName as string,
+        type: data.taskType as TaskType
       }
     })
   }
 
   return {
-    searchNode,
-    getAllNodes,
-    allNodes,
-    toggleSearchInput,
-    searchInputVisible
+    getNodes
   }
 }
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-cell-update.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-cell-update.ts
new file mode 100644
index 0000000..b69033f
--- /dev/null
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-cell-update.ts
@@ -0,0 +1,76 @@
+/*
+ * 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 { TaskType } from '@/views/projects/task/constants/task-type'
+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 {
+  graph: Ref<Graph | undefined>
+}
+
+/**
+ * Expose some cell query
+ * @param {Options} options
+ */
+export function useCellUpdate(options: Options) {
+  const { graph } = options
+
+  const { buildNode } = useCustomCellBuilder()
+
+  /**
+   * 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 })
+    }
+  }
+
+  /**
+   * 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 (!TASK_TYPES_MAP[type as TaskType]) {
+      console.warn(`taskType:${type} is invalid!`)
+      return
+    }
+    const node = buildNode(id, type, '', coordinate)
+    graph.value?.addNode(node)
+  }
+
+  return {
+    setNodeName,
+    addNode
+  }
+}
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-graph-operations.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-custom-cell-builder.ts
similarity index 51%
rename from dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-graph-operations.ts
rename to dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-custom-cell-builder.ts
index 2ec36a3..390b28c 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-graph-operations.ts
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-custom-cell-builder.ts
@@ -15,24 +15,27 @@
  * limitations under the License.
  */
 
-import type { Ref } from 'vue'
-import type { Node, Graph, Edge } from '@antv/x6'
+import type { Node, Edge } from '@antv/x6'
 import { X6_NODE_NAME, X6_EDGE_NAME } from './dag-config'
-import { ALL_TASK_TYPES } from '../../../task/constants/task-type'
 import utils from '@/utils'
+import { WorkflowDefinition } from './types'
 
-interface Options {
-  graph: Ref<Graph | undefined>
-}
-
-type Coordinate = { x: number; y: number }
+export type Coordinate = { x: number; y: number }
 
-/**
- * Expose some graph operation methods
- * @param {Options} options
- */
-export function useGraphOperations(options: Options) {
-  const { graph } = options
+export function useCustomCellBuilder() {
+  /**
+   * Convert locationStr to JSON
+   * @param {string} locationStr
+   * @returns
+   */
+  function parseLocationStr(locationStr: string) {
+    let locations = null
+    if (!locationStr) return locations
+    try {
+      locations = JSON.parse(locationStr)
+    } catch (error) {}
+    return Array.isArray(locations) ? locations : null
+  }
 
   /**
    * Build edge metadata
@@ -40,7 +43,7 @@ export function useGraphOperations(options: Options) {
    * @param {string} targetId
    * @param {string} label
    */
-  function buildEdgeMetadata(
+  function buildEdge(
     sourceId: string,
     targetId: string,
     label: string = ''
@@ -63,7 +66,7 @@ export function useGraphOperations(options: Options) {
    * @param {string} taskType
    * @param {Coordinate} coordinate Default is { x: 100, y: 100 }
    */
-  function buildNodeMetadata(
+  function buildNode(
     id: string,
     type: string,
     taskName: string,
@@ -92,74 +95,43 @@ export function useGraphOperations(options: Options) {
   }
 
   /**
-   * Add a node to the graph
-   * @param {string} id
-   * @param {string} taskType
-   * @param {Coordinate} coordinate Default is { x: 100, y: 100 }
+   * Build graph JSON
+   * @param {WorkflowDefinition} definition
+   * @returns
    */
-  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)
-  }
+  function buildGraph(definition: WorkflowDefinition) {
+    const nodes: Node.Metadata[] = []
+    const edges: Edge.Metadata[] = []
 
-  /**
-   * 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 })
-    }
-  }
+    const locations =
+      parseLocationStr(definition.processDefinition.locations) || []
+    const tasks = definition.taskDefinitionList
+    const connects = definition.processTaskRelationList
 
-  /**
-   * 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
-      }
+    tasks.forEach((task) => {
+      const location = locations.find((l) => l.taskCode === task.code) || {}
+      const node = buildNode(task.code, task.taskType, task.name, {
+        x: location.x,
+        y: location.y
+      })
+      nodes.push(node)
     })
-  }
 
-  /**
-   * 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)
+    connects
+      .filter((r) => !!r.preTaskCode)
+      .forEach((c) => {
+        const edge = buildEdge(c.preTaskCode + '', c.postTaskCode, c.name)
+        edges.push(edge)
+      })
+    return {
+      nodes,
+      edges
+    }
   }
 
   return {
-    buildEdgeMetadata,
-    buildNodeMetadata,
-    addNode,
-    setNodeName,
-    getNodes,
-    navigateTo
+    buildNode,
+    buildEdge,
+    buildGraph
   }
 }
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-canvas-drop.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-dag-drag-drop.ts
similarity index 64%
rename from dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-canvas-drop.ts
rename to dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-dag-drag-drop.ts
index 7036052..3aa8811 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-canvas-drop.ts
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-dag-drag-drop.ts
@@ -15,39 +15,60 @@
  * limitations under the License.
  */
 
+import { ref } from 'vue'
 import type { Ref } from 'vue'
 import type { Graph } from '@antv/x6'
-import type { Dragged } from './dag'
+import type { Dragged } from '.'
 import { genTaskCodeList } from '@/service/modules/task-definition'
-import { useGraphOperations } from './dag-hooks'
+import { useCellUpdate } from './dag-hooks'
+import { useRoute } from 'vue-router'
 
 interface Options {
   readonly: Ref<boolean>
   graph: Ref<Graph | undefined>
-  container: Ref<HTMLElement | undefined>
-  dragged: Ref<Dragged>
-  projectCode: string
 }
 
 /**
- * Drop sidebar item in canvas
+ * Sidebar item drag && drop in canvas
  */
-export function useCanvasDrop(options: Options) {
-  const { readonly, graph, container, dragged, projectCode } = options
+export function useDagDragAndDrop(options: Options) {
+  const { readonly, graph } = options
 
-  const { addNode } = useGraphOperations({ graph })
+  const route = useRoute()
+  const projectCode = Number(route.params.projectCode)
 
-  const onDrop = (e: DragEvent) => {
+  const { addNode } = useCellUpdate({ graph })
+
+  // The element currently being dragged up
+  const dragged = ref<Dragged>({
+    x: 0,
+    y: 0,
+    type: ''
+  })
+
+  function onDragStart(e: DragEvent, type: string) {
+    if (readonly.value) {
+      e.preventDefault()
+      return
+    }
+    dragged.value = {
+      x: e.offsetX,
+      y: e.offsetY,
+      type: type
+    }
+  }
+
+  function onDrop(e: DragEvent) {
     e.stopPropagation()
     e.preventDefault()
     if (readonly.value) {
       return
     }
-    if (dragged.value && graph.value && container.value && projectCode) {
+    if (dragged.value && graph.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) => {
+      genTaskCodeList(genNums, projectCode).then((res) => {
         const [code] = res
         addNode(code + '', type, { x: x - eX, y: y - eY })
         // openTaskConfigModel(code, type)
@@ -60,6 +81,7 @@ export function useCanvasDrop(options: Options) {
   }
 
   return {
+    onDragStart,
     onDrop,
     onDragenter: preventDefault,
     onDragover: preventDefault,
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-sidebar-drag.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-graph-backfill.ts
similarity index 59%
copy from dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-sidebar-drag.ts
copy to dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-graph-backfill.ts
index d7b6c53..4cd19d3 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-sidebar-drag.ts
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-graph-backfill.ts
@@ -15,33 +15,29 @@
  * limitations under the License.
  */
 
-import type { Ref } from 'vue'
-import type { Dragged } from './dag'
+import { Ref, watch } from 'vue'
+import { useCustomCellBuilder } from './dag-hooks'
+import type { Graph } from '@antv/x6'
+import { WorkflowDefinition } from './types'
 
 interface Options {
-  readonly: Ref<boolean>
-  dragged: Ref<Dragged>
+  graph: Ref<Graph | undefined>
+  definition: Ref<WorkflowDefinition | undefined>
 }
 
 /**
- * Sidebar drag
+ * Backfill workflow into graph
  */
-export function useSidebarDrag(options: Options) {
-  const { readonly, dragged } = options
+export function useGraphBackfill(options: Options) {
+  const { graph, definition } = options
 
-  const onDragStart = (e: DragEvent, type: string) => {
-    if (readonly.value) {
-      e.preventDefault()
-      return
-    }
-    dragged.value = {
-      x: e.offsetX,
-      y: e.offsetY,
-      type: type
+  const { buildGraph } = useCustomCellBuilder()
+
+  watch([graph, definition], () => {
+    if (graph.value && definition.value) {
+      graph.value.fromJSON(buildGraph(definition.value))
     }
-  }
+  })
 
-  return {
-    onDragStart
-  }
+  return {}
 }
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-node-search.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-node-search.ts
index 59d96e8..27eae9f 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-node-search.ts
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-node-search.ts
@@ -17,7 +17,7 @@
 
 import type { Graph } from '@antv/x6'
 import { ref, Ref } from 'vue'
-import { useGraphOperations } from './dag-hooks'
+import { useCellQuery } from './dag-hooks'
 
 interface Options {
   graph: Ref<Graph | undefined>
@@ -29,30 +29,43 @@ interface Options {
 export function useNodeSearch(options: Options) {
   const { graph } = options
 
+  /**
+   * Search input visible control
+   */
   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)
+
+  /**
+   * 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 getAllNodes = () => {
-    const nodes = getNodes()
-    allNodes.value = nodes.map((node) => {
-      return {
-        label: node.name,
-        value: node.code
-      }
-    })
+
+  /**
+   * 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 {
-    searchNode,
-    getAllNodes,
-    allNodes,
+    navigateTo,
     toggleSearchInput,
-    searchInputVisible
+    searchInputVisible,
+    reQueryNodes,
+    nodesDropdown
   }
 }
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-sidebar-drag.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-text-copy.ts
similarity index 63%
rename from dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-sidebar-drag.ts
rename to dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-text-copy.ts
index d7b6c53..4bd5450 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-sidebar-drag.ts
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-text-copy.ts
@@ -15,33 +15,23 @@
  * limitations under the License.
  */
 
-import type { Ref } from 'vue'
-import type { Dragged } from './dag'
-
-interface Options {
-  readonly: Ref<boolean>
-  dragged: Ref<Dragged>
-}
+import { useClipboard } from '@vueuse/core'
+import { useMessage } from 'naive-ui'
+import { useI18n } from 'vue-i18n'
 
 /**
- * Sidebar drag
+ * Text copy with success message
  */
-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
-    }
+export function useTextCopy() {
+  const { t } = useI18n()
+  const { copy } = useClipboard()
+  const message = useMessage()
+  const copyText = (text: string) => {
+    copy(text).then((res) => {
+      message.success(t('project.dag.copy_success'))
+    })
   }
-
   return {
-    onDragStart
+    copy: copyText
   }
 }
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/components/version-modal.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/definition/components/version-modal.tsx
index 31e1ae3..cf104a5 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/components/version-modal.tsx
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/definition/components/version-modal.tsx
@@ -58,9 +58,11 @@ export default defineComponent({
     }
 
     watch(
-      () => props.row.code,
+      () => props.show,
       () => {
-        getTableData(props.row)
+        if (props.show && props.row?.code) {
+          getTableData(props.row)
+        }
       }
     )
 
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.tsx
index 7e2de1b6..7462b42 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.tsx
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.tsx
@@ -25,11 +25,6 @@ export default defineComponent({
   setup() {
     const theme = useThemeStore()
 
-    const slots = {
-      toolbarLeft: () => <span>left-operations</span>,
-      toolbarRight: () => <span>right-operations</span>
-    }
-
     return () => (
       <div
         class={[
@@ -37,7 +32,7 @@ export default defineComponent({
           theme.darkTheme ? Styles['dark'] : Styles['light']
         ]}
       >
-        <Dag v-slots={slots} />
+        <Dag />
       </div>
     )
   }
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/detail/index.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/definition/detail/index.module.scss
similarity index 64%
copy from dolphinscheduler-ui-next/src/views/projects/workflow/definition/detail/index.tsx
copy to dolphinscheduler-ui-next/src/views/projects/workflow/definition/detail/index.module.scss
index ebcf900..baab217 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/detail/index.tsx
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/definition/detail/index.module.scss
@@ -15,11 +15,27 @@
  * limitations under the License.
  */
 
-import { defineComponent } from 'vue'
-
-export default defineComponent({
-  name: 'WorkflowDefinitionDetails',
-  setup() {
-    return () => <div>WorkflowDefinitionDetails</div>
-  }
-})
+ $borderDark: rgba(255, 255, 255, 0.09);
+ $borderLight: rgb(239, 239, 245);
+ $bgDark: rgb(24, 24, 28);
+ $bgLight: #ffffff;
+ 
+ .container {
+   width: 100%;
+   padding: 20px;
+   box-sizing: border-box;
+   height: calc(100vh - 100px);
+   overflow: hidden;
+   display: block;
+ }
+ 
+ .dark {
+   border: solid 1px $borderDark;
+   background-color: $bgDark;
+ }
+ 
+ .light {
+   border: solid 1px $borderLight;
+   background-color: $bgLight;
+ }
+ 
\ No newline at end of file
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/detail/index.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/definition/detail/index.tsx
index ebcf900..fd7200d 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/detail/index.tsx
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/definition/detail/index.tsx
@@ -15,11 +15,44 @@
  * limitations under the License.
  */
 
-import { defineComponent } from 'vue'
+import { defineComponent, onMounted, ref } from 'vue'
+import { useRoute } from 'vue-router'
+import { useThemeStore } from '@/store/theme/theme'
+import Dag from '../../components/dag'
+import { queryProcessDefinitionByCode } from '@/service/modules/process-definition'
+import { WorkflowDefinition } from '../../components/dag/types'
+import Styles from './index.module.scss'
 
 export default defineComponent({
   name: 'WorkflowDefinitionDetails',
   setup() {
-    return () => <div>WorkflowDefinitionDetails</div>
+    const theme = useThemeStore()
+    const route = useRoute()
+    const projectCode = Number(route.params.projectCode)
+    const code = Number(route.params.code)
+
+    const definition = ref<WorkflowDefinition>()
+
+    const refresh = () => {
+      queryProcessDefinitionByCode(code, projectCode).then((res: any) => {
+        definition.value = res
+      })
+    }
+
+    onMounted(() => {
+      if (!code || !projectCode) return
+      refresh()
+    })
+
+    return () => (
+      <div
+        class={[
+          Styles.container,
+          theme.darkTheme ? Styles['dark'] : Styles['light']
+        ]}
+      >
+        <Dag definition={definition.value} onRefresh={refresh} />
+      </div>
+    )
   }
 })
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/index.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/definition/index.tsx
index 3bcc8f3..792a4f4 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/index.tsx
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/definition/index.tsx
@@ -32,11 +32,17 @@ import ImportModal from './components/import-modal'
 import StartModal from './components/start-modal'
 import TimingModal from './components/timing-modal'
 import VersionModal from './components/version-modal'
+import { useRouter, useRoute } from 'vue-router'
+import type { Router } from 'vue-router'
 import styles from './index.module.scss'
 
 export default defineComponent({
   name: 'WorkflowDefinitionList',
   setup() {
+    const router: Router = useRouter()
+    const route = useRoute()
+    const projectCode = Number(route.params.projectCode)
+
     const { variables, getTableData } = useTable()
 
     const requestData = () => {
@@ -61,6 +67,12 @@ export default defineComponent({
       requestData()
     }
 
+    const createDefinition = () => {
+      router.push({
+        path: `/projects/${projectCode}/workflow/definitions/create`
+      })
+    }
+
     onMounted(() => {
       requestData()
     })
@@ -69,6 +81,7 @@ export default defineComponent({
       requestData,
       handleSearch,
       handleUpdateList,
+      createDefinition,
       handleChangePageSize,
       ...toRefs(variables)
     }
@@ -81,7 +94,7 @@ export default defineComponent({
         <Card class={styles.card}>
           <div class={styles.header}>
             <NSpace>
-              <NButton type='primary' /* TODO: Create workflow */>
+              <NButton type='primary' onClick={this.createDefinition}>
                 {t('project.workflow.create_workflow')}
               </NButton>
               <NButton strong secondary onClick={() => (this.showRef = true)}>
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/use-table.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/definition/use-table.tsx
similarity index 95%
rename from dolphinscheduler-ui-next/src/views/projects/workflow/definition/use-table.ts
rename to dolphinscheduler-ui-next/src/views/projects/workflow/definition/use-table.tsx
index f2e3044..351cecf 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/use-table.ts
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/definition/use-table.tsx
@@ -17,7 +17,7 @@
 
 import { h, ref, reactive } from 'vue'
 import { useI18n } from 'vue-i18n'
-import { useRouter } from 'vue-router'
+import { useRouter, RouterLink } from 'vue-router'
 import type { Router } from 'vue-router'
 import type { TableColumns } from 'naive-ui/es/data-table/src/interface'
 import { useAsyncState } from '@vueuse/core'
@@ -49,14 +49,21 @@ export function useTable() {
       title: t('project.workflow.workflow_name'),
       key: 'name',
       width: 200,
-      render: (_row) =>
-        h(
-          NEllipsis,
-          { style: 'max-width: 200px' },
-          {
-            default: () => _row.name
-          }
-        )
+      render: (_row) => (
+        <NEllipsis
+          style={{
+            maxWidth: '200px'
+          }}
+        >
+          <RouterLink
+            to={{
+              path: `/projects/${_row.projectCode}/workflow/definitions/${_row.code}`
+            }}
+          >
+            {_row.name}
+          </RouterLink>
+        </NEllipsis>
+      )
     },
     {
       title: t('project.workflow.status'),