You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dolphinscheduler.apache.org by ji...@apache.org on 2021/12/03 05:40:15 UTC

[dolphinscheduler] branch 2.0.1-prepare updated: [2.0.1-cherrypick][Improvement][UI] DAG page interaction optimization (#7137)

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

jinyleechina pushed a commit to branch 2.0.1-prepare
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler.git


The following commit(s) were added to refs/heads/2.0.1-prepare by this push:
     new b2143c3  [2.0.1-cherrypick][Improvement][UI] DAG page interaction optimization (#7137)
b2143c3 is described below

commit b2143c31e68e656ced1c89a613608289bfc0ba21
Author: wangyizhi <wa...@cmss.chinamobile.com>
AuthorDate: Fri Dec 3 13:40:09 2021 +0800

    [2.0.1-cherrypick][Improvement][UI] DAG page interaction optimization (#7137)
    
    * [Feature-6918][UI] DAG page interaction optimization (#6919)
    
    * [Feature] Add grid layout and optimize DAG style
    
    * [Improvement] Add process definition status label and code optimization
    
    * [Feature] add DAG node search bar
    
    * [Feature] Add DAG scale bar
    
    * [Improvement] Open pre-tasks settings for all tasks
    
    * [Fix] Fix ut
    
    * [Fix] Replace /deep/ with ::v-deep
    
    * [Fix] Fix code smell
    
    * [Improvement-7092][UI] Optimize DAG to adapt to situations without locations (#7102)
---
 .../conf/home/pages/dag/_source/canvas/canvas.scss |  29 +-
 .../conf/home/pages/dag/_source/canvas/canvas.vue  | 325 +++++++++------------
 .../pages/dag/_source/canvas/layoutConfigModal.vue | 115 ++++++++
 .../home/pages/dag/_source/canvas/taskbar.scss     |   4 +
 .../conf/home/pages/dag/_source/canvas/taskbar.vue |   9 +
 .../home/pages/dag/_source/canvas/toolbar.scss     |  14 +
 .../conf/home/pages/dag/_source/canvas/toolbar.vue | 101 +++++--
 .../home/pages/dag/_source/canvas/x6-helper.js     | 163 ++++++-----
 .../pages/dag/_source/canvas/x6-style-mixin.js     | 145 +++++++++
 .../home/pages/dag/_source/canvas/x6-style.scss    |  21 +-
 .../src/js/conf/home/pages/dag/_source/dag.vue     |  10 +-
 .../home/pages/dag/_source/formModel/formModel.vue |   1 -
 .../src/js/conf/home/store/dag/actions.js          |  17 +-
 13 files changed, 657 insertions(+), 297 deletions(-)

diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.scss b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.scss
index bcf6a36..ea15323 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.scss
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.scss
@@ -34,14 +34,35 @@
 
     .minimap {
       position: absolute;
-      width: 300px;
-      height: 200px;
-      right: 10px;
-      bottom: 10px;
+      right: 0px;
+      bottom: 0px;
       border: dashed 1px #e4e4e4;
       z-index: 9;
     }
 
+    .scale-slider{
+      position: absolute;
+      height: 140px;
+      width: 70px;
+      right: 0px;
+      bottom: 140px;
+      z-index: 9;
+      display: flex;
+      justify-content: center;
+
+      ::v-deep .el-slider__runway{
+        background-color: #fff;
+      }
+
+      .scale-title{
+        position: absolute;
+        top: -30px;
+        left: 22px;
+        font-size: 12px;
+        color: #666;
+      }
+    }
+
     .context-menu{
       position: absolute;
       left: 100px;
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.vue
index 2e4bc44..67c56a6 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/canvas.vue
@@ -27,8 +27,21 @@
     >
       <div ref="paper" class="paper"></div>
       <div ref="minimap" class="minimap"></div>
+      <div class="scale-slider">
+        <span class="scale-title">{{$t('dagScale')}}</span>
+        <el-slider
+          v-model="scale"
+          vertical
+          :max="2"
+          :min="0.2"
+          :step="0.2"
+          :marks="SCALE_MARKS"
+          @input='scaleChange'
+        />
+      </div>
       <context-menu ref="contextMenu" />
     </div>
+    <layout-config-modal ref="layoutModal" @submit="format" />
   </div>
 </template>
 
@@ -37,23 +50,25 @@
   import { Graph, DataUri } from '@antv/x6'
   import dagTaskbar from './taskbar.vue'
   import contextMenu from './contextMenu.vue'
+  import layoutConfigModal, { LAYOUT_TYPE, DEFAULT_LAYOUT_CONFIG } from './layoutConfigModal.vue'
   import {
-    NODE_PROPS,
-    EDGE_PROPS,
-    PORT_PROPS,
+    NODE,
+    EDGE,
     X6_NODE_NAME,
-    X6_PORT_OUT_NAME,
-    X6_PORT_IN_NAME,
     X6_EDGE_NAME,
-    NODE_HIGHLIGHT_PROPS,
-    PORT_HIGHLIGHT_PROPS,
-    EDGE_HIGHLIGHT_PROPS,
     NODE_STATUS_MARKUP
   } from './x6-helper'
-  import { DagreLayout } from '@antv/layout'
+  import { DagreLayout, GridLayout } from '@antv/layout'
   import { tasksType, tasksState } from '../config'
   import { mapActions, mapMutations, mapState } from 'vuex'
   import nodeStatus from './nodeStatus'
+  import x6StyleMixin from './x6-style-mixin'
+
+  const SCALE_MARKS = {
+    0.2: '0.2',
+    1: '1',
+    2: '2'
+  }
 
   export default {
     name: 'dag-canvas',
@@ -71,7 +86,10 @@
           x: 0,
           y: 0,
           type: ''
-        }
+        },
+        // The canvas scale
+        scale: 1,
+        SCALE_MARKS
       }
     },
     provide () {
@@ -79,10 +97,12 @@
         dagCanvas: this
       }
     },
+    mixins: [x6StyleMixin],
     inject: ['dagChart'],
     components: {
       dagTaskbar,
-      contextMenu
+      contextMenu,
+      layoutConfigModal
     },
     computed: {
       ...mapState('dag', ['tasks'])
@@ -118,6 +138,14 @@
             movable: true,
             showNodeSelectionBox: false
           },
+          scaling: {
+            min: 0.2,
+            max: 2
+          },
+          mousewheel: {
+            enabled: true,
+            modifiers: ['ctrl', 'meta']
+          },
           scroller: true,
           grid: {
             size: 10,
@@ -126,7 +154,10 @@
           snapline: true,
           minimap: {
             enabled: true,
-            container: minimap
+            container: minimap,
+            scalable: false,
+            width: 200,
+            height: 120
           },
           interacting: {
             edgeLabelMovable: false,
@@ -134,9 +165,6 @@
             magnetConnectable: !!editable
           },
           connecting: {
-            snap: {
-              radius: 30
-            },
             // 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
@@ -148,32 +176,14 @@
             // Whether edges are allowed to link to nodes
             allowNode: true,
             // Whether to allow edge links to ports
-            allowPort: true,
+            allowPort: false,
             // Whether all available ports or nodes are highlighted when you drag the edge
             highlight: true,
             createEdge () {
               return graph.createEdge({ shape: X6_EDGE_NAME })
             },
-            validateMagnet ({ magnet }) {
-              return magnet.getAttribute('port-group') !== X6_PORT_IN_NAME
-            },
             validateConnection (data) {
-              const { sourceCell, targetCell, sourceMagnet, targetMagnet } = data
-              // Connections can only be created from the output link post
-              if (
-                !sourceMagnet ||
-                sourceMagnet.getAttribute('port-group') !== X6_PORT_OUT_NAME
-              ) {
-                return false
-              }
-
-              // Can only be connected to the input link post
-              if (
-                !targetMagnet ||
-                targetMagnet.getAttribute('port-group') !== X6_PORT_IN_NAME
-              ) {
-                return false
-              }
+              const { sourceCell, targetCell } = data
 
               if (
                 sourceCell &&
@@ -214,6 +224,7 @@
             }
           }
         }))
+
         this.registerX6Shape()
         this.bindGraphEvent()
         this.originalScrollPosition = graph.getScrollbarPosition()
@@ -224,37 +235,17 @@
       registerX6Shape () {
         Graph.unregisterNode(X6_NODE_NAME)
         Graph.unregisterEdge(X6_EDGE_NAME)
-        Graph.registerNode(X6_NODE_NAME, { ...NODE_PROPS })
-        Graph.registerEdge(X6_EDGE_NAME, { ...EDGE_PROPS })
+        Graph.registerNode(X6_NODE_NAME, { ...NODE })
+        Graph.registerEdge(X6_EDGE_NAME, { ...EDGE })
       },
       /**
        * Bind grap event
        */
       bindGraphEvent () {
-        // nodes and edges hover
-        this.graph.on('cell:mouseenter', (data) => {
-          const { cell, e } = data
-          const isStatusIcon = (tagName) =>
-            tagName &&
-            (tagName.toLocaleLowerCase() === 'em' ||
-              tagName.toLocaleLowerCase() === 'body')
-          if (!isStatusIcon(e.target.tagName)) {
-            this.setHighlight(cell)
-          }
-        })
-        this.graph.on('cell:mouseleave', ({ cell }) => {
-          if (!this.graph.isSelected(cell)) {
-            this.resetHighlight(cell)
-          }
-        })
-        // select
-        this.graph.on('cell:selected', ({ cell }) => {
-          this.setHighlight(cell)
-        })
-        this.graph.on('cell:unselected', ({ cell }) => {
-          if (!this.graph.isSelected(cell)) {
-            this.resetHighlight(cell)
-          }
+        this.bindStyleEvent(this.graph)
+        // update scale bar
+        this.graph.on('scale', ({ sx }) => {
+          this.scale = sx
         })
         // right click
         this.graph.on('node:contextmenu', ({ x, y, cell }) => {
@@ -279,6 +270,13 @@
             label: labelName
           })
         })
+        // Make sure the edge starts with node, not port
+        this.graph.on('edge:connected', ({ isNew, edge }) => {
+          if (isNew) {
+            const sourceNode = edge.getSourceNode()
+            edge.setSource(sourceNode)
+          }
+        })
       },
       /**
        * @param {Edge|string} edge
@@ -297,9 +295,6 @@
       setEdgeLabel (id, label) {
         const edge = this.graph.getCellById(id)
         edge.setLabels(label)
-        if (this.graph.isSelected(edge)) {
-          this.setEdgeHighlight(edge)
-        }
       },
       /**
        * @param {number} limit
@@ -349,94 +344,6 @@
         }
       },
       /**
-       * Set node highlight
-       * @param {Node} node
-       */
-      setNodeHighlight (node) {
-        const url = require(`../images/task-icos/${node.data.taskType.toLocaleLowerCase()}_hover.png`)
-        node.setAttrs(NODE_HIGHLIGHT_PROPS.attrs)
-        node.setAttrByPath('image/xlink:href', url)
-        node.setPortProp(
-          X6_PORT_OUT_NAME,
-          'attrs',
-          PORT_HIGHLIGHT_PROPS[X6_PORT_OUT_NAME].attrs
-        )
-      },
-      /**
-       * Reset node style
-       * @param {Node} node
-       */
-      resetNodeStyle (node) {
-        const url = require(`../images/task-icos/${node.data.taskType.toLocaleLowerCase()}.png`)
-        node.setAttrs(NODE_PROPS.attrs)
-        node.setAttrByPath('image/xlink:href', url)
-        node.setPortProp(
-          X6_PORT_OUT_NAME,
-          'attrs',
-          PORT_PROPS.groups[X6_PORT_OUT_NAME].attrs
-        )
-      },
-      /**
-       * Set edge highlight
-       * @param {Edge} edge
-       */
-      setEdgeHighlight (edge) {
-        const labelName = this.getEdgeLabelName(edge)
-        edge.setAttrs(EDGE_HIGHLIGHT_PROPS.attrs)
-        edge.setLabels([
-          _.merge(
-            {
-              attrs: _.cloneDeep(EDGE_HIGHLIGHT_PROPS.defaultLabel.attrs)
-            },
-            {
-              attrs: { label: { text: labelName } }
-            }
-          )
-        ])
-      },
-      /**
-       * Reset edge style
-       * @param {Edge} edge
-       */
-      resetEdgeStyle (edge) {
-        const labelName = this.getEdgeLabelName(edge)
-        edge.setAttrs(EDGE_PROPS.attrs)
-        edge.setLabels([
-          {
-            ..._.merge(
-              {
-                attrs: _.cloneDeep(EDGE_PROPS.defaultLabel.attrs)
-              },
-              {
-                attrs: { label: { text: labelName } }
-              }
-            )
-          }
-        ])
-      },
-      /**
-       * Set cell highlight
-       * @param {Cell} cell
-       */
-      setHighlight (cell) {
-        if (cell.isEdge()) {
-          this.setEdgeHighlight(cell)
-        } else if (cell.isNode()) {
-          this.setNodeHighlight(cell)
-        }
-      },
-      /**
-       * Reset cell highlight
-       * @param {Cell} cell
-       */
-      resetHighlight (cell) {
-        if (cell.isEdge()) {
-          this.resetEdgeStyle(cell)
-        } else if (cell.isNode()) {
-          this.resetNodeStyle(cell)
-        }
-      },
-      /**
        * Convert the graph to JSON
        * @return {{cells:Cell[]}}
        */
@@ -512,38 +419,70 @@
           }
         )
       },
