Skip to content

Commit 8b6603f

Browse files
committed
[1579] Reveal action flow compartment on ControlNode creation
Bug: #1579 Signed-off-by: Axel RICHARD <axel.richard@obeo.fr>
1 parent f55803e commit 8b6603f

7 files changed

Lines changed: 453 additions & 5 deletions

File tree

CHANGELOG.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ Now it has been fixed in Sirius Web 2025.8.4, `SysONUpdateLibraryExecutor` has b
8787
- https://github.com/eclipse-syson/syson/issues/1547[#1547] [diagrams] Fix an issue where the _Delete from diagram_ action did nothing on elements inside a _Package_ in diagrams.
8888
- https://github.com/eclipse-syson/syson/issues/1567[#1567] [diagrams] Fix an issue where a `PartUsage` contained in a `PartUsage` contained in a `Package` was not displayed, even though it was exposed in the `ViewUsage` corresponding to the diagram.
8989
`PartUsage` is just one example, but the same issue could appear with any other `Element` contained in `Element` contained in a `Package`.
90+
- https://github.com/eclipse-syson/syson/issues/1579[#1579] [diagrams] Fix an issue where the _action flow_ compartment was not revealed when the _New Decision_/_New Fork_/_New Join_/_New Merge_ tools were executed.
9091

