Skip to content

Commit

Permalink
[4095] Add conditional tree item label element description
Browse files Browse the repository at this point in the history
Bug: eclipse-sirius#4095
Signed-off-by: Jerome Gout <[email protected]>
  • Loading branch information
jerome-obeo committed Oct 18, 2024
1 parent 75f2a22 commit c223237
Show file tree
Hide file tree
Showing 37 changed files with 2,389 additions and 1,481 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ This will allow specifier to create images that fir perfectly in the project tem
- https://github.com/eclipse-sirius/sirius-web/issues/2163[#2163] [form] Make EMF default form support non changeable features
- https://github.com/eclipse-sirius/sirius-web/issues/4086[#4086] [form] Wrap widget returned by property section in a div with a specific classname
- https://github.com/eclipse-sirius/sirius-web/issues/4088[#4088] Change tree item context menu entries internal management
- https://github.com/eclipse-sirius/sirius-web/issues/4095[#4095] Add conditional tree item label element description


== v2024.9.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,13 @@ public Object toggleAbstractEntity(Object self) {
return self;
}

public boolean isAbstractEntity(Object self) {
if (self instanceof Entity entity) {
return entity.isAbstract();
}
return false;
}

/**
* Wrapper for {@link org.eclipse.emf.ecore.EStructuralFeature.Setting} to avoid AQL interpreter sees this element as an list.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public class DomainViewTreeDescriptionProvider implements IEditingContextProcess

private static final String AQL_SELF_IS_AN_ENTITY = "aql:self.oclIsKindOf(domain::Entity)";

private static final String AQL_SELF_GET_TREE_ITEM_LABEL = "aql:self.getTreeItemLabel()";

private final IStudioCapableEditingContextPredicate studioCapableEditingContextPredicate;

private final ViewBuilders viewBuilderHelper = new ViewBuilders();
Expand Down Expand Up @@ -135,7 +137,6 @@ private TreeDescription domainExplorerDescription(TextStylePalette domainTextSty
return this.viewDescription;
}


private TextStylePalette createTextStylePalette() {
return new ViewBuilders()
.newTextStylePalette()
Expand All @@ -154,35 +155,44 @@ private TreeItemLabelDescription attributeStyle(TextStylePalette textStylePalett
}

private TreeItemLabelDescription entityStyle(TextStylePalette textStylePalette) {
return new TreeBuilders()
.newTreeItemLabelDescription()
return new TreeBuilders().newTreeItemLabelDescription()
.name("entity style")
.preconditionExpression(AQL_SELF_IS_AN_ENTITY)
.children(this.getEntityKeyFragment(textStylePalette), this.getEntityValueFragment(textStylePalette))
.children(
this.getEntityKeyFragment(textStylePalette),
this.getEntityValueFragment(textStylePalette),
this.getAbstractEntityValueFragment(textStylePalette))
.build();
}

private TreeItemLabelDescription defaultStyle() {
return new TreeBuilders()
.newTreeItemLabelDescription()
return new TreeBuilders().newTreeItemLabelDescription()
.name("default style")
.preconditionExpression("aql:true")
.children(new TreeBuilders()
.newTreeItemLabelFragmentDescription()
.labelExpression("aql:self.getTreeItemLabel()")
.children(new TreeBuilders().newTreeItemLabelFragmentDescription()
.labelExpression(AQL_SELF_GET_TREE_ITEM_LABEL)
// no style specified => default one will be chosen
.build()
)
.build())
.build();
}

private TreeItemLabelElementDescription getEntityValueFragment(TextStylePalette textStylePalette) {
return new TreeBuilders().newTreeItemLabelFragmentDescription()
.labelExpression("aql:self.getTreeItemLabel()")
.labelExpression(AQL_SELF_GET_TREE_ITEM_LABEL)
.style(this.getTextStyleByName(textStylePalette, NORMAL_TEXT_STYLE_NAME))
.build();
}

private TreeItemLabelElementDescription getAbstractEntityValueFragment(TextStylePalette textStylePalette) {
return new TreeBuilders().newIfTreeItemLabelElementDescription()
.predicateExpression("aql:self.isAbstractEntity()")
.children(new TreeBuilders().newTreeItemLabelFragmentDescription()
.labelExpression(" [abstract]")
.style(this.getTextStyleByName(textStylePalette, BLUE_ITALIC_TEXT_STYLE_NAME))
.build())
.build();
}

private TreeItemLabelElementDescription getEntityKeyFragment(TextStylePalette textStylePalette) {
return new TreeBuilders().newTreeItemLabelFragmentDescription()
.labelExpression("aql:'[Entity] '")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@
import org.eclipse.sirius.components.collaborative.trees.dto.InvokeSingleClickTreeItemContextMenuEntryInput;
import org.eclipse.sirius.components.collaborative.trees.dto.TreeRefreshedEventPayload;
import org.eclipse.sirius.components.core.api.SuccessPayload;
import org.eclipse.sirius.components.trees.Tree;
import org.eclipse.sirius.web.AbstractIntegrationTests;
import org.eclipse.sirius.web.application.views.explorer.ExplorerEventInput;
import org.eclipse.sirius.web.application.views.explorer.services.ExplorerDescriptionProvider;
import org.eclipse.sirius.web.data.StudioIdentifiers;
import org.eclipse.sirius.web.tests.graphql.ExplorerDescriptionsQueryRunner;
import org.eclipse.sirius.web.tests.graphql.FetchTreeItemContextMenuEntryDataQueryRunner;
import org.eclipse.sirius.web.tests.graphql.SingleClickTreeItemContextMenuEntryMutationRunner;
import org.eclipse.sirius.web.tests.graphql.TreeItemContextMenuQueryRunner;
import org.eclipse.sirius.web.tests.graphql.FetchTreeItemContextMenuEntryDataQueryRunner;
import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState;
import org.eclipse.sirius.web.tests.services.explorer.ExplorerEventSubscriptionRunner;
import org.eclipse.sirius.web.tests.services.representation.RepresentationIdBuilder;
Expand Down Expand Up @@ -80,7 +81,7 @@ public class TreeItemContextMenuControllerTests extends AbstractIntegrationTests
private RepresentationIdBuilder representationIdBuilder;

@Autowired
private ExplorerEventSubscriptionRunner treeEventSubscriptionRunner;
private ExplorerEventSubscriptionRunner explorerEventSubscriptionRunner;

@Autowired
private ExplorerDescriptionsQueryRunner explorerDescriptionsQueryRunner;
Expand Down Expand Up @@ -110,23 +111,16 @@ public void givenAStudioWhenTheContextMenuActionsAreRequestedOnAnEntityThenTheCo
explorerDescriptionId.set(explorerIds.get(1));

var explorerRepresentationId = this.representationIdBuilder.buildExplorerRepresentationId(explorerDescriptionId.get(), List.of(StudioIdentifiers.DOMAIN_DOCUMENT.toString(), StudioIdentifiers.DOMAIN_OBJECT.toString(), ROOT_ENTITY_ID), List.of());
var input = new ExplorerEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), explorerRepresentationId);
var flux = this.treeEventSubscriptionRunner.run(input);
var input = new ExplorerEventInput(UUID.randomUUID(), StudioIdentifiers.SAMPLE_STUDIO_PROJECT.toString(), explorerRepresentationId);
var flux = this.explorerEventSubscriptionRunner.run(input);

// 2- Retrieve the representation id (the id of DSL Domain explorer example tree)
var treeId = new AtomicReference<String>();

Consumer<Object> initialTreeContentConsumer = object -> Optional.of(object)
.filter(DataFetcherResult.class::isInstance)
.map(DataFetcherResult.class::cast)
.map(DataFetcherResult::getData)
.filter(TreeRefreshedEventPayload.class::isInstance)
.map(TreeRefreshedEventPayload.class::cast)
.map(TreeRefreshedEventPayload::tree)
.ifPresentOrElse(tree -> {
assertThat(tree).isNotNull();
treeId.set(tree.getId());
}, () -> fail("Missing tree"));
Consumer<Object> initialTreeContentConsumer = this.getTreeSubscriptionConsumer(tree -> {
assertThat(tree).isNotNull();
treeId.set(tree.getId());
});

var helpId = new AtomicReference<String>();
var toggleAbstractAction = new AtomicReference<String>();
Expand Down Expand Up @@ -190,4 +184,15 @@ public void givenAStudioWhenTheContextMenuActionsAreRequestedOnAnEntityThenTheCo
.thenCancel()
.verify(Duration.ofSeconds(30));
}

private Consumer<Object> getTreeSubscriptionConsumer(Consumer<Tree> treeConsumer) {
return object -> Optional.of(object)
.filter(DataFetcherResult.class::isInstance)
.map(DataFetcherResult.class::cast)
.map(DataFetcherResult::getData)
.filter(TreeRefreshedEventPayload.class::isInstance)
.map(TreeRefreshedEventPayload.class::cast)
.map(TreeRefreshedEventPayload::tree)
.ifPresentOrElse(treeConsumer, () -> fail("Missing tree"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*******************************************************************************
* 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.trees;

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.List;
import java.util.Map;
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.trees.dto.TreeRefreshedEventPayload;
import org.eclipse.sirius.components.core.api.labels.StyledString;
import org.eclipse.sirius.components.trees.Tree;
import org.eclipse.sirius.components.trees.TreeItem;
import org.eclipse.sirius.web.AbstractIntegrationTests;
import org.eclipse.sirius.web.application.views.explorer.ExplorerEventInput;
import org.eclipse.sirius.web.application.views.explorer.services.ExplorerDescriptionProvider;
import org.eclipse.sirius.web.data.StudioIdentifiers;
import org.eclipse.sirius.web.tests.graphql.ExplorerDescriptionsQueryRunner;
import org.eclipse.sirius.web.tests.graphql.FetchTreeItemContextMenuEntryDataQueryRunner;
import org.eclipse.sirius.web.tests.graphql.SingleClickTreeItemContextMenuEntryMutationRunner;
import org.eclipse.sirius.web.tests.graphql.TreeItemContextMenuQueryRunner;
import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState;
import org.eclipse.sirius.web.tests.services.explorer.ExplorerEventSubscriptionRunner;
import org.eclipse.sirius.web.tests.services.representation.RepresentationIdBuilder;
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 graphql.execution.DataFetcherResult;
import reactor.test.StepVerifier;

/**
* Integration tests of the tree item label description controllers.
*
* @author Jerome Gout
*/
@Transactional
@SuppressWarnings("checkstyle:MultipleStringLiterals")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { "sirius.web.test.enabled=studio" })
public class TreeItemLabelDescriptionControllerTests extends AbstractIntegrationTests {

private static final String ROOT_ENTITY_ID = "c341bf91-d315-4264-9787-c51b121a6375";

@Autowired
private IGivenInitialServerState givenInitialServerState;

@Autowired
private TreeItemContextMenuQueryRunner treeItemContextMenuQueryRunner;

@Autowired
private FetchTreeItemContextMenuEntryDataQueryRunner treeItemFetchContextActionDataQueryRunner;

@Autowired
private SingleClickTreeItemContextMenuEntryMutationRunner singleClickTreeItemContexteMenuEntryMutationRunner;

@Autowired
private RepresentationIdBuilder representationIdBuilder;

@Autowired
private ExplorerEventSubscriptionRunner explorerEventSubscriptionRunner;

@Autowired
private ExplorerDescriptionsQueryRunner explorerDescriptionsQueryRunner;

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

@Test
@DisplayName("Given a studio, when the tree item labels are requested, then the correct styles are returned")
@Sql(scripts = { "/scripts/studio.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 givenAStudioWhenTheTreeItemLabelsAreRequestedThenTheCorrectStylesAreReturned() {

// 1- retrieve the tree description id of the DSL Domain explorer example
var explorerDescriptionId = new AtomicReference<String>();

Map<String, Object> explorerVariables = Map.of(
"editingContextId", StudioIdentifiers.SAMPLE_STUDIO_PROJECT.toString()
);
var explorerResult = TreeItemLabelDescriptionControllerTests.this.explorerDescriptionsQueryRunner.run(explorerVariables);
List<String> explorerIds = JsonPath.read(explorerResult, "$.data.viewer.editingContext.explorerDescriptions[*].id");
assertThat(explorerIds).isNotEmpty()
.hasSize(2);
assertThat(explorerIds.get(0)).isEqualTo(ExplorerDescriptionProvider.DESCRIPTION_ID);
assertThat(explorerIds.get(1)).startsWith("siriusComponents://representationDescription?kind=treeDescription");
explorerDescriptionId.set(explorerIds.get(1));

var explorerRepresentationId = this.representationIdBuilder.buildExplorerRepresentationId(explorerDescriptionId.get(),
List.of(StudioIdentifiers.DOMAIN_DOCUMENT.toString(), StudioIdentifiers.DOMAIN_OBJECT.toString(), ROOT_ENTITY_ID), List.of());

// 2- check that styles are properly applied
// - check that the ROOT entity has not the abstract style applied.
// - check that the NamedElement entity has the abstract style (if style)
var inputStyle = new ExplorerEventInput(UUID.randomUUID(), StudioIdentifiers.SAMPLE_STUDIO_PROJECT.toString(), explorerRepresentationId);
var fluxStyle = this.explorerEventSubscriptionRunner.run(inputStyle);

Consumer<Object> styleTreeContentConsumer = this.getTreeSubscriptionConsumer(tree -> {
assertThat(tree).isNotNull();
List<TreeItem> domainChildren = tree.getChildren()
.get(0)
.getChildren()
.get(0)
.getChildren();
StyledString rootLabel = domainChildren.get(0).getLabel();
StyledString namedElementLabel = domainChildren.get(1).getLabel();
assertThat(rootLabel.styledStringFragments()).hasSize(2);
assertThat(namedElementLabel.styledStringFragments()).hasSize(3);
assertThat(namedElementLabel.toString()).isEqualTo("[Entity] NamedElement [abstract]");
});

StepVerifier.create(fluxStyle)
.consumeNextWith(styleTreeContentConsumer)
.thenCancel()
.verify(Duration.ofSeconds(30));
}

private Consumer<Object> getTreeSubscriptionConsumer(Consumer<Tree> treeConsumer) {
return object -> Optional.of(object)
.filter(DataFetcherResult.class::isInstance)
.map(DataFetcherResult.class::cast)
.map(DataFetcherResult::getData)
.filter(TreeRefreshedEventPayload.class::isInstance)
.map(TreeRefreshedEventPayload.class::cast)
.map(TreeRefreshedEventPayload::tree)
.ifPresentOrElse(treeConsumer, () -> fail("Missing tree"));
}
}
Loading

0 comments on commit c223237

Please sign in to comment.