diff --git a/src/app/demo_page/demo_page.ng.html b/src/app/demo_page/demo_page.ng.html
index 626727d..38b5d29 100644
--- a/src/app/demo_page/demo_page.ng.html
+++ b/src/app/demo_page/demo_page.ng.html
@@ -556,6 +556,30 @@
+
+
+
+
+ Node/Group
+
+
+ {{selectable.label}}
+
+
+
+
diff --git a/src/app/demo_page/demo_page.scss b/src/app/demo_page/demo_page.scss
index a1535d8..116e12a 100644
--- a/src/app/demo_page/demo_page.scss
+++ b/src/app/demo_page/demo_page.scss
@@ -71,6 +71,9 @@ h2 {
&.demo-select {
width: 40%;
}
+ &.demo-select-full-width {
+ width: 80%;
+ }
}
.demo-control-group {
diff --git a/src/app/demo_page/demo_page.spec.ts b/src/app/demo_page/demo_page.spec.ts
index 7ec7144..3196ea4 100644
--- a/src/app/demo_page/demo_page.spec.ts
+++ b/src/app/demo_page/demo_page.spec.ts
@@ -113,6 +113,21 @@ describe('Demo Page', () => {
await screenShot.expectMatch('dark-mode');
});
});
+
+ describe('Node selection', () => {
+ it('Selects correct nested iteration loop node (screenshot)', async () => {
+ await harness.getNodeSelectInput().then(
+ select => select.clickOptions(
+ {text: 'Trainer (TensorFlow Training, it-1)'}));
+ await screenShot.expectMatch('select-nested-iteration-loop');
+ });
+
+ it('Selects correct nested group node (screenshot)', async () => {
+ await harness.getNodeSelectInput().then(
+ select => select.clickOptions({text: 'Fake Exec 1 (sub1)'}));
+ await screenShot.expectMatch('select-nested-group');
+ })
+ })
});
diff --git a/src/app/demo_page/demo_page.ts b/src/app/demo_page/demo_page.ts
index e2da9ae..3f0458e 100644
--- a/src/app/demo_page/demo_page.ts
+++ b/src/app/demo_page/demo_page.ts
@@ -20,6 +20,7 @@ import {HttpClientModule} from '@angular/common/http';
import {Component, ElementRef, NgModule, TemplateRef, ViewChild, ViewEncapsulation} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
+import {MatOptionModule} from '@angular/material/core';
import {MatDialog, MatDialogModule} from '@angular/material/dialog';
import {MatDividerModule} from '@angular/material/divider';
import {MatFormFieldModule} from '@angular/material/form-field';
@@ -170,6 +171,32 @@ function translateColor(col: string, isBg = false) {
}
}
+function getAllSelectableNestedNodesAndGroups(
+ params: {nodes: DagNode[], groups: DagGroup[]},
+ path: string[] = []): Array {
+ const allNodesAndGroups = [];
+ const pathLabel = path.length ? ` (${path.join(', ')})` : '';
+ for (const subGroup of params.groups) {
+ allNodesAndGroups.push(
+ {node: subGroup, path, label: `${subGroup.id}${pathLabel}`});
+ if (!subGroup.treatAsLoop) {
+ allNodesAndGroups.push(...getAllSelectableNestedNodesAndGroups(
+ subGroup, [...path, subGroup.id]));
+ } else {
+ // We need to skip the iteration node itself, as it is not selectable
+ subGroup.groups.forEach((iterationGroup) => {
+ allNodesAndGroups.push(...getAllSelectableNestedNodesAndGroups(
+ iterationGroup, [...path, subGroup.id, iterationGroup.id]));
+ });
+ }
+ }
+ for (const subNode of params.nodes) {
+ allNodesAndGroups.push(
+ {node: subNode, path, label: `${subNode.id}${pathLabel}`});
+ }
+ return allNodesAndGroups;
+}
+
/** Demo component for directed acyclic graph view. */
@Component({
standalone: false,
@@ -237,6 +264,7 @@ export class DagDemoPage {
userConfigChange = new Subject();
destroy = new Subject();
+ allSelectableNodesAndGroups: Array = [];
constructor(public dialog: MatDialog) {
this.setCurrDataset(DEFAULT_DATASET, true);
this.resetAll();
@@ -339,6 +367,8 @@ export class DagDemoPage {
const newGraph = cloneGraph(this.datasets[name]);
this.calibrateNodes(newGraph);
this.currDataset = newGraph;
+ this.allSelectableNodesAndGroups =
+ getAllSelectableNestedNodesAndGroups(newGraph);
}
onDatasetChanged(event: Event) {
@@ -531,6 +561,14 @@ export class DagDemoPage {
this.destroy.next();
this.destroy.complete();
}
+
+ triggerSelection(node: SelectedNode) {
+ this.selectedNode = node;
+ }
+
+ selectedNodeComparator(a: SelectedNode, b: SelectedNode) {
+ return a.node.id === b.node.id && a.path.join('') === b.path.join('');
+ }
}
@NgModule({
@@ -546,6 +584,7 @@ export class DagDemoPage {
DagScaffoldModule,
DagToolbarModule,
MatSelectModule,
+ MatOptionModule,
WorkflowGraphIconModule,
MatDialogModule,
FormsModule,
diff --git a/src/app/demo_page/scuba_goldens/demo_page/chrome-linux/select-nested-group.png b/src/app/demo_page/scuba_goldens/demo_page/chrome-linux/select-nested-group.png
new file mode 100644
index 0000000..32d26d7
Binary files /dev/null and b/src/app/demo_page/scuba_goldens/demo_page/chrome-linux/select-nested-group.png differ
diff --git a/src/app/demo_page/scuba_goldens/demo_page/chrome-linux/select-nested-iteration-loop.png b/src/app/demo_page/scuba_goldens/demo_page/chrome-linux/select-nested-iteration-loop.png
new file mode 100644
index 0000000..210c0f9
Binary files /dev/null and b/src/app/demo_page/scuba_goldens/demo_page/chrome-linux/select-nested-iteration-loop.png differ
diff --git a/src/app/directed_acyclic_graph.ts b/src/app/directed_acyclic_graph.ts
index d9811d6..60b33d5 100644
--- a/src/app/directed_acyclic_graph.ts
+++ b/src/app/directed_acyclic_graph.ts
@@ -29,7 +29,7 @@ import {ColorThemeLoader} from './color_theme_loader';
import {DagStateService} from './dag-state.service';
import {STATE_SERVICE_PROVIDER} from './dag-state.service.provider';
import {baseColors, BLUE_THEME, clampVal, CLASSIC_THEME, createDAGFeatures, createNewSizeConfig, type DagTheme, DEFAULT_LAYOUT_OPTIONS, DEFAULT_THEME, defaultFeatures, defaultZoomConfig, EdgeStyle, type FeatureToggleOptions, generateTheme, getMargin, isPoint, type LayoutOptions, type Logger, MarkerStyle, type MinimapPosition, nanSafePt, NODE_HEIGHT, NODE_WIDTH, NodeState, OrientationMarginConfig, RankAlignment, RankDirection, RankerAlgorithim, SCROLL_STEP_PER_DELTA, SizeConfig, SVG_ELEMENT_SIZE, type ZoomConfig} from './data_types_internal';
-import {DagRaw, DagRawModule, EnhancedDagGroup, GraphDims} from './directed_acyclic_graph_raw';
+import {DagRaw, DagRawModule, EnhancedDagGroup, GraphDims, setEnhancedGroupSelection} from './directed_acyclic_graph_raw';
import {DagLogger} from './logger/dag_logger';
import {Minimap, MinimapModule} from './minimap/minimap';
import {type DagEdge, DagGroup, DagNode, GraphSpec, GroupIterationRecord, GroupToggleEvent, isDagreInit, NodeMap, type NodeRef, Point, type SelectedNode} from './node_spec';
@@ -577,7 +577,9 @@ export class DirectedAcyclicGraph implements OnInit, OnDestroy {
const loopGroup = parent?.$groups?.find(g => g.id === segment);
if (loopGroup) {
const selectedLoop = path.shift()!;
- loopGroup.selectedLoopId = selectedLoop;
+ const selectedLoopNode =
+ loopGroup.groups.find(n => n.id === selectedLoop);
+ setEnhancedGroupSelection(loopGroup, selectedLoopNode!);
// Increase pathDepth as we've consumed 2 path segments
pathDepth++;
return true;
@@ -592,6 +594,8 @@ export class DirectedAcyclicGraph implements OnInit, OnDestroy {
expandedStateChanges++;
// Detect changes so groups can expand properly
this.detectChanges();
+ // Detect changes in new dagEl so QueryList is updated properly
+ dagEl.detectChanges();
}
pathDepth++;
}
diff --git a/src/app/directed_acyclic_graph_raw.ng.html b/src/app/directed_acyclic_graph_raw.ng.html
index 3e0fd10..2f0b03f 100644
--- a/src/app/directed_acyclic_graph_raw.ng.html
+++ b/src/app/directed_acyclic_graph_raw.ng.html
@@ -1,5 +1,5 @@