From bc42c8e8226ca980ae5e46d1921c1d6e1a254c2a Mon Sep 17 00:00:00 2001 From: Jerome Gout Date: Fri, 28 Aug 2026 18:43:12 +0200 Subject: [PATCH] [2471] Add selection dialog in New Port as Receiver tool Add the selection of an existing PortUsage in New Port as Receiver tool on AcceptAction graphical node. Bug: https://github.com/eclipse-syson/syson/issues/2471 Signed-off-by: Jerome Gout --- CHANGELOG.adoc | 1 + .../GVSubNodeActionFlowCreationTests.java | 31 ++++++++++++---- .../services/DiagramMutationToolService.java | 36 +++++++++++++------ .../aql/DiagramMutationAQLService.java | 6 ++-- ...tionPortUsageReceiverToolNodeProvider.java | 35 +++++++++++++++++- .../pages/release-notes/2026.9.0.adoc | 2 ++ 6 files changed, 91 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 6bb91af68c..d18a2d2c30 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -115,6 +115,7 @@ The edge goes from the derived requirement to the original one and is displayed - https://github.com/eclipse-syson/syson/issues/2428[#2428] [diagrams] Add the _New Derived Requirement_ edge tool on `RequirementUsage` graphical nodes, creating a requirement derivation towards another `RequirementUsage`. The edge is drawn from the derived requirement to the original one, and the created derivation has the same shape as one written in text: a `ConnectionUsage` annotated with `#derivation`, whose ends are annotated with `#original` and `#derive`. The import of the `RequirementDerivation` library is added to the owning namespace when it is missing. +- https://github.com/eclipse-syson/syson/issues/2471[#2471] [diagrams] Add the selection of an existing `PortUsage` in `New Port as Receiver` tool on `AcceptAction` graphical node. == v20 == v2026.7.0 diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeActionFlowCreationTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeActionFlowCreationTests.java index c52dff1d53..7d9d89742d 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeActionFlowCreationTests.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeActionFlowCreationTests.java @@ -133,6 +133,13 @@ private static Stream acceptActionUsagePayloadParameters() { .map(TestNameGenerator::namedArguments); } + private static Stream acceptActionUsageReceiverParameters() { + return Stream.of( + Arguments.of(""), + Arguments.of(GeneralViewWithTopNodesTestProjectData.SemanticIds.PORT_USAGE_ID) + ); + } + private static Stream actionUsageSiblingNodeParameters() { return Stream.of( Arguments.of(SysmlPackage.eINSTANCE.getPartUsage(), SysmlPackage.eINSTANCE.getUsage_NestedPart(), 13)) @@ -284,10 +291,21 @@ public void createAcceptActionUsagePayload(EClass eClass) { } @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) - @Test - public void createAcceptActionUsageReceiver() { + @MethodSource("acceptActionUsageReceiverParameters") + @ParameterizedTest + public void createAcceptActionUsageReceiver(String selectedObjectId) { var flux = this.givenSubscriptionToDiagram(); + AtomicReference expectedReceiverName = new AtomicReference<>(); + ISemanticChecker initialSemanticChecker = (editingContext) -> { + expectedReceiverName.set(this.objectSearchService.getObject(editingContext, selectedObjectId) + .filter(Element.class::isInstance) + .map(Element.class::cast) + .map(Element::getDeclaredName) + .orElse("acceptActionReceiver")); + }; + Runnable initialSemanticCheck = this.semanticCheckerService.checkEditingContext(initialSemanticChecker); + var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID); var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider); @@ -299,8 +317,8 @@ public void createAcceptActionUsageReceiver() { AtomicReference diagram = new AtomicReference<>(); Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set); - Runnable createNodeRunnable = this.creationTestsService.createNode(diagramDescriptionIdProvider, diagram, parentEClass, targetObjectId, - this.descriptionNameGenerator.getCreationToolName("New {0} as Receiver", eClass)); + Runnable createNodeRunnable = this.creationTestsService.createNodeWithSelectionDialogWithSingleSelection(diagramDescriptionIdProvider, diagram, parentEClass, targetObjectId, + this.descriptionNameGenerator.getCreationToolName("New {0} as Receiver", eClass), selectedObjectId); Consumer diagramCheck = assertRefreshedDiagramThat(newDiagram -> { new CheckDiagramElementCount(this.diagramComparator) @@ -314,7 +332,7 @@ public void createAcceptActionUsageReceiver() { assertThat(updatedNode.getInsideLabel()).as("The updated node label should exist").isNotNull(); assertThat(updatedNode.getInsideLabel().getText()) .contains(LabelConstants.OPEN_QUOTE + "via" + LabelConstants.CLOSE_QUOTE) - .contains("acceptAction's receiver"); + .contains(expectedReceiverName.get()); }); ISemanticChecker semanticChecker = (editingContext) -> { @@ -332,13 +350,14 @@ public void createAcceptActionUsageReceiver() { assertThat(relationship).isInstanceOf(Membership.class); Membership membership = (Membership) relationship; assertThat(eClass.isInstance(membership.getMemberElement())).isTrue(); - assertThat(membership.getMemberElement().getName()).isEqualTo("acceptAction's receiver"); + assertThat(membership.getMemberElement().getName()).isEqualTo(expectedReceiverName.get()); }; Runnable semanticCheck = this.semanticCheckerService.checkEditingContext(semanticChecker); StepVerifier.create(flux) .consumeNextWith(initialDiagramContentConsumer) + .then(initialSemanticCheck) .then(createNodeRunnable) .consumeNextWith(diagramCheck) .then(semanticCheck) diff --git a/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationToolService.java b/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationToolService.java index 50da322c74..749233ff1b 100644 --- a/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationToolService.java +++ b/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationToolService.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.eclipse.emf.ecore.EClass; @@ -56,6 +57,7 @@ import org.eclipse.syson.sysml.PartDefinition; import org.eclipse.syson.sysml.PartUsage; import org.eclipse.syson.sysml.PerformActionUsage; +import org.eclipse.syson.sysml.PortUsage; import org.eclipse.syson.sysml.ReferenceUsage; import org.eclipse.syson.sysml.RequirementConstraintKind; import org.eclipse.syson.sysml.RequirementConstraintMembership; @@ -634,23 +636,20 @@ public Element createAcceptActionPayload(AcceptActionUsage self, String payloadE * Create or replace the receiver parameter of an accept action usage. * * @param self - * the accept action usage + * the accept action usage + * @param selectedObject + * the existing port given by the selection dialog. Can be null. * @return the given accept action usage */ - public Element createAcceptActionReceiver(AcceptActionUsage self) { - var newPort = SysmlFactory.eINSTANCE.createPortUsage(); - newPort.setDeclaredName(self.getDeclaredName() + "'s receiver"); - var owningMembership = SysmlFactory.eINSTANCE.createOwningMembership(); - owningMembership.getOwnedRelatedElement().add(newPort); - var receiverParent = this.getClosestContainingPackageFrom(self); - receiverParent.getOwnedRelationship().add(owningMembership); - + public Element createAcceptActionReceiver(AcceptActionUsage self, PortUsage selectedObject) { + var portReceiver = Optional.ofNullable(selectedObject) + .orElseGet(() -> this.createNewAcceptActionReceiver(self)); var feature = SysmlFactory.eINSTANCE.createFeature(); feature.setDirection(FeatureDirectionKind.OUT); var returnParameterMembership = SysmlFactory.eINSTANCE.createReturnParameterMembership(); returnParameterMembership.getOwnedRelatedElement().add(feature); var membership = SysmlFactory.eINSTANCE.createMembership(); - membership.setMemberElement(newPort); + membership.setMemberElement(portReceiver); var featureReferenceExpression = SysmlFactory.eINSTANCE.createFeatureReferenceExpression(); featureReferenceExpression.getOwnedRelationship().add(membership); featureReferenceExpression.getOwnedRelationship().add(returnParameterMembership); @@ -671,6 +670,23 @@ public Element createAcceptActionReceiver(AcceptActionUsage self) { return self; } + /** + * Creates a new receiver {@link PortUsage} to set in the given {@link AcceptActionUsage}. + * + * @param self + * the accept action in which the new receiver should be set. + * @return the newly created receiver {@link PortUsage}. + */ + private PortUsage createNewAcceptActionReceiver(AcceptActionUsage self) { + var newPort = SysmlFactory.eINSTANCE.createPortUsage(); + newPort.setDeclaredName(self.getDeclaredName() + "Receiver"); + var owningMembership = SysmlFactory.eINSTANCE.createOwningMembership(); + owningMembership.getOwnedRelatedElement().add(newPort); + var receiverParent = this.getClosestContainingPackageFrom(self); + receiverParent.getOwnedRelationship().add(owningMembership); + return newPort; + } + /** * Add the standard start action as the child of the given element. * diff --git a/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/aql/DiagramMutationAQLService.java b/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/aql/DiagramMutationAQLService.java index 81f0c9badd..b3e678d80c 100644 --- a/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/aql/DiagramMutationAQLService.java +++ b/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/aql/DiagramMutationAQLService.java @@ -151,10 +151,10 @@ public Element createAcceptActionPayload(AcceptActionUsage self, String payloadE } /** - * {@link DiagramMutationToolService#createAcceptActionReceiver(AcceptActionUsage)}. + * {@link DiagramMutationToolService#createAcceptActionReceiver(AcceptActionUsage, PortUsage)}. */ - public Element createAcceptActionReceiver(AcceptActionUsage self) { - return this.diagramMutationToolService.createAcceptActionReceiver(self); + public Element createAcceptActionReceiver(AcceptActionUsage self, PortUsage selectedObject) { + return this.diagramMutationToolService.createAcceptActionReceiver(self, selectedObject); } /** diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/AcceptActionPortUsageReceiverToolNodeProvider.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/AcceptActionPortUsageReceiverToolNodeProvider.java index 7599c5033d..8a8264f3ff 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/AcceptActionPortUsageReceiverToolNodeProvider.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/AcceptActionPortUsageReceiverToolNodeProvider.java @@ -12,17 +12,25 @@ *******************************************************************************/ package org.eclipse.syson.diagram.common.view.tools; +import java.util.List; + +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.trees.renderer.TreeRenderer; import org.eclipse.sirius.components.view.builder.IViewDiagramElementFinder; 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.syson.diagram.services.aql.DiagramMutationAQLService; import org.eclipse.syson.diagram.services.aql.DiagramQueryAQLService; import org.eclipse.syson.sysml.PortUsage; import org.eclipse.syson.sysml.SysmlPackage; +import org.eclipse.syson.tree.services.aql.TreeQueryAQLService; import org.eclipse.syson.util.AQLConstants; +import org.eclipse.syson.util.AQLUtils; import org.eclipse.syson.util.ServiceMethod; +import org.eclipse.syson.util.SysMLMetamodelHelper; /** * Used to create a {@link PortUsage} as the receiver of an accept action usage. @@ -40,7 +48,7 @@ public NodeTool create(IViewDiagramElementFinder cache) { var builder = this.diagramBuilderHelper.newNodeTool(); var creationPayloadServiceCall = this.viewBuilderHelper.newChangeContext() - .expression(ServiceMethod.of0(DiagramMutationAQLService::createAcceptActionReceiver).aqlSelf()) + .expression(ServiceMethod.of1(DiagramMutationAQLService::createAcceptActionReceiver).aqlSelf("selectedObject")) .build(); var rootChangContext = this.viewBuilderHelper.newChangeContext() @@ -52,6 +60,31 @@ public NodeTool create(IViewDiagramElementFinder cache) { .iconURLsExpression("/icons/full/obj16/" + SysmlPackage.eINSTANCE.getPortUsage().getName() + ".svg") .body(rootChangContext) .preconditionExpression(ServiceMethod.of0(DiagramQueryAQLService::isEmptyAcceptActionUsageReceiver).aqlSelf()) + .dialogDescription(this.getSelectionDialogDescription()) + .build(); + } + + private DialogDescription getSelectionDialogDescription() { + var receiverTypeName = SysMLMetamodelHelper.buildQualifiedName(SysmlPackage.eINSTANCE.getPortUsage()); + var selectionDialogTree = this.diagramBuilderHelper.newSelectionDialogTreeDescription() + .elementsExpression(ServiceMethod.of1(TreeQueryAQLService::getSelectionDialogElements).aql(IEditingContext.EDITING_CONTEXT, AQLUtils.aqlSequence(List.of(receiverTypeName)))) + .childrenExpression( + ServiceMethod.of3(TreeQueryAQLService::getSelectionDialogChildren).aqlSelf(IEditingContext.EDITING_CONTEXT, TreeRenderer.EXPANDED, AQLUtils.aqlSequence(List.of(receiverTypeName)))) + .isSelectableExpression(AQLConstants.AQL_SELF + ".oclIsKindOf(" + receiverTypeName + ")") + .build(); + String receiverName = SysmlPackage.eINSTANCE.getPortUsage().getName(); + return this.diagramBuilderHelper.newSelectionDialogDescription() + .selectionDialogTreeDescription(selectionDialogTree) + .defaultTitleExpression("Set the receiver") + .descriptionExpression(receiverName + " as receiver") + .noSelectionActionLabelExpression("Create a New " + receiverName) + .noSelectionActionDescriptionExpression("Set the receiver with a New " + receiverName) + .withSelectionActionLabelExpression("Select an existing " + receiverName) + .withSelectionActionDescriptionExpression("Set the receiver with an existing " + receiverName) + .noSelectionActionStatusMessageExpression("It will set the receiver with a New " + receiverName) + .selectionRequiredWithoutSelectionStatusMessageExpression("Select one " + receiverName) + .selectionRequiredWithSelectionStatusMessageExpression(AQLConstants.AQL + "'It will set the receiver with ' + selectedObjects->first().name") + .optional(true) .build(); } } diff --git a/doc/content/modules/user-manual/pages/release-notes/2026.9.0.adoc b/doc/content/modules/user-manual/pages/release-notes/2026.9.0.adoc index cebb8f3d63..dbe194c492 100644 --- a/doc/content/modules/user-manual/pages/release-notes/2026.9.0.adoc +++ b/doc/content/modules/user-manual/pages/release-notes/2026.9.0.adoc @@ -98,6 +98,8 @@ image::release-notes-port-inherited-item-border-nodes.png[ItemUsage border node ** Improve the edge tool creating a `FlowUsage`, by creating a `ReferenceUsage` used for the redefinition of the `FlowEnd` only when it is needed. ** Fix the edge tool creating a `ConnectionUsage` between an `ActionUsage` graphical node and the _done_ `ActionUsage` graphical node. +** Add the capability of selecting an existing `PortUsage` in `New Port as Receiver` tool on `AcceptAction` graphical node. +The creation of a new port remains available. == Bug fixes