forked from eclipse-sirius/sirius-web
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[3447] Add support for For construct in the operations DSL
Bug: eclipse-sirius#3447 Signed-off-by: Denis Nikiforov <[email protected]>
- Loading branch information
1 parent
128d890
commit a0f8d43
Showing
29 changed files
with
1,164 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
133 changes: 133 additions & 0 deletions
133
...pse/sirius/web/application/controllers/diagrams/ModelOperationDiagramControllerTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
182 changes: 182 additions & 0 deletions
182
...va/org/eclipse/sirius/web/services/diagrams/ModelOperationDiagramDescriptionProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.