diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 142112460..3b5cef437 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -82,6 +82,8 @@ The cache holding standard libraries can be invalidated for a specific test meth - https://github.com/eclipse-syson/syson/issues/2170[#2170] [diagrams] Improve the _Add existing elements_ diagram tool on graphical nodes to support multi-selection in standard diagrams. - https://github.com/eclipse-syson/syson/issues/2148[#2148] [diagrams] Merge the two perform action creation tools into a single tool, leveraging the updated selection dialog. - https://github.com/eclipse-syson/syson/issues/2152[#2152] [diagrams] Leverage the latest selection dialog changes to allow creating a sub action with and without associating the sub action with another `ActionUsage`. +- https://github.com/eclipse-syson/syson/issues/2161[#2161] [diagrams] Leverage the latest selection dialog changes to optionally allow creating a `SatisfyRequirement` graphical node, either standalone, feature typed, or subsetting a `RequirementUsage` by reference. +Also use that same tool in the `interconnection` compartment. === New features diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/checkers/CheckNodeOnDiagram.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/checkers/CheckNodeOnDiagram.java index c57930a39..8df021567 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/checkers/CheckNodeOnDiagram.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/checkers/CheckNodeOnDiagram.java @@ -63,6 +63,7 @@ public CheckNodeOnDiagram hasVisibleCompartmentCount(int expectedVisibleCompartm return this; } + @Deprecated public CheckNodeOnDiagram hasTargetObjectLabel(String expectedTargetObjectLabel) { this.targetObjectLabel = Objects.requireNonNull(expectedTargetObjectLabel); return this; diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSatisfyRequirementTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSatisfyRequirementTests.java index 87599cd81..93d1e5f22 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSatisfyRequirementTests.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSatisfyRequirementTests.java @@ -25,6 +25,8 @@ import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramEventInput; import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramRefreshedEventPayload; +import org.eclipse.sirius.components.core.api.IIdentityService; +import org.eclipse.sirius.components.core.api.IObjectSearchService; import org.eclipse.sirius.components.diagrams.ArrowStyle; import org.eclipse.sirius.components.diagrams.Diagram; import org.eclipse.sirius.components.diagrams.Edge; @@ -36,12 +38,17 @@ import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState; import org.eclipse.syson.AbstractIntegrationTests; import org.eclipse.syson.GivenSysONServer; +import org.eclipse.syson.application.controller.editingcontext.checkers.SemanticCheckerService; +import org.eclipse.syson.application.controllers.diagrams.checkers.CheckDiagramElementCount; +import org.eclipse.syson.application.controllers.diagrams.checkers.CheckNodeOnDiagram; import org.eclipse.syson.application.controllers.diagrams.testers.EdgeCreationTester; import org.eclipse.syson.application.controllers.diagrams.testers.EdgeReconnectionTester; import org.eclipse.syson.application.controllers.diagrams.testers.ToolTester; import org.eclipse.syson.application.data.GeneralViewWithTopNodesTestProjectData; +import org.eclipse.syson.services.SemanticRunnableFactory; import org.eclipse.syson.services.diagrams.DiagramComparator; import org.eclipse.syson.services.diagrams.DiagramDescriptionIdProvider; +import org.eclipse.syson.services.diagrams.NodeCreationTestsService; import org.eclipse.syson.services.diagrams.api.IGivenDiagramDescription; import org.eclipse.syson.services.diagrams.api.IGivenDiagramSubscription; import org.eclipse.syson.standard.diagrams.view.SDVDescriptionNameGenerator; @@ -66,6 +73,7 @@ * @author arichard */ @Transactional +@SuppressWarnings("checkstyle:MultipleStringLiterals") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class GVSatisfyRequirementTests extends AbstractIntegrationTests { @@ -84,6 +92,15 @@ public class GVSatisfyRequirementTests extends AbstractIntegrationTests { @Autowired private DiagramComparator diagramComparator; + @Autowired + private SemanticRunnableFactory semanticRunnableFactory; + + @Autowired + private IObjectSearchService objectSearchService; + + @Autowired + private IIdentityService identityService; + @Autowired private ToolTester toolTester; @@ -93,8 +110,12 @@ public class GVSatisfyRequirementTests extends AbstractIntegrationTests { @Autowired private EdgeReconnectionTester edgeReconnectionTester; + private NodeCreationTestsService creationTestsService; + private final IDescriptionNameGenerator descriptionNameGenerator = new SDVDescriptionNameGenerator(); + private SemanticCheckerService semanticCheckerService; + private Flux givenSubscriptionToDiagram() { var diagramEventInput = new DiagramEventInput(UUID.randomUUID(), GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, GeneralViewWithTopNodesTestProjectData.GraphicalIds.DIAGRAM_ID); return this.givenDiagramSubscription.subscribe(diagramEventInput); @@ -103,6 +124,255 @@ private Flux givenSubscriptionToDiagram() { @BeforeEach public void setUp() { this.givenInitialServerState.initialize(); + this.creationTestsService = new NodeCreationTestsService(this.toolTester, this.descriptionNameGenerator, GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID); + this.semanticCheckerService = new SemanticCheckerService(this.semanticRunnableFactory, this.objectSearchService, GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, + GeneralViewWithTopNodesTestProjectData.SemanticIds.PACKAGE_1_ID); + } + + @DisplayName("Given a Part, WHEN applying the 'New Satisfy' tool selecting a requirement, THEN the created satisfy requirement is subsetted by reference by the selected requirement") + @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) + @Test + public void testCreateSatisfy() { + var flux = this.givenSubscriptionToDiagram(); + + var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, + SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID); + var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider); + + AtomicReference diagram = new AtomicReference<>(); + AtomicReference createdSemanticId = new AtomicReference<>(); + + Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set); + + Runnable createNodeRunnable = this.creationTestsService.createNodeWithSelectionDialogWithSingleSelection(diagramDescriptionIdProvider, diagram, SysmlPackage.eINSTANCE.getPartUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.PART_USAGE_ID, "New Satisfy", GeneralViewWithTopNodesTestProjectData.SemanticIds.REQUIREMENT_USAGE_ID); + + Consumer diagramCheck = assertRefreshedDiagramThat(newDiagram -> { + var initialDiagram = diagram.get(); + new CheckDiagramElementCount(this.diagramComparator) + .hasNewEdgeCount(3) + .hasNewNodeCount(10) + .hasNewBorderNodeCount(0) + .check(initialDiagram, newDiagram); + + String newNodeDescriptionName = this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getSatisfyRequirementUsage()); + + new CheckNodeOnDiagram(diagramDescriptionIdProvider, this.diagramComparator) + .hasNodeDescriptionName(newNodeDescriptionName) + .hasTotalCompartmentCount(8) + .hasVisibleCompartmentCount(0) + .check(initialDiagram, newDiagram); + + String newNodeDescriptionId = diagramDescriptionIdProvider.getNodeDescriptionId(newNodeDescriptionName); + var newNodes = this.diagramComparator.newNodes(initialDiagram, newDiagram); + var createdNode = newNodes.stream() + .filter(node -> node.getDescriptionId().equals(newNodeDescriptionId)) + .findFirst(); + assertThat(createdNode).isPresent(); + createdSemanticId.set(createdNode.get().getTargetObjectId()); + }); + + Runnable semanticCheck = this.semanticCheckerService.checkElement(SatisfyRequirementUsage.class, createdSemanticId::get, (satisfyRequirement) -> { + var featureMembership = satisfyRequirement.eContainer(); + assertThat(featureMembership.eClass()).isEqualTo(SysmlPackage.eINSTANCE.getFeatureMembership()); + + var partParent = featureMembership.eContainer(); + assertThat(partParent.eClass()).isEqualTo(SysmlPackage.eINSTANCE.getPartUsage()); + assertThat(this.identityService.getId(partParent)).isEqualTo(GeneralViewWithTopNodesTestProjectData.SemanticIds.PART_USAGE_ID); + assertThat(this.identityService.getId(satisfyRequirement.getSatisfiedRequirement())).isEqualTo(GeneralViewWithTopNodesTestProjectData.SemanticIds.REQUIREMENT_USAGE_ID); + }); + + StepVerifier.create(flux) + .consumeNextWith(initialDiagramContentConsumer) + .then(createNodeRunnable) + .consumeNextWith(diagramCheck) + .then(semanticCheck) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } + + @DisplayName("Given a Part, WHEN applying the 'New Satisfy Requirement' tool selecting a requirement, THEN the created satisfy requirement is subsetted by reference by the selected requirement") + @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) + @Test + public void testCreateSatisfyRequirementSelectingRequirementUsage() { + var flux = this.givenSubscriptionToDiagram(); + + var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, + SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID); + var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider); + + AtomicReference diagram = new AtomicReference<>(); + AtomicReference createdSemanticId = new AtomicReference<>(); + + Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set); + + Runnable createNodeRunnable = this.creationTestsService.createNodeWithSelectionDialogWithSingleSelection(diagramDescriptionIdProvider, diagram, SysmlPackage.eINSTANCE.getPartUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.PART_USAGE_ID, "New Satisfy Requirement", GeneralViewWithTopNodesTestProjectData.SemanticIds.REQUIREMENT_USAGE_ID); + + Consumer diagramCheck = assertRefreshedDiagramThat(newDiagram -> { + var initialDiagram = diagram.get(); + new CheckDiagramElementCount(this.diagramComparator) + .hasNewEdgeCount(3) + .hasNewNodeCount(10) + .hasNewBorderNodeCount(0) + .check(initialDiagram, newDiagram); + + String newNodeDescriptionName = this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getSatisfyRequirementUsage()); + + new CheckNodeOnDiagram(diagramDescriptionIdProvider, this.diagramComparator) + .hasNodeDescriptionName(newNodeDescriptionName) + .hasTotalCompartmentCount(8) + .hasVisibleCompartmentCount(0) + .check(initialDiagram, newDiagram); + + String newNodeDescriptionId = diagramDescriptionIdProvider.getNodeDescriptionId(newNodeDescriptionName); + var newNodes = this.diagramComparator.newNodes(initialDiagram, newDiagram); + var createdNode = newNodes.stream() + .filter(node -> node.getDescriptionId().equals(newNodeDescriptionId)) + .findFirst(); + assertThat(createdNode).isPresent(); + createdSemanticId.set(createdNode.get().getTargetObjectId()); + }); + + Runnable semanticCheck = this.semanticCheckerService.checkElement(SatisfyRequirementUsage.class, createdSemanticId::get, (satisfyRequirement) -> { + var featureMembership = satisfyRequirement.eContainer(); + assertThat(featureMembership.eClass()).isEqualTo(SysmlPackage.eINSTANCE.getFeatureMembership()); + + var partParent = featureMembership.eContainer(); + assertThat(partParent.eClass()).isEqualTo(SysmlPackage.eINSTANCE.getPartUsage()); + assertThat(this.identityService.getId(partParent)).isEqualTo(GeneralViewWithTopNodesTestProjectData.SemanticIds.PART_USAGE_ID); + assertThat(this.identityService.getId(satisfyRequirement.getSatisfiedRequirement())).isEqualTo(GeneralViewWithTopNodesTestProjectData.SemanticIds.REQUIREMENT_USAGE_ID); + }); + + StepVerifier.create(flux) + .consumeNextWith(initialDiagramContentConsumer) + .then(createNodeRunnable) + .consumeNextWith(diagramCheck) + .then(semanticCheck) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } + + @DisplayName("Given a Part, WHEN applying the 'New Satisfy Requirement' tool selecting a Definition, THEN the created satisfy requirement is typed by the selected Definition") + @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) + @Test + public void testCreateSatisfyRequirementSelectingDefinition() { + var flux = this.givenSubscriptionToDiagram(); + + var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, + SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID); + var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider); + + AtomicReference diagram = new AtomicReference<>(); + AtomicReference createdSemanticId = new AtomicReference<>(); + + Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set); + + Runnable createNodeRunnable = this.creationTestsService.createNodeWithSelectionDialogWithSingleSelection(diagramDescriptionIdProvider, diagram, SysmlPackage.eINSTANCE.getPartUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.PART_USAGE_ID, "New Satisfy Requirement", GeneralViewWithTopNodesTestProjectData.SemanticIds.ITEM_DEFINITION_ID); + + Consumer diagramCheck = assertRefreshedDiagramThat(newDiagram -> { + var initialDiagram = diagram.get(); + new CheckDiagramElementCount(this.diagramComparator) + .hasNewEdgeCount(3) + .hasNewNodeCount(10) + .hasNewBorderNodeCount(0) + .check(initialDiagram, newDiagram); + + String newNodeDescriptionName = this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getSatisfyRequirementUsage()); + + new CheckNodeOnDiagram(diagramDescriptionIdProvider, this.diagramComparator) + .hasNodeDescriptionName(newNodeDescriptionName) + .hasTotalCompartmentCount(8) + .hasVisibleCompartmentCount(0) + .check(initialDiagram, newDiagram); + + String newNodeDescriptionId = diagramDescriptionIdProvider.getNodeDescriptionId(newNodeDescriptionName); + var newNodes = this.diagramComparator.newNodes(initialDiagram, newDiagram); + var createdNode = newNodes.stream() + .filter(node -> node.getDescriptionId().equals(newNodeDescriptionId)) + .findFirst(); + assertThat(createdNode).isPresent(); + createdSemanticId.set(createdNode.get().getTargetObjectId()); + }); + + Runnable semanticCheck = this.semanticCheckerService.checkElement(SatisfyRequirementUsage.class, createdSemanticId::get, (satisfyRequirement) -> { + var featureMembership = satisfyRequirement.eContainer(); + assertThat(featureMembership.eClass()).isEqualTo(SysmlPackage.eINSTANCE.getFeatureMembership()); + + var partParent = featureMembership.eContainer(); + assertThat(partParent.eClass()).isEqualTo(SysmlPackage.eINSTANCE.getPartUsage()); + assertThat(this.identityService.getId(partParent)).isEqualTo(GeneralViewWithTopNodesTestProjectData.SemanticIds.PART_USAGE_ID); + + assertThat(satisfyRequirement.getType()).anySatisfy(type -> assertThat(this.identityService.getId(type)).isEqualTo(GeneralViewWithTopNodesTestProjectData.SemanticIds.ITEM_DEFINITION_ID)); + }); + + StepVerifier.create(flux) + .consumeNextWith(initialDiagramContentConsumer) + .then(createNodeRunnable) + .consumeNextWith(diagramCheck) + .then(semanticCheck) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } + + @DisplayName("Given a Part, WHEN applying the 'New Satisfy Requirement' tool without selection, THEN the standalone satisfy requirement is created") + @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) + @Test + public void testCreateStandaloneSatisfyRequirement() { + var flux = this.givenSubscriptionToDiagram(); + + var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, + SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID); + var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider); + + AtomicReference diagram = new AtomicReference<>(); + AtomicReference createdSemanticId = new AtomicReference<>(); + + Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set); + + Runnable createNodeRunnable = this.creationTestsService.createNodeWithSelectionDialogWithoutSelectionProvided(diagramDescriptionIdProvider, diagram, SysmlPackage.eINSTANCE.getPartUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.PART_USAGE_ID, "New Satisfy Requirement"); + + Consumer diagramCheck = assertRefreshedDiagramThat(newDiagram -> { + var initialDiagram = diagram.get(); + new CheckDiagramElementCount(this.diagramComparator) + .hasNewEdgeCount(2) + .hasNewNodeCount(10) + .hasNewBorderNodeCount(0) + .check(initialDiagram, newDiagram); + + String newNodeDescriptionName = this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getSatisfyRequirementUsage()); + + new CheckNodeOnDiagram(diagramDescriptionIdProvider, this.diagramComparator) + .hasNodeDescriptionName(newNodeDescriptionName) + .hasTotalCompartmentCount(8) + .hasVisibleCompartmentCount(0) + .check(initialDiagram, newDiagram); + + String newNodeDescriptionId = diagramDescriptionIdProvider.getNodeDescriptionId(newNodeDescriptionName); + var newNodes = this.diagramComparator.newNodes(initialDiagram, newDiagram); + var createdNode = newNodes.stream() + .filter(node -> node.getDescriptionId().equals(newNodeDescriptionId)) + .findFirst(); + assertThat(createdNode).isPresent(); + createdSemanticId.set(createdNode.get().getTargetObjectId()); + }); + + Runnable semanticCheck = this.semanticCheckerService.checkElement(SatisfyRequirementUsage.class, createdSemanticId::get, (satisfyRequirement) -> { + var featureMembership = satisfyRequirement.eContainer(); + assertThat(featureMembership.eClass()).isEqualTo(SysmlPackage.eINSTANCE.getFeatureMembership()); + + var partParent = featureMembership.eContainer(); + assertThat(partParent.eClass()).isEqualTo(SysmlPackage.eINSTANCE.getPartUsage()); + assertThat(this.identityService.getId(partParent)).isEqualTo(GeneralViewWithTopNodesTestProjectData.SemanticIds.PART_USAGE_ID); + + assertThat(this.identityService.getId(satisfyRequirement.getSatisfiedRequirement())).isEqualTo(createdSemanticId.get()); + }); + + StepVerifier.create(flux) + .consumeNextWith(initialDiagramContentConsumer) + .then(createNodeRunnable) + .consumeNextWith(diagramCheck) + .then(semanticCheck) + .thenCancel() + .verify(Duration.ofSeconds(10)); } @DisplayName("GIVEN a SatisfyRequirement edge, WHEN reconnecting the source to a new valid source, THEN the edge is connected to the new source") diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeStructureCreationTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeStructureCreationTests.java index 0e8d1df82..82140fb1f 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeStructureCreationTests.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeStructureCreationTests.java @@ -809,7 +809,7 @@ public void createPartDefinitionSiblingNodes(EClass childEClass, EReference cont @ParameterizedTest @MethodSource("partDefinitionSiblingAndChildNodeParameters") public void createPartDefinitionSiblingAndChildNodes(EClass childEClass, String compartmentName, EReference containmentReference, int expectedNumberOfNewNodes, - int expectedNumberOfNewEdges, String compartmentNodeDecriptionNameSuffix) { + int expectedNumberOfNewEdges, String compartmentNodeDescriptionNameSuffix) { var flux = this.givenSubscriptionToDiagram(); var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, @@ -822,14 +822,15 @@ public void createPartDefinitionSiblingAndChildNodes(EClass childEClass, String EClass parentEClass = SysmlPackage.eINSTANCE.getPartDefinition(); String targetObjectId = GeneralViewWithTopNodesTestProjectData.SemanticIds.PART_DEFINITION_ID; - Runnable createNodeRunnable = this.creationTestsService.createNode(diagramDescriptionIdProvider, diagram, parentEClass, targetObjectId, childEClass); + var variables = List.of(new ToolVariable("selectedObject", "", ToolVariableType.OBJECT_ID)); + Runnable createNodeRunnable = this.creationTestsService.createNode(diagramDescriptionIdProvider, diagram, parentEClass, targetObjectId, childEClass, variables); Consumer diagramChecker = assertRefreshedDiagramThat(newDiagram -> { new CheckDiagramElementCount(this.diagramComparator) .hasNewNodeCount(expectedNumberOfNewNodes) .hasNewEdgeCount(expectedNumberOfNewEdges) .check(diagram.get(), newDiagram); - String compartmentItemNodeDescriptionName = this.descriptionNameGenerator.getCompartmentItemName(parentEClass, containmentReference) + compartmentNodeDecriptionNameSuffix; + String compartmentItemNodeDescriptionName = this.descriptionNameGenerator.getCompartmentItemName(parentEClass, containmentReference) + compartmentNodeDescriptionNameSuffix; new CheckNodeInCompartment(diagramDescriptionIdProvider, this.diagramComparator) .withTargetObjectId(targetObjectId) .withCompartmentName(compartmentName) @@ -929,7 +930,8 @@ public void createPartUsageSiblingAndChildNodes(EClass childEClass, String compa EClass parentEClass = SysmlPackage.eINSTANCE.getPartUsage(); String targetObjectId = GeneralViewWithTopNodesTestProjectData.SemanticIds.PART_USAGE_ID; - Runnable createNodeRunnable = this.creationTestsService.createNode(diagramDescriptionIdProvider, diagram, parentEClass, targetObjectId, childEClass); + var variables = List.of(new ToolVariable("selectedObject", "", ToolVariableType.OBJECT_ID)); + Runnable createNodeRunnable = this.creationTestsService.createNode(diagramDescriptionIdProvider, diagram, parentEClass, targetObjectId, childEClass, variables); Consumer diagramChecker = assertRefreshedDiagramThat(newDiagram -> { new CheckDiagramElementCount(this.diagramComparator) diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/interconnection/view/IVInterconnectionCompartmentToolsTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/interconnection/view/IVInterconnectionCompartmentToolsTests.java index 1cce53f36..a75cdb5ef 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/interconnection/view/IVInterconnectionCompartmentToolsTests.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/interconnection/view/IVInterconnectionCompartmentToolsTests.java @@ -26,6 +26,8 @@ import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramEventInput; import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramRefreshedEventPayload; +import org.eclipse.sirius.components.collaborative.diagrams.dto.ToolVariable; +import org.eclipse.sirius.components.collaborative.diagrams.dto.ToolVariableType; import org.eclipse.sirius.components.diagrams.tests.graphql.InvokeSingleClickOnDiagramElementToolExecutor; import org.eclipse.sirius.components.diagrams.tests.graphql.PaletteQueryRunner; import org.eclipse.sirius.components.diagrams.tests.navigation.DiagramNavigator; @@ -135,7 +137,7 @@ public void testQueryPaletteAndExecuteTools() { diagramId.get(), List.of(interconnectionCompartmentNodeId.get()), newActionToolId.get(), 0, 0, List.of()) .isSuccess(); Runnable createSatisfyRequirementTool = () -> this.invokeSingleClickOnDiagramElementToolExecutor.execute(InterconnectionViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, - diagramId.get(), List.of(interconnectionCompartmentNodeId.get()), newSatisfyRequirementToolId.get(), 0, 0, List.of()) + diagramId.get(), List.of(interconnectionCompartmentNodeId.get()), newSatisfyRequirementToolId.get(), 0, 0, List.of(new ToolVariable("selectedObject", "", ToolVariableType.OBJECT_ID))) .isSuccess(); Consumer afterCreatePartToolConsumer = assertRefreshedDiagramThat(diagram -> { diff --git a/backend/services/syson-model-services/src/main/java/org/eclipse/syson/model/services/ModelMutationElementService.java b/backend/services/syson-model-services/src/main/java/org/eclipse/syson/model/services/ModelMutationElementService.java index 85a7ae8f1..09da1c24e 100644 --- a/backend/services/syson-model-services/src/main/java/org/eclipse/syson/model/services/ModelMutationElementService.java +++ b/backend/services/syson-model-services/src/main/java/org/eclipse/syson/model/services/ModelMutationElementService.java @@ -18,6 +18,7 @@ import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.sirius.components.emf.utils.SiriusEMFCopier; +import org.eclipse.syson.sysml.Definition; import org.eclipse.syson.sysml.Element; import org.eclipse.syson.sysml.FeatureTyping; import org.eclipse.syson.sysml.Namespace; @@ -198,6 +199,47 @@ public SatisfyRequirementUsage createSatisfy(Element element, RequirementUsage e return newSatisfyRequirementUsage; } + /** + * Creates a {@link SatisfyRequirementUsage SatisfyRequirement} on {@code parentElement}. + *

