Skip to content

Commit c0542ea

Browse files
gcoutableAxelRICHARD
authored andcommitted
[2260] Add edge tools to create 'assume' and 'require' relations
Bug: #2260 Signed-off-by: Guillaume Coutable <guillaume.coutable@obeo.fr>
1 parent bb3cc9a commit c0542ea

7 files changed

Lines changed: 269 additions & 1 deletion

File tree

CHANGELOG.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ This is currently supported on `Features` (e.g. `Attribute`), `Constraints` and
5555
It leverages the selection dialog to either create an _occurrence timeslice/snapshot_, or the _usage timeslice/snapshot_ matching the `OccurrenceDefinition` on which the tool is applied.
5656
- https://github.com/eclipse-syson/syson/issues/2250[#2250] [diagrams] Leverage the selection dialog to improve the graphical node tools creating a _require_ `ConstraintUsage`, or an _assume_ `ConstraintUsage`, from `RequirementUsage` and `RequirementDefinition` graphical nodes.
5757
- https://github.com/eclipse-syson/syson/issues/2254[#2254] [diagrams] Add the support for _assume_ and _require_ graphical edges.
58+
- https://github.com/eclipse-syson/syson/issues/2260[#2260] [diagrams] Add the _New Assume Constraint_ or _New Require Constraint_ edge tools to create _assume_ and _require_ graphical edges.
5859

