Skip to content

Commit

Permalink
[3447] Add support for For construct in the operations DSL
Browse files Browse the repository at this point in the history
Bug: eclipse-sirius#3447
Signed-off-by: Denis Nikiforov <[email protected]>
  • Loading branch information
AresEkb authored and sbegaudeau committed Jun 3, 2024
1 parent 128d890 commit a0f8d43
Show file tree
Hide file tree
Showing 29 changed files with 1,164 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
=== New Features

- https://github.com/eclipse-sirius/sirius-web/issues/3466[#3466] [gantt] Persist the gantt table columns
- https://github.com/eclipse-sirius/sirius-web/issues/3447[#3447] [view] Add support for For construct in the operations DSL

=== Improvements

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*******************************************************************************
* Copyright (c) 2024 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.sirius.web.application.controllers.diagrams;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;

import com.jayway.jsonpath.JsonPath;

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

import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramRefreshedEventPayload;
import org.eclipse.sirius.components.collaborative.diagrams.dto.InvokeSingleClickOnDiagramElementToolInput;
import org.eclipse.sirius.components.collaborative.diagrams.dto.InvokeSingleClickOnDiagramElementToolSuccessPayload;
import org.eclipse.sirius.components.collaborative.dto.CreateRepresentationInput;
import org.eclipse.sirius.components.diagrams.tests.graphql.DiagramEventSubscriptionRunner;
import org.eclipse.sirius.components.diagrams.tests.graphql.InvokeSingleClickOnDiagramElementToolMutationRunner;
import org.eclipse.sirius.web.AbstractIntegrationTests;
import org.eclipse.sirius.web.data.PapayaIdentifiers;
import org.eclipse.sirius.web.services.api.IGivenCreatedDiagramSubscription;
import org.eclipse.sirius.web.services.api.IGivenInitialServerState;
import org.eclipse.sirius.web.services.diagrams.ModelOperationDiagramDescriptionProvider;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.transaction.annotation.Transactional;

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

