Skip to content
Merged
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 @@ -71,6 +71,7 @@ It leverages the selection dialog to either create an _occurrence timeslice/snap
- https://github.com/eclipse-syson/syson/issues/2250[#2250] [diagrams] Leverage the selection dialog to improve the graphical node tools creating a _require_ `ConstraintUsage`, or an _assume_ `ConstraintUsage`, from `RequirementUsage` and `RequirementDefinition` graphical nodes.
- https://github.com/eclipse-syson/syson/issues/2254[#2254] [diagrams] Add the support for _assume_ and _require_ graphical edges.
- https://github.com/eclipse-syson/syson/issues/2260[#2260] [diagrams] Add the _New Assume Constraint_ or _New Require Constraint_ edge tools to create _assume_ and _require_ graphical edges.
- https://github.com/eclipse-syson/syson/issues/2113[#2113] [diagrams] Handle start/end/merge/decision... graphical nodes on Action Flow View diagram background

== v2026.5.0

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*******************************************************************************
* 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.application.migration;

import org.eclipse.sirius.components.collaborative.representations.migration.IRepresentationMigrationParticipant;
import org.eclipse.sirius.components.core.api.IEditingContext;
import org.springframework.stereotype.Service;

import tools.jackson.databind.JsonNode;
import tools.jackson.databind.node.ObjectNode;