+      showLayoutModal () {
+        const layoutModal = this.$refs.layoutModal
+        if (layoutModal) {
+          layoutModal.show()
+        }
+      },
       /**
        * format
        * @desc Auto layout use @antv/layout
        */
-      format () {
-        const dagreLayout = new DagreLayout({
-          type: 'dagre',
-          rankdir: 'LR',
-          align: 'UL',
-          // Calculate the node spacing based on the edge label length
-          ranksepFunc: (d) => {
-            const edges = this.graph.getOutgoingEdges(d.id)
-            let max = 0
-            if (edges && edges.length > 0) {
-              edges.forEach((edge) => {
-                const edgeView = this.graph.findViewByCell(edge)
-                const labelWidth = +edgeView.findAttr(
-                  'width',
-                  _.get(edgeView, ['labelSelectors', '0', 'body'], null)
-                )
-                max = Math.max(max, labelWidth)
-              })
-            }
-            return 50 + max
-          },
-          nodesep: 50,
-          controlPoints: true
-        })
+      format (layoutConfig) {
+        if (!layoutConfig) {
+          layoutConfig = DEFAULT_LAYOUT_CONFIG
+        }
+        this.graph.cleanSelection()
+
+        let layoutFunc = null
+        if (layoutConfig.type === LAYOUT_TYPE.DAGRE) {
+          layoutFunc = new DagreLayout({
+            type: LAYOUT_TYPE.DAGRE,
+            rankdir: 'LR',
+            align: 'UL',
+            // Calculate the node spacing based on the edge label length
+            ranksepFunc: (d) => {
+              const edges = this.graph.getOutgoingEdges(d.id)
+              let max = 0
+              if (edges && edges.length > 0) {
+                edges.forEach((edge) => {
+                  const edgeView = this.graph.findViewByCell(edge)
+                  const labelWidth = +edgeView.findAttr(
+                    'width',
+                    _.get(edgeView, ['labelSelectors', '0', 'body'], null)
+                  )
+                  max = Math.max(max, labelWidth)
+                })
+              }
+              return layoutConfig.ranksep + max
+            },
+            nodesep: layoutConfig.nodesep,
+            controlPoints: true
+          })
+        } else if (layoutConfig.type === LAYOUT_TYPE.GRID) {
+          layoutFunc = new GridLayout({
+            type: LAYOUT_TYPE.GRID,
+            preventOverlap: true,
+            preventOverlapPadding: layoutConfig.padding,
+            sortBy: '_index',
+            rows: layoutConfig.rows || undefined,
+            cols: layoutConfig.cols || undefined,
+            nodeSize: 220
+          })
+        }
         const json = this.toJSON()
-        const nodes = json.cells.filter((cell) => cell.shape === X6_NODE_NAME)
+        const nodes = json.cells
+          .filter((cell) => cell.shape === X6_NODE_NAME)
+          .map((item) => {
+            return {
+              ...item,
+              // sort by code aesc
+              _index: -item.id
+            }
+          })
         const edges = json.cells.filter((cell) => cell.shape === X6_EDGE_NAME)
-        const newModel = dagreLayout.layout({
+        const newModel = layoutFunc.layout({
           nodes: nodes,
           edges: edges
         })
@@ -606,12 +545,10 @@
         return {
           shape: X6_EDGE_NAME,
           source: {
-            cell: sourceId,
-            port: X6_PORT_OUT_NAME
+            cell: sourceId
           },
           target: {
-            cell: targetId,
-            port: X6_PORT_IN_NAME
+            cell: targetId
           },
           labels: label ? [label] : undefined
         }
@@ -688,7 +625,7 @@
         if (node) {
           // Destroy the previous dom
           node.removeMarkup()
-          node.setMarkup(NODE_PROPS.markup.concat(NODE_STATUS_MARKUP))
+          node.setMarkup(NODE.markup.concat(NODE_STATUS_MARKUP))
           const nodeView = this.graph.findViewByCell(node)
           const el = nodeView.find('div')[0]
           nodeStatus({
@@ -828,6 +765,28 @@
           const edge = this.genEdgeJSON(code, postCode)
           this.graph.addEdge(edge)
         })
+      },
+      /**
+       * Navigate to cell
+       * @param {string} taskName
+       */
+      navigateTo (taskName) {
+        const nodes = this.getNodes()
+        nodes.forEach((node) => {
+          if (node.data.taskName === taskName) {
+            const id = node.id
+            const cell = this.graph.getCellById(id)
+            this.graph.scrollToCell(cell, { animation: { duration: 600 } })
+            this.graph.cleanSelection()
+            this.graph.select(cell)
+          }
+        })
+      },
+      /**
+       * Canvas scale
+       */
+      scaleChange (val) {
+        this.graph.zoomTo(val)
       }
     }
   }
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/layoutConfigModal.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/layoutConfigModal.vue
new file mode 100644
index 0000000..d13bd68
--- /dev/null
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/layoutConfigModal.vue
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+<template>
+  <el-dialog
+    :title="$t('Format DAG')"
+    :visible.sync="visible"
+    width="500px"
+    class="dag-layout-modal"
+    :append-to-body="true"
+  >
+    <el-form
+      ref="form"
+      :model="form"
+      label-width="100px"
+      class="dag-layout-form"
+    >
+      <el-form-item :label="$t('layoutType')">
+        <el-radio-group v-model="form.type">
+          <el-radio label="grid">{{ $t("gridLayout") }}</el-radio>
+          <el-radio label="dagre">{{ $t("dagreLayout") }}</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item :label="$t('rows')" v-if="form.type === LAYOUT_TYPE.GRID">
+        <el-input-number
+          v-model="form.rows"
+          :min="0"
+          size="small"
+        ></el-input-number>
+      </el-form-item>
+      <el-form-item :label="$t('cols')" v-if="form.type === LAYOUT_TYPE.GRID">
+        <el-input-number
+          v-model="form.cols"
+          :min="0"
+          size="small"
+        ></el-input-number>
+      </el-form-item>
+    </el-form>
+    <span slot="footer" class="dialog-footer">
+      <el-button size="small" @click="close">{{ $t("Cancel") }}</el-button>
+      <el-button size="small" type="primary" @click="submit">{{
+        $t("Confirm")
+      }}</el-button>
+    </span>
+  </el-dialog>
+</template>
+<script>
+  export const LAYOUT_TYPE = {
+    GRID: 'grid',
+    DAGRE: 'dagre'
+  }
+
+  export const DEFAULT_LAYOUT_CONFIG = {
+    cols: 0,
+    nodesep: 50,
+    padding: 50,
+    ranksep: 50,
+    rows: 0,
+    type: LAYOUT_TYPE.DAGRE
+  }
+
+  export default {
+    data () {
+      return {
+        visible: false,
+        form: { ...DEFAULT_LAYOUT_CONFIG },
+        LAYOUT_TYPE
+      }
+    },
+    methods: {
+      show () {
+        this.visible = true
+      },
+      close () {
+        this.visible = false
+      },
+      submit () {
+        this.$emit('submit', this.form)
+        this.close()
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+.dag-layout-modal {
+  ::v-deep .el-dialog__header {
+    border-bottom: solid 1px #d4d4d4;
+  }
+
+  ::v-deep .dag-layout-form {
+    margin-top: 20px;
+  }
+
+  ::v-deep .el-radio {
+    margin-bottom: 0;
+  }
+
+  .el-form-item {
+    margin-bottom: 10px;
+  }
+}
+</style>
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.scss b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.scss
index 9f1c6ee..aec283f 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.scss
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.scss
@@ -174,6 +174,10 @@
           }
         }
       }
+
+      &.disabled{
+        cursor: default
+      }
     }
   }
 }
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.vue
index 0ffc40c..7c5b665 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.vue
@@ -24,6 +24,9 @@
         <draggable-box
           :key="taskType.name"
           @onDragstart="(e) => $emit('on-drag-start', e, taskType)"
