diff --git a/dinky-web/package-lock.json b/dinky-web/package-lock.json index bd3f7f48f9..f85cd6470d 100644 --- a/dinky-web/package-lock.json +++ b/dinky-web/package-lock.json @@ -31,7 +31,6 @@ "@xterm/xterm": "^5.5.0", "antd": "5.21.0", "antd-style": "^3.7.1", - "butterfly-dag": "^4.3.29", "classnames": "^2.5.1", "dayjs": "^1.11.11", "echarts": "^5.5.0", @@ -54,7 +53,6 @@ "react-fast-marquee": "^1.6.5", "react-grid-layout": "^1.4.4", "react-helmet-async": "^2.0.5", - "react-lineage-dag": "^2.0.36", "react-lottie": "^1.2.4", "react-markdown": "^9.0.1", "react-resizable-panels": "^2.1.4", @@ -1682,25 +1680,6 @@ "node": ">=12" } }, - "node_modules/@antv/matrix-util": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@antv/matrix-util/-/matrix-util-3.0.4.tgz", - "integrity": "sha512-BAPyu6dUliHcQ7fm9hZSGKqkwcjEDVLVAstlHULLvcMZvANHeLXgHEgV7JqcAV/GIhIz8aZChIlzM1ZboiXpYQ==", - "dependencies": { - "@antv/util": "^2.0.9", - "gl-matrix": "^3.3.0", - "tslib": "^2.0.3" - } - }, - "node_modules/@antv/matrix-util/node_modules/@antv/util": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/@antv/util/-/util-2.0.17.tgz", - "integrity": "sha512-o6I9hi5CIUvLGDhth0RxNSFDRwXeywmt6ExR4+RmVAzIi48ps6HUy+svxOCayvrPBN37uE6TAc2KDofRo0nK9Q==", - "dependencies": { - "csstype": "^3.0.8", - "tslib": "^2.0.3" - } - }, "node_modules/@antv/scale": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/@antv/scale/-/scale-0.4.16.tgz", @@ -13571,49 +13550,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/butterfly-dag": { - "version": "4.3.29", - "resolved": "https://registry.npmjs.org/butterfly-dag/-/butterfly-dag-4.3.29.tgz", - "integrity": "sha512-4WwMoVVF6qyA56mojgRNNsuwUhyIfcAAbQ0J+Eb2kRONYk0QdiHqkYJKMDldFbRBk7wMD+w+B1FyW08vfeniCQ==", - "dependencies": { - "@antv/hierarchy": "~0.6.4", - "@antv/matrix-util": "^3.0.4", - "d3-force": "~2.1.1", - "dagre": "~0.8.5", - "dom-to-image": "~2.6.0", - "eventemitter3": "4.0.7", - "ml-matrix": "^6.5.0" - }, - "peerDependencies": { - "jquery": ">=2.0.0", - "lodash": ">=4.0.0" - } - }, - "node_modules/butterfly-dag/node_modules/d3-dispatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz", - "integrity": "sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA==" - }, - "node_modules/butterfly-dag/node_modules/d3-force": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-2.1.1.tgz", - "integrity": "sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew==", - "dependencies": { - "d3-dispatch": "1 - 2", - "d3-quadtree": "1 - 2", - "d3-timer": "1 - 2" - } - }, - "node_modules/butterfly-dag/node_modules/d3-timer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-2.0.0.tgz", - "integrity": "sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==" - }, - "node_modules/butterfly-dag/node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -15898,11 +15834,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/dom-to-image": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/dom-to-image/-/dom-to-image-2.6.0.tgz", - "integrity": "sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA==" - }, "node_modules/dom-walk": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", @@ -27984,18 +27915,6 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, - "node_modules/react-lineage-dag": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/react-lineage-dag/-/react-lineage-dag-2.0.36.tgz", - "integrity": "sha512-5eZPvi3sgJa3gLJqozGVzWVMhydwYZ7gtNNHD5bU0gZqormeNXDPzMY4AyKhB1GevyuMclAWZ/mg1aduifgzuQ==", - "peerDependencies": { - "butterfly-dag": ">=4.0.0", - "jquery": ">3.5.1", - "lodash": "^4.17.20", - "react": ">15.6.1", - "react-dom": ">15.6.1" - } - }, "node_modules/react-lottie": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/react-lottie/-/react-lottie-1.2.4.tgz", diff --git a/dinky-web/package.json b/dinky-web/package.json index 1a8a3b1b3c..cff3ba3f78 100644 --- a/dinky-web/package.json +++ b/dinky-web/package.json @@ -56,7 +56,6 @@ "@xterm/xterm": "^5.5.0", "antd": "5.21.0", "antd-style": "^3.7.1", - "butterfly-dag": "^4.3.29", "classnames": "^2.5.1", "dayjs": "^1.11.11", "echarts": "^5.5.0", @@ -79,7 +78,6 @@ "react-fast-marquee": "^1.6.5", "react-grid-layout": "^1.4.4", "react-helmet-async": "^2.0.5", - "react-lineage-dag": "^2.0.36", "react-lottie": "^1.2.4", "react-markdown": "^9.0.1", "react-resizable-panels": "^2.1.4", diff --git a/dinky-web/src/components/LineageGraph/index.tsx b/dinky-web/src/components/LineageGraph/index.tsx deleted file mode 100644 index 273b0dc627..0000000000 --- a/dinky-web/src/components/LineageGraph/index.tsx +++ /dev/null @@ -1,344 +0,0 @@ -/* - * - * 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 { Badge, Tooltip, Typography } from 'antd'; - -import LineageDagExt from '@/components/LineageGraph/lineage-dag-ext'; -import { - LineageDetailInfo, - LineageRelations, - LineageTable, - LineageTableColumn -} from '@/types/DevOps/data.d'; -import { l } from '@/utils/intl'; -import { SuccessNotification, WarningNotification } from '@/utils/messages'; -import { - ArrowsAltOutlined, - ColumnHeightOutlined, - CompassOutlined, - InsertRowAboveOutlined, - ReloadOutlined, - ShrinkOutlined -} from '@ant-design/icons'; -import React, { useEffect } from 'react'; -import 'react-lineage-dag/dist/index.css'; - -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; -} - -type JobLineageProps = { - lineageData: LineageDetailInfo; - refreshCallBack: () => void; -}; - -type ITable = { - id: string; - name: string; - isCollapse?: boolean; - fields: LineageTableColumn[]; -}; - -const { Text } = Typography; - -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 -}; - -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 - })); -}; - -const LineageGraph: React.FC = (props) => { - const { lineageData, refreshCallBack } = props; - const [lineageState, setLineageState] = React.useState({ - ...InitLineageState, - lineageData: lineageData, - tables: buildLineageTables(lineageData.tables), - relations: buildLineageRelations(lineageData.relations) - }); - - useEffect(() => { - if (lineageData) { - setLineageState((prevState) => ({ - ...prevState, - lineageData: lineageData, - tables: buildLineageTables(lineageData.tables), - relations: buildLineageRelations(lineageData.relations) - })); - } - }, [lineageData, lineageState.refresh]); - - const handleExpandField = (nodeData: any, tables: ITable[]) => { - const { isCollapse, id } = nodeData; - lineageState.tables - .filter((item) => item.id === id) - .forEach((item) => (item.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, 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: 30, - lineWidth: 0.2, - circleRadiu: 5 - } - } - }} - butterfly={{ - zoomable: true, - draggable: true, - movable: true, - linkable: true - }} - actionMenu={renderExtActionButton()} - /> - ); -}; - -export default LineageGraph; diff --git a/dinky-web/src/components/LineageGraph/lineage-dag-ext.tsx b/dinky-web/src/components/LineageGraph/lineage-dag-ext.tsx deleted file mode 100644 index e2d098a0ce..0000000000 --- a/dinky-web/src/components/LineageGraph/lineage-dag-ext.tsx +++ /dev/null @@ -1,148 +0,0 @@ -/* - * - * 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 LineageDag from 'react-lineage-dag'; - -import * as _ from 'lodash'; -import * as ReactDOM from 'react-dom'; -import { transformEdges, transformInitData } from 'react-lineage-dag/src/adaptor'; -import LineageCanvas from 'react-lineage-dag/src/canvas/canvas'; - -export default class LineageDagExt extends LineageDag { - componentDidMount() { - let root = ReactDOM.findDOMNode(this) as HTMLElement; - - let enableHoverChain = _.get(this.props, 'config.enableHoverChain', true); - let titleRender = _.get(this.props, 'config.titleRender'); - - let canvasObj = { - root: root, - disLinkable: false, - linkable: false, - draggable: false, - zoomable: true, - moveable: true, - theme: { - edge: { - type: 'endpoint', - shapeType: 'AdvancedBezier', - arrow: true, - isExpandWidth: true, - arrowPosition: 1, - arrowOffset: -5 - }, - endpoint: { - limitNum: undefined, - expandArea: { - left: 0, - right: 0, - top: 0, - botton: 0 - } - } - }, - data: { - enableHoverChain: enableHoverChain - } - }; - - this.canvas = new LineageCanvas(canvasObj); - - let result = transformInitData({ - tables: this.props.tables, - relations: this.props.relations, - columns: this.props.columns, - operator: this.props.operator, - _titleRender: titleRender, - _enableHoverChain: enableHoverChain, - _emptyContent: this.props.emptyContent, - _emptyWidth: this.props.emptyWidth - }); - - this.originEdges = result.edges; - - result = transformEdges(result.nodes, _.cloneDeep(result.edges)); - result.edges = result.edges.map((item) => { - return { - ...item, - // 线条的类型: Bezier/Flow/Straight/Manhattan/AdvancedBezier/Bezier2-1/Bezier2-2/Bezier2-3/BrokenLine , 新版本可以指定 但是布局调整了 - // https://github.com/zhu-mingye/butterfly/blob/master/docs/zh-CN/edge.md#shapetype--string----%E9%80%89%E5%A1%AB - shapeType: 'AdvancedBezier' - }; - }); - - this.canvasData = { - nodes: result.nodes, - edges: result.edges - }; - - setTimeout( - () => { - let tmpEdges = result.edges; - result.edges = []; - // this.canvas.wrapper.style.visibility = 'hidden'; - this.canvas.draw(result, () => { - this.canvas.relayout( - { - edges: tmpEdges.map((item) => { - return { - source: item.sourceNode, - target: item.targetNode - }; - }) - }, - true - ); - // this.canvas.wrapper.style.visibility = 'visible'; - this.canvas.addEdges(tmpEdges, true); - - let minimap = _.get(this, 'props.config.minimap', {}); - - const minimapCfg = _.assign({}, minimap.config, { - events: ['system.node.click', 'system.canvas.click'] - }); - - if (minimap && minimap.enable) { - this.canvas.setMinimap(true, minimapCfg); - } - - if (_.get(this, 'props.config.gridMode')) { - this.canvas.setGridMode(true, _.assign({}, _.get(this, 'props.config.gridMode', {}))); - } - - if (result.nodes.length !== 0) { - this.canvas.focusCenterWithAnimate(); - this._isFirstFocus = true; - } - - this.forceUpdate(); - this.props.onLoaded && this.props.onLoaded(this.canvas); - }); - this.canvas.on('system.node.click', (data) => { - let node = data.node; - this.canvas.focus(node.id); - }); - this.canvas.on('system.canvas.click', () => { - this.canvas.unfocus(); - }); - }, - _.get(this.props, 'config.delayDraw', 0) - ); - } -} diff --git a/dinky-web/src/pages/DataStudio/CenterTabContent/SqlTask/index.tsx b/dinky-web/src/pages/DataStudio/CenterTabContent/SqlTask/index.tsx index 9eafcf375e..302c73d8b9 100644 --- a/dinky-web/src/pages/DataStudio/CenterTabContent/SqlTask/index.tsx +++ b/dinky-web/src/pages/DataStudio/CenterTabContent/SqlTask/index.tsx @@ -140,6 +140,8 @@ export const SqlTask = memo((props: FlinkSqlProps & any) => { // 代码恢复 const [openDiffModal, setOpenDiffModal] = useState(false); const [diff, setDiff] = useState([]); + // 是否正在提交 + const [isSubmitting, setIsSubmitting] = useState(false); const formRef = useRef(); const [isFullscreen, { enterFullscreen, exitFullscreen }] = useFullscreen(containerRef); @@ -386,36 +388,41 @@ export const SqlTask = memo((props: FlinkSqlProps & any) => { }, [currentState, updateAction]); const handleSubmit = useCallback(async () => { - await handleSave(); - updateAction({ - actionType: DataStudioActionType.TASK_RUN_SUBMIT, - params: { - taskId: currentState.taskId, - envId: currentState.envId - } - }); - const result = await executeSql( - l('pages.datastudio.editor.submitting', '', { jobName: title }), - currentState.taskId - ); - if (result.success) { - setCurrentState((prevState) => { - return { - ...prevState, - status: result.data.status === 'SUCCESS' ? 'RUNNING' : result.data.status - }; + setIsSubmitting(true); + try { + await handleSave(); + updateAction({ + actionType: DataStudioActionType.TASK_RUN_SUBMIT, + params: { + taskId: currentState.taskId, + envId: currentState.envId + } }); - if (isSql(currentState.dialect) && result?.data?.result?.success) { - updateAction({ - actionType: DataStudioActionType.TASK_PREVIEW_RESULT, - params: { - taskId: currentState.taskId, - dialect: currentState.dialect, - columns: result.data.result.columns, - rowData: result.data.result.rowData - } + const result = await executeSql( + l('pages.datastudio.editor.submitting', '', { jobName: title }), + currentState.taskId + ); + if (result.success) { + setCurrentState((prevState) => { + return { + ...prevState, + status: result.data.status === 'SUCCESS' ? 'RUNNING' : result.data.status + }; }); + if (isSql(currentState.dialect) && result?.data?.result?.success) { + updateAction({ + actionType: DataStudioActionType.TASK_PREVIEW_RESULT, + params: { + taskId: currentState.taskId, + dialect: currentState.dialect, + columns: result.data.result.columns, + rowData: result.data.result.rowData + } + }); + } } + } finally { + setIsSubmitting(false); } }, [updateAction, currentState.envId, handleSave, currentState.taskId, currentState.dialect]); @@ -426,31 +433,36 @@ export const SqlTask = memo((props: FlinkSqlProps & any) => { taskId: params.taskId } }); - const res = await debugTask( - l('pages.datastudio.editor.debugging', '', { jobName: currentState.name }), - { ...currentState } - ); - if (res?.success && res?.data?.result?.success) { - updateAction({ - actionType: DataStudioActionType.TASK_PREVIEW_RESULT, - params: { - taskId: params.taskId, - isMockSinkResult: res.data?.result?.mockSinkResult, - columns: res.data?.result?.columns ?? [], - rowData: res.data?.result?.rowData ?? [] - } - }); - setCurrentState((prevState) => { - return { - ...prevState, - status: - res.data.status === 'SUCCESS' - ? res.data.pipeline - ? 'RUNNING' - : 'SUCCESS' - : res.data.status - }; - }); + setIsSubmitting(true); + try { + const res = await debugTask( + l('pages.datastudio.editor.debugging', '', { jobName: currentState.name }), + { ...currentState } + ); + if (res?.success && res?.data?.result?.success) { + updateAction({ + actionType: DataStudioActionType.TASK_PREVIEW_RESULT, + params: { + taskId: params.taskId, + isMockSinkResult: res.data?.result?.mockSinkResult, + columns: res.data?.result?.columns ?? [], + rowData: res.data?.result?.rowData ?? [] + } + }); + setCurrentState((prevState) => { + return { + ...prevState, + status: + res.data.status === 'SUCCESS' + ? res.data.pipeline + ? 'RUNNING' + : 'SUCCESS' + : res.data.status + }; + }); + } + } finally { + setIsSubmitting(false); } }, [currentState, updateAction]); @@ -653,7 +665,7 @@ export const SqlTask = memo((props: FlinkSqlProps & any) => { ) } showDesc={showDesc} - disabled={isLockTask} + disabled={isLockTask || isSubmitting} color={'green'} desc={l('pages.datastudio.editor.exec')} icon={} @@ -670,7 +682,7 @@ export const SqlTask = memo((props: FlinkSqlProps & any) => { assert(currentState.dialect, [DIALECT.FLINK_SQL], true, 'includes') } showDesc={showDesc} - disabled={isLockTask} + disabled={isLockTask || isSubmitting} color={'red'} desc={l('pages.datastudio.editor.debug')} icon={} diff --git a/dinky-web/src/pages/DataStudio/Toolbar/Service/Lineage/index.tsx b/dinky-web/src/pages/DataStudio/Toolbar/Service/Lineage/index.tsx index f85e7bb8a0..408724b17a 100644 --- a/dinky-web/src/pages/DataStudio/Toolbar/Service/Lineage/index.tsx +++ b/dinky-web/src/pages/DataStudio/Toolbar/Service/Lineage/index.tsx @@ -17,22 +17,242 @@ * */ -import LineageGraph from '@/components/LineageGraph'; +import { Circle, Group, Path } from '@antv/g'; +import { + ExtensionCategory, + Graph, + PathArray, + RectCombo, + RectComboStyleProps, + register +} from '@antv/g6'; +import { memo, useContext, useEffect, useRef } from 'react'; +import { Flex } from 'antd'; +import { ReactNode } from '@antv/g6-extension-react'; +import { Graphin } from '@antv/graphin'; +import { DagreLayout, GridLayout } from '@antv/layout'; import { LineageDetailInfo } from '@/types/DevOps/data'; -import { l } from '@/utils/intl'; -import { Card, Result } from 'antd'; -import React from 'react'; +import { DataStudioContext } from '@/pages/DataStudio/DataStudioContext'; -export default (props: { data: LineageDetailInfo }) => { +const collapse = (x: number, y: number, r: number) => { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x + r - 4, y] + ] as PathArray; +}; + +const expand = (x: number, y: number, r: number) => { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x - r + 2 * r - 4, y], + ['M', x - r + r, y - r + 4], + ['L', x, y + r - 4] + ] as PathArray; +}; + +class RectComboWithExtraButton extends RectCombo { + render(attributes: Required, container: Group) { + super.render(attributes, container); + this.drawButton(attributes); + } + + drawButton(attributes: Required) { + const { collapsed } = attributes; + const [, height] = this.getKeySize(attributes); + const btnR = 8; + const y = -(height / 2 + btnR); + const d = collapsed ? expand(0, y, btnR) : collapse(0, y, btnR); + + const hitArea = this.upsert( + 'hit-area', + Circle, + { cy: y, r: 10, fill: '#fff', cursor: 'pointer' }, + this + ); + this.upsert('button', Path, { stroke: '#3d81f7', d, cursor: 'pointer' }, hitArea!!); + } + + onCreate() { + this.shapeMap['hit-area'].addEventListener('click', () => { + const id = this.id; + const collapsed = !this.attributes.collapsed; + const { graph } = this.attributes.context!!; + if (collapsed) graph.collapseElement(id); + else graph.expandElement(id); + }); + } +} + +register(ExtensionCategory.COMBO, 'circle-combo-with-extra-button', RectComboWithExtraButton); + +register(ExtensionCategory.NODE, 'react', ReactNode); +export const Lineage = memo((props: { data: LineageDetailInfo }) => { const { data } = props; + const { theme } = useContext(DataStudioContext); + const graphRef = useRef(null); + const containerRef = useRef(null); + + useEffect(() => { + // 监控布局宽度高度变化,重新计算树的高度 + const element = containerRef.current!!; + const observer = new ResizeObserver((entries) => { + if ( + graphRef.current && + entries?.length === 1 && + entries[0].contentRect.width > 0 && + entries[0].contentRect.height > 0 + ) { + graphRef.current?.setSize(entries[0].contentRect.width, entries[0].contentRect.height); + } + }); + observer.observe(element); + return () => observer.unobserve(element); + }, []); + // 把data.tables 的id ,name转成map + const tablesMap = data.tables.reduce( + (acc, item) => { + acc[item.id] = item.name; + return acc; + }, + {} as Record + ); return ( - - {data && (data.tables.length !== 0 || data.relations.length !== 0) ? ( - // todo 刷新没用,可以去掉api - {}} /> - ) : ( - - )} - +
+ + item.columns.map((column) => ({ + id: item.id + column.name, + combo: item.id, + data: { name: column.name } + })) + ), + edges: data.relations.map((item) => ({ + source: item.srcTableId + item.srcTableColName, + target: item.tgtTableId + item.tgtTableColName + })), + combos: data.tables.map((item) => ({ id: item.id })) + }, + combo: { + type: 'circle-combo-with-extra-button', + style: { + labelText: (d) => tablesMap[d.id] + } + }, + node: { + type: 'react', + style: { + size: [240, 20], + component: (data: { data: { name: string } }) => ( + + {data.data.name} + + ), + port: true, + ports: [{ placement: 'right' }, { placement: 'left' }] + } + }, + edge: { + type: 'cubic-horizontal', + style: { + endArrow: true, + endArrowType: 'vee' + } + }, + layout: { + type: 'combo-combined', + innerLayout: new GridLayout({ cols: 1, condense: true }), + outerLayout: new DagreLayout({ + rankdir: 'LR', + edgeLabelSpace: false, + nodesep: 5, + ranksep: 50 + }) + }, + behaviors: [ + 'focus-element', + 'drag-canvas', + 'zoom-canvas', + { + type: 'hover-activate', + enable: (event: any) => event.targetType === 'node', + degree: 1, // 👈🏻 Activate relations. + state: 'highlight', + inactiveState: 'dim', + onHover: (event: any) => { + event.view.setCursor('pointer'); + }, + onHoverEnd: (event: any) => { + event.view.setCursor('default'); + } + } + ], + plugins: [ + { + key: 'grid-line', + type: 'grid-line', + follow: false, + size: 40, + stroke: 'var(--border-color)', + borderStroke: 'var(--border-color)' + }, + { + type: 'toolbar', + position: 'right-top', + onClick: (item: string) => { + const graph = graphRef.current; + switch (item) { + // 放大 + case 'zoom-in': + graph?.zoomTo(graph?.getZoom() + 0.2); + break; + case 'zoom-out': + graph?.zoomBy(0.5); + break; + case 'auto-fit': + graph?.fitView(); + break; + } + }, + getItems: () => { + return [ + { id: 'zoom-in', value: 'zoom-in' }, + { id: 'zoom-out', value: 'zoom-out' }, + { id: 'auto-fit', value: 'auto-fit' } + ]; + }, + style: { + backgroundColor: 'var(--btn-background-color)' + } + }, + { key: 'background', type: 'background', background: 'var(--primary-color)' } + ], + transforms: ['process-parallel-edges'], + autoFit: 'view' + }} + /> +
); -}; +}); diff --git a/dinky-web/src/pages/DataStudio/Toolbar/Service/LineageNew/index.tsx b/dinky-web/src/pages/DataStudio/Toolbar/Service/LineageNew/index.tsx deleted file mode 100644 index 69e3f68e2b..0000000000 --- a/dinky-web/src/pages/DataStudio/Toolbar/Service/LineageNew/index.tsx +++ /dev/null @@ -1,258 +0,0 @@ -/* - * - * 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 { Circle, Group, Path } from '@antv/g'; -import { - ExtensionCategory, - Graph, - PathArray, - RectCombo, - RectComboStyleProps, - register -} from '@antv/g6'; -import { memo, useContext, useEffect, useRef } from 'react'; -import { Flex } from 'antd'; -import { ReactNode } from '@antv/g6-extension-react'; -import { Graphin } from '@antv/graphin'; -import { DagreLayout, GridLayout } from '@antv/layout'; -import { LineageDetailInfo } from '@/types/DevOps/data'; -import { DataStudioContext } from '@/pages/DataStudio/DataStudioContext'; - -const collapse = (x: number, y: number, r: number) => { - return [ - ['M', x - r, y], - ['a', r, r, 0, 1, 0, r * 2, 0], - ['a', r, r, 0, 1, 0, -r * 2, 0], - ['M', x - r + 4, y], - ['L', x + r - 4, y] - ] as PathArray; -}; - -const expand = (x: number, y: number, r: number) => { - return [ - ['M', x - r, y], - ['a', r, r, 0, 1, 0, r * 2, 0], - ['a', r, r, 0, 1, 0, -r * 2, 0], - ['M', x - r + 4, y], - ['L', x - r + 2 * r - 4, y], - ['M', x - r + r, y - r + 4], - ['L', x, y + r - 4] - ] as PathArray; -}; - -class RectComboWithExtraButton extends RectCombo { - render(attributes: Required, container: Group) { - super.render(attributes, container); - this.drawButton(attributes); - } - - drawButton(attributes: Required) { - const { collapsed } = attributes; - const [, height] = this.getKeySize(attributes); - const btnR = 8; - const y = -(height / 2 + btnR); - const d = collapsed ? expand(0, y, btnR) : collapse(0, y, btnR); - - const hitArea = this.upsert( - 'hit-area', - Circle, - { cy: y, r: 10, fill: '#fff', cursor: 'pointer' }, - this - ); - this.upsert('button', Path, { stroke: '#3d81f7', d, cursor: 'pointer' }, hitArea!!); - } - - onCreate() { - this.shapeMap['hit-area'].addEventListener('click', () => { - const id = this.id; - const collapsed = !this.attributes.collapsed; - const { graph } = this.attributes.context!!; - if (collapsed) graph.collapseElement(id); - else graph.expandElement(id); - }); - } -} - -register(ExtensionCategory.COMBO, 'circle-combo-with-extra-button', RectComboWithExtraButton); - -register(ExtensionCategory.NODE, 'react', ReactNode); -export const LineageNew = memo((props: { data: LineageDetailInfo }) => { - const { data } = props; - const { theme } = useContext(DataStudioContext); - const graphRef = useRef(null); - const containerRef = useRef(null); - - useEffect(() => { - // 监控布局宽度高度变化,重新计算树的高度 - const element = containerRef.current!!; - const observer = new ResizeObserver((entries) => { - if ( - graphRef.current && - entries?.length === 1 && - entries[0].contentRect.width > 0 && - entries[0].contentRect.height > 0 - ) { - graphRef.current?.setSize(entries[0].contentRect.width, entries[0].contentRect.height); - } - }); - observer.observe(element); - return () => observer.unobserve(element); - }, []); - // 把data.tables 的id ,name转成map - const tablesMap = data.tables.reduce( - (acc, item) => { - acc[item.id] = item.name; - return acc; - }, - {} as Record - ); - return ( -
- - item.columns.map((column) => ({ - id: item.id + column.name, - combo: item.id, - data: { name: column.name } - })) - ), - edges: data.relations.map((item) => ({ - source: item.srcTableId + item.srcTableColName, - target: item.tgtTableId + item.tgtTableColName - })), - combos: data.tables.map((item) => ({ id: item.id })) - }, - combo: { - type: 'circle-combo-with-extra-button', - style: { - labelText: (d) => tablesMap[d.id] - } - }, - node: { - type: 'react', - style: { - size: [240, 20], - component: (data: { data: { name: string } }) => ( - - {data.data.name} - - ), - port: true, - ports: [{ placement: 'right' }, { placement: 'left' }] - } - }, - edge: { - type: 'cubic-horizontal', - style: { - endArrow: true, - endArrowType: 'vee' - } - }, - layout: { - type: 'combo-combined', - innerLayout: new GridLayout({ cols: 1, condense: true }), - outerLayout: new DagreLayout({ - rankdir: 'LR', - edgeLabelSpace: false, - nodesep: 5, - ranksep: 50 - }) - }, - behaviors: [ - 'focus-element', - 'drag-canvas', - 'zoom-canvas', - { - type: 'hover-activate', - enable: (event: any) => event.targetType === 'node', - degree: 1, // 👈🏻 Activate relations. - state: 'highlight', - inactiveState: 'dim', - onHover: (event: any) => { - event.view.setCursor('pointer'); - }, - onHoverEnd: (event: any) => { - event.view.setCursor('default'); - } - } - ], - plugins: [ - { - key: 'grid-line', - type: 'grid-line', - follow: false, - size: 40, - stroke: 'var(--border-color)', - borderStroke: 'var(--border-color)' - }, - { - type: 'toolbar', - position: 'right-top', - onClick: (item: string) => { - const graph = graphRef.current; - switch (item) { - // 放大 - case 'zoom-in': - graph?.zoomTo(graph?.getZoom() + 0.2); - break; - case 'zoom-out': - graph?.zoomBy(0.5); - break; - case 'auto-fit': - graph?.fitView(); - break; - } - }, - getItems: () => { - return [ - { id: 'zoom-in', value: 'zoom-in' }, - { id: 'zoom-out', value: 'zoom-out' }, - { id: 'auto-fit', value: 'auto-fit' } - ]; - }, - style: { - backgroundColor: 'var(--btn-background-color)' - } - }, - { key: 'background', type: 'background', background: 'var(--primary-color)' } - ], - transforms: ['process-parallel-edges'], - autoFit: 'view' - }} - /> -
- ); -}); diff --git a/dinky-web/src/pages/DataStudio/Toolbar/Service/index.tsx b/dinky-web/src/pages/DataStudio/Toolbar/Service/index.tsx index 95ea1af17e..def92262ec 100644 --- a/dinky-web/src/pages/DataStudio/Toolbar/Service/index.tsx +++ b/dinky-web/src/pages/DataStudio/Toolbar/Service/index.tsx @@ -17,7 +17,6 @@ * */ -import { connect } from '@@/exports'; import { CenterTab, DataStudioState } from '@/pages/DataStudio/model'; import { mapDispatchToProps } from '@/pages/DataStudio/DvaFunction'; import { Flex, Tabs, TabsProps, TreeDataNode } from 'antd'; @@ -49,11 +48,12 @@ import { getTabIcon } from '@/pages/DataStudio/function'; import { DIALECT } from '@/services/constants'; import { TableData } from '@/pages/DataStudio/Toolbar/Service/TableData'; import { isSql } from '@/pages/DataStudio/utils'; -import { LineageNew } from '@/pages/DataStudio/Toolbar/Service/LineageNew'; import { useAsyncEffect } from 'ahooks'; import { sleep } from '@antfu/utils'; import { l } from '@/utils/intl'; import { assert } from '@/pages/DataStudio/utils'; +import { connect } from '@umijs/max'; +import { Lineage } from '@/pages/DataStudio/Toolbar/Service/Lineage'; const Service = (props: { showDesc: boolean; tabs: CenterTab[]; action: any }) => { const { @@ -97,7 +97,7 @@ const Service = (props: { showDesc: boolean; tabs: CenterTab[]; action: any }) = key: actionType, label: l('menu.datastudio.lineage'), icon: , - children: + children: } }; }, [actionType, params?.data]); diff --git a/dinky-web/src/pages/DevOps/JobDetail/JobLineage/index.tsx b/dinky-web/src/pages/DevOps/JobDetail/JobLineage/index.tsx index 1f980a10aa..15eea331f6 100644 --- a/dinky-web/src/pages/DevOps/JobDetail/JobLineage/index.tsx +++ b/dinky-web/src/pages/DevOps/JobDetail/JobLineage/index.tsx @@ -17,17 +17,15 @@ * */ -import LineageGraph from '@/components/LineageGraph'; 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'; +import { Lineage } from '@/pages/DataStudio/Toolbar/Service/Lineage'; -const JobLineage: React.FC = (props) => { +const JobLineage = (props: { jobDetail: { id: number } }) => { const { jobDetail: { id: jobInstanceId } } = props; @@ -48,9 +46,13 @@ const JobLineage: React.FC = (props) => { return ( <> - + {lineageData && (lineageData.tables.length !== 0 || lineageData.relations.length !== 0) ? ( - + ) : ( )}