/**
* Migration participant used to migrate start, done actions and control nodes elements previous to 2026.7.0.
*
* We need to update descriptionId, because it has been changed due to VSM description change.
*
* @author Jerome Gout
*/
@Service
public class StartDoneAndControlNodeMigrationParticipant implements IRepresentationMigrationParticipant {

public static final String PARTICIPANT_VERSION = "2026.7.0-202606240000";
public static final String DESCRIPTION_ID = "descriptionId";

public static final String OLD_START_DESCRIPTION_ID = "siriusComponents://nodeDescription?sourceKind=view&sourceId=8dcd14b0-6259-3193-ad2c-743f394c68e4&sourceElementId=bf75f645-8a6d-30d9-9514-4e9738e212a3";
public static final String NEW_START_DESCRIPTION_ID = "siriusComponents://nodeDescription?sourceKind=view&sourceId=8dcd14b0-6259-3193-ad2c-743f394c68e4&sourceElementId=596f7e8d-ba35-38d6-937f-011e05b74063";

public static final String OLD_DONE_DESCRIPTION_ID = "siriusComponents://nodeDescription?sourceKind=view&sourceId=8dcd14b0-6259-3193-ad2c-743f394c68e4&sourceElementId=03789d5f-51d6-3f66-a496-146b1685930c";
public static final String NEW_DONE_DESCRIPTION_ID = "siriusComponents://nodeDescription?sourceKind=view&sourceId=8dcd14b0-6259-3193-ad2c-743f394c68e4&sourceElementId=e7465fdc-fec8-38da-b7c0-26ba953b96de";

public static final String OLD_DECISION_DESCRIPTION_ID = "siriusComponents://nodeDescription?sourceKind=view&sourceId=8dcd14b0-6259-3193-ad2c-743f394c68e4&sourceElementId=d6e7c035-98ae-3fcf-a5a8-a8e502cd447e";
public static final String NEW_DECISION_DESCRIPTION_ID = "siriusComponents://nodeDescription?sourceKind=view&sourceId=8dcd14b0-6259-3193-ad2c-743f394c68e4&sourceElementId=b2f1248d-2a25-37a2-8822-9e21ed709c72";

public static final String OLD_FORK_DESCRIPTION_ID = "siriusComponents://nodeDescription?sourceKind=view&sourceId=8dcd14b0-6259-3193-ad2c-743f394c68e4&sourceElementId=46dc7020-1788-3413-967d-e1e3053349ca";
public static final String NEW_FORK_DESCRIPTION_ID = "siriusComponents://nodeDescription?sourceKind=view&sourceId=8dcd14b0-6259-3193-ad2c-743f394c68e4&sourceElementId=e940b643-cce5-3904-b785-fb3814493642";

public static final String OLD_JOIN_DESCRIPTION_ID = "siriusComponents://nodeDescription?sourceKind=view&sourceId=8dcd14b0-6259-3193-ad2c-743f394c68e4&sourceElementId=ae252080-4a68-3129-970b-2d049bb3e5a3";
public static final String NEW_JOIN_DESCRIPTION_ID = "siriusComponents://nodeDescription?sourceKind=view&sourceId=8dcd14b0-6259-3193-ad2c-743f394c68e4&sourceElementId=963e573a-d0ff-3134-9e96-9ce55e3384ec";

public static final String OLD_MERGE_DESCRIPTION_ID = "siriusComponents://nodeDescription?sourceKind=view&sourceId=8dcd14b0-6259-3193-ad2c-743f394c68e4&sourceElementId=91f597c3-96b8-3a9e-8b30-99e5401952fa";
public static final String NEW_MERGE_DESCRIPTION_ID = "siriusComponents://nodeDescription?sourceKind=view&sourceId=8dcd14b0-6259-3193-ad2c-743f394c68e4&sourceElementId=d47553da-9705-3eca-99ea-dc05ff972f55";

@Override
public String getVersion() {
return PARTICIPANT_VERSION;
}

@Override
public String getKind() {
return "siriusComponents://representation?type=Diagram";
}

@Override
public void replaceJsonNode(IEditingContext editingContext, ObjectNode root, String currentAttribute, JsonNode currentValue) {
if (currentAttribute.equals(DESCRIPTION_ID) && currentValue.isString()) {
if (OLD_START_DESCRIPTION_ID.equals(currentValue.asString())) {
root.put(DESCRIPTION_ID, NEW_START_DESCRIPTION_ID);
}
if (OLD_DONE_DESCRIPTION_ID.equals(currentValue.asString())) {
root.put(DESCRIPTION_ID, NEW_DONE_DESCRIPTION_ID);
}
if (OLD_DECISION_DESCRIPTION_ID.equals(currentValue.asString())) {
root.put(DESCRIPTION_ID, NEW_DECISION_DESCRIPTION_ID);
}
if (OLD_FORK_DESCRIPTION_ID.equals(currentValue.asString())) {
root.put(DESCRIPTION_ID, NEW_FORK_DESCRIPTION_ID);
}
if (OLD_JOIN_DESCRIPTION_ID.equals(currentValue.asString())) {
root.put(DESCRIPTION_ID, NEW_JOIN_DESCRIPTION_ID);
}
if (OLD_MERGE_DESCRIPTION_ID.equals(currentValue.asString())) {
root.put(DESCRIPTION_ID, NEW_MERGE_DESCRIPTION_ID);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*******************************************************************************
* 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.application.controllers.diagrams.actionflow.view;

import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.sirius.components.diagrams.tests.DiagramEventPayloadConsumer.assertRefreshedDiagramThat;

import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Stream;

import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramEventInput;
import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramRefreshedEventPayload;
import org.eclipse.sirius.components.diagrams.Diagram;
import org.eclipse.sirius.components.view.emf.diagram.IDiagramIdProvider;
import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState;
import org.eclipse.syson.AbstractIntegrationTests;
import org.eclipse.syson.GivenSysONServer;
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.ToolTester;
import org.eclipse.syson.application.data.ActionFlowViewInsideActionUsageEmptyTestProjectData;
import org.eclipse.syson.services.diagrams.DiagramComparator;
import org.eclipse.syson.services.diagrams.DiagramDescriptionIdProvider;
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.SysmlPackage;
import org.eclipse.syson.util.SysONRepresentationDescriptionIdentifiers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;

/**
* Tests the creation of control nodes (Decision, Fork, Join and Merge) as well as Start and Done actions in a Action Flow view diagram background when it is contained by an ActionUsage.
*
* @author Jerome Gout
*/
@Transactional
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AFVAddTopControlNodeActionTests extends AbstractIntegrationTests {

@Autowired
private IGivenInitialServerState givenInitialServerState;

@Autowired
private IGivenDiagramDescription givenDiagramDescription;

@Autowired
private IGivenDiagramSubscription givenDiagramSubscription;

@Autowired
private IDiagramIdProvider diagramIdProvider;

@Autowired
private ToolTester toolTester;

@Autowired
private DiagramComparator diagramComparator;

private final SDVDescriptionNameGenerator descriptionNameGenerator = new SDVDescriptionNameGenerator();

private static Stream<Arguments> topControlNodeParameters() {
return Stream.of(
Arguments.of("New Start Action", "StartAction"),
Arguments.of("New Done Action", "DoneAction"),
Arguments.of("New Decision", SysmlPackage.eINSTANCE.getDecisionNode().getName()),
Arguments.of("New Fork", SysmlPackage.eINSTANCE.getForkNode().getName()),
Arguments.of("New Join", SysmlPackage.eINSTANCE.getJoinNode().getName()),
Arguments.of("New Merge", SysmlPackage.eINSTANCE.getMergeNode().getName())
);
}

private Flux<DiagramRefreshedEventPayload> givenSubscriptionToDiagram() {
var diagramEventInput = new DiagramEventInput(UUID.randomUUID(),
ActionFlowViewInsideActionUsageEmptyTestProjectData.EDITING_CONTEXT_ID,
ActionFlowViewInsideActionUsageEmptyTestProjectData.GraphicalIds.AFV_IN_ACTION_DIAGRAM_ID);
return this.givenDiagramSubscription.subscribe(diagramEventInput);
}

@BeforeEach
public void beforeEach() {
this.givenInitialServerState.initialize();
}

@DisplayName("GIVEN an ActionFlow View diagram, WHEN control node creation tool is requested on digram background, THEN a new control node is created")
@GivenSysONServer({ ActionFlowViewInsideActionUsageEmptyTestProjectData.SCRIPT_PATH })
@ParameterizedTest
@MethodSource("topControlNodeParameters")
public void givenAnAFVDiagramWhenControlNodeCreationToolIsRequestedOnDiagramBackgroundThenANewControlNodeIsCreated(String toolLabel, String nodeName) {
var flux = this.givenSubscriptionToDiagram();

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

String creationToolId = diagramDescriptionIdProvider.getDiagramCreationToolId(toolLabel);
assertThat(creationToolId).as("The tool '" + toolLabel + "' should exist on diagram background").isNotNull();

var diagram = new AtomicReference<Diagram>();

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

Runnable createNodeRunnable = () -> {
this.toolTester.invokeTool(ActionFlowViewInsideActionUsageEmptyTestProjectData.EDITING_CONTEXT_ID, diagram, null, creationToolId);
};

Consumer<Object> diagramCheck = assertRefreshedDiagramThat(newDiagram -> {
var initialDiagram = diagram.get();
new CheckDiagramElementCount(this.diagramComparator)
.hasNewEdgeCount(0)
.hasNewNodeCount(1)
.check(initialDiagram, newDiagram);

new CheckNodeOnDiagram(diagramDescriptionIdProvider, this.diagramComparator)
.hasNodeDescriptionName(this.descriptionNameGenerator.getNodeName(nodeName))
.check(initialDiagram, newDiagram);
});

StepVerifier.create(flux)
.consumeNextWith(initialDiagramContentConsumer)
.then(createNodeRunnable)
.consumeNextWith(diagramCheck)
.thenCancel()
.verify(Duration.ofSeconds(10));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ public class GVAddExistingElementsTests extends AbstractIntegrationTests {

private static final String PART2 = "part2";

private static final String SUCCESSION = "SuccessionAsUsage";

private static final String ATTRIBUTE_DEFINITION = "AttributeDefinition1";

private static final String NODE_SHOULD_BE_ON_DIAGRAM_MESSAGE = "Node {0} should exist on the diagram";
Expand Down Expand Up @@ -166,15 +168,17 @@ public void addExistingElementsRecursiveOnDiagram() {
Runnable addExistingElementTool = () -> this.nodeCreationTester.invokeTool(GeneralViewAddExistingElementsTestProjectData.EDITING_CONTEXT_ID, diagram, addExistingElementToolId);

Consumer<Object> updatedDiagramConsumer = assertRefreshedDiagramThat(newDiagram -> {
assertThat(newDiagram.getNodes()).as("7 root nodes should be visible on the diagram").hasSize(7);
assertThat(newDiagram.getNodes()).as("8 root nodes should be visible on the diagram").hasSize(8);
assertThat(newDiagram.getEdges().stream().filter(e -> ViewModifier.Normal.equals(e.getState())).toList())
.as("3 edges should be visible on the diagram").hasSize(3)
.as("4 edges should be visible on the diagram").hasSize(4)
.as("The diagram should contain a composite edge between part2 and part1")
.anyMatch(edge -> edge.getTargetObjectLabel().equals(PART1))
.as("The diagram should contain a composite edge between action1 and action2")
.anyMatch(edge -> edge.getTargetObjectLabel().equals(ACTION1))
.as("The diagram should contain a composite edge between action2 and action3")
.anyMatch(edge -> edge.getTargetObjectLabel().equals(ACTION2));
.anyMatch(edge -> edge.getTargetObjectLabel().equals(ACTION2))
.as("The diagram should contain a SuccessionAsUsage edge between start and action2")
.anyMatch(edge -> edge.getTargetObjectLabel().equals(SUCCESSION));
assertThat(newDiagram.getNodes())
.as(MessageFormat.format(NODE_SHOULD_BE_ON_DIAGRAM_MESSAGE, PACKAGE1))
.anyMatch(n -> Objects.equals(n.getTargetObjectLabel(), PACKAGE1))
Expand All @@ -185,7 +189,9 @@ public void addExistingElementsRecursiveOnDiagram() {
.as(MessageFormat.format(NODE_SHOULD_BE_ON_DIAGRAM_MESSAGE, PART2))
.anyMatch(n -> Objects.equals(n.getTargetObjectLabel(), PART2))
.as(MessageFormat.format(NODE_SHOULD_BE_ON_DIAGRAM_MESSAGE, "RequirementUsage"))
.anyMatch(n -> Objects.equals(n.getTargetObjectLabel(), "RequirementUsage"));
.anyMatch(n -> Objects.equals(n.getTargetObjectLabel(), "RequirementUsage"))
.as(MessageFormat.format(NODE_SHOULD_BE_ON_DIAGRAM_MESSAGE, "start"))
.anyMatch(n -> Objects.equals(n.getTargetObjectLabel(), "start"));

this.checkPackageNode(newDiagram);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,10 @@ private static Stream<Arguments> actionDefinitionBorderAndChildNodeParameters()

private static Stream<Arguments> actionUsageFreeFormNodeParameters() {
return Stream.of(
Arguments.of(SysmlPackage.eINSTANCE.getDecisionNode(), SysmlPackage.eINSTANCE.getUsage_NestedAction(), 0, 0, 0),
Arguments.of(SysmlPackage.eINSTANCE.getForkNode(), SysmlPackage.eINSTANCE.getUsage_NestedAction(), 0, 0, 0),
Arguments.of(SysmlPackage.eINSTANCE.getJoinNode(), SysmlPackage.eINSTANCE.getUsage_NestedAction(), 0, 0, 0),
Arguments.of(SysmlPackage.eINSTANCE.getMergeNode(), SysmlPackage.eINSTANCE.getUsage_NestedAction(), 0, 0, 0),
Arguments.of(SysmlPackage.eINSTANCE.getDecisionNode(), SysmlPackage.eINSTANCE.getUsage_NestedAction(), 1, 0, 1),
Arguments.of(SysmlPackage.eINSTANCE.getForkNode(), SysmlPackage.eINSTANCE.getUsage_NestedAction(), 1, 0, 1),
Arguments.of(SysmlPackage.eINSTANCE.getJoinNode(), SysmlPackage.eINSTANCE.getUsage_NestedAction(), 1, 0, 1),
Arguments.of(SysmlPackage.eINSTANCE.getMergeNode(), SysmlPackage.eINSTANCE.getUsage_NestedAction(), 1, 0, 1),
Arguments.of(SysmlPackage.eINSTANCE.getAcceptActionUsage(), SysmlPackage.eINSTANCE.getUsage_NestedAction(), 5, 2, 1))
.map(TestNameGenerator::namedArguments);
}
Expand All @@ -191,10 +191,10 @@ private static Stream<Arguments> actionDefinitionSiblingNodeParameters() {

private static Stream<Arguments> actionDefinitionFreeFormNodeParameters() {
return Stream.of(
Arguments.of(SysmlPackage.eINSTANCE.getDecisionNode(), SysmlPackage.eINSTANCE.getDefinition_OwnedAction(), 0, 0, 0),
Arguments.of(SysmlPackage.eINSTANCE.getForkNode(), SysmlPackage.eINSTANCE.getDefinition_OwnedAction(), 0, 0, 0),
Arguments.of(SysmlPackage.eINSTANCE.getJoinNode(), SysmlPackage.eINSTANCE.getDefinition_OwnedAction(), 0, 0, 0),
Arguments.of(SysmlPackage.eINSTANCE.getMergeNode(), SysmlPackage.eINSTANCE.getDefinition_OwnedAction(), 0, 0, 0),
Arguments.of(SysmlPackage.eINSTANCE.getDecisionNode(), SysmlPackage.eINSTANCE.getDefinition_OwnedAction(), 1, 0, 1),
Arguments.of(SysmlPackage.eINSTANCE.getForkNode(), SysmlPackage.eINSTANCE.getDefinition_OwnedAction(), 1, 0, 1),
Arguments.of(SysmlPackage.eINSTANCE.getJoinNode(), SysmlPackage.eINSTANCE.getDefinition_OwnedAction(), 1, 0, 1),
Arguments.of(SysmlPackage.eINSTANCE.getMergeNode(), SysmlPackage.eINSTANCE.getDefinition_OwnedAction(), 1, 0, 1),
Arguments.of(SysmlPackage.eINSTANCE.getAcceptActionUsage(), SysmlPackage.eINSTANCE.getDefinition_OwnedAction(), 5, 2, 1))
.map(TestNameGenerator::namedArguments);
}
Expand Down
Loading
Loading