+          :class="{
+            disabled: isDetails
+          }"
         >
           <div class="task-item">
             <em :class="`icos-${taskType.name.toLocaleLowerCase()}`"></em>
@@ -38,6 +41,7 @@
 <script>
   import draggableBox from './draggableBox.vue'
   import { tasksType } from '../config.js'
+  import { mapState } from 'vuex'
 
   export default {
     name: 'dag-taskbar',
@@ -55,6 +59,11 @@
       return {
         tasksTypeList
       }
+    },
+    computed: {
+      ...mapState('dag', [
+        'isDetails'
+      ])
     }
   }
 </script>
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.scss b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.scss
index 03578f3..155083c 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.scss
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.scss
@@ -110,4 +110,18 @@
       }
     }
   }
+
+  .process-online-tag{
+    margin-left: 10px;
+  }
+
+  .search-box{
+    width: 0;
+    overflow: hidden;
+    transition: all 0.5s;
+
+    &.visible{
+      width: 200px;
+    }
+  }
 }
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.vue
index 4071fd9..d8f57ad 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.vue
@@ -23,76 +23,111 @@
       :content="$t('Copy name')"
       placement="bottom"
     >
-      <i class="el-icon-copy-document" @click="copyName"></i>
+      <em class="el-icon-copy-document" @click="copyName"></em>
     </el-tooltip>
     <textarea ref="textarea" cols="30" rows="10" class="transparent"></textarea>
     <div class="toolbar-left">