9192
=== Improvements
9293

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Obeo.
3+
* This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v2.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Obeo - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.syson.application.controllers.diagrams.general.view;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.eclipse.sirius.components.diagrams.tests.DiagramEventPayloadConsumer.assertRefreshedDiagramThat;
17+
18+
import java.time.Duration;
19+
import java.util.List;
20+
import java.util.UUID;
21+
import java.util.concurrent.atomic.AtomicReference;
22+
import java.util.function.Consumer;
23+
24+
import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramEventInput;
25+
import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramRefreshedEventPayload;
26+
import org.eclipse.sirius.components.diagrams.Node;
27+
import org.eclipse.sirius.components.diagrams.ViewModifier;
28+
import org.eclipse.sirius.components.diagrams.tests.navigation.DiagramNavigator;
29+
import org.eclipse.sirius.components.view.emf.diagram.IDiagramIdProvider;
30+
import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState;
31+
import org.eclipse.syson.AbstractIntegrationTests;
32+
import org.eclipse.syson.application.controllers.diagrams.testers.ToolTester;
33+
import org.eclipse.syson.application.data.GeneralViewWithTopNodesTestProjectData;
34+
import org.eclipse.syson.services.diagrams.DiagramDescriptionIdProvider;
35+
import org.eclipse.syson.services.diagrams.api.IGivenDiagramDescription;
36+
import org.eclipse.syson.services.diagrams.api.IGivenDiagramSubscription;
37+
import org.eclipse.syson.standard.diagrams.view.SDVDescriptionNameGenerator;
38+
import org.eclipse.syson.sysml.SysmlPackage;
39+
import org.eclipse.syson.util.IDescriptionNameGenerator;
40+
import org.eclipse.syson.util.SysONRepresentationDescriptionIdentifiers;
41+
import org.junit.jupiter.api.BeforeEach;
42+
import org.junit.jupiter.api.DisplayName;
43+
import org.junit.jupiter.api.Test;
44+
import org.springframework.beans.factory.annotation.Autowired;
45+
import org.springframework.boot.test.context.SpringBootTest;
46+
import org.springframework.test.context.jdbc.Sql;
47+
import org.springframework.test.context.jdbc.SqlConfig;
48+
import org.springframework.transaction.annotation.Transactional;
49+
50+
import reactor.core.publisher.Flux;
51+
import reactor.test.StepVerifier;
52+
53+
/**
54+
* Tests the control nodes (fork, merge, decision, join) inside the General View diagram.
55+
*
56+
* @author arichard
57+
*/
58+
@Transactional
59+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
60+
public class GVControlNodeTests extends AbstractIntegrationTests {
61+
62+
private static final String ACTION_LABEL = "\u00ABref action\u00BB\naction";
63+
64+
private static final String ACTION_FLOW_LABEL = "action flow";
65+
66+
@Autowired
67+
private IGivenInitialServerState givenInitialServerState;
68+
69+
@Autowired
70+
private IGivenDiagramSubscription givenDiagramSubscription;
71+
72+
@Autowired
73+
private IGivenDiagramDescription givenDiagramDescription;
74+
75+
@Autowired
76+
private IDiagramIdProvider diagramIdProvider;
77+
78+
@Autowired
79+
private ToolTester toolTester;
80+
81+
private final IDescriptionNameGenerator descriptionNameGenerator = new SDVDescriptionNameGenerator();
82+
83+
private Flux<DiagramRefreshedEventPayload> givenSubscriptionToDiagram() {
84+
var diagramEventInput = new DiagramEventInput(UUID.randomUUID(),
85+
GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID,
86+
GeneralViewWithTopNodesTestProjectData.GraphicalIds.DIAGRAM_ID);
87+
var flux = this.givenDiagramSubscription.subscribe(diagramEventInput);
88+
return flux;
89+
}
90+
91+
@BeforeEach
92+
public void beforeEach() {
93+
this.givenInitialServerState.initialize();
94+
}
95+
96+
@DisplayName("GIVEN a diagram with an ActionUsage, WHEN a DecisionNode is created, THEN the diagram shows the action flow compartment and the DecisionNode inside the ActionUsage")
97+
@Sql(scripts = { GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
98+
config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
99+
@Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
100+
@Test
101+
public void createDecisionNode() {
102+
var flux = this.givenSubscriptionToDiagram();
103+
104+
var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID,
105+
SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID);
106+
var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider);
107+
108+
var newDecisionNodeToolId = diagramDescriptionIdProvider.getNodeCreationToolId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getActionUsage()), "New Decision");
109+
assertThat(newDecisionNodeToolId).as("The tool 'New Decision' should exist on ActionUsage").isNotNull();
110+
111+
var diagramId = new AtomicReference<String>();
112+
var actionNodeId = new AtomicReference<String>();
113+
114+
Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
115+
diagramId.set(diagram.getId());
116+
117+
var actionNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).getNode();
118+
assertThat(actionNode).isNotNull();
119+
actionNodeId.set(actionNode.getId());
120+
121+
var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
122+
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Hidden);
123+
});
124+
125+
Runnable newDecisionTool = () -> this.toolTester.invokeTool(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagramId.get(), actionNodeId.get(), newDecisionNodeToolId,
126+
List.of());
127+
128+
Consumer<Object> updatedDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
129+
var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
130+
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Normal);
131+
List<Node> childNodes = actionFlowCompartmentNode.getChildNodes();
132+
assertThat(childNodes).hasSize(1);
133+
assertThat(childNodes.get(0).getOutsideLabels()).isNotNull();
134+
assertThat(childNodes.get(0).getOutsideLabels().get(0).text()).isEqualTo("decisionNode1");
135+
});
136+
137+
StepVerifier.create(flux)
138+
.consumeNextWith(initialDiagramContentConsumer)
139+
.then(newDecisionTool)
140+
.consumeNextWith(updatedDiagramContentConsumer)
141+
.thenCancel()
142+
.verify(Duration.ofSeconds(10));
143+
}
144+
145+
@DisplayName("GIVEN a diagram with an ActionUsage, WHEN a ForkNode is created, THEN the diagram shows the action flow compartment and the ForkNode inside the ActionUsage")
146+
@Sql(scripts = { GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
147+
config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
148+
@Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
149+
@Test
150+
public void createForkNode() {
151+
var flux = this.givenSubscriptionToDiagram();
152+
153+
var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID,
154+
SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID);
155+
var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider);
156+
157+
var newForkNodeToolId = diagramDescriptionIdProvider.getNodeCreationToolId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getActionUsage()), "New Fork");
158+
assertThat(newForkNodeToolId).as("The tool 'New Fork' should exist on ActionUsage").isNotNull();
159+
160+
var diagramId = new AtomicReference<String>();
161+
var actionNodeId = new AtomicReference<String>();
162+
163+
Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
164+
diagramId.set(diagram.getId());
165+
166+
var actionNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).getNode();
167+
assertThat(actionNode).isNotNull();
168+
actionNodeId.set(actionNode.getId());
169+
170+
var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
171+
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Hidden);
172+
});
173+
174+
Runnable newForkTool = () -> this.toolTester.invokeTool(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagramId.get(), actionNodeId.get(), newForkNodeToolId,
175+
List.of());
176+
177+
Consumer<Object> updatedDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
178+
var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
179+
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Normal);
180+
List<Node> childNodes = actionFlowCompartmentNode.getChildNodes();
181+
assertThat(childNodes).hasSize(1);
182+
assertThat(childNodes.get(0).getOutsideLabels()).isNotNull();
183+
assertThat(childNodes.get(0).getOutsideLabels().get(0).text()).isEqualTo("forkNode1");
184+
});
185+
186+
StepVerifier.create(flux)
187+
.consumeNextWith(initialDiagramContentConsumer)
188+
.then(newForkTool)
189+
.consumeNextWith(updatedDiagramContentConsumer)
190+
.thenCancel()
191+
.verify(Duration.ofSeconds(10));
192+
}
193+
194+
@DisplayName("GIVEN a diagram with an ActionUsage, WHEN a JoinNode is created, THEN the diagram shows the action flow compartment and the JoinNode inside the ActionUsage")
195+
@Sql(scripts = { GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
196+
config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
197+
@Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
198+
@Test
199+
public void createJoinNode() {
200+
var flux = this.givenSubscriptionToDiagram();
201+
202+
var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID,
203+
SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID);
204+
var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider);
205+
206+
var newJoinNodeToolId = diagramDescriptionIdProvider.getNodeCreationToolId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getActionUsage()), "New Join");
207+
assertThat(newJoinNodeToolId).as("The tool 'New Join' should exist on ActionUsage").isNotNull();
208+
209+
var diagramId = new AtomicReference<String>();
210+
var actionNodeId = new AtomicReference<String>();
211+
212+
Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
213+
diagramId.set(diagram.getId());
214+
215+
var actionNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).getNode();
216+
assertThat(actionNode).isNotNull();
217+
actionNodeId.set(actionNode.getId());
218+
219+
var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
220+
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Hidden);
221+
});
222+
223+
Runnable newJoinTool = () -> this.toolTester.invokeTool(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagramId.get(), actionNodeId.get(), newJoinNodeToolId,
224+
List.of());
225+
226+
Consumer<Object> updatedDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
227+
var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
228+
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Normal);
229+
List<Node> childNodes = actionFlowCompartmentNode.getChildNodes();
230+
assertThat(childNodes).hasSize(1);
231+
assertThat(childNodes.get(0).getOutsideLabels()).isNotNull();
232+
assertThat(childNodes.get(0).getOutsideLabels().get(0).text()).isEqualTo("joinNode1");
233+
});
234+
235+
StepVerifier.create(flux)
236+
.consumeNextWith(initialDiagramContentConsumer)
237+
.then(newJoinTool)
238+
.consumeNextWith(updatedDiagramContentConsumer)
239+
.thenCancel()
240+
.verify(Duration.ofSeconds(10));
241+
}
242+
243+
@DisplayName("GIVEN a diagram with an ActionUsage, WHEN a MergeNode is created, THEN the diagram shows the action flow compartment and the MergeNode inside the ActionUsage")
244+
@Sql(scripts = { GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
245+
config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
246+
@Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
247+
@Test
248+
public void createMergeNode() {
249+
var flux = this.givenSubscriptionToDiagram();
250+
251+
var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID,
252+
SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID);
253+
var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider);
254+
255+
var newMergeNodeToolId = diagramDescriptionIdProvider.getNodeCreationToolId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getActionUsage()), "New Merge");
256+
assertThat(newMergeNodeToolId).as("The tool 'New Merge' should exist on ActionUsage").isNotNull();
257+
258+
var diagramId = new AtomicReference<String>();
259+
var actionNodeId = new AtomicReference<String>();
260+
261+
Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
262+
diagramId.set(diagram.getId());
263+
264+
var actionNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).getNode();
265+
assertThat(actionNode).isNotNull();
266+
actionNodeId.set(actionNode.getId());
267+
268+
var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
269+
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Hidden);
270+
});
271+
272+
Runnable newMergeTool = () -> this.toolTester.invokeTool(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagramId.get(), actionNodeId.get(), newMergeNodeToolId,
273+
List.of());
274+
275+
Consumer<Object> updatedDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
276+
var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
277+
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Normal);
278+
List<Node> childNodes = actionFlowCompartmentNode.getChildNodes();
279+
assertThat(childNodes).hasSize(1);
280+
assertThat(childNodes.get(0).getOutsideLabels()).isNotNull();
281+
assertThat(childNodes.get(0).getOutsideLabels().get(0).text()).isEqualTo("mergeNode1");
282+
});
283+
284+
StepVerifier.create(flux)
285+
.consumeNextWith(initialDiagramContentConsumer)
286+
.then(newMergeTool)
287+
.consumeNextWith(updatedDiagramContentConsumer)
288+
.thenCancel()
289+
.verify(Duration.ofSeconds(10));
290+
}
291+
}

