Skip to content

Commit 4622932

Browse files
committed
[1581] Add support for redefining inherited port usages in diagrams
Bug: #1581 Signed-off-by: Florian ROUËNÉ <florian.rouene@obeosoft.com>
1 parent 2c1e8f2 commit 4622932

14 files changed

Lines changed: 438 additions & 57 deletions

File tree

CHANGELOG.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
- https://github.com/eclipse-syson/syson/issues/1581[#1581] [diagrams] Display inherited `PortUsages` as border nodes in diagrams
2020
- https://github.com/eclipse-syson/syson/issues/1589[#1589] [explorer] Add a filter to hide expose elements in `ViewUsage`
21+
- https://github.com/eclipse-syson/syson/issues/1581[#1581] [diagrams] Redefine inherited `PortUsages` when connected to another port
2122

2223
== v2025.10.0
2324

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

Lines changed: 180 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,19 @@
1818
import com.jayway.jsonpath.JsonPath;
1919

2020
import java.time.Duration;
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
import java.util.Map;
2124
import java.util.UUID;
2225
import java.util.concurrent.atomic.AtomicReference;
2326
import java.util.function.Consumer;
2427

2528
import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramEventInput;
2629
import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramRefreshedEventPayload;
30+
import org.eclipse.sirius.components.collaborative.diagrams.dto.InvokeSingleClickOnTwoDiagramElementsToolInput;
31+
import org.eclipse.sirius.components.collaborative.diagrams.dto.InvokeSingleClickOnTwoDiagramElementsToolSuccessPayload;
32+
import org.eclipse.sirius.components.diagrams.tests.graphql.ConnectorToolsQueryRunner;
33+
import org.eclipse.sirius.components.diagrams.tests.graphql.InvokeSingleClickOnTwoDiagramElementsToolMutationRunner;
2734
import org.eclipse.sirius.components.diagrams.tests.navigation.DiagramNavigator;
2835
import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState;
2936
import org.eclipse.syson.AbstractIntegrationTests;
@@ -35,6 +42,8 @@
3542
import org.junit.jupiter.api.BeforeEach;
3643
import org.junit.jupiter.api.DisplayName;
3744
import org.junit.jupiter.api.Test;
45+
import org.junit.jupiter.params.ParameterizedTest;
46+
import org.junit.jupiter.params.provider.ValueSource;
3847
import org.springframework.beans.factory.annotation.Autowired;
3948
import org.springframework.boot.test.context.SpringBootTest;
4049
import org.springframework.test.context.jdbc.Sql;
@@ -50,6 +59,7 @@
5059
* @author frouene
5160
*/
5261
@Transactional
62+
@SuppressWarnings("checkstyle:MultipleStringLiterals")
5363
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
5464
public class GVInheritedPortTests extends AbstractIntegrationTests {
5565

@@ -59,6 +69,12 @@ public class GVInheritedPortTests extends AbstractIntegrationTests {
5969
@Autowired
6070
private IGivenDiagramSubscription givenDiagramSubscription;
6171

72+
@Autowired
73+
private ConnectorToolsQueryRunner connectorToolsQueryRunner;
74+
75+
@Autowired
76+
private InvokeSingleClickOnTwoDiagramElementsToolMutationRunner invokeSingleClickOnTwoDiagramElementsToolMutationRunner;
77+
6278
@Autowired
6379
private ShowDiagramsInheritedMembersMutationRunner showDiagramsInheritedMembersMutationRunner;
6480

@@ -105,17 +121,179 @@ public void checkInheritedPortsVisibility() {
105121
assertThat(typename).isEqualTo(ShowDiagramsInheritedMembersSuccessPayload.class.getSimpleName());
106122
};
107123

108-
Consumer<Object> updatedDiagramContentConsumerAfterInheritedVisibilityChange = assertRefreshedDiagramThat(diagram -> {
124+
Consumer<Object> updatedDiagramContentConsumerAfterUncheckInheritedVisibilityChange = assertRefreshedDiagramThat(diagram -> {
109125
var part2Node = new DiagramNavigator(diagram).nodeWithLabel("«part»\npart2").getNode();
110126
assertThat(part2Node.getBorderNodes()).hasSize(1);
111127
var v1Node = new DiagramNavigator(diagram).nodeWithLabel("«part»\nv1 : Vehicle").getNode();
112128
assertThat(v1Node.getBorderNodes()).hasSize(0);
113129
});
114130

131+
Runnable checkShowInheritedMembersFilter = () -> {
132+
var input = new ShowDiagramsInheritedMembersInput(
133+
UUID.randomUUID(),
134+
GeneralViewInheritedPortTestProjectData.EDITING_CONTEXT_ID,
135+
diagramId.get(),
136+
true);
137+
var result = this.showDiagramsInheritedMembersMutationRunner.run(input);
138+
String typename = JsonPath.read(result, "$.data.showDiagramsInheritedMembers.__typename");
139+
assertThat(typename).isEqualTo(ShowDiagramsInheritedMembersSuccessPayload.class.getSimpleName());
140+
};
141+
142+
Consumer<Object> updatedDiagramContentConsumerAfterCheckInheritedVisibilityChange = assertRefreshedDiagramThat(diagram -> {
143+
var part2Node = new DiagramNavigator(diagram).nodeWithLabel("«part»\npart2").getNode();
144+
assertThat(part2Node.getBorderNodes()).hasSize(1);
145+
var v1Node = new DiagramNavigator(diagram).nodeWithLabel("«part»\nv1 : Vehicle").getNode();
146+
assertThat(v1Node.getBorderNodes()).hasSize(1);
147+
});
148+
115149
StepVerifier.create(flux)
116150
.consumeNextWith(initialDiagramContentConsumer)
117151
.then(uncheckShowInheritedMembersFilter)
118-
.consumeNextWith(updatedDiagramContentConsumerAfterInheritedVisibilityChange)
152+
.consumeNextWith(updatedDiagramContentConsumerAfterUncheckInheritedVisibilityChange)
153+
.then(checkShowInheritedMembersFilter)
154+
.consumeNextWith(updatedDiagramContentConsumerAfterCheckInheritedVisibilityChange)
155+
.thenCancel()
156+
.verify(Duration.ofSeconds(10));
157+
}
158+
159+
@DisplayName("GIVEN a diagram with some inherited port, WHEN an edge tool is invoke from inherited port, THEN inherited port is redefined")
160+
@Sql(scripts = { GeneralViewInheritedPortTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
161+
config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
162+
@Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
163+
@ParameterizedTest
164+
@ValueSource(strings = { "New Binding Connector As Usage (bind)", "New Interface (connect)", "New Flow (flow)" })
165+
public void checkInheritedPortSourceRedefinition(String parameterizedValue) {
166+
var flux = this.givenSubscriptionToDiagram();
167+
var diagramId = new AtomicReference<String>();
168+
var port1Id = new AtomicReference<String>();
169+
var inheritedPortId = new AtomicReference<String>();
170+
171+
Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
172+
diagramId.set(diagram.getId());
173+
var part2Node = new DiagramNavigator(diagram).nodeWithLabel("«part»\npart2").getNode();
174+
assertThat(part2Node.getBorderNodes()).hasSize(1);
175+
assertThat(part2Node.getBorderNodes()).allMatch(node -> node.getOutsideLabels().get(0).text().equals("port1"));
176+
port1Id.set(part2Node.getBorderNodes().get(0).getId());
177+
var v1Node = new DiagramNavigator(diagram).nodeWithLabel("«part»\nv1 : Vehicle").getNode();
178+
assertThat(v1Node.getBorderNodes()).hasSize(1);
179+
assertThat(v1Node.getBorderNodes()).allMatch(node -> node.getOutsideLabels().get(0).text().equals("^fuelInPort : FuelPort"));
180+
inheritedPortId.set(v1Node.getBorderNodes().get(0).getId());
181+
assertThat(diagram.getEdges()).hasSize(1);
182+
});
183+
184+
Runnable triggerEdgeTool = () -> {
185+
Map<String, Object> variables = Map.of(
186+
"editingContextId", GeneralViewInheritedPortTestProjectData.EDITING_CONTEXT_ID,
187+
"representationId", diagramId.get(),
188+
"sourceDiagramElementId", inheritedPortId.get(),
189+
"targetDiagramElementId", port1Id.get()
190+
);
191+
var connectorToolsResult = this.connectorToolsQueryRunner.run(variables);
192+
List<String> ids = JsonPath.read(connectorToolsResult, String.format("$.data.viewer.editingContext.representation.description.connectorTools[?(@.label=='Redefine Port And %s')].id",
193+
parameterizedValue));
194+
String toolId = ids.get(0);
195+
196+
var createEdgeInput = new InvokeSingleClickOnTwoDiagramElementsToolInput(
197+
UUID.randomUUID(),
198+
GeneralViewInheritedPortTestProjectData.EDITING_CONTEXT_ID,
199+
diagramId.get(),
200+
inheritedPortId.get(),
201+
port1Id.get(),
202+
0,
203+
0,
204+
0,
205+
0,
206+
toolId,
207+
new ArrayList<>());
208+
var createEdgeResult = this.invokeSingleClickOnTwoDiagramElementsToolMutationRunner.run(createEdgeInput);
209+
String typename = JsonPath.read(createEdgeResult, "$.data.invokeSingleClickOnTwoDiagramElementsTool.__typename");
210+
assertThat(typename).isEqualTo(InvokeSingleClickOnTwoDiagramElementsToolSuccessPayload.class.getSimpleName());
211+
};
212+
213+
Consumer<Object> updatedDiagramContentConsumerAfterEdgeTool = assertRefreshedDiagramThat(diagram -> {
214+
var part2Node = new DiagramNavigator(diagram).nodeWithLabel("«part»\npart2").getNode();
215+
assertThat(part2Node.getBorderNodes()).hasSize(1);
216+
var v1Node = new DiagramNavigator(diagram).nodeWithLabel("«part»\nv1 : Vehicle").getNode();
217+
assertThat(v1Node.getBorderNodes()).hasSize(1);
218+
assertThat(v1Node.getBorderNodes()).allMatch(node -> node.getOutsideLabels().get(0).text().equals(" :>> fuelInPort"));
219+
assertThat(diagram.getEdges()).hasSize(2);
220+
});
221+
222+
StepVerifier.create(flux)
223+
.consumeNextWith(initialDiagramContentConsumer)
224+
.then(triggerEdgeTool)
225+
.consumeNextWith(updatedDiagramContentConsumerAfterEdgeTool)
226+
.thenCancel()
227+
.verify(Duration.ofSeconds(10));
228+
}
229+
230+
@DisplayName("GIVEN a diagram with some inherited port, WHEN an edge tool is invoke targeting an inherited port, THEN inherited port is redefined")
231+
@Sql(scripts = { GeneralViewInheritedPortTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
232+
config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
233+
@Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
234+
@ParameterizedTest
235+
@ValueSource(strings = { "New Binding Connector As Usage (bind)", "New Interface (connect)", "New Flow (flow)" })
236+
public void checkInheritedPortTargetRedefinition(String parameterizedValue) {
237+
var flux = this.givenSubscriptionToDiagram();
238+
var diagramId = new AtomicReference<String>();
239+
var port1Id = new AtomicReference<String>();
240+
var inheritedPortId = new AtomicReference<String>();
241+
242+
Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
243+
diagramId.set(diagram.getId());
244+
var part2Node = new DiagramNavigator(diagram).nodeWithLabel("«part»\npart2").getNode();
245+
assertThat(part2Node.getBorderNodes()).hasSize(1);
246+
assertThat(part2Node.getBorderNodes()).allMatch(node -> node.getOutsideLabels().get(0).text().equals("port1"));
247+
port1Id.set(part2Node.getBorderNodes().get(0).getId());
248+
var v1Node = new DiagramNavigator(diagram).nodeWithLabel("«part»\nv1 : Vehicle").getNode();
249+
assertThat(v1Node.getBorderNodes()).hasSize(1);
250+
assertThat(v1Node.getBorderNodes()).allMatch(node -> node.getOutsideLabels().get(0).text().equals("^fuelInPort : FuelPort"));
251+
inheritedPortId.set(v1Node.getBorderNodes().get(0).getId());
252+
assertThat(diagram.getEdges()).hasSize(1);
253+
});
254+
255+
Runnable triggerEdgeTool = () -> {
256+
Map<String, Object> variables = Map.of(
257+
"editingContextId", GeneralViewInheritedPortTestProjectData.EDITING_CONTEXT_ID,
258+
"representationId", diagramId.get(),
259+
"sourceDiagramElementId", port1Id.get(),
260+
"targetDiagramElementId", inheritedPortId.get()
261+
);
262+
var connectorToolsResult = this.connectorToolsQueryRunner.run(variables);
263+
List<String> ids = JsonPath.read(connectorToolsResult, String.format("$.data.viewer.editingContext.representation.description.connectorTools[?(@.label=='Redefine Port And %s')].id",
264+
parameterizedValue));
265+
String toolId = ids.get(0);
266+
267+
var createEdgeInput = new InvokeSingleClickOnTwoDiagramElementsToolInput(
268+
UUID.randomUUID(),
269+
GeneralViewInheritedPortTestProjectData.EDITING_CONTEXT_ID,
270+
diagramId.get(),
271+
port1Id.get(),
272+
inheritedPortId.get(),
273+
0,
274+
0,
275+
0,
276+
0,
277+
toolId,
278+
new ArrayList<>());
279+
var createEdgeResult = this.invokeSingleClickOnTwoDiagramElementsToolMutationRunner.run(createEdgeInput);
280+
String typename = JsonPath.read(createEdgeResult, "$.data.invokeSingleClickOnTwoDiagramElementsTool.__typename");
281+
assertThat(typename).isEqualTo(InvokeSingleClickOnTwoDiagramElementsToolSuccessPayload.class.getSimpleName());
282+
};
283+
284+
Consumer<Object> updatedDiagramContentConsumerAfterEdgeTool = assertRefreshedDiagramThat(diagram -> {
285+
var part2Node = new DiagramNavigator(diagram).nodeWithLabel("«part»\npart2").getNode();
286+
assertThat(part2Node.getBorderNodes()).hasSize(1);
287+
var v1Node = new DiagramNavigator(diagram).nodeWithLabel("«part»\nv1 : Vehicle").getNode();
288+
assertThat(v1Node.getBorderNodes()).hasSize(1);
289+
assertThat(v1Node.getBorderNodes()).allMatch(node -> node.getOutsideLabels().get(0).text().equals(" :>> fuelInPort"));
290+
assertThat(diagram.getEdges()).hasSize(2);
291+
});
292+
293+
StepVerifier.create(flux)
294+
.consumeNextWith(initialDiagramContentConsumer)
295+
.then(triggerEdgeTool)
296+
.consumeNextWith(updatedDiagramContentConsumerAfterEdgeTool)
119297
.thenCancel()
120298
.verify(Duration.ofSeconds(10));
121299
}

backend/services/syson-services/src/main/java/org/eclipse/syson/services/ToolService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ public boolean removeFromExposedElements(Element element, Node selectedNode, IEd
289289

290290
protected Node getParentNode(IDiagramElement diagramElement, Node nodeContainer) {
291291
List<Node> nodes = nodeContainer.getChildNodes();
292+
nodes.addAll(nodeContainer.getBorderNodes());
292293
if (nodes.contains(diagramElement)) {
293294
return nodeContainer;
294295
}

backend/services/syson-services/src/main/java/org/eclipse/syson/util/AQLConstants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public class AQLConstants {
2323

2424
public static final String AQL_SELF = "aql:self";
2525

26+
public static final String SELF = "self";
27+
2628
public static final String AQL_TRUE = "aql:true";
2729

2830
public static final String AQL_FALSE = "aql:false";

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

Lines changed: 15 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import java.util.List;
1616
import java.util.Objects;
1717

18-
import org.eclipse.sirius.components.diagrams.description.EdgeDescription;
1918
import org.eclipse.sirius.components.view.builder.IViewDiagramElementFinder;
2019
import org.eclipse.sirius.components.view.builder.providers.IColorProvider;
2120
import org.eclipse.sirius.components.view.diagram.ConditionalNodeStyle;
@@ -41,6 +40,8 @@
4140
*/
4241
public abstract class AbstractPortUsageBorderNodeDescriptionProvider extends AbstractNodeDescriptionProvider {
4342

43+
protected static final String REDEFINE_PORT_PREFIX_TOOL_NAME = "Redefine Port And ";
44+
4445
protected final IDescriptionNameGenerator nameGenerator;
4546

4647
public AbstractPortUsageBorderNodeDescriptionProvider(IColorProvider colorProvider, IDescriptionNameGenerator nameGenerator) {
@@ -55,6 +56,17 @@ public AbstractPortUsageBorderNodeDescriptionProvider(IColorProvider colorProvid
5556
*/
5657
protected abstract String getSemanticCandidatesExpression();
5758

59+
/**
60+
* Implementers should provide the list of edge tool descriptions used inside this {@link NodeDescription}.
61+
*
62+
* @param cache
63+
* the cache used to retrieve node descriptions.
64+
* @param nodeDescription
65+
* the actual Usage node description.
66+
* @return the list of edge tool descriptions.
67+
*/
68+
protected abstract List<EdgeTool> getEdgeTools(IViewDiagramElementFinder cache, NodeDescription nodeDescription);
69+
5870
protected abstract OutsideLabelDescription createOutsideLabelDescription();
5971

6072
protected abstract String getName();
@@ -145,56 +157,8 @@ private NodePalette createNodePalette(IViewDiagramElementFinder cache, NodeDescr
145157
.build();
146158
}
147159

148-
private List<EdgeTool> getEdgeTools(IViewDiagramElementFinder cache, NodeDescription nodeDescription) {
149-
if (cache.getNodeDescription(this.getName()).isPresent()) {
150-
NodeDescription portBorderNode = cache.getNodeDescription(this.getName()).get();
151-
return List.of(this.createBindingConnectorAsUsageEdgeTool(List.of(nodeDescription, portBorderNode)),
152-
this.createInterfaceUsageEdgeTool(List.of(nodeDescription, portBorderNode)),
153-
this.createFlowUsageEdgeTool(List.of(nodeDescription, portBorderNode)));
154-
}
155-
return List.of();
156-
}
157-
158-
private EdgeTool createBindingConnectorAsUsageEdgeTool(List<NodeDescription> targetElementDescriptions) {
159-
var builder = this.diagramBuilderHelper.newEdgeTool();
160-
161-
var body = this.viewBuilderHelper.newChangeContext()
162-
.expression(AQLUtils.getServiceCallExpression(EdgeDescription.SEMANTIC_EDGE_SOURCE, "createBindingConnectorAsUsage", EdgeDescription.SEMANTIC_EDGE_TARGET));
163-
164-
return builder
165-
.name(this.nameGenerator.getCreationToolName(SysmlPackage.eINSTANCE.getBindingConnectorAsUsage()) + " (bind)")
166-
.iconURLsExpression("/icons/full/obj16/" + SysmlPackage.eINSTANCE.getBindingConnectorAsUsage().getName() + ".svg")
167-
.body(body.build())
168-
.targetElementDescriptions(targetElementDescriptions.toArray(NodeDescription[]::new))
169-
.build();
170-
}
171-
172-
private EdgeTool createInterfaceUsageEdgeTool(List<NodeDescription> targetElementDescriptions) {
173-
var builder = this.diagramBuilderHelper.newEdgeTool();
174-
175-
var body = this.viewBuilderHelper.newChangeContext()
176-
.expression(AQLUtils.getServiceCallExpression(EdgeDescription.SEMANTIC_EDGE_SOURCE, "createInterfaceUsage", EdgeDescription.SEMANTIC_EDGE_TARGET));
177-
178-
return builder
179-
.name(this.nameGenerator.getCreationToolName(SysmlPackage.eINSTANCE.getInterfaceUsage()) + " (connect)")
180-
.iconURLsExpression("/icons/full/obj16/" + SysmlPackage.eINSTANCE.getInterfaceUsage().getName() + ".svg")
181-
.body(body.build())
182-
.targetElementDescriptions(targetElementDescriptions.toArray(NodeDescription[]::new))
183-
.build();
184-
}
185-
186-
private EdgeTool createFlowUsageEdgeTool(List<NodeDescription> targetElementDescriptions) {
187-
var builder = this.diagramBuilderHelper.newEdgeTool();
188-
189-
var body = this.viewBuilderHelper.newChangeContext()
190-
.expression(AQLUtils.getServiceCallExpression(EdgeDescription.SEMANTIC_EDGE_SOURCE, "createFlowUsage", EdgeDescription.SEMANTIC_EDGE_TARGET));
191-
192-
return builder
193-
.name(this.nameGenerator.getCreationToolName(SysmlPackage.eINSTANCE.getFlowUsage()) + " (flow)")
194-
.iconURLsExpression("/icons/full/obj16/" + SysmlPackage.eINSTANCE.getFlowUsage().getName() + ".svg")
195-
.body(body.build())
196-
.targetElementDescriptions(targetElementDescriptions.toArray(NodeDescription[]::new))
197-
.build();
160+
protected String getToolIconURLsExpression(String elementName) {
161+
return "/icons/full/obj16/" + elementName + ".svg";
198162
}
199163

200164
}

0 commit comments

Comments
 (0)