Skip to content

Commit 247a8fc

Browse files
gcoutableAxelRICHARD
authored andcommitted
[2201] Fix the compartment reveal on a wrong node
Bug: #2201 Signed-off-by: Guillaume Coutable <guillaume.coutable@obeo.fr>
1 parent 44fc8bb commit 247a8fc

4 files changed

Lines changed: 243 additions & 7 deletions

File tree

CHANGELOG.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ This change ensures compatibility with classpath resource resolution, which requ
6464
- https://github.com/eclipse-syson/syson/issues/2185[#2185] [explorer] When creating a `ConstraintUsage` inside a `Requirement` the new constraint is now correctly owned through a `RequirementConstraintMembership`.
6565
- https://github.com/eclipse-syson/syson/issues/2196[#2196] [diagrams] Align the _New Flow (flow)_ tool on `PortUsage`, `ItemUsage`, and `ReferenceUsage` border nodes with the supported `FlowUsage` rendered endpoints, preventing unsupported targets such as regular `ActionUsage` graphical nodes.
6666
- https://github.com/eclipse-syson/syson/issues/2197[#2197] [diagrams] Fix an issue where the _New Perform action_ tool was missing from `CaseUsage`, `CaseDefinition`, `UseCaseUsage`, and `UseCaseDefinition` graphical node palettes, while remaining available in the revealed `perform actions` compartment.
67-
- https://github.com/eclipse-syson/syson/issues/2204[#2204] [diagrams] Fix an issue where the resize of _Documentation_ graphical nodes was not working correctly, allowing inside text to overflow outside of the graphical node.
67+
- https://github.com/eclipse-syson/syson/issues/2204[#2204] [diagrams] Fix an issue where the resize of _Documentation_ graphical nodes was not working correctly, allowing inside text to overflow outside the graphical node.
68+
- https://github.com/eclipse-syson/syson/issues/2201[#2201] [diagrams] Fix the issue where sometime, applying a tool to create a child element was revealing compartments of the wrong graphical node.
6869