backend/application/syson-application/src/test/resources/scripts/database-content/GeneralView-ActionUsage.sql

Lines changed: 131 additions & 0 deletions
Large diffs are not rendered by default.

backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractControlNodeActionNodeDescriptionProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.eclipse.sirius.components.view.diagram.SynchronizationPolicy;
3232
import org.eclipse.sirius.components.view.diagram.UserResizableDirection;
3333
import org.eclipse.syson.diagram.common.view.services.ViewEdgeToolSwitch;
34+
import org.eclipse.syson.util.AQLConstants;
3435
import org.eclipse.syson.util.AQLUtils;
3536
import org.eclipse.syson.util.IDescriptionNameGenerator;
3637
import org.eclipse.syson.util.SysMLMetamodelHelper;
@@ -120,7 +121,7 @@ private OutsideLabelStyle createOutsideLabelStyle() {
120121
return this.diagramBuilderHelper.newOutsideLabelStyle()
121122
.borderSize(0)
122123
.labelColor(this.colorProvider.getColor(ViewConstants.DEFAULT_LABEL_COLOR))
123-
.showIconExpression("aql:false")
124+
.showIconExpression(AQLConstants.AQL_FALSE)
124125
.build();
125126
}
126127

backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/RevealCompartmentSwitch.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
import org.eclipse.emf.ecore.EObject;
1616
import org.eclipse.syson.sysml.ActionUsage;
1717
import org.eclipse.syson.sysml.ConstraintUsage;
18+
import org.eclipse.syson.sysml.DecisionNode;
19+
import org.eclipse.syson.sysml.ForkNode;
20+
import org.eclipse.syson.sysml.JoinNode;
21+
import org.eclipse.syson.sysml.MergeNode;
1822
import org.eclipse.syson.sysml.OccurrenceDefinition;
1923
import org.eclipse.syson.sysml.OccurrenceUsage;
2024
import org.eclipse.syson.sysml.StateUsage;
@@ -53,6 +57,26 @@ public Boolean caseConstraintUsage(ConstraintUsage object) {
5357
return !this.isGeneralView;
5458
}
5559

60+
@Override
61+
public Boolean caseDecisionNode(DecisionNode object) {
62+
return Boolean.TRUE;
63+
}
64+
65+
@Override
66+
public Boolean caseForkNode(ForkNode object) {
67+
return Boolean.TRUE;
68+
}
69+
70+
@Override
71+
public Boolean caseJoinNode(JoinNode object) {
72+
return Boolean.TRUE;
73+
}
74+
75+
@Override
76+
public Boolean caseMergeNode(MergeNode object) {
77+
return Boolean.TRUE;
78+
}
79+
5680
@Override
5781
public Boolean caseOccurrenceDefinition(OccurrenceDefinition object) {
5882
return !this.isGeneralView;

0 commit comments

Comments
 (0)