+      <el-tag
+        class="process-online-tag"
+        size="small"
+        v-if="dagChart.type === 'definition' && releaseState === 'ONLINE'"
+        >{{ $t("processOnline") }}</el-tag
+      >
       <el-tooltip
         :content="$t('View variables')"
         placement="bottom"
         class="toolbar-operation"
       >
-        <i
+        <em
           class="custom-ico view-variables"
           v-if="$route.name === 'projects-instance-details'"
           @click="toggleVariableView"
-        ></i>
+        ></em>
       </el-tooltip>
       <el-tooltip
         :content="$t('Startup parameter')"
         placement="bottom"
         class="toolbar-operation"
       >
-        <i
+        <em
           class="custom-ico startup-parameters"
           v-if="$route.name === 'projects-instance-details'"
           @click="toggleParamView"
-        ></i>
+        ></em>
       </el-tooltip>
     </div>
     <div class="toolbar-right">
       <el-tooltip
         class="toolbar-operation"
+        :content="$t('searchNode')"
+        placement="bottom"
+        v-if="!searchInputVisible"
+      >
+        <em
+          class="el-icon-search"
+          @click="showSearchInput"
+        ></em>
+      </el-tooltip>
+      <div
+        :class="{
+          'search-box': true,
+          'visible': searchInputVisible
+        }"
+      >
+        <el-input
+          v-model="searchText"
+          placeholder=""
+          prefix-icon="el-icon-search"
+          size="mini"
+          @keyup.enter.native="onSearch"
+          clearable
+          @blur="searchInputBlur"
+          ref="searchInput"
+        ></el-input>
+      </div>
+      <el-tooltip
+        class="toolbar-operation"
         :content="$t('Delete selected lines or nodes')"
         placement="bottom"
         v-if="!isDetails"
       >