/**
* Integration tests of the model operations.
*
* @author sbegaudeau
*/
@Transactional
@SuppressWarnings("checkstyle:MultipleStringLiterals")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { "sirius.web.test.enabled=studio" })
public class ModelOperationDiagramControllerTests extends AbstractIntegrationTests {

@Autowired
private IGivenInitialServerState givenInitialServerState;

@Autowired
private IGivenCreatedDiagramSubscription givenCreatedDiagramSubscription;

@Autowired
private InvokeSingleClickOnDiagramElementToolMutationRunner invokeSingleClickOnDiagramElementToolMutationRunner;

@Autowired
private DiagramEventSubscriptionRunner diagramEventSubscriptionRunner;

@Autowired
private ModelOperationDiagramDescriptionProvider modelOperationDiagramDescriptionProvider;

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

private Flux<DiagramRefreshedEventPayload> givenSubscriptionToModelOperationDiagram() {
var input = new CreateRepresentationInput(
UUID.randomUUID(),
PapayaIdentifiers.PAPAYA_PROJECT.toString(),
this.modelOperationDiagramDescriptionProvider.getRepresentationDescriptionId(),
PapayaIdentifiers.PROJECT_OBJECT.toString(),
"ModelOperationDiagram"
);
return this.givenCreatedDiagramSubscription.createAndSubscribe(input);
}

@Test
@DisplayName("Given a diagram, when a tool with complex model operations is executed, then it works as expected")
@Sql(scripts = {"/scripts/papaya.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = {"/scripts/cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
public void givenDiagramWhenToolWithComplexModelOperationsIsExecutedThenItWorksAsExpected() {
var flux = this.givenSubscriptionToModelOperationDiagram();

var diagramId = new AtomicReference<String>();

Consumer<DiagramRefreshedEventPayload> initialDiagramContentConsumer = payload -> Optional.of(payload)
.map(DiagramRefreshedEventPayload::diagram)
.ifPresentOrElse(diagram -> {
diagramId.set(diagram.getId());
assertThat(diagram.getNodes())
.noneMatch(node -> node.getInsideLabel().getText().equals("a"))
.noneMatch(node -> node.getInsideLabel().getText().equals("c"));
}, () -> fail("Missing diagram"));

Runnable createNode = () -> {
var createNodeToolId = this.modelOperationDiagramDescriptionProvider.getCreateNodeToolId();
var input = new InvokeSingleClickOnDiagramElementToolInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), diagramId.get(), diagramId.get(), createNodeToolId, 0, 0, null);
var result = this.invokeSingleClickOnDiagramElementToolMutationRunner.run(input);

String typename = JsonPath.read(result, "$.data.invokeSingleClickOnDiagramElementTool.__typename");
assertThat(typename).isEqualTo(InvokeSingleClickOnDiagramElementToolSuccessPayload.class.getSimpleName());
};

Consumer<DiagramRefreshedEventPayload> updatedDiagramContentMatcher = payload -> Optional.of(payload)
.map(DiagramRefreshedEventPayload::diagram)
.ifPresentOrElse(diagram -> {
assertThat(diagram.getNodes())
.anyMatch(node -> node.getInsideLabel().getText().equals("a"))
.noneMatch(node -> node.getInsideLabel().getText().equals("b"))
.anyMatch(node -> node.getInsideLabel().getText().equals("c"));
}, () -> fail("Missing diagram"));

StepVerifier.create(flux)
.consumeNextWith(initialDiagramContentConsumer)
.then(createNode)
.consumeNextWith(updatedDiagramContentMatcher)
.thenCancel()
.verify(Duration.ofSeconds(10));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*******************************************************************************
* Copyright (c) 2024 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.sirius.web.services.diagrams;

import java.util.Objects;
import java.util.UUID;

import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.sirius.components.core.api.IEditingContext;
import org.eclipse.sirius.components.core.api.IEditingContextProcessor;
import org.eclipse.sirius.components.emf.ResourceMetadataAdapter;
import org.eclipse.sirius.components.emf.services.IDAdapter;
import org.eclipse.sirius.components.emf.services.JSONResourceFactory;
import org.eclipse.sirius.components.view.View;
import org.eclipse.sirius.components.view.builder.generated.ChangeContextBuilder;
import org.eclipse.sirius.components.view.builder.generated.CreateInstanceBuilder;
import org.eclipse.sirius.components.view.builder.generated.DiagramDescriptionBuilder;
import org.eclipse.sirius.components.view.builder.generated.DiagramPaletteBuilder;
import org.eclipse.sirius.components.view.builder.generated.InsideLabelDescriptionBuilder;
import org.eclipse.sirius.components.view.builder.generated.NodeDescriptionBuilder;
import org.eclipse.sirius.components.view.builder.generated.NodeToolBuilder;
import org.eclipse.sirius.components.view.builder.generated.RectangularNodeStyleDescriptionBuilder;
import org.eclipse.sirius.components.view.builder.generated.ViewBuilder;
import org.eclipse.sirius.components.view.builder.generated.ViewBuilders;
import org.eclipse.sirius.components.view.diagram.DiagramDescription;
import org.eclipse.sirius.components.view.diagram.DiagramFactory;
import org.eclipse.sirius.components.view.diagram.InsideLabelPosition;
import org.eclipse.sirius.components.view.diagram.NodeTool;
import org.eclipse.sirius.components.view.diagram.SynchronizationPolicy;
import org.eclipse.sirius.components.view.emf.diagram.IDiagramIdProvider;
import org.eclipse.sirius.emfjson.resource.JsonResource;
import org.eclipse.sirius.web.application.editingcontext.EditingContext;
import org.eclipse.sirius.web.services.OnStudioTests;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Service;

/**
* Used to provide a view based diagram description to test model operations.
*
* @author sbegaudeau
*/
@Service
@Conditional(OnStudioTests.class)
public class ModelOperationDiagramDescriptionProvider implements IEditingContextProcessor {

private final IDiagramIdProvider diagramIdProvider;

private final View view;

private DiagramDescription diagramDescription;

private NodeTool createNodeTool;


public ModelOperationDiagramDescriptionProvider(IDiagramIdProvider diagramIdProvider) {
this.diagramIdProvider = Objects.requireNonNull(diagramIdProvider);
this.view = this.createView();
}

@Override
public void preProcess(IEditingContext editingContext) {
if (editingContext instanceof EditingContext siriusWebEditingContext) {
siriusWebEditingContext.getViews().add(this.view);
}
}

public String getRepresentationDescriptionId() {
return this.diagramIdProvider.getId(this.diagramDescription);
}

public String getCreateNodeToolId() {
return UUID.nameUUIDFromBytes(EcoreUtil.getURI(this.createNodeTool).toString().getBytes()).toString();
}

private View createView() {
ViewBuilder viewBuilder = new ViewBuilder();
View unsynchronizedView = viewBuilder.build();
unsynchronizedView.getDescriptions().add(this.createDiagramDescription());

unsynchronizedView.eAllContents().forEachRemaining(eObject -> {
eObject.eAdapters().add(new IDAdapter(UUID.nameUUIDFromBytes(EcoreUtil.getURI(eObject).toString().getBytes())));
});

String resourcePath = UUID.nameUUIDFromBytes("ModelOperationDiagramDescription".getBytes()).toString();
JsonResource resource = new JSONResourceFactory().createResourceFromPath(resourcePath);
resource.eAdapters().add(new ResourceMetadataAdapter("ModelOperationDiagramDescription"));
resource.getContents().add(unsynchronizedView);

return unsynchronizedView;
}
private DiagramDescription createDiagramDescription() {
var nodeStyle = new RectangularNodeStyleDescriptionBuilder()
.build();

var insideLabel = new InsideLabelDescriptionBuilder()
.labelExpression("aql:self.name")
.style(DiagramFactory.eINSTANCE.createInsideLabelStyle())
.position(InsideLabelPosition.TOP_CENTER)
.build();

var nodeDescription = new NodeDescriptionBuilder()
.name("Component")
.domainType("papaya:Component")
.semanticCandidatesExpression("aql:self.eContents()")
.insideLabel(insideLabel)
.synchronizationPolicy(SynchronizationPolicy.SYNCHRONIZED)
.style(nodeStyle)
.build();

var createNewComponent = new CreateInstanceBuilder()
.typeName("papaya:Component")
.referenceName("components")
.variableName("newInstance")
.children(
new ViewBuilders().newChangeContext()
.expression("aql:newInstance")
.children(
new ViewBuilders().newSetValue()
.featureName("name")
.valueExpression("aql:newComponentName")
.build()
)
.build()
)
.build();

var createNewComponentForAllValues = new ViewBuilders().newLet()
.valueExpression("aql:Sequence{'a', 'b', 'c'}")
.variableName("newComponentNames")
.children(
new ViewBuilders().newFor()
.expression("aql:newComponentNames")
.iteratorName("newComponentName")
.children(
new ViewBuilders().newIf()
.conditionExpression("aql:newComponentName <> 'b'")
.children(createNewComponent)
.build()
)
.build()
)
.build();

this.createNodeTool = new NodeToolBuilder()
.name("Create Component")
.body(
new ChangeContextBuilder()
.expression("aql:self")
.children(
createNewComponentForAllValues
)
.build()
)
.build();

var diagramPalette = new DiagramPaletteBuilder()
.nodeTools(this.createNodeTool)
.build();

this.diagramDescription = new DiagramDescriptionBuilder()
.name("Diagram")
.titleExpression("aql:'ModelOperationDiagram'")
.domainType("papaya:Project")
.nodeDescriptions(nodeDescription)
.edgeDescriptions()
.palette(diagramPalette)
.autoLayout(false)
.build();

return this.diagramDescription;
}
}
Loading

0 comments on commit a0f8d43

Please sign in to comment.