From 7afa45f7df3ab065b7e6790528b0f736a9c78704 Mon Sep 17 00:00:00 2001 From: Jerome Gout Date: Thu, 22 Aug 2024 17:59:41 +0200 Subject: [PATCH] [3875] Refactor the tree event classes related to the explorer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: https://github.com/eclipse-sirius/sirius-web/issues/3875 Signed-off-by: Jerome Gout Signed-off-by: Florian ROUËNÉ --- CHANGELOG.adoc | 3 +- .../ModelBrowserEventProcessorFactory.java | 100 ++++++ .../ModelBrowsersDescriptionProvider.java | 20 +- .../ModelBrowserConfiguration.java | 59 ++++ .../reference/dto/ModelBrowserEventInput.java | 26 ++ .../reference/provider/ModelBrowserTests.java | 3 - ...scriptionModelBrowserEventDataFetcher.java | 72 +++++ .../resources/schema/modelbrowser.graphqls | 10 + .../src/components/ModelBrowserTreeView.tsx | 59 ++-- .../components/ModelBrowserTreeView.types.ts | 4 +- .../useModelBrowserSubscription.tsx | 89 ++++++ .../useModelBrowserSubscription.types.ts | 28 ++ .../explorer/ExplorerConfiguration.java} | 10 +- .../views/explorer/ExplorerEventInput.java} | 6 +- .../ExplorerEventProcessorFactory.java} | 31 +- ...SubscriptionExplorerEventDataFetcher.java} | 14 +- .../services/ExplorerDescriptionProvider.java | 9 +- .../main/resources/schema/explorer.graphqls | 11 + .../backend/sirius-web-tests/pom.xml | 5 + .../ExplorerEventSubscriptionRunner.java} | 20 +- .../ModelBrowserEventSubscriptionRunner.java | 51 ++++ .../ExplorerExpandAllControllerTests.java | 10 +- ...orerNonSemanticElementControllerTests.java | 10 +- .../ExplorerTreeFilterControllerTests.java | 10 +- .../ExplorerTreePathControllerTests.java | 8 +- .../ModelBrowserExpandAllControllerTests.java | 16 +- .../trees/TreeControllerIntegrationTests.java | 22 +- .../trees/TreeIconURLControllerTests.java | 112 +++++++ .../extension/DefaultExtensionRegistry.tsx | 9 +- .../src/views/explorer}/ExplorerView.tsx | 38 ++- .../src/views/explorer}/ExplorerView.types.ts | 9 +- .../explorer/useExplorerSubscription.tsx | 90 ++++++ .../explorer/useExplorerSubscription.types.ts | 29 ++ .../collaborative/trees/TreeService.java | 2 - .../src/main/resources/schema/tree.graphqls | 12 - .../handlers/TreePathEventHandlerTests.java | 85 ++++++ .../sirius-components-trees/src/index.ts | 9 +- .../src/toolbar/TreeToolBar.types.ts | 2 +- .../src/views/TreeFiltersMenu.tsx | 3 +- .../src/views/TreeFiltersMenu.types.ts | 8 +- .../src/views/TreeView.tsx | 238 ++++++--------- .../src/views/TreeView.types.ts | 53 ++-- .../src/views/TreeViewMachine.ts | 286 ------------------ .../GraphqlTreeEventSubscription.test.ts | 6 +- .../src/views/getTreeEventSubscription.ts | 6 +- 45 files changed, 1070 insertions(+), 633 deletions(-) create mode 100644 packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/browser/ModelBrowserEventProcessorFactory.java create mode 100644 packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/configurations/ModelBrowserConfiguration.java create mode 100644 packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/dto/ModelBrowserEventInput.java create mode 100644 packages/forms/backend/sirius-components-widget-reference-graphql/src/main/java/org/eclipse/sirius/components/widget/reference/graphql/datafetchers/subscription/SubscriptionModelBrowserEventDataFetcher.java create mode 100644 packages/forms/backend/sirius-components-widget-reference/src/main/resources/schema/modelbrowser.graphqls create mode 100644 packages/forms/frontend/sirius-components-widget-reference/src/components/useModelBrowserSubscription.tsx create mode 100644 packages/forms/frontend/sirius-components-widget-reference/src/components/useModelBrowserSubscription.types.ts rename packages/{trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/api/TreeConfiguration.java => sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerConfiguration.java} (84%) rename packages/{trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/TreeEventInput.java => sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerEventInput.java} (71%) rename packages/{trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeEventProcessorFactory.java => sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerEventProcessorFactory.java} (75%) rename packages/{trees/backend/sirius-components-trees-graphql/src/main/java/org/eclipse/sirius/components/trees/graphql/datafetchers/subscription/SubscriptionTreeEventDataFetcher.java => sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/SubscriptionExplorerEventDataFetcher.java} (76%) create mode 100644 packages/sirius-web/backend/sirius-web-application/src/main/resources/schema/explorer.graphqls rename packages/{trees/backend/sirius-components-trees-tests/src/main/java/org/eclipse/sirius/components/trees/tests/graphql/TreeEventSubscriptionRunner.java => sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/explorer/ExplorerEventSubscriptionRunner.java} (60%) create mode 100644 packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/modelbrowser/ModelBrowserEventSubscriptionRunner.java create mode 100644 packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/TreeIconURLControllerTests.java rename packages/{trees/frontend/sirius-components-trees/src/views => sirius-web/frontend/sirius-web-application/src/views/explorer}/ExplorerView.tsx (84%) rename packages/{trees/frontend/sirius-components-trees/src/views => sirius-web/frontend/sirius-web-application/src/views/explorer}/ExplorerView.types.ts (86%) create mode 100644 packages/sirius-web/frontend/sirius-web-application/src/views/explorer/useExplorerSubscription.tsx create mode 100644 packages/sirius-web/frontend/sirius-web-application/src/views/explorer/useExplorerSubscription.types.ts create mode 100644 packages/trees/backend/sirius-components-collaborative-trees/src/test/java/org/eclipse/sirius/components/collaborative/trees/architecture/handlers/TreePathEventHandlerTests.java delete mode 100644 packages/trees/frontend/sirius-components-trees/src/views/TreeViewMachine.ts diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 265f2081df0..ae74e75704d 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -18,7 +18,7 @@ === Breaking changes - https://github.com/eclipse-sirius/sirius-web/issues/3846[#3846] [core] Migrate frontend to React 18, you now need React 18 to import sirius web modules. - +- https://github.com/eclipse-sirius/sirius-web/issues/3875[#3875] [sirius-web] `ExplorerView` component has been moved from components-trees to sirius-web-application module === Dependency update @@ -34,6 +34,7 @@ === Improvements +- https://github.com/eclipse-sirius/sirius-web/issues/3875[#3875] [sirius-web] Move explorer related code from sirius-components-trees to sirius-web-application == v2024.9.0 diff --git a/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/browser/ModelBrowserEventProcessorFactory.java b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/browser/ModelBrowserEventProcessorFactory.java new file mode 100644 index 00000000000..a4466ac5da8 --- /dev/null +++ b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/browser/ModelBrowserEventProcessorFactory.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * 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.widget.reference.browser; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.eclipse.sirius.components.collaborative.api.IRepresentationConfiguration; +import org.eclipse.sirius.components.collaborative.api.IRepresentationEventProcessor; +import org.eclipse.sirius.components.collaborative.api.IRepresentationEventProcessorFactory; +import org.eclipse.sirius.components.collaborative.api.IRepresentationRefreshPolicyRegistry; +import org.eclipse.sirius.components.collaborative.api.ISubscriptionManagerFactory; +import org.eclipse.sirius.components.collaborative.trees.TreeEventProcessor; +import org.eclipse.sirius.components.collaborative.trees.api.ITreeEventHandler; +import org.eclipse.sirius.components.collaborative.trees.api.ITreeService; +import org.eclipse.sirius.components.collaborative.trees.api.TreeCreationParameters; +import org.eclipse.sirius.components.collaborative.widget.reference.configurations.ModelBrowserConfiguration; +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.core.api.IRepresentationDescriptionSearchService; +import org.eclipse.sirius.components.trees.description.TreeDescription; +import org.springframework.stereotype.Service; + +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; + +/** + * Used to create the tree event processors in the context of model browser. + * + * @author Jerome Gout + */ +@Service +public class ModelBrowserEventProcessorFactory implements IRepresentationEventProcessorFactory { + + private final IRepresentationDescriptionSearchService representationDescriptionSearchService; + + private final ITreeService treeService; + + private final List treeEventHandlers; + + private final ISubscriptionManagerFactory subscriptionManagerFactory; + + private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; + + public ModelBrowserEventProcessorFactory(IRepresentationDescriptionSearchService representationDescriptionSearchService, List treeEventHandlers, ITreeService treeService, IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry, ISubscriptionManagerFactory subscriptionManagerFactory) { + this.representationDescriptionSearchService = Objects.requireNonNull(representationDescriptionSearchService); + this.treeService = Objects.requireNonNull(treeService); + this.treeEventHandlers = Objects.requireNonNull(treeEventHandlers); + this.subscriptionManagerFactory = Objects.requireNonNull(subscriptionManagerFactory); + this.representationRefreshPolicyRegistry = Objects.requireNonNull(representationRefreshPolicyRegistry); + } + + @Override + public boolean canHandle(IRepresentationConfiguration configuration) { + return configuration instanceof ModelBrowserConfiguration; + } + + @Override + public Optional createRepresentationEventProcessor(IRepresentationConfiguration configuration, IEditingContext editingContext) { + if (configuration instanceof ModelBrowserConfiguration modelBrowserConfiguration) { + + String descriptionId; + if (modelBrowserConfiguration.getId().startsWith(ModelBrowsersDescriptionProvider.MODEL_BROWSER_CONTAINER_PREFIX)) { + descriptionId = ModelBrowsersDescriptionProvider.CONTAINER_DESCRIPTION_ID; + } else { + descriptionId = ModelBrowsersDescriptionProvider.REFERENCE_DESCRIPTION_ID; + } + + Optional optionalTreeDescription = this.representationDescriptionSearchService + .findById(editingContext, descriptionId) + .filter(TreeDescription.class::isInstance) + .map(TreeDescription.class::cast); + if (optionalTreeDescription.isPresent()) { + var treeDescription = optionalTreeDescription.get(); + + TreeCreationParameters treeCreationParameters = TreeCreationParameters.newTreeCreationParameters(modelBrowserConfiguration.getId()) + .treeDescription(treeDescription) + .activeFilterIds(List.of()) + .expanded(modelBrowserConfiguration.getExpanded()) + .editingContext(editingContext) + .build(); + + IRepresentationEventProcessor treeEventProcessor = new TreeEventProcessor(editingContext, this.treeService, treeCreationParameters, this.treeEventHandlers, + this.subscriptionManagerFactory.create(), new SimpleMeterRegistry(), this.representationRefreshPolicyRegistry); + return Optional.of(treeEventProcessor); + } + } + return Optional.empty(); + } + +} diff --git a/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/browser/ModelBrowsersDescriptionProvider.java b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/browser/ModelBrowsersDescriptionProvider.java index fe7e83e28bd..a880fd7896d 100644 --- a/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/browser/ModelBrowsersDescriptionProvider.java +++ b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/browser/ModelBrowsersDescriptionProvider.java @@ -22,7 +22,6 @@ import java.util.Optional; import java.util.UUID; import java.util.function.Function; -import java.util.function.Predicate; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EClass; @@ -34,7 +33,6 @@ import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.edit.command.CommandParameter; import org.eclipse.emf.edit.domain.EditingDomain; -import org.eclipse.sirius.components.collaborative.trees.api.TreeConfiguration; import org.eclipse.sirius.components.collaborative.widget.reference.api.IReferenceWidgetRootCandidateSearchProvider; import org.eclipse.sirius.components.core.CoreImageConstants; import org.eclipse.sirius.components.core.URLParser; @@ -99,34 +97,28 @@ public ModelBrowsersDescriptionProvider(IObjectService objectService, IURLParser @Override public List getRepresentationDescriptions(IEditingContext editingContext) { - Predicate containerDescriptionCanCreatePredicate = variableManager -> variableManager.get(TreeConfiguration.TREE_ID, String.class) - .map(treeId -> treeId.startsWith(MODEL_BROWSER_CONTAINER_PREFIX)) - .orElse(false); Function containerDescriptionIsSelectableProvider = variableManager -> { EClass referenceKind = this.resolveReferenceEClass(variableManager).orElse(null); return this.isContainerSelectable(variableManager, referenceKind); }; - var containerDescription = this.getModelBrowserDescription(CONTAINER_DESCRIPTION_ID, containerDescriptionCanCreatePredicate, containerDescriptionIsSelectableProvider, this::getCreationScopeElements); + var containerDescription = this.getModelBrowserDescription(CONTAINER_DESCRIPTION_ID, containerDescriptionIsSelectableProvider, this::getCreationScopeElements, MODEL_BROWSER_CONTAINER_PREFIX); - Predicate referenceDescriptionCanCreatePredicate = variableManager -> variableManager.get(TreeConfiguration.TREE_ID, String.class) - .map(treeId -> treeId.startsWith(MODEL_BROWSER_REFERENCE_PREFIX)) - .orElse(false); Function referenceDescriptionIsSelectableProvider = variableManager -> { EClass targetType = this.resolveTargetType(variableManager).orElse(null); boolean isContainment = this.resolveIsContainment(variableManager); return this.isTypeSelectable(variableManager, targetType, isContainment); }; - var referenceDescription = this.getModelBrowserDescription(REFERENCE_DESCRIPTION_ID, referenceDescriptionCanCreatePredicate, referenceDescriptionIsSelectableProvider, this::getSearchScopeElements); + var referenceDescription = this.getModelBrowserDescription(REFERENCE_DESCRIPTION_ID, referenceDescriptionIsSelectableProvider, this::getSearchScopeElements, MODEL_BROWSER_REFERENCE_PREFIX); return List.of(containerDescription, referenceDescription); } - private TreeDescription getModelBrowserDescription(String descriptionId, Predicate canCreatePredicate, Function isSelectableProvider, - Function> elementsProvider) { + private TreeDescription getModelBrowserDescription(String descriptionId, Function isSelectableProvider, + Function> elementsProvider, String treeId) { return TreeDescription.newTreeDescription(descriptionId) .label(REPRESENTATION_NAME) - .idProvider(variableManager -> variableManager.get(TreeConfiguration.TREE_ID, String.class).orElse(null)) + .idProvider(variableManager -> variableManager.get(GetOrCreateRandomIdProvider.PREVIOUS_REPRESENTATION_ID, String.class).orElse(treeId)) .treeItemIdProvider(this::getTreeItemId) .kindProvider(this::getKind) .labelProvider(this::getLabel) @@ -140,7 +132,7 @@ private TreeDescription getModelBrowserDescription(String descriptionId, Predica .childrenProvider(variableManager -> this.getChildren(variableManager, isSelectableProvider)) // This predicate will NOT be used while creating the model browser, but we don't want to see the description of the // model browser in the list of representations that can be created. Thus, we will return false all the time. - .canCreatePredicate(canCreatePredicate) + .canCreatePredicate(variableManager -> false) .deleteHandler(this::getDeleteHandler) .renameHandler(this::getRenameHandler) .treeItemObjectProvider(this::getTreeItemObject) diff --git a/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/configurations/ModelBrowserConfiguration.java b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/configurations/ModelBrowserConfiguration.java new file mode 100644 index 00000000000..c35af506c83 --- /dev/null +++ b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/configurations/ModelBrowserConfiguration.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * 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.widget.reference.configurations; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; + +import org.eclipse.sirius.components.collaborative.api.IRepresentationConfiguration; +import org.eclipse.sirius.components.collaborative.widget.reference.browser.ModelBrowsersDescriptionProvider; + +/** + * The configuration of the model browser event processor. + * + * @author Jerome Gout + */ +public class ModelBrowserConfiguration implements IRepresentationConfiguration { + + private final String treeId; + + private final List expanded; + + public ModelBrowserConfiguration(String editingContextId, String treeId, List expanded) { + this.expanded = Objects.requireNonNull(expanded); + + StringBuilder idBuilder = new StringBuilder(treeId); + if (treeId.endsWith(ModelBrowsersDescriptionProvider.MODEL_BROWSER_CONTAINER_PREFIX) || treeId.endsWith(ModelBrowsersDescriptionProvider.MODEL_BROWSER_REFERENCE_PREFIX)) { + idBuilder.append("?"); + } else { + idBuilder.append("&"); + } + + List expandedObjectIds = expanded.stream().map(id -> URLEncoder.encode(id, StandardCharsets.UTF_8)).toList(); + idBuilder.append("expandedIds=[").append(String.join(",", expandedObjectIds)).append("]"); + + this.treeId = idBuilder.toString(); + } + + @Override + public String getId() { + return this.treeId; + } + + public List getExpanded() { + return this.expanded; + } + +} diff --git a/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/dto/ModelBrowserEventInput.java b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/dto/ModelBrowserEventInput.java new file mode 100644 index 00000000000..78a781e72b7 --- /dev/null +++ b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/dto/ModelBrowserEventInput.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * 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.widget.reference.dto; + +import java.util.List; +import java.util.UUID; + +import org.eclipse.sirius.components.core.api.IInput; + +/** + * The input of the model browser event subscription. + * + * @author Jerome Gout + */ +public record ModelBrowserEventInput(UUID id, String editingContextId, String treeId, List expanded) implements IInput { +} diff --git a/packages/forms/backend/sirius-components-collaborative-widget-reference/src/test/java/org/eclipse/sirius/components/collaborative/widget/reference/provider/ModelBrowserTests.java b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/test/java/org/eclipse/sirius/components/collaborative/widget/reference/provider/ModelBrowserTests.java index 76e4b11181c..cb3d76cc609 100644 --- a/packages/forms/backend/sirius-components-collaborative-widget-reference/src/test/java/org/eclipse/sirius/components/collaborative/widget/reference/provider/ModelBrowserTests.java +++ b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/test/java/org/eclipse/sirius/components/collaborative/widget/reference/provider/ModelBrowserTests.java @@ -16,7 +16,6 @@ import java.util.List; -import org.eclipse.sirius.components.collaborative.trees.api.TreeConfiguration; import org.eclipse.sirius.components.collaborative.widget.reference.browser.ModelBrowsersDescriptionProvider; import org.eclipse.sirius.components.core.URLParser; import org.eclipse.sirius.components.core.api.IEditingContext; @@ -55,7 +54,6 @@ public void testModelBrowserDescriptionProvider() { variableManager.put(IEditingContext.EDITING_CONTEXT, editingContext); var referenceTreeId = "modelBrowser://reference"; - variableManager.put(TreeConfiguration.TREE_ID, referenceTreeId); var tree = new TreeRenderer(variableManager, referenceBrowserDescription.get()).render(); assertThat(tree).isNotNull(); assertThat(tree.getId()).isEqualTo(referenceTreeId); @@ -63,7 +61,6 @@ public void testModelBrowserDescriptionProvider() { assertThat(tree.getKind()).isEqualTo(Tree.KIND); var containerTreeId = "modelBrowser://container"; - variableManager.put(TreeConfiguration.TREE_ID, containerTreeId); tree = new TreeRenderer(variableManager, containerBrowserDescription.get()).render(); assertThat(tree).isNotNull(); assertThat(tree.getId()).isEqualTo(containerTreeId); diff --git a/packages/forms/backend/sirius-components-widget-reference-graphql/src/main/java/org/eclipse/sirius/components/widget/reference/graphql/datafetchers/subscription/SubscriptionModelBrowserEventDataFetcher.java b/packages/forms/backend/sirius-components-widget-reference-graphql/src/main/java/org/eclipse/sirius/components/widget/reference/graphql/datafetchers/subscription/SubscriptionModelBrowserEventDataFetcher.java new file mode 100644 index 00000000000..0554d810d7c --- /dev/null +++ b/packages/forms/backend/sirius-components-widget-reference-graphql/src/main/java/org/eclipse/sirius/components/widget/reference/graphql/datafetchers/subscription/SubscriptionModelBrowserEventDataFetcher.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * 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.widget.reference.graphql.datafetchers.subscription; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.sirius.components.annotations.spring.graphql.SubscriptionDataFetcher; +import org.eclipse.sirius.components.collaborative.widget.reference.configurations.ModelBrowserConfiguration; +import org.eclipse.sirius.components.collaborative.widget.reference.dto.ModelBrowserEventInput; +import org.eclipse.sirius.components.core.api.IPayload; +import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates; +import org.eclipse.sirius.components.graphql.api.IEventProcessorSubscriptionProvider; +import org.eclipse.sirius.components.graphql.api.IExceptionWrapper; +import org.eclipse.sirius.components.graphql.api.LocalContextConstants; +import org.reactivestreams.Publisher; + +import graphql.execution.DataFetcherResult; +import graphql.schema.DataFetchingEnvironment; + +/** + * The data fetcher used to send the refreshed tree to a model browser subscription . + * + * @author Jerome Gout + */ +@SubscriptionDataFetcher(type = "Subscription", field = "modelBrowserEvent") +public class SubscriptionModelBrowserEventDataFetcher implements IDataFetcherWithFieldCoordinates>> { + + private static final String INPUT_ARGUMENT = "input"; + + private final ObjectMapper objectMapper; + + private final IExceptionWrapper exceptionWrapper; + + private final IEventProcessorSubscriptionProvider eventProcessorSubscriptionProvider; + + public SubscriptionModelBrowserEventDataFetcher(ObjectMapper objectMapper, IExceptionWrapper exceptionWrapper, IEventProcessorSubscriptionProvider eventProcessorSubscriptionProvider) { + this.objectMapper = Objects.requireNonNull(objectMapper); + this.exceptionWrapper = Objects.requireNonNull(exceptionWrapper); + this.eventProcessorSubscriptionProvider = Objects.requireNonNull(eventProcessorSubscriptionProvider); + } + + @Override + public Publisher> get(DataFetchingEnvironment environment) throws Exception { + Object argument = environment.getArgument(INPUT_ARGUMENT); + var input = this.objectMapper.convertValue(argument, ModelBrowserEventInput.class); + var modelBrowserConfiguration = new ModelBrowserConfiguration(input.editingContextId(), input.treeId(), input.expanded()); + + Map localContext = new HashMap<>(); + localContext.put(LocalContextConstants.EDITING_CONTEXT_ID, input.editingContextId()); + localContext.put(LocalContextConstants.REPRESENTATION_ID, modelBrowserConfiguration.getId()); + + return this.exceptionWrapper.wrapFlux(() -> this.eventProcessorSubscriptionProvider.getSubscription(input.editingContextId(), modelBrowserConfiguration, input), input) + .map(payload -> DataFetcherResult.newResult() + .data(payload) + .localContext(localContext) + .build()); + } +} diff --git a/packages/forms/backend/sirius-components-widget-reference/src/main/resources/schema/modelbrowser.graphqls b/packages/forms/backend/sirius-components-widget-reference/src/main/resources/schema/modelbrowser.graphqls new file mode 100644 index 00000000000..7ef36e3356f --- /dev/null +++ b/packages/forms/backend/sirius-components-widget-reference/src/main/resources/schema/modelbrowser.graphqls @@ -0,0 +1,10 @@ +extend type Subscription { + modelBrowserEvent(input: ModelBrowserEventInput!): TreeEventPayload! +} + +input ModelBrowserEventInput { + id: ID! + treeId: String! + editingContextId: ID! + expanded: [String!]! +} \ No newline at end of file diff --git a/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.tsx b/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.tsx index 9db03cfedde..76fe368e763 100644 --- a/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.tsx +++ b/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.tsx @@ -11,13 +11,14 @@ * Obeo - initial API and implementation *******************************************************************************/ -import { TreeView, TreeItemActionProps } from '@eclipse-sirius/sirius-components-trees'; -import IconButton from '@mui/material/IconButton'; -import { makeStyles } from 'tss-react/mui'; +import { TreeItemActionProps, TreeView } from '@eclipse-sirius/sirius-components-trees'; import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore'; +import IconButton from '@mui/material/IconButton'; import { useState } from 'react'; +import { makeStyles } from 'tss-react/mui'; import { ModelBrowserFilterBar } from './ModelBrowserFilterBar'; import { ModelBrowserTreeViewProps, ModelBrowserTreeViewState } from './ModelBrowserTreeView.types'; +import { useModelBrowserSubscription } from './useModelBrowserSubscription'; const useTreeStyle = makeStyles()((theme) => ({ title: { @@ -43,33 +44,47 @@ export const ModelBrowserTreeView = ({ }: ModelBrowserTreeViewProps) => { const { classes } = useTreeStyle(); - const [state, setState] = useState({ filterBarText: '' }); + const [state, setState] = useState({ + filterBarText: '', + expanded: [], + maxDepth: 1, + }); + + const treeId: string = `modelBrowser://${leafType}?ownerKind=${encodeURIComponent( + ownerKind + )}&targetType=${encodeURIComponent(widget.reference.referenceKind)}&ownerId=${ + widget.ownerId + }&descriptionId=${encodeURIComponent(widget.descriptionId)}&isContainment=${widget.reference.containment}`; + const { tree } = useModelBrowserSubscription(editingContextId, treeId, state.expanded, state.maxDepth); + + const onExpandedElementChange = (expanded: string[], maxDepth: number) => { + setState((prevState) => ({ ...prevState, expanded, maxDepth })); + }; return ( <> setState({ filterBarText: event.target.value })} - onTextClear={() => setState({ filterBarText: '' })} + onTextChange={(event) => setState((prevState) => ({ ...prevState, filterBarText: event.target.value }))} + onTextClear={() => setState((prevState) => ({ ...prevState, filterBarText: '' }))} text={state.filterBarText} /> {title}
- } - /> + {tree && ( + } + onExpandedElementChange={onExpandedElementChange} + /> + )}
); diff --git a/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.types.ts b/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.types.ts index 84510c4df5d..1a7c9478733 100644 --- a/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.types.ts +++ b/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.types.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Obeo. + * Copyright (c) 2023, 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 @@ -24,4 +24,6 @@ export interface ModelBrowserTreeViewProps { export interface ModelBrowserTreeViewState { filterBarText: string; + expanded: string[]; + maxDepth: number; } diff --git a/packages/forms/frontend/sirius-components-widget-reference/src/components/useModelBrowserSubscription.tsx b/packages/forms/frontend/sirius-components-widget-reference/src/components/useModelBrowserSubscription.tsx new file mode 100644 index 00000000000..5d3229767f8 --- /dev/null +++ b/packages/forms/frontend/sirius-components-widget-reference/src/components/useModelBrowserSubscription.tsx @@ -0,0 +1,89 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ + +import { gql, OnDataOptions, useSubscription } from '@apollo/client'; +import { useMultiToast } from '@eclipse-sirius/sirius-components-core'; +import { + getTreeEventSubscription, + GQLTreeEventPayload, + GQLTreeRefreshedEventPayload, + UseTreeSubscriptionState, + UseTreeSubscriptionValue, +} from '@eclipse-sirius/sirius-components-trees'; +import { useEffect, useState } from 'react'; +import { + GQLModelBrowserEventData, + GQLModelBrowserEventInput, + GQLModelBrowserEventVariables, +} from './useModelBrowserSubscription.types'; + +const isTreeRefreshedEventPayload = (payload: GQLTreeEventPayload): payload is GQLTreeRefreshedEventPayload => + payload.__typename === 'TreeRefreshedEventPayload'; + +export const useModelBrowserSubscription = ( + editingContextId: string, + treeId: string, + expanded: string[], + maxDepth: number +): UseTreeSubscriptionValue => { + const [state, setState] = useState({ + id: crypto.randomUUID(), + tree: null, + complete: false, + }); + + const input: GQLModelBrowserEventInput = { + id: state.id, + editingContextId, + treeId, + expanded, + }; + + const variables: GQLModelBrowserEventVariables = { input }; + + const onData = ({ data }: OnDataOptions) => { + const { data: gqlTreeData } = data; + if (gqlTreeData) { + const { modelBrowserEvent: payload } = gqlTreeData; + if (isTreeRefreshedEventPayload(payload)) { + const { tree } = payload; + setState((prevState) => ({ ...prevState, tree })); + } + } + }; + + const onComplete = () => setState((prevState) => ({ ...prevState, complete: true })); + + const { error, loading } = useSubscription( + gql(getTreeEventSubscription(maxDepth, 'modelBrowserEvent', 'ModelBrowserEventInput')), + { + variables, + fetchPolicy: 'no-cache', + onData, + onComplete, + } + ); + + const { addErrorMessage } = useMultiToast(); + useEffect(() => { + if (error) { + addErrorMessage('An unexpected error has occurred, please refresh the page'); + } + }, [error]); + + return { + loading, + tree: state.tree, + complete: state.complete, + }; +}; diff --git a/packages/forms/frontend/sirius-components-widget-reference/src/components/useModelBrowserSubscription.types.ts b/packages/forms/frontend/sirius-components-widget-reference/src/components/useModelBrowserSubscription.types.ts new file mode 100644 index 00000000000..47f752e5c13 --- /dev/null +++ b/packages/forms/frontend/sirius-components-widget-reference/src/components/useModelBrowserSubscription.types.ts @@ -0,0 +1,28 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +import { GQLTreeEventPayload } from '@eclipse-sirius/sirius-components-trees'; + +export interface GQLModelBrowserEventInput { + id: string; + treeId: string; + editingContextId: string; + expanded: string[]; +} + +export interface GQLModelBrowserEventVariables { + input: GQLModelBrowserEventInput; +} + +export interface GQLModelBrowserEventData { + modelBrowserEvent: GQLTreeEventPayload; +} diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/api/TreeConfiguration.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerConfiguration.java similarity index 84% rename from packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/api/TreeConfiguration.java rename to packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerConfiguration.java index fe1e07153a7..44f6bceb923 100644 --- a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/api/TreeConfiguration.java +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerConfiguration.java @@ -10,7 +10,7 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -package org.eclipse.sirius.components.collaborative.trees.api; +package org.eclipse.sirius.web.application.views.explorer; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -20,13 +20,11 @@ import org.eclipse.sirius.components.collaborative.api.IRepresentationConfiguration; /** - * The configuration of the tree event processor. + * The configuration of the explorer event processor. * * @author sbegaudeau */ -public class TreeConfiguration implements IRepresentationConfiguration { - - public static final String TREE_ID = "treeId"; +public class ExplorerConfiguration implements IRepresentationConfiguration { private final String treeId; @@ -34,7 +32,7 @@ public class TreeConfiguration implements IRepresentationConfiguration { private final List expanded; - public TreeConfiguration(String editingContextId, String treeId, List expanded, List activeFilters) { + public ExplorerConfiguration(String editingContextId, String treeId, List expanded, List activeFilters) { this.activeFilterIds = Objects.requireNonNull(activeFilters); this.expanded = Objects.requireNonNull(expanded); diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/TreeEventInput.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerEventInput.java similarity index 71% rename from packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/TreeEventInput.java rename to packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerEventInput.java index 51051c087e4..bc063a2e2ad 100644 --- a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/TreeEventInput.java +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerEventInput.java @@ -10,7 +10,7 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -package org.eclipse.sirius.components.collaborative.trees.dto; +package org.eclipse.sirius.web.application.views.explorer; import java.util.List; import java.util.UUID; @@ -18,9 +18,9 @@ import org.eclipse.sirius.components.core.api.IInput; /** - * The input of the tree event subscription. + * The input of the explorer event subscription. * * @author sbegaudeau */ -public record TreeEventInput(UUID id, String editingContextId, String treeId, List expanded, List activeFilterIds) implements IInput { +public record ExplorerEventInput(UUID id, String editingContextId, String treeId, List expanded, List activeFilterIds) implements IInput { } diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeEventProcessorFactory.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerEventProcessorFactory.java similarity index 75% rename from packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeEventProcessorFactory.java rename to packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerEventProcessorFactory.java index d911fe13363..99c991df867 100644 --- a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeEventProcessorFactory.java +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerEventProcessorFactory.java @@ -10,7 +10,7 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -package org.eclipse.sirius.components.collaborative.trees; +package org.eclipse.sirius.web.application.views.explorer; import java.util.List; import java.util.Objects; @@ -22,14 +22,14 @@ import org.eclipse.sirius.components.collaborative.api.IRepresentationEventProcessorFactory; import org.eclipse.sirius.components.collaborative.api.IRepresentationRefreshPolicyRegistry; import org.eclipse.sirius.components.collaborative.api.ISubscriptionManagerFactory; +import org.eclipse.sirius.components.collaborative.trees.TreeEventProcessor; import org.eclipse.sirius.components.collaborative.trees.api.ITreeEventHandler; import org.eclipse.sirius.components.collaborative.trees.api.ITreeService; -import org.eclipse.sirius.components.collaborative.trees.api.TreeConfiguration; import org.eclipse.sirius.components.collaborative.trees.api.TreeCreationParameters; import org.eclipse.sirius.components.core.api.IEditingContext; import org.eclipse.sirius.components.core.api.IRepresentationDescriptionSearchService; -import org.eclipse.sirius.components.representations.VariableManager; import org.eclipse.sirius.components.trees.description.TreeDescription; +import org.eclipse.sirius.web.application.views.explorer.services.ExplorerDescriptionProvider; import org.springframework.stereotype.Service; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; @@ -40,7 +40,7 @@ * @author sbegaudeau */ @Service -public class TreeEventProcessorFactory implements IRepresentationEventProcessorFactory { +public class ExplorerEventProcessorFactory implements IRepresentationEventProcessorFactory { public static final String TREE_ID = UUID.nameUUIDFromBytes("explorer_tree_description".getBytes()).toString(); @@ -54,7 +54,7 @@ public class TreeEventProcessorFactory implements IRepresentationEventProcessorF private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; - public TreeEventProcessorFactory(IRepresentationDescriptionSearchService representationDescriptionSearchService, ITreeService treeService, List treeEventHandlers, + public ExplorerEventProcessorFactory(IRepresentationDescriptionSearchService representationDescriptionSearchService, ITreeService treeService, List treeEventHandlers, ISubscriptionManagerFactory subscriptionManagerFactory, IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry) { this.representationDescriptionSearchService = Objects.requireNonNull(representationDescriptionSearchService); this.treeService = Objects.requireNonNull(treeService); @@ -65,14 +65,17 @@ public TreeEventProcessorFactory(IRepresentationDescriptionSearchService represe @Override public boolean canHandle(IRepresentationConfiguration configuration) { - return configuration instanceof TreeConfiguration; + return configuration instanceof ExplorerConfiguration; } @Override public Optional createRepresentationEventProcessor(IRepresentationConfiguration configuration, IEditingContext editingContext) { - if (configuration instanceof TreeConfiguration treeConfiguration) { + if (configuration instanceof ExplorerConfiguration treeConfiguration) { - Optional optionalTreeDescription = this.findTreeDescription(editingContext, treeConfiguration); + Optional optionalTreeDescription = this.representationDescriptionSearchService + .findById(editingContext, ExplorerDescriptionProvider.DESCRIPTION_ID) + .filter(TreeDescription.class::isInstance) + .map(TreeDescription.class::cast); if (optionalTreeDescription.isPresent()) { var treeDescription = optionalTreeDescription.get(); @@ -90,16 +93,4 @@ public Optional createRepresentationEventProcesso } return Optional.empty(); } - - private Optional findTreeDescription(IEditingContext editingContext, TreeConfiguration treeConfiguration) { - VariableManager variableManager = new VariableManager(); - variableManager.put(TreeConfiguration.TREE_ID, treeConfiguration.getId()); - return this.representationDescriptionSearchService - .findAll(editingContext).values().stream() - .filter(TreeDescription.class::isInstance) - .map(TreeDescription.class::cast) - .filter(treeDescription -> treeDescription.getCanCreatePredicate().test(variableManager)) - .findFirst(); - } - } diff --git a/packages/trees/backend/sirius-components-trees-graphql/src/main/java/org/eclipse/sirius/components/trees/graphql/datafetchers/subscription/SubscriptionTreeEventDataFetcher.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/SubscriptionExplorerEventDataFetcher.java similarity index 76% rename from packages/trees/backend/sirius-components-trees-graphql/src/main/java/org/eclipse/sirius/components/trees/graphql/datafetchers/subscription/SubscriptionTreeEventDataFetcher.java rename to packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/SubscriptionExplorerEventDataFetcher.java index 09dd6db54d7..c8b8fc28448 100644 --- a/packages/trees/backend/sirius-components-trees-graphql/src/main/java/org/eclipse/sirius/components/trees/graphql/datafetchers/subscription/SubscriptionTreeEventDataFetcher.java +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/SubscriptionExplorerEventDataFetcher.java @@ -10,7 +10,7 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -package org.eclipse.sirius.components.trees.graphql.datafetchers.subscription; +package org.eclipse.sirius.web.application.views.explorer; import com.fasterxml.jackson.databind.ObjectMapper; @@ -19,8 +19,6 @@ import java.util.Objects; import org.eclipse.sirius.components.annotations.spring.graphql.SubscriptionDataFetcher; -import org.eclipse.sirius.components.collaborative.trees.api.TreeConfiguration; -import org.eclipse.sirius.components.collaborative.trees.dto.TreeEventInput; import org.eclipse.sirius.components.core.api.IPayload; import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates; import org.eclipse.sirius.components.graphql.api.IEventProcessorSubscriptionProvider; @@ -37,8 +35,8 @@ * @author hmarchadour * @author pcdavid */ -@SubscriptionDataFetcher(type = "Subscription", field = "treeEvent") -public class SubscriptionTreeEventDataFetcher implements IDataFetcherWithFieldCoordinates>> { +@SubscriptionDataFetcher(type = "Subscription", field = "explorerEvent") +public class SubscriptionExplorerEventDataFetcher implements IDataFetcherWithFieldCoordinates>> { private static final String INPUT_ARGUMENT = "input"; @@ -48,7 +46,7 @@ public class SubscriptionTreeEventDataFetcher implements IDataFetcherWithFieldCo private final IEventProcessorSubscriptionProvider eventProcessorSubscriptionProvider; - public SubscriptionTreeEventDataFetcher(ObjectMapper objectMapper, IExceptionWrapper exceptionWrapper, IEventProcessorSubscriptionProvider eventProcessorSubscriptionProvider) { + public SubscriptionExplorerEventDataFetcher(ObjectMapper objectMapper, IExceptionWrapper exceptionWrapper, IEventProcessorSubscriptionProvider eventProcessorSubscriptionProvider) { this.objectMapper = Objects.requireNonNull(objectMapper); this.exceptionWrapper = Objects.requireNonNull(exceptionWrapper); this.eventProcessorSubscriptionProvider = Objects.requireNonNull(eventProcessorSubscriptionProvider); @@ -57,8 +55,8 @@ public SubscriptionTreeEventDataFetcher(ObjectMapper objectMapper, IExceptionWra @Override public Publisher> get(DataFetchingEnvironment environment) throws Exception { Object argument = environment.getArgument(INPUT_ARGUMENT); - var input = this.objectMapper.convertValue(argument, TreeEventInput.class); - var treeConfiguration = new TreeConfiguration(input.editingContextId(), input.treeId(), input.expanded(), input.activeFilterIds()); + var input = this.objectMapper.convertValue(argument, ExplorerEventInput.class); + var treeConfiguration = new ExplorerConfiguration(input.editingContextId(), input.treeId(), input.expanded(), input.activeFilterIds()); Map localContext = new HashMap<>(); localContext.put(LocalContextConstants.EDITING_CONTEXT_ID, input.editingContextId()); diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/services/ExplorerDescriptionProvider.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/services/ExplorerDescriptionProvider.java index 4e6ce924cf9..68c8bec8eb3 100644 --- a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/services/ExplorerDescriptionProvider.java +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/services/ExplorerDescriptionProvider.java @@ -17,7 +17,6 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.function.Predicate; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; @@ -104,10 +103,6 @@ public ExplorerDescriptionProvider(ExplorerDescriptionProviderConfiguration expl @Override public List getRepresentationDescriptions(IEditingContext editingContext) { - Predicate canCreatePredicate = variableManager -> variableManager.get("treeId", String.class) - .map(treeId -> treeId.startsWith(PREFIX)) - .orElse(false); - var explorerTreeDescription = TreeDescription.newTreeDescription(DESCRIPTION_ID) .label(REPRESENTATION_NAME) .idProvider(this::getTreeId) @@ -123,7 +118,9 @@ public List getRepresentationDescriptions(IEditingCo .elementsProvider(this.explorerElementsProvider::getElements) .hasChildrenProvider(this.explorerChildrenProvider::hasChildren) .childrenProvider(this.explorerChildrenProvider::getChildren) - .canCreatePredicate(canCreatePredicate) + // This predicate will NOT be used while creating the explorer, but we don't want to see the description of the + // explorer in the list of representations that can be created. Thus, we will return false all the time. + .canCreatePredicate(variableManager -> false) .deleteHandler(this::getDeleteHandler) .renameHandler(this::getRenameHandler) .treeItemObjectProvider(this::getTreeItemObject) diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/resources/schema/explorer.graphqls b/packages/sirius-web/backend/sirius-web-application/src/main/resources/schema/explorer.graphqls new file mode 100644 index 00000000000..e93489e9e62 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/resources/schema/explorer.graphqls @@ -0,0 +1,11 @@ +extend type Subscription { + explorerEvent(input: ExplorerEventInput!): TreeEventPayload! +} + +input ExplorerEventInput { + id: ID! + treeId: String! + editingContextId: ID! + expanded: [String!]! + activeFilterIds: [String!]! +} \ No newline at end of file diff --git a/packages/sirius-web/backend/sirius-web-tests/pom.xml b/packages/sirius-web/backend/sirius-web-tests/pom.xml index 670a690880f..34368635f3c 100644 --- a/packages/sirius-web/backend/sirius-web-tests/pom.xml +++ b/packages/sirius-web/backend/sirius-web-tests/pom.xml @@ -75,6 +75,11 @@ sirius-components-collaborative-gantt 2024.9.0 + + org.eclipse.sirius + sirius-components-collaborative-widget-reference + 2024.9.0 + diff --git a/packages/trees/backend/sirius-components-trees-tests/src/main/java/org/eclipse/sirius/components/trees/tests/graphql/TreeEventSubscriptionRunner.java b/packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/explorer/ExplorerEventSubscriptionRunner.java similarity index 60% rename from packages/trees/backend/sirius-components-trees-tests/src/main/java/org/eclipse/sirius/components/trees/tests/graphql/TreeEventSubscriptionRunner.java rename to packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/explorer/ExplorerEventSubscriptionRunner.java index 7eeff525780..920e51c3c3e 100644 --- a/packages/trees/backend/sirius-components-trees-tests/src/main/java/org/eclipse/sirius/components/trees/tests/graphql/TreeEventSubscriptionRunner.java +++ b/packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/explorer/ExplorerEventSubscriptionRunner.java @@ -10,28 +10,28 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -package org.eclipse.sirius.components.trees.tests.graphql; +package org.eclipse.sirius.web.tests.services.explorer; import java.util.Objects; -import org.eclipse.sirius.components.collaborative.trees.dto.TreeEventInput; import org.eclipse.sirius.components.graphql.tests.api.IGraphQLRequestor; import org.eclipse.sirius.components.graphql.tests.api.ISubscriptionRunner; +import org.eclipse.sirius.web.application.views.explorer.ExplorerEventInput; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; /** - * Used to get the tree event subscription with the GraphQL API. + * Used to get the explorer event subscription with the GraphQL API. * * @author gdaniel */ @Service -public class TreeEventSubscriptionRunner implements ISubscriptionRunner { +public class ExplorerEventSubscriptionRunner implements ISubscriptionRunner { - private static final String TREE_EVENT_SUBSCRIPTION = """ - subscription treeEvent($input: TreeEventInput!) { - treeEvent(input: $input) { + private static final String EXPLORER_EVENT_SUBSCRIPTION = """ + subscription explorerEvent($input: ExplorerEventInput!) { + explorerEvent(input: $input) { __typename } } @@ -39,13 +39,13 @@ subscription treeEvent($input: TreeEventInput!) { private final IGraphQLRequestor graphQLRequestor; - public TreeEventSubscriptionRunner(IGraphQLRequestor graphQLRequestor) { + public ExplorerEventSubscriptionRunner(IGraphQLRequestor graphQLRequestor) { this.graphQLRequestor = Objects.requireNonNull(graphQLRequestor); } @Override - public Flux run(TreeEventInput input) { - return this.graphQLRequestor.subscribe(TREE_EVENT_SUBSCRIPTION, input); + public Flux run(ExplorerEventInput input) { + return this.graphQLRequestor.subscribe(EXPLORER_EVENT_SUBSCRIPTION, input); } } diff --git a/packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/modelbrowser/ModelBrowserEventSubscriptionRunner.java b/packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/modelbrowser/ModelBrowserEventSubscriptionRunner.java new file mode 100644 index 00000000000..2c568f472cb --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/modelbrowser/ModelBrowserEventSubscriptionRunner.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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.tests.services.modelbrowser; + +import java.util.Objects; + +import org.eclipse.sirius.components.collaborative.widget.reference.dto.ModelBrowserEventInput; +import org.eclipse.sirius.components.graphql.tests.api.IGraphQLRequestor; +import org.eclipse.sirius.components.graphql.tests.api.ISubscriptionRunner; +import org.springframework.stereotype.Service; + +import reactor.core.publisher.Flux; + +/** + * Used to get the model browser event subscription with the GraphQL API. + * + * @author Jerome Gout + */ +@Service +public class ModelBrowserEventSubscriptionRunner implements ISubscriptionRunner { + + private static final String MODEL_BROWSER_EVENT_SUBSCRIPTION = """ + subscription modelBrowserEvent($input: ModelBrowserEventInput!) { + modelBrowserEvent(input: $input) { + __typename + } + } + """; + + private final IGraphQLRequestor graphQLRequestor; + + public ModelBrowserEventSubscriptionRunner(IGraphQLRequestor graphQLRequestor) { + this.graphQLRequestor = Objects.requireNonNull(graphQLRequestor); + } + + @Override + public Flux run(ModelBrowserEventInput input) { + return this.graphQLRequestor.subscribe(MODEL_BROWSER_EVENT_SUBSCRIPTION, input); + } + +} diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerExpandAllControllerTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerExpandAllControllerTests.java index 0d47c8d2983..64d3eb77ede 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerExpandAllControllerTests.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerExpandAllControllerTests.java @@ -33,7 +33,6 @@ import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.sirius.components.collaborative.api.ChangeDescription; import org.eclipse.sirius.components.collaborative.api.ChangeKind; -import org.eclipse.sirius.components.collaborative.trees.dto.TreeEventInput; import org.eclipse.sirius.components.collaborative.trees.dto.TreeRefreshedEventPayload; import org.eclipse.sirius.components.core.api.IEditingContext; import org.eclipse.sirius.components.core.api.IIdentityService; @@ -45,14 +44,15 @@ import org.eclipse.sirius.components.graphql.tests.ExecuteEditingContextFunctionSuccessPayload; import org.eclipse.sirius.components.trees.Tree; import org.eclipse.sirius.components.trees.tests.graphql.ExpandAllTreePathQueryRunner; -import org.eclipse.sirius.components.trees.tests.graphql.TreeEventSubscriptionRunner; import org.eclipse.sirius.components.view.RepresentationDescription; 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.services.PapayaViewInjector; import org.eclipse.sirius.web.services.TaskViewInjector; import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState; +import org.eclipse.sirius.web.tests.services.explorer.ExplorerEventSubscriptionRunner; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -79,7 +79,7 @@ public class ExplorerExpandAllControllerTests extends AbstractIntegrationTests { private IGivenInitialServerState givenInitialServerState; @Autowired - private TreeEventSubscriptionRunner treeEventSubscriptionRunner; + private ExplorerEventSubscriptionRunner treeEventSubscriptionRunner; @Autowired private ExecuteEditingContextFunctionRunner executeEditingContextFunctionRunner; @@ -118,7 +118,7 @@ public void givenStudioWhenWeAskForTheTreePathOfTaskViewObjectThenItsPathInTheEx } public void givenStudioWhenWeAskForTheTreePathOfAnObjectThenItsPathInTheExplorerIsReturned(BiFunction objectInjector) { - var input = new TreeEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, List.of(), List.of()); + var input = new ExplorerEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, List.of(), List.of()); var flux = this.treeEventSubscriptionRunner.run(input); var treeId = new AtomicReference(); @@ -192,7 +192,7 @@ public void givenStudioWhenWeAskForTheTreePathOfAnObjectThenItsPathInTheExplorer .thenCancel() .verify(Duration.ofSeconds(10)); - var expandedTreeInput = new TreeEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, treeItemIds.get(), List.of()); + var expandedTreeInput = new ExplorerEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, treeItemIds.get(), List.of()); var expandedTreeFlux = this.treeEventSubscriptionRunner.run(expandedTreeInput); Consumer initialExpandedTreeContentConsumer = this.getTreeSubscriptionConsumer(tree -> { diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerNonSemanticElementControllerTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerNonSemanticElementControllerTests.java index e9b906c8a4f..8f9c7e1c365 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerNonSemanticElementControllerTests.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerNonSemanticElementControllerTests.java @@ -25,16 +25,16 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; -import org.eclipse.sirius.components.collaborative.trees.dto.TreeEventInput; import org.eclipse.sirius.components.collaborative.trees.dto.TreeRefreshedEventPayload; 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.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.services.api.IGivenInitialServerState; +import org.eclipse.sirius.web.tests.services.explorer.ExplorerEventSubscriptionRunner; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -61,7 +61,7 @@ public class ExplorerNonSemanticElementControllerTests extends AbstractIntegrati private IGivenInitialServerState givenInitialServerState; @Autowired - private TreeEventSubscriptionRunner treeEventSubscriptionRunner; + private ExplorerEventSubscriptionRunner treeEventSubscriptionRunner; @Autowired private ExpandAllTreePathQueryRunner expandAllTreePathQueryRunner; @@ -76,7 +76,7 @@ public void beforeEach() { @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 givenStudioWhenWeAskForTheTreePathOfPapayaViewObjectThenItsPathInTheExplorerIsReturned() { - var input = new TreeEventInput(UUID.randomUUID(), StudioIdentifiers.SAMPLE_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, List.of(), List.of()); + var input = new ExplorerEventInput(UUID.randomUUID(), StudioIdentifiers.SAMPLE_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, List.of(), List.of()); var flux = this.treeEventSubscriptionRunner.run(input); var treeId = new AtomicReference(); @@ -118,7 +118,7 @@ public void givenStudioWhenWeAskForTheTreePathOfPapayaViewObjectThenItsPathInThe .verify(Duration.ofSeconds(10)); - var expandedTreeInput = new TreeEventInput(UUID.randomUUID(), StudioIdentifiers.SAMPLE_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, treeItemIds.get(), List.of()); + var expandedTreeInput = new ExplorerEventInput(UUID.randomUUID(), StudioIdentifiers.SAMPLE_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, treeItemIds.get(), List.of()); var expandedTreeFlux = this.treeEventSubscriptionRunner.run(expandedTreeInput); var rootTreeItemId = new AtomicReference(); diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerTreeFilterControllerTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerTreeFilterControllerTests.java index 85b64051380..3820a0d0dbb 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerTreeFilterControllerTests.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerTreeFilterControllerTests.java @@ -24,17 +24,17 @@ import java.util.UUID; import java.util.function.Consumer; -import org.eclipse.sirius.components.collaborative.trees.dto.TreeEventInput; import org.eclipse.sirius.components.collaborative.trees.dto.TreeRefreshedEventPayload; import org.eclipse.sirius.components.emf.services.api.IEMFEditingContext; -import org.eclipse.sirius.components.trees.tests.graphql.TreeEventSubscriptionRunner; import org.eclipse.sirius.components.trees.tests.graphql.TreeFiltersQueryRunner; import org.eclipse.sirius.components.view.util.services.ColorPaletteService; import org.eclipse.sirius.web.AbstractIntegrationTests; import org.eclipse.sirius.web.application.studio.services.StudioExplorerTreeFilterProvider; +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.services.api.IGivenInitialServerState; +import org.eclipse.sirius.web.tests.services.explorer.ExplorerEventSubscriptionRunner; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -60,7 +60,7 @@ public class ExplorerTreeFilterControllerTests extends AbstractIntegrationTests private IGivenInitialServerState givenInitialServerState; @Autowired - private TreeEventSubscriptionRunner treeEventSubscriptionRunner; + private ExplorerEventSubscriptionRunner treeEventSubscriptionRunner; @Autowired private TreeFiltersQueryRunner treeFiltersQueryRunner; @@ -92,7 +92,7 @@ public void givenTreeIdWhenWeRequestItsTreeFiltersThenTheListIsReturned() { @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 givenStudioWhenFilterToHideDefaultPaletteIsActiveThenTheDefaultPaletteIsHidden() { - var input = new TreeEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, List.of(), List.of(StudioExplorerTreeFilterProvider.HIDE_STUDIO_COLOR_PALETTES_TREE_FILTER_ID)); + var input = new ExplorerEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, List.of(), List.of(StudioExplorerTreeFilterProvider.HIDE_STUDIO_COLOR_PALETTES_TREE_FILTER_ID)); var flux = this.treeEventSubscriptionRunner.run(input); Consumer projectContentConsumer = object -> Optional.of(object) @@ -117,7 +117,7 @@ public void givenStudioWhenFilterToHideDefaultPaletteIsActiveThenTheDefaultPalet @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 givenStudioWhenFilterToHideDefaultPaletteIsInactiveThenTheDefaultPaletteIsHidden() { - var input = new TreeEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, List.of(), List.of()); + var input = new ExplorerEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, List.of(), List.of()); var flux = this.treeEventSubscriptionRunner.run(input); var defaultPaletteTreeItemId = ColorPaletteService.SIRIUS_STUDIO_COLOR_PALETTES_URI.substring((IEMFEditingContext.RESOURCE_SCHEME + ":///").length()); diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerTreePathControllerTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerTreePathControllerTests.java index eefcec1b125..74c4a2ae919 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerTreePathControllerTests.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ExplorerTreePathControllerTests.java @@ -31,7 +31,6 @@ import org.eclipse.sirius.components.collaborative.api.ChangeDescription; import org.eclipse.sirius.components.collaborative.api.ChangeKind; -import org.eclipse.sirius.components.collaborative.trees.dto.TreeEventInput; import org.eclipse.sirius.components.collaborative.trees.dto.TreeRefreshedEventPayload; import org.eclipse.sirius.components.core.api.IEditingContext; import org.eclipse.sirius.components.core.api.IIdentityService; @@ -41,14 +40,15 @@ import org.eclipse.sirius.components.graphql.tests.ExecuteEditingContextFunctionInput; import org.eclipse.sirius.components.graphql.tests.ExecuteEditingContextFunctionRunner; import org.eclipse.sirius.components.graphql.tests.ExecuteEditingContextFunctionSuccessPayload; -import org.eclipse.sirius.components.trees.tests.graphql.TreeEventSubscriptionRunner; import org.eclipse.sirius.components.trees.tests.graphql.TreePathQueryRunner; import org.eclipse.sirius.components.view.diagram.RectangularNodeStyleDescription; 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.services.PapayaViewInjector; import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState; +import org.eclipse.sirius.web.tests.services.explorer.ExplorerEventSubscriptionRunner; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -74,7 +74,7 @@ public class ExplorerTreePathControllerTests extends AbstractIntegrationTests { private IGivenInitialServerState givenInitialServerState; @Autowired - private TreeEventSubscriptionRunner treeEventSubscriptionRunner; + private ExplorerEventSubscriptionRunner treeEventSubscriptionRunner; @Autowired private ExecuteEditingContextFunctionRunner executeEditingContextFunctionRunner; @@ -98,7 +98,7 @@ public void beforeEach() { @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 givenStudioWhenWeAskForTheTreePathOfAnObjectThenItsPathInTheExplorerIsReturned() { - var input = new TreeEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, List.of(), List.of()); + var input = new ExplorerEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, List.of(), List.of()); var flux = this.treeEventSubscriptionRunner.run(input); var treeId = new AtomicReference(); diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ModelBrowserExpandAllControllerTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ModelBrowserExpandAllControllerTests.java index 8416789a415..583124e75ab 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ModelBrowserExpandAllControllerTests.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ModelBrowserExpandAllControllerTests.java @@ -25,15 +25,14 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; -import org.eclipse.sirius.components.collaborative.trees.dto.TreeEventInput; import org.eclipse.sirius.components.collaborative.trees.dto.TreeRefreshedEventPayload; +import org.eclipse.sirius.components.collaborative.widget.reference.dto.ModelBrowserEventInput; import org.eclipse.sirius.components.trees.Tree; 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.application.views.explorer.services.ExplorerDescriptionProvider; import org.eclipse.sirius.web.data.StudioIdentifiers; import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState; +import org.eclipse.sirius.web.tests.services.modelbrowser.ModelBrowserEventSubscriptionRunner; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -56,11 +55,14 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class ModelBrowserExpandAllControllerTests extends AbstractIntegrationTests { + private static final String TREE_ID = "modelBrowser://container?ownerKind=siriusComponents%3A%2F%2Fsemantic%3Fdomain%3Dview%26entity%3DUserColor&targetType=siriusComponents%3A%2F%2Fsemantic%3Fdomain%3Dview%26entity%3DUserColor" + + "&ownerId=7ec7c9bf-96a1-4f69-8441-176161c08877&descriptionId=rectangular.nodestyle.background&isContainment=false"; + @Autowired private IGivenInitialServerState givenInitialServerState; @Autowired - private TreeEventSubscriptionRunner treeEventSubscriptionRunner; + private ModelBrowserEventSubscriptionRunner treeEventSubscriptionRunner; @Autowired private ExpandAllTreePathQueryRunner expandAllTreePathQueryRunner; @@ -70,14 +72,12 @@ public void beforeEach() { this.givenInitialServerState.initialize(); } - @Test @DisplayName("Given a reference widget, when we ask for the tree path to expand all a document, then its path in the explorer is 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 givenReferenceWidgetWhenWeAskForTheTreePathToExpandAllThenItsPathInTheExplorerIsReturned() { - var input = new TreeEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), "modelBrowser://container?ownerKind=siriusComponents%3A%2F%2Fsemantic%3Fdomain%3Dview%26entity%3DUserColor&targetType=siriusComponents%3A%2F%2Fsemantic%3Fdomain%3Dview%26entity%3DUserColor" + - "&ownerId=7ec7c9bf-96a1-4f69-8441-176161c08877&descriptionId=rectangular.nodestyle.background&isContainment=false", List.of(), List.of()); + var input = new ModelBrowserEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), TREE_ID, List.of()); var flux = this.treeEventSubscriptionRunner.run(input); var treeId = new AtomicReference(); @@ -115,7 +115,7 @@ public void givenReferenceWidgetWhenWeAskForTheTreePathToExpandAllThenItsPathInT .thenCancel() .verify(Duration.ofSeconds(10)); - var expandedTreeInput = new TreeEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, treeItemIds.get(), List.of()); + var expandedTreeInput = new ModelBrowserEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), TREE_ID, treeItemIds.get()); var expandedTreeFlux = this.treeEventSubscriptionRunner.run(expandedTreeInput); Consumer initialExpandedTreeContentConsumer = this.getTreeSubscriptionConsumer(tree -> { diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/TreeControllerIntegrationTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/TreeControllerIntegrationTests.java index bebf1bb8c2e..4905c8544dd 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/TreeControllerIntegrationTests.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/TreeControllerIntegrationTests.java @@ -29,10 +29,8 @@ import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.sirius.components.collaborative.portals.dto.PortalEventInput; import org.eclipse.sirius.components.collaborative.portals.dto.PortalRefreshedEventPayload; -import org.eclipse.sirius.components.collaborative.trees.api.TreeConfiguration; import org.eclipse.sirius.components.collaborative.trees.dto.DeleteTreeItemInput; import org.eclipse.sirius.components.collaborative.trees.dto.RenameTreeItemInput; -import org.eclipse.sirius.components.collaborative.trees.dto.TreeEventInput; import org.eclipse.sirius.components.collaborative.trees.dto.TreeRefreshedEventPayload; import org.eclipse.sirius.components.core.api.IEditingContextSearchService; import org.eclipse.sirius.components.core.api.IIdentityService; @@ -44,11 +42,13 @@ import org.eclipse.sirius.components.trees.tests.graphql.DeleteTreeItemMutationRunner; import org.eclipse.sirius.components.trees.tests.graphql.InitialDirectEditTreeItemLabelQueryRunner; import org.eclipse.sirius.components.trees.tests.graphql.RenameTreeItemMutationRunner; -import org.eclipse.sirius.components.trees.tests.graphql.TreeEventSubscriptionRunner; import org.eclipse.sirius.web.AbstractIntegrationTests; +import org.eclipse.sirius.web.application.views.explorer.ExplorerConfiguration; +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.TestIdentifiers; import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState; +import org.eclipse.sirius.web.tests.services.explorer.ExplorerEventSubscriptionRunner; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -131,7 +131,7 @@ private record TreeItemMatcher(Function treeItemFinder, Predicat private IGivenInitialServerState givenInitialServerState; @Autowired - private TreeEventSubscriptionRunner treeEventSubscriptionRunner; + private ExplorerEventSubscriptionRunner treeEventSubscriptionRunner; @Autowired private DeleteTreeItemMutationRunner deleteTreeItemMutationRunner; @@ -161,7 +161,7 @@ public void beforeEach() { @Sql(scripts = { "/scripts/initialize.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 givenProjectWhenWeSubscribeToTreeEventsOfItsExplorerThenTheTreeOfTheExplorerIsSent() { - var input = new TreeEventInput(UUID.randomUUID(), TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, List.of(), List.of()); + var input = new ExplorerEventInput(UUID.randomUUID(), TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, List.of(), List.of()); var flux = this.treeEventSubscriptionRunner.run(input); Predicate projectContentMatcher = object -> Optional.of(object) @@ -183,7 +183,7 @@ public void givenProjectWhenWeSubscribeToTreeEventsOfItsExplorerThenTheTreeOfThe @Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) public void givenExplorerOfProjectWhenWeDeleteTreeItemsThenTheTreeIsRefreshed() { var expandedIds = this.getAllTreeItemIdsForEcoreSampleProject(); - var input = new TreeEventInput(UUID.randomUUID(), TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, expandedIds, List.of()); + var input = new ExplorerEventInput(UUID.randomUUID(), TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, expandedIds, List.of()); var flux = this.treeEventSubscriptionRunner.run(input); var hasProjectContent = this.getTreeRefreshedEventPayloadMatcher(List.of(this.rootDocumentIsNamedEcore, this.ePackageIsNamedSample, ePackageTreeItemIsStyled, this.representationIsAPortal)); @@ -200,7 +200,7 @@ public void givenExplorerOfProjectWhenWeDeleteTreeItemsThenTheTreeIsRefreshed() .filter(tree -> tree.getChildren().isEmpty()) .isPresent(); - var treeId = new TreeConfiguration(input.editingContextId(), input.treeId(), input.expanded(), input.activeFilterIds()).getId(); + var treeId = new ExplorerConfiguration(input.editingContextId(), input.treeId(), input.expanded(), input.activeFilterIds()).getId(); StepVerifier.create(flux) .expectNextMatches(hasProjectContent) @@ -220,12 +220,12 @@ public void givenExplorerOfProjectWhenWeDeleteTreeItemsThenTheTreeIsRefreshed() @Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) public void givenExplorerOfProjectWhenWeTriggerATreeItemRenameThenTheInitialDirectEditTreeItemLabelIsExecuted() { var expandedIds = this.getAllTreeItemIdsForEcoreSampleProject(); - var treeEventInput = new TreeEventInput(UUID.randomUUID(), TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, expandedIds, List.of()); + var treeEventInput = new ExplorerEventInput(UUID.randomUUID(), TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, expandedIds, List.of()); var treeFlux = this.treeEventSubscriptionRunner.run(treeEventInput); var hasProjectContent = this.getTreeRefreshedEventPayloadMatcher(List.of(this.rootDocumentIsNamedEcore, this.ePackageIsNamedSample, ePackageTreeItemIsStyled)); - var treeId = new TreeConfiguration(treeEventInput.editingContextId(), treeEventInput.treeId(), treeEventInput.expanded(), treeEventInput.activeFilterIds()).getId(); + var treeId = new ExplorerConfiguration(treeEventInput.editingContextId(), treeEventInput.treeId(), treeEventInput.expanded(), treeEventInput.activeFilterIds()).getId(); StepVerifier.create(treeFlux) .expectNextMatches(hasProjectContent) @@ -251,14 +251,14 @@ public void givenExplorerOfProjectWhenWeRenameTreeItemThenTheTreeIsRefreshed() { .isPresent(); }; var expandedIds = this.getAllTreeItemIdsForEcoreSampleProject(); - var treeEventInput = new TreeEventInput(UUID.randomUUID(), TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, expandedIds, List.of()); + var treeEventInput = new ExplorerEventInput(UUID.randomUUID(), TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, expandedIds, List.of()); var treeFlux = this.treeEventSubscriptionRunner.run(treeEventInput); var hasProjectContent = this.getTreeRefreshedEventPayloadMatcher(List.of(this.rootDocumentIsNamedEcore, this.ePackageIsNamedSample, ePackageTreeItemIsStyled, this.representationIsAPortal)); var hasObjectNewLabel = this.getTreeRefreshedEventPayloadMatcher(List.of(this.ePackageIsNamedSampleRenamed)); var hasDocumentNewLabel = this.getTreeRefreshedEventPayloadMatcher(List.of(this.rootDocumentIsNamedEcoreRenamed)); - var treeId = new TreeConfiguration(treeEventInput.editingContextId(), treeEventInput.treeId(), treeEventInput.expanded(), treeEventInput.activeFilterIds()).getId(); + var treeId = new ExplorerConfiguration(treeEventInput.editingContextId(), treeEventInput.treeId(), treeEventInput.expanded(), treeEventInput.activeFilterIds()).getId(); StepVerifier.create(Flux.merge(portalFlux, treeFlux)) .expectNextMatches(portalRefreshedEventPayloadMatcher) diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/TreeIconURLControllerTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/TreeIconURLControllerTests.java new file mode 100644 index 00000000000..d61baf7668b --- /dev/null +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/TreeIconURLControllerTests.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.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.Optional; +import java.util.UUID; +import java.util.function.Consumer; + +import org.eclipse.sirius.components.collaborative.trees.dto.TreeRefreshedEventPayload; +import org.eclipse.sirius.components.graphql.api.URLConstants; +import org.eclipse.sirius.components.graphql.tests.api.IGraphQLRequestor; +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.services.api.IGivenInitialServerState; +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.test.StepVerifier; + +/** + * Integration tests of the tree icon URL controller. + * + * @author frouene + */ +@Transactional +@SuppressWarnings("checkstyle:MultipleStringLiterals") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class TreeIconURLControllerTests extends AbstractIntegrationTests { + + private static final String GET_EXPLORER_EVENT_SUBSCRIPTION = """ + subscription explorerEvent($input: ExplorerEventInput!) { + explorerEvent(input: $input) { + __typename + ... on TreeRefreshedEventPayload { + tree { + children { + id + iconURL + } + } + } + } + } + """; + + @Autowired + private IGraphQLRequestor graphQLRequestor; + + @Autowired + private IGivenInitialServerState givenInitialServerState; + + @BeforeEach + public void beforeEach() { + this.givenInitialServerState.initialize(); + } + + @Test + @DisplayName("Given a semantic object, when we subscribe to its tree events, then the URL of its objects is valid") + @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 givenSemanticObjectWhenWeSubscribeToItsTreeEventsThenTheURLOfItsObjectsIsValid() { + var input = new ExplorerEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, List.of(), List.of()); + var flux = this.graphQLRequestor.subscribeToSpecification(GET_EXPLORER_EVENT_SUBSCRIPTION, input); + + Consumer treeContentConsumer = payload -> Optional.of(payload) + .ifPresentOrElse(body -> { + String typename = JsonPath.read(body, "$.data.explorerEvent.__typename"); + assertThat(typename).isEqualTo(TreeRefreshedEventPayload.class.getSimpleName()); + + List> treeItemIconURLs = JsonPath.read(body, "$.data.explorerEvent.tree.children[*].iconURL"); + assertThat(treeItemIconURLs) + .isNotEmpty() + .allSatisfy(iconURLs -> { + assertThat(iconURLs) + .isNotEmpty() + .hasSize(1) + .allSatisfy(iconURL -> assertThat(iconURL).startsWith(URLConstants.IMAGE_BASE_PATH)); + }); + }, () -> fail("Missing tree")); + + StepVerifier.create(flux) + .consumeNextWith(treeContentConsumer) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } + +} diff --git a/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx b/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx index 8f21737f8d7..42d9ba17173 100644 --- a/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx +++ b/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx @@ -35,8 +35,9 @@ import { } from '@eclipse-sirius/sirius-components-forms'; import { GanttRepresentation } from '@eclipse-sirius/sirius-components-gantt'; import { PortalRepresentation } from '@eclipse-sirius/sirius-components-portals'; -import { ExplorerView, treeItemContextMenuEntryExtensionPoint } from '@eclipse-sirius/sirius-components-trees'; +import { treeItemContextMenuEntryExtensionPoint } from '@eclipse-sirius/sirius-components-trees'; import { ValidationView } from '@eclipse-sirius/sirius-components-validation'; +import { SelectionDialog } from '@eclipse-sirius/sirius-components-selection'; import { GQLReferenceWidget, ReferenceIcon, @@ -59,17 +60,17 @@ import { ObjectTreeItemContextMenuContribution } from '../views/edit-project/Obj import { DetailsView } from '../views/edit-project/workbench-views/DetailsView'; import { RelatedElementsView } from '../views/edit-project/workbench-views/RelatedElementsView'; import { RepresentationsView } from '../views/edit-project/workbench-views/RepresentationsView'; +import { ExplorerView } from '../views/explorer/ExplorerView'; import { createProjectAreaCardExtensionPoint } from '../views/project-browser/create-projects-area/CreateProjectAreaExtensionPoints'; import { NewProjectCard } from '../views/project-browser/create-projects-area/NewProjectCard'; import { ShowAllProjectTemplatesCard } from '../views/project-browser/create-projects-area/ShowAllProjectTemplatesCard'; import { UploadProjectCard } from '../views/project-browser/create-projects-area/UploadProjectCard'; +import { ProjectSettingTabContribution } from '../views/project-settings/ProjectSettingsView.types'; +import { projectSettingsTabExtensionPoint } from '../views/project-settings/ProjectSettingsViewExtensionPoints'; import { ProjectImagesSettings } from '../views/project-settings/images/ProjectImagesSettings'; import { ellipseNodeStyleDocumentTransform } from './ElipseNodeDocumentTransform'; import { referenceWidgetDocumentTransform } from './ReferenceWidgetDocumentTransform'; -import { SelectionDialog } from '@eclipse-sirius/sirius-components-selection'; -import { ProjectSettingTabContribution } from '../views/project-settings/ProjectSettingsView.types'; -import { projectSettingsTabExtensionPoint } from '../views/project-settings/ProjectSettingsViewExtensionPoints'; const getType = (representation: RepresentationMetadata): string | null => { const query = representation.kind.substring(representation.kind.indexOf('?') + 1, representation.kind.length); const params = new URLSearchParams(query); diff --git a/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.tsx b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/ExplorerView.tsx similarity index 84% rename from packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.tsx rename to packages/sirius-web/frontend/sirius-web-application/src/views/explorer/ExplorerView.tsx index eb9e4a72373..cbbec7b2f81 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.tsx +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/ExplorerView.tsx @@ -11,16 +11,20 @@ * Obeo - initial API and implementation *******************************************************************************/ import { WorkbenchViewComponentProps } from '@eclipse-sirius/sirius-components-core'; +import { + TreeToolBar, + TreeToolBarContext, + TreeToolBarContextValue, + TreeView, + TreeFilter, + FilterBar, + useTreeFilters, +} from '@eclipse-sirius/sirius-components-trees'; import { Theme } from '@mui/material/styles'; import { useContext, useEffect, useRef, useState } from 'react'; import { makeStyles } from 'tss-react/mui'; -import { TreeToolBar } from '../toolbar/TreeToolBar'; -import { TreeToolBarContext } from '../toolbar/TreeToolBarContext'; -import { TreeToolBarContextValue } from '../toolbar/TreeToolBarContext.types'; -import { FilterBar } from '../trees/FilterBar'; -import { ExplorerViewState, TreeFilter } from './ExplorerView.types'; -import { TreeView } from './TreeView'; -import { useTreeFilters } from './useTreeFilters'; +import { ExplorerViewState } from './ExplorerView.types'; +import { useExplorerSubscription } from './useExplorerSubscription'; const useStyles = makeStyles()((theme: Theme) => ({ treeView: { @@ -43,11 +47,16 @@ export const ExplorerView = ({ editingContextId, readOnly }: WorkbenchViewCompon filterBarText: '', filterBarTreeFiltering: false, treeFilters: [], + expanded: [], + maxDepth: 1, }; const [state, setState] = useState(initialState); const treeToolBarContributionComponents = useContext(TreeToolBarContext).map( (contribution) => contribution.props.component ); + const activeTreeFilterIds = state.treeFilters.filter((filter) => filter.state).map((filter) => filter.id); + + const { tree } = useExplorerSubscription(editingContextId, activeTreeFilterIds, state.expanded, state.maxDepth); const { loading, treeFilters } = useTreeFilters(editingContextId, 'explorer://'); @@ -96,9 +105,10 @@ export const ExplorerView = ({ editingContextId, readOnly }: WorkbenchViewCompon }); }} onFilterButtonClick={(enabled) => - setState((prevState) => { - return { ...prevState, filterBarTreeFiltering: enabled }; - }) + setState((prevState) => ({ + ...prevState, + filterBarTreeFiltering: enabled, + })) } onClose={() => setState((prevState) => { @@ -108,8 +118,9 @@ export const ExplorerView = ({ editingContextId, readOnly }: WorkbenchViewCompon /> ); } - - const activeTreeFilterIds = state.treeFilters.filter((filter) => filter.state).map((filter) => filter.id); + const onExpandedElementChange = (expanded: string[], maxDepth: number) => { + setState((prevState) => ({ ...prevState, expanded, maxDepth })); + }; return (
@@ -136,11 +147,12 @@ export const ExplorerView = ({ editingContextId, readOnly }: WorkbenchViewCompon editingContextId={editingContextId} readOnly={readOnly} treeId={'explorer://'} + tree={tree} enableMultiSelection={true} synchronizedWithSelection={state.synchronizedWithSelection} - activeFilterIds={activeTreeFilterIds} textToHighlight={state.filterBarText} textToFilter={state.filterBarTreeFiltering ? state.filterBarText : null} + onExpandedElementChange={onExpandedElementChange} />
diff --git a/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.types.ts b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/ExplorerView.types.ts similarity index 86% rename from packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.types.ts rename to packages/sirius-web/frontend/sirius-web-application/src/views/explorer/ExplorerView.types.ts index 8436ee962a9..e3418decbc7 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.types.ts +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/ExplorerView.types.ts @@ -10,6 +10,7 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ +import { TreeFilter } from '@eclipse-sirius/sirius-components-trees'; export interface ExplorerViewState { synchronizedWithSelection: boolean; @@ -17,10 +18,6 @@ export interface ExplorerViewState { filterBarText: string | null; filterBarTreeFiltering: boolean; treeFilters: TreeFilter[]; -} - -export interface TreeFilter { - id: string; - label: string; - state: boolean; + expanded: string[]; + maxDepth: number; } diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/useExplorerSubscription.tsx b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/useExplorerSubscription.tsx new file mode 100644 index 00000000000..cf1e5be32fe --- /dev/null +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/useExplorerSubscription.tsx @@ -0,0 +1,90 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ + +import { gql, OnDataOptions, useSubscription } from '@apollo/client'; +import { useMultiToast } from '@eclipse-sirius/sirius-components-core'; +import { useEffect, useState } from 'react'; +import { + GQLTreeEventPayload, + GQLTreeRefreshedEventPayload, + UseTreeSubscriptionValue, + UseTreeSubscriptionState, + getTreeEventSubscription, +} from '@eclipse-sirius/sirius-components-trees'; +import { + GQLExplorerEventInput, + GQLExplorerEventVariables, + GQLExplorerEventData, +} from './useExplorerSubscription.types'; + +const isTreeRefreshedEventPayload = (payload: GQLTreeEventPayload): payload is GQLTreeRefreshedEventPayload => + payload.__typename === 'TreeRefreshedEventPayload'; + +export const useExplorerSubscription = ( + editingContextId: string, + activeFilterIds: string[], + expanded: string[], + maxDepth: number +): UseTreeSubscriptionValue => { + const [state, setState] = useState({ + id: crypto.randomUUID(), + tree: null, + complete: false, + }); + + const input: GQLExplorerEventInput = { + id: state.id, + editingContextId, + treeId: 'explorer://', + activeFilterIds, + expanded, + }; + + const variables: GQLExplorerEventVariables = { input }; + + const onData = ({ data }: OnDataOptions) => { + const { data: gqlTreeData } = data; + if (gqlTreeData) { + const { explorerEvent: payload } = gqlTreeData; + if (isTreeRefreshedEventPayload(payload)) { + const { tree } = payload; + setState((prevState) => ({ ...prevState, tree })); + } + } + }; + + const onComplete = () => setState((prevState) => ({ ...prevState, complete: true })); + + const { error, loading } = useSubscription( + gql(getTreeEventSubscription(maxDepth, 'explorerEvent', 'ExplorerEventInput')), + { + variables, + fetchPolicy: 'no-cache', + onData, + onComplete, + } + ); + + const { addErrorMessage } = useMultiToast(); + useEffect(() => { + if (error) { + addErrorMessage('An unexpected error has occurred, please refresh the page'); + } + }, [error]); + + return { + loading, + tree: state.tree, + complete: state.complete, + }; +}; diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/useExplorerSubscription.types.ts b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/useExplorerSubscription.types.ts new file mode 100644 index 00000000000..f9315b9973c --- /dev/null +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/useExplorerSubscription.types.ts @@ -0,0 +1,29 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +import { GQLTreeEventPayload } from '@eclipse-sirius/sirius-components-trees'; + +export interface GQLExplorerEventInput { + id: string; + treeId: string; + editingContextId: string; + expanded: string[]; + activeFilterIds: string[]; +} + +export interface GQLExplorerEventVariables { + input: GQLExplorerEventInput; +} + +export interface GQLExplorerEventData { + explorerEvent: GQLTreeEventPayload; +} diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeService.java b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeService.java index 1aaf08a576d..1a056dec475 100644 --- a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeService.java +++ b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeService.java @@ -13,7 +13,6 @@ package org.eclipse.sirius.components.collaborative.trees; import org.eclipse.sirius.components.collaborative.trees.api.ITreeService; -import org.eclipse.sirius.components.collaborative.trees.api.TreeConfiguration; import org.eclipse.sirius.components.collaborative.trees.api.TreeCreationParameters; import org.eclipse.sirius.components.core.api.IEditingContext; import org.eclipse.sirius.components.representations.GetOrCreateRandomIdProvider; @@ -34,7 +33,6 @@ public class TreeService implements ITreeService { public Tree create(TreeCreationParameters treeCreationParameters) { VariableManager variableManager = new VariableManager(); variableManager.put(GetOrCreateRandomIdProvider.PREVIOUS_REPRESENTATION_ID, treeCreationParameters.getId()); - variableManager.put(TreeConfiguration.TREE_ID, treeCreationParameters.getId()); variableManager.put(IEditingContext.EDITING_CONTEXT, treeCreationParameters.getEditingContext()); variableManager.put(TreeRenderer.EXPANDED, treeCreationParameters.getExpanded()); variableManager.put(TreeRenderer.ACTIVE_FILTER_IDS, treeCreationParameters.getActiveFilterIds()); diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/main/resources/schema/tree.graphqls b/packages/trees/backend/sirius-components-collaborative-trees/src/main/resources/schema/tree.graphqls index f6dbebb4210..d231858d23a 100644 --- a/packages/trees/backend/sirius-components-collaborative-trees/src/main/resources/schema/tree.graphqls +++ b/packages/trees/backend/sirius-components-collaborative-trees/src/main/resources/schema/tree.graphqls @@ -1,7 +1,3 @@ -extend type Subscription { - treeEvent(input: TreeEventInput!): TreeEventPayload! -} - extend type EditingContext { treePath(treeId: ID!, selectionEntryIds: [ID!]!): TreePath! expandAllTreePath(treeId: ID!, treeItemId: ID!): TreePath! @@ -12,14 +8,6 @@ type TreePath { maxDepth: Int! } -input TreeEventInput { - id: ID! - treeId: String! - editingContextId: ID! - expanded: [String!]! - activeFilterIds: [String!]! -} - union TreeEventPayload = ErrorPayload | TreeRefreshedEventPayload type TreeRefreshedEventPayload { diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/test/java/org/eclipse/sirius/components/collaborative/trees/architecture/handlers/TreePathEventHandlerTests.java b/packages/trees/backend/sirius-components-collaborative-trees/src/test/java/org/eclipse/sirius/components/collaborative/trees/architecture/handlers/TreePathEventHandlerTests.java new file mode 100644 index 00000000000..ad7c7989596 --- /dev/null +++ b/packages/trees/backend/sirius-components-collaborative-trees/src/test/java/org/eclipse/sirius/components/collaborative/trees/architecture/handlers/TreePathEventHandlerTests.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * 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.trees.architecture.handlers; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.UUID; + +import org.eclipse.sirius.components.collaborative.api.ChangeDescription; +import org.eclipse.sirius.components.collaborative.trees.api.ITreePathProvider; +import org.eclipse.sirius.components.collaborative.trees.dto.TreePathInput; +import org.eclipse.sirius.components.collaborative.trees.dto.TreePathSuccessPayload; +import org.eclipse.sirius.components.collaborative.trees.handlers.TreePathEventHandler; +import org.eclipse.sirius.components.core.api.ErrorPayload; +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.core.api.IPayload; +import org.eclipse.sirius.components.trees.Tree; +import org.junit.jupiter.api.Test; + +import reactor.core.publisher.Sinks; +import reactor.core.publisher.Sinks.Many; +import reactor.core.publisher.Sinks.One; + +/** + * Tests for the tree path even handler. + * + * @author Jerome Gout + */ +public class TreePathEventHandlerTests { + + @Test + public void testTreePathWithEmptyProviders() { + var handler = new TreePathEventHandler(List.of()); + TreePathInput input = new TreePathInput(UUID.randomUUID(), "editingContextId", "representationId", List.of()); + + assertThat(handler.canHandle(input)).isTrue(); + + One payloadSink = Sinks.one(); + Many changeDescriptionSink = Sinks.many().unicast().onBackpressureBuffer(); + + handler.handle(payloadSink, changeDescriptionSink, new IEditingContext.NoOp(), null, null, input); + + IPayload payload = payloadSink.asMono().block(); + assertThat(payload).isInstanceOf(TreePathSuccessPayload.class); + } + + @Test + public void testTreePathWihErrorProvider() { + ITreePathProvider errorProvider = new ITreePathProvider() { + @Override + public IPayload handle(IEditingContext editingContext, Tree tree, TreePathInput input) { + return new ErrorPayload(UUID.randomUUID(), "provider failed"); + } + @Override + public boolean canHandle(Tree tree) { + return true; + } + }; + + var handler = new TreePathEventHandler(List.of(errorProvider)); + TreePathInput input = new TreePathInput(UUID.randomUUID(), "editingContextId", "representationId", List.of()); + + assertThat(handler.canHandle(input)).isTrue(); + + One payloadSink = Sinks.one(); + Many changeDescriptionSink = Sinks.many().unicast().onBackpressureBuffer(); + + handler.handle(payloadSink, changeDescriptionSink, new IEditingContext.NoOp(), null, null, input); + + IPayload payload = payloadSink.asMono().block(); + assertThat(payload).isInstanceOf(TreePathSuccessPayload.class); + assertThat(((TreePathSuccessPayload) payload).treePath().toString()).isEqualTo("TreePath {treeItemIdsToExpand: [], maxDepth: 0}"); + } +} diff --git a/packages/trees/frontend/sirius-components-trees/src/index.ts b/packages/trees/frontend/sirius-components-trees/src/index.ts index 89bc33b5751..19fe55a5f6d 100644 --- a/packages/trees/frontend/sirius-components-trees/src/index.ts +++ b/packages/trees/frontend/sirius-components-trees/src/index.ts @@ -10,17 +10,22 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ +export * from './toolbar/TreeToolBar'; export * from './toolbar/TreeToolBarContext'; export * from './toolbar/TreeToolBarContext.types'; export * from './toolbar/TreeToolBarContribution'; export * from './toolbar/TreeToolBarContribution.types'; +export * from './treeitems/filterTreeItem'; export * from './treeitems/TreeItemAction.types'; export * from './treeitems/TreeItemContextMenu'; export * from './treeitems/TreeItemContextMenu.types'; export * from './treeitems/TreeItemContextMenuEntry.types'; export * from './treeitems/TreeItemContextMenuEntryExtensionPoints'; -export * from './treeitems/filterTreeItem'; -export * from './views/ExplorerView'; +export * from './trees/FilterBar'; +export * from './views/getTreeEventSubscription'; +export * from './views/TreeFiltersMenu'; +export * from './views/TreeFiltersMenu.types'; export * from './views/TreeView'; export * from './views/TreeView.types'; export * from './views/TreeViewExtensionPoints'; +export * from './views/useTreeFilters'; diff --git a/packages/trees/frontend/sirius-components-trees/src/toolbar/TreeToolBar.types.ts b/packages/trees/frontend/sirius-components-trees/src/toolbar/TreeToolBar.types.ts index bb5f3c47d8f..6837b611c61 100644 --- a/packages/trees/frontend/sirius-components-trees/src/toolbar/TreeToolBar.types.ts +++ b/packages/trees/frontend/sirius-components-trees/src/toolbar/TreeToolBar.types.ts @@ -10,8 +10,8 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { TreeFilter } from '../views/ExplorerView.types'; import { TreeToolBarContributionComponentProps } from './TreeToolBarContribution.types'; +import { TreeFilter } from '../views/TreeFiltersMenu.types'; export interface TreeToolBarProps { editingContextId: string; diff --git a/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.tsx b/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.tsx index 74e7ccf2ab8..c739283b224 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.tsx +++ b/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.tsx @@ -22,8 +22,7 @@ import Paper from '@mui/material/Paper'; import Popper from '@mui/material/Popper'; import { useEffect, useRef, useState } from 'react'; import { makeStyles } from 'tss-react/mui'; -import { TreeFilter } from './ExplorerView.types'; -import { TreeFilterMenuProps } from './TreeFiltersMenu.types'; +import { TreeFilterMenuProps, TreeFilter } from './TreeFiltersMenu.types'; const useTreeFiltersMenuStyles = makeStyles()((_) => ({ root: { diff --git a/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.types.ts b/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.types.ts index 0b8c08f991b..4ff55da1566 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.types.ts +++ b/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.types.ts @@ -11,9 +11,13 @@ * Obeo - initial API and implementation *******************************************************************************/ -import { TreeFilter } from './ExplorerView.types'; - export interface TreeFilterMenuProps { filters: TreeFilter[]; onTreeFilterMenuItemClick: (filters: TreeFilter[]) => void; } + +export interface TreeFilter { + id: string; + label: string; + state: boolean; +} diff --git a/packages/trees/frontend/sirius-components-trees/src/views/TreeView.tsx b/packages/trees/frontend/sirius-components-trees/src/views/TreeView.tsx index 5f8214e8995..0c06aba45bc 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/TreeView.tsx +++ b/packages/trees/frontend/sirius-components-trees/src/views/TreeView.tsx @@ -10,11 +10,9 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { gql, useLazyQuery, useSubscription } from '@apollo/client'; -import { DataExtension, Toast, useData, useSelection } from '@eclipse-sirius/sirius-components-core'; -import { useMachine } from '@xstate/react'; -import { useEffect } from 'react'; -import { StateMachine } from 'xstate'; +import { gql, useLazyQuery } from '@apollo/client'; +import { DataExtension, useData, useMultiToast, useSelection } from '@eclipse-sirius/sirius-components-core'; +import { useEffect, useState } from 'react'; import { Tree } from '../trees/Tree'; import { GQLGetExpandAllTreePathData, @@ -22,31 +20,12 @@ import { GQLGetTreePathData, GQLGetTreePathVariables, GQLTree, - GQLTreeEventData, - GQLTreeEventVariables, GQLTreeItem, TreeConverter, TreeViewComponentProps, + TreeViewComponentState, } from './TreeView.types'; import { treeViewTreeConverterExtensionPoint } from './TreeViewExtensionPoints'; -import { - AutoExpandToRevealSelectionEvent, - HandleCompleteEvent, - HandleExpandAllTreePathEvent, - HandleExpandedEvent, - HandleOnExpandAllEvent, - HandleSubscriptionResultEvent, - HandleTreePathEvent, - HideToastEvent, - SchemaValue, - ShowToastEvent, - SynchronizeWithSelectionEvent, - TreeViewContext, - TreeViewEvent, - treeViewMachine, - TreeViewStateSchema, -} from './TreeViewMachine'; -import { getTreeEventSubscription } from './getTreeEventSubscription'; const getTreePathQuery = gql` query getTreePath($editingContextId: ID!, $treeId: ID!, $selectionEntryIds: [ID!]!) { @@ -77,28 +56,23 @@ const getExpandAllTreePathQuery = gql` export const TreeView = ({ editingContextId, readOnly, + tree, treeId, enableMultiSelection, synchronizedWithSelection, - activeFilterIds, textToHighlight, textToFilter, markedItemIds = [], treeItemActionRender, + onExpandedElementChange, }: TreeViewComponentProps) => { - const [{ value, context }, dispatch] = useMachine>( - treeViewMachine, - { - context: { - synchronizedWithSelection: synchronizedWithSelection, - }, - } - ); + const [state, setState] = useState({ + autoExpandToRevealSelection: synchronizedWithSelection, + expanded: [], + maxDepth: 1, + }); const { selection } = useSelection(); - const { toast, treeView } = value as SchemaValue; - const { id, tree, expanded, maxDepth, autoExpandToRevealSelection, treeItemToExpandAll, message } = context; - const [getTreePath, { loading: treePathLoading, data: treePathData, error: treePathError }] = useLazyQuery< GQLGetTreePathData, GQLGetTreePathVariables @@ -115,7 +89,7 @@ export const TreeView = ({ .sort() .join(':'); useEffect(() => { - if (tree && autoExpandToRevealSelection) { + if (tree && state.autoExpandToRevealSelection) { const variables: GQLGetTreePathVariables = { editingContextId, treeId: tree.id, @@ -123,106 +97,97 @@ export const TreeView = ({ }; getTreePath({ variables }); } - }, [editingContextId, tree, selectionKey, autoExpandToRevealSelection, getTreePath]); + }, [editingContextId, tree, selectionKey, state.autoExpandToRevealSelection, getTreePath]); useEffect(() => { if (!treePathLoading) { if (treePathData) { - const handleTreePathEvent: HandleTreePathEvent = { type: 'HANDLE_TREE_PATH', treePathData }; - dispatch(handleTreePathEvent); - } - if (treePathError) { - const { message } = treePathError; - const showToastEvent: ShowToastEvent = { type: 'SHOW_TOAST', message }; - dispatch(showToastEvent); + const { expanded, maxDepth } = state; + if (treePathData.viewer?.editingContext?.treePath) { + const { treeItemIdsToExpand, maxDepth: expandedMaxDepth } = treePathData.viewer.editingContext.treePath; + const newExpanded: string[] = [...expanded]; + + treeItemIdsToExpand?.forEach((itemToExpand) => { + if (!expanded.includes(itemToExpand)) { + newExpanded.push(itemToExpand); + } + }); + setState((prevState) => ({ + ...prevState, + expanded: newExpanded, + maxDepth: Math.max(expandedMaxDepth, maxDepth), + })); + } } } - }, [treePathLoading, treePathData, treePathError]); - - useEffect(() => { - if (tree && treeItemToExpandAll) { - const variables: GQLGetExpandAllTreePathVariables = { - editingContextId, - treeId: tree.id, - treeItemId: treeItemToExpandAll, - }; - getExpandAllTreePath({ variables }); - } - }, [editingContextId, tree, treeItemToExpandAll, getExpandAllTreePathQuery]); + }, [treePathLoading, treePathData]); useEffect(() => { if (!expandAllTreePathLoading) { if (expandAllTreePathData) { - const handleExpandAllTreePathEvent: HandleExpandAllTreePathEvent = { - type: 'HANDLE_EXPAND_ALL_TREE_PATH', - expandAllTreePathData, - }; - dispatch(handleExpandAllTreePathEvent); - } - if (expandAllTreePathError) { - const { message } = expandAllTreePathError; - const showToastEvent: ShowToastEvent = { type: 'SHOW_TOAST', message }; - dispatch(showToastEvent); + const { expanded, maxDepth } = state; + if (expandAllTreePathData.viewer?.editingContext?.expandAllTreePath) { + const { treeItemIdsToExpand, maxDepth: expandedMaxDepth } = + expandAllTreePathData.viewer.editingContext.expandAllTreePath; + const newExpanded: string[] = [...expanded]; + + treeItemIdsToExpand?.forEach((itemToExpand) => { + if (!expanded.includes(itemToExpand)) { + newExpanded.push(itemToExpand); + } + }); + setState((prevState) => ({ + ...prevState, + expanded: newExpanded, + maxDepth: Math.max(expandedMaxDepth, maxDepth), + })); + } } } - }, [expandAllTreePathLoading, expandAllTreePathData, expandAllTreePathError]); + }, [expandAllTreePathLoading, expandAllTreePathData]); - const { error } = useSubscription(gql(getTreeEventSubscription(maxDepth)), { - variables: { - input: { - id, - treeId, - editingContextId, - expanded, - activeFilterIds, - }, - }, - fetchPolicy: 'no-cache', - skip: treeView === 'complete', - onData: ({ data }) => { - const handleDataEvent: HandleSubscriptionResultEvent = { - type: 'HANDLE_SUBSCRIPTION_RESULT', - result: data, - }; - dispatch(handleDataEvent); - }, - onComplete: () => { - const completeEvent: HandleCompleteEvent = { type: 'HANDLE_COMPLETE' }; - dispatch(completeEvent); - }, - }); + const { addMessages } = useMultiToast(); useEffect(() => { - if (error) { - const { message } = error; - const showToastEvent: ShowToastEvent = { type: 'SHOW_TOAST', message }; - dispatch(showToastEvent); + if (expandAllTreePathError) { + addMessages(expandAllTreePathError.message); } - }, [error, dispatch]); - - useEffect(() => { - const autoExpandToRevealSelectionEvent: AutoExpandToRevealSelectionEvent = { - type: 'AUTO_EXPAND_TO_REVEAL_SELECTION', - autoExpandToRevealSelection: true, - }; - dispatch(autoExpandToRevealSelectionEvent); - }, [selection]); - + }, [expandAllTreePathError]); useEffect(() => { - const synchronizeWithSelectionEvent: SynchronizeWithSelectionEvent = { - type: 'SYNCHRONIZE_WITH_SELECTION', - synchronizedWithSelection: synchronizedWithSelection, - }; - dispatch(synchronizeWithSelectionEvent); - }, [synchronizedWithSelection]); + if (treePathError) { + addMessages(treePathError.message); + } + }, [treePathError]); const onExpand = (id: string, depth: number) => { - const handleExpandedEvent: HandleExpandedEvent = { type: 'HANDLE_EXPANDED', id, depth }; - dispatch(handleExpandedEvent); + const { expanded, maxDepth } = state; + + if (expanded.includes(id)) { + const newExpanded = [...expanded]; + newExpanded.splice(newExpanded.indexOf(id), 1); + + // Disable synchronize mode on collapse + setState((prevState) => ({ + ...prevState, + autoExpandToRevealSelection: false, + expanded: newExpanded, + maxDepth: Math.max(maxDepth, depth), + })); + } else { + setState((prevState) => ({ ...prevState, expanded: [...expanded, id], maxDepth: Math.max(maxDepth, depth) })); + } }; + useEffect(() => { + onExpandedElementChange(state.expanded, state.maxDepth); + }, [state.expanded, state.maxDepth]); + const onExpandAll = (treeItem: GQLTreeItem) => { - const handleOnExpandAllEvent: HandleOnExpandAllEvent = { type: 'HANDLE_ON_EXPAND_ALL', treeItemId: treeItem.id }; - dispatch(handleOnExpandAllEvent); + const variables: GQLGetExpandAllTreePathVariables = { + editingContextId, + treeId: tree.id, + treeItemId: treeItem.id, + }; + getExpandAllTreePath({ variables }); }; const { data: treeConverters }: DataExtension = useData(treeViewTreeConverterExtensionPoint); @@ -233,28 +198,21 @@ export const TreeView = ({ }); return ( - <> -
- {tree ? ( - - ) : null} -
- dispatch({ type: 'HIDE_TOAST' } as HideToastEvent)} - /> - +
+ {tree ? ( + + ) : null} +
); }; diff --git a/packages/trees/frontend/sirius-components-trees/src/views/TreeView.types.ts b/packages/trees/frontend/sirius-components-trees/src/views/TreeView.types.ts index c9e85e928c5..1d9f1979faa 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/TreeView.types.ts +++ b/packages/trees/frontend/sirius-components-trees/src/views/TreeView.types.ts @@ -16,42 +16,24 @@ import { TreeItemActionProps } from '../treeitems/TreeItemAction.types'; export interface TreeViewComponentProps extends WorkbenchViewComponentProps { treeId: string; + tree: GQLTree; enableMultiSelection: boolean; synchronizedWithSelection: boolean; - activeFilterIds: string[]; textToHighlight: string | null; textToFilter: string | null; markedItemIds?: string[]; treeItemActionRender?: (props: TreeItemActionProps) => React.ReactNode; + onExpandedElementChange: (expanded: string[], maxDepth: number) => void; } -export interface TreeConverter { - convert(editingContextId: string, tree: GQLTree): GQLTree; -} - -export interface GQLTreeEventVariables { - input: GQLTreeEventInput; -} - -export interface GQLTreeEventInput { - id: string; - treeId: string; - editingContextId: string; +export interface TreeViewComponentState { + autoExpandToRevealSelection: boolean; expanded: string[]; - activeFilterIds: string[]; -} - -export interface GQLTreeEventData { - treeEvent: GQLTreeEventPayload; -} - -export interface GQLTreeEventPayload { - __typename: string; + maxDepth: number; } -export interface GQLTreeRefreshedEventPayload extends GQLTreeEventPayload { - id: string; - tree: GQLTree; +export interface TreeConverter { + convert(editingContextId: string, tree: GQLTree): GQLTree; } export interface GQLTree { @@ -112,3 +94,24 @@ export interface GQLGetExpandAllTreePathViewer { export interface GQLGetExpandAllTreePathEditingContext { expandAllTreePath: GQLTreePath; } + +export interface GQLTreeEventPayload { + __typename: string; +} + +export interface GQLTreeRefreshedEventPayload extends GQLTreeEventPayload { + id: string; + tree: GQLTree; +} + +export interface UseTreeSubscriptionValue { + loading: boolean; + tree: GQLTree | null; + complete: boolean; +} + +export interface UseTreeSubscriptionState { + id: string; + tree: GQLTree | null; + complete: boolean; +} diff --git a/packages/trees/frontend/sirius-components-trees/src/views/TreeViewMachine.ts b/packages/trees/frontend/sirius-components-trees/src/views/TreeViewMachine.ts deleted file mode 100644 index 9591d8c52f7..00000000000 --- a/packages/trees/frontend/sirius-components-trees/src/views/TreeViewMachine.ts +++ /dev/null @@ -1,286 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022, 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 - *******************************************************************************/ -import { SubscriptionResult } from '@apollo/client'; -import { assign, Machine } from 'xstate'; -import { - GQLGetExpandAllTreePathData, - GQLGetTreePathData, - GQLTree, - GQLTreeEventData, - GQLTreeEventPayload, - GQLTreeRefreshedEventPayload, -} from './TreeView.types'; - -export interface TreeViewStateSchema { - states: { - toast: { - states: { - visible: {}; - hidden: {}; - }; - }; - treeView: { - states: { - idle: {}; - ready: {}; - complete: {}; - }; - }; - }; -} - -export type SchemaValue = { - toast: 'visible' | 'hidden'; - treeView: 'idle' | 'ready' | 'complete'; -}; - -export interface TreeViewContext { - id: string; - tree: GQLTree | null; - expanded: string[]; - maxDepth: number; - autoExpandToRevealSelection: boolean; - treeItemToExpandAll: string | null; - synchronizedWithSelection: boolean; - message: string | null; -} - -export type ShowToastEvent = { type: 'SHOW_TOAST'; message: string }; -export type HideToastEvent = { type: 'HIDE_TOAST' }; -export type HandleSubscriptionResultEvent = { - type: 'HANDLE_SUBSCRIPTION_RESULT'; - result: SubscriptionResult; -}; -export type HandleCompleteEvent = { type: 'HANDLE_COMPLETE' }; -export type AutoExpandToRevealSelectionEvent = { - type: 'AUTO_EXPAND_TO_REVEAL_SELECTION'; - autoExpandToRevealSelection: boolean; -}; -export type SynchronizeWithSelectionEvent = { - type: 'SYNCHRONIZE_WITH_SELECTION'; - synchronizedWithSelection: boolean; -}; -export type HandleExpandedEvent = { type: 'HANDLE_EXPANDED'; id: string; depth: number }; -export type HandleOnExpandAllEvent = { - type: 'HANDLE_ON_EXPAND_ALL'; - treeItemId: string; -}; -export type HandleExpandAllTreePathEvent = { - type: 'HANDLE_EXPAND_ALL_TREE_PATH'; - expandAllTreePathData: GQLGetExpandAllTreePathData; -}; -export type HandleTreePathEvent = { type: 'HANDLE_TREE_PATH'; treePathData: GQLGetTreePathData }; -export type TreeViewEvent = - | HandleSubscriptionResultEvent - | HandleCompleteEvent - | ShowToastEvent - | HideToastEvent - | AutoExpandToRevealSelectionEvent - | HandleExpandedEvent - | HandleOnExpandAllEvent - | HandleExpandAllTreePathEvent - | HandleTreePathEvent - | SynchronizeWithSelectionEvent; - -const isTreeRefreshedEventPayload = (payload: GQLTreeEventPayload): payload is GQLTreeRefreshedEventPayload => - payload.__typename === 'TreeRefreshedEventPayload'; - -export const treeViewMachine = Machine( - { - type: 'parallel', - context: { - id: crypto.randomUUID(), - tree: null, - expanded: [], - maxDepth: 1, - autoExpandToRevealSelection: true, - treeItemToExpandAll: null, - synchronizedWithSelection: true, - message: null, - }, - states: { - toast: { - initial: 'hidden', - states: { - hidden: { - on: { - SHOW_TOAST: { - target: 'visible', - actions: 'setMessage', - }, - }, - }, - visible: { - on: { - HIDE_TOAST: { - target: 'hidden', - actions: 'clearMessage', - }, - }, - }, - }, - }, - treeView: { - initial: 'idle', - states: { - idle: { - on: { - HANDLE_SUBSCRIPTION_RESULT: [ - { - cond: 'isTreeRefreshedEventPayload', - target: 'ready', - actions: 'handleSubscriptionResult', - }, - { - target: 'idle', - actions: 'handleSubscriptionResult', - }, - ], - }, - }, - ready: { - on: { - HANDLE_SUBSCRIPTION_RESULT: { - target: 'ready', - actions: 'handleSubscriptionResult', - }, - AUTO_EXPAND_TO_REVEAL_SELECTION: { - actions: 'autoExpandToRevealSelection', - }, - HANDLE_EXPANDED: { - actions: 'expand', - }, - HANDLE_ON_EXPAND_ALL: { - actions: 'onExpandAll', - }, - HANDLE_EXPAND_ALL_TREE_PATH: { - actions: 'expandAllTreePath', - }, - HANDLE_TREE_PATH: { - actions: 'handleTreePath', - }, - HANDLE_COMPLETE: { - target: 'complete', - }, - SYNCHRONIZE_WITH_SELECTION: { - actions: 'synchronizeWithSelection', - }, - }, - }, - complete: { - type: 'final', - }, - }, - }, - }, - }, - { - guards: { - isTreeRefreshedEventPayload: (_, event) => { - const { result } = event as HandleSubscriptionResultEvent; - const { data } = result; - return isTreeRefreshedEventPayload(data.treeEvent); - }, - }, - actions: { - handleSubscriptionResult: assign((_, event) => { - const { result } = event as HandleSubscriptionResultEvent; - const { data } = result; - if (isTreeRefreshedEventPayload(data.treeEvent)) { - const { tree } = data.treeEvent; - - return { tree }; - } - return {}; - }), - synchronizeWithSelection: assign((_, event) => { - const { synchronizedWithSelection } = event as SynchronizeWithSelectionEvent; - if (synchronizedWithSelection) { - return { synchronizedWithSelection, autoExpandToRevealSelection: true }; - } - return { synchronizedWithSelection, autoExpandToRevealSelection: false }; - }), - autoExpandToRevealSelection: assign((context, event) => { - const { autoExpandToRevealSelection } = event as AutoExpandToRevealSelectionEvent; - const { synchronizedWithSelection } = context; - if (synchronizedWithSelection) { - return { autoExpandToRevealSelection }; - } - return {}; - }), - expand: assign((context, event) => { - const { expanded, maxDepth } = context; - const { id, depth } = event as HandleExpandedEvent; - - if (expanded.includes(id)) { - const newExpanded = [...expanded]; - newExpanded.splice(newExpanded.indexOf(id), 1); - - // Disable synchronize mode on collapse - return { expanded: newExpanded, autoExpandToRevealSelection: false, maxDepth: Math.max(maxDepth, depth) }; - } - return { expanded: [...expanded, id], maxDepth: Math.max(maxDepth, depth) }; - }), - onExpandAll: assign((_, event) => { - const { treeItemId } = event as HandleOnExpandAllEvent; - return { treeItemToExpandAll: treeItemId }; - }), - expandAllTreePath: assign((context, event) => { - const { expanded, maxDepth } = context; - const { expandAllTreePathData } = event as HandleExpandAllTreePathEvent; - if (expandAllTreePathData.viewer?.editingContext?.expandAllTreePath) { - const { treeItemIdsToExpand, maxDepth: expandedMaxDepth } = - expandAllTreePathData.viewer.editingContext.expandAllTreePath; - const newExpanded: string[] = [...expanded]; - - treeItemIdsToExpand?.forEach((itemToExpand) => { - if (!expanded.includes(itemToExpand)) { - newExpanded.push(itemToExpand); - } - }); - - return { - expanded: newExpanded, - maxDepth: Math.max(expandedMaxDepth, maxDepth), - treeItemToExpandAll: null, - }; - } - return { treeItemToExpandAll: null }; - }), - handleTreePath: assign((context, event) => { - const { expanded, maxDepth } = context; - const { treePathData } = event as HandleTreePathEvent; - if (treePathData.viewer?.editingContext?.treePath) { - const { treeItemIdsToExpand, maxDepth: expandedMaxDepth } = treePathData.viewer.editingContext.treePath; - const newExpanded: string[] = [...expanded]; - - treeItemIdsToExpand?.forEach((itemToExpand) => { - if (!expanded.includes(itemToExpand)) { - newExpanded.push(itemToExpand); - } - }); - - return { expanded: newExpanded, maxDepth: Math.max(expandedMaxDepth, maxDepth) }; - } - return {}; - }), - setMessage: assign((_, event) => { - const { message } = event as ShowToastEvent; - return { message }; - }), - clearMessage: assign((_) => { - return { message: null }; - }), - }, - } -); diff --git a/packages/trees/frontend/sirius-components-trees/src/views/__tests__/GraphqlTreeEventSubscription.test.ts b/packages/trees/frontend/sirius-components-trees/src/views/__tests__/GraphqlTreeEventSubscription.test.ts index aa22b6ef6de..326cb8c904c 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/__tests__/GraphqlTreeEventSubscription.test.ts +++ b/packages/trees/frontend/sirius-components-trees/src/views/__tests__/GraphqlTreeEventSubscription.test.ts @@ -15,8 +15,8 @@ import { expect, test } from 'vitest'; import { getTreeEventSubscription } from '../getTreeEventSubscription'; const getDocumentSubscription = gql` - subscription treeEvent($input: TreeEventInput!) { - treeEvent(input: $input) { + subscription explorerEvent($input: ExplorerEventInput!) { + explorerEvent(input: $input) { __typename ... on TreeRefreshedEventPayload { id @@ -66,7 +66,7 @@ const getDocumentSubscription = gql` test('looks like the graphql subscription loaded from graphql test subscription file', () => { // apply getTreeEventSubscription with depth 2 - const getBuiltSubscription = getTreeEventSubscription(2); + const getBuiltSubscription = getTreeEventSubscription(2, 'explorerEvent', 'ExplorerEventInput'); // compare results const received = getBuiltSubscription.trim().replace(/\s+/g, ' '); const expected = getDocumentSubscription.replace(/\s+/g, ' '); diff --git a/packages/trees/frontend/sirius-components-trees/src/views/getTreeEventSubscription.ts b/packages/trees/frontend/sirius-components-trees/src/views/getTreeEventSubscription.ts index e76680f88e7..402846c946d 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/getTreeEventSubscription.ts +++ b/packages/trees/frontend/sirius-components-trees/src/views/getTreeEventSubscription.ts @@ -10,11 +10,11 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -export const getTreeEventSubscription = (depth: number): string => { +export const getTreeEventSubscription = (depth: number, eventType: string, eventInputType: string): string => { const treeChildren = recursiveGetChildren(depth); const subscription = ` -subscription treeEvent($input: TreeEventInput!) { - treeEvent(input: $input) { +subscription ${eventType}($input: ${eventInputType}!) { + ${eventType}(input: $input) { __typename ... on TreeRefreshedEventPayload { id