-        <i class="el-icon-delete" @click="removeCells"></i>
+        <em class="el-icon-delete" @click="removeCells"></em>
       </el-tooltip>
       <el-tooltip
         class="toolbar-operation"
         :content="$t('Download')"
         placement="bottom"
       >
-        <i class="el-icon-download" @click="downloadPNG"></i>
+        <em class="el-icon-download" @click="downloadPNG"></em>
       </el-tooltip>
       <el-tooltip
         class="toolbar-operation"
-        :content="$t('Full Screen')"
+        :content="$t('Refresh DAG status')"
         placement="bottom"
+        v-if="dagChart.type === 'instance'"
       >
-        <i
-          :class="[
-            'custom-ico',
-            dagChart.fullScreen ? 'full-screen-close' : 'full-screen-open',
-          ]"
-          @click="toggleFullScreen"
-        ></i>
+        <em class="el-icon-refresh" @click="refreshTaskStatus"></em>
       </el-tooltip>
       <el-tooltip
         class="toolbar-operation"
-        :content="$t('Refresh DAG status')"
+        :content="$t('Format DAG')"
         placement="bottom"
-        v-if="dagChart.type === 'instance'"
+        v-if="!isDetails"
       >
-        <i class="el-icon-refresh" @click="refreshTaskStatus"></i>
+        <em class="custom-ico graph-format" @click="chartFormat"></em>
       </el-tooltip>
       <el-tooltip
         class="toolbar-operation last"
