Skip to content

Commit 52bc6c7

Browse files
committed
[1567] Fix Sub-parts are not displayed inside Package nodes
Bug: #1567 Signed-off-by: Axel RICHARD <axel.richard@obeo.fr>
1 parent 46bccad commit 52bc6c7

7 files changed

Lines changed: 231 additions & 13 deletions

File tree

CHANGELOG.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ Now it has been fixed in Sirius Web 2025.8.4, `SysONUpdateLibraryExecutor` has b
8585
- https://github.com/eclipse-syson/syson/issues/1546[#1546] [diagrams] Fix drag and drop of a graphical node from the diagram background into a _Package_ graphical node.
8686
- https://github.com/eclipse-syson/syson/issues/1550[#1550] [diagrams] Fix an issue where the _action flow_ compartment was not revealed when the _New Start Action_ was executed.
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.
88+
- 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.
89+
`PartUsage` is just one example, but the same issue could appear with any other `Element` contained in `Element` contained in a `Package`.
8890

8991
=== Improvements
9092

backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVDropFromDiagramTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public void beforeEach() {
7575
this.givenInitialServerState.initialize();
7676
}
7777

78-
@DisplayName("Given a diagram with some nodes, when a node is dropped in another one, then the diagram is updated")
78+
@DisplayName("GIVEN a diagram with some nodes, WHEN a node is dropped in another one, THEN the diagram is updated")
7979
@Sql(scripts = { GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
8080
config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
8181
@Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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.tests.navigation.DiagramNavigator;
27+
import org.eclipse.sirius.components.view.emf.diagram.IDiagramIdProvider;
28+
import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState;
29+
import org.eclipse.syson.AbstractIntegrationTests;
30+
import org.eclipse.syson.application.controllers.diagrams.testers.ToolTester;
31+
import org.eclipse.syson.application.data.GeneralViewWithTopNodesTestProjectData;
32+
import org.eclipse.syson.services.diagrams.DiagramDescriptionIdProvider;
33+
import org.eclipse.syson.services.diagrams.api.IGivenDiagramDescription;
34+
import org.eclipse.syson.services.diagrams.api.IGivenDiagramSubscription;
35+
import org.eclipse.syson.standard.diagrams.view.SDVDescriptionNameGenerator;
36+
import org.eclipse.syson.sysml.SysmlPackage;
37+
import org.eclipse.syson.util.IDescriptionNameGenerator;
38+
import org.eclipse.syson.util.SysONRepresentationDescriptionIdentifiers;
39+
import org.junit.jupiter.api.BeforeEach;
40+
import org.junit.jupiter.api.DisplayName;
41+
import org.junit.jupiter.api.Test;
42+
import org.springframework.beans.factory.annotation.Autowired;
43+
import org.springframework.boot.test.context.SpringBootTest;
44+
import org.springframework.test.context.jdbc.Sql;
45+
import org.springframework.test.context.jdbc.SqlConfig;
46+
import org.springframework.transaction.annotation.Transactional;
47+
48+
import reactor.core.publisher.Flux;
49+
import reactor.test.StepVerifier;
50+
51+
/**
52+
* Tests the nodes inside the a PAckage nodes in the General View diagram.
53+
*
54+
* @author arichard
55+
*/
56+
@Transactional
57+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
58+
public class GVPackageTests extends AbstractIntegrationTests {
59+
60+
@Autowired
61+
private IGivenInitialServerState givenInitialServerState;
62+
63+
@Autowired
64+
private IGivenDiagramSubscription givenDiagramSubscription;
65+
66+
@Autowired
67+
private IGivenDiagramDescription givenDiagramDescription;
68+
69+
@Autowired
70+
private IDiagramIdProvider diagramIdProvider;
71+
72+
@Autowired
73+
private ToolTester toolTester;
74+
75+
private final IDescriptionNameGenerator descriptionNameGenerator = new SDVDescriptionNameGenerator();
76+
77+
private Flux<DiagramRefreshedEventPayload> givenSubscriptionToDiagram() {
78+
var diagramEventInput = new DiagramEventInput(UUID.randomUUID(),
79+
GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID,
80+
GeneralViewWithTopNodesTestProjectData.GraphicalIds.DIAGRAM_ID);
81+
var flux = this.givenDiagramSubscription.subscribe(diagramEventInput);
82+
return flux;
83+
}
84+
85+
@BeforeEach
86+
public void beforeEach() {
87+
this.givenInitialServerState.initialize();
88+
}
89+
90+
@DisplayName("GIVEN a diagram with a Package node, WHEN a Part node and a Sub-Part node are created, THEN the Part node and the Sub-Part node are visible inside the Package, on the same level, with an edge between them")
91+
@Sql(scripts = { GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
92+
config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
93+
@Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
94+
@Test
95+
public void testCreateSubPartInPackage() {
96+
var flux = this.givenSubscriptionToDiagram();
97+
98+
var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID,
99+
SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID);
100+
var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider);
101+
102+
var newPartToolId = diagramDescriptionIdProvider.getNodeCreationToolId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getPackage()), "New Part");
103+
assertThat(newPartToolId).as("The tool 'New Part' should exist on the Package").isNotNull();
104+
105+
var newSubPartToolId = diagramDescriptionIdProvider.getNodeCreationToolId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getPartUsage()), "New Part");
106+
assertThat(newPartToolId).as("The tool 'New Part' should exist on the PartUsage").isNotNull();
107+
108+
var diagramId = new AtomicReference<String>();
109+
var packageNodeId = new AtomicReference<String>();
110+
var partNodeId = new AtomicReference<String>();
111+
112+
Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diag -> {
113+
diagramId.set(diag.getId());
114+
115+
var packageNode = new DiagramNavigator(diag).nodeWithLabel("Package").getNode();
116+
packageNodeId.set(packageNode.getId());
117+
118+
assertThat(packageNode.getChildNodes()).hasSize(0);
119+
120+
assertThat(new DiagramNavigator(diag).findDiagramEdgeCount()).isEqualTo(3);
121+
122+
});
123+
124+
Runnable newPartUsageTool = () -> this.toolTester.invokeTool(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagramId.get(), packageNodeId.get(), newPartToolId,
125+
List.of());
126+
127+
Consumer<Object> updatedDiagramContentConsumerAfterNewPart = assertRefreshedDiagramThat(diag -> {
128+
var packageNode = new DiagramNavigator(diag).nodeWithLabel("Package").getNode();
129+
130+
var partNode = new DiagramNavigator(diag).nodeWithLabel("Package").childNodeWithLabel("\u00ABpart\u00BB\npart1").getNode();
131+
partNodeId.set(partNode.getId());
132+
133+
assertThat(packageNode.getChildNodes()).hasSize(1);
134+
assertThat(packageNode.getChildNodes().get(0)).isEqualTo(partNode);
135+
136+
assertThat(new DiagramNavigator(diag).findDiagramEdgeCount()).isEqualTo(3);
137+
138+
});
139+
140+
Runnable newSubPartUsageTool = () -> this.toolTester.invokeTool(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagramId.get(), partNodeId.get(), newSubPartToolId,
141+
List.of());
142+
143+
Consumer<Object> updatedDiagramContentConsumerAfterNewSubPart = assertRefreshedDiagramThat(diag -> {
144+
var packageNode = new DiagramNavigator(diag).nodeWithLabel("Package").getNode();
145+
146+
assertThat(packageNode.getChildNodes()).hasSize(2);
147+
148+
// a new edge exists between the Part and the sub-Part inside the Package node
149+
assertThat(new DiagramNavigator(diag).findDiagramEdgeCount()).isEqualTo(4);
150+
});
151+
152+
StepVerifier.create(flux)
153+
.consumeNextWith(initialDiagramContentConsumer)
154+
.then(newPartUsageTool)
155+
.consumeNextWith(updatedDiagramContentConsumerAfterNewPart)
156+
.then(newSubPartUsageTool)
157+
.consumeNextWith(updatedDiagramContentConsumerAfterNewSubPart)
158+
.thenCancel()
159+
.verify(Duration.ofSeconds(10));
160+
}
161+
}

