From 8c5ae7a7f58ae0da8a8f4fcb19ff467d17d9b3ae Mon Sep 17 00:00:00 2001 From: Jeff Phillips Date: Mon, 6 May 2024 15:04:03 -0400 Subject: [PATCH] fix(pipelines): Handle collapsed groups better (#186) --- packages/demo-app-ts/src/Demos.ts | 10 +- .../demos/pipelineGroupsDemo/DemoTaskEdge.tsx | 33 +- .../pipelineGroupsDemo/DemoTaskGroup.tsx | 6 +- .../demos/pipelineGroupsDemo/OptionsBar.tsx | 18 +- .../pipelineGroupsDemo/PipelineGroupsDemo.tsx | 30 +- .../PipelineGroupsDemoContext.tsx | 2 +- .../createDemoPipelineGroupsNodes.ts | 725 +++++++++++++++++- .../pipelineGroupsComponentFactory.tsx | 3 + .../useSourceStatusAnchor.tsx | 4 +- .../src/layouts/defaultLayoutFactory.ts | 4 +- packages/module/src/elements/BaseNode.ts | 2 +- .../anchors/TaskNodeSourceAnchor.ts | 6 +- .../anchors/TaskNodeSourceAnchorSave.ts | 22 - .../anchors/TaskNodeTargetAnchor.ts | 3 +- .../components/groups/DefaultTaskGroup.tsx | 1 - .../groups/DefaultTaskGroupExpanded.tsx | 312 ++++---- .../pipelines/components/nodes/TaskNode.tsx | 7 +- .../module/src/pipelines/decorators/index.ts | 2 +- .../module/src/pipelines/elements/index.ts | 4 +- packages/module/src/pipelines/utils/utils.ts | 27 +- packages/module/src/types.ts | 1 + 21 files changed, 983 insertions(+), 239 deletions(-) delete mode 100644 packages/module/src/pipelines/components/anchors/TaskNodeSourceAnchorSave.ts diff --git a/packages/demo-app-ts/src/Demos.ts b/packages/demo-app-ts/src/Demos.ts index 453ebced..ade9923b 100644 --- a/packages/demo-app-ts/src/Demos.ts +++ b/packages/demo-app-ts/src/Demos.ts @@ -1,6 +1,9 @@ import { PipelineTasksDemo } from './demos/pipelinesDemo/PipelineTasksDemo'; import { PipelineLayoutDemo } from './demos/pipelinesDemo/PipelineLayoutDemo'; -import { PipelineGroupsDemo } from './demos/pipelineGroupsDemo/PipelineGroupsDemo'; +import { + PipelineGroupsComplexDemo, + PipelineGroupsDemo +} from './demos/pipelineGroupsDemo/PipelineGroupsDemo'; import { Basics } from './demos/Basics'; import { StyleEdges, StyleGroups, StyleLabels, StyleNodes } from './demos/stylesDemo/Styles'; import { Selection } from './demos/Selection'; @@ -56,6 +59,11 @@ export const Demos: DemoInterface[] = [ name: 'Pipeline Groups Layout', componentType: PipelineGroupsDemo, }, + { + id: 'pipelines-groups-complex-layout-demo', + name: 'Pipeline Groups Complex Layout', + componentType: PipelineGroupsComplexDemo, + }, ] }, { diff --git a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskEdge.tsx b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskEdge.tsx index 095cf4a1..7a6928c8 100644 --- a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskEdge.tsx +++ b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskEdge.tsx @@ -1,24 +1,27 @@ import * as React from 'react'; import { observer } from 'mobx-react'; -import { Edge, EdgeTerminalType, GraphElement, TaskEdge } from '@patternfly/react-topology'; +import { + DEFAULT_SPACER_NODE_TYPE, + Edge, + EdgeTerminalType, + GraphElement, + TaskEdge +} from '@patternfly/react-topology'; interface DemoTaskEdgeProps { element: GraphElement; } -const DemoTaskEdge: React.FunctionComponent = ({ element, ...props }) => { - - const isDependency = (element as Edge).getTarget().getData()?.isDependency; - - return ( - - ); -}; +const DemoTaskEdge: React.FunctionComponent = ({ element, ...props }) => ( + +); export default observer(DemoTaskEdge); diff --git a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskGroup.tsx b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskGroup.tsx index ddd70b02..348f1827 100644 --- a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskGroup.tsx +++ b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskGroup.tsx @@ -18,14 +18,12 @@ import { GROUPS_LAYER, RunStatus } from '@patternfly/react-topology'; +import { DEFAULT_TASK_HEIGHT, GROUP_TASK_WIDTH } from './createDemoPipelineGroupsNodes'; type DemoTaskGroupProps = { element: GraphElement; } & WithSelectionProps; -export const DEFAULT_TASK_WIDTH = 180; -export const DEFAULT_TASK_HEIGHT = 32; - const getEdgeCreationTypes = (): EdgeCreationTypes => ({ edgeType: 'edge', spacerEdgeType: 'edge' @@ -48,7 +46,7 @@ const DemoTaskGroup: React.FunctionComponent = ({ element, . { Layout: - - pipelineOptions.setVerticalLayout(false)} - label="Horizontal" - /> - { label="Vertical" /> + + pipelineOptions.setVerticalLayout(false)} + label="Horizontal" + /> + ); }); diff --git a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/PipelineGroupsDemo.tsx b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/PipelineGroupsDemo.tsx index 43871040..4b6e392f 100644 --- a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/PipelineGroupsDemo.tsx +++ b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/PipelineGroupsDemo.tsx @@ -19,14 +19,17 @@ import { TOP_TO_BOTTOM, PipelineNodeModel, useVisualizationController, + addSpacerNodes, + pipelineElementFactory, } from '@patternfly/react-topology'; import pipelineGroupsComponentFactory from './pipelineGroupsComponentFactory'; -import { createDemoPipelineGroupsNodes } from './createDemoPipelineGroupsNodes'; +import { + createComplexDemoPipelineGroupsNodes, + createDemoPipelineGroupsNodes +} from './createDemoPipelineGroupsNodes'; import { PipelineGroupsDemoContext, PipelineGroupsDemoModel } from './PipelineGroupsDemoContext'; import OptionsBar from './OptionsBar'; import DemoControlBar from '../DemoControlBar'; -import pipelineElementFactory - from '@patternfly/react-topology/dist/esm/pipelines/elements/pipelineElementFactory'; const TopologyPipelineGroups: React.FC<{ nodes: PipelineNodeModel[] }> = observer(({ nodes }) => { const controller = useVisualizationController(); @@ -38,7 +41,9 @@ const TopologyPipelineGroups: React.FC<{ nodes: PipelineNodeModel[] }> = observe }); React.useEffect(() => { - const edges = getEdgesFromNodes(nodes, DEFAULT_SPACER_NODE_TYPE, 'edge', 'edge'); + const pipelineNodes = addSpacerNodes(nodes); + const edges = getEdgesFromNodes(pipelineNodes, DEFAULT_SPACER_NODE_TYPE, 'edge', 'edge'); + controller.fromModel( { graph: { @@ -48,7 +53,7 @@ const TopologyPipelineGroups: React.FC<{ nodes: PipelineNodeModel[] }> = observe y: 25, layout: options.verticalLayout ? TOP_TO_BOTTOM : LEFT_TO_RIGHT }, - nodes, + nodes: pipelineNodes, edges, }, false @@ -64,7 +69,7 @@ const TopologyPipelineGroups: React.FC<{ nodes: PipelineNodeModel[] }> = observe TopologyPipelineGroups.displayName = 'TopologyPipelineLayout'; -export const PipelineGroupsDemo = observer(() => { +export const PipelineGroupsDemoComponent: React.FC<{ complex?: boolean }> = ({ complex }) => { const controller = new Visualization(); controller.registerElementFactory(pipelineElementFactory); controller.registerComponentFactory(pipelineGroupsComponentFactory); @@ -77,7 +82,7 @@ export const PipelineGroupsDemo = observer(() => { ignoreGroups: true, }) ); - const nodes = createDemoPipelineGroupsNodes(); + const nodes = complex ? createComplexDemoPipelineGroupsNodes() : createDemoPipelineGroupsNodes(); return (
@@ -87,4 +92,13 @@ export const PipelineGroupsDemo = observer(() => {
); -}); +}; + +export const PipelineGroupsDemo = () => { + return +}; + +export const PipelineGroupsComplexDemo = () => { + return +}; + diff --git a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/PipelineGroupsDemoContext.tsx b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/PipelineGroupsDemoContext.tsx index 22a82cc7..0e47e097 100644 --- a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/PipelineGroupsDemoContext.tsx +++ b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/PipelineGroupsDemoContext.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { makeObservable, observable, action } from 'mobx'; export class PipelineGroupsDemoModel { - protected verticalLayoutP: boolean = false; + protected verticalLayoutP: boolean = true; constructor() { makeObservable< diff --git a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/createDemoPipelineGroupsNodes.ts b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/createDemoPipelineGroupsNodes.ts index db178242..49ba6e5c 100644 --- a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/createDemoPipelineGroupsNodes.ts +++ b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/createDemoPipelineGroupsNodes.ts @@ -7,8 +7,9 @@ export const NODE_PADDING_HORIZONTAL = 15; export const GROUP_PADDING_VERTICAL = 40; export const GROUP_PADDING_HORIZONTAL = 25; -export const DEFAULT_TASK_WIDTH = 180; +export const DEFAULT_TASK_WIDTH = 210; export const DEFAULT_TASK_HEIGHT = 32; +export const GROUP_TASK_WIDTH = 210; export const createExecution2 = (): [string, PipelineNodeModel[]] => { const execution2: PipelineNodeModel = { @@ -329,6 +330,728 @@ export const createExecution3 = (runAfter?: string): [string, PipelineNodeModel[ return ['execution-3', nodes]; }; +export const createComplexDemoPipelineGroupsNodes = (): PipelineNodeModel[] => ( + [ + { + id: 'automl-tabular-finalizer', + label: 'automl-tabular-finalizer', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['exit-handler-1'], + data: {}, + }, + { + id: 'feature-attribution-2-feature_attributions', + label: 'feature-attribution-2-feature_attributions (Type: Metrics)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['exit-handler-1'], + data: {}, + }, + { + id: 'feature-attribution-feature_attributions', + label: 'feature-attribution-feature_attributions (Type: Metrics)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['exit-handler-1'], + data: {}, + }, + { + id: 'exit-handler-1', + label: 'exit-handler-1', + type: 'EXECUTION_TASK_NODE', + group: true, + collapsed: true, + width: GROUP_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['set-optional-inputs'], + children: [ + 'condition-2', + 'condition-4', + 'dataset_stats', + 'feature_ranking', + 'instance_schema', + 'materialized_data', + 'training_schema', + 'transform_output', + 'feature-transform-engine', + 'materialized_eval_split', + 'materialized_test_split', + 'materialized_train_split', + 'split-materialized-data', + 'string-not-empty', + 'instance_baseline', + 'metadata', + 'training-configurator-and-validator', + ], + style: { padding: [15, 15] }, + data: {}, + }, + { + id: 'condition-2', + label: 'stage_1_tuning_result_artifact_uri_not_empty', + type: 'EXECUTION_TASK_NODE', + group: true, + collapsed: true, + width: GROUP_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [ + 'feature-transform-engine', + 'split-materialized-data', + 'string-not-empty', + 'training-configurator-and-validator', + ], + children: [ + 'example_instance', + 'explanation_metadata_artifact', + 'model_architecture', + 'unmanaged_container_model', + 'automl-forecasting-ensemble', + 'tuning_result_output', + 'automl-forecasting-stage-2-tuner', + 'calculate-training-parameters', + 'condition-3', + 'get-or-create-model-description', + 'get-prediction-image-uri', + 'artifact', + 'importer', + 'model', + 'model-upload', + ], + style: { padding: [15, 15] }, + data: {}, + }, + { + id: 'example_instance', + label: 'example_instance (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['automl-forecasting-ensemble'], + data: {}, + }, + { + id: 'explanation_metadata_artifact', + label: 'explanation_metadata_artifact (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['automl-forecasting-ensemble'], + data: {}, + }, + { + id: 'model_architecture', + label: 'model_architecture (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['automl-forecasting-ensemble'], + data: {}, + }, + { + id: 'unmanaged_container_model', + label: 'unmanaged_container_model (Type: UnmanagedContainerModel)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['automl-forecasting-ensemble'], + data: {}, + }, + { + id: 'automl-forecasting-ensemble', + label: 'automl-forecasting-ensemble', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['automl-forecasting-stage-2-tuner', 'get-prediction-image-uri'], + data: {}, + }, + { + id: 'tuning_result_output', + label: 'tuning_result_output (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['automl-forecasting-stage-2-tuner'], + data: {}, + }, + { + id: 'automl-forecasting-stage-2-tuner', + label: 'automl-forecasting-stage-2-tuner', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['calculate-training-parameters', 'importer'], + data: {}, + }, + { + id: 'calculate-training-parameters', + label: 'calculate-training-parameters', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + { + id: 'condition-3', + label: 'should_run_model_evaluation', + type: 'EXECUTION_TASK_NODE', + group: true, + collapsed: true, + width: GROUP_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['automl-forecasting-ensemble', 'model-upload'], + children: [ + 'feature_attributions', + 'feature-attribution', + 'finalize-eval-quantile-parameters', + 'get-predictions-column', + 'batchpredictionjob', + 'bigquery_output_table', + 'gcs_output_directory', + 'model-batch-explanation', + 'model-batch-predict', + 'evaluation_metrics', + 'model-evaluation-forecasting', + 'model-evaluation-import', + 'table-to-uri', + ], + style: { padding: [15, 15] }, + data: {}, + }, + { + id: 'feature_attributions', + label: 'feature_attributions (Type: Metrics)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['feature-attribution'], + data: {}, + }, + { + id: 'feature-attribution', + label: 'feature-attribution', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['model-batch-explanation'], + data: {}, + }, + { + id: 'finalize-eval-quantile-parameters', + label: 'finalize-eval-quantile-parameters', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + { + id: 'get-predictions-column', + label: 'get-predictions-column', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['finalize-eval-quantile-parameters'], + data: {}, + }, + { + id: 'batchpredictionjob', + label: 'batchpredictionjob (Type: VertexBatchPredictionJob)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['model-batch-explanation'], + data: {}, + }, + { + id: 'bigquery_output_table', + label: 'bigquery_output_table (Type: BQTable)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['model-batch-explanation'], + data: {}, + }, + { + id: 'gcs_output_directory', + label: 'gcs_output_directory (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['model-batch-explanation'], + data: {}, + }, + { + id: 'model-batch-explanation', + label: 'model-batch-explanation', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + { + id: 'batchpredictionjob', + label: 'batchpredictionjob (Type: VertexBatchPredictionJob)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['model-batch-predict'], + data: {}, + }, + { + id: 'bigquery_output_table', + label: 'bigquery_output_table (Type: BQTable)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['model-batch-predict'], + data: {}, + }, + { + id: 'gcs_output_directory', + label: 'gcs_output_directory (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['model-batch-predict'], + data: {}, + }, + { + id: 'model-batch-predict', + label: 'model-batch-predict', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + { + id: 'evaluation_metrics', + label: 'evaluation_metrics (Type: ForecastingMetrics)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['model-evaluation-forecasting'], + data: {}, + }, + { + id: 'model-evaluation-forecasting', + label: 'model-evaluation-forecasting', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [ + 'finalize-eval-quantile-parameters', + 'get-predictions-column', + 'model-batch-predict', + 'table-to-uri', + ], + data: {}, + }, + { + id: 'model-evaluation-import', + label: 'model-evaluation-import', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['feature-attribution', 'model-evaluation-forecasting'], + data: {}, + }, + { + id: 'table-to-uri', + label: 'table-to-uri', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['model-batch-predict'], + data: {}, + }, + { + id: 'get-or-create-model-description', + label: 'get-or-create-model-description', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + { + id: 'get-prediction-image-uri', + label: 'get-prediction-image-uri', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + { + id: 'artifact', + label: 'artifact (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['importer'], + data: {}, + }, + { + id: 'importer', + label: 'get-hyperparameter-tuning-results', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + { + id: 'model', + label: 'model (Type: VertexModel)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['model-upload'], + data: {}, + }, + { + id: 'model-upload', + label: 'model-upload', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['automl-forecasting-ensemble', 'get-or-create-model-description'], + data: {}, + }, + { + id: 'condition-4', + label: 'stage_1_tuning_result_artifact_uri_empty', + type: 'EXECUTION_TASK_NODE', + group: true, + collapsed: true, + width: GROUP_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [ + 'feature-transform-engine', + 'split-materialized-data', + 'string-not-empty', + 'training-configurator-and-validator', + ], + children: [ + 'automl-forecasting-ensemble-2', + 'automl-forecasting-stage-1-tuner', + 'calculate-training-parameters-2', + 'condition-5', + 'get-or-create-model-description-2', + 'get-prediction-image-uri-2', + 'model-upload-2', + ], + style: { padding: [15, 15] }, + data: {}, + }, + { + id: 'automl-forecasting-ensemble-2', + label: 'automl-forecasting-ensemble-2', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['automl-forecasting-stage-1-tuner', 'get-prediction-image-uri-2'], + data: {}, + }, + { + id: 'automl-forecasting-stage-1-tuner', + label: 'automl-forecasting-stage-1-tuner', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['calculate-training-parameters-2'], + data: {}, + }, + { + id: 'calculate-training-parameters-2', + label: 'calculate-training-parameters-2', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + { + id: 'condition-5', + label: 'should_run_model_evaluation', + type: 'EXECUTION_TASK_NODE', + group: true, + collapsed: true, + width: GROUP_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['automl-forecasting-ensemble-2', 'model-upload-2'], + children: [ + 'feature-attribution-2', + 'finalize-eval-quantile-parameters-2', + 'get-predictions-column-2', + 'model-batch-explanation-2', + 'model-batch-predict-2', + 'model-evaluation-forecasting-2', + 'model-evaluation-import-2', + 'table-to-uri-2', + ], + style: { padding: [15, 15] }, + data: {}, + }, + { + id: 'feature-attribution-2', + label: 'feature-attribution-2', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['model-batch-explanation-2'], + data: {}, + }, + { + id: 'finalize-eval-quantile-parameters-2', + label: 'finalize-eval-quantile-parameters-2', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + { + id: 'get-predictions-column-2', + label: 'get-predictions-column-2', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['finalize-eval-quantile-parameters-2'], + data: {}, + }, + { + id: 'model-batch-explanation-2', + label: 'model-batch-explanation-2', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + { + id: 'model-batch-predict-2', + label: 'model-batch-predict-2', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + { + id: 'model-evaluation-forecasting-2', + label: 'model-evaluation-forecasting-2', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [ + 'finalize-eval-quantile-parameters-2', + 'get-predictions-column-2', + 'model-batch-predict-2', + 'table-to-uri-2', + ], + data: {}, + }, + { + id: 'model-evaluation-import-2', + label: 'model-evaluation-import-2', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['feature-attribution-2', 'model-evaluation-forecasting-2'], + data: {}, + }, + { + id: 'table-to-uri-2', + label: 'table-to-uri-2', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['model-batch-predict-2'], + data: {}, + }, + { + id: 'get-or-create-model-description-2', + label: 'get-or-create-model-description-2', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + { + id: 'get-prediction-image-uri-2', + label: 'get-prediction-image-uri-2', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + { + id: 'model-upload-2', + label: 'model-upload-2', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['automl-forecasting-ensemble-2', 'get-or-create-model-description-2'], + data: {}, + }, + { + id: 'dataset_stats', + label: 'dataset_stats (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['feature-transform-engine'], + data: {}, + }, + { + id: 'feature_ranking', + label: 'feature_ranking (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['feature-transform-engine'], + data: {}, + }, + { + id: 'instance_schema', + label: 'instance_schema (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['feature-transform-engine'], + data: {}, + }, + { + id: 'materialized_data', + label: 'materialized_data (Type: Dataset)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['feature-transform-engine'], + data: {}, + }, + { + id: 'training_schema', + label: 'training_schema (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['feature-transform-engine'], + data: {}, + }, + { + id: 'transform_output', + label: 'transform_output (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['feature-transform-engine'], + data: {}, + }, + { + id: 'feature-transform-engine', + label: 'feature-transform-engine', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + { + id: 'materialized_eval_split', + label: 'materialized_eval_split (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['split-materialized-data'], + data: {}, + }, + { + id: 'materialized_test_split', + label: 'materialized_test_split (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['split-materialized-data'], + data: {}, + }, + { + id: 'materialized_train_split', + label: 'materialized_train_split (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['split-materialized-data'], + data: {}, + }, + { + id: 'split-materialized-data', + label: 'split-materialized-data', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['feature-transform-engine'], + data: {}, + }, + { + id: 'string-not-empty', + label: 'check-if-hyperparameter-tuning-results-are-supplied-by-user', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + { + id: 'instance_baseline', + label: 'instance_baseline (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['training-configurator-and-validator'], + data: {}, + }, + { + id: 'metadata', + label: 'metadata (Type: Artifact)', + type: 'ICON_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['training-configurator-and-validator'], + data: {}, + }, + { + id: 'training-configurator-and-validator', + label: 'training-configurator-and-validator', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: ['feature-transform-engine'], + data: {}, + }, + { + id: 'set-optional-inputs', + label: 'set-optional-inputs', + type: 'DEFAULT_TASK_NODE', + width: DEFAULT_TASK_WIDTH, + height: DEFAULT_TASK_HEIGHT, + runAfterTasks: [], + data: {}, + }, + ] +); + export const createDemoPipelineGroupsNodes = (): PipelineNodeModel[] => { const nodes: PipelineNodeModel[] = []; diff --git a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/pipelineGroupsComponentFactory.tsx b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/pipelineGroupsComponentFactory.tsx index 0832175e..c368731d 100644 --- a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/pipelineGroupsComponentFactory.tsx +++ b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/pipelineGroupsComponentFactory.tsx @@ -22,8 +22,11 @@ const pipelineGroupsComponentFactory: ComponentFactory = ( } switch (type) { case 'Execution': + case 'EXECUTION_TASK_NODE': return withSelection()(DemoTaskGroup); case 'Task': + case 'DEFAULT_TASK_NODE': + case 'ICON_TASK_NODE': return withSelection()(DemoTaskNode); case DEFAULT_SPACER_NODE_TYPE: return SpacerNode; diff --git a/packages/demo-app-ts/src/demos/statusConnectorsDemo/useSourceStatusAnchor.tsx b/packages/demo-app-ts/src/demos/statusConnectorsDemo/useSourceStatusAnchor.tsx index 8b1fd280..198ecda7 100644 --- a/packages/demo-app-ts/src/demos/statusConnectorsDemo/useSourceStatusAnchor.tsx +++ b/packages/demo-app-ts/src/demos/statusConnectorsDemo/useSourceStatusAnchor.tsx @@ -4,9 +4,9 @@ import { isNode, AnchorEnd, ElementContext, + Point, + AbstractAnchor, } from '@patternfly/react-topology'; -import AbstractAnchor from '@patternfly/react-topology/dist/esm/anchors/AbstractAnchor'; -import Point from '@patternfly/react-topology/dist/esm/geom/Point'; export type SvgAnchorRef = (node: SVGElement | null) => void; diff --git a/packages/demo-app-ts/src/layouts/defaultLayoutFactory.ts b/packages/demo-app-ts/src/layouts/defaultLayoutFactory.ts index 9e785417..56fc671b 100644 --- a/packages/demo-app-ts/src/layouts/defaultLayoutFactory.ts +++ b/packages/demo-app-ts/src/layouts/defaultLayoutFactory.ts @@ -7,9 +7,9 @@ import { ConcentricLayout, DagreLayout, GridLayout, - BreadthFirstLayout + BreadthFirstLayout, + ColaGroupsLayout } from '@patternfly/react-topology'; -import { ColaGroupsLayout } from '@patternfly/react-topology/dist/esm/layouts/ColaGroupsLayout'; const defaultLayoutFactory: LayoutFactory = (type: string, graph: Graph): Layout | undefined => { switch (type) { diff --git a/packages/module/src/elements/BaseNode.ts b/packages/module/src/elements/BaseNode.ts index 035a0322..11632a28 100644 --- a/packages/module/src/elements/BaseNode.ts +++ b/packages/module/src/elements/BaseNode.ts @@ -172,7 +172,7 @@ export default class BaseNode extends return super.getChildren().reduce((total, nexChild) => { if (isNode(nexChild)) { if (nexChild.isGroup() && !nexChild.isCollapsed()) { - return total.concat(nexChild.getAllNodeChildren()); + return total.concat(nexChild.getPositionableChildren()); } total.push(nexChild); } diff --git a/packages/module/src/pipelines/components/anchors/TaskNodeSourceAnchor.ts b/packages/module/src/pipelines/components/anchors/TaskNodeSourceAnchor.ts index 4083ae79..0b587eb8 100644 --- a/packages/module/src/pipelines/components/anchors/TaskNodeSourceAnchor.ts +++ b/packages/module/src/pipelines/components/anchors/TaskNodeSourceAnchor.ts @@ -21,11 +21,11 @@ export default class TaskNodeSourceAnchor extends Abstrac getReferencePoint(): Point { const bounds = this.owner.getBounds(); if (this.detailsLevel !== ScaleDetailsLevel.high) { - const scale = this.owner.getGraph().getScale(); + const nodeSize = this.lowDetailsStatusIconSize / this.owner.getGraph().getScale(); if (this.vertical) { - return new Point(bounds.x + (this.lowDetailsStatusIconSize / 2 + 2) * (1 / scale), bounds.bottom()); + return new Point(bounds.x + bounds.width / 2, bounds.y + nodeSize); } - return new Point(bounds.x + this.lowDetailsStatusIconSize * (1 / scale), bounds.y + bounds.height / 2); + return new Point(bounds.x + nodeSize, bounds.y + bounds.height / 2 ); } if (this.vertical) { return new Point(bounds.x + bounds.width / 2, bounds.bottom()); diff --git a/packages/module/src/pipelines/components/anchors/TaskNodeSourceAnchorSave.ts b/packages/module/src/pipelines/components/anchors/TaskNodeSourceAnchorSave.ts deleted file mode 100644 index e3b0db29..00000000 --- a/packages/module/src/pipelines/components/anchors/TaskNodeSourceAnchorSave.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Point } from '../../../geom'; -import { AbstractAnchor } from '../../../anchors'; -import { Node } from '../../../types'; - -export default class TaskNodeSourceAnchor extends AbstractAnchor { - private pillWidth = 0; - private whenOffset = 0; - - constructor(owner: E, whenOffset: number, pillWidth: number) { - super(owner); - this.whenOffset = whenOffset; - this.pillWidth = pillWidth; - } - getLocation(): Point { - return this.getReferencePoint(); - } - - getReferencePoint(): Point { - const bounds = this.owner.getBounds(); - return new Point(bounds.x + this.pillWidth + this.whenOffset, bounds.y + bounds.height / 2); - } -} diff --git a/packages/module/src/pipelines/components/anchors/TaskNodeTargetAnchor.ts b/packages/module/src/pipelines/components/anchors/TaskNodeTargetAnchor.ts index 3a585a84..6a60f24f 100644 --- a/packages/module/src/pipelines/components/anchors/TaskNodeTargetAnchor.ts +++ b/packages/module/src/pipelines/components/anchors/TaskNodeTargetAnchor.ts @@ -25,8 +25,7 @@ export default class TaskNodeTargetAnchor extends Abstrac if (this.vertical) { if (this.detailsLevel !== ScaleDetailsLevel.high) { - const scale = this.owner.getGraph().getScale(); - return new Point(bounds.x + (this.lowDetailsStatusIconSize / 2 + 2) * (1 / scale), bounds.y); + return new Point(bounds.x + (bounds.width / 2), bounds.y); } return new Point(bounds.x + bounds.width / 2, bounds.y - this.whenOffset); } diff --git a/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx b/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx index 948fd552..9affad7a 100644 --- a/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx +++ b/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx @@ -188,7 +188,6 @@ const DefaultTaskGroupInner: React.FunctionComponent & { element: Node }> = observer( - ({ - className, - element, - collapsible, - selected, - onSelect, - hover, - label, - secondaryLabel, - showLabel = true, - truncateLength, - canDrop, - dropTarget, - onContextMenu, - contextMenuOpen, - dragging, - dragNodeRef, - badge, - badgeColor, - badgeTextColor, - badgeBorderColor, - badgeClassName, - badgeLocation, - labelOffset = 17, - labelIconClass, - labelIcon, - labelIconPadding, - onCollapseChange, - }) => { - const [hovered, hoverRef] = useHover(); - const [labelHover, labelHoverRef] = useHover(); - const dragLabelRef = useDragNode()[1]; - const refs = useCombineRefs(hoverRef, dragNodeRef); - const isHover = hover !== undefined ? hover : hovered; - const labelPosition = element.getLabelPosition(); - const verticalLayout = (element.getGraph().getLayoutOptions?.() as DagreLayoutOptions)?.rankdir === TOP_TO_BOTTOM; +const DefaultTaskGroupExpanded: React.FunctionComponent & { element: Node }> = + observer( + ({ + className, + element, + collapsible, + selected, + onSelect, + hover, + label, + secondaryLabel, + showLabel = true, + truncateLength, + canDrop, + dropTarget, + onContextMenu, + contextMenuOpen, + dragging, + dragNodeRef, + badge, + badgeColor, + badgeTextColor, + badgeBorderColor, + badgeClassName, + badgeLocation, + labelOffset = 17, + labelIconClass, + labelIcon, + labelIconPadding, + onCollapseChange, + labelPosition, + }) => { + const [hovered, hoverRef] = useHover(); + const [labelHover, labelHoverRef] = useHover(); + const dragLabelRef = useDragNode()[1]; + const refs = useCombineRefs(hoverRef, dragNodeRef); + const isHover = hover !== undefined ? hover : hovered; + const verticalLayout = (element.getGraph().getLayoutOptions?.() as DagreLayoutOptions)?.rankdir === TOP_TO_BOTTOM; + const groupLabelPosition = labelPosition ?? element.getLabelPosition() ?? LabelPosition.bottom; + let parent = element.getParent(); + let altGroup = false; + while (!isGraph(parent)) { + altGroup = !altGroup; + parent = parent.getParent(); + } - let parent = element.getParent(); - let altGroup = false; - while (!isGraph(parent)) { - altGroup = !altGroup; - parent = parent.getParent(); - } + useAnchor( + React.useCallback((node: Node) => new TaskGroupSourceAnchor(node, verticalLayout), [verticalLayout]), + AnchorEnd.source + ); + useAnchor( + React.useCallback((node: Node) => new TaskGroupTargetAnchor(node, verticalLayout), [verticalLayout]), + AnchorEnd.target + ); - useAnchor( - React.useCallback((node: Node) => new TaskGroupSourceAnchor(node, verticalLayout), [verticalLayout]), - AnchorEnd.source - ); - useAnchor( - React.useCallback((node: Node) => new TaskGroupTargetAnchor(node, verticalLayout), [verticalLayout]), - AnchorEnd.target - ); + const children = element.getNodes().filter((c) => c.isVisible()); - const children = element.getNodes().filter((c) => c.isVisible()); + // cast to number and coerce + const padding = maxPadding(element.getStyle().padding ?? 17); - // cast to number and coerce - const padding = maxPadding(element.getStyle().padding ?? 17); + const { minX, minY, maxX, maxY } = children.reduce( + (acc, child) => { + const bounds = child.getBounds(); + return { + minX: Math.min(acc.minX, bounds.x - padding), + minY: Math.min(acc.minY, bounds.y - padding), + maxX: Math.max(acc.maxX, bounds.x + bounds.width + padding), + maxY: Math.max(acc.maxY, bounds.y + bounds.height + padding) + }; + }, + { minX: Infinity, minY: Infinity, maxX: 0, maxY: 0 } + ); - const { minX, minY, maxX, maxY } = children.reduce( - (acc, child) => { - const bounds = child.getBounds(); - return { - minX: Math.min(acc.minX, bounds.x - padding), - minY: Math.min(acc.minY, bounds.y - padding), - maxX: Math.max(acc.maxX, bounds.x + bounds.width + padding), - maxY: Math.max(acc.maxY, bounds.y + bounds.height + padding) - }; - }, - { minX: Infinity, minY: Infinity, maxX: 0, maxY: 0 } - ); + const [labelX, labelY] = React.useMemo(() => { + if (!showLabel || !(label || element.getLabel())) { + return [0, 0]; + } + switch (groupLabelPosition) { + case LabelPosition.top: + return [minX + (maxX - minX) / 2, -minY + labelOffset]; + case LabelPosition.right: + return [maxX + labelOffset, minY + (maxY - minY) / 2]; + case LabelPosition.bottom: + default: + return [minX + (maxX - minX) / 2, maxY + labelOffset]; + } + }, [element, label, labelOffset, groupLabelPosition, maxX, maxY, minX, minY, showLabel]); - const [labelX, labelY] = React.useMemo(() => { - if (!showLabel || !(label || element.getLabel())) { - return [0, 0]; + if (children.length === 0) { + return null; } - switch (labelPosition) { - case LabelPosition.top: - return [minX + (maxX - minX) / 2, -minY + labelOffset]; - case LabelPosition.right: - return [maxX + labelOffset, minY + (maxY - minY) / 2]; - case LabelPosition.bottom: - default: - return [minX + (maxX - minX) / 2, maxY + labelOffset]; - } - }, [element, label, labelOffset, labelPosition, maxX, maxY, minX, minY, showLabel]); - if (children.length === 0) { - return null; - } + const groupClassName = css( + styles.topologyGroup, + className, + altGroup && 'pf-m-alt-group', + canDrop && 'pf-m-highlight', + dragging && 'pf-m-dragging', + selected && 'pf-m-selected' + ); + const innerGroupClassName = css( + styles.topologyGroup, + className, + altGroup && 'pf-m-alt-group', + canDrop && 'pf-m-highlight', + dragging && 'pf-m-dragging', + selected && 'pf-m-selected', + (isHover || labelHover) && 'pf-m-hover', + canDrop && dropTarget && 'pf-m-drop-target' + ); - const groupClassName = css( - styles.topologyGroup, - className, - altGroup && 'pf-m-alt-group', - canDrop && 'pf-m-highlight', - dragging && 'pf-m-dragging', - selected && 'pf-m-selected' - ); - const innerGroupClassName = css( - styles.topologyGroup, - className, - altGroup && 'pf-m-alt-group', - canDrop && 'pf-m-highlight', - dragging && 'pf-m-dragging', - selected && 'pf-m-selected', - (isHover || labelHover) && 'pf-m-hover', - canDrop && dropTarget && 'pf-m-drop-target' - ); - - return ( - - - - - - - {showLabel && (label || element.getLabel()) && ( - - : undefined} - onActionIconClick={() => onCollapseChange(element, true)} - > - {label || element.getLabel()} - + return ( + + + + + - )} - - ); - } -); + {showLabel && (label || element.getLabel()) && ( + + : undefined} + onActionIconClick={() => onCollapseChange(element, true)} + > + {label || element.getLabel()} + + + )} + + ); + } + ); export default DefaultTaskGroupExpanded; \ No newline at end of file diff --git a/packages/module/src/pipelines/components/nodes/TaskNode.tsx b/packages/module/src/pipelines/components/nodes/TaskNode.tsx index 6c1aa404..1aa216fe 100644 --- a/packages/module/src/pipelines/components/nodes/TaskNode.tsx +++ b/packages/module/src/pipelines/components/nodes/TaskNode.tsx @@ -165,7 +165,7 @@ const TaskNodeInner: React.FC = observer(({ const taskRef = React.useRef(); const taskIconComponentRef = React.useRef(); const isHover = hover !== undefined ? hover : hovered; - const { width } = element.getBounds(); + const { width, height: boundsHeight } = element.getBounds(); const label = truncateMiddle(element.getLabel(), { length: truncateLength, omission: '...' }); const [textSize, textRef] = useSize([label, className]); const nameLabelTriggerRef = React.useRef(); @@ -400,11 +400,12 @@ const TaskNodeInner: React.FC = observer(({ const renderTask = () => { if (showStatusState && !scaleNode && hideDetailsAtMedium && detailsLevel !== ScaleDetailsLevel.high) { const statusBackgroundRadius = statusIconSize / 2 + 4; - const height = element.getBounds().height; const upScale = 1 / scale; + const translateX = verticalLayout ? width / 2 - statusBackgroundRadius * upScale: 0; + const translateY = verticalLayout ? 0 : (boundsHeight - statusBackgroundRadius * 2 * upScale) / 2; return ( - + { + const spacerNodes = getSpacerNodes(nodes, spacerNodeType, finallyNodeTypes); + const newNodes = [ ...nodes, ...spacerNodes]; + + if (addSpacersToGroups) { + // find the parent of each spacer node + spacerNodes.forEach((spacerNode) => { + const nodeIds = spacerNode.id.split('|'); + if (nodeIds[0]) { + const parent = newNodes.find((n) => n.children?.includes(nodeIds[0])); + if (parent) { + parent.children?.push(spacerNode.id); + } + } + }); + } + + return newNodes; +}; + export const getEdgesFromNodes = ( nodes: PipelineNodeModel[], spacerNodeType = DEFAULT_SPACER_NODE_TYPE, diff --git a/packages/module/src/types.ts b/packages/module/src/types.ts index f138c2ac..79c7b85e 100644 --- a/packages/module/src/types.ts +++ b/packages/module/src/types.ts @@ -233,6 +233,7 @@ export interface Node extends GraphEle getSourceEdges(): Edge[]; getTargetEdges(): Edge[]; getAllNodeChildren(): Node[]; // Return all children regardless of collapse status or child groups' collapsed status + getPositionableChildren(): Node[]; // Return all children that can be positioned (collapsed groups are positionable) isDimensionsInitialized(): boolean; isPositioned(): boolean; }