-        :content="$t('Format DAG')"
+        :content="$t('Full Screen')"
         placement="bottom"
       >
-        <i class="custom-ico graph-format" @click="chartFormat"></i>
+        <em
+          :class="[
+            'custom-ico',
+            dagChart.fullScreen ? 'full-screen-close' : 'full-screen-open',
+          ]"
+          @click="toggleFullScreen"
+        ></em>
       </el-tooltip>
       <el-button
         class="toolbar-el-btn"
@@ -101,7 +136,7 @@
         v-if="dagChart.type === 'definition'"
         @click="showVersions"
         icon="el-icon-info"
-        >{{$t('Version Info')}}</el-button
+        >{{ $t("Version Info") }}</el-button
       >
       <el-button
         class="toolbar-el-btn"
@@ -125,7 +160,6 @@
         type="primary"
         icon="el-icon-switch-button"
         size="mini"
-        v-if="type === 'instance' || 'definition'"
         @click="returnToListPage"
       >
         {{ $t("Close") }}
@@ -143,15 +177,28 @@
     inject: ['dagChart'],
     data () {
       return {
-        canvasRef: null
+        canvasRef: null,
+        searchText: '',
+        searchInputVisible: false
       }
     },
     computed: {
-      ...mapState('dag', [
-        'isDetails'
-      ])
+      ...mapState('dag', ['isDetails', 'releaseState'])
     },
     methods: {
+      onSearch () {
+        const canvas = this.getDagCanvasRef()
+        canvas.navigateTo(this.searchText)
+      },
+      showSearchInput () {
+        this.searchInputVisible = true
+        this.$refs.searchInput.focus()
+      },
+      searchInputBlur () {
+        if (!this.searchText) {
+          this.searchInputVisible = false
+        }
+      },
       getDagCanvasRef () {
         if (this.canvasRef) {
           return this.canvasRef
@@ -200,7 +247,7 @@
       },
       chartFormat () {
         const canvas = this.getDagCanvasRef()
-        canvas.format()
+        canvas.showLayoutModal()
       },
       refreshTaskStatus () {
         this.dagChart.refreshTaskStatus()
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-helper.js b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-helper.js
index 96a48eb..1be067f 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-helper.js
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-helper.js
@@ -17,16 +17,17 @@
 export const X6_NODE_NAME = 'dag-task'
 export const X6_EDGE_NAME = 'dag-edge'
 export const X6_PORT_OUT_NAME = 'dag-port-out'
-export const X6_PORT_IN_NAME = 'dag-port-in'
 
-const EDGE = '#999999'
-const BG_BLUE = 'rgba(40, 143, 255, 0.1)'
+const EDGE_COLOR = '#999999'
+const BG_BLUE = '#DFE9F7'
 const BG_WHITE = '#FFFFFF'
-const NODE_BORDER = '#e4e4e4'
-const TITLE = '#333'
+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_PROPS = {
+export const PORT = {
   groups: {
     [X6_PORT_OUT_NAME]: {
       position: {
@@ -62,14 +63,14 @@ export const PORT_PROPS = {
         },
         'plus-text': {
           fontSize: 12,
-          fill: EDGE,
+          fill: NODE_BORDER,
           text: '+',
           textAnchor: 'middle',
           x: 0,
           y: 3
         },
         'circle-outer': {
-          stroke: EDGE,
+          stroke: NODE_BORDER,
           strokeWidth: 1,
           r: 6,
           fill: BG_WHITE
@@ -79,57 +80,42 @@ export const PORT_PROPS = {
           fill: 'transparent'
         }
       }
-    },
-    [X6_PORT_IN_NAME]: {
-      position: {
-        name: 'absolute',
-        args: {
-          x: 0,
-          y: 24
-        }
-      },
-      markup: [
-        {
-          tagName: 'g',
-          selector: 'body',
-          className: 'in-port-body',
-          children: [{
-            tagName: 'circle',
-            selector: 'circle',
-            className: 'circle'
-          }]
-        }
-      ],
+    }
+  }
+}
+
+export const PORT_HOVER = {
+  groups: {
+    [X6_PORT_OUT_NAME]: {
       attrs: {
-        body: {
-          magnet: true
+        'circle-outer': {
+          stroke: STROKE_BLUE,
+          fill: BG_BLUE,
+          r: 8
         },
-        circle: {
-          r: 4,
-          strokeWidth: 0,
-          fill: 'transparent'
+        'circle-inner': {
+          fill: STROKE_BLUE,
+          r: 6
         }
       }
     }
   }
 }
 
-export const PORT_HIGHLIGHT_PROPS = {
-  [X6_PORT_OUT_NAME]: {
-    attrs: {
-      'circle-outer': {
-        stroke: STROKE_BLUE,
-        fill: BG_BLUE
-      },
-      'plus-text': {
-        fill: STROKE_BLUE
-      },
-      'circle-inner': {
-        fill: STROKE_BLUE
+export const PORT_SELECTED = {
+  groups: {
+    [X6_PORT_OUT_NAME]: {
+      attrs: {
+        'plus-text': {
+          fill: STROKE_BLUE
+        },
+        'circle-outer': {
+          stroke: STROKE_BLUE,
+          fill: BG_WHITE
+        }
       }
     }
-  },
-  [X6_PORT_IN_NAME]: {}
+  }
 }
 
 export const NODE_STATUS_MARKUP = [{
@@ -148,13 +134,14 @@ export const NODE_STATUS_MARKUP = [{
   ]
 }]
 
-export const NODE_PROPS = {
+export const NODE = {
   width: 220,
   height: 48,
   markup: [
     {
       tagName: 'rect',
-      selector: 'body'
+      selector: 'body',
+      className: 'dag-task-body'
     },
     {
       tagName: 'image',
@@ -174,7 +161,9 @@ export const NODE_PROPS = {
       pointerEvents: 'visiblePainted',
       fill: BG_WHITE,
       stroke: NODE_BORDER,
-      strokeWidth: 1
+      strokeWidth: 1,
+      strokeDasharray: 'none',
+      filter: 'none'
     },
     image: {
       width: 30,
@@ -199,21 +188,17 @@ export const NODE_PROPS = {
     }
   },
   ports: {
-    ...PORT_PROPS,
+    ...PORT,
     items: [
       {
         id: X6_PORT_OUT_NAME,
         group: X6_PORT_OUT_NAME
-      },
-      {
-        id: X6_PORT_IN_NAME,
-        group: X6_PORT_IN_NAME
       }
     ]
   }
 }
 
-export const NODE_HIGHLIGHT_PROPS = {
+export const NODE_HOVER = {
   attrs: {
     body: {
       fill: BG_BLUE,
@@ -226,28 +211,42 @@ export const NODE_HIGHLIGHT_PROPS = {
   }
 }
 
-export const EDGE_PROPS = {
+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,
-      strokeWidth: 0.8,
+      stroke: EDGE_COLOR,
+      strokeWidth: 1,
       targetMarker: {
         tagName: 'path',
-        fill: EDGE,
+        fill: EDGE_COLOR,
         strokeWidth: 0,
         d: 'M 6 -3 0 0 6 3 Z'
-      }
+      },
+      filter: 'none'
     }
   },
   connector: {
     name: 'rounded'
   },
   router: {
-    name: 'er',
+    name: 'manhattan',
     args: {
-      offset: 20,
-      min: 20,
-      direction: 'L'
+      endDirections: ['top', 'bottom', 'left']
     }
   },
   defaultLabel: {
@@ -263,7 +262,7 @@ export const EDGE_PROPS = {
     ],
     attrs: {
       label: {
-        fill: EDGE,
+        fill: EDGE_COLOR,
         fontSize: 14,
         textAnchor: 'middle',
         textVerticalAnchor: 'middle',
@@ -272,7 +271,7 @@ export const EDGE_PROPS = {
       body: {
         ref: 'label',
         fill: BG_WHITE,
-        stroke: EDGE,
+        stroke: EDGE_COLOR,
         strokeWidth: 1,
         rx: 4,
         ry: 4,
@@ -292,7 +291,7 @@ export const EDGE_PROPS = {
   }
 }
 
-export const EDGE_HIGHLIGHT_PROPS = {
+export const EDGE_HOVER = {
   attrs: {
     line: {
       stroke: STROKE_BLUE,
@@ -313,3 +312,27 @@ export const EDGE_HIGHLIGHT_PROPS = {
     }
   }
 }
+
+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
+      }
+    }
+  }
+}
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style-mixin.js b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style-mixin.js
new file mode 100644
index 0000000..d5f9f7b
--- /dev/null
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style-mixin.js
@@ -0,0 +1,145 @@
+/*
+ * 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 {
+  NODE,
+  EDGE,
+  PORT,
+  NODE_HOVER,
+  PORT_HOVER,
+  EDGE_HOVER,
+  PORT_SELECTED,
+  NODE_SELECTED,
+  EDGE_SELECTED,
+  X6_PORT_OUT_NAME
+} from './x6-helper'
+import _ from 'lodash'
+
+export default {
+  data () {
+    return {
+      hoverCell: null
+    }
+  },
+  methods: {
+    bindStyleEvent (graph) {
+      // nodes and edges hover
+      graph.on('cell:mouseenter', (data) => {
+        const { cell, e } = data
+        const isStatusIcon = (tagName) =>
+          tagName &&
+          (tagName.toLocaleLowerCase() === 'em' ||
+            tagName.toLocaleLowerCase() === 'body')
+        if (!isStatusIcon(e.target.tagName)) {
+          this.hoverCell = cell
+          this.updateCellStyle(cell, graph)
+        }
+      })
+      graph.on('cell:mouseleave', ({ cell }) => {
+        this.hoverCell = null
+        this.updateCellStyle(cell, graph)
+      })
+      // select
+      graph.on('cell:selected', ({ cell }) => {
+        this.updateCellStyle(cell, graph)
+      })
+      graph.on('cell:unselected', ({ cell }) => {
+        this.updateCellStyle(cell, graph)
+      })
+    },
+    updateCellStyle (cell, graph) {
+      if (cell.isEdge()) {
+        this.setEdgeStyle(cell, graph)
+      } else if (cell.isNode()) {
+        this.setNodeStyle(cell, graph)
+      }
+    },
+    /**
+     * Set node style
+     * @param {Node} node
+     * @param {Graph} graph
+     */
+    setNodeStyle (node, graph) {
+      const isHover = node === this.hoverCell
+      const isSelected = graph.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 = require(`../images/task-icos/${node.data.taskType.toLocaleLowerCase()}_hover.png`)
+        if (isHover) {
+          nodeAttrs = nodeHover
+          portAttrs = _.merge(portDefault, portHover)
+        } else {
+          nodeAttrs = nodeSelected
+          portAttrs = _.merge(portDefault, portSelected)
+        }
+      } else {
+        img = require(`../images/task-icos/${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
+      )
+    },
+    /**
+     * Set edge style
+     * @param {Edge} edge
+     * @param {Graph} graph
+     */
+    setEdgeStyle (edge, graph) {
+      const isHover = edge === this.hoverCell
+      const isSelected = graph.isSelected(edge)
+      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 } }
+            }
+          )
+        }
+      ])
+    }
+  }
+}
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style.scss b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style.scss
index b86b51a..bc56d51 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style.scss
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/x6-style.scss
@@ -14,16 +14,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-$STROKE_BLUE: #288FFF;
-$BG_WHITE: #FFFFFF;
+$STROKE_BLUE: #288fff;
+$BG_WHITE: #ffffff;
 
-.x6-node[data-shape="dag-task"]{
-  .in-port-body{
-    &.adsorbed,&.available{
-      .circle {
-        stroke: $STROKE_BLUE;
-        stroke-width: 4;
-        fill: $BG_WHITE;
+.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;
       }
     }
   }
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue
index 77946e7..1489020 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue
@@ -60,6 +60,10 @@
     </el-dialog>
     <edge-edit-model ref="edgeEditModel" />
     <el-drawer :visible.sync="versionDrawer" size="" :with-header="false">
+      <!-- fix the bug that Element-ui(2.13.2) auto focus on the first input -->
+      <div style="width: 0px; height: 0px; overflow: hidden">
+        <el-input type="text" />
+      </div>
       <m-versions
         :versionData="versionData"
         :isInstance="type === 'instance'"
@@ -187,7 +191,6 @@
     },
     beforeDestroy () {
       this.resetParams()
-
       clearInterval(this.statusTimer)
       window.removeEventListener('resize', this.resizeDebounceFunc)
     },
@@ -400,6 +403,7 @@
       buildGraphJSON (tasks, locations, connects) {
         const nodes = []
         const edges = []
+        if (!locations) { locations = [] }
         tasks.forEach((task) => {
           const location = locations.find((l) => l.taskCode === task.code) || {}
           const node = this.$refs.canvas.genNodeJSON(
@@ -484,6 +488,10 @@
         const connects = this.connects
         const json = this.buildGraphJSON(tasks, locations, connects)
         this.$refs.canvas.fromJSON(json)
+        // Auto format
+        if (!locations) {
+          this.$refs.canvas.format()
+        }
       },
       /**
        * Return to the previous process
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue
index fcafefc..0552a4d 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue
@@ -398,7 +398,6 @@
         <!-- Pre-tasks in workflow -->
         <m-pre-tasks
           ref="preTasks"
-          v-if="['SHELL', 'SUB_PROCESS'].indexOf(nodeData.taskType) > -1"
           :code="code"
         />
       </div>
diff --git a/dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js b/dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js
index a78df48..fda65d9 100644
--- a/dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js
+++ b/dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js
@@ -18,6 +18,16 @@
 import _ from 'lodash'
 import io from '@/module/io'
 
+// Avoid passing in illegal values when users directly call third-party interfaces
+const convertLocations = (locationStr) => {
+  let locations = null
+  if (!locationStr) return locations
+  try {
+    locations = JSON.parse(locationStr)
+  } catch (error) {}
+  return Array.isArray(locations) ? locations : null
+}
+
 export default {
   /**
    *  Task status acquisition
@@ -133,12 +143,14 @@ export default {
         state.version = res.data.processDefinition.version
         // name
         state.name = res.data.processDefinition.name
+        // releaseState
+        state.releaseState = res.data.processDefinition.releaseState
         // description
         state.description = res.data.processDefinition.description
         // taskRelationJson
         state.connects = res.data.processTaskRelationList
         // locations
-        state.locations = JSON.parse(res.data.processDefinition.locations)
+        state.locations = convertLocations(res.data.processDefinition.locations)
         // global params
         state.globalParams = res.data.processDefinition.globalParamList
         // timeout
@@ -164,6 +176,7 @@ export default {
           'timeout',
           'environmentCode'
         ]))
+
         resolve(res.data)
       }).catch(res => {
         reject(res)
@@ -235,7 +248,7 @@ export default {
         // connects
         state.connects = processTaskRelationList
         // locations
-        state.locations = JSON.parse(processDefinition.locations)
+        state.locations = convertLocations(processDefinition.locations)
         // global params
         state.globalParams = processDefinition.globalParamList
         // timeout