backend/releng/syson-test-coverage/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@
9090
<artifactId>syson-sysml-validation</artifactId>
9191
<version>2025.8.9</version>
9292
</dependency>
93+
<dependency>
94+
<groupId>org.eclipse.syson</groupId>
95+
<artifactId>syson-common-view</artifactId>
96+
<version>2025.8.9</version>
97+
</dependency>
9398
<dependency>
9499
<groupId>org.eclipse.syson</groupId>
95100
<artifactId>syson-diagram-common-view</artifactId>

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

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.eclipse.syson.sysml.AttributeUsage;
2323
import org.eclipse.syson.sysml.Documentation;
2424
import org.eclipse.syson.sysml.Element;
25+
import org.eclipse.syson.sysml.Namespace;
2526
import org.eclipse.syson.sysml.NamespaceImport;
2627
import org.eclipse.syson.sysml.Package;
2728
import org.eclipse.syson.sysml.PortUsage;
@@ -75,13 +76,13 @@ public ViewFilterSwitch(ViewDefinitionKind kind, List<Element> exposedElements,
7576
public Boolean defaultCase(EObject object) {
7677
Boolean displayAsTreeNode = Boolean.FALSE;
7778
if (ViewDefinitionKind.isGeneralView(this.kind)) {
78-
displayAsTreeNode = Boolean.TRUE;
79+
displayAsTreeNode = this.parentElement == null || (this.isDirectNestedNode(object));
7980
} else if (ViewDefinitionKind.isActionFlowView(this.kind)) {
8081
displayAsTreeNode = object instanceof ActionUsage || object instanceof ActionDefinition;
8182
} else if (ViewDefinitionKind.isStateTransitionView(this.kind)) {
8283
displayAsTreeNode = object instanceof StateUsage || object instanceof StateDefinition;
8384
} else {
84-
displayAsTreeNode = !this.isNestedNode(object);
85+
displayAsTreeNode = !this.isIndirectNestedNode(object);
8586
}
8687
return displayAsTreeNode;
8788
}
@@ -90,7 +91,7 @@ public Boolean defaultCase(EObject object) {
9091
public Boolean caseAttributeUsage(AttributeUsage object) {
9192
// For AttributeUsages we don't want nested Nodes, no matter the type of ViewDefinition.
9293
// The sub AttributeUsages are displayed in a compartment list called "attributes".
93-
return !this.isNestedNode(object) && !ViewDefinitionKind.isActionFlowView(this.kind) && !ViewDefinitionKind.isStateTransitionView(this.kind);
94+
return !this.isIndirectNestedNode(object) && !ViewDefinitionKind.isActionFlowView(this.kind) && !ViewDefinitionKind.isStateTransitionView(this.kind);
9495
}
9596

9697
@Override
@@ -103,18 +104,51 @@ public Boolean caseDocumentation(Documentation object) {
103104
public Boolean casePortUsage(PortUsage object) {
104105
// For PortUsages we don't want nested Nodes, no matter the type of ViewDefinition.
105106
// The sub PortUsages are displayed in a compartment list called "ports" and/or as border nodes.
106-
return !this.isNestedNode(object) && !ViewDefinitionKind.isActionFlowView(this.kind) && !ViewDefinitionKind.isStateTransitionView(this.kind);
107+
return !this.isIndirectNestedNode(object) && !ViewDefinitionKind.isActionFlowView(this.kind) && !ViewDefinitionKind.isStateTransitionView(this.kind);
107108
}
108109

109-
private boolean isNestedNode(EObject object) {
110-
boolean isNestedNode = false;
111-
for (Element exposedElement : this.exposedElements) {
112-
isNestedNode = !Objects.equals(exposedElement, object) && (this.parentElement == null && EMFUtils.isAncestor(exposedElement, object));
113-
if (isNestedNode) {
114-
break;
110+
/**
111+
* Check if the given object is an indirect nested node of the parentElement of this class.
112+
*
113+
* @param object
114+
* the given object
115+
* @return <code>true</code> if the given object is an indirect node of the parentElement of this class,
116+
* <code>false</code> otherwise.
117+
*/
118+
private boolean isIndirectNestedNode(EObject object) {
119+
boolean isDirectNestedNode = false;
120+
if (this.parentElement instanceof Namespace elt && !elt.getOwnedMember().contains(object)) {
121+
isDirectNestedNode = true;
122+
} else {
123+
for (Element exposedElement : this.exposedElements) {
124+
if (Objects.equals(exposedElement, object)) {
125+
continue;
126+
} else if (this.parentElement == null && EMFUtils.isAncestor(exposedElement, object)) {
127+
isDirectNestedNode = true;
128+
}
129+
if (isDirectNestedNode) {
130+
break;
131+
}
115132
}
116133
}
117-
return isNestedNode;
134+
return isDirectNestedNode;
118135
}
119136

137+
/**
138+
* Check if the given object is a direct nested node of the parentElement of this class.
139+
*
140+
* @param object
141+
* the given object
142+
* @return <code>true</code> if the given object is a direct nested node of the parentElement of this class,
143+
* <code>false</code> otherwise.
144+
*/
145+
private boolean isDirectNestedNode(EObject object) {
146+
boolean isDirectNestedNode = false;
147+
if (this.parentElement instanceof Namespace elt && elt.getOwnedMember().contains(object)) {
148+
isDirectNestedNode = true;
149+
} else if (this.parentElement instanceof Package) {
150+
isDirectNestedNode = true;
151+
}
152+
return isDirectNestedNode;
153+
}
120154
}

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public List<Element> getExposedElements(Element element, Element parent, EClass
137137
exposedElements.addAll(viewUsage.getExposedElement());
138138
}
139139
var filteredExposedElements = exposedElements.stream()
140-
.filter(elt -> elt != null && Objects.equals(elt.eClass(), domainType) && (parent == null || parent.getOwnedElement().contains(elt)))
140+
.filter(elt -> this.isTypeOf(elt, domainType) && (parent == null || (!Objects.equals(parent, elt) && EMFUtils.isAncestor(parent, elt))))
141141
.toList();
142142
elementsToExpose.addAll(filteredExposedElements);
143143
// if it is not a General View, we don't want to display nested nodes as tree (i.e. sibling nodes +
@@ -686,4 +686,18 @@ protected List<Object> getAncestors(Node selectedNode, Diagram diagram, IEditing
686686
}
687687
return ancestors;
688688
}
689+
690+
/**
691+
* Check if the given {@link Element}'s {@link EClass} is the same than the given domainType.
692+
*
693+
* @param element
694+
* the given {@link Element}.
695+
* @param domainType
696+
* the given domainType as an {@link EClass}.
697+
* @return <code>true</code> if the given {@link Element}'s {@link EClass} is the same than the given domainType,
698+
* <code>false</code> otherwise.
699+
*/
700+
protected boolean isTypeOf(Element element, EClass domainType) {
701+
return element != null && Objects.equals(element.eClass(), domainType);
702+
}
689703
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ Now it is only available on SysML model elements.
7676
- Fix drag and drop of a graphical node from the diagram background into a _Package_ graphical node.
7777
- Fix an issue where the _action flow_ compartment was not revealed when the _New Start Action_ was executed.
7878
- Fix an issue where the _Delete from diagram_ action did nothing on elements inside a _Package_ in diagrams.
79+
- 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.
80+
`PartUsage` is just one example, but the same issue could appear with any other `Element` contained in `Element` contained in a `Package`.
7981

8082
== Improvements
8183

0 commit comments

Comments
 (0)