6970
=== Improvements
7071

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 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.compartments;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.eclipse.sirius.components.diagrams.tests.DiagramEventPayloadConsumer.assertRefreshedDiagramThat;
17+
18+
import com.jayway.jsonpath.JsonPath;
19+
20+
import java.time.Duration;
21+
import java.util.List;
22+
import java.util.Set;
23+
import java.util.UUID;
24+
import java.util.concurrent.atomic.AtomicReference;
25+
26+
import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramEventInput;
27+
import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramRefreshedEventPayload;
28+
import org.eclipse.sirius.components.collaborative.diagrams.dto.HideDiagramElementInput;
29+
import org.eclipse.sirius.components.collaborative.diagrams.dto.ToolVariable;
30+
import org.eclipse.sirius.components.collaborative.diagrams.dto.ToolVariableType;
31+
import org.eclipse.sirius.components.core.api.SuccessPayload;
32+
import org.eclipse.sirius.components.diagrams.Diagram;
33+
import org.eclipse.sirius.components.diagrams.Node;
34+
import org.eclipse.sirius.components.diagrams.ViewModifier;
35+
import org.eclipse.sirius.components.diagrams.tests.graphql.HideDiagramElementMutationRunner;
36+
import org.eclipse.sirius.components.diagrams.tests.navigation.DiagramNavigator;
37+
import org.eclipse.sirius.components.diagrams.tests.navigation.NavigatorCache;
38+
import org.eclipse.sirius.components.view.emf.diagram.IDiagramIdProvider;
39+
import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState;
40+
import org.eclipse.syson.AbstractIntegrationTests;
41+
import org.eclipse.syson.GivenSysONServer;
42+
import org.eclipse.syson.application.controllers.diagrams.checkers.CheckDiagramElementCount;
43+
import org.eclipse.syson.application.controllers.diagrams.testers.ToolTester;
44+
import org.eclipse.syson.application.data.GeneralViewWithTopNodesTestProjectData;
45+
import org.eclipse.syson.services.diagrams.DiagramComparator;
46+
import org.eclipse.syson.services.diagrams.DiagramDescriptionIdProvider;
47+
import org.eclipse.syson.services.diagrams.NodeCreationTestsService;
48+
import org.eclipse.syson.services.diagrams.api.IGivenDiagramDescription;
49+
import org.eclipse.syson.services.diagrams.api.IGivenDiagramSubscription;
50+
import org.eclipse.syson.standard.diagrams.view.SDVDescriptionNameGenerator;
51+
import org.eclipse.syson.standard.diagrams.view.nodes.PerformActionsCompartmentNodeDescriptionProvider;
52+
import org.eclipse.syson.sysml.SysmlPackage;
53+
import org.eclipse.syson.util.IDescriptionNameGenerator;
54+
import org.eclipse.syson.util.SysONRepresentationDescriptionIdentifiers;
55+
import org.junit.jupiter.api.BeforeEach;
56+
import org.junit.jupiter.api.DisplayName;
57+
import org.junit.jupiter.api.Test;
58+
import org.springframework.beans.factory.annotation.Autowired;
59+
import org.springframework.boot.test.context.SpringBootTest;
60+
import org.springframework.transaction.annotation.Transactional;
61+
62+
import reactor.core.publisher.Flux;
63+
import reactor.test.StepVerifier;
64+
65+
/**
66+
* Tests the compartment reveal behavior of a State.
67+
*
68+
* @author gcoutable
69+
*/
70+
@Transactional
71+
@SuppressWarnings("checkstyle:MultipleStringLiterals")
72+
@GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH })
73+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
74+
public class StateCompartmentRevealTests extends AbstractIntegrationTests {
75+
76+
@Autowired
77+
private IGivenInitialServerState givenInitialServerState;
78+
79+
@Autowired
80+
private IGivenDiagramDescription givenDiagramDescription;
81+
82+
@Autowired
83+
private IGivenDiagramSubscription givenDiagramSubscription;
84+
85+
@Autowired
86+
private HideDiagramElementMutationRunner hideDiagramElementMutationRunner;
87+
88+
@Autowired
89+
private IDiagramIdProvider diagramIdProvider;
90+
91+
@Autowired
92+
private ToolTester nodeCreationTester;
93+
94+
@Autowired
95+
private DiagramComparator diagramComparator;
96+
97+
private NodeCreationTestsService creationTestsService;
98+
99+
private final IDescriptionNameGenerator descriptionNameGenerator = new SDVDescriptionNameGenerator();
100+
101+
private Flux<DiagramRefreshedEventPayload> givenSubscriptionToDiagram() {
102+
var diagramEventInput = new DiagramEventInput(UUID.randomUUID(),
103+
GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID,
104+
GeneralViewWithTopNodesTestProjectData.GraphicalIds.DIAGRAM_ID);
105+
return this.givenDiagramSubscription.subscribe(diagramEventInput);
106+
}
107+
108+
@BeforeEach
109+
public void setUp() {
110+
this.givenInitialServerState.initialize();
111+
this.creationTestsService = new NodeCreationTestsService(this.nodeCreationTester, this.descriptionNameGenerator, GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID);
112+
}
113+
114+
@DisplayName("GIVEN a StateUsage graphical node in a state transition compartment, WHEN executing the tool to create a Do Action, THEN the perform action compartment is revealed on the StateUsage graphical node")
115+
@Test
116+
public void testRevealPerformActionOfStateInsideStateTransitionCompartment() {
117+
var flux = this.givenSubscriptionToDiagram();
118+
119+
var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID,
120+
SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID);
121+
var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider);
122+
var diagram = new AtomicReference<Diagram>();
123+
var stateTransitionCompartmentNodeId = new AtomicReference<String>();
124+
var newStateUsageGraphicalNode = new AtomicReference<Node>();
125+
126+
var initialDiagramContentConsumer = assertRefreshedDiagramThat(initialDiagram -> {
127+
diagram.set(initialDiagram);
128+
var stateTransitionCompartment = new DiagramNavigator(initialDiagram)
129+
.nodeWithId(GeneralViewWithTopNodesTestProjectData.GraphicalIds.STATE_USAGE_ID)
130+
.childNodeWithNodeDescriptionId(diagramDescriptionIdProvider
131+
.getNodeDescriptionId(this.descriptionNameGenerator.getFreeFormCompartmentName(SysmlPackage.eINSTANCE.getStateUsage(), SysmlPackage.eINSTANCE.getUsage_NestedState())))
132+
.getNode();
133+
assertThat(stateTransitionCompartment.getState()).isEqualTo(ViewModifier.Hidden);
134+
stateTransitionCompartmentNodeId.set(stateTransitionCompartment.getId());
135+
});
136+
137+
Runnable revealStateTransitionCompartment = () -> {
138+
// Reveal the "state transition" compartment of StateUsage graphical node
139+
var input = new HideDiagramElementInput(UUID.randomUUID(), GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagram.get().getId(), Set.of(stateTransitionCompartmentNodeId.get()), false);
140+
var result = this.hideDiagramElementMutationRunner.run(input);
141+
assertThat(result.errors()).isEmpty();
142+
143+
var typename = JsonPath.read(result.data(), "$.data.hideDiagramElement.__typename");
144+
assertThat(typename).isEqualTo(SuccessPayload.class.getSimpleName());
145+
};
146+
147+
var updatedDiagramAfterCompartmentReveal = assertRefreshedDiagramThat(refreshedDiagram -> {
148+
var stateTransitionCompartment = new DiagramNavigator(refreshedDiagram)
149+
.nodeWithId(GeneralViewWithTopNodesTestProjectData.GraphicalIds.STATE_USAGE_ID)
150+
.childNodeWithNodeDescriptionId(diagramDescriptionIdProvider
151+
.getNodeDescriptionId(this.descriptionNameGenerator.getFreeFormCompartmentName(SysmlPackage.eINSTANCE.getStateUsage(), SysmlPackage.eINSTANCE.getUsage_NestedState())))
152+
.getNode();
153+
assertThat(stateTransitionCompartment.getState()).isEqualTo(ViewModifier.Normal);
154+
// Check there is only one StateUsage graphical node in the diagram for now (because the diagram contains
155+
// one of many kind of SysML elements)
156+
var stateUsages = new NavigatorCache(refreshedDiagram).getNodeDescriptionIdToNodes().get(diagramDescriptionIdProvider.getNodeDescriptionId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getStateUsage())));
157+
assertThat(stateUsages).size().isEqualTo(1);
158+
diagram.set(refreshedDiagram);
159+
});
160+
161+
// Create a state in the StateUsage which will create two graphical nodes, one hidden on the diagram background,
162+
// and another one visible in the StateUsage "state transition" compartment.
163+
Runnable createStateInStateTransition = this.creationTestsService.createNode(diagramDescriptionIdProvider, diagram, SysmlPackage.eINSTANCE.getStateUsage(),
164+
GeneralViewWithTopNodesTestProjectData.SemanticIds.STATE_USAGE_ID, SysmlPackage.eINSTANCE.getStateUsage());
165+
166+
var updatedDiagramAfterStateCreation = assertRefreshedDiagramThat(refreshedDiagram -> {
167+
// We have created a StateUsage in a StateUsage, thus, there are two more StateUsage graphical nodes, one
168+
// hidden on the diagram background, and another one visible in the StateUsage "state transition"
169+
// compartment.
170+
var stateUsages = new NavigatorCache(refreshedDiagram).getNodeDescriptionIdToNodes().get(diagramDescriptionIdProvider.getNodeDescriptionId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getStateUsage())));
171+
assertThat(stateUsages).size().isEqualTo(3);
172+
new CheckDiagramElementCount(this.diagramComparator)
173+
.hasNewNodeCount(1)
174+
.check(diagram.get(), refreshedDiagram, true);
175+
var newStateNode = new DiagramNavigator(refreshedDiagram)
176+
.nodeWithId(stateTransitionCompartmentNodeId.get())
177+
.childNodeWithNodeDescriptionId(diagramDescriptionIdProvider.getNodeDescriptionId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getStateUsage())))
178+
.getNode();
179+
assertThat(newStateNode.getState()).isEqualTo(ViewModifier.Normal);
180+
diagram.set(refreshedDiagram);
181+
newStateUsageGraphicalNode.set(newStateNode);
182+
});
183+
184+
Runnable createDoAction = () -> {
185+
// Create a Do Action in the StateUsage which should reveal the StateUsage "perform action" compartment
186+
var toolId = diagramDescriptionIdProvider.getNodeToolId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getStateUsage()), "New Do Action");
187+
var variables = List.of(new ToolVariable("selectedObject", "", ToolVariableType.OBJECT_ID));
188+
this.nodeCreationTester.invokeTool(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagram.get().getId(), newStateUsageGraphicalNode.get().getId(), toolId, variables);
189+
};
190+
191+
var updatedDiagramAfterDoActionCreated = assertRefreshedDiagramThat(refreshedDiagram -> {
192+
this.checkDiagramAfterDoActionCreated(diagramDescriptionIdProvider, newStateUsageGraphicalNode, refreshedDiagram);
193+
});
194+
195+
StepVerifier.create(flux)
196+
.consumeNextWith(initialDiagramContentConsumer)
197+
.then(revealStateTransitionCompartment)
198+
.consumeNextWith(updatedDiagramAfterCompartmentReveal)
199+
.then(createStateInStateTransition)
200+
.consumeNextWith(updatedDiagramAfterStateCreation)
201+
.then(createDoAction)
202+
.consumeNextWith(updatedDiagramAfterDoActionCreated)
203+
.thenCancel()
204+
.verify(Duration.ofSeconds(10));
205+
}
206+
207+
private void checkDiagramAfterDoActionCreated(DiagramDescriptionIdProvider diagramDescriptionIdProvider, AtomicReference<Node> newStateUsageGraphicalNode, Diagram refreshedDiagram) {
208+
// Check that between the two new StateUsage graphical node, the compartment is revealed on the graphical node with ViewModifier.Normal state.
209+
var stateUsages = new NavigatorCache(refreshedDiagram).getNodeDescriptionIdToNodes().get(diagramDescriptionIdProvider.getNodeDescriptionId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getStateUsage()))).stream()
210+
.filter(node -> node.getTargetObjectId().equals(newStateUsageGraphicalNode.get().getTargetObjectId()))
211+
.toList();
212+
var performActionCompartmentDescriptionId = diagramDescriptionIdProvider.getNodeDescriptionId(this.descriptionNameGenerator.getCompartmentName(SysmlPackage.eINSTANCE.getActionUsage(), SysmlPackage.eINSTANCE.getUsage_NestedAction()) + PerformActionsCompartmentNodeDescriptionProvider.PERFORM_ACTIONS_COMPARTMENT_NAME);
213+
assertThat(stateUsages).allSatisfy(node -> {
214+
if (node.getState().equals(ViewModifier.Hidden)) {
215+
var matchingCompartment = node.getChildNodes().stream()
216+
.filter(child -> child.getDescriptionId().equals(performActionCompartmentDescriptionId))
217+
.toList();
218+
assertThat(matchingCompartment).isNotEmpty().allMatch(child -> child.getState().equals(ViewModifier.Hidden));
219+
} else {
220+
var matchingCompartment = node.getChildNodes().stream()
221+
.filter(child -> child.getDescriptionId().equals(performActionCompartmentDescriptionId))
222+
.toList();
223+
assertThat(matchingCompartment).isNotEmpty().allMatch(child -> child.getState().equals(ViewModifier.Normal));
224+
}
225+
});
226+
}
227+
}

backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationCompartmentService.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,18 @@ public Node revealCompartment(Node node, Element targetElement, DiagramContext d
8484
.toList();
8585

8686
if (!compartmentDescriptionCandidates.isEmpty()) {
87-
NodeFinder nodeFinder = new NodeFinder(diagramContext.diagram());
88-
List<Node> compartmentNodeCandidates = nodeFinder
89-
.getAllNodesMatching(n -> compartmentDescriptionCandidates.stream().anyMatch(id -> Objects.equals(id, n.getDescriptionId()))
90-
&& Objects.equals(n.getTargetObjectId(), node.getTargetObjectId()));
87+
// Search for candidate in the node first
88+
List<Node> compartmentNodeCandidates = node.getChildNodes().stream()
89+
.filter(n -> compartmentDescriptionCandidates.stream().anyMatch(id -> Objects.equals(id, n.getDescriptionId()))
90+
&& Objects.equals(n.getTargetObjectId(), node.getTargetObjectId()))
91+
.toList();
92+
if (compartmentNodeCandidates.isEmpty()) {
93+
// If no candidate are found, search for candidate in the whole diagram
94+
NodeFinder nodeFinder = new NodeFinder(diagramContext.diagram());
95+
compartmentNodeCandidates = nodeFinder
96+
.getAllNodesMatching(n -> compartmentDescriptionCandidates.stream().anyMatch(id -> Objects.equals(id, n.getDescriptionId()))
97+
&& Objects.equals(n.getTargetObjectId(), node.getTargetObjectId()));
98+
}
9199
var noCompartmentToHandleTargetElement = compartmentNodeCandidates.stream()
92100
.allMatch(candidate -> ViewModifier.Hidden.equals(candidate.getState()));
93101
if (noCompartmentToHandleTargetElement) {

doc/content/modules/user-manual/pages/release-notes/2026.5.0.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
** Add tools to create _timeslices_ and _snapshots_ on `OccurrenceUsage`, `ItemUsage`, and `PartUsage` graphical nodes.
2020
These tools use a selection dialog to either create a graphical node based on an existing _timeslice_ or _snapshot_, or, if nothing is selected, create a default _timeslice_ or _snapshot_ `OccurrenceUsage` graphical node.
21-
2221
** Improved support for `Stakeholder`.
2322
It is now possible to create new _stakeholders_ from `Requirement` and `Concern` graphical nodes, whether they are definitions or usages.
2423
Created stakeholders must reference an existing `PartUsage`.
@@ -35,7 +34,8 @@ image::release-notes-stakeholder-node.png[Default representation of Stakeholder
3534
** In _Interconnection View_ diagrams, fix an issue where the `parts` compartment of a `PartDefinition` graphical node was incorrectly revealed when creating a `PartUsage` from that `PartDefinition`, even when the `interconnection` compartment was already visible.
3635
** Fix an issue where the _New Flow (flow)_ tool on `PortUsage`, `ItemUsage`, and `ReferenceUsage` graphical border nodes could expose unsupported targets, preventing some valid flow endpoints from being proposed and allowing non-renderable ones.
3736
** Fix an issue where the _New Perform action_ tool was not available directly on `CaseUsage`, `CaseDefinition`, `UseCaseUsage`, and `UseCaseDefinition` graphical nodes, even though it was available in their revealed `perform actions` compartment.
38-
** Fix an issue where the resize of _Documentation_ graphical nodes was not working correctly, allowing inside text to overflow outside of the graphical node.
37+
** Fix an issue where the resize of _Documentation_ graphical nodes was not working correctly, allowing inside text to overflow outside the graphical node.
38+
** Fix an issue where applying a tool to create a child element may reveal compartments of the wrong graphical node.
3939

4040
* In the _Details_ view:
4141

0 commit comments

Comments
 (0)