From 5459da1c7f48a2934c5cbc6bb247ddb483aa4b46 Mon Sep 17 00:00:00 2001 From: Florian Barbin Date: Wed, 28 Aug 2024 19:12:51 +0200 Subject: [PATCH] [3763] Fix expand issues with the SelectionDialog tree Bug: https://github.com/eclipse-sirius/sirius-web/issues/3763 Signed-off-by: Florian Barbin --- CHANGELOG.adoc | 1 + .../emf/services/DefaultContentService.java | 14 +- .../pom.xml | 7 + ...ectionDialogExpandAllTreePathProvider.java | 112 +++++++++ .../SelectionDialogNavigationService.java | 91 +++++++ .../ISelectionDialogNavigationService.java | 44 ++++ .../src/SelectionDialog.tsx | 4 +- .../SelectionControllerIntegrationTests.java | 222 ++++++++++++++++- .../SelectionDescriptionProvider.java | 224 +++++++++--------- .../SelectionDialogDescriptionConverter.java | 138 ++++++++--- 10 files changed, 708 insertions(+), 149 deletions(-) create mode 100644 packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/SelectionDialogExpandAllTreePathProvider.java create mode 100644 packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/SelectionDialogNavigationService.java create mode 100644 packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/api/ISelectionDialogNavigationService.java diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 257760c2091..713b809fa00 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -87,6 +87,7 @@ As a result, the following maven modules have been deleted: `sirius-web-sample-a - https://github.com/eclipse-sirius/sirius-web/issues/3763[#3763] [core] updates some services behavior: ** The `org.eclipse.sirius.components.emf.services.DefaultIdentityService#getId(Object object)` Can now provides the Id if the Object is of type `org.eclipse.emf.ecore.resource.Resource`. Before this change, it would have returned null. ** The `org.eclipse.sirius.components.emf.services.DefaultLabelService` Can now provides the label if the Object is of type `org.eclipse.emf.ecore.resource.Resource`. Before this change, it would have returned an empty string. +** The `org.eclipse.sirius.components.emf.services.DefaultContentService#getContents(Object)` Can now provides the content if the Object is of type `org.eclipse.emf.ecore.resource.Resource`. Before this change, it would have returned an empty list. === Dependency update diff --git a/packages/emf/backend/sirius-components-emf/src/main/java/org/eclipse/sirius/components/emf/services/DefaultContentService.java b/packages/emf/backend/sirius-components-emf/src/main/java/org/eclipse/sirius/components/emf/services/DefaultContentService.java index a235bfa652d..1603a60e342 100644 --- a/packages/emf/backend/sirius-components-emf/src/main/java/org/eclipse/sirius/components/emf/services/DefaultContentService.java +++ b/packages/emf/backend/sirius-components-emf/src/main/java/org/eclipse/sirius/components/emf/services/DefaultContentService.java @@ -12,17 +12,17 @@ *******************************************************************************/ package org.eclipse.sirius.components.emf.services; -import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.edit.provider.ComposedAdapterFactory; import org.eclipse.emf.edit.provider.IEditingDomainItemProvider; import org.eclipse.sirius.components.core.api.IDefaultContentService; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; +import org.springframework.stereotype.Service; /** * Default implementation of {@link IDefaultContentService}. @@ -48,6 +48,10 @@ public List getContents(Object object) { contents.addAll(eObject.eContents()); } } + else if (object instanceof Resource resource) { + // The object may be a document + contents.addAll(resource.getContents()); + } return contents; } } diff --git a/packages/selection/backend/sirius-components-collaborative-selection/pom.xml b/packages/selection/backend/sirius-components-collaborative-selection/pom.xml index deb89ff17c4..5b82f256b50 100644 --- a/packages/selection/backend/sirius-components-collaborative-selection/pom.xml +++ b/packages/selection/backend/sirius-components-collaborative-selection/pom.xml @@ -75,6 +75,13 @@ assertj-core test + + org.eclipse.sirius + + sirius-components-collaborative-trees + + 2024.9.0 + diff --git a/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/SelectionDialogExpandAllTreePathProvider.java b/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/SelectionDialogExpandAllTreePathProvider.java new file mode 100644 index 00000000000..69c146d0ce9 --- /dev/null +++ b/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/SelectionDialogExpandAllTreePathProvider.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * 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.components.collaborative.selection.services; + +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.sirius.components.collaborative.selection.services.api.ISelectionDialogNavigationService; +import org.eclipse.sirius.components.collaborative.trees.api.IExpandAllTreePathProvider; +import org.eclipse.sirius.components.collaborative.trees.dto.ExpandAllTreePathInput; +import org.eclipse.sirius.components.collaborative.trees.dto.ExpandAllTreePathSuccessPayload; +import org.eclipse.sirius.components.collaborative.trees.dto.TreePath; +import org.eclipse.sirius.components.core.api.IContentService; +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.core.api.IIdentityService; +import org.eclipse.sirius.components.core.api.IPayload; +import org.eclipse.sirius.components.core.api.IRepresentationDescriptionSearchService; +import org.eclipse.sirius.components.representations.VariableManager; +import org.eclipse.sirius.components.trees.Tree; +import org.eclipse.sirius.components.trees.description.TreeDescription; +import org.springframework.stereotype.Service; + +/** + * Implementation of {@link IExpandAllTreePathProvider} for the Selection Dialog Tree. + * + * @author frouene + * @author fbarbin + */ +@Service +public class SelectionDialogExpandAllTreePathProvider implements IExpandAllTreePathProvider { + + private final IIdentityService identityService; + + private final IContentService contentService; + + private final ISelectionDialogNavigationService selectionDialogNavigationService; + + private final IRepresentationDescriptionSearchService representationDescriptionSearchService; + + public SelectionDialogExpandAllTreePathProvider(IIdentityService identityService, IContentService contentService, ISelectionDialogNavigationService selectionDialogNavigationService, IRepresentationDescriptionSearchService representationDescriptionSearchService) { + this.identityService = Objects.requireNonNull(identityService); + this.contentService = Objects.requireNonNull(contentService); + this.selectionDialogNavigationService = Objects.requireNonNull(selectionDialogNavigationService); + this.representationDescriptionSearchService = Objects.requireNonNull(representationDescriptionSearchService); + } + + @Override + public boolean canHandle(Tree tree) { + return tree.getDescriptionId().startsWith(ISelectionDialogNavigationService.SELECTION_DIALOG_TREE_DESCRIPTION_KIND); + } + + @Override + public IPayload handle(IEditingContext editingContext, Tree tree, ExpandAllTreePathInput input) { + int maxDepth = 0; + String treeItemId = input.treeItemId(); + Set treeItemIdsToExpand = new LinkedHashSet<>(); + var object = this.getTreeItemObject(editingContext, treeItemId, tree); + if (object != null) { + // We need to get the current depth of the tree item + var itemAncestors = this.selectionDialogNavigationService.getAncestors(editingContext, treeItemId, tree); + maxDepth = itemAncestors.size(); + maxDepth = this.addAllContents(editingContext, treeItemId, maxDepth, treeItemIdsToExpand, tree); + } + return new ExpandAllTreePathSuccessPayload(input.id(), new TreePath(treeItemIdsToExpand.stream().toList(), maxDepth)); + } + + private int addAllContents(IEditingContext editingContext, String treeItemId, int depth, Set treeItemIdsToExpand, Tree tree) { + var depthConsidered = depth; + var object = this.getTreeItemObject(editingContext, treeItemId, tree); + if (object != null) { + var contents = this.contentService.getContents(object); + if (!contents.isEmpty()) { + treeItemIdsToExpand.add(treeItemId); + + for (var child : contents) { + String childId = this.identityService.getId(child); + treeItemIdsToExpand.add(childId); + var childTreePathMaxDepth = depth + 1; + childTreePathMaxDepth = this.addAllContents(editingContext, childId, childTreePathMaxDepth, treeItemIdsToExpand, tree); + depthConsidered = Math.max(depthConsidered, childTreePathMaxDepth); + } + } + } + + return depthConsidered; + } + + private Object getTreeItemObject(IEditingContext editingContext, String treeItemId, Tree tree) { + var optionalTreeDescription = this.representationDescriptionSearchService.findById(editingContext, tree.getDescriptionId()) + .filter(TreeDescription.class::isInstance) + .map(TreeDescription.class::cast); + + if (optionalTreeDescription.isPresent()) { + var variableManager = new VariableManager(); + variableManager.put(IEditingContext.EDITING_CONTEXT, editingContext); + variableManager.put(TreeDescription.ID, treeItemId); + return optionalTreeDescription.get().getTreeItemObjectProvider().apply(variableManager); + } + return null; + } +} diff --git a/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/SelectionDialogNavigationService.java b/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/SelectionDialogNavigationService.java new file mode 100644 index 00000000000..0206beb01c3 --- /dev/null +++ b/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/SelectionDialogNavigationService.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * 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.components.collaborative.selection.services; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.eclipse.sirius.components.collaborative.selection.services.api.ISelectionDialogNavigationService; +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.core.api.IObjectService; +import org.eclipse.sirius.components.core.api.IRepresentationDescriptionSearchService; +import org.eclipse.sirius.components.representations.VariableManager; +import org.eclipse.sirius.components.trees.Tree; +import org.eclipse.sirius.components.trees.description.TreeDescription; +import org.springframework.stereotype.Service; + +/** + * Services for the navigation through the Selection Dialog Tree. + * + * @author frouene + * @author fbarbin + */ +@Service +public class SelectionDialogNavigationService implements ISelectionDialogNavigationService { + + private final IObjectService objectService; + + private final IRepresentationDescriptionSearchService representationDescriptionSearchService; + + public SelectionDialogNavigationService(IObjectService objectService, IRepresentationDescriptionSearchService representationDescriptionSearchService) { + this.objectService = Objects.requireNonNull(objectService); + this.representationDescriptionSearchService = Objects.requireNonNull(representationDescriptionSearchService); + } + + @Override + public List getAncestors(IEditingContext editingContext, String treeItemId, Tree tree) { + List ancestorsIds = new ArrayList<>(); + + Optional optionalObject = this.getParentSemanticObject(treeItemId, ancestorsIds, editingContext, tree); + while (optionalObject.isPresent()) { + String parentId = this.objectService.getId(optionalObject.get()); + ancestorsIds.add(parentId); + optionalObject = this.getParentSemanticObject(parentId, ancestorsIds, editingContext, tree); + } + return ancestorsIds; + } + + private Optional getParentSemanticObject(String elementId, List ancestorsIds, IEditingContext editingContext, Tree tree) { + Optional result = Optional.empty(); + + var variableManager = new VariableManager(); + var optionalSemanticObject = this.getTreeItemObject(editingContext, elementId, tree); + var optionalTreeDescription = this.representationDescriptionSearchService.findById(editingContext, tree.getDescriptionId()) + .filter(TreeDescription.class::isInstance) + .map(TreeDescription.class::cast); + + if (optionalSemanticObject.isPresent() && optionalTreeDescription.isPresent()) { + variableManager.put(VariableManager.SELF, optionalSemanticObject.get()); + variableManager.put(TreeDescription.ID, elementId); + variableManager.put(IEditingContext.EDITING_CONTEXT, editingContext); + result = Optional.ofNullable(optionalTreeDescription.get().getParentObjectProvider().apply(variableManager)); + } + return result; + } + + private Optional getTreeItemObject(IEditingContext editingContext, String id, Tree tree) { + var optionalTreeDescription = this.representationDescriptionSearchService.findById(editingContext, tree.getDescriptionId()) + .filter(TreeDescription.class::isInstance) + .map(TreeDescription.class::cast); + + if (optionalTreeDescription.isPresent()) { + var variableManager = new VariableManager(); + variableManager.put(IEditingContext.EDITING_CONTEXT, editingContext); + variableManager.put(TreeDescription.ID, id); + return Optional.ofNullable(optionalTreeDescription.get().getTreeItemObjectProvider().apply(variableManager)); + } + return Optional.empty(); + } +} diff --git a/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/api/ISelectionDialogNavigationService.java b/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/api/ISelectionDialogNavigationService.java new file mode 100644 index 00000000000..db3f5794358 --- /dev/null +++ b/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/api/ISelectionDialogNavigationService.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * 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.components.collaborative.selection.services.api; + +import java.util.List; + +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.trees.Tree; + +/** + * Interface of the service for the navigation through the Selection Dialog Tree. + * + * @author fbarbin + */ +public interface ISelectionDialogNavigationService { + + String SELECTION_DIALOG_TREE_DESCRIPTION_KIND = "siriusComponents://selectionDialogTreeDescription"; + + List getAncestors(IEditingContext editingContext, String treeItemId, Tree tree); + + /** + * Implementation which does nothing, used for mocks in unit tests. + * + * @author fbarbin + */ + class NoOp implements ISelectionDialogNavigationService { + + @Override + public List getAncestors(IEditingContext editingContext, String treeItemId, Tree tree) { + return List.of(); + } + } + +} diff --git a/packages/selection/frontend/sirius-components-selection/src/SelectionDialog.tsx b/packages/selection/frontend/sirius-components-selection/src/SelectionDialog.tsx index 2aca1c4fb0f..de3226cfa42 100644 --- a/packages/selection/frontend/sirius-components-selection/src/SelectionDialog.tsx +++ b/packages/selection/frontend/sirius-components-selection/src/SelectionDialog.tsx @@ -73,8 +73,8 @@ export const SelectionDialog = ({ enableMultiSelection={true} synchronizedWithSelection={true} activeFilterIds={[]} - textToFilter={null} - textToHighlight={null} + textToFilter={''} + textToHighlight={''} treeItemActionRender={(props) => } /> ); diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/selection/SelectionControllerIntegrationTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/selection/SelectionControllerIntegrationTests.java index 8cc2c63dbcb..0128476b89e 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/selection/SelectionControllerIntegrationTests.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/selection/SelectionControllerIntegrationTests.java @@ -13,7 +13,7 @@ package org.eclipse.sirius.web.application.controllers.selection; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; +import static org.assertj.core.api.Assertions.fail; import com.jayway.jsonpath.JsonPath; @@ -24,6 +24,7 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Predicate; @@ -32,6 +33,8 @@ import org.eclipse.sirius.components.graphql.api.URLConstants; import org.eclipse.sirius.components.graphql.tests.api.IGraphQLRequestor; import org.eclipse.sirius.components.trees.Tree; +import org.eclipse.sirius.components.trees.TreeItem; +import org.eclipse.sirius.components.trees.tests.graphql.ExpandAllTreePathQueryRunner; import org.eclipse.sirius.components.trees.tests.graphql.TreeEventSubscriptionRunner; import org.eclipse.sirius.web.AbstractIntegrationTests; import org.eclipse.sirius.web.data.PapayaIdentifiers; @@ -105,6 +108,12 @@ subscription treeEvent($input: TreeEventInput!) { @Autowired private TreeEventSubscriptionRunner treeEventSubscriptionRunner; + @Autowired + private SelectionDescriptionProvider selectionDescriptionProvider; + + @Autowired + private ExpandAllTreePathQueryRunner expandAllTreePathQueryRunner; + @BeforeEach public void beforeEach() { this.givenInitialServerState.initialize(); @@ -115,7 +124,7 @@ public void beforeEach() { @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 givenSemanticObjectWhenRequestingSelectionDescriptionThenTheSelectionDescriptionIsSent() { - String representationId = "selectionDialog://?representationDescription=" + URLEncoder.encode(SelectionDescriptionProvider.REPRESENTATION_DESCRIPTION_ID, StandardCharsets.UTF_8); + String representationId = "selectionDialog://?representationDescription=" + URLEncoder.encode(this.selectionDescriptionProvider.getSelectionDialogDescriptionId(), StandardCharsets.UTF_8); Map variables = Map.of("editingContextId", PapayaIdentifiers.PAPAYA_PROJECT.toString(), "representationId", representationId, "targetObjectId", PapayaIdentifiers.PROJECT_OBJECT.toString()); var result = this.graphQLRequestor.execute(GET_SELECTION_DESCRIPTION, variables); @@ -123,7 +132,7 @@ public void givenSemanticObjectWhenRequestingSelectionDescriptionThenTheSelectio String treeDescriptionId = JsonPath.read(result, "$.data.viewer.editingContext.representation.description.treeDescription.id"); assertThat(message).isEqualTo("Select the objects to consider"); - assertThat(treeDescriptionId).isEqualTo("siriusComponents://selectionDialogTreeDescription"); + assertThat(treeDescriptionId).isEqualTo(this.selectionDescriptionProvider.getSelectionDialogTreeDescriptionId()); } @Test @@ -131,7 +140,8 @@ public void givenSemanticObjectWhenRequestingSelectionDescriptionThenTheSelectio @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 givenSemanticObjectWhenWeSubscribeToItsSelectionEventsThenTheSelectionIsSent() { - var treeId = "selection://?treeDescriptionId=" + SelectionDescriptionProvider.TREE_DESCRIPTION_ID + "&targetObjectId=" + PapayaIdentifiers.PROJECT_OBJECT.toString(); + var treeId = "selection://?treeDescriptionId=" + URLEncoder.encode(this.selectionDescriptionProvider.getSelectionDialogTreeDescriptionId(), StandardCharsets.UTF_8) + "&targetObjectId=" + + PapayaIdentifiers.PROJECT_OBJECT.toString(); var input = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(), List.of()); var flux = this.treeEventSubscriptionRunner.run(input); var hasResourceRootContent = this.getTreeRefreshedEventPayloadMatcher(); @@ -141,13 +151,202 @@ public void givenSemanticObjectWhenWeSubscribeToItsSelectionEventsThenTheSelecti .verify(); } + @Test + @DisplayName("Given the selection dialog tree, when we expand the first item, then the children are sent") + @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 givenSelectionDialogTreeWhenWeExpandTheFirstItemThenChildrenAreSent() { + var treeId = "selection://?treeDescriptionId=" + URLEncoder.encode(this.selectionDescriptionProvider.getSelectionDialogTreeDescriptionId(), StandardCharsets.UTF_8) + "&targetObjectId=" + + PapayaIdentifiers.PROJECT_OBJECT.toString(); + var input = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(), List.of()); + var flux = this.treeEventSubscriptionRunner.run(input); + + var treeItemId = new AtomicReference(); + + Consumer initialTreeContentConsumer = this.getTreeSubscriptionConsumer(tree -> { + assertThat(tree).isNotNull(); + assertThat(tree.getChildren()).hasSize(1); + assertThat(tree.getChildren()).allSatisfy(treeItem -> assertThat(treeItem.getChildren()).isEmpty()); + + treeItemId.set(tree.getChildren().get(0).getId()); + }); + + StepVerifier.create(flux) + .consumeNextWith(initialTreeContentConsumer) + .thenCancel() + .verify(); + + var expandedTreeInput = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(treeItemId.get()), List.of()); + var expandedTreeFlux = this.treeEventSubscriptionRunner.run(expandedTreeInput); + var treeRefreshedEventPayloadExpandMatcher = this.getTreeRefreshedEventPayloadExpandMatcher(); + StepVerifier.create(expandedTreeFlux) + .expectNextMatches(treeRefreshedEventPayloadExpandMatcher) + .thenCancel() + .verify(); + } + + + @Test + @DisplayName("Given the selection dialog tree, when we perform expand all on the first item, then the children are sent") + @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 givenSelectionDialogTreeWhenWePerformExpandAllOnTheFirstItemThenChildrenAreSent() { + var treeId = "selection://?treeDescriptionId=" + URLEncoder.encode(this.selectionDescriptionProvider.getSelectionDialogTreeDescriptionId(), StandardCharsets.UTF_8) + "&targetObjectId=" + + PapayaIdentifiers.PROJECT_OBJECT.toString(); + var input = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(), List.of()); + var flux = this.treeEventSubscriptionRunner.run(input); + + var treeItemId = new AtomicReference(); + var treeInstanceId = new AtomicReference(); + + Consumer initialTreeContentConsumer = this.getTreeSubscriptionConsumer(tree -> { + assertThat(tree).isNotNull(); + assertThat(tree.getChildren()).hasSize(1); + assertThat(tree.getChildren()).allSatisfy(treeItem -> assertThat(treeItem.getChildren()).isEmpty()); + treeInstanceId.set(tree.getId()); + treeItemId.set(tree.getChildren().get(0).getId()); + }); + + var treeItemIds = new AtomicReference>(); + + Runnable getTreePath = () -> { + Map variables = Map.of( + "editingContextId", PapayaIdentifiers.PAPAYA_PROJECT.toString(), + "treeId", treeInstanceId.get(), + "treeItemId", treeItemId.get() + ); + var result = this.expandAllTreePathQueryRunner.run(variables); + List treeItemIdsToExpand = JsonPath.read(result, "$.data.viewer.editingContext.expandAllTreePath.treeItemIdsToExpand"); + assertThat(treeItemIdsToExpand).isNotEmpty(); + + treeItemIds.set(treeItemIdsToExpand); + }; + + StepVerifier.create(flux) + .consumeNextWith(initialTreeContentConsumer) + .then(getTreePath) + .thenCancel() + .verify(Duration.ofSeconds(10)); + + Consumer initialExpandedTreeContentConsumer = this.getTreeSubscriptionConsumer(tree -> { + assertThat(tree).isNotNull(); + assertThat(tree.getChildren()).hasSize(1); + assertThat(tree.getChildren()).anySatisfy(treeItem -> assertThat(treeItem.getChildren()).isNotEmpty()); + + }); + + var expandedTreeInput = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, treeItemIds.get(), List.of()); + var expandedTreeFlux = this.treeEventSubscriptionRunner.run(expandedTreeInput); + + StepVerifier.create(expandedTreeFlux) + .consumeNextWith(initialExpandedTreeContentConsumer) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } + + @Test + @DisplayName("Given the selection dialog tree, when we perform expand all on the second item (the root project), then the children are sent") + @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 givenSelectionDialogTreeWhenWePerformExpandAllOnTheSecondItemThenChildrenAreSent() { + var treeId = "selection://?treeDescriptionId=" + URLEncoder.encode(this.selectionDescriptionProvider.getSelectionDialogTreeDescriptionId(), StandardCharsets.UTF_8) + "&targetObjectId=" + + PapayaIdentifiers.PROJECT_OBJECT.toString(); + var input = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(), List.of()); + var flux = this.treeEventSubscriptionRunner.run(input); + + var rootTreeItemId = new AtomicReference(); + var treeInstanceId = new AtomicReference(); + + Consumer initialTreeContentConsumer = this.getTreeSubscriptionConsumer(tree -> { + assertThat(tree).isNotNull(); + assertThat(tree.getChildren()).hasSize(1); + assertThat(tree.getChildren()).allSatisfy(treeItem -> assertThat(treeItem.getChildren()).isEmpty()); + treeInstanceId.set(tree.getId()); + rootTreeItemId.set(tree.getChildren().get(0).getId()); + }); + + StepVerifier.create(flux) + .consumeNextWith(initialTreeContentConsumer) + .thenCancel() + .verify(Duration.ofSeconds(10)); + + //We expand the first tree item (representing the resource) + var expandedFirstTreeItemInput = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(rootTreeItemId.get()), List.of()); + var expandedFirstTreeItemFlux = this.treeEventSubscriptionRunner.run(expandedFirstTreeItemInput); + + //Used to retrieve the Papaya Root Project tree item + var rootProjectTreeItemId = new AtomicReference(); + Consumer firstTreeItemExpandedContentConsumer = this.getTreeSubscriptionConsumer(tree -> { + assertThat(tree).isNotNull(); + assertThat(tree.getChildren()).hasSize(1); + var rootResourceTreeItem = tree.getChildren().get(0); + assertThat(tree.getChildren()).hasSize(1); + rootProjectTreeItemId.set(rootResourceTreeItem.getChildren().get(0).getId()); + }); + + var treeItemIds = new AtomicReference>(); + + Runnable getTreePath = () -> { + Map variables = Map.of( + "editingContextId", PapayaIdentifiers.PAPAYA_PROJECT.toString(), + "treeId", treeInstanceId.get(), + "treeItemId", rootProjectTreeItemId.get() + ); + var result = this.expandAllTreePathQueryRunner.run(variables); + List treeItemIdsToExpand = JsonPath.read(result, "$.data.viewer.editingContext.expandAllTreePath.treeItemIdsToExpand"); + assertThat(treeItemIdsToExpand).isNotEmpty(); + treeItemIdsToExpand.add(0, rootTreeItemId.get()); + treeItemIds.set(treeItemIdsToExpand); + }; + + StepVerifier.create(expandedFirstTreeItemFlux) + .consumeNextWith(firstTreeItemExpandedContentConsumer) + //Now that we have expand and retrieve the Papaya Root Project tree item, we can perform the expandAll from it + .then(getTreePath) + .thenCancel() + .verify(Duration.ofSeconds(10)); + + + Consumer initialExpandedTreeContentConsumer = this.getTreeSubscriptionConsumer(tree -> { + assertThat(tree).isNotNull(); + assertThat(tree.getChildren()).hasSize(1); + TreeItem rootProjectTreeItem = tree.getChildren().get(0); + assertThat(rootProjectTreeItem.getChildren()).isNotEmpty(); + }); + + var expandedTreeInput = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, treeItemIds.get(), List.of()); + var expandedTreeFlux = this.treeEventSubscriptionRunner.run(expandedTreeInput); + // We now verify the expandAll result + StepVerifier.create(expandedTreeFlux) + .consumeNextWith(initialExpandedTreeContentConsumer) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } private Predicate getTreeRefreshedEventPayloadMatcher() { Predicate treeMatcher = tree -> { return tree.getChildren().size() == 1 && tree.getChildren().get(0).getIconURL().get(0).equals("/icons/Resource.svg") - && tree.getChildren().get(0).getLabel().toString().equals("Sirius Web Architecture"); + && tree.getChildren().get(0).getLabel().toString().equals("Sirius Web Architecture") && tree.getChildren().get(0).getChildren().isEmpty(); + }; + + return object -> { + return 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) + .filter(treeMatcher) + .isPresent(); + }; + } + + private Predicate getTreeRefreshedEventPayloadExpandMatcher() { + Predicate treeMatcher = tree -> { + return tree.getChildren().size() == 1 && !tree.getChildren().get(0).getChildren().isEmpty(); }; return object -> { @@ -168,7 +367,8 @@ private Predicate getTreeRefreshedEventPayloadMatcher() { @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 givenSemanticObjectWhenWeSubscribeToItsSelectionEventsThenTheURLOfItsObjectsIsValid() { - var treeId = "selection://?treeDescriptionId=" + SelectionDescriptionProvider.TREE_DESCRIPTION_ID + "&targetObjectId=" + PapayaIdentifiers.PROJECT_OBJECT.toString(); + var treeId = "selection://?treeDescriptionId=" + URLEncoder.encode(this.selectionDescriptionProvider.getSelectionDialogTreeDescriptionId(), StandardCharsets.UTF_8) + "&targetObjectId=" + + PapayaIdentifiers.PROJECT_OBJECT.toString(); var input = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(), List.of()); var flux = this.graphQLRequestor.subscribeToSpecification(GET_TREE_EVENT_SUBSCRIPTION, input); @@ -194,4 +394,14 @@ public void givenSemanticObjectWhenWeSubscribeToItsSelectionEventsThenTheURLOfIt .verify(Duration.ofSeconds(10)); } + private Consumer getTreeSubscriptionConsumer(Consumer 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")); + } } diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/services/selection/SelectionDescriptionProvider.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/services/selection/SelectionDescriptionProvider.java index faa2f018158..9a8be5f677e 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/services/selection/SelectionDescriptionProvider.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/services/selection/SelectionDescriptionProvider.java @@ -12,24 +12,36 @@ *******************************************************************************/ package org.eclipse.sirius.web.services.selection; -import java.util.List; import java.util.Objects; -import java.util.function.Function; -import java.util.function.Predicate; +import java.util.UUID; -import org.eclipse.emf.ecore.EObject; -import org.eclipse.emf.ecore.resource.Resource; -import org.eclipse.sirius.components.core.URLParser; +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.core.api.IObjectService; -import org.eclipse.sirius.components.core.api.labels.StyledString; -import org.eclipse.sirius.components.representations.Failure; -import org.eclipse.sirius.components.representations.VariableManager; -import org.eclipse.sirius.components.selection.description.SelectionDescription; -import org.eclipse.sirius.components.trees.description.TreeDescription; +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.SelectionDialogTreeDescriptionBuilder; +import org.eclipse.sirius.components.view.builder.generated.diagram.DiagramDescriptionBuilder; +import org.eclipse.sirius.components.view.builder.generated.diagram.DiagramPaletteBuilder; +import org.eclipse.sirius.components.view.builder.generated.diagram.InsideLabelDescriptionBuilder; +import org.eclipse.sirius.components.view.builder.generated.diagram.NodeDescriptionBuilder; +import org.eclipse.sirius.components.view.builder.generated.diagram.NodeToolBuilder; +import org.eclipse.sirius.components.view.builder.generated.diagram.RectangularNodeStyleDescriptionBuilder; +import org.eclipse.sirius.components.view.builder.generated.diagram.SelectionDialogDescriptionBuilder; +import org.eclipse.sirius.components.view.builder.generated.view.ChangeContextBuilder; +import org.eclipse.sirius.components.view.builder.generated.view.ViewBuilder; +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.SelectionDialogDescription; +import org.eclipse.sirius.components.view.diagram.SelectionDialogTreeDescription; +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.data.PapayaIdentifiers; import org.springframework.stereotype.Service; /** @@ -40,121 +52,121 @@ @Service public class SelectionDescriptionProvider implements IEditingContextProcessor { - public static final String LABEL = "Selection"; - public static final String REPRESENTATION_DESCRIPTION_ID = "siriusComponents://representationDescription?kind=selectionDescription&sourceElementId=Test"; + private static final String DIALOG_MESSAGE = "Select the objects to consider"; - public static final String TREE_DESCRIPTION_ID = "siriusComponents://selectionDialogTreeDescription"; + private final View view; - private static final String DIALOG_MESSAGE = "Select the objects to consider"; + private DiagramDescription diagramDescription; - private static final String SELECTION_PREFIX = "selection://"; + private NodeTool createNodeTool; - private static final String TREE_ID = "treeId"; + private final IDiagramIdProvider diagramIdProvider; - private static final String TARGET_OBJECT_ID = "targetObjectId"; + private SelectionDialogTreeDescription selectionDialogTreeDescription; - private final IObjectService objectService; + private SelectionDialogDescription selectionDialog; - private final URLParser urlParser; - public SelectionDescriptionProvider(IObjectService objectService, URLParser urlParser) { - this.objectService = Objects.requireNonNull(objectService); - this.urlParser = Objects.requireNonNull(urlParser); + public SelectionDescriptionProvider(IDiagramIdProvider diagramIdProvider) { + this.diagramIdProvider = Objects.requireNonNull(diagramIdProvider); + this.view = this.createView(); } @Override public void preProcess(IEditingContext editingContext) { if (editingContext instanceof EditingContext siriusWebEditingContext) { - SelectionDescription selectionDescription = this.getSelectionDescription(); - siriusWebEditingContext.getRepresentationDescriptions().put(REPRESENTATION_DESCRIPTION_ID, selectionDescription); - siriusWebEditingContext.getRepresentationDescriptions().put(TREE_DESCRIPTION_ID, selectionDescription.getTreeDescription()); + siriusWebEditingContext.getViews().add(this.view); } } - private SelectionDescription getSelectionDescription() { - - Function labelProvider = variableManager -> variableManager.get(VariableManager.SELF, Object.class) - .map(this.objectService::getFullLabel) - .orElse(""); - - Function treeLabelProvider = variableManager -> variableManager.get(VariableManager.SELF, Object.class) - .map(this.objectService::getStyledLabel) - .orElse(StyledString.of("")); - - Function kindProvider = variableManager -> variableManager.get(VariableManager.SELF, Object.class) - .map(this.objectService::getKind) - .orElse(""); - - Function> iconURLProvider = variableManager -> variableManager.get(VariableManager.SELF, Object.class) - .map(this.objectService::getImagePath) - .orElse(null); - - Function> objectsProvider = variableManager -> { - IEditingContext editingContext = variableManager.get(IEditingContext.EDITING_CONTEXT, IEditingContext.class).get(); - String projectId = PapayaIdentifiers.PROJECT_OBJECT.toString(); - Resource rootResource = this.objectService.getObject(editingContext, projectId) - .map(EObject.class::cast) - .map(EObject::eResource) - .get(); - return List.of(rootResource); - }; - - Function> childrenProvider = variableManager -> { - List children = List.of(); - Object currentElement = variableManager.get(VariableManager.SELF, Object.class).get(); - if (currentElement instanceof Resource resource) { - children = resource.getContents(); - } else if (currentElement instanceof EObject eObject) { - children = eObject.eContents(); - } - return children; - }; - - Function targetObjectIdProvider = variableManager -> - variableManager.get(TREE_ID, String.class).map(this.urlParser::getParameterValues) - .map(parameters -> parameters.get( - TARGET_OBJECT_ID).get(0)) - .orElse(""); - - TreeDescription treeDescription = TreeDescription.newTreeDescription(TREE_DESCRIPTION_ID) - .canCreatePredicate(this.canCreateTreeRepresentation()) - .childrenProvider(childrenProvider) - .deletableProvider(variableManager -> false) - .deleteHandler(variableManager -> new Failure("")) - .editableProvider(variableManager -> false) - .elementsProvider(objectsProvider) - .hasChildrenProvider(variableManager -> true) - .iconURLProvider(iconURLProvider) - .idProvider(variableManager -> SELECTION_PREFIX) - .labelProvider(treeLabelProvider) - .kindProvider(kindProvider) - .label(LABEL) - .renameHandler((variableManager, newName) -> new Failure("")) - .selectableProvider(variableManager -> true) - .targetObjectIdProvider(targetObjectIdProvider) - .treeItemIdProvider(targetObjectIdProvider) - .treeItemObjectProvider(variableManager -> null) - .parentObjectProvider(variableManager -> null) + public String getSelectionDialogTreeDescriptionId() { + return this.diagramIdProvider.getId(this.selectionDialogTreeDescription); + } + + public String getSelectionDialogDescriptionId() { + return this.diagramIdProvider.getId(this.selectionDialog); + } + + 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("SelectionDescriptionDiagramDescription".getBytes()).toString(); + JsonResource resource = new JSONResourceFactory().createResourceFromPath(resourcePath); + resource.eAdapters().add(new ResourceMetadataAdapter("SelectionDescriptionDiagramDescription")); + 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(); - return SelectionDescription.newSelectionDescription(REPRESENTATION_DESCRIPTION_ID) - .label(LABEL) - .idProvider(variableManager -> SELECTION_PREFIX) - .labelProvider(labelProvider) - .messageProvider(variableManager -> DIALOG_MESSAGE) - .targetObjectIdProvider(targetObjectIdProvider) - .canCreatePredicate(this.canCreateTreeRepresentation()) - .treeDescription(treeDescription) + this.createCreateNodeTool(); + + var diagramPalette = new DiagramPaletteBuilder() + .nodeTools(this.createNodeTool) .build(); + + this.diagramDescription = new DiagramDescriptionBuilder() + .name("Diagram") + .titleExpression("aql:'SelectionDescriptionDiagram'") + .domainType("papaya:Project") + .nodeDescriptions(nodeDescription) + .edgeDescriptions() + .palette(diagramPalette) + .autoLayout(false) + .build(); + + return this.diagramDescription; } - private Predicate canCreateTreeRepresentation() { - return variableManager -> { - return variableManager.get(TREE_ID, String.class) - .filter(id -> id.startsWith(SELECTION_PREFIX) && id.contains(TREE_DESCRIPTION_ID)) - .isPresent(); - }; + private void createCreateNodeTool() { + this.selectionDialog = this.createSelectionDialog(); + this.createNodeTool = new NodeToolBuilder() + .name("Create Component") + .body( + new ChangeContextBuilder() + .expression("aql:self") + .build() + ) + .dialogDescription(this.selectionDialog) + .build(); + } + + private SelectionDialogDescription createSelectionDialog() { + this.selectionDialogTreeDescription = new SelectionDialogTreeDescriptionBuilder() + .elementsExpression("aql:self.eResource()") + .childrenExpression("aql:if self.oclIsKindOf(papaya::NamedElement) then self.eContents() else self.getContents() endif ") + .isSelectableExpression("aql:self.oclIsKindOf(papaya::Component)") + .build(); + return new SelectionDialogDescriptionBuilder() + .selectionMessage(DIALOG_MESSAGE) + .selectionDialogTreeDescription(this.selectionDialogTreeDescription) + .build(); + } } diff --git a/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/api/SelectionDialogDescriptionConverter.java b/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/api/SelectionDialogDescriptionConverter.java index f636b6119fd..a4344471f12 100644 --- a/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/api/SelectionDialogDescriptionConverter.java +++ b/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/api/SelectionDialogDescriptionConverter.java @@ -20,10 +20,15 @@ import java.util.function.Function; import java.util.function.Predicate; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.sirius.components.core.api.IEditingContext; import org.eclipse.sirius.components.core.api.IObjectService; import org.eclipse.sirius.components.core.api.IURLParser; import org.eclipse.sirius.components.core.api.labels.StyledString; +import org.eclipse.sirius.components.emf.services.JSONResourceFactory; +import org.eclipse.sirius.components.emf.services.api.IEMFEditingContext; import org.eclipse.sirius.components.interpreter.AQLInterpreter; import org.eclipse.sirius.components.interpreter.Result; import org.eclipse.sirius.components.representations.Failure; @@ -32,6 +37,7 @@ import org.eclipse.sirius.components.representations.VariableManager; import org.eclipse.sirius.components.selection.description.SelectionDescription; import org.eclipse.sirius.components.trees.description.TreeDescription; +import org.eclipse.sirius.components.trees.renderer.TreeRenderer; import org.eclipse.sirius.components.view.diagram.DialogDescription; import org.eclipse.sirius.components.view.diagram.SelectionDialogDescription; import org.eclipse.sirius.components.view.diagram.SelectionDialogTreeDescription; @@ -116,21 +122,8 @@ private TreeDescription createTreeDescription(SelectionDialogDescription selecti SelectionDialogTreeDescription selectionDialogTreeDescription = selectionDescription.getSelectionDialogTreeDescription(); final String treeDescriptionId = this.diagramIdProvider.getId(selectionDialogTreeDescription); - Function> childrenProvider = this.getChildrenProvider(interpreter, selectionDialogTreeDescription); - Function> elementsProvider = this.getElementProvider(interpreter, selectionDialogTreeDescription); - Function hasChildrenProvider = variableManager -> { - String childrenExpression = selectionDialogTreeDescription.getChildrenExpression(); - String safeExpression = Optional.ofNullable(childrenExpression).orElse(""); - if (safeExpression.isBlank()) { - return false; - } else { - Result result = interpreter.evaluateExpression(variableManager.getVariables(), safeExpression); - return !result.asObjects().orElse(List.of()).isEmpty(); - } - }; - Function deleteHandler = variableManager -> new Failure("Unexecutable delete handler"); BiFunction renameHandler = (variableManager, newValue) -> new Failure("Unexecutable rename handler"); @@ -175,6 +168,11 @@ private TreeDescription createTreeDescription(SelectionDialogDescription selecti .orElse(false); }; + + Function> childrenProvider = variableManager -> this.getChildren(variableManager, interpreter, selectionDialogTreeDescription, isSelectableProvider); + + Function hasChildrenProvider = variableManager -> this.hasChildren(interpreter, selectionDialogTreeDescription, variableManager, isSelectableProvider); + Predicate canCreatePredicate = variableManager -> { return variableManager.get(TREE_ID, String.class) .filter(id -> id.startsWith(SELECTION_PREFIX)) @@ -202,35 +200,106 @@ private TreeDescription createTreeDescription(SelectionDialogDescription selecti .iconURLProvider(imageURLProvider) .selectableProvider(isSelectableProvider) .treeItemObjectProvider(this::getTreeItemObject) - .parentObjectProvider(variableManager -> null) + .parentObjectProvider(this::getParentObject) .build(); } + private Boolean hasChildren(AQLInterpreter interpreter, SelectionDialogTreeDescription selectionDialogTreeDescription, VariableManager variableManager, Function isSelectableProvider) { + boolean hasChildren = false; + Object self = variableManager.getVariables().get(VariableManager.SELF); + hasChildren = !this.computeChildrenFromExpression(variableManager, interpreter, selectionDialogTreeDescription).isEmpty(); + return hasChildren && this.hasCompatibleDescendants(interpreter, selectionDialogTreeDescription, variableManager, self, false, isSelectableProvider); + } + + private boolean hasCompatibleDescendants(AQLInterpreter interpreter, SelectionDialogTreeDescription selectionDialogTreeDescription, VariableManager variableManager, Object object, boolean isDescendant, Function isSelectableProvider) { + VariableManager childVariableManager = variableManager.createChild(); + childVariableManager.put(VariableManager.SELF, object); + return isDescendant && isSelectableProvider.apply(childVariableManager) + || this.computeChildrenFromExpression(childVariableManager, interpreter, selectionDialogTreeDescription).stream().anyMatch(eContent -> this.hasCompatibleDescendants(interpreter, selectionDialogTreeDescription, childVariableManager, eContent, true, isSelectableProvider)); + } + private Object getTreeItemObject(VariableManager variableManager) { + Object result = null; var optionalEditingContext = variableManager.get(IEditingContext.EDITING_CONTEXT, IEditingContext.class); var optionalId = variableManager.get(TreeDescription.ID, String.class); if (optionalId.isPresent() && optionalEditingContext.isPresent()) { - return this.objectService.getObject(optionalEditingContext.get(), optionalId.get()); + var optionalObject = this.objectService.getObject(optionalEditingContext.get(), optionalId.get()); + if (optionalObject.isPresent()) { + result = optionalObject.get(); + } else { + var optionalEditingDomain = Optional.of(optionalEditingContext.get()) + .filter(IEMFEditingContext.class::isInstance) + .map(IEMFEditingContext.class::cast) + .map(IEMFEditingContext::getDomain); + + if (optionalEditingDomain.isPresent()) { + var editingDomain = optionalEditingDomain.get(); + ResourceSet resourceSet = editingDomain.getResourceSet(); + URI uri = new JSONResourceFactory().createResourceURI(optionalId.get()); + + result = resourceSet.getResources().stream() + .filter(resource -> resource.getURI().equals(uri)). + findFirst() + .orElse(null); + } + } } - return null; + + return result; } - private Function> getChildrenProvider(AQLInterpreter interpreter, SelectionDialogTreeDescription selectionDialogTreeDescription) { - return variableManager -> { - String childrenExpression = selectionDialogTreeDescription.getChildrenExpression(); - String safeExpression = Optional.ofNullable(childrenExpression).orElse(""); - if (safeExpression.isBlank()) { - return List.of(); + + private Object getParentObject(VariableManager variableManager) { + Object result = null; + Object self = variableManager.getVariables().get(VariableManager.SELF); + if (self instanceof EObject eObject) { + Object semanticContainer = eObject.eContainer(); + if (semanticContainer == null) { + semanticContainer = eObject.eResource(); } - else { - Result result = interpreter.evaluateExpression(variableManager.getVariables(), safeExpression); - return result.asObjects() - .orElse(List.of()) - .stream() - .filter(Objects::nonNull) - .toList(); + result = semanticContainer; + } + return result; + } + + private List getChildren(VariableManager variableManager, AQLInterpreter interpreter, SelectionDialogTreeDescription selectionDialogTreeDescription, Function isSelectableProvider) { + List result = new ArrayList<>(); + + List expandedIds = new ArrayList<>(); + Object objects = variableManager.getVariables().get(TreeRenderer.EXPANDED); + if (objects instanceof List list) { + expandedIds = list.stream().filter(String.class::isInstance).map(String.class::cast).toList(); + } + + String id = this.getTreeItemId(variableManager); + if (expandedIds.contains(id)) { + result.addAll(this.computeChildrenFromExpression(variableManager, interpreter, selectionDialogTreeDescription)); + } + result.removeIf(object -> { + if (object instanceof EObject eObject) { + VariableManager childVariableManager = variableManager.createChild(); + childVariableManager.put(VariableManager.SELF, eObject); + return !isSelectableProvider.apply(childVariableManager) && !this.hasChildren(interpreter, selectionDialogTreeDescription, childVariableManager, isSelectableProvider); + } else { + return false; } - }; + }); + return result; + } + + private List computeChildrenFromExpression(VariableManager variableManager, AQLInterpreter interpreter, SelectionDialogTreeDescription selectionDialogTreeDescription) { + List result = new ArrayList<>(); + String childrenExpression = selectionDialogTreeDescription.getChildrenExpression(); + String safeExpression = Optional.ofNullable(childrenExpression).orElse(""); + if (!safeExpression.isBlank()) { + Result interpreterResult = interpreter.evaluateExpression(variableManager.getVariables(), safeExpression); + result = interpreterResult.asObjects() + .orElse(List.of()) + .stream() + .filter(Objects::nonNull) + .toList(); + } + return result; } private Function> getElementProvider(AQLInterpreter interpreter, SelectionDialogTreeDescription selectionDialogTreeDescription) { @@ -261,4 +330,13 @@ private Optional getTargetObjectId(VariableManager variableManager) { .map(parameters -> parameters.get(TARGET_OBJECT_ID).get(0)); } + private String getTreeItemId(VariableManager variableManager) { + Object self = variableManager.getVariables().get(VariableManager.SELF); + String id = null; + if (self != null) { + id = this.objectService.getId(self); + } + return id; + } + }