Skip to content

Commit c77bf4d

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 c77bf4d

14 files changed

Lines changed: 437 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: 179 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;
@@ -59,6 +68,12 @@ public class GVInheritedPortTests extends AbstractIntegrationTests {
5968
@Autowired
6069
private IGivenDiagramSubscription givenDiagramSubscription;
6170

71+
@Autowired
72+
private ConnectorToolsQueryRunner connectorToolsQueryRunner;
73+
74+
@Autowired
75+
private InvokeSingleClickOnTwoDiagramElementsToolMutationRunner invokeSingleClickOnTwoDiagramElementsToolMutationRunner;
76+
6277
@Autowired
6378
private ShowDiagramsInheritedMembersMutationRunner showDiagramsInheritedMembersMutationRunner;
6479

@@ -105,17 +120,179 @@ public void checkInheritedPortsVisibility() {
105120
assertThat(typename).isEqualTo(ShowDiagramsInheritedMembersSuccessPayload.class.getSimpleName());
106121
};
107122

108-
Consumer<Object> updatedDiagramContentConsumerAfterInheritedVisibilityChange = assertRefreshedDiagramThat(diagram -> {
123+
Consumer<Object> updatedDiagramContentConsumerAfterUncheckInheritedVisibilityChange = assertRefreshedDiagramThat(diagram -> {
109124
var part2Node = new DiagramNavigator(diagram).nodeWithLabel("«part»\npart2").getNode();
110125
assertThat(part2Node.getBorderNodes()).hasSize(1);
111126
var v1Node = new DiagramNavigator(diagram).nodeWithLabel("«part»\nv1 : Vehicle").getNode();
112127
assertThat(v1Node.getBorderNodes()).hasSize(0);
113128
});
114129

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

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)