5960
== v2026.5.0
6061

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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.general.view;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.assertj.core.api.InstanceOfAssertFactories.type;
17+
import static org.eclipse.sirius.components.diagrams.tests.DiagramEventPayloadConsumer.assertRefreshedDiagramThat;
18+
import static org.eclipse.sirius.components.diagrams.tests.assertions.DiagramInstanceOfAssertFactories.EDGE;
19+
import static org.eclipse.sirius.components.diagrams.tests.assertions.DiagramInstanceOfAssertFactories.EDGE_STYLE;
20+
21+
import java.time.Duration;
22+
import java.util.List;
23+
import java.util.UUID;
24+
import java.util.concurrent.atomic.AtomicReference;
25+
import java.util.function.Consumer;
26+
27+
import org.eclipse.emf.ecore.EClass;
28+
import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramEventInput;
29+
import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramRefreshedEventPayload;
30+
import org.eclipse.sirius.components.core.api.IObjectSearchService;
31+
import org.eclipse.sirius.components.diagrams.ArrowStyle;
32+
import org.eclipse.sirius.components.diagrams.Diagram;
33+
import org.eclipse.sirius.components.diagrams.Edge;
34+
import org.eclipse.sirius.components.diagrams.Label;
35+
import org.eclipse.sirius.components.diagrams.ViewModifier;
36+
import org.eclipse.sirius.components.view.emf.diagram.IDiagramIdProvider;
37+
import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState;
38+
import org.eclipse.syson.AbstractIntegrationTests;
39+
import org.eclipse.syson.GivenSysONServer;
40+
import org.eclipse.syson.application.controller.editingcontext.checkers.SemanticCheckerService;
41+
import org.eclipse.syson.application.controllers.diagrams.testers.EdgeCreationTester;
42+
import org.eclipse.syson.application.data.GeneralViewWithTopNodesTestProjectData;
43+
import org.eclipse.syson.services.SemanticRunnableFactory;
44+
import org.eclipse.syson.services.diagrams.DiagramComparator;
45+
import org.eclipse.syson.services.diagrams.DiagramDescriptionIdProvider;
46+
import org.eclipse.syson.services.diagrams.api.IGivenDiagramDescription;
47+
import org.eclipse.syson.services.diagrams.api.IGivenDiagramSubscription;
48+
import org.eclipse.syson.standard.diagrams.view.SDVDescriptionNameGenerator;
49+
import org.eclipse.syson.sysml.RequirementConstraintMembership;
50+
import org.eclipse.syson.sysml.SysmlPackage;
51+
import org.eclipse.syson.sysml.metamodel.helper.LabelConstants;
52+
import org.eclipse.syson.util.IDescriptionNameGenerator;
53+
import org.eclipse.syson.util.SysONRepresentationDescriptionIdentifiers;
54+
import org.junit.jupiter.api.BeforeEach;
55+
import org.junit.jupiter.api.DisplayName;
56+
import org.junit.jupiter.api.Test;
57+
import org.springframework.beans.factory.annotation.Autowired;
58+
import org.springframework.boot.test.context.SpringBootTest;
59+
import org.springframework.transaction.annotation.Transactional;
60+
61+
import reactor.core.publisher.Flux;
62+
import reactor.test.StepVerifier;
63+
64+
/**
65+
* {@link org.eclipse.syson.sysml.RequirementConstraintMembership} related test in General View.
66+
*
67+
* @author gcoutable
68+
*/
69+
@Transactional
70+
@GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH })
71+
@SuppressWarnings("checkstyle:MultipleStringLiterals")
72+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
73+
public class GVRequirementConstraintMembershipTests extends AbstractIntegrationTests {
74+
75+
@Autowired
76+
private IGivenInitialServerState givenInitialServerState;
77+
78+
@Autowired
79+
private IGivenDiagramSubscription givenDiagramSubscription;
80+
81+
@Autowired
82+
private IGivenDiagramDescription givenDiagramDescription;
83+
84+
@Autowired
85+
private IDiagramIdProvider diagramIdProvider;
86+
87+
@Autowired
88+
private DiagramComparator diagramComparator;
89+
90+
@Autowired
91+
private SemanticRunnableFactory semanticRunnableFactory;
92+
93+
@Autowired
94+
private IObjectSearchService objectSearchService;
95+
96+
@Autowired
97+
private EdgeCreationTester edgeCreationTester;
98+
99+
private final IDescriptionNameGenerator descriptionNameGenerator = new SDVDescriptionNameGenerator();
100+
101+
private SemanticCheckerService semanticCheckerService;
102+
103+
private Flux<DiagramRefreshedEventPayload> givenSubscriptionToDiagram() {
104+
var diagramEventInput = new DiagramEventInput(UUID.randomUUID(), GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, GeneralViewWithTopNodesTestProjectData.GraphicalIds.DIAGRAM_ID);
105+
return this.givenDiagramSubscription.subscribe(diagramEventInput);
106+
}
107+
108+
@BeforeEach
109+
public void setUp() {
110+
this.givenInitialServerState.initialize();
111+
this.semanticCheckerService = new SemanticCheckerService(this.semanticRunnableFactory, this.objectSearchService, GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID,
112+
GeneralViewWithTopNodesTestProjectData.SemanticIds.PACKAGE_1_ID);
113+
}
114+
115+
@DisplayName("GIVEN the general view diagram, WHEN the assume edge tool between a RequirementUsage graphical node and a ConstraintUsage graphical node is used, THEN it creates an assume edge between both elements")
116+
@Test
117+
public void testCreateAssumeConstraintWithEdgeToolBetweenRequirementUsageAndConstraintUsage() {
118+
this.createRequirementConstraintMembershipWithEdgeTool(SysmlPackage.eINSTANCE.getRequirementUsage(), "New Assume Constraint", GeneralViewWithTopNodesTestProjectData.GraphicalIds.REQUIREMENT_USAGE_ID, LabelConstants.OPEN_QUOTE + LabelConstants.ASSUME + LabelConstants.CLOSE_QUOTE, "requirement");
119+
}
120+
121+
@DisplayName("GIVEN the general view diagram, WHEN the assume edge tool between a RequirementDefinition graphical node and a ConstraintUsage graphical node is used, THEN it creates an assume edge between both elements")
122+
@Test
123+
public void testCreateAssumeConstraintWithEdgeToolBetweenRequirementDefinitionAndConstraintUsage() {
124+
this.createRequirementConstraintMembershipWithEdgeTool(SysmlPackage.eINSTANCE.getRequirementDefinition(), "New Assume Constraint", GeneralViewWithTopNodesTestProjectData.GraphicalIds.REQUIREMENT_DEFINITION_ID, LabelConstants.OPEN_QUOTE + LabelConstants.ASSUME + LabelConstants.CLOSE_QUOTE, "RequirementDefinition");
125+
}
126+
127+
@DisplayName("GIVEN the general view diagram, WHEN the require edge tool between a RequirementUsage graphical node and a ConstraintUsage graphical node is used, THEN it creates a require edge between both elements")
128+
@Test
129+
public void testCreateRequireConstraintWithEdgeToolBetweenRequirementUsageAndConstraintUsage() {
130+
this.createRequirementConstraintMembershipWithEdgeTool(SysmlPackage.eINSTANCE.getRequirementUsage(), "New Require Constraint", GeneralViewWithTopNodesTestProjectData.GraphicalIds.REQUIREMENT_USAGE_ID, LabelConstants.OPEN_QUOTE + LabelConstants.REQUIRE + LabelConstants.CLOSE_QUOTE, "requirement");
131+
}
132+
133+
@DisplayName("GIVEN the general view diagram, WHEN the require edge tool between a RequirementDefinition graphical node and a ConstraintUsage graphical node is used, THEN it creates a require edge between both elements")
134+
@Test
135+
public void testCreateRequireConstraintWithEdgeToolBetweenRequirementDefinitionAndConstraintUsage() {
136+
this.createRequirementConstraintMembershipWithEdgeTool(SysmlPackage.eINSTANCE.getRequirementDefinition(), "New Require Constraint", GeneralViewWithTopNodesTestProjectData.GraphicalIds.REQUIREMENT_DEFINITION_ID, LabelConstants.OPEN_QUOTE + LabelConstants.REQUIRE + LabelConstants.CLOSE_QUOTE, "RequirementDefinition");
137+
}
138+
139+
private void createRequirementConstraintMembershipWithEdgeTool(EClass parentEClass, String toolName, String graphicalSourceNodeId, String expectedEdgeLabel, String parentLabel) {
140+
var flux = this.givenSubscriptionToDiagram();
141+
142+
var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID,
143+
SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID);
144+
var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider);
145+
146+
var edgeCreationToolId = diagramDescriptionIdProvider.getEdgeCreationToolId(
147+
this.descriptionNameGenerator.getNodeName(parentEClass),
148+
toolName);
149+
150+
AtomicReference<Diagram> diagram = new AtomicReference<>();
151+
Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set);
152+
153+
Runnable createEdgeRunnable = () -> this.edgeCreationTester.createEdgeUsingNodeId(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID,
154+
diagram,
155+
graphicalSourceNodeId,
156+
GeneralViewWithTopNodesTestProjectData.GraphicalIds.CONSTRAINT_USAGE_ID,
157+
edgeCreationToolId);
158+
159+
Consumer<Object> diagramContentConsumerAfterNewEdge = assertRefreshedDiagramThat(newDiagram -> {
160+
var newEdges = this.diagramComparator.newEdges(diagram.get(), newDiagram);
161+
var newVisibleEdges = newEdges.stream().filter(edge -> edge.getState() != ViewModifier.Hidden).toList();
162+
163+
assertThat(newVisibleEdges).hasSize(1).first(EDGE)
164+
.hasSourceId(graphicalSourceNodeId)
165+
.hasTargetId(GeneralViewWithTopNodesTestProjectData.GraphicalIds.CONSTRAINT_USAGE_ID)
166+
.extracting(Edge::getCenterLabel)
167+
.extracting(Label::text)
168+
.hasToString(expectedEdgeLabel);
169+
170+
assertThat(newVisibleEdges).hasSize(1).first(EDGE)
171+
.extracting(Edge::getStyle, EDGE_STYLE)
172+
.hasSourceArrow(ArrowStyle.None)
173+
.hasTargetArrow(ArrowStyle.InputArrow);
174+
});
175+
176+
Consumer<Object> additionalCheck = object -> {
177+
assertThat(object).isInstanceOf(List.class)
178+
.asInstanceOf(type(List.class))
179+
.satisfies(requirementConstraintMemberships -> {
180+
assertThat((List<?>) requirementConstraintMemberships).size().isEqualTo(1);
181+
assertThat(requirementConstraintMemberships.getFirst())
182+
.isInstanceOf(RequirementConstraintMembership.class)
183+
.asInstanceOf(type(RequirementConstraintMembership.class))
184+
.satisfies(requirementConstraintMembership -> {
185+
var ownedConstraint = requirementConstraintMembership.getOwnedConstraint();
186+
var ownedSubsetting = ownedConstraint.getOwnedSubsetting();
187+
assertThat(ownedSubsetting).isNotEmpty();
188+
assertThat(ownedSubsetting.getFirst().getSubsettedFeature()).isEqualTo(requirementConstraintMembership.getReferencedConstraint());
189+
});
190+
});
191+
};
192+
193+
Runnable semanticCheck = this.semanticCheckerService.checkEditingContext(this.semanticCheckerService.getElementInParentSemanticChecker(parentLabel, SysmlPackage.eINSTANCE.getElement_OwnedRelationship(), SysmlPackage.eINSTANCE.getRequirementConstraintMembership(), additionalCheck));
194+
195+
StepVerifier.create(flux)
196+
.consumeNextWith(initialDiagramContentConsumer)
197+
.then(createEdgeRunnable)
198+
.consumeNextWith(diagramContentConsumerAfterNewEdge)
199+
.then(semanticCheck)
200+
.thenCancel()
201+
.verify(Duration.ofSeconds(10));
202+
}
203+
}

backend/application/syson-application/src/test/java/org/eclipse/syson/application/data/GeneralViewWithTopNodesTestProjectData.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public static class GraphicalIds {
3838

3939
public static final String CONCERN_USAGE_ID = "0999b8c3-d37c-3644-a1d6-b9777a499d11";
4040

41+
public static final String CONSTRAINT_USAGE_ID = "22da3b61-32f6-346e-9deb-7dd1f64bfd69";
42+
4143
public static final String DIAGRAM_ID = "fa8c8a8d-2813-404c-876f-c04e8b297134";
4244

4345
public static final String ITEM_DEFINITION_ID = "df3542d9-6314-3da5-993c-a296f4042674";

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.eclipse.syson.diagram.services.aql.DiagramMutationAQLService;
3232
import org.eclipse.syson.diagram.services.aql.DiagramQueryAQLService;
3333
import org.eclipse.syson.model.services.aql.ModelMutationAQLService;
34+
import org.eclipse.syson.sysml.RequirementConstraintKind;
3435
import org.eclipse.syson.sysml.SysmlPackage;
3536
import org.eclipse.syson.sysml.Usage;
3637
import org.eclipse.syson.util.AQLConstants;
@@ -682,4 +683,56 @@ public EdgeTool createFramedConcernEdgeTool() {
682683
.targetElementDescriptions(targetNodeDescriptions.toArray(NodeDescription[]::new))
683684
.build();
684685
}
686+
687+
public EdgeTool createAssumeConstraintEdgeTool() {
688+
var builder = this.diagramBuilderHelper.newEdgeTool();
689+
var body = this.viewBuilderHelper.newChangeContext()
690+
.expression(ServiceMethod.of2(ModelMutationAQLService::createConstraint).aql(EdgeDescription.SEMANTIC_EDGE_SOURCE, EdgeDescription.SEMANTIC_EDGE_TARGET, "assumptionConstraint"));
691+
692+
var targetNodeDescriptions = new ArrayList<NodeDescription>();
693+
var constraintUsageNodeDescriptionName = this.nameGenerator.getNodeName(SysmlPackage.eINSTANCE.getConstraintUsage());
694+
this.allNodeDescriptions.stream()
695+
.filter(nd -> constraintUsageNodeDescriptionName.equals(nd.getName()))
696+
.findFirst()
697+
.ifPresent(targetNodeDescriptions::add);
698+
699+
String requirementConstraintKindType = SysMLMetamodelHelper.buildQualifiedName(SysmlPackage.eINSTANCE.getRequirementConstraintKind());
700+
var letRequirementConstraintLiteral = this.viewBuilderHelper.newLet()
701+
.variableName("assumptionConstraint")
702+
.valueExpression(AQLConstants.AQL + requirementConstraintKindType + ".getEEnumLiteral('" + RequirementConstraintKind.ASSUMPTION.getLiteral() + "').instance")
703+
.children(body.build());
704+
705+
return builder
706+
.name(this.nameGenerator.getCreationToolName("New Assume ", SysmlPackage.eINSTANCE.getConstraintUsage()))
707+
.iconURLsExpression(METAMODEL_ICONS_PATH + SysmlPackage.eINSTANCE.getRequirementConstraintMembership().getName() + SVG)
708+
.body(letRequirementConstraintLiteral.build())
709+
.targetElementDescriptions(targetNodeDescriptions.toArray(NodeDescription[]::new))
710+
.build();
711+
}
712+
713+
public EdgeTool createRequireConstraintEdgeTool() {
714+
var builder = this.diagramBuilderHelper.newEdgeTool();
715+
var body = this.viewBuilderHelper.newChangeContext()
716+
.expression(ServiceMethod.of2(ModelMutationAQLService::createConstraint).aql(EdgeDescription.SEMANTIC_EDGE_SOURCE, EdgeDescription.SEMANTIC_EDGE_TARGET, "requirementConstraint"));
717+
718+
var targetNodeDescriptions = new ArrayList<NodeDescription>();
719+
var constraintUsageNodeDescriptionName = this.nameGenerator.getNodeName(SysmlPackage.eINSTANCE.getConstraintUsage());
720+
this.allNodeDescriptions.stream()
721+
.filter(nd -> constraintUsageNodeDescriptionName.equals(nd.getName()))
722+
.findFirst()
723+
.ifPresent(targetNodeDescriptions::add);
724+
725+
String requirementConstraintKindType = SysMLMetamodelHelper.buildQualifiedName(SysmlPackage.eINSTANCE.getRequirementConstraintKind());
726+
var letRequirementConstraintLiteral = this.viewBuilderHelper.newLet()
727+
.variableName("requirementConstraint")
728+
.valueExpression(AQLConstants.AQL + requirementConstraintKindType + ".getEEnumLiteral('" + RequirementConstraintKind.REQUIREMENT.getLiteral() + "').instance")
729+
.children(body.build());
730+
731+
return builder
732+
.name(this.nameGenerator.getCreationToolName("New Require ", SysmlPackage.eINSTANCE.getConstraintUsage()))
733+
.iconURLsExpression(METAMODEL_ICONS_PATH + SysmlPackage.eINSTANCE.getRequirementConstraintMembership().getName() + SVG)
734+
.body(letRequirementConstraintLiteral.build())
735+
.targetElementDescriptions(targetNodeDescriptions.toArray(NodeDescription[]::new))
736+
.build();
737+
}
685738
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ public List<EdgeTool> casePortUsage(PortUsage object) {
258258
public List<EdgeTool> caseRequirementDefinition(RequirementDefinition object) {
259259
var edgeTools = new ArrayList<EdgeTool>();
260260
edgeTools.add(this.edgeToolService.createFramedConcernEdgeTool());
261+
edgeTools.add(this.edgeToolService.createAssumeConstraintEdgeTool());
262+
edgeTools.add(this.edgeToolService.createRequireConstraintEdgeTool());
261263
edgeTools.addAll(this.caseDefinition(object));
262264
return edgeTools;
263265
}
@@ -277,6 +279,8 @@ public List<EdgeTool> caseRequirementUsage(RequirementUsage object) {
277279
.toList();
278280
edgeTools.add(this.edgeToolService.createBecomeObjectiveRequirementEdgeTool(objectiveTargets));
279281
edgeTools.add(this.edgeToolService.createFramedConcernEdgeTool());
282+
edgeTools.add(this.edgeToolService.createAssumeConstraintEdgeTool());
283+
edgeTools.add(this.edgeToolService.createRequireConstraintEdgeTool());
280284
edgeTools.addAll(this.caseUsage(object));
281285
return edgeTools;
282286
}
42.7 KB
Loading

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,15 @@ image::release-notes-frames-compartment.png[frames compartment displaying concer
3434
It leverages the selection dialog to either create an _occurrence timeslice/snapshot_, or the _usage timeslice/snapshot_ matching the `OccurrenceDefinition` on which the tool is applied.
3535

3636
** Add the support for _assume_ and _require_ graphical edges.
37-
These edges are displayed using the tool to create a _require_ `ConstraintUsage`, or an _assume_ `ConstraintUsage`, from `RequirementUsage` and `RequirementDefinition` graphical nodes, when another `ConstraintUsage` is selected.
37+
These graphical edges are displayed using the tool to create a _require_ `ConstraintUsage`, or an _assume_ `ConstraintUsage`, from `RequirementUsage` and `RequirementDefinition` graphical nodes, when another `ConstraintUsage` is selected.
38+
These graphical edges can also be created using the _New Assume Constraint_ or _New Require Constraint_ graphical edge tools.
3839
+
3940
image::release-notes-assume-and-require-edges.png[assume and require edge between a RequirementDefinition and two different ConstraintUsage, width=60%,height=60%]
4041

42+
** Add the _New Assume Constraint_ or _New Require Constraint_ edge tools to create _assume_ and _require_ graphical edges.
43+
+
44+
image::release-notes-assume-and-require-edge-tools.png[list available edge tools containing the two new tools, between a RequirementUsage and a ConstraintUsage, width=60%,height=60%]
45+
4146
* In the _Explorer_ view:
4247

4348
** The tree items corresponding to the internals of `Expression` elements (syntax tree) are now hidden by default.

0 commit comments

Comments
 (0)