You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dolphinscheduler.apache.org by so...@apache.org on 2022/03/04 09:38:47 UTC

[dolphinscheduler] branch dev updated: [Feature][UI Next][V1.0.0-Alpha] Support gantt (#8697)

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

songjian pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler.git


The following commit(s) were added to refs/heads/dev by this push:
     new c8b5d1e  [Feature][UI Next][V1.0.0-Alpha] Support gantt (#8697)
c8b5d1e is described below

commit c8b5d1e5088241faaf29f61a5625d33fffb27a8e
Author: Devosend <de...@gmail.com>
AuthorDate: Fri Mar 4 17:37:40 2022 +0800

    [Feature][UI Next][V1.0.0-Alpha] Support gantt (#8697)
---
 .../src/router/modules/projects.ts                 |  10 +
 .../src/service/modules/process-instances/index.ts |   2 +-
 dolphinscheduler-ui-next/src/utils/common.ts       |   2 +-
 .../workflow/instance/components/table-action.tsx  |  11 +-
 .../instance/gantt/components/gantt-chart.tsx      | 214 +++++++++++++++++++++
 .../projects/workflow/instance/gantt/index.tsx     |  73 +++++++
 .../views/projects/workflow/instance/gantt/type.ts |  39 ++++
 .../projects/workflow/instance/gantt/use-gantt.ts  |  53 +++++
 8 files changed, 401 insertions(+), 3 deletions(-)

diff --git a/dolphinscheduler-ui-next/src/router/modules/projects.ts b/dolphinscheduler-ui-next/src/router/modules/projects.ts
index 683517c..afc401d 100644
--- a/dolphinscheduler-ui-next/src/router/modules/projects.ts
+++ b/dolphinscheduler-ui-next/src/router/modules/projects.ts
@@ -122,6 +122,16 @@ export default {
       }
     },
     {
+      path: '/projects/:projectCode/workflow/instances/:id/gantt',
+      name: 'workflow-instance-gantt',
+      component: components['projects-workflow-instance-gantt'],
+      meta: {
+        title: '工作流实例甘特图',
+        showSide: true,
+        auth: []
+      }
+    },
+    {
       path: '/projects/:projectCode/task/definitions',
       name: 'task-definition',
       component: components['projects-task-definition'],
diff --git a/dolphinscheduler-ui-next/src/service/modules/process-instances/index.ts b/dolphinscheduler-ui-next/src/service/modules/process-instances/index.ts
index 67b6323..42578ab 100644
--- a/dolphinscheduler-ui-next/src/service/modules/process-instances/index.ts
+++ b/dolphinscheduler-ui-next/src/service/modules/process-instances/index.ts
@@ -118,7 +118,7 @@ export function queryTaskListByProcessId(id: number, code: number): any {
   })
 }
 
-export function vieGanttTree(id: IdReq, code: CodeReq): any {
+export function viewGanttTree(id: number, code: number): any {
   return axios({
     url: `/projects/${code}/process-instances/${id}/view-gantt`,
     method: 'get'
diff --git a/dolphinscheduler-ui-next/src/utils/common.ts b/dolphinscheduler-ui-next/src/utils/common.ts
index e5675b9..f328dbb 100644
--- a/dolphinscheduler-ui-next/src/utils/common.ts
+++ b/dolphinscheduler-ui-next/src/utils/common.ts
@@ -261,7 +261,7 @@ export const tasksState = (t: any): ITaskState => ({
   SUCCESS: {
     id: 7,
     desc: `${t('project.workflow.success')}`,
-    color: '#33cc00',
+    color: '#95DF96',
     icon: CheckCircleOutlined,
     isSpin: false,
     classNames: 'success'
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/instance/components/table-action.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/components/table-action.tsx
index a480041..faa9e60 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/instance/components/table-action.tsx
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/components/table-action.tsx
@@ -62,6 +62,14 @@ export default defineComponent({
       })
     }
 
+    const handleGantt = () => {
+      router.push({
+        name: 'workflow-instance-gantt',
+        params: { id: props.row!.id },
+        query: { code: props.row!.processDefinitionCode }
+      })
+    }
+
     const handleReRun = () => {
       ctx.emit('reRun')
     }
@@ -89,6 +97,7 @@ export default defineComponent({
       handleStop,
       handleSuspend,
       handleDeleteInstance,
+      handleGantt,
       ...toRefs(props)
     }
   },
@@ -280,8 +289,8 @@ export default defineComponent({
                 size='tiny'
                 type='info'
                 circle
-                /* TODO: Goto gantt*/
                 disabled={this.row?.disabled}
+                onClick={this.handleGantt}
               >
                 <NIcon>
                   <ControlOutlined />
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/instance/gantt/components/gantt-chart.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/gantt/components/gantt-chart.tsx
new file mode 100644
index 0000000..f4785b6
--- /dev/null
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/gantt/components/gantt-chart.tsx
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { defineComponent, ref, PropType } from 'vue'
+import * as echarts from 'echarts'
+import type { Ref } from 'vue'
+import { useI18n } from 'vue-i18n'
+import initChart from '@/components/chart'
+import { tasksState } from '@/utils/common'
+import { format } from 'date-fns'
+import { parseTime } from '@/utils/common'
+import { ISeriesData } from '../type'
+
+const props = {
+  height: {
+    type: [String, Number] as PropType<string | number>,
+    default: window.innerHeight - 174
+  },
+  width: {
+    type: [String, Number] as PropType<string | number>,
+    default: '100%'
+  },
+  seriesData: {
+    type: Array as PropType<Array<any>>,
+    default: () => []
+  },
+  taskList: {
+    type: Array as PropType<Array<string>>,
+    default: []
+  }
+}
+
+const GanttChart = defineComponent({
+  name: 'GanttChart',
+  props,
+  setup(props) {
+    const graphChartRef: Ref<HTMLDivElement | null> = ref(null)
+    const { t } = useI18n()
+
+    const state = tasksState(t)
+
+    const data: ISeriesData = {}
+    Object.keys(state).forEach((key) => (data[key] = []))
+    const series = Object.keys(state).map((key) => ({
+      id: key,
+      type: 'custom',
+      name: state[key].desc,
+      renderItem: renderItem,
+      itemStyle: {
+        opacity: 0.8,
+        color: state[key].color,
+        color0: state[key].color
+      },
+      encode: {
+        x: [1, 2],
+        y: 0
+      },
+      data: data[key]
+    }))
+
+    // format series data
+    let minTime = Number(new Date())
+    props.seriesData.forEach(function (task, index) {
+      minTime = minTime < task.startDate[0] ? minTime : task.startDate[0]
+      data[task.status].push({
+        name: task.name,
+        value: [
+          index,
+          task.startDate[0],
+          task.endDate[0],
+          task.endDate[0] - task.startDate[0]
+        ],
+        itemStyle: {
+          color: state[task.status].color
+        }
+      })
+    })
+
+    // customer render
+    function renderItem(params: any, api: any) {
+      const taskIndex = api.value(0)
+      const start = api.coord([api.value(1), taskIndex])
+      const end = api.coord([api.value(2), taskIndex])
+      const height = api.size([0, 1])[1] * 0.6
+
+      const rectShape = echarts.graphic.clipRectByRect(
+        {
+          x: start[0],
+          y: start[1] - height / 2,
+          width: end[0] - start[0],
+          height: height
+        },
+        {
+          x: params.coordSys.x,
+          y: params.coordSys.y,
+          width: params.coordSys.width,
+          height: params.coordSys.height
+        }
+      )
+      return (
+        rectShape && {
+          type: 'rect',
+          transition: ['shape'],
+          shape: rectShape,
+          style: api.style()
+        }
+      )
+    }
+
+    const option = {
+      title: {
+        text: t('project.workflow.task_state'),
+        textStyle: {
+          fontWeight: 'normal',
+          fontSize: 14
+        },
+        left: 50
+      },
+      tooltip: {
+        formatter: function (params: any) {
+          const taskName = params.data.name
+          const data = props.seriesData.filter(
+            (item) => item.taskName === taskName
+          )
+          let str = `taskName : ${taskName}</br>`
+          str += `status : ${state[data[0].status].desc} (${
+            data[0].status
+          })</br>`
+          str += `startTime : ${data[0].isoStart}</br>`
+          str += `endTime : ${data[0].isoEnd}</br>`
+          str += `duration : ${data[0].duration}</br>`
+          return str
+        }
+      },
+      legend: {
+        left: 150,
+        padding: [5, 5, 5, 5]
+      },
+      dataZoom: [
+        {
+          type: 'slider',
+          filterMode: 'weakFilter',
+          showDataShadow: false,
+          top:
+            props.taskList.length * 100 > 200
+              ? props.taskList.length * 100
+              : 200,
+          labelFormatter: ''
+        },
+        {
+          type: 'inside',
+          filterMode: 'weakFilter'
+        }
+      ],
+      grid: {
+        height: props.taskList.length * 50,
+        top: 80
+      },
+      xAxis: {
+        min: minTime,
+        scale: true,
+        position: 'top',
+        axisTick: { show: true },
+        splitLine: { show: false },
+        axisLabel: {
+          formatter: function (val: number) {
+            return format(parseTime(val), 'HH:mm:ss')
+          }
+        }
+      },
+      yAxis: {
+        axisTick: { show: false },
+        splitLine: { show: false },
+        axisLine: { show: false },
+        max: props.taskList.length,
+        data: props.taskList
+      },
+      series: series
+    }
+
+    initChart(graphChartRef, option)
+
+    return { graphChartRef }
+  },
+  render() {
+    const { height, width } = this
+
+    return (
+      <div
+        ref='graphChartRef'
+        style={{
+          height: typeof height === 'number' ? height + 'px' : height,
+          width: typeof width === 'number' ? width + 'px' : width
+        }}
+      />
+    )
+  }
+})
+
+export default GanttChart
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/instance/gantt/index.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/gantt/index.tsx
new file mode 100644
index 0000000..9f68e37
--- /dev/null
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/gantt/index.tsx
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { defineComponent, onMounted, toRefs, watch } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { useRoute } from 'vue-router'
+import Card from '@/components/card'
+import GanttChart from './components/gantt-chart'
+import { useGantt } from './use-gantt'
+
+const workflowRelation = defineComponent({
+  name: 'workflow-relation',
+  setup() {
+    const { t, locale } = useI18n()
+    const route = useRoute()
+
+    const { variables, getGantt } = useGantt()
+
+    const id = Number(route.params.id)
+    const code = Number(route.params.projectCode)
+
+    const handleResetDate = () => {
+      variables.seriesData = []
+      variables.taskList = []
+      getGantt(id, code)
+    }
+
+    onMounted(() => {
+      getGantt(id, code)
+    })
+
+    watch(
+      () => [locale.value],
+      () => {
+        handleResetDate()
+      }
+    )
+
+    return { t, ...toRefs(variables) }
+  },
+  render() {
+    const { t } = this
+    return (
+      <Card title={t('project.workflow.gantt')}>
+        {{
+          default: () =>
+            this.seriesData.length > 0 && (
+              <GanttChart
+                seriesData={this.seriesData}
+                taskList={this.taskList}
+              />
+            )
+        }}
+      </Card>
+    )
+  }
+})
+
+export default workflowRelation
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/instance/gantt/type.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/gantt/type.ts
new file mode 100644
index 0000000..24068b5
--- /dev/null
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/gantt/type.ts
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+interface ITask {
+  taskName: string
+  startDate: Array<number>
+  endDate: Array<number>
+  isoStart: string
+  isoEnd: string
+  status: string
+  duration: string
+}
+
+interface IGanttRes {
+  height: number
+  taskNames: Array<number>
+  taskStatus: Object
+  tasks: Array<ITask>
+}
+
+interface ISeriesData {
+  [taskState: string]: Array<any>
+}
+
+export { ITask, IGanttRes, ISeriesData }
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/instance/gantt/use-gantt.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/gantt/use-gantt.ts
new file mode 100644
index 0000000..1560d45
--- /dev/null
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/gantt/use-gantt.ts
@@ -0,0 +1,53 @@
+/*
+ * 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 { reactive } from 'vue'
+import { useAsyncState } from '@vueuse/core'
+import { viewGanttTree } from '@/service/modules/process-instances'
+import { IGanttRes } from './type'
+
+export function useGantt() {
+  const variables = reactive({
+    seriesData: [],
+    taskList: [] as Array<string>
+  })
+
+  const formatGantt = (obj: IGanttRes) => {
+    variables.seriesData = []
+    variables.taskList = []
+
+    variables.seriesData = obj.tasks.map((item) => {
+      variables.taskList.push(item.taskName)
+      return {
+        name: item.taskName,
+        ...item
+      }
+    }) as any
+  }
+
+  const getGantt = (id: number, code: number) => {
+    const { state } = useAsyncState(
+      viewGanttTree(id, code).then((res: IGanttRes) => {
+        formatGantt(res)
+      }),
+      {}
+    )
+    return state
+  }
+
+  return { variables, getGantt }
+}