diff --git a/dinky-admin/src/main/java/org/dinky/data/dto/JobDataDto.java b/dinky-admin/src/main/java/org/dinky/data/dto/JobDataDto.java index 83fa2b8636..c8781d61ca 100644 --- a/dinky-admin/src/main/java/org/dinky/data/dto/JobDataDto.java +++ b/dinky-admin/src/main/java/org/dinky/data/dto/JobDataDto.java @@ -19,10 +19,10 @@ package org.dinky.data.dto; +import org.dinky.data.flink.config.FlinkJobConfigInfo; +import org.dinky.data.flink.exceptions.FlinkJobExceptionsDetail; +import org.dinky.data.flink.job.FlinkJobDetailInfo; import org.dinky.data.model.JobHistory; -import org.dinky.data.model.flink.config.FlinkJobConfigInfo; -import org.dinky.data.model.flink.exceptions.FlinkJobExceptionsDetail; -import org.dinky.data.model.flink.job.FlinkJobDetailInfo; import org.dinky.utils.JsonUtils; import java.time.LocalDateTime; diff --git a/dinky-admin/src/main/java/org/dinky/job/handler/JobRefreshHandler.java b/dinky-admin/src/main/java/org/dinky/job/handler/JobRefreshHandler.java index 9d422fd252..ea29e096c8 100644 --- a/dinky-admin/src/main/java/org/dinky/job/handler/JobRefreshHandler.java +++ b/dinky-admin/src/main/java/org/dinky/job/handler/JobRefreshHandler.java @@ -26,13 +26,13 @@ import org.dinky.data.dto.ClusterConfigurationDTO; import org.dinky.data.dto.JobDataDto; import org.dinky.data.enums.JobStatus; +import org.dinky.data.flink.backpressure.FlinkJobNodeBackPressure; +import org.dinky.data.flink.config.FlinkJobConfigInfo; +import org.dinky.data.flink.exceptions.FlinkJobExceptionsDetail; +import org.dinky.data.flink.job.FlinkJobDetailInfo; +import org.dinky.data.flink.watermark.FlinkJobNodeWaterMark; import org.dinky.data.model.JobInfoDetail; import org.dinky.data.model.JobInstance; -import org.dinky.data.model.flink.backpressure.FlinkJobNodeBackPressure; -import org.dinky.data.model.flink.config.FlinkJobConfigInfo; -import org.dinky.data.model.flink.exceptions.FlinkJobExceptionsDetail; -import org.dinky.data.model.flink.job.FlinkJobDetailInfo; -import org.dinky.data.model.flink.watermark.FlinkJobNodeWaterMark; import org.dinky.gateway.Gateway; import org.dinky.gateway.config.GatewayConfig; import org.dinky.gateway.enums.GatewayType; diff --git a/dinky-admin/src/main/resources/db/db-h2.sql b/dinky-admin/src/main/resources/db/db-h2.sql index 03811fa533..7685d64b98 100644 --- a/dinky-admin/src/main/resources/db/db-h2.sql +++ b/dinky-admin/src/main/resources/db/db-h2.sql @@ -1338,7 +1338,7 @@ CREATE TABLE `dinky_task` ( `alert_group_id` bigint(20) null DEFAULT null COMMENT 'alert group id', `config_json` text null COMMENT 'configuration json', `note` varchar(255) null DEFAULT null COMMENT 'Job Note', - `step` int(11) null DEFAULT null COMMENT 'Job lifecycle', + `step` int(11) null DEFAULT 1 COMMENT 'Job lifecycle', `job_instance_id` bigint(20) null DEFAULT null COMMENT 'job instance id', `enabled` tinyint(1) NOT null DEFAULT 1 COMMENT 'is enable', `create_time` datetime(0) null DEFAULT null COMMENT 'create time', diff --git a/dinky-alert/dinky-alert-base/src/main/java/org/dinky/alert/Rules/ExceptionRule.java b/dinky-alert/dinky-alert-base/src/main/java/org/dinky/alert/Rules/ExceptionRule.java index c778f7123a..01ccc0606f 100644 --- a/dinky-alert/dinky-alert-base/src/main/java/org/dinky/alert/Rules/ExceptionRule.java +++ b/dinky-alert/dinky-alert-base/src/main/java/org/dinky/alert/Rules/ExceptionRule.java @@ -19,9 +19,10 @@ package org.dinky.alert.Rules; +import org.dinky.data.flink.exceptions.FlinkJobExceptionsDetail; + import java.util.concurrent.TimeUnit; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; @@ -43,20 +44,20 @@ public ExceptionRule() { * @param exceptions The exceptions object containing relevant data. * @return True if the operation should be executed, false otherwise. */ - public Boolean isException(Integer key, ObjectNode exceptions) { + public Boolean isException(Integer key, FlinkJobExceptionsDetail exceptions) { // If the exception is the same as the previous one, it will not be reported again - if (exceptions.get("timestamp") == null) { + if (exceptions.getTimestamp() == null) { return false; } - long timestamp = exceptions.get("timestamp").asLong(0); + long timestamp = exceptions.getTimestamp(); Long hisTimeIfPresent = hisTime.getIfPresent(key); if (hisTimeIfPresent != null && hisTimeIfPresent == timestamp) { return false; } hisTime.put(key, timestamp); - if (exceptions.has("root-exception")) { - return !exceptions.get("root-exception").isNull(); + if (exceptions.getRootException() != null) { + return !exceptions.getRootException().isEmpty(); } else { return false; } diff --git a/dinky-common/pom.xml b/dinky-common/pom.xml index 828ba1bf25..7630e97744 100644 --- a/dinky-common/pom.xml +++ b/dinky-common/pom.xml @@ -51,7 +51,10 @@ cn.hutool hutool-all - + + com.alibaba.fastjson2 + fastjson2 + com.github.docker-java docker-java-core diff --git a/dinky-admin/src/main/java/org/dinky/data/model/flink/backpressure/FlinkJobNodeBackPressure.java b/dinky-common/src/main/java/org/dinky/data/flink/backpressure/FlinkJobNodeBackPressure.java similarity index 98% rename from dinky-admin/src/main/java/org/dinky/data/model/flink/backpressure/FlinkJobNodeBackPressure.java rename to dinky-common/src/main/java/org/dinky/data/flink/backpressure/FlinkJobNodeBackPressure.java index de8c631700..14b8db13c0 100644 --- a/dinky-admin/src/main/java/org/dinky/data/model/flink/backpressure/FlinkJobNodeBackPressure.java +++ b/dinky-common/src/main/java/org/dinky/data/flink/backpressure/FlinkJobNodeBackPressure.java @@ -17,7 +17,7 @@ * */ -package org.dinky.data.model.flink.backpressure; +package org.dinky.data.flink.backpressure; import java.io.Serializable; import java.util.List; diff --git a/dinky-admin/src/main/java/org/dinky/data/model/flink/config/ExecutionConfig.java b/dinky-common/src/main/java/org/dinky/data/flink/config/ExecutionConfig.java similarity index 98% rename from dinky-admin/src/main/java/org/dinky/data/model/flink/config/ExecutionConfig.java rename to dinky-common/src/main/java/org/dinky/data/flink/config/ExecutionConfig.java index 3de8314956..9a10677855 100644 --- a/dinky-admin/src/main/java/org/dinky/data/model/flink/config/ExecutionConfig.java +++ b/dinky-common/src/main/java/org/dinky/data/flink/config/ExecutionConfig.java @@ -17,7 +17,7 @@ * */ -package org.dinky.data.model.flink.config; +package org.dinky.data.flink.config; import java.io.Serializable; diff --git a/dinky-admin/src/main/java/org/dinky/data/model/flink/config/FlinkJobConfigInfo.java b/dinky-common/src/main/java/org/dinky/data/flink/config/FlinkJobConfigInfo.java similarity index 98% rename from dinky-admin/src/main/java/org/dinky/data/model/flink/config/FlinkJobConfigInfo.java rename to dinky-common/src/main/java/org/dinky/data/flink/config/FlinkJobConfigInfo.java index 0c4c0452e2..2f06f9841e 100644 --- a/dinky-admin/src/main/java/org/dinky/data/model/flink/config/FlinkJobConfigInfo.java +++ b/dinky-common/src/main/java/org/dinky/data/flink/config/FlinkJobConfigInfo.java @@ -17,7 +17,7 @@ * */ -package org.dinky.data.model.flink.config; +package org.dinky.data.flink.config; import java.io.Serializable; diff --git a/dinky-admin/src/main/java/org/dinky/data/model/flink/exceptions/FlinkJobExceptionsDetail.java b/dinky-common/src/main/java/org/dinky/data/flink/exceptions/FlinkJobExceptionsDetail.java similarity index 98% rename from dinky-admin/src/main/java/org/dinky/data/model/flink/exceptions/FlinkJobExceptionsDetail.java rename to dinky-common/src/main/java/org/dinky/data/flink/exceptions/FlinkJobExceptionsDetail.java index bd06ae2eee..8cacb145f1 100644 --- a/dinky-admin/src/main/java/org/dinky/data/model/flink/exceptions/FlinkJobExceptionsDetail.java +++ b/dinky-common/src/main/java/org/dinky/data/flink/exceptions/FlinkJobExceptionsDetail.java @@ -17,7 +17,7 @@ * */ -package org.dinky.data.model.flink.exceptions; +package org.dinky.data.flink.exceptions; import java.io.Serializable; import java.util.List; diff --git a/dinky-admin/src/main/java/org/dinky/data/model/flink/job/FlinkJobDetailInfo.java b/dinky-common/src/main/java/org/dinky/data/flink/job/FlinkJobDetailInfo.java similarity index 99% rename from dinky-admin/src/main/java/org/dinky/data/model/flink/job/FlinkJobDetailInfo.java rename to dinky-common/src/main/java/org/dinky/data/flink/job/FlinkJobDetailInfo.java index 24299a6fd5..a4e80e9f89 100644 --- a/dinky-admin/src/main/java/org/dinky/data/model/flink/job/FlinkJobDetailInfo.java +++ b/dinky-common/src/main/java/org/dinky/data/flink/job/FlinkJobDetailInfo.java @@ -17,7 +17,7 @@ * */ -package org.dinky.data.model.flink.job; +package org.dinky.data.flink.job; import java.io.Serializable; import java.util.List; diff --git a/dinky-admin/src/main/java/org/dinky/data/model/flink/job/FlinkJobPlan.java b/dinky-common/src/main/java/org/dinky/data/flink/job/FlinkJobPlan.java similarity index 97% rename from dinky-admin/src/main/java/org/dinky/data/model/flink/job/FlinkJobPlan.java rename to dinky-common/src/main/java/org/dinky/data/flink/job/FlinkJobPlan.java index 14dced6534..3d4bcd0828 100644 --- a/dinky-admin/src/main/java/org/dinky/data/model/flink/job/FlinkJobPlan.java +++ b/dinky-common/src/main/java/org/dinky/data/flink/job/FlinkJobPlan.java @@ -17,7 +17,7 @@ * */ -package org.dinky.data.model.flink.job; +package org.dinky.data.flink.job; import java.io.Serializable; import java.util.List; diff --git a/dinky-admin/src/main/java/org/dinky/data/model/flink/job/FlinkJobPlanNode.java b/dinky-common/src/main/java/org/dinky/data/flink/job/FlinkJobPlanNode.java similarity index 95% rename from dinky-admin/src/main/java/org/dinky/data/model/flink/job/FlinkJobPlanNode.java rename to dinky-common/src/main/java/org/dinky/data/flink/job/FlinkJobPlanNode.java index e64ea860f9..994e06c7ea 100644 --- a/dinky-admin/src/main/java/org/dinky/data/model/flink/job/FlinkJobPlanNode.java +++ b/dinky-common/src/main/java/org/dinky/data/flink/job/FlinkJobPlanNode.java @@ -17,10 +17,10 @@ * */ -package org.dinky.data.model.flink.job; +package org.dinky.data.flink.job; -import org.dinky.data.model.flink.backpressure.FlinkJobNodeBackPressure; -import org.dinky.data.model.flink.watermark.FlinkJobNodeWaterMark; +import org.dinky.data.flink.backpressure.FlinkJobNodeBackPressure; +import org.dinky.data.flink.watermark.FlinkJobNodeWaterMark; import java.io.Serializable; import java.util.List; diff --git a/dinky-admin/src/main/java/org/dinky/data/model/flink/job/FlinkJobPlanNodeInput.java b/dinky-common/src/main/java/org/dinky/data/flink/job/FlinkJobPlanNodeInput.java similarity index 98% rename from dinky-admin/src/main/java/org/dinky/data/model/flink/job/FlinkJobPlanNodeInput.java rename to dinky-common/src/main/java/org/dinky/data/flink/job/FlinkJobPlanNodeInput.java index 4d65550c9c..71244a0202 100644 --- a/dinky-admin/src/main/java/org/dinky/data/model/flink/job/FlinkJobPlanNodeInput.java +++ b/dinky-common/src/main/java/org/dinky/data/flink/job/FlinkJobPlanNodeInput.java @@ -17,7 +17,7 @@ * */ -package org.dinky.data.model.flink.job; +package org.dinky.data.flink.job; import java.io.Serializable; diff --git a/dinky-admin/src/main/java/org/dinky/data/model/flink/job/FlinkJobVertex.java b/dinky-common/src/main/java/org/dinky/data/flink/job/FlinkJobVertex.java similarity index 98% rename from dinky-admin/src/main/java/org/dinky/data/model/flink/job/FlinkJobVertex.java rename to dinky-common/src/main/java/org/dinky/data/flink/job/FlinkJobVertex.java index 4115059eb8..c9469674a2 100644 --- a/dinky-admin/src/main/java/org/dinky/data/model/flink/job/FlinkJobVertex.java +++ b/dinky-common/src/main/java/org/dinky/data/flink/job/FlinkJobVertex.java @@ -17,7 +17,7 @@ * */ -package org.dinky.data.model.flink.job; +package org.dinky.data.flink.job; import java.io.Serializable; import java.util.Map; diff --git a/dinky-admin/src/main/java/org/dinky/data/model/flink/watermark/FlinkJobNodeWaterMark.java b/dinky-common/src/main/java/org/dinky/data/flink/watermark/FlinkJobNodeWaterMark.java similarity index 97% rename from dinky-admin/src/main/java/org/dinky/data/model/flink/watermark/FlinkJobNodeWaterMark.java rename to dinky-common/src/main/java/org/dinky/data/flink/watermark/FlinkJobNodeWaterMark.java index a9a7b6bf37..be03e7c263 100644 --- a/dinky-admin/src/main/java/org/dinky/data/model/flink/watermark/FlinkJobNodeWaterMark.java +++ b/dinky-common/src/main/java/org/dinky/data/flink/watermark/FlinkJobNodeWaterMark.java @@ -17,7 +17,7 @@ * */ -package org.dinky.data.model.flink.watermark; +package org.dinky.data.flink.watermark; import java.io.Serializable; diff --git a/dinky-web/package.json b/dinky-web/package.json index 9dcf39ecba..4115635c05 100644 --- a/dinky-web/package.json +++ b/dinky-web/package.json @@ -47,6 +47,7 @@ "@monaco-editor/react": "^4.5.2", "@umijs/route-utils": "^4.0.1", "antd": "^5.9.3", + "butterfly-dag": "^4.3.28", "classnames": "^2.3.2", "dayjs": "^1.11.10", "js-cookie": "^3.0.5", @@ -62,6 +63,7 @@ "react-countup": "^6.4.2", "react-dom": "^18.0.0", "react-helmet-async": "^1.3.0", + "react-lineage-dag": "^2.0.36", "react-markdown": "^8.0.7", "react-spring": "^9.7.3", "react-use-cookie": "^1.4.0", diff --git a/dinky-web/src/components/CallBackButton/CircleBtn.tsx b/dinky-web/src/components/CallBackButton/CircleBtn.tsx index 3435ddca66..ab383b6a55 100644 --- a/dinky-web/src/components/CallBackButton/CircleBtn.tsx +++ b/dinky-web/src/components/CallBackButton/CircleBtn.tsx @@ -16,9 +16,9 @@ * */ +import { TabsItemType } from '@/pages/DataStudio/model'; import { Button } from 'antd'; import React from 'react'; -import {TabsItemType} from "@/pages/DataStudio/model"; export type CircleButtonProps = { icon: React.ReactNode; @@ -30,7 +30,7 @@ export type CircleButtonProps = { export type CircleDataStudioButtonProps = { icon: React.ReactNode; loading?: boolean; - onClick?: (panes:TabsItemType[],activeKey:string) => void; + onClick?: (panes: TabsItemType[], activeKey: string) => void; title?: string; key?: string; }; diff --git a/dinky-web/src/components/FlinkDag/component/DagDataNode.tsx b/dinky-web/src/components/FlinkDag/component/DagDataNode.tsx index 51ed6a7fff..3f181ec9da 100644 --- a/dinky-web/src/components/FlinkDag/component/DagDataNode.tsx +++ b/dinky-web/src/components/FlinkDag/component/DagDataNode.tsx @@ -98,13 +98,20 @@ const DagDataNode = (props: any) => { {' '} {l('devops.baseinfo.busy')}: - {renderRatio((backpressure && backpressure.subtasks)?backpressure.subtasks[0]?.busyRatio:0, false)} + {renderRatio( + backpressure && backpressure.subtasks ? backpressure.subtasks[0]?.busyRatio : 0, + false + )} {l('devops.baseinfo.backpressure')}: - + @@ -113,7 +120,10 @@ const DagDataNode = (props: any) => { {l('devops.baseinfo.idle')}: - {renderRatio((backpressure && backpressure.subtasks)?backpressure.subtasks[0]?.idleRatio:0, true)} + {renderRatio( + backpressure && backpressure.subtasks ? backpressure.subtasks[0]?.idleRatio : 0, + true + )} diff --git a/dinky-web/src/components/FlinkDag/index.tsx b/dinky-web/src/components/FlinkDag/index.tsx index 0e48b2b92d..19e134834f 100644 --- a/dinky-web/src/components/FlinkDag/index.tsx +++ b/dinky-web/src/components/FlinkDag/index.tsx @@ -20,74 +20,72 @@ import DagDataNode from '@/components/FlinkDag/component/DagDataNode'; import DagPlanNode from '@/components/FlinkDag/component/DagPlanNode'; import { - edgeConfig, - graphConfig, - layoutConfig, - portConfig, - zoomOptions + edgeConfig, + graphConfig, + layoutConfig, + portConfig, + zoomOptions } from '@/components/FlinkDag/config'; -import {buildDag, regConnect, updateDag} from '@/components/FlinkDag/functions'; -import {Jobs} from '@/types/DevOps/data'; -import {DagreLayout} from '@antv/layout'; -import {Edge, Graph} from '@antv/x6'; -import {Selection} from '@antv/x6-plugin-selection'; -import {register} from '@antv/x6-react-shape'; -import {Drawer, Select, Slider, Table, Tabs, TabsProps, Typography} from 'antd'; -import {useEffect, useRef, useState} from 'react'; +import { buildDag, regConnect, updateDag } from '@/components/FlinkDag/functions'; +import { getData } from '@/services/api'; +import { API_CONSTANTS } from '@/services/endpoints'; +import { Jobs } from '@/types/DevOps/data'; +import { DagreLayout } from '@antv/layout'; +import { Edge, Graph } from '@antv/x6'; +import { Rectangle } from '@antv/x6-geometry'; +import { Selection } from '@antv/x6-plugin-selection'; +import { register } from '@antv/x6-react-shape'; +import { Drawer, Select, Slider, Table, Tabs, TabsProps, Typography } from 'antd'; +import { useEffect, useRef, useState } from 'react'; import './index.css'; -import {getData} from "@/services/api"; -import {API_CONSTANTS} from "@/services/endpoints"; -import {Rectangle} from "@antv/x6-geometry"; -import {Options as GraphOptions} from "@antv/x6/src/graph/options"; -import path from "path"; export type DagProps = { - job: Jobs.Job; - onlyPlan?: boolean; - checkPoints?: any; + job: Jobs.Job; + onlyPlan?: boolean; + checkPoints?: any; }; -const {Paragraph} = Typography; +const { Paragraph } = Typography; const FlinkDag = (props: DagProps) => { - const container = useRef(null); - - const {job, onlyPlan = false, checkPoints = {}} = props; - - const [graph, setGraph] = useState(); - const [currentJob, setCurrentJob] = useState(); - const [currentSelect, setCurrentSelect] = useState(); - const [open, setOpen] = useState(false); - const [zoom, setZoom] = useState(1); - let originPosition = { - zoom: 1 - }; - - const handleClose = () => { - setOpen(false); - setCurrentSelect(undefined); - graph?.zoomToFit(zoomOptions); - graph?.centerContent(); - }; - - const initListen = (graph: Graph) => { - graph.on('node:selected', ({cell}) => { - if (!onlyPlan) { - setOpen(true); - setZoom(oldValue => { - originPosition = {zoom: oldValue} - return 1; - }) - graph.zoomTo(1) - setCurrentSelect(cell); - graph.positionPoint(Rectangle.create(cell.getBBox()).getLeftMiddle(), '10%', '50%'); - } - }); + const container = useRef(null); + + const { job, onlyPlan = false, checkPoints = {} } = props; + + const [graph, setGraph] = useState(); + const [currentJob, setCurrentJob] = useState(); + const [currentSelect, setCurrentSelect] = useState(); + const [open, setOpen] = useState(false); + const [zoom, setZoom] = useState(1); + let originPosition = { + zoom: 1 + }; - graph.on('node:unselected', ({cell}) => { - setZoom(originPosition.zoom) - handleClose(); + const handleClose = () => { + setOpen(false); + setCurrentSelect(undefined); + graph?.zoomToFit(zoomOptions); + graph?.centerContent(); + }; + + const initListen = (graph: Graph) => { + graph.on('node:selected', ({ cell }) => { + if (!onlyPlan) { + setOpen(true); + setZoom((oldValue) => { + originPosition = { zoom: oldValue }; + return 1; }); - }; + graph.zoomTo(1); + setCurrentSelect(cell); + graph.positionPoint(Rectangle.create(cell.getBBox()).getLeftMiddle(), '10%', '50%'); + } + }); + + graph.on('node:unselected', ({ cell }) => { + setZoom(originPosition.zoom); + handleClose(); + }); + }; const initGraph = (flinkData: any) => { register({ @@ -98,156 +96,193 @@ const FlinkDag = (props: DagProps) => { ports: portConfig }); - Edge.config(edgeConfig); - Graph.registerConnector('curveConnector', regConnect, true); - Graph.registerEdge('data-processing-curve', Edge, true); + Edge.config(edgeConfig); + Graph.registerConnector('curveConnector', regConnect, true); + Graph.registerEdge('data-processing-curve', Edge, true); - const graph: Graph = new Graph({ - // @ts-ignore - container: container.current, - ...graphConfig - }); + const graph: Graph = new Graph({ + // @ts-ignore + container: container.current, + ...graphConfig + }); - graph.use( - new Selection({ - enabled: true, - multiple: false, - rubberband: false, - showNodeSelectionBox: true - }) - ); - - // Adaptive layout - const model = new DagreLayout(layoutConfig).layout(flinkData); - graph.fromJSON(model); - - // Automatically zoom to fit - graph.zoomToFit(zoomOptions); - graph.on('scale', ({sx}) => setZoom(sx)); - graph.centerContent(); - graph?.zoomTo(zoom) - updateDag(job?.vertices, graph); - initListen(graph); - return graph; - }; + graph.use( + new Selection({ + enabled: true, + multiple: false, + rubberband: false, + showNodeSelectionBox: true + }) + ); - useEffect(() => { - const flinkData = buildDag(job?.plan); - // Clean up old data - if (graph) { - graph.clearCells(); - } - setGraph(initGraph(flinkData)); - setZoom(1 / flinkData.nodes.length + 0.5) - }, [currentJob]); + // Adaptive layout + const model = new DagreLayout(layoutConfig).layout(flinkData); + graph.fromJSON(model); - useEffect(() => { - updateDag(job?.vertices, graph); - if (currentJob != job?.jid) { - setCurrentJob(job?.jid); - } - }, [job]); + // Automatically zoom to fit + graph.zoomToFit(zoomOptions); + graph.on('scale', ({ sx }) => setZoom(sx)); + graph.centerContent(); + graph?.zoomTo(zoom); + updateDag(job?.vertices, graph); + initListen(graph); + return graph; + }; + + useEffect(() => { + const flinkData = buildDag(job?.plan); + // Clean up old data + if (graph) { + graph.clearCells(); + } + setGraph(initGraph(flinkData)); + setZoom(1 / flinkData.nodes.length + 0.5); + }, [currentJob]); + + useEffect(() => { + updateDag(job?.vertices, graph); + if (currentJob != job?.jid) { + setCurrentJob(job?.jid); + } + }, [job]); + useEffect(() => { + graph?.zoomTo(zoom); + }, [zoom]); + + const renderCheckpoint = (id: string) => { + const [selectPath, setSelectPath] = useState(''); + const key = id + selectPath; + const [itemChildren, setItemChildren] = useState({ [key]: [] as TabsProps['items'] }); + const checkpointArray = ((checkPoints.history ?? []) as any[]) + .filter((x) => x.status === 'COMPLETED') + .map((x) => { + return { checkpointType: x.checkpoint_type, path: x.external_path, id: x.id }; + }); useEffect(() => { - graph?.zoomTo(zoom) - }, [zoom]); - - const renderCheckpoint = (id: string) => { - const [selectPath, setSelectPath] = useState(''); - const key = id + selectPath; - const [itemChildren, setItemChildren] = useState({[key]: [] as TabsProps['items']}); - const checkpointArray = ((checkPoints.history?? []) as any[]).filter(x => x.status === "COMPLETED").map(x => { - return {checkpointType: x.checkpoint_type, path: x.external_path, id: x.id} - }); - useEffect(() => { - if (selectPath && id) { - if (!itemChildren[key]) { - getData(API_CONSTANTS.READ_CHECKPOINT, {path: selectPath, operatorId: id}).then(res => { - const genData = Object.keys(res.datas).map(x => { - const datum = res.datas[x]; - return { - key: x, - label: x, - children: - { - return { - key: y, - label: y, - children: { - return { - title: z, - dataIndex: z, - key: z, - render: (text) => {text}, - } - })}/> - } - }) - } tabBarStyle={{marginBlock: 0}} tabBarGutter={10}/> - } - }) - setItemChildren({...itemChildren, [key]: genData}) - }) - } + if (selectPath && id) { + if (!itemChildren[key]) { + getData(API_CONSTANTS.READ_CHECKPOINT, { path: selectPath, operatorId: id }).then( + (res) => { + const genData = Object.keys(res.datas).map((x) => { + const datum = res.datas[x]; + return { + key: x, + label: x, + children: ( + { + return { + key: y, + label: y, + children: ( +
{ + return { + title: z, + dataIndex: z, + key: z, + render: (text) => ( + + {text} + + ) + }; + })} + /> + ) + }; + })} + tabBarStyle={{ marginBlock: 0 }} + tabBarGutter={10} + /> + ) + }; + }); + setItemChildren({ ...itemChildren, [key]: genData }); } - }, [selectPath, id]) - - return <> - { + return { label: x.id, value: x.path }; + })} + onChange={(path) => { + setSelectPath(path); + }} + /> + + + ); + }; + return ( + +
+ +
+
+ + {onlyPlan ? ( + <> + ) : ( + {currentSelect?.getData().description}

+ }, + { + key: '2', + label: 'CheckPointRead', + children: renderCheckpoint(currentSelect?.id) + } + ]} + tabBarGutter={10} + /> + )} +
+ + ); }; export default FlinkDag; diff --git a/dinky-web/src/components/LineageGraph/index.tsx b/dinky-web/src/components/LineageGraph/index.tsx new file mode 100644 index 0000000000..b140b8c831 --- /dev/null +++ b/dinky-web/src/components/LineageGraph/index.tsx @@ -0,0 +1,353 @@ +import { Badge, Tooltip, Typography } from 'antd'; + +import { + LineageDetailInfo, + LineageRelations, + LineageTable, + LineageTableColumn +} from '@/types/DevOps/data.d'; +import { l } from '@/utils/intl'; +import { SuccessNotification, WarningNotification } from '@/utils/messages'; +import { + CompassOutlined, + ExpandAltOutlined, + FullscreenExitOutlined, + InsertRowAboveOutlined, + PlusCircleOutlined, + ReloadOutlined +} from '@ant-design/icons'; +import _ from 'lodash'; +import React, { useEffect } from 'react'; +import LineageDag from 'react-lineage-dag'; +import 'react-lineage-dag/dist/index.css'; + +const { Text } = Typography; + +interface LineageState { + lineageData: LineageDetailInfo; + canvas: any; + actionMenu: any[]; + tables: ITable[]; + relations: LineageRelations[]; + columns: any[]; + operator: any[]; + centerId: string; + showMinimap: boolean; + refresh: boolean; + expandField: boolean; + collapseField: boolean; + expandDownstream: boolean; + collapseDownstream: boolean; + expandUpstream: boolean; + collapseUpstream: boolean; +} + +const InitLineageState: LineageState = { + lineageData: { + tables: [], + relations: [] + }, + canvas: undefined, + actionMenu: [], + tables: [], + relations: [], + columns: [], + operator: [], + centerId: '', + showMinimap: false, + refresh: false, + expandField: false, + collapseField: false, + expandDownstream: false, + collapseDownstream: false, + expandUpstream: false, + collapseUpstream: false +}; + +type JobLineageProps = { + lineageData: LineageDetailInfo; + refreshCallBack: () => void; +}; + +type ITable = { + id: string; + name: string; + isCollapse: boolean; + fields: LineageTableColumn[]; +}; + +const LineageGraph: React.FC = (props) => { + const { lineageData, refreshCallBack } = props; + + const [lineageState, setLineageState] = React.useState(InitLineageState); + + useEffect(() => { + if (lineageData) { + setLineageState((prevState) => ({ + ...prevState, + lineageData: lineageData, + tables: buildLineageTables(lineageData.tables), + relations: buildLineageRelations(lineageData.relations) + })); + } + }, [lineageData, lineageState.refresh]); + + const buildLineageColumns = (data: LineageDetailInfo) => { + return [ + { + key: 'id', + width: '100', + render: (text: any, record: any, index: number) => { + return ( + <> + + + ); + } + }, + { + key: 'name', + primaryKey: true, + width: '250', + render: (text: any, record: any, index: number) => { + return ( + <> + {text} + + ); + } + } + ]; + }; + + const buildLineageTables = (tables: LineageTable[]) => { + return tables.map((table: LineageTable) => ({ + id: table?.id, + name: table?.name, + isCollapse: false, + fields: table?.columns + })); + }; + + const buildLineageRelations = (relations: LineageRelations[]) => { + return relations.map((relation: LineageRelations) => ({ + id: relation?.id, + srcTableId: relation?.srcTableId, + tgtTableId: relation?.tgtTableId, + srcTableColName: relation?.srcTableColName, + tgtTableColName: relation?.tgtTableColName + })); + }; + + function findTableById(id: string) { + const { tables } = lineageState; + return tables.find((item) => { + return String(item.id) === String(id); + }); + } + + const handleExpandField = (nodeData: any, tables: ITable[]) => { + const { isCollapse, id } = nodeData; + console.log(id, findTableById(id), tables); + const iTable = findTableById(id); + if (iTable) { + iTable.isCollapse = isCollapse; + } + // todo: 待实现 展开字段 + setLineageState((prevState) => ({ ...prevState, expandField: !prevState.expandField })); + SuccessNotification( + lineageState.expandField ? l('lineage.expandField') : l('lineage.expandField') + ); + }; + const buildActionMenu = (data: ITable[]) => { + return [ + { + id: 'expandField', + name: lineageState.expandField ? l('lineage.expandField') : l('lineage.expandField'), + icon: ( + + + + ), + onClick: (nodeData: any) => handleExpandField(nodeData, _.clone(data)) + }, + { + id: 'expandDownstream', + name: lineageState.expandDownstream + ? l('lineage.expandDownstream') + : l('lineage.collapseDownstream'), + icon: ( + + + + ), + onClick: (nodeData: { id: string }) => { + setLineageState((prevState) => ({ + ...prevState, + expandDownstream: !prevState.expandDownstream + })); + // todo 展开下游 + WarningNotification( + lineageState.expandDownstream + ? l('lineage.expandDownstream') + : l('lineage.collapseDownstream') + ); + } + }, + { + id: 'expandUpstream', + name: lineageState.expandUpstream + ? l('lineage.expandUpstream') + : l('lineage.collapseUpstream'), + icon: ( + + + + ), + onClick: (nodeData: { id: string }) => { + setLineageState((prevState) => ({ + ...prevState, + expandUpstream: !prevState.expandUpstream + })); + // todo 展开上游 + WarningNotification( + lineageState.expandUpstream + ? l('lineage.expandUpstream') + : l('lineage.collapseUpstream') + ); + } + } + ]; + }; + + const renderExtActionButton = () => { + return [ + { + key: 'minimap', + icon: ( + + + + ), + name: lineageState.showMinimap ? l('lineage.showMap') : l('lineage.hideMap'), + onClick: (canvas: any) => { + setLineageState((prevState) => ({ + ...prevState, + canvas, + showMinimap: !prevState.showMinimap + })); + } + }, + { + key: 'refresh', + icon: ( + + + + ), + title: l('lineage.refresh'), + onClick: (canvas: any) => { + setLineageState((prevState) => ({ ...prevState, canvas, refresh: true })); + refreshCallBack(); + setLineageState((prevState) => ({ ...prevState, canvas, refresh: false })); + } + } + ]; + }; + + const renderTitle = (title: string) => { + return ( + { + lineageState.canvas.nodes.forEach((item: { redrawTitle: () => void }) => { + item.redrawTitle(); + }); + }} + > +
{title.toString().split('.')[2] ?? title}
+
+ ); + }; + + return ( + <> + setLineageState((prevState) => ({ ...prevState, canvas }))} + onChange={(data: any) => + setLineageState((prevState) => ({ ...prevState, centerId: data.id })) + } + config={{ + titleRender: (title: string) => renderTitle(title), + minimap: { enable: lineageState.showMinimap }, + enableHoverChain: true, + showActionIcon: true, + gridMode: { + isAdsorb: true, + theme: { + shapeType: 'line', + gap: 20, + lineWidth: 1, + lineColor: '#e8e8e8', + circleRadiu: 5, + circleColor: '#e8e8e8' + } + } + // theme: { + // isMouseMoveStopPropagation: true, + // autoFixCanvas: { + // enable: true, + // autoMovePadding: [40, 40, 40, 40], + // }, + // edge: { + // type: 'endpoint', + // shapeType: 'AdvancedBezier', + // hasRadius: true, + // isExpandWidth: true, + // label: '111', + // defaultAnimate: true, + // arrowPosition: 1, + // arrowOffset: -5 + // }, + // } + }} + butterfly={{ + zoomable: true, + draggable: true, + movable: true, + linkable: true + }} + actionMenu={renderExtActionButton()} + /> + + ); +}; + +export default LineageGraph; diff --git a/dinky-web/src/global.less b/dinky-web/src/global.less index a09e4a3359..4ee8342362 100644 --- a/dinky-web/src/global.less +++ b/dinky-web/src/global.less @@ -117,6 +117,7 @@ body, ::-webkit-scrollbar-corner { background-color: #f5f5f5; } + .monaco-scrollable-element > .scrollbar > .slider { background: #8db7fd !important; } @@ -249,9 +250,11 @@ ol { } } } + .ant-tree-node-content-wrapper { display: flex; } + .gitCodeTree { min-height: 70vh; max-height: 70vh; @@ -344,10 +347,12 @@ h5 { opacity: 1; animation: show-anim 0.5s; } + .hide { opacity: 0; animation: hide-anim 0.5s; } + @keyframes show-anim { 0% { opacity: 0; @@ -356,6 +361,7 @@ h5 { opacity: 1; } } + @keyframes hide-anim { 0% { opacity: 1; @@ -373,6 +379,7 @@ h5 { white-space: nowrap; text-overflow: ellipsis; } + .container-header { display: flex; align-items: center; @@ -390,6 +397,7 @@ h5 { scrollbar-color: #d4aa70 #e4e4e4; scrollbar-width: thin; } + .monaco-float { height: 100%; } @@ -400,6 +408,7 @@ h5 { .ant-tabs-tab { padding: 4px 8px; } + .ant-tabs-tab-active { box-sizing: content-box; border-block-end: 3px solid #8db7fd; @@ -422,7 +431,153 @@ h5 { .ant-pro-checkcard { overflow: hidden; + .ant-pro-checkcard-body { width: 100vw; } } + +// ==================== lineage style start ==================== + +.butterfly-lineage-dag .table-node.focus { + border-width: 10px; + border-radius: 4px; + box-shadow: 0 0 5px #0d74e8; +} + +.butterfly-lineage-dag .table-node:hover { + border-width: 10px; + border-radius: 4px; + box-shadow: 0 0 5px #0d74e8; +} + +.butterfly-lineage-dag .table-node .field { + display: flex; + flex-flow: row; + justify-content: space-between; + height: 20px; + border-width: 10px; + border-radius: 4px; +} + +.field.hover-chain { + width: 97%; + background: #0d74e8 !important; +} + +.butterfly-lineage-dag .table-node .title-con { + .title { + display: inline-flex; + align-items: center; + width: 100%; + height: 40px; + max-height: 4vh; + padding-left: 5px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + background: #63e0e0; + border-radius: 4px; + box-shadow: #0d74e8; + + .ant-typography { + display: flex; + flex-flow: row; + align-items: center; + justify-content: space-between; + } + } +} + +.butterfly-lineage-dag .table-node .field .point.left-point { + left: -10px; + z-index: 9999; +} + +.butterfly-lineage-dag .table-node .field .point.right-point { + right: -10px; + z-index: 9999; +} + +.butterfly-lineage-dag .table-node .title-con { + position: relative; + display: flex; + min-width: 40%; +} + +.butterfly-lineage-dag .butterflies-link.hover-chain { + transition: all 0.5s ease-in-out; + // 线条缓慢流动 + animation: 30s linear 0s infinite normal none running running-line; + stroke: #02f657; + stop-opacity: 1; +} + +.butterfly-lineage-dag .butterflies-arrow.hover-chain { + transition: all 0.5s ease-in-out; + animation: 30s linear 0s infinite normal none running running-line; + fill: #28da8f; + stroke: #28da8f; +} + +.butterfly-lineage-dag .table-node .field.hover-chain .point { + background: #0095ff; + transition: all 0.5s ease-in-out; +} + +.butterfly-lineage-dag .table-node .field { + position: relative; + align-items: center; + height: 26px !important; + margin: 0 6px; + padding: 4px 4px 4px 6px; + white-space: nowrap; +} +.butterfly-lineage-dag .table-node .field.hover-chain { + color: #fff; + transition: all 0.5s ease-in-out; +} + +.butterfly-lineage-dag .table-node .title-con .operator { + position: absolute; + top: 2px; + right: 5px; + display: flex; + align-items: baseline; + justify-content: space-between; + height: 100%; + max-height: 4vh; + margin: 0 20px; + .operator-item { + margin-left: 15px; + } +} + +.butterflies-link { + transition: all 0.5s ease-in-out; // 线条缓慢流动 + fill: none; // 线条填充色 + stroke: #04671f; // 线条颜色 + stroke-width: 1.5px; // 线条宽度 + stroke-dasharray: 5, 8; // 线条样式 5px实线 8px空白 + stroke-dashoffset: -2px; // 线条偏移量 + stroke-linecap: round; // 线条两端样式 + stroke-linejoin: round; // 线条连接样式 miter-clip斜角 miter斜角 round圆角 bevel斜角 +} + +.butterflies-link:hover { + right: 0; // 激活连接线 右边距 +} + +.butterflies-arrow { + padding-right: 10px !important; // 箭头右边距 + transition: all 0.5s ease-in-out; // 线条缓慢流动 + fill: #04671f; // 箭头颜色 + stroke: #04671f; // 箭头颜色 + stroke-width: 4px; // 箭头宽度 +} + +.butterflies-arrow:hover { + right: 0; + background: #0366d6; +} +// ==================== lineage style end ==================== diff --git a/dinky-web/src/locales/en-US/pages.ts b/dinky-web/src/locales/en-US/pages.ts index 2ef729902f..6ffa381b6e 100644 --- a/dinky-web/src/locales/en-US/pages.ts +++ b/dinky-web/src/locales/en-US/pages.ts @@ -1003,5 +1003,16 @@ export default { 'user.type': 'User Type', 'user.update': 'Modify User', 'user.username': 'User Name', - 'user.usernamePlaceholder': 'Please enter user name' + 'user.usernamePlaceholder': 'Please enter user name', + + 'lineage.getError': 'Cannot Get Lineage', + 'lineage.expandField': 'Expand Field', + 'lineage.collapseField': 'Collapse Field', + 'lineage.expandDownstream': 'Expand Downstream', + 'lineage.collapseDownstream': 'Collapse Downstream', + 'lineage.expandUpstream': 'Expand Upstream', + 'lineage.collapseUpstream': 'Collapse Upstream', + 'lineage.showMap': 'Show Map', + 'lineage.hideMap': 'Hide Map', + 'lineage.refresh': 'Refresh' }; diff --git a/dinky-web/src/locales/zh-CN/pages.ts b/dinky-web/src/locales/zh-CN/pages.ts index 257bae2b02..506529ad60 100644 --- a/dinky-web/src/locales/zh-CN/pages.ts +++ b/dinky-web/src/locales/zh-CN/pages.ts @@ -965,5 +965,16 @@ export default { 'user.type': '注册类型', 'user.update': '修改用户', 'user.username': '用户名', - 'user.usernamePlaceholder': '请输入用户名' + 'user.usernamePlaceholder': '请输入用户名', + + 'lineage.getError': '无法获取血缘', + 'lineage.expandField': '展开字段', + 'lineage.collapseField': '收缩字段', + 'lineage.expandDownstream': '展开下游', + 'lineage.collapseDownstream': '收起下游', + 'lineage.expandUpstream': '展开上游', + 'lineage.collapseUpstream': '收起上游', + 'lineage.showMap': '显示小地图', + 'lineage.hideMap': '隐藏小地图', + 'lineage.refresh': '刷新血缘' }; diff --git a/dinky-web/src/pages/DataStudio/BottomContainer/Lineage/index.tsx b/dinky-web/src/pages/DataStudio/BottomContainer/Lineage/index.tsx new file mode 100644 index 0000000000..bdb1299654 --- /dev/null +++ b/dinky-web/src/pages/DataStudio/BottomContainer/Lineage/index.tsx @@ -0,0 +1,79 @@ +import LineageGraph from '@/components/LineageGraph'; +import { getCurrentData, mapDispatchToProps } from '@/pages/DataStudio/function'; +import { StateType } from '@/pages/DataStudio/model'; +import { getDataByParams } from '@/services/BusinessCrud'; +import { API_CONSTANTS } from '@/services/endpoints'; +import { LineageDetailInfo } from '@/types/DevOps/data'; +import { l } from '@/utils/intl'; +import { connect } from '@umijs/max'; +import { Card, Result } from 'antd'; +import React, { useEffect } from 'react'; + +interface StudioLineageParams { + type: number; + statementSet: boolean; + dialect: string; + databaseId: number; + statement: string; + envId: number; + fragment: boolean; + variables: any; +} + +const Lineage: React.FC = (props) => { + const { + tabs: { panes, activeKey }, + bottomHeight + } = props; + const [lineageData, setLineageData] = React.useState({ + tables: [], + relations: [] + }); + const queryLineageData = () => { + // 组装参数 statementSet type dialect databaseId + const currentData = getCurrentData(panes, activeKey); + if (!currentData) return; + const { type, statementSet, dialect, databaseId, statement, envId, fragment } = currentData; + const params: StudioLineageParams = { + type: 1, // todo: 暂时写死 ,后续优化 + dialect: dialect, + envId: envId, + fragment: fragment, + statement: statement, + statementSet: statementSet, + databaseId: databaseId, + variables: {} + }; + getDataByParams(API_CONSTANTS.STUDIO_GET_LINEAGE, params).then((res) => + setLineageData(res as LineageDetailInfo) + ); + }; + + useEffect(() => { + queryLineageData(); + }, [activeKey]); + + return ( + <> + + {lineageData && (lineageData.tables.length !== 0 || lineageData.relations.length !== 0) ? ( + + ) : ( + + )} + + + ); +}; + +export default connect( + ({ Studio }: { Studio: StateType }) => ({ + tabs: Studio.tabs, + bottomHeight: Studio.bottomContainer.height + }), + mapDispatchToProps +)(Lineage); diff --git a/dinky-web/src/pages/DataStudio/HeaderContainer/index.tsx b/dinky-web/src/pages/DataStudio/HeaderContainer/index.tsx index c99788b836..6b3f2c2a68 100644 --- a/dinky-web/src/pages/DataStudio/HeaderContainer/index.tsx +++ b/dinky-web/src/pages/DataStudio/HeaderContainer/index.tsx @@ -44,13 +44,14 @@ import { SettingConfigKeyEnum } from '@/pages/SettingCenter/GlobalSetting/Settin import { handlePutDataJson } from '@/services/BusinessCrud'; import { BaseConfigProperties } from '@/types/SettingCenter/data'; import { l } from '@/utils/intl'; -import {connect, history} from '@@/exports'; +import { connect, history } from '@@/exports'; import { EnvironmentOutlined, FlagTwoTone, MoreOutlined, PauseCircleTwoTone, - PlayCircleTwoTone, RotateRightOutlined, + PlayCircleTwoTone, + RotateRightOutlined, SafetyCertificateTwoTone, SaveTwoTone, SendOutlined, @@ -260,9 +261,9 @@ const HeaderContainer = (props: any) => { // flink jobdetail跳转 icon: , title: l('pages.datastudio.to.jobDetail'), - click: ()=>history.push(`/devops/job-detail?id=${getCurrentData(panes, activeKey)?.id}`), + click: () => history.push(`/devops/job-detail?id=${getCurrentData(panes, activeKey)?.id}`), isShow: (type?: TabsPageType, subType?: string, data?: any) => - type === TabsPageType.project && data?.jobInstanceId && subType==="flinksql" + type === TabsPageType.project && data?.jobInstanceId && subType === 'flinksql' }, // { // // 异步提交按钮 @@ -329,13 +330,13 @@ const HeaderContainer = (props: any) => { }; const renderHotkey = () => { document.onkeydown = (e) => { - if (getCurrentTab(panes, activeKey)){ + if (getCurrentTab(panes, activeKey)) { routes - .filter((r) => r.hotKey?.(e)) - .forEach((r) => { - r.click(); - e.preventDefault(); - }); + .filter((r) => r.hotKey?.(e)) + .forEach((r) => { + r.click(); + e.preventDefault(); + }); } }; }; diff --git a/dinky-web/src/pages/DataStudio/LeftContainer/Project/JobTree/index.tsx b/dinky-web/src/pages/DataStudio/LeftContainer/Project/JobTree/index.tsx index f2f2a308b1..4d967af0c3 100644 --- a/dinky-web/src/pages/DataStudio/LeftContainer/Project/JobTree/index.tsx +++ b/dinky-web/src/pages/DataStudio/LeftContainer/Project/JobTree/index.tsx @@ -17,13 +17,14 @@ * */ +import { getCurrentTab } from '@/pages/DataStudio/function'; import { buildProjectTree, generateList, getLeafKeyList, getParentKey } from '@/pages/DataStudio/LeftContainer/Project/function'; -import {StateType, TabsItemType} from '@/pages/DataStudio/model'; +import { StateType, TabsItemType } from '@/pages/DataStudio/model'; import { BtnRoute } from '@/pages/DataStudio/route'; import { l } from '@/utils/intl'; import { connect } from '@@/exports'; @@ -31,7 +32,6 @@ import { Key } from '@ant-design/pro-components'; import { Empty, Tree } from 'antd'; import Search from 'antd/es/input/Search'; import React, { useEffect, useState } from 'react'; -import {getCurrentData, getCurrentTab} from "@/pages/DataStudio/function"; const { DirectoryTree } = Tree; @@ -46,8 +46,7 @@ type TreeProps = { }; const JobTree: React.FC = (props) => { - - const { projectData, onNodeClick, style, height, onRightClick} = props; + const { projectData, onNodeClick, style, height, onRightClick } = props; const [searchValue, setSearchValueValue] = useState(''); const [data, setData] = useState(buildProjectTree(projectData, searchValue)); @@ -89,25 +88,24 @@ const JobTree: React.FC = (props) => { setExpandedKeys(getLeafKeyList(projectData)); }; - const btn = BtnRoute['menu.datastudio.project']; - const positionKey = (panes:TabsItemType[],activeKey:string) => { - const treeKey = getCurrentTab(panes,activeKey)?.treeKey; - if (treeKey){ + const positionKey = (panes: TabsItemType[], activeKey: string) => { + const treeKey = getCurrentTab(panes, activeKey)?.treeKey; + if (treeKey) { const expandList: any[] = generateList(data, []); let expandedKeys: any = expandList - .map((item: any) => { - if (item?.key==treeKey) { - return getParentKey(item.key, data); - } - return null; - }) - .filter((item: any, i: number, self: any) => item && self.indexOf(item) === i); + .map((item: any) => { + if (item?.key == treeKey) { + return getParentKey(item.key, data); + } + return null; + }) + .filter((item: any, i: number, self: any) => item && self.indexOf(item) === i); setExpandedKeys(expandedKeys); setAutoExpandParent(true); - setSelectedKeys([treeKey]) + setSelectedKeys([treeKey]); } - } + }; btn[1].onClick = expandAll; @@ -147,5 +145,5 @@ const JobTree: React.FC = (props) => { export default connect(({ Studio }: { Studio: StateType }) => ({ height: Studio.toolContentHeight, - projectData: Studio.project.data, + projectData: Studio.project.data }))(JobTree); diff --git a/dinky-web/src/pages/DataStudio/LeftContainer/Project/index.tsx b/dinky-web/src/pages/DataStudio/LeftContainer/Project/index.tsx index c1d906645f..9a820b86e4 100644 --- a/dinky-web/src/pages/DataStudio/LeftContainer/Project/index.tsx +++ b/dinky-web/src/pages/DataStudio/LeftContainer/Project/index.tsx @@ -42,7 +42,6 @@ import { Modal, Typography } from 'antd'; import { MenuInfo } from 'rc-menu/es/interface'; import React, { useEffect, useState } from 'react'; import { connect } from 'umi'; -import {BtnRoute} from "@/pages/DataStudio/route"; const { Text } = Typography; diff --git a/dinky-web/src/pages/DataStudio/LeftContainer/index.tsx b/dinky-web/src/pages/DataStudio/LeftContainer/index.tsx index 141af7cbfd..7db42c332e 100644 --- a/dinky-web/src/pages/DataStudio/LeftContainer/index.tsx +++ b/dinky-web/src/pages/DataStudio/LeftContainer/index.tsx @@ -17,7 +17,7 @@ * */ -import {CircleBtn, CircleButtonProps, CircleDataStudioButtonProps} from '@/components/CallBackButton/CircleBtn'; +import { CircleBtn, CircleDataStudioButtonProps } from '@/components/CallBackButton/CircleBtn'; import MovableSidebar, { MovableSidebarProps } from '@/components/Sidebar/MovableSidebar'; import useThemeValue from '@/hooks/useThemeValue'; import ProjectTitle from '@/pages/DataStudio/LeftContainer/Project/ProjectTitle'; @@ -31,7 +31,14 @@ export type LeftContainerProps = { size: number; }; const LeftContainer: React.FC = (props: any) => { - const { dispatch, size, toolContentHeight, leftContainer, rightContainer , tabs: { panes, activeKey }} = props; + const { + dispatch, + size, + toolContentHeight, + leftContainer, + rightContainer, + tabs: { panes, activeKey } + } = props; const themeValue = useThemeValue(); const MAX_WIDTH = size.width - 2 * VIEW.leftToolWidth - rightContainer.width - 700; @@ -81,7 +88,12 @@ const LeftContainer: React.FC = (props: any) => { enable: { right: true }, btnGroup: BtnRoute[leftContainer.selectKey] ? BtnRoute[leftContainer.selectKey].map((item: CircleDataStudioButtonProps) => ( - item.onClick?.(panes,activeKey)} key={item.title} /> + item.onClick?.(panes, activeKey)} + key={item.title} + /> )) : [], style: { borderInlineEnd: `1px solid ${themeValue.borderColor}` } diff --git a/dinky-web/src/pages/DataStudio/route.tsx b/dinky-web/src/pages/DataStudio/route.tsx index 3c58231333..bcedbe9320 100644 --- a/dinky-web/src/pages/DataStudio/route.tsx +++ b/dinky-web/src/pages/DataStudio/route.tsx @@ -17,8 +17,9 @@ * */ -import {CircleButtonProps, CircleDataStudioButtonProps} from '@/components/CallBackButton/CircleBtn'; +import { CircleDataStudioButtonProps } from '@/components/CallBackButton/CircleBtn'; import Console from '@/pages/DataStudio/BottomContainer/Console'; +import Lineage from '@/pages/DataStudio/BottomContainer/Lineage'; import Result from '@/pages/DataStudio/BottomContainer/Result'; import TableData from '@/pages/DataStudio/BottomContainer/TableData'; import MetaData from '@/pages/DataStudio/LeftContainer/MetaData'; @@ -37,9 +38,10 @@ import { CalendarOutlined, ConsoleSqlOutlined, DatabaseOutlined, - DesktopOutlined, EnvironmentOutlined, + DesktopOutlined, + EnvironmentOutlined, FolderOutlined, - HistoryOutlined, HolderOutlined, + HistoryOutlined, InfoCircleOutlined, MonitorOutlined, PlayCircleOutlined, @@ -146,7 +148,8 @@ export const LeftBottomSide = [ auth: '/datastudio/bottom/lineage', key: 'menu.datastudio.lineage', icon: , - label: l('menu.datastudio.lineage') + label: l('menu.datastudio.lineage'), + children: }, { auth: '/datastudio/bottom/process', @@ -270,7 +273,7 @@ export const BtnRoute: { [c: string]: CircleDataStudioButtonProps[] } = { }, { icon: , - title:l('button.position'), + title: l('button.position'), key: 'button.position', onClick: () => {} } diff --git a/dinky-web/src/pages/DevOps/JobDetail/CheckPointsTab/components/CkDesc.tsx b/dinky-web/src/pages/DevOps/JobDetail/CheckPointsTab/components/CkDesc.tsx index d1e19b7ea1..c6fe465195 100644 --- a/dinky-web/src/pages/DevOps/JobDetail/CheckPointsTab/components/CkDesc.tsx +++ b/dinky-web/src/pages/DevOps/JobDetail/CheckPointsTab/components/CkDesc.tsx @@ -30,92 +30,83 @@ import { Descriptions, Space, Tag } from 'antd'; const CkDesc = (props: JobProps) => { const { jobDetail } = props; - let counts = jobDetail?.jobDataDto?.checkpoints.counts; - let latest = jobDetail?.jobDataDto?.checkpoints.latest; - let checkpointsConfigInfo = jobDetail?.jobDataDto?.checkpointsConfig; + const { counts, latest } = jobDetail?.jobDataDto?.checkpoints; + const { checkpointsConfigInfo } = jobDetail?.jobDataDto?.checkpointsConfig; return ( <> - {checkpointsConfigInfo.mode.toUpperCase()} + {checkpointsConfigInfo?.mode?.toUpperCase() ?? 'None'} - {checkpointsConfigInfo.interval} + {checkpointsConfigInfo?.interval ?? 'None'} - {checkpointsConfigInfo.timeout} + {checkpointsConfigInfo?.timeout ?? 'None'} - {checkpointsConfigInfo.unaligned_checkpoints ? 'Enabled' : 'Disabled'} + {checkpointsConfigInfo?.unaligned_checkpoints ? 'Enabled' : 'Disabled'} - {latest.restored === null ? 'None' : latest.restored.external_path} + {latest?.restored?.external_path ?? 'None'} - {latest.failed === null ? ( - - {'None'} - - ) : ( - <> - - {'id: ' + latest.failed.id} - - - {'Cause: ' + latest.failed.failure_message} - - - )} + + id: {latest?.failed.id ?? 'None'} + + {/**/} + {/* {'Cause: ' + latest?.failed?.failure_message ?? 'None'}*/} + {/**/} - {latest.completed === null ? 'None' : latest.completed.external_path} + {latest?.completed?.external_path ?? 'None'} - {checkpointsConfigInfo.externalization.enabled ? 'Enabled' : 'Disabled'} + {checkpointsConfigInfo?.externalization.enabled ? 'Enabled' : 'Disabled'} - {latest.savepoint === null ? 'None' : latest.savepoint.external_path} + {latest?.savepoint?.external_path ?? 'None'} - Total: {counts.total} + Total: {counts?.total ?? 0} - Failed: {counts.failed} + Failed: {counts?.failed ?? 0} - Restored: {counts.restored} + Restored: {counts?.restored ?? 0} - Completed: {counts.completed} + Completed: {counts?.completed ?? 0} - In Progress: {counts.in_progress} + In Progress: {counts?.in_progress ?? 0} diff --git a/dinky-web/src/pages/DevOps/JobDetail/JobLineage/index.tsx b/dinky-web/src/pages/DevOps/JobDetail/JobLineage/index.tsx new file mode 100644 index 0000000000..08f3d86ffe --- /dev/null +++ b/dinky-web/src/pages/DevOps/JobDetail/JobLineage/index.tsx @@ -0,0 +1,46 @@ +import LineageGraph from '@/components/LineageGraph'; +import { DevopsType } from '@/pages/DevOps/JobDetail/model'; +import { queryDataByParams } from '@/services/BusinessCrud'; +import { API_CONSTANTS } from '@/services/endpoints'; +import { LineageDetailInfo } from '@/types/DevOps/data'; +import { l } from '@/utils/intl'; +import { connect } from '@umijs/max'; +import { Card, Result } from 'antd'; +import React, { useEffect } from 'react'; +import 'react-lineage-dag/dist/index.css'; + +const JobLineage: React.FC = (props) => { + const { + jobDetail: { id: jobInstanceId } + } = props; + + const [lineageData, setLineageData] = React.useState({ + tables: [], + relations: [] + }); + const queryLineageData = () => { + queryDataByParams(API_CONSTANTS.JOB_INSTANCE_GET_LINEAGE, { id: jobInstanceId }).then((res) => + setLineageData(res as LineageDetailInfo) + ); + }; + + useEffect(() => { + queryLineageData(); + }, [jobInstanceId]); + + return ( + <> + + {lineageData && (lineageData.tables.length !== 0 || lineageData.relations.length !== 0) ? ( + + ) : ( + + )} + + + ); +}; + +export default connect(({ Devops }: { Devops: DevopsType }) => ({ + jobDetail: Devops.jobInfoDetail +}))(JobLineage); diff --git a/dinky-web/src/pages/DevOps/JobDetail/JobOverview/JobOverview.tsx b/dinky-web/src/pages/DevOps/JobDetail/JobOverview/JobOverview.tsx index 420fb205fd..8f1370479c 100644 --- a/dinky-web/src/pages/DevOps/JobDetail/JobOverview/JobOverview.tsx +++ b/dinky-web/src/pages/DevOps/JobDetail/JobOverview/JobOverview.tsx @@ -36,7 +36,11 @@ const JobConfigTab = (props: JobProps) => { height: '40vh' }} > - {job ? : } + {job ? ( + + ) : ( + + )} diff --git a/dinky-web/src/pages/DevOps/JobDetail/index.tsx b/dinky-web/src/pages/DevOps/JobDetail/index.tsx index b5d8013ce9..8ca169b64f 100644 --- a/dinky-web/src/pages/DevOps/JobDetail/index.tsx +++ b/dinky-web/src/pages/DevOps/JobDetail/index.tsx @@ -20,6 +20,7 @@ import JobLifeCycleTag from '@/components/JobTags/JobLifeCycleTag'; import AlertHistory from '@/pages/DevOps/JobDetail/AlertHistory'; import CheckPoints from '@/pages/DevOps/JobDetail/CheckPointsTab'; +import JobLineage from '@/pages/DevOps/JobDetail/JobLineage'; import JobLogsTab from '@/pages/DevOps/JobDetail/JobLogs/JobLogsTab'; import JobMetrics from '@/pages/DevOps/JobDetail/JobMetrics'; import JobOperator from '@/pages/DevOps/JobDetail/JobOperator/JobOperator'; @@ -70,7 +71,7 @@ const JobDetail = (props: any) => { [OperatorEnum.JOB_VERSION]: , [OperatorEnum.JOB_CHECKPOINTS]: , [OperatorEnum.JOB_METRICS]: , - // [OperatorEnum.JOB_LINEAGE]: , + [OperatorEnum.JOB_LINEAGE]: , [OperatorEnum.JOB_ALERT]: }; diff --git a/dinky-web/src/pages/Metrics/Job/index.tsx b/dinky-web/src/pages/Metrics/Job/index.tsx index f8ba26019e..868fae0f53 100644 --- a/dinky-web/src/pages/Metrics/Job/index.tsx +++ b/dinky-web/src/pages/Metrics/Job/index.tsx @@ -17,290 +17,290 @@ * */ -import {ChartData, JobMetrics, MetricsLayout, SubTask, Task} from '@/pages/Metrics/Job/data'; +import { ChartData, JobMetrics, MetricsLayout, SubTask, Task } from '@/pages/Metrics/Job/data'; import { - buildMetricsList, - buildRunningJobList, - buildSubTaskList + buildMetricsList, + buildRunningJobList, + buildSubTaskList } from '@/pages/Metrics/Job/function'; -import {getFlinkRunTask, saveFlinkMetrics} from '@/pages/Metrics/Job/service'; -import {getData} from '@/services/api'; -import {API_CONSTANTS} from '@/services/endpoints'; -import {l} from '@/utils/intl'; -import {ProCard, ProFormSelect} from '@ant-design/pro-components'; -import {Button, Input, Row} from 'antd'; -import {useEffect, useState} from 'react'; +import { getFlinkRunTask, saveFlinkMetrics } from '@/pages/Metrics/Job/service'; +import { getData } from '@/services/api'; +import { API_CONSTANTS } from '@/services/endpoints'; +import { l } from '@/utils/intl'; +import { ProCard, ProFormSelect } from '@ant-design/pro-components'; +import { Button, Input, Row } from 'antd'; +import { useEffect, useState } from 'react'; import FlinkChart from '../../../components/FlinkChart'; const getJobMetrics = async (job: JobMetrics) => { - const url = - API_CONSTANTS.FLINK_PROXY + - '/' + - job.url + - '/jobs/' + - job.flinkJobId + - '/vertices/' + - job.subTaskId + - '/metrics' + - '?get=' + - encodeURIComponent(job.metrics); - const json = await getData(url); - json[0].time = new Date(); - return json[0] as ChartData; + const url = + API_CONSTANTS.FLINK_PROXY + + '/' + + job.url + + '/jobs/' + + job.flinkJobId + + '/vertices/' + + job.subTaskId + + '/metrics' + + '?get=' + + encodeURIComponent(job.metrics); + const json = await getData(url); + json[0].time = new Date(); + return json[0] as ChartData; }; const Job = () => { - const [metricsData, setMetricsData] = useState({ - url: '', - jid: '', - flinkName: '', - selectTaskId: 0, - selectSubTask: '', - selectMetrics: [] as string[] - }); + const [metricsData, setMetricsData] = useState({ + url: '', + jid: '', + flinkName: '', + selectTaskId: 0, + selectSubTask: '', + selectMetrics: [] as string[] + }); - const [subTaskList, setSubTaskList] = useState([]); - const [metrics, setMetrics] = useState([]); - const [taskData, setTaskData] = useState([]); - const [jobMetricsList, setJobMetricsList] = useState([]); - const [chartData, setChartData] = useState>({}); - const [layoutName, setLayoutName] = useState(''); - const [timers, setTimers] = useState>({}); + const [subTaskList, setSubTaskList] = useState([]); + const [metrics, setMetrics] = useState([]); + const [taskData, setTaskData] = useState([]); + const [jobMetricsList, setJobMetricsList] = useState([]); + const [chartData, setChartData] = useState>({}); + const [layoutName, setLayoutName] = useState(''); + const [timers, setTimers] = useState>({}); - useEffect(() => { - getFlinkRunTask().then((res) => { - setTaskData(res.data); - }); - }, []); + useEffect(() => { + getFlinkRunTask().then((res) => { + setTaskData(res.data); + }); + }, []); - useEffect(() => { - Object.keys(timers) - .filter((x) => !jobMetricsList.map((x) => x.metrics).includes(x)) - // @ts-ignore - .forEach((x) => clearInterval(timers[x])); - }, [jobMetricsList]); + useEffect(() => { + Object.keys(timers) + .filter((x) => !jobMetricsList.map((x) => x.metrics).includes(x)) + // @ts-ignore + .forEach((x) => clearInterval(timers[x])); + }, [jobMetricsList]); - /** - * query flink job detail - * @param {number} id - * @returns {Promise} - */ - const getFlinkTaskDetail = async (id: number) => { - return await getData(API_CONSTANTS.REFRESH_JOB_DETAIL, {id: id}); - }; + /** + * query flink job detail + * @param {number} id + * @returns {Promise} + */ + const getFlinkTaskDetail = async (id: number) => { + return await getData(API_CONSTANTS.REFRESH_JOB_DETAIL, { id: id }); + }; - /** - * query flink job sub task - * @param {string} url - * @param {string} jid - * @returns {Promise<[]>} - */ - const getFlinkJobSubTask = async (url: string, jid: string) => { - const flinkJobVertices = await getData(API_CONSTANTS.FLINK_PROXY + '/' + url + '/jobs/' + jid); - return flinkJobVertices.vertices as SubTask[]; - }; + /** + * query flink job sub task + * @param {string} url + * @param {string} jid + * @returns {Promise<[]>} + */ + const getFlinkJobSubTask = async (url: string, jid: string) => { + const flinkJobVertices = await getData(API_CONSTANTS.FLINK_PROXY + '/' + url + '/jobs/' + jid); + return flinkJobVertices.vertices as SubTask[]; + }; - /** - * query flink job metrics list - * @param {string} url - * @param {string} jid - * @param subTask - * @returns {Promise} - */ - const getFlinkJobMetrics = async (url: string, jid: string, subTask: string) => { - const flinkJobMetrics = await getData( - API_CONSTANTS.FLINK_PROXY + '/' + url + '/jobs/' + jid + '/vertices/' + subTask + '/metrics' - ); - return (flinkJobMetrics as any[]).map((x) => x.id as string); - }; + /** + * query flink job metrics list + * @param {string} url + * @param {string} jid + * @param subTask + * @returns {Promise} + */ + const getFlinkJobMetrics = async (url: string, jid: string, subTask: string) => { + const flinkJobMetrics = await getData( + API_CONSTANTS.FLINK_PROXY + '/' + url + '/jobs/' + jid + '/vertices/' + subTask + '/metrics' + ); + return (flinkJobMetrics as any[]).map((x) => x.id as string); + }; - /** - * 1 level , change running job - * @returns {Promise} - * @param taskId - */ - const handleRunningJobChange = async (taskId: number) => { - // query data of flink running job - const taskDetail = await getFlinkTaskDetail(taskId); - // 解构出 flink job url , job name , job id - const { - cluster: {hosts: url}, - instance: {name: flinkJobName, jid: flinkJobId} - } = taskDetail.datas; - setMetricsData((prevState) => ({ - ...prevState, - url: url, - flinkName: flinkJobName, - jid: flinkJobId, - selectTaskId: taskId - })); - const subTasks = await getFlinkJobSubTask(url, flinkJobId); - setSubTaskList(subTasks); - }; + /** + * 1 level , change running job + * @returns {Promise} + * @param taskId + */ + const handleRunningJobChange = async (taskId: number) => { + // query data of flink running job + const taskDetail = await getFlinkTaskDetail(taskId); + // 解构出 flink job url , job name , job id + const { + cluster: { hosts: url }, + instance: { name: flinkJobName, jid: flinkJobId } + } = taskDetail.datas; + setMetricsData((prevState) => ({ + ...prevState, + url: url, + flinkName: flinkJobName, + jid: flinkJobId, + selectTaskId: taskId + })); + const subTasks = await getFlinkJobSubTask(url, flinkJobId); + setSubTaskList(subTasks); + }; - /** - * 2 level , change subtask - * @returns {Promise} - * @param subTaskName - */ - const handleSubTaskChange = async (subTaskName: string) => { - setMetricsData((prevState) => ({ - ...prevState, - selectSubTask: subTaskName - })); - const jobMetricsDataList = await getFlinkJobMetrics( - metricsData.url, - metricsData.jid, - subTaskName - ); - setMetrics(jobMetricsDataList.sort()); - }; + /** + * 2 level , change subtask + * @returns {Promise} + * @param subTaskName + */ + const handleSubTaskChange = async (subTaskName: string) => { + setMetricsData((prevState) => ({ + ...prevState, + selectSubTask: subTaskName + })); + const jobMetricsDataList = await getFlinkJobMetrics( + metricsData.url, + metricsData.jid, + subTaskName + ); + setMetrics(jobMetricsDataList.sort()); + }; - /** - * 3 level , change metrics list - * @returns {Promise} - * @param selectList - */ - const handleMetricsChange = async (selectList: string[]) => { - setMetricsData((prevState) => ({ - ...prevState, - selectMetrics: selectList - })); + /** + * 3 level , change metrics list + * @returns {Promise} + * @param selectList + */ + const handleMetricsChange = async (selectList: string[]) => { + setMetricsData((prevState) => ({ + ...prevState, + selectMetrics: selectList + })); - const d: JobMetrics[] = selectList.map((item) => { - return { - taskId: metricsData.selectTaskId, - url: metricsData.url, - flinkJobId: metricsData.jid, - jobName: metricsData.flinkName, - subTaskId: metricsData.selectSubTask, - metrics: item, - layoutName: layoutName, - title: item, - showSize: '25%', - showType: 'Chart' - }; - }); - d.forEach((j) => { - const data: ChartData[] = []; - chartData[j.taskId + j.subTaskId + j.metrics] = data; - setChartData(chartData); - timers[j.metrics] = setInterval(() => { - getJobMetrics(j).then((res) => { - data.push(res); - }); - }, 1000); - setTimers(timers); + const d: JobMetrics[] = selectList.map((item) => { + return { + taskId: metricsData.selectTaskId, + url: metricsData.url, + flinkJobId: metricsData.jid, + jobName: metricsData.flinkName, + subTaskId: metricsData.selectSubTask, + metrics: item, + layoutName: layoutName, + title: item, + showSize: '25%', + showType: 'Chart' + }; + }); + d.forEach((j) => { + const data: ChartData[] = []; + chartData[j.taskId + j.subTaskId + j.metrics] = data; + setChartData(chartData); + timers[j.metrics] = setInterval(() => { + getJobMetrics(j).then((res) => { + data.push(res); }); - setJobMetricsList(d); - }; - /** - * render metrics card list - * @param {JobMetrics[]} metricsList - * @returns {JSX.Element} - */ - const renderMetricsCardList = (metricsList: JobMetrics[]) => { - return ( - <> - - {metricsList.map((j) => { - return ( - { - j.showSize = chartSize; - j.showType = chartType; - }} - data={chartData[j.taskId + j.subTaskId + j.metrics]} - title={j.metrics} - extraType={'size'} - /> - ); - })} - - - ); - }; - + }, 1000); + setTimers(timers); + }); + setJobMetricsList(d); + }; + /** + * render metrics card list + * @param {JobMetrics[]} metricsList + * @returns {JSX.Element} + */ + const renderMetricsCardList = (metricsList: JobMetrics[]) => { return ( - <> - setLayoutName(e.target.value)} - style={{width: '100vh'}} - /> - } - extra={ - - } - > - handleRunningJobChange(value as number)}} - /> - {metricsData.selectTaskId !== 0 && ( - handleSubTaskChange(value as string)}} - /> - )} - {metricsData.selectSubTask !== '' && ( - handleMetricsChange(value as string[])}} - /> - )} - {/* render metrics list */} - {jobMetricsList.length > 0 && renderMetricsCardList(jobMetricsList)} - - + <> + + {metricsList.map((j) => { + return ( + { + j.showSize = chartSize; + j.showType = chartType; + }} + data={chartData[j.taskId + j.subTaskId + j.metrics]} + title={j.metrics} + extraType={'size'} + /> + ); + })} + + ); + }; + + return ( + <> + setLayoutName(e.target.value)} + style={{ width: '100vh' }} + /> + } + extra={ + + } + > + handleRunningJobChange(value as number) }} + /> + {metricsData.selectTaskId !== 0 && ( + handleSubTaskChange(value as string) }} + /> + )} + {metricsData.selectSubTask !== '' && ( + handleMetricsChange(value as string[]) }} + /> + )} + {/* render metrics list */} + {jobMetricsList.length > 0 && renderMetricsCardList(jobMetricsList)} + + + ); }; export default Job; diff --git a/dinky-web/src/services/endpoints.tsx b/dinky-web/src/services/endpoints.tsx index 2608e0fa0a..14a31dcd10 100644 --- a/dinky-web/src/services/endpoints.tsx +++ b/dinky-web/src/services/endpoints.tsx @@ -226,6 +226,10 @@ export enum API_CONSTANTS { GET_TASKMANAGER_LOG = 'api/jobInstance/getTaskManagerLog', GET_JOB_MERTICE_ITEMS = 'api/jobInstance/getJobMetricsItems', CANCEL_JOB = '/api/studio/cancel', + // /api/studio/getLineage + STUDIO_GET_LINEAGE = '/api/studio/getLineage', + // /api/jobInstance/getLineage + JOB_INSTANCE_GET_LINEAGE = '/api/jobInstance/getLineage', OFFLINE_TASK = '/api/task/offLineTask', RESTART_TASK = '/api/task/restartTask', RESTART_TASK_FROM_CHECKPOINT = '/api/task/selectSavePointRestartTask', diff --git a/dinky-web/src/types/DevOps/data.d.ts b/dinky-web/src/types/DevOps/data.d.ts index 8fe3dd42c8..b5671e6b66 100644 --- a/dinky-web/src/types/DevOps/data.d.ts +++ b/dinky-web/src/types/DevOps/data.d.ts @@ -216,3 +216,28 @@ export interface AlertHistory { createTime: Date; updateTime: Date; } + +export interface LineageTableColumn { + name: string; + title: string; +} + +export interface LineageTable { + id: string; + name: string; + isCollapse: boolean; + columns: LineageTableColumn[]; +} + +export interface LineageRelations { + id: string; + srcTableId: string; + tgtTableId: string; + srcTableColName: string; + tgtTableColName: string; +} + +export interface LineageDetailInfo { + tables: LineageTable[]; + relations: LineageRelations[]; +} diff --git a/script/sql/dinky-mysql.sql b/script/sql/dinky-mysql.sql index 8f3c975218..dd3afe995b 100644 --- a/script/sql/dinky-mysql.sql +++ b/script/sql/dinky-mysql.sql @@ -668,7 +668,7 @@ CREATE TABLE `dinky_task` ( `alert_group_id` bigint(20) NULL DEFAULT NULL COMMENT 'alert group id', `config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT 'configuration json', `note` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'Job Note', - `step` int(11) NULL DEFAULT NULL COMMENT 'Job lifecycle', + `step` int(11) NULL DEFAULT 1 COMMENT 'Job lifecycle,', `job_instance_id` bigint(20) NULL DEFAULT NULL COMMENT 'job instance id', `enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'is enable', `create_time` datetime(0) NULL DEFAULT NULL COMMENT 'create time', diff --git a/script/sql/dinky-pg.sql b/script/sql/dinky-pg.sql index e375132e28..dc2b4e2d90 100644 --- a/script/sql/dinky-pg.sql +++ b/script/sql/dinky-pg.sql @@ -1132,7 +1132,7 @@ CREATE TABLE "public"."dinky_task" ( "alert_group_id" int8, "config_json" text COLLATE "pg_catalog"."default", "note" varchar(255) COLLATE "pg_catalog"."default", - "step" int4, + "step" int4 default 1, "job_instance_id" int8, "enabled" int2 NOT null, "create_time" timestamp(6), diff --git a/script/sql/upgrade/1.0.0-SNAPSHOT_schema/mysql/dinky_dml.sql b/script/sql/upgrade/1.0.0-SNAPSHOT_schema/mysql/dinky_dml.sql index d26456d9ca..3e28e5bf47 100644 --- a/script/sql/upgrade/1.0.0-SNAPSHOT_schema/mysql/dinky_dml.sql +++ b/script/sql/upgrade/1.0.0-SNAPSHOT_schema/mysql/dinky_dml.sql @@ -235,3 +235,5 @@ COMMIT; update dinky_user set super_admin_flag =1 where id =1; + +alter table dinky_task alter column `step` set default 1;