From 5147f2d21c93d4f56babcb00a7c13108f638721a Mon Sep 17 00:00:00 2001 From: Guillaume Coutable Date: Tue, 26 May 2026 17:30:21 +0200 Subject: [PATCH] [2242] Allow creating a ConcernUsage from a RequirementUsage or RequirementDefinition in the Explorer View Bug: https://github.com/eclipse-syson/syson/issues/2242 Signed-off-by: Guillaume Coutable --- CHANGELOG.adoc | 2 + .../services/GetChildCreationSwitch.java | 12 ++++ .../services/GetChildCreationSwitchTest.java | 65 ++++++++++++++++++- ...xplorerViewControllerIntegrationTests.java | 59 +++++++++++++++-- ...etIntermediateContainerCreationSwitch.java | 14 ++++ .../pages/release-notes/2026.7.0.adoc | 6 ++ 6 files changed, 153 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index d6ffa3690..b64ee95ab 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -31,6 +31,8 @@ Disabling the _Hide expression internals_ filter in the _Explorer_ view allows t - https://github.com/eclipse-syson/syson/issues/2112[#2112] [diagrams] Add tools to create _Start_ and _Done_ `StateUsages`, available on `StateUsage` and `StateDefinition` graphical nodes. - https://github.com/eclipse-syson/syson/issues/2231[#2231] [diagrams] Add a new tools to create a _frame_ `ConcernUsage` from `RequirementUsage` and `RequirementDefinition` graphical nodes. - https://github.com/eclipse-syson/syson/issues/2231[#2231] [diagrams] Add the support for the _frames_ compartment graphical node in `RequirementUsage` and `RequirementDefinition` graphical nodes. +- https://github.com/eclipse-syson/syson/issues/2242[#2242] [explorer] Add two options to the dialog creating a child element of `RequirementUsage` or `RequirementDefinition` tree items. +One creates a `ConcernUsage` and another one creates `FramedConcernMembership`. == v2026.5.0 diff --git a/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/services/GetChildCreationSwitch.java b/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/services/GetChildCreationSwitch.java index 9d273d049..d5444ef0d 100644 --- a/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/services/GetChildCreationSwitch.java +++ b/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/services/GetChildCreationSwitch.java @@ -24,6 +24,7 @@ import org.eclipse.syson.sysml.Definition; import org.eclipse.syson.sysml.EnumerationDefinition; import org.eclipse.syson.sysml.FeatureMembership; +import org.eclipse.syson.sysml.FramedConcernMembership; import org.eclipse.syson.sysml.InterfaceUsage; import org.eclipse.syson.sysml.ItemUsage; import org.eclipse.syson.sysml.Namespace; @@ -134,6 +135,13 @@ public List caseFeatureMembership(FeatureMembership object) { return childrenCandidates; } + @Override + public List caseFramedConcernMembership(FramedConcernMembership object) { + List childrenCandidates = new ArrayList<>(); + childrenCandidates.add(SysmlPackage.eINSTANCE.getConcernUsage()); + return childrenCandidates; + } + @Override public List caseInterfaceUsage(InterfaceUsage object) { List childrenCandidates = new ArrayList<>(); @@ -224,6 +232,8 @@ public List caseRequirementDefinition(RequirementDefinition object) { List childrenCandidates = new ArrayList<>(); childrenCandidates.addAll(this.caseDefinition(object)); childrenCandidates.add(SysmlPackage.eINSTANCE.getSubjectMembership()); + childrenCandidates.add(SysmlPackage.eINSTANCE.getFramedConcernMembership()); + childrenCandidates.add(SysmlPackage.eINSTANCE.getConcernUsage()); return childrenCandidates; } @@ -232,6 +242,8 @@ public List caseRequirementUsage(RequirementUsage object) { List childrenCandidates = new ArrayList<>(); childrenCandidates.addAll(this.caseUsage(object)); childrenCandidates.add(SysmlPackage.eINSTANCE.getSubjectMembership()); + childrenCandidates.add(SysmlPackage.eINSTANCE.getFramedConcernMembership()); + childrenCandidates.add(SysmlPackage.eINSTANCE.getConcernUsage()); return childrenCandidates; } diff --git a/backend/application/syson-application-configuration/src/test/java/org/eclipse/syson/application/services/GetChildCreationSwitchTest.java b/backend/application/syson-application-configuration/src/test/java/org/eclipse/syson/application/services/GetChildCreationSwitchTest.java index 46f8ce996..e7ca91bcc 100644 --- a/backend/application/syson-application-configuration/src/test/java/org/eclipse/syson/application/services/GetChildCreationSwitchTest.java +++ b/backend/application/syson-application-configuration/src/test/java/org/eclipse/syson/application/services/GetChildCreationSwitchTest.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 @@ -19,6 +19,7 @@ import org.eclipse.emf.ecore.EClass; import org.eclipse.syson.sysml.SysmlFactory; import org.eclipse.syson.sysml.SysmlPackage; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; /** @@ -28,6 +29,7 @@ */ public class GetChildCreationSwitchTest { + @DisplayName("Check the list of children that can be created from a Package") @Test public void testPackageChildren() { List children = new GetChildCreationSwitch().doSwitch(SysmlFactory.eINSTANCE.createPackage()); @@ -47,6 +49,7 @@ public void testPackageChildren() { || SysmlPackage.eINSTANCE.getPackage().isSuperTypeOf(eClass)); } + @DisplayName("Check the list of children that can be created from a Namespace") @Test public void testNamespaceChildren() { List children = new GetChildCreationSwitch().doSwitch(SysmlFactory.eINSTANCE.createNamespace()); @@ -66,6 +69,7 @@ public void testNamespaceChildren() { || SysmlPackage.eINSTANCE.getPackage().isSuperTypeOf(eClass)); } + @DisplayName("Check the list of children that can be created from a ViewUsage") @Test public void testViewUsageChildren() { List children = new GetChildCreationSwitch().doSwitch(SysmlFactory.eINSTANCE.createViewUsage()); @@ -79,4 +83,63 @@ public void testViewUsageChildren() { children.removeAll(expectedChildren); assertThat(children).isEmpty(); } + + @DisplayName("Check the list of children that can be created from a RequirementDefinition") + @Test + public void testRequirementDefinitionChildren() { + List children = new GetChildCreationSwitch().doSwitch(SysmlFactory.eINSTANCE.createRequirementDefinition()); + List expectedChildren = List.of( + SysmlPackage.eINSTANCE.getSubclassification(), + SysmlPackage.eINSTANCE.getDocumentation(), + SysmlPackage.eINSTANCE.getComment(), + SysmlPackage.eINSTANCE.getTextualRepresentation(), + SysmlPackage.eINSTANCE.getSubjectMembership(), + SysmlPackage.eINSTANCE.getFramedConcernMembership(), + SysmlPackage.eINSTANCE.getConcernUsage() + ); + assertThat(children).containsAll(expectedChildren); + children.removeAll(expectedChildren); + assertThat(children).allMatch(eClass -> { + boolean authorizedClasses = SysmlPackage.eINSTANCE.getUsage().isSuperTypeOf(eClass) || SysmlPackage.eINSTANCE.getImport().isSuperTypeOf(eClass); + return !eClass.isAbstract() && !eClass.isInterface() && authorizedClasses; + }); + } + + @DisplayName("Check the list of children that can be created from a RequirementUsage") + @Test + public void testRequirementUsageChildren() { + List children = new GetChildCreationSwitch().doSwitch(SysmlFactory.eINSTANCE.createRequirementUsage()); + List expectedChildren = List.of( + SysmlPackage.eINSTANCE.getAttributeUsage(), + SysmlPackage.eINSTANCE.getFeatureTyping(), + SysmlPackage.eINSTANCE.getSubsetting(), + SysmlPackage.eINSTANCE.getRedefinition(), + SysmlPackage.eINSTANCE.getReferenceSubsetting(), + SysmlPackage.eINSTANCE.getLiteralBoolean(), + SysmlPackage.eINSTANCE.getLiteralInfinity(), + SysmlPackage.eINSTANCE.getLiteralInteger(), + SysmlPackage.eINSTANCE.getLiteralRational(), + SysmlPackage.eINSTANCE.getLiteralString(), + SysmlPackage.eINSTANCE.getFeatureMembership(), + SysmlPackage.eINSTANCE.getDocumentation(), + SysmlPackage.eINSTANCE.getComment(), + SysmlPackage.eINSTANCE.getTextualRepresentation(), + SysmlPackage.eINSTANCE.getSubjectMembership(), + SysmlPackage.eINSTANCE.getFramedConcernMembership(), + SysmlPackage.eINSTANCE.getConcernUsage() + ); + assertThat(children).containsAll(expectedChildren); + children.removeAll(expectedChildren); + assertThat(children).isEmpty(); + } + + @DisplayName("Check the list of children that can be created from a FramedConcernMembership") + @Test + public void testFramedConcernMembership() { + List children = new GetChildCreationSwitch().doSwitch(SysmlFactory.eINSTANCE.createFramedConcernMembership()); + List expectedChildren = List.of(SysmlPackage.eINSTANCE.getConcernUsage()); + assertThat(children).containsAll(expectedChildren); + children.removeAll(expectedChildren); + assertThat(children).isEmpty(); + } } diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/explorer/view/ExplorerViewControllerIntegrationTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/explorer/view/ExplorerViewControllerIntegrationTests.java index dc379b5d7..e47832b6a 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/explorer/view/ExplorerViewControllerIntegrationTests.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/explorer/view/ExplorerViewControllerIntegrationTests.java @@ -60,8 +60,10 @@ import org.eclipse.syson.application.data.GeneralViewEmptyTestProjectData; import org.eclipse.syson.application.data.ProjectWithLibraryDependencyContainingLibraryPackageTestProjectData; import org.eclipse.syson.application.data.WithUserLibrariesTestProjectData; +import org.eclipse.syson.sysml.ConcernUsage; import org.eclipse.syson.sysml.ConstraintUsage; import org.eclipse.syson.sysml.Element; +import org.eclipse.syson.sysml.FramedConcernMembership; import org.eclipse.syson.sysml.LibraryPackage; import org.eclipse.syson.sysml.Namespace; import org.eclipse.syson.sysml.RequirementConstraintMembership; @@ -446,11 +448,11 @@ public void testCreateConstraintInRequirement() { Runnable checkConstraintOwnership = () -> { var editingContextFunctionInput = new ExecuteEditingContextFunctionInput(UUID.randomUUID(), ExplorerViewDirectEditTestProjectData.EDITING_CONTEXT_ID, (editingContext, input) -> { - var optionalContraint = this.objectSearchService.getObject(editingContext, newConstraintId.get()); - assertThat(optionalContraint).containsInstanceOf(ConstraintUsage.class); - var constraint = (ConstraintUsage) optionalContraint.get(); + var optionalConstraint = this.objectSearchService.getObject(editingContext, newConstraintId.get()); + assertThat(optionalConstraint).containsInstanceOf(ConstraintUsage.class); + var constraint = (ConstraintUsage) optionalConstraint.get(); assertThat(constraint.getOwningRelationship()).isInstanceOf(RequirementConstraintMembership.class); - return new ExecuteEditingContextFunctionSuccessPayload(input.id(), optionalContraint.get()); + return new ExecuteEditingContextFunctionSuccessPayload(input.id(), optionalConstraint.get()); }); Mono result = this.executeEditingContextFunctionRunner.execute(editingContextFunctionInput); var payload = result.block(); @@ -466,6 +468,55 @@ public void testCreateConstraintInRequirement() { .verify(Duration.ofSeconds(10)); } + @DisplayName("GIVEN the Sirius Explorer View, WHEN creating a concern inside a requirement, THEN the new concern is owned through a FramedConcernMembership") + @GivenSysONServer({ ExplorerViewDirectEditTestProjectData.SCRIPT_PATH }) + @Test + public void testCreateConcernInRequirement() { + var expandedIds = this.getAllTreeItemIds(ExplorerViewDirectEditTestProjectData.EDITING_CONTEXT_ID); + var activatedFilters = List.of(SysONTreeFilterConstants.HIDE_ROOT_NAMESPACES_ID); + var treeRepresentationId = this.representationIdBuilder.buildExplorerRepresentationId(this.sysONTreeViewDescriptionProvider.getDescriptionId(), expandedIds, activatedFilters); + + var treeEventInput = new ExplorerEventInput(UUID.randomUUID(), ExplorerViewDirectEditTestProjectData.EDITING_CONTEXT_ID, treeRepresentationId); + var treeFlux = this.treeEventSubscriptionRunner.run(treeEventInput).flux(); + + var newConcernId = new AtomicReference(null); + + Consumer ignorePayload = (o) -> { + // Ignore the refresh event payload, we will check the actual semantic model content. + }; + + Runnable createChildConstraint = () -> { + var input = new CreateChildInput(UUID.randomUUID(), ExplorerViewDirectEditTestProjectData.EDITING_CONTEXT_ID, ExplorerViewDirectEditTestProjectData.SemanticIds.REQ1_RU_ID, + "SysMLv2EditService-ConcernUsage"); + var result = this.createChildMutationRunner.run(input); + String typename = JsonPath.read(result.data(), "$.data.createChild.__typename"); + assertThat(typename).isEqualTo(CreateChildSuccessPayload.class.getSimpleName()); + String objectId = JsonPath.read(result.data(), "$.data.createChild.object.id"); + newConcernId.set(objectId); + }; + + Runnable checkConcernOwnership = () -> { + var editingContextFunctionInput = new ExecuteEditingContextFunctionInput(UUID.randomUUID(), ExplorerViewDirectEditTestProjectData.EDITING_CONTEXT_ID, (editingContext, input) -> { + var optionalConcern = this.objectSearchService.getObject(editingContext, newConcernId.get()); + assertThat(optionalConcern).containsInstanceOf(ConstraintUsage.class); + var concern = (ConcernUsage) optionalConcern.get(); + assertThat(concern.getOwningRelationship()).isInstanceOf(FramedConcernMembership.class); + return new ExecuteEditingContextFunctionSuccessPayload(input.id(), optionalConcern.get()); + }); + Mono result = this.executeEditingContextFunctionRunner.execute(editingContextFunctionInput); + var payload = result.block(); + assertThat(payload).isInstanceOf(ExecuteEditingContextFunctionSuccessPayload.class); + }; + + StepVerifier.create(treeFlux) + .consumeNextWith(ignorePayload) + .then(createChildConstraint) + .consumeNextWith(ignorePayload) + .then(checkConcernOwnership) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } + private List getAllTreeItemIds(String editingContextId) { ExecuteEditingContextFunctionInput executeEditingContextFunctionInput = new ExecuteEditingContextFunctionInput(UUID.randomUUID(), editingContextId, (editingContext, input) -> { if (editingContext instanceof IEMFEditingContext emfEditingContext) { diff --git a/backend/services/syson-services/src/main/java/org/eclipse/syson/util/GetIntermediateContainerCreationSwitch.java b/backend/services/syson-services/src/main/java/org/eclipse/syson/util/GetIntermediateContainerCreationSwitch.java index 93d25092b..378e57935 100644 --- a/backend/services/syson-services/src/main/java/org/eclipse/syson/util/GetIntermediateContainerCreationSwitch.java +++ b/backend/services/syson-services/src/main/java/org/eclipse/syson/util/GetIntermediateContainerCreationSwitch.java @@ -18,6 +18,7 @@ import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.syson.sysml.Comment; +import org.eclipse.syson.sysml.ConcernUsage; import org.eclipse.syson.sysml.ConstraintUsage; import org.eclipse.syson.sysml.Definition; import org.eclipse.syson.sysml.Documentation; @@ -70,6 +71,19 @@ public Optional caseConstraintUsage(ConstraintUsage object) { return intermediateContainer; } + @Override + public Optional caseConcernUsage(ConcernUsage object) { + Optional intermediateContainer; + if (this.container instanceof RequirementDefinition || this.container instanceof RequirementUsage) { + // SysML v2 8.3.21.5: "A FramedConcernMembership is a RequirementConstraintMembership for a framed ConcernUsage of a + // RequirementDefinition or RequirementUsage." + intermediateContainer = Optional.of(SysmlPackage.eINSTANCE.getFramedConcernMembership()); + } else { + intermediateContainer = this.caseFeature(object); + } + return intermediateContainer; + } + @Override public Optional caseDefinition(Definition object) { return Optional.of(SysmlPackage.eINSTANCE.getOwningMembership()); diff --git a/doc/content/modules/user-manual/pages/release-notes/2026.7.0.adoc b/doc/content/modules/user-manual/pages/release-notes/2026.7.0.adoc index 8baf5785c..cd4249257 100644 --- a/doc/content/modules/user-manual/pages/release-notes/2026.7.0.adoc +++ b/doc/content/modules/user-manual/pages/release-notes/2026.7.0.adoc @@ -41,6 +41,12 @@ a|image::explorer-expression-internals-hidden.png[Internals hidden (default)] a|image::explorer-expression-internals-visible.png[Internals visible] |=== +** The dialog creating a child element provide two more options for `RequirementUsage` or `RequirementDefinition` tree items. +One creates a `ConcernUsage` and another one creates `FramedConcernMembership`. +The one creating a `ConcernUsage` create a `FramedConcernMembership` as an intermediate container. +The one creating a `FramedConcernMembership` only creates the membership. +In both cases, to display the `FramedConcernMembership` deactivate the `Hide Memberships` filter. + == Bug fixes == Improvements