+ * Depending on the value of {@code selectedObject} the new {@link SatisfyRequirementUsage SatisfyRequirement} will be: + *

    + *
  • typed with {@code selectedObject} if the {@code selectedObject} is a {@link Definition}
  • + *
  • subsetted by reference by {@code selectedObject} if the {@code selectedObject} is a {@link RequirementUsage}
  • + *
  • standalone otherwise
  • + *
+ *

+ * + * @param parentElement + * The parent element of the new {@link SatisfyRequirementUsage SatisfyRequirement} + * @param selectedObject + * The optionally selected object which will be used to provide additional behavior depending on its value + * @return The new created {@link SatisfyRequirementUsage SatisfyRequirement} + * + * @see ModelMutationElementService#createSatisfy(Element, RequirementUsage) + */ + public SatisfyRequirementUsage createSatisfyRequirement(Element parentElement, Element selectedObject) { + // create a new SatisfyRequirementUsage as child of the given Element + var newSatisfyRequirementUsage = SysmlFactory.eINSTANCE.createSatisfyRequirementUsage(); + this.metamodelMutationElementService.addChildInParent(parentElement, newSatisfyRequirementUsage); + + if (selectedObject instanceof Definition definition) { + var newFeatureTyping = SysmlFactory.eINSTANCE.createFeatureTyping(); + newSatisfyRequirementUsage.getOwnedRelationship().add(newFeatureTyping); + newFeatureTyping.setType(definition); + newFeatureTyping.setTypedFeature(newSatisfyRequirementUsage); + this.metamodelMutationElementService.initialize(newFeatureTyping); + } else if (selectedObject instanceof RequirementUsage requirementUsage) { + var newReferenceSubsetting = SysmlFactory.eINSTANCE.createReferenceSubsetting(); + newSatisfyRequirementUsage.getOwnedRelationship().add(newReferenceSubsetting); + newReferenceSubsetting.setReferencedFeature(requirementUsage); + this.metamodelMutationElementService.initialize(newReferenceSubsetting); + } + + this.metamodelMutationElementService.initialize(newSatisfyRequirementUsage); + return newSatisfyRequirementUsage; + } + /** * Creates a {@link ViewUsage}. * diff --git a/backend/services/syson-model-services/src/main/java/org/eclipse/syson/model/services/aql/ModelMutationAQLService.java b/backend/services/syson-model-services/src/main/java/org/eclipse/syson/model/services/aql/ModelMutationAQLService.java index 4e7c5097f..2e9e3714a 100644 --- a/backend/services/syson-model-services/src/main/java/org/eclipse/syson/model/services/aql/ModelMutationAQLService.java +++ b/backend/services/syson-model-services/src/main/java/org/eclipse/syson/model/services/aql/ModelMutationAQLService.java @@ -85,6 +85,13 @@ public Element createSatisfy(Element element, RequirementUsage existingRequireme return this.modelMutationElementService.createSatisfy(element, existingRequirement); } + /** + * {@link ModelMutationElementService#createSatisfyRequirement(Element, Element)}. + */ + public Element createSatisfyRequirement(Element self, Element selectedObject) { + return this.modelMutationElementService.createSatisfyRequirement(self, selectedObject); + } + /** * {@link MetamodelMutationElementService#createOccurrenceInOccurrence(OccurrenceUsage, EClass)}. */ diff --git a/backend/services/syson-services/src/main/java/org/eclipse/syson/util/AQLUtils.java b/backend/services/syson-services/src/main/java/org/eclipse/syson/util/AQLUtils.java index 8dbb70417..7c950781d 100644 --- a/backend/services/syson-services/src/main/java/org/eclipse/syson/util/AQLUtils.java +++ b/backend/services/syson-services/src/main/java/org/eclipse/syson/util/AQLUtils.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024, 2025 Obeo. + * Copyright (c) 2024, 2026 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -13,6 +13,7 @@ package org.eclipse.syson.util; import java.util.List; +import java.util.stream.Collectors; /** * Tooling around AQL expressions. @@ -117,4 +118,15 @@ public static String getServiceCallExpression(String var, String serviceName, Li return AQLConstants.AQL + var + '.' + serviceName + '(' + String.join(",", parameters) + ')'; } + /** + * Returns an AQL sequence from the given members. + * + * @param members + * The members to transform into a sequence + * @return the AQL sequence made from the members + */ + public static String aqlSequence(List members) { + return members.stream().collect(Collectors.joining(",", "Sequence{", "}")); + } + } diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/InterconnectionCompartmentNodeDescriptionProvider.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/InterconnectionCompartmentNodeDescriptionProvider.java index b0c36bcef..e03d5692b 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/InterconnectionCompartmentNodeDescriptionProvider.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/InterconnectionCompartmentNodeDescriptionProvider.java @@ -37,6 +37,7 @@ import org.eclipse.sirius.components.view.diagram.UserResizableDirection; import org.eclipse.syson.diagram.common.view.services.description.ToolConstants; import org.eclipse.syson.diagram.common.view.tools.ActionFlowCompartmentNodeToolProvider; +import org.eclipse.syson.diagram.common.view.tools.SatisfyRequirementNodeToolProvider; import org.eclipse.syson.diagram.services.aql.DiagramQueryAQLService; import org.eclipse.syson.sysml.Element; import org.eclipse.syson.sysml.SysmlPackage; @@ -110,7 +111,7 @@ protected NodePalette createCompartmentPalette(IViewDiagramElementFinder cache) this.toolDescriptionService.addNodeTool(toolSections, ToolConstants.STRUCTURE, this.toolDescriptionService.createNodeTool(nodeDesc, SysmlPackage.eINSTANCE.getActionUsage())); this.toolDescriptionService.addNodeTool(toolSections, ToolConstants.REQUIREMENTS, - this.toolDescriptionService.createNodeTool(nodeDesc, SysmlPackage.eINSTANCE.getSatisfyRequirementUsage())); + new SatisfyRequirementNodeToolProvider().create(cache)); }); this.toolDescriptionService.removeEmptyNodeToolSections(toolSections); diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/SatisfyNodeToolProvider.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/SatisfyNodeToolProvider.java index ebee4e262..412eb6a7f 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/SatisfyNodeToolProvider.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/SatisfyNodeToolProvider.java @@ -12,6 +12,8 @@ *******************************************************************************/ package org.eclipse.syson.diagram.common.view.tools; +import java.util.List; + import org.eclipse.sirius.components.collaborative.diagrams.DiagramContext; import org.eclipse.sirius.components.core.api.IEditingContext; import org.eclipse.sirius.components.diagrams.Node; @@ -26,6 +28,7 @@ import org.eclipse.syson.model.services.aql.ModelMutationAQLService; import org.eclipse.syson.sysml.SysmlPackage; import org.eclipse.syson.util.AQLConstants; +import org.eclipse.syson.util.AQLUtils; import org.eclipse.syson.util.ServiceMethod; import org.eclipse.syson.util.SysMLMetamodelHelper; @@ -47,8 +50,8 @@ public NodeTool create(IViewDiagramElementFinder cache) { String reqUsageType = SysMLMetamodelHelper.buildQualifiedName(SysmlPackage.eINSTANCE.getRequirementUsage()); var selectionDialogTree = this.diagramBuilderHelper.newSelectionDialogTreeDescription() - .elementsExpression(ServiceMethod.of1(ViewToolService::getSelectionDialogElements).aql(IEditingContext.EDITING_CONTEXT, "Sequence{" + reqUsageType + "}")) - .childrenExpression(ServiceMethod.of1(ViewToolService::getSelectionDialogChildren).aqlSelf("Sequence{" + reqUsageType + "}")) + .elementsExpression(ServiceMethod.of1(ViewToolService::getSelectionDialogElements).aql(IEditingContext.EDITING_CONTEXT, AQLUtils.aqlSequence(List.of(reqUsageType)))) + .childrenExpression(ServiceMethod.of1(ViewToolService::getSelectionDialogChildren).aqlSelf(AQLUtils.aqlSequence(List.of(reqUsageType)))) .isSelectableExpression(AQLConstants.AQL_SELF + ".oclIsKindOf(" + reqUsageType + ")") .build(); var selectionDialog = this.diagramBuilderHelper.newSelectionDialogDescription() diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/SatisfyRequirementNodeToolProvider.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/SatisfyRequirementNodeToolProvider.java index 5e5de6d12..cfa2e2160 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/SatisfyRequirementNodeToolProvider.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/SatisfyRequirementNodeToolProvider.java @@ -12,6 +12,8 @@ *******************************************************************************/ package org.eclipse.syson.diagram.common.view.tools; +import java.util.List; + import org.eclipse.sirius.components.collaborative.diagrams.DiagramContext; import org.eclipse.sirius.components.core.api.IEditingContext; import org.eclipse.sirius.components.diagrams.Node; @@ -19,14 +21,18 @@ import org.eclipse.sirius.components.view.builder.generated.diagram.DiagramBuilders; import org.eclipse.sirius.components.view.builder.generated.view.ViewBuilders; import org.eclipse.sirius.components.view.builder.providers.INodeToolProvider; +import org.eclipse.sirius.components.view.diagram.DialogDescription; import org.eclipse.sirius.components.view.diagram.NodeTool; import org.eclipse.sirius.components.view.emf.diagram.ViewDiagramDescriptionConverter; -import org.eclipse.syson.diagram.common.view.services.ViewCreateService; +import org.eclipse.syson.diagram.common.view.services.ViewToolService; import org.eclipse.syson.diagram.services.aql.DiagramMutationAQLService; import org.eclipse.syson.model.services.aql.ModelMutationAQLService; import org.eclipse.syson.sysml.SysmlPackage; +import org.eclipse.syson.util.AQLConstants; +import org.eclipse.syson.util.AQLUtils; import org.eclipse.syson.util.ServiceMethod; import org.eclipse.syson.util.SysMLMetamodelHelper; + /** * Node Tool allowing to create a SatisfyRequirement. * @@ -34,6 +40,8 @@ */ public class SatisfyRequirementNodeToolProvider implements INodeToolProvider { + private static final String TOOL_NAME = "New Satisfy Requirement"; + private final DiagramBuilders diagramBuilderHelper = new DiagramBuilders(); private final ViewBuilders viewBuilderHelper = new ViewBuilders(); @@ -46,26 +54,43 @@ public NodeTool create(IViewDiagramElementFinder cache) { .expression(ServiceMethod.of4(DiagramMutationAQLService::expose).aqlSelf(IEditingContext.EDITING_CONTEXT, DiagramContext.DIAGRAM_CONTEXT, Node.SELECTED_NODE, ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE)); - var changeContextNewInstance = this.viewBuilderHelper.newChangeContext() - .expression("aql:newInstance") - .children(addToExposedElements.build()); + var newSatisfyRequirementTool = this.viewBuilderHelper.newChangeContext() + .expression(ServiceMethod.of1(ModelMutationAQLService::createSatisfyRequirement).aqlSelf("selectedObject")) + .children(addToExposedElements.build()) + .build(); - var changeContextInitializeNewInstance = this.viewBuilderHelper.newChangeContext() - .expression(ServiceMethod.of0(ViewCreateService::elementInitializer).aql("newInstance")); + return builder.name(TOOL_NAME) + .iconURLsExpression("/icons/full/obj16/SatisfyRequirementUsage.svg") + .dialogDescription(this.getDialogDescription()) + .body(newSatisfyRequirementTool) + .build(); + } - var createEClassInstance = this.viewBuilderHelper.newCreateInstance() - .typeName(SysMLMetamodelHelper.buildQualifiedName(SysmlPackage.eINSTANCE.getSatisfyRequirementUsage())) - .referenceName(SysmlPackage.eINSTANCE.getRelationship_OwnedRelatedElement().getName()) - .variableName("newInstance") - .children(changeContextInitializeNewInstance.build(), changeContextNewInstance.build()); + private DialogDescription getDialogDescription() { + var reqUsageType = SysMLMetamodelHelper.buildQualifiedName(SysmlPackage.eINSTANCE.getRequirementUsage()); + var defType = SysMLMetamodelHelper.buildQualifiedName(SysmlPackage.eINSTANCE.getDefinition()); + var selectableTypes = List.of(reqUsageType, defType); - var createMembership = this.viewBuilderHelper.newChangeContext() - .expression(ServiceMethod.of0(ModelMutationAQLService::createMembership).aqlSelf()) - .children(createEClassInstance.build()); + var selectionDialogTreeDescription = this.diagramBuilderHelper.newSelectionDialogTreeDescription() + .elementsExpression(ServiceMethod.of1(ViewToolService::getSelectionDialogElements).aql(IEditingContext.EDITING_CONTEXT, AQLUtils.aqlSequence(selectableTypes))) + .childrenExpression(ServiceMethod.of1(ViewToolService::getSelectionDialogChildren).aqlSelf(AQLUtils.aqlSequence(selectableTypes))) + .isSelectableExpression(AQLConstants.AQL + "self.oclIsKindOf(" + defType + ") or self.oclIsKindOf(" + reqUsageType + ")") + .build(); - return builder.name("New Satisfy Requirement") - .iconURLsExpression("/icons/full/obj16/SatisfyRequirementUsage.svg") - .body(createMembership.build()) + return this.diagramBuilderHelper.newSelectionDialogDescription() + .selectionDialogTreeDescription(selectionDialogTreeDescription) + .defaultTitleExpression(TOOL_NAME) + .noSelectionTitleExpression(TOOL_NAME) + .withSelectionTitleExpression(TOOL_NAME) + .descriptionExpression(AQLConstants.AQL + "'Create a Satisfy Requirement with ' + self.name + ' as subject:'") + .noSelectionActionLabelExpression("Create a new Satisfy Requirement") + .noSelectionActionDescriptionExpression("Create a new Satisfy Requirement without specialization") + .withSelectionActionLabelExpression("Select an existing Element as specialization") + .withSelectionActionDescriptionExpression("Create a new specialized Satisfy Requirement") + .noSelectionActionStatusMessageExpression("It will create a new Satisfy Requirement without specialization") + .selectionRequiredWithoutSelectionStatusMessageExpression("Select one Element to specialize the new Satisfy Requirement") + .selectionRequiredWithSelectionStatusMessageExpression(AQLConstants.AQL + "'It will create a Satisfy Requirement specialized with ' + selectedObjects->first().name") + .optional(true) .build(); } } diff --git a/doc/content/modules/user-manual/pages/release-notes/2026.5.0.adoc b/doc/content/modules/user-manual/pages/release-notes/2026.5.0.adoc index d2b8db361..3fb3c0a62 100644 --- a/doc/content/modules/user-manual/pages/release-notes/2026.5.0.adoc +++ b/doc/content/modules/user-manual/pages/release-notes/2026.5.0.adoc @@ -42,6 +42,7 @@ image::release-notes-stakeholder-node.png[Default representation of Stakeholder ** Improve the _View As_ tool on graphical nodes to support multi-selection in standard diagrams. ** Improve the _Add existing elements_ tool on graphical nodes to support multi-selection in standard diagrams. ** Improve the tools to create `PerformActionUsage` by making the selection of a subsetted reference optional. +** Improve the tool to create a `SatisfyRequirement` graphical node, either standalone, feature typed, or subsetting a `RequirementUsage` by reference. * In the _Explorer_ view: