Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ 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/2235[#2235] [diagrams] Leverage the selection dialog to improve the node tool creating a _frame_ `ConcernUsage` from `RequirementUsage` and `RequirementDefinition` graphical nodes.

== v2026.5.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.eclipse.emf.ecore.EReference;
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.Diagram;
import org.eclipse.sirius.components.view.emf.diagram.IDiagramIdProvider;
Expand All @@ -44,15 +45,19 @@
import org.eclipse.syson.application.controllers.diagrams.testers.ToolTester;
import org.eclipse.syson.application.controllers.utils.TestNameGenerator;
import org.eclipse.syson.application.data.GeneralViewWithTopNodesTestProjectData;
import org.eclipse.syson.diagram.common.view.nodes.FramedConcernCompartmentItemNodeDescription;
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;
import org.eclipse.syson.sysml.ConcernUsage;
import org.eclipse.syson.sysml.Element;
import org.eclipse.syson.sysml.FramedConcernMembership;
import org.eclipse.syson.sysml.PartUsage;
import org.eclipse.syson.sysml.ReferenceSubsetting;
import org.eclipse.syson.sysml.ReferenceUsage;
import org.eclipse.syson.sysml.Specialization;
import org.eclipse.syson.sysml.Subsetting;
Expand Down Expand Up @@ -111,6 +116,9 @@ public class GVSubNodeRequirementCreationTests extends AbstractIntegrationTests
@Autowired
private DiagramComparator diagramComparator;

@Autowired
private IIdentityService identityService;

private NodeCreationTestsService creationTestsService;

private final IDescriptionNameGenerator descriptionNameGenerator = new SDVDescriptionNameGenerator();
Expand Down Expand Up @@ -806,10 +814,10 @@ public void createRequirementUsageSiblingNodes(EClass childEClass, EReference co
.verify(Duration.ofMinutes(10));
}

@DisplayName("GIVEN a Requirement Usage, WHEN creating a new framed concern, THEN a new framed concern is created")
@DisplayName("GIVEN a Requirement Usage, WHEN creating a new framed concern without referencing another concern, THEN a new framed concern is created")
@GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH })
@Test
public void createRequirementUsageFramedConcern() {
public void createRequirementUsageFramedConcernWithoutSelection() {
var flux = this.givenSubscriptionToDiagram();

AtomicReference<Diagram> diagram = new AtomicReference<>();
Expand All @@ -821,7 +829,7 @@ public void createRequirementUsageFramedConcern() {

EClass parentEClass = SysmlPackage.eINSTANCE.getRequirementUsage();
String targetObjectId = GeneralViewWithTopNodesTestProjectData.SemanticIds.REQUIREMENT_USAGE_ID;
Runnable createNodeRunnable = this.creationTestsService.createNode(diagramDescriptionIdProvider, diagram, parentEClass, targetObjectId, "New framed concern");
Runnable createNodeRunnable = this.creationTestsService.createNodeWithSelectionDialogWithoutSelectionProvided(diagramDescriptionIdProvider, diagram, parentEClass, targetObjectId, "New framed concern");
Consumer<Object> diagramCheck = this.diagramCheckerService.siblingNodeGraphicalChecker(diagram, diagramDescriptionIdProvider, SysmlPackage.eINSTANCE.getConcernUsage(), 8, 2);
Runnable semanticCheck = this.semanticCheckerService.checkEditingContext(this.semanticCheckerService.getElementInParentSemanticChecker("requirement", SysmlPackage.eINSTANCE.getRequirementUsage_FramedConcern(), SysmlPackage.eINSTANCE.getConcernUsage()));

Expand All @@ -834,6 +842,74 @@ public void createRequirementUsageFramedConcern() {
.verify(Duration.ofSeconds(10));
}

@DisplayName("GIVEN a Requirement Usage, WHEN creating a new framed concern referencing another concern, THEN a new framed concern is created")
@GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH })
@Test
public void createRequirementUsageFramedConcernWithSelection() {
var flux = this.givenSubscriptionToDiagram();

AtomicReference<Diagram> diagram = new AtomicReference<>();
Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set);

var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID,
SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID);
var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider);

EClass parentEClass = SysmlPackage.eINSTANCE.getRequirementUsage();
String targetObjectId = GeneralViewWithTopNodesTestProjectData.SemanticIds.REQUIREMENT_USAGE_ID;
Runnable createNodeRunnable = this.creationTestsService.createNodeWithSelectionDialogWithSingleSelection(diagramDescriptionIdProvider, diagram, parentEClass, targetObjectId, "New framed concern", GeneralViewWithTopNodesTestProjectData.SemanticIds.CONCERN_USAGE_ID);
Consumer<Object> diagramCheck = assertRefreshedDiagramThat(newDiagram -> {
new CheckDiagramElementCount(this.diagramComparator)
.hasNewNodeCount(1)
.hasNewEdgeCount(1)
.check(diagram.get(), newDiagram);

String newNodeDescriptionName = this.descriptionNameGenerator.getCompartmentItemName(SysmlPackage.eINSTANCE.getRequirementUsage(), SysmlPackage.eINSTANCE.getRequirementUsage_FramedConcern()) + FramedConcernCompartmentItemNodeDescription.COMPARTMENT_ITEM_NAME;
new CheckNodeInCompartment(diagramDescriptionIdProvider, this.diagramComparator)
.withTargetObjectId(targetObjectId)
.withCompartmentName("frames")
.hasNodeDescriptionName(newNodeDescriptionName)
.hasCompartmentCount(0)
.isHidden()
.check(diagram.get(), newDiagram);
});

Consumer<Object> additionalCheck = object -> {
assertThat(object).isInstanceOf(List.class)
.asInstanceOf(type(List.class))
.satisfies(concernUsages -> {
assertThat((List<?>) concernUsages).size().isEqualTo(1);
assertThat(concernUsages.getFirst())
.isInstanceOf(ConcernUsage.class)
.asInstanceOf(type(ConcernUsage.class))
.satisfies(concernUsage -> {
assertThat(concernUsage.eContainer())
.isInstanceOf(FramedConcernMembership.class)
.asInstanceOf(type(FramedConcernMembership.class))
.satisfies(framedConcernMembership -> {
assertThat(this.identityService.getId(framedConcernMembership.getReferencedConcern())).isEqualTo(GeneralViewWithTopNodesTestProjectData.SemanticIds.CONCERN_USAGE_ID);
assertThat(concernUsage.getOwnedRelationship().getFirst())
.isInstanceOf(ReferenceSubsetting.class)
.asInstanceOf(type(ReferenceSubsetting.class))
.satisfies(referenceSubsetting -> {
assertThat(referenceSubsetting.getReferencedFeature()).isEqualTo(framedConcernMembership.getReferencedConcern());
});
});
});
});
};

Runnable semanticCheck = this.semanticCheckerService.checkEditingContext(this.semanticCheckerService.getElementInParentSemanticChecker("requirement", SysmlPackage.eINSTANCE.getRequirementUsage_FramedConcern(), SysmlPackage.eINSTANCE.getConcernUsage(), additionalCheck));

StepVerifier.create(flux)
.consumeNextWith(initialDiagramContentConsumer)
.then(createNodeRunnable)
.consumeNextWith(diagramCheck)
.then(semanticCheck)
.thenCancel()
.verify(Duration.ofSeconds(10));
}

@GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH })
@ParameterizedTest
@MethodSource("requirementUsageChildNodeParameters")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,16 @@ public FramedConcernMembership createFramedConcern(Type type, ConcernUsage conce
var newConcernUsage = SysmlFactory.eINSTANCE.createConcernUsage();
newFramedConcernMembership.getOwnedRelatedElement().add(newConcernUsage);

var newReferenceSubsetting = SysmlFactory.eINSTANCE.createReferenceSubsetting();
newConcernUsage.getOwnedRelationship().add(newReferenceSubsetting);
newReferenceSubsetting.setReferencedFeature(concernUsage);

this.metamodelMutationElementService.initialize(newFramedConcernMembership);
this.metamodelMutationElementService.initialize(newConcernUsage);
this.metamodelMutationElementService.initialize(newReferenceSubsetting);

if (concernUsage != null) {
var newReferenceSubsetting = SysmlFactory.eINSTANCE.createReferenceSubsetting();
newConcernUsage.getOwnedRelationship().add(newReferenceSubsetting);
newReferenceSubsetting.setReferencedFeature(concernUsage);
this.metamodelMutationElementService.initialize(newReferenceSubsetting);
}

return newFramedConcernMembership;
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
import org.eclipse.emf.ecore.EReference;
import org.eclipse.sirius.components.view.builder.IViewDiagramElementFinder;
import org.eclipse.sirius.components.view.builder.providers.IColorProvider;
import org.eclipse.sirius.components.view.builder.providers.INodeToolProvider;
import org.eclipse.sirius.components.view.diagram.DiagramDescription;
import org.eclipse.sirius.components.view.diagram.NodeDescription;
import org.eclipse.syson.diagram.common.view.tools.FramedConcernNodeToolProvider;
import org.eclipse.syson.util.IDescriptionNameGenerator;

/**
Expand Down Expand Up @@ -54,6 +56,11 @@ public void link(DiagramDescription diagramDescription, IViewDiagramElementFinde
});
}

@Override
protected List<INodeToolProvider> getItemCreationToolProviders() {
return List.of(new FramedConcernNodeToolProvider());
}

@Override
protected List<NodeDescription> getDroppableNodes(IViewDiagramElementFinder cache) {
return List.of();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,36 @@ public List<? extends Object> getExhibitStateSelectionDialogChildren(Object sele
return this.getChildrenWithInstancesOf(selectionDialogTreeElement, editingContext, expandedIds, List.of(SysmlPackage.eINSTANCE.getStateUsage()));
}

/**
* Provides the root elements in the tree of the selection dialog for presenting all existing ConcernUsage.
*
* @param editingContext
* the (non-{@code null}) {@link IEditingContext}.
* @return the (non-{@code null}) {@link List} of all {@link Resource} and {@link ISysONExplorerFragment} that
* contain at least one {@link org.eclipse.syson.sysml.ConcernUsage}.
*/
public List<Object> getConcernReferenceSelectionDialogElements(IEditingContext editingContext) {
return this.getAllResourcesWithInstancesOf(editingContext, List.of(SysmlPackage.eINSTANCE.getConcernUsage()));
}

/**
* Provides the children of element in the tree of the selection dialog for presenting all existing ConcernUsage.
*
* @param selectionDialogTreeElement
* a (non-{@code null}) selection dialog tree element.
* @param editingContext
* the (non-{@code null}) {@link IEditingContext}.
* @param expandedIds
* the list of already expanded treeItems, by their Ids.
* @return the (non-{@code null}) {@link List} of all children that contain (possibly indirectly) an
* {@link org.eclipse.syson.sysml.ConcernUsage}.
*/
public List<? extends Object> getConcernReferenceSelectionDialogChildren(Object selectionDialogTreeElement, IEditingContext editingContext, List<String> expandedIds) {
return this.getChildrenWithInstancesOf(selectionDialogTreeElement, editingContext, expandedIds, List.of(SysmlPackage.eINSTANCE.getConcernUsage()));
}



/**
* Provides the root elements in the tree of the selection dialog for the any creation tool.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*******************************************************************************
* Copyright (c) 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.syson.diagram.common.view.tools;

import org.eclipse.sirius.components.collaborative.diagrams.DiagramContext;
import org.eclipse.sirius.components.core.api.IEditingContext;
import org.eclipse.sirius.components.diagrams.Node;
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.sirius.components.view.emf.diagram.ViewDiagramDescriptionConverter;
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.ServiceMethod;
import org.eclipse.syson.util.SysMLMetamodelHelper;

/**
* Used to create a framed {@link org.eclipse.syson.sysml.ConcernUsage} in a {@link org.eclipse.syson.sysml.RequirementUsage} or a {@link org.eclipse.syson.sysml.RequirementDefinition}.
*
* @author gcoutable
*/
public class FramedConcernNodeToolProvider implements INodeToolProvider {

private final DiagramBuilders diagramBuilderHelper = new DiagramBuilders();

private final ViewBuilders viewBuilderHelper = new ViewBuilders();

@Override
public NodeTool create(IViewDiagramElementFinder cache) {
var builder = this.diagramBuilderHelper.newNodeTool();

var updateExposedElements = this.viewBuilderHelper.newChangeContext()
.expression(ServiceMethod.of4(DiagramMutationAQLService::expose).aqlSelf(IEditingContext.EDITING_CONTEXT, DiagramContext.DIAGRAM_CONTEXT, Node.SELECTED_NODE,
ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE));

var selectedElementToExpose = this.viewBuilderHelper.newChangeContext()
.expression(AQLConstants.AQL_SELF + ".referencedConcern")
.children(updateExposedElements.build());

var body = this.viewBuilderHelper.newChangeContext()
.expression(ServiceMethod.of1(ModelMutationAQLService::createFramedConcern).aqlSelf("selectedObject"))
.children(selectedElementToExpose.build());

return builder
.name(this.getToolName())
.iconURLsExpression("/icons/full/obj16/ConcernUsage.svg")
.body(body.build())
.dialogDescription(this.getSelectionDialogDescription())
.build();
}

private String getToolName() {
return "New framed concern";
}

private DialogDescription getSelectionDialogDescription() {
String concernUsageType = SysMLMetamodelHelper.buildQualifiedName(SysmlPackage.eINSTANCE.getConcernUsage());

var selectionDialogTree = this.diagramBuilderHelper.newSelectionDialogTreeDescription()
.isSelectableExpression(AQLConstants.AQL_SELF + ".oclIsKindOf(" + concernUsageType + ")")
.elementsExpression(ServiceMethod.of0(ViewToolService::getConcernReferenceSelectionDialogElements).aql(IEditingContext.EDITING_CONTEXT))
.childrenExpression(ServiceMethod.of2(ViewToolService::getConcernReferenceSelectionDialogChildren).aqlSelf(IEditingContext.EDITING_CONTEXT, TreeRenderer.EXPANDED))
.build();

return this.diagramBuilderHelper.newSelectionDialogDescription()
.selectionDialogTreeDescription(selectionDialogTree)
.defaultTitleExpression(this.getToolName())
.noSelectionTitleExpression(this.getToolName())
.withSelectionTitleExpression(this.getToolName())
.descriptionExpression("Create a framed Concern")
.noSelectionActionLabelExpression("Create a new framed Concern")
.noSelectionActionDescriptionExpression("Create a new framed Concern without referencing an existing Concern")
.withSelectionActionLabelExpression("Select an existing Concern to frame")
.withSelectionActionDescriptionExpression("Create a new framed Concern referencing the selected Concern")
.noSelectionActionStatusMessageExpression("It will create a new framed Concern without referencing an existing Concern")
.selectionRequiredWithoutSelectionStatusMessageExpression("Select on Concern to frame")
.selectionRequiredWithSelectionStatusMessageExpression(AQLConstants.AQL + "'It will create a new Concern referencing ' + selectedObjects->first().name")
.optional(true)
.build();
}
}
Loading
Loading