From 36df83b63c99a5e6917422cbc94963656b863948 Mon Sep 17 00:00:00 2001 From: Florian Barbin Date: Thu, 12 Sep 2024 17:40:48 +0200 Subject: [PATCH] [3763] Adapt the Selection Dialog to the TreeEvent refactoring Bug: https://github.com/eclipse-sirius/sirius-web/issues/3763 Signed-off-by: Florian Barbin --- .../SelectionDialogTreeConfiguration.java | 53 +++++++++ .../dto/SelectionDialogTreeEventInput.java | 26 +++++ ...ectionDialogTreeEventProcessorFactory.java | 101 ++++++++++++++++++ .../main/resources/schema/selection.graphqls | 11 ++ ...onSelectionDialogTreeEventDataFetcher.java | 72 +++++++++++++ .../sirius-components-selection/package.json | 2 + .../src/SelectionDialog.tsx | 61 ++--------- .../src/SelectionDialog.types.ts | 6 +- .../src/SelectionDialogTreeView.tsx | 89 +++++++++++++++ .../src/SelectionDialogTreeView.types.ts | 23 ++++ .../useSelectionDialogTreeSubscription.tsx | 87 +++++++++++++++ ...seSelectionDialogTreeSubscription.types.ts | 49 +++++++++ ...tionDialogTreeEventSubscriptionRunner.java | 51 +++++++++ .../SelectionControllerIntegrationTests.java | 48 ++++----- .../SelectionDialogDescriptionConverter.java | 11 +- 15 files changed, 607 insertions(+), 83 deletions(-) create mode 100644 packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/configurations/SelectionDialogTreeConfiguration.java create mode 100644 packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/dto/SelectionDialogTreeEventInput.java create mode 100644 packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/SelectionDialogTreeEventProcessorFactory.java create mode 100644 packages/selection/backend/sirius-components-selection-graphql/src/main/java/org/eclipse/sirius/components/selection/graphql/datafetchers/subscription/SubscriptionSelectionDialogTreeEventDataFetcher.java create mode 100644 packages/selection/frontend/sirius-components-selection/src/SelectionDialogTreeView.tsx create mode 100644 packages/selection/frontend/sirius-components-selection/src/SelectionDialogTreeView.types.ts create mode 100644 packages/selection/frontend/sirius-components-selection/src/useSelectionDialogTreeSubscription.tsx create mode 100644 packages/selection/frontend/sirius-components-selection/src/useSelectionDialogTreeSubscription.types.ts create mode 100644 packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/selection/SelectionDialogTreeEventSubscriptionRunner.java diff --git a/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/configurations/SelectionDialogTreeConfiguration.java b/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/configurations/SelectionDialogTreeConfiguration.java new file mode 100644 index 00000000000..c37b6d51ad3 --- /dev/null +++ b/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/configurations/SelectionDialogTreeConfiguration.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.sirius.components.collaborative.selection.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; + +/** + * The configuration of the selection dialog event processor. + * + * @author fbarbin + */ +public class SelectionDialogTreeConfiguration implements IRepresentationConfiguration { + + private final String treeId; + + private final List expanded; + + public SelectionDialogTreeConfiguration(String editingContextId, String treeId, List expanded) { + this.expanded = Objects.requireNonNull(expanded); + + StringBuilder idBuilder = new StringBuilder(treeId); + + 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/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/dto/SelectionDialogTreeEventInput.java b/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/dto/SelectionDialogTreeEventInput.java new file mode 100644 index 00000000000..c132e39a06f --- /dev/null +++ b/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/dto/SelectionDialogTreeEventInput.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.selection.dto; + +import java.util.List; +import java.util.UUID; + +import org.eclipse.sirius.components.core.api.IInput; + +/** + * The input of the selection dialog tree event subscription. + * + * @author fbarbin + */ +public record SelectionDialogTreeEventInput(UUID id, String editingContextId, String treeId, List expanded) implements IInput { +} diff --git a/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/SelectionDialogTreeEventProcessorFactory.java b/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/SelectionDialogTreeEventProcessorFactory.java new file mode 100644 index 00000000000..969f8c57ae2 --- /dev/null +++ b/packages/selection/backend/sirius-components-collaborative-selection/src/main/java/org/eclipse/sirius/components/collaborative/selection/services/SelectionDialogTreeEventProcessorFactory.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.sirius.components.collaborative.selection.services; + +import java.util.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.selection.configurations.SelectionDialogTreeConfiguration; +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.core.api.IEditingContext; +import org.eclipse.sirius.components.core.api.IRepresentationDescriptionSearchService; +import org.eclipse.sirius.components.representations.GetOrCreateRandomIdProvider; +import org.eclipse.sirius.components.representations.VariableManager; +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 a selection dialog tree. + * + * @author fbarbin + */ +@Service +public class SelectionDialogTreeEventProcessorFactory implements IRepresentationEventProcessorFactory { + + private final IRepresentationDescriptionSearchService representationDescriptionSearchService; + + private final ITreeService treeService; + + private final List treeEventHandlers; + + private final ISubscriptionManagerFactory subscriptionManagerFactory; + + private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; + + public SelectionDialogTreeEventProcessorFactory(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 SelectionDialogTreeConfiguration; + } + + @Override + public Optional createRepresentationEventProcessor(IRepresentationConfiguration configuration, IEditingContext editingContext) { + if (configuration instanceof SelectionDialogTreeConfiguration selectionDialogTreeConfiguration) { + + Optional optionalTreeDescription = this.findTreeDescription(editingContext, selectionDialogTreeConfiguration); + if (optionalTreeDescription.isPresent()) { + var treeDescription = optionalTreeDescription.get(); + + TreeCreationParameters treeCreationParameters = TreeCreationParameters.newTreeCreationParameters(selectionDialogTreeConfiguration.getId()) + .treeDescription(treeDescription) + .activeFilterIds(List.of()) + .expanded(selectionDialogTreeConfiguration.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(); + } + private Optional findTreeDescription(IEditingContext editingContext, SelectionDialogTreeConfiguration treeConfiguration) { + VariableManager variableManager = new VariableManager(); + variableManager.put(GetOrCreateRandomIdProvider.PREVIOUS_REPRESENTATION_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/selection/backend/sirius-components-collaborative-selection/src/main/resources/schema/selection.graphqls b/packages/selection/backend/sirius-components-collaborative-selection/src/main/resources/schema/selection.graphqls index a1d7472c57c..f4ad38f86c6 100644 --- a/packages/selection/backend/sirius-components-collaborative-selection/src/main/resources/schema/selection.graphqls +++ b/packages/selection/backend/sirius-components-collaborative-selection/src/main/resources/schema/selection.graphqls @@ -1,3 +1,14 @@ +extend type Subscription { + selectionDialogTreeEvent(input: SelectionDialogTreeEventInput!): TreeEventPayload! +} + +input SelectionDialogTreeEventInput { + id: ID! + treeId: String! + editingContextId: ID! + expanded: [String!]! +} + type SelectionDescription implements RepresentationDescription { id: ID! label: String! diff --git a/packages/selection/backend/sirius-components-selection-graphql/src/main/java/org/eclipse/sirius/components/selection/graphql/datafetchers/subscription/SubscriptionSelectionDialogTreeEventDataFetcher.java b/packages/selection/backend/sirius-components-selection-graphql/src/main/java/org/eclipse/sirius/components/selection/graphql/datafetchers/subscription/SubscriptionSelectionDialogTreeEventDataFetcher.java new file mode 100644 index 00000000000..511a37a734a --- /dev/null +++ b/packages/selection/backend/sirius-components-selection-graphql/src/main/java/org/eclipse/sirius/components/selection/graphql/datafetchers/subscription/SubscriptionSelectionDialogTreeEventDataFetcher.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.selection.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.selection.configurations.SelectionDialogTreeConfiguration; +import org.eclipse.sirius.components.collaborative.selection.dto.SelectionDialogTreeEventInput; +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 selection dialog tree subscription . + * + * @author fbarbin + */ +@SubscriptionDataFetcher(type = "Subscription", field = "selectionDialogTreeEvent") +public class SubscriptionSelectionDialogTreeEventDataFetcher implements IDataFetcherWithFieldCoordinates>> { + + private static final String INPUT_ARGUMENT = "input"; + + private final ObjectMapper objectMapper; + + private final IExceptionWrapper exceptionWrapper; + + private final IEventProcessorSubscriptionProvider eventProcessorSubscriptionProvider; + + public SubscriptionSelectionDialogTreeEventDataFetcher(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, SelectionDialogTreeEventInput.class); + var selectionDialogTreeConfiguration = new SelectionDialogTreeConfiguration(input.editingContextId(), input.treeId(), input.expanded()); + + Map localContext = new HashMap<>(); + localContext.put(LocalContextConstants.EDITING_CONTEXT_ID, input.editingContextId()); + localContext.put(LocalContextConstants.REPRESENTATION_ID, selectionDialogTreeConfiguration.getId()); + + return this.exceptionWrapper.wrapFlux(() -> this.eventProcessorSubscriptionProvider.getSubscription(input.editingContextId(), selectionDialogTreeConfiguration, input), input) + .map(payload -> DataFetcherResult.newResult() + .data(payload) + .localContext(localContext) + .build()); + } +} diff --git a/packages/selection/frontend/sirius-components-selection/package.json b/packages/selection/frontend/sirius-components-selection/package.json index b187d6a6a87..cfb9bfa082a 100644 --- a/packages/selection/frontend/sirius-components-selection/package.json +++ b/packages/selection/frontend/sirius-components-selection/package.json @@ -33,6 +33,7 @@ "@apollo/client": "3.10.4", "@eclipse-sirius/sirius-components-core": "*", "@eclipse-sirius/sirius-components-diagrams": "*", + "@eclipse-sirius/sirius-components-trees": "*", "@mui/material": "5.15.19", "@mui/icons-material": "5.15.19", "@xstate/react": "3.0.0", @@ -46,6 +47,7 @@ "@apollo/client": "3.10.4", "@eclipse-sirius/sirius-components-core": "*", "@eclipse-sirius/sirius-components-diagrams": "*", + "@eclipse-sirius/sirius-components-trees": "*", "@eclipse-sirius/sirius-components-tsconfig": "*", "@mui/material": "5.15.19", "@mui/icons-material": "5.15.19", diff --git a/packages/selection/frontend/sirius-components-selection/src/SelectionDialog.tsx b/packages/selection/frontend/sirius-components-selection/src/SelectionDialog.tsx index 4bdd599d349..794a7fc751b 100644 --- a/packages/selection/frontend/sirius-components-selection/src/SelectionDialog.tsx +++ b/packages/selection/frontend/sirius-components-selection/src/SelectionDialog.tsx @@ -12,31 +12,18 @@ *******************************************************************************/ import { Selection, SelectionContext } from '@eclipse-sirius/sirius-components-core'; import { DiagramDialogComponentProps } from '@eclipse-sirius/sirius-components-diagrams'; -import { TreeItemActionProps, TreeView } from '@eclipse-sirius/sirius-components-trees'; -import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore'; import Button from '@mui/material/Button'; import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; import DialogTitle from '@mui/material/DialogTitle'; -import IconButton from '@mui/material/IconButton'; import { useEffect, useState } from 'react'; -import { makeStyles } from 'tss-react/mui'; -import { DiagramDialogComponentState } from './SelectionDialog.types'; +import { DiagramDialogState } from './SelectionDialog.types'; +import { SelectionDialogTreeView } from './SelectionDialogTreeView'; import { useSelectionDescription } from './useSelectionDescription'; - export const SELECTION_DIALOG_TYPE: string = 'selectionDialogDescription'; -const useTreeStyle = makeStyles()((theme) => ({ - borderStyle: { - border: '1px solid', - borderColor: theme.palette.grey[500], - height: 300, - overflow: 'auto', - }, -})); - export const SelectionDialog = ({ editingContextId, dialogDescriptionId, @@ -44,13 +31,13 @@ export const SelectionDialog = ({ onClose, onFinish, }: DiagramDialogComponentProps) => { - const initialState: DiagramDialogComponentState = { + const initialState: DiagramDialogState = { treeDescriptionId: '', message: '', selectedObjects: [], }; - const { classes } = useTreeStyle(); - const [state, setState] = useState(initialState); + + const [state, setState] = useState(initialState); const { loading, selectionDescription } = useSelectionDescription({ editingContextId, @@ -71,25 +58,16 @@ export const SelectionDialog = ({ })); } }, [loading, selectionDescription]); + let content: JSX.Element | null = null; if (state.treeDescriptionId) { content = ( -
- } - /> -
+ ); } return ( @@ -126,20 +104,3 @@ export const SelectionDialog = ({ ); }; - -const SelectionDialogTreeItemAction = ({ onExpandAll, item, isHovered }: TreeItemActionProps) => { - if (!onExpandAll || !item || !item.hasChildren || !isHovered) { - return null; - } - return ( - { - onExpandAll(item); - }}> - - - ); -}; diff --git a/packages/selection/frontend/sirius-components-selection/src/SelectionDialog.types.ts b/packages/selection/frontend/sirius-components-selection/src/SelectionDialog.types.ts index e599f4d8cba..05f70d6e11c 100644 --- a/packages/selection/frontend/sirius-components-selection/src/SelectionDialog.types.ts +++ b/packages/selection/frontend/sirius-components-selection/src/SelectionDialog.types.ts @@ -19,13 +19,13 @@ export interface SelectionDialogProps { onFinish: (selectedObjectId: string) => void; } -export interface DiagramDialogComponentState { +export interface DiagramDialogState { treeDescriptionId: string; message: string; - selectedObjects: DiagramDialogComponentSelectedObject[]; + selectedObjects: DiagramDialogSelectedObject[]; } -export interface DiagramDialogComponentSelectedObject { +export interface DiagramDialogSelectedObject { id: string; kind: string; } diff --git a/packages/selection/frontend/sirius-components-selection/src/SelectionDialogTreeView.tsx b/packages/selection/frontend/sirius-components-selection/src/SelectionDialogTreeView.tsx new file mode 100644 index 00000000000..242a6090cb2 --- /dev/null +++ b/packages/selection/frontend/sirius-components-selection/src/SelectionDialogTreeView.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 { 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 { SelectionDialogTreeViewProps, SelectionDialogTreeViewState } from './SelectionDialogTreeView.types'; +import { useSelectionDialogTreeSubscription } from './useSelectionDialogTreeSubscription'; +export const SELECTION_DIALOG_TYPE: string = 'selectionDialogDescription'; + +const useTreeStyle = makeStyles()((theme) => ({ + borderStyle: { + border: '1px solid', + borderColor: theme.palette.grey[500], + height: 300, + overflow: 'auto', + }, +})); + +export const SelectionDialogTreeView = ({ + editingContextId, + treeDescriptionId, + targetObjectId, +}: SelectionDialogTreeViewProps) => { + const initialState: SelectionDialogTreeViewState = { + expanded: [], + maxDepth: 1, + }; + + const { classes } = useTreeStyle(); + const [state, setState] = useState(initialState); + + const treeId = `selection://?treeDescriptionId=${encodeURIComponent( + treeDescriptionId + )}&targetObjectId=${encodeURIComponent(targetObjectId)}`; + + const { tree } = useSelectionDialogTreeSubscription(editingContextId, treeId, state.expanded, state.maxDepth); + + const onExpandedElementChange = (expanded: string[], maxDepth: number) => { + setState((prevState) => ({ ...prevState, expanded, maxDepth })); + }; + + return ( +
+ {tree ? ( + } + onExpandedElementChange={onExpandedElementChange} + /> + ) : null} +
+ ); +}; + +const SelectionDialogTreeItemAction = ({ onExpandAll, item, isHovered }: TreeItemActionProps) => { + if (!onExpandAll || !item || !item.hasChildren || !isHovered) { + return null; + } + return ( + { + onExpandAll(item); + }}> + + + ); +}; diff --git a/packages/selection/frontend/sirius-components-selection/src/SelectionDialogTreeView.types.ts b/packages/selection/frontend/sirius-components-selection/src/SelectionDialogTreeView.types.ts new file mode 100644 index 00000000000..aba103cf1bd --- /dev/null +++ b/packages/selection/frontend/sirius-components-selection/src/SelectionDialogTreeView.types.ts @@ -0,0 +1,23 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ + +export interface SelectionDialogTreeViewProps { + editingContextId: string; + treeDescriptionId: string; + targetObjectId: string; +} + +export interface SelectionDialogTreeViewState { + expanded: string[]; + maxDepth: number; +} diff --git a/packages/selection/frontend/sirius-components-selection/src/useSelectionDialogTreeSubscription.tsx b/packages/selection/frontend/sirius-components-selection/src/useSelectionDialogTreeSubscription.tsx new file mode 100644 index 00000000000..8e22f6ea0af --- /dev/null +++ b/packages/selection/frontend/sirius-components-selection/src/useSelectionDialogTreeSubscription.tsx @@ -0,0 +1,87 @@ +/******************************************************************************* + * 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 } from '@eclipse-sirius/sirius-components-trees'; +import { useEffect, useState } from 'react'; +import { + GQLSelectionDialogTreeEventData, + GQLSelectionDialogTreeEventInput, + GQLSelectionDialogTreeEventVariables, + GQLTreeEventPayload, + GQLTreeRefreshedEventPayload, + UseSelectionDialogTreeSubscriptionState, + UseSelectionDialogTreeSubscriptionValue, +} from './useSelectionDialogTreeSubscription.types'; + +const isTreeRefreshedEventPayload = (payload: GQLTreeEventPayload): payload is GQLTreeRefreshedEventPayload => + payload.__typename === 'TreeRefreshedEventPayload'; + +export const useSelectionDialogTreeSubscription = ( + editingContextId: string, + treeId: string, + expanded: string[], + maxDepth: number +): UseSelectionDialogTreeSubscriptionValue => { + const [state, setState] = useState({ + id: crypto.randomUUID(), + tree: null, + complete: false, + }); + + const input: GQLSelectionDialogTreeEventInput = { + id: state.id, + editingContextId, + treeId, + expanded, + }; + + const variables: GQLSelectionDialogTreeEventVariables = { input }; + + const onData = ({ data }: OnDataOptions) => { + const { data: gqlTreeData } = data; + if (gqlTreeData) { + const { selectionDialogTreeEvent: 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, 'selectionDialogTreeEvent', 'SelectionDialogTreeEventInput')), + { + 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/selection/frontend/sirius-components-selection/src/useSelectionDialogTreeSubscription.types.ts b/packages/selection/frontend/sirius-components-selection/src/useSelectionDialogTreeSubscription.types.ts new file mode 100644 index 00000000000..fb1c3d87489 --- /dev/null +++ b/packages/selection/frontend/sirius-components-selection/src/useSelectionDialogTreeSubscription.types.ts @@ -0,0 +1,49 @@ +/******************************************************************************* + * 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 { GQLTree } from '@eclipse-sirius/sirius-components-trees'; + +export interface UseSelectionDialogTreeSubscriptionValue { + loading: boolean; + tree: GQLTree | null; + complete: boolean; +} + +export interface UseSelectionDialogTreeSubscriptionState { + id: string; + tree: GQLTree | null; + complete: boolean; +} + +export interface GQLSelectionDialogTreeEventInput { + id: string; + treeId: string; + editingContextId: string; + expanded: string[]; +} + +export interface GQLSelectionDialogTreeEventVariables { + input: GQLSelectionDialogTreeEventInput; +} + +export interface GQLSelectionDialogTreeEventData { + selectionDialogTreeEvent: GQLTreeEventPayload; +} + +export interface GQLTreeEventPayload { + __typename: string; +} + +export interface GQLTreeRefreshedEventPayload extends GQLTreeEventPayload { + id: string; + tree: GQLTree; +} diff --git a/packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/selection/SelectionDialogTreeEventSubscriptionRunner.java b/packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/selection/SelectionDialogTreeEventSubscriptionRunner.java new file mode 100644 index 00000000000..f16732e31d9 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/selection/SelectionDialogTreeEventSubscriptionRunner.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.selection; + +import java.util.Objects; + +import org.eclipse.sirius.components.collaborative.selection.dto.SelectionDialogTreeEventInput; +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 selection dialog tree event subscription with the GraphQL API. + * + * @author fbarbin + */ +@Service +public class SelectionDialogTreeEventSubscriptionRunner implements ISubscriptionRunner { + + private static final String SELECTION_DIALOG_TREE_EVENT_SUBSCRIPTION = """ + subscription selectionDialogTreeEvent($input: SelectionDialogTreeEventInput!) { + selectionDialogTreeEvent(input: $input) { + __typename + } + } + """; + + private final IGraphQLRequestor graphQLRequestor; + + public SelectionDialogTreeEventSubscriptionRunner(IGraphQLRequestor graphQLRequestor) { + this.graphQLRequestor = Objects.requireNonNull(graphQLRequestor); + } + + @Override + public Flux run(SelectionDialogTreeEventInput input) { + return this.graphQLRequestor.subscribe(SELECTION_DIALOG_TREE_EVENT_SUBSCRIPTION, input); + } + +} diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/selection/SelectionControllerIntegrationTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/selection/SelectionControllerIntegrationTests.java index 0128476b89e..12556f9701d 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/selection/SelectionControllerIntegrationTests.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/selection/SelectionControllerIntegrationTests.java @@ -28,18 +28,18 @@ import java.util.function.Consumer; import java.util.function.Predicate; -import org.eclipse.sirius.components.collaborative.trees.dto.TreeEventInput; +import org.eclipse.sirius.components.collaborative.selection.dto.SelectionDialogTreeEventInput; 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.components.trees.Tree; import org.eclipse.sirius.components.trees.TreeItem; import org.eclipse.sirius.components.trees.tests.graphql.ExpandAllTreePathQueryRunner; -import org.eclipse.sirius.components.trees.tests.graphql.TreeEventSubscriptionRunner; import org.eclipse.sirius.web.AbstractIntegrationTests; import org.eclipse.sirius.web.data.PapayaIdentifiers; import org.eclipse.sirius.web.services.selection.SelectionDescriptionProvider; import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState; +import org.eclipse.sirius.web.tests.services.selection.SelectionDialogTreeEventSubscriptionRunner; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -82,8 +82,8 @@ query getSelectionDescription($editingContextId: ID!, $representationId: ID!, $t """; private static final String GET_TREE_EVENT_SUBSCRIPTION = """ - subscription treeEvent($input: TreeEventInput!) { - treeEvent(input: $input) { + subscription selectionDialogTreeEvent($input: SelectionDialogTreeEventInput!) { + selectionDialogTreeEvent(input: $input) { __typename ... on TreeRefreshedEventPayload { id @@ -106,7 +106,7 @@ subscription treeEvent($input: TreeEventInput!) { private IGivenInitialServerState givenInitialServerState; @Autowired - private TreeEventSubscriptionRunner treeEventSubscriptionRunner; + private SelectionDialogTreeEventSubscriptionRunner selectionDialogTreeEventSubscriptionRunner; @Autowired private SelectionDescriptionProvider selectionDescriptionProvider; @@ -142,8 +142,8 @@ public void givenSemanticObjectWhenRequestingSelectionDescriptionThenTheSelectio public void givenSemanticObjectWhenWeSubscribeToItsSelectionEventsThenTheSelectionIsSent() { var treeId = "selection://?treeDescriptionId=" + URLEncoder.encode(this.selectionDescriptionProvider.getSelectionDialogTreeDescriptionId(), StandardCharsets.UTF_8) + "&targetObjectId=" + PapayaIdentifiers.PROJECT_OBJECT.toString(); - var input = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(), List.of()); - var flux = this.treeEventSubscriptionRunner.run(input); + var input = new SelectionDialogTreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of()); + var flux = this.selectionDialogTreeEventSubscriptionRunner.run(input); var hasResourceRootContent = this.getTreeRefreshedEventPayloadMatcher(); StepVerifier.create(flux) .expectNextMatches(hasResourceRootContent) @@ -158,8 +158,8 @@ public void givenSemanticObjectWhenWeSubscribeToItsSelectionEventsThenTheSelecti public void givenSelectionDialogTreeWhenWeExpandTheFirstItemThenChildrenAreSent() { var treeId = "selection://?treeDescriptionId=" + URLEncoder.encode(this.selectionDescriptionProvider.getSelectionDialogTreeDescriptionId(), StandardCharsets.UTF_8) + "&targetObjectId=" + PapayaIdentifiers.PROJECT_OBJECT.toString(); - var input = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(), List.of()); - var flux = this.treeEventSubscriptionRunner.run(input); + var input = new SelectionDialogTreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of()); + var flux = this.selectionDialogTreeEventSubscriptionRunner.run(input); var treeItemId = new AtomicReference(); @@ -176,8 +176,8 @@ public void givenSelectionDialogTreeWhenWeExpandTheFirstItemThenChildrenAreSent( .thenCancel() .verify(); - var expandedTreeInput = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(treeItemId.get()), List.of()); - var expandedTreeFlux = this.treeEventSubscriptionRunner.run(expandedTreeInput); + var expandedTreeInput = new SelectionDialogTreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(treeItemId.get())); + var expandedTreeFlux = this.selectionDialogTreeEventSubscriptionRunner.run(expandedTreeInput); var treeRefreshedEventPayloadExpandMatcher = this.getTreeRefreshedEventPayloadExpandMatcher(); StepVerifier.create(expandedTreeFlux) .expectNextMatches(treeRefreshedEventPayloadExpandMatcher) @@ -193,8 +193,8 @@ public void givenSelectionDialogTreeWhenWeExpandTheFirstItemThenChildrenAreSent( public void givenSelectionDialogTreeWhenWePerformExpandAllOnTheFirstItemThenChildrenAreSent() { var treeId = "selection://?treeDescriptionId=" + URLEncoder.encode(this.selectionDescriptionProvider.getSelectionDialogTreeDescriptionId(), StandardCharsets.UTF_8) + "&targetObjectId=" + PapayaIdentifiers.PROJECT_OBJECT.toString(); - var input = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(), List.of()); - var flux = this.treeEventSubscriptionRunner.run(input); + var input = new SelectionDialogTreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of()); + var flux = this.selectionDialogTreeEventSubscriptionRunner.run(input); var treeItemId = new AtomicReference(); var treeInstanceId = new AtomicReference(); @@ -235,8 +235,8 @@ public void givenSelectionDialogTreeWhenWePerformExpandAllOnTheFirstItemThenChil }); - var expandedTreeInput = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, treeItemIds.get(), List.of()); - var expandedTreeFlux = this.treeEventSubscriptionRunner.run(expandedTreeInput); + var expandedTreeInput = new SelectionDialogTreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, treeItemIds.get()); + var expandedTreeFlux = this.selectionDialogTreeEventSubscriptionRunner.run(expandedTreeInput); StepVerifier.create(expandedTreeFlux) .consumeNextWith(initialExpandedTreeContentConsumer) @@ -251,8 +251,8 @@ public void givenSelectionDialogTreeWhenWePerformExpandAllOnTheFirstItemThenChil public void givenSelectionDialogTreeWhenWePerformExpandAllOnTheSecondItemThenChildrenAreSent() { var treeId = "selection://?treeDescriptionId=" + URLEncoder.encode(this.selectionDescriptionProvider.getSelectionDialogTreeDescriptionId(), StandardCharsets.UTF_8) + "&targetObjectId=" + PapayaIdentifiers.PROJECT_OBJECT.toString(); - var input = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(), List.of()); - var flux = this.treeEventSubscriptionRunner.run(input); + var input = new SelectionDialogTreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of()); + var flux = this.selectionDialogTreeEventSubscriptionRunner.run(input); var rootTreeItemId = new AtomicReference(); var treeInstanceId = new AtomicReference(); @@ -271,8 +271,8 @@ public void givenSelectionDialogTreeWhenWePerformExpandAllOnTheSecondItemThenChi .verify(Duration.ofSeconds(10)); //We expand the first tree item (representing the resource) - var expandedFirstTreeItemInput = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(rootTreeItemId.get()), List.of()); - var expandedFirstTreeItemFlux = this.treeEventSubscriptionRunner.run(expandedFirstTreeItemInput); + var expandedFirstTreeItemInput = new SelectionDialogTreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(rootTreeItemId.get())); + var expandedFirstTreeItemFlux = this.selectionDialogTreeEventSubscriptionRunner.run(expandedFirstTreeItemInput); //Used to retrieve the Papaya Root Project tree item var rootProjectTreeItemId = new AtomicReference(); @@ -314,8 +314,8 @@ public void givenSelectionDialogTreeWhenWePerformExpandAllOnTheSecondItemThenChi assertThat(rootProjectTreeItem.getChildren()).isNotEmpty(); }); - var expandedTreeInput = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, treeItemIds.get(), List.of()); - var expandedTreeFlux = this.treeEventSubscriptionRunner.run(expandedTreeInput); + var expandedTreeInput = new SelectionDialogTreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, treeItemIds.get()); + var expandedTreeFlux = this.selectionDialogTreeEventSubscriptionRunner.run(expandedTreeInput); // We now verify the expandAll result StepVerifier.create(expandedTreeFlux) .consumeNextWith(initialExpandedTreeContentConsumer) @@ -369,15 +369,15 @@ private Predicate getTreeRefreshedEventPayloadExpandMatcher() { public void givenSemanticObjectWhenWeSubscribeToItsSelectionEventsThenTheURLOfItsObjectsIsValid() { var treeId = "selection://?treeDescriptionId=" + URLEncoder.encode(this.selectionDescriptionProvider.getSelectionDialogTreeDescriptionId(), StandardCharsets.UTF_8) + "&targetObjectId=" + PapayaIdentifiers.PROJECT_OBJECT.toString(); - var input = new TreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of(), List.of()); + var input = new SelectionDialogTreeEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), treeId, List.of()); var flux = this.graphQLRequestor.subscribeToSpecification(GET_TREE_EVENT_SUBSCRIPTION, input); Consumer treeContentConsumer = payload -> Optional.of(payload) .ifPresentOrElse(body -> { - String typename = JsonPath.read(body, "$.data.treeEvent.__typename"); + String typename = JsonPath.read(body, "$.data.selectionDialogTreeEvent.__typename"); assertThat(typename).isEqualTo(TreeRefreshedEventPayload.class.getSimpleName()); - List> objectIconURLs = JsonPath.read(body, "$.data.treeEvent.tree.children[*].iconURL"); + List> objectIconURLs = JsonPath.read(body, "$.data.selectionDialogTreeEvent.tree.children[*].iconURL"); assertThat(objectIconURLs) .isNotEmpty() .allSatisfy(iconURLs -> { diff --git a/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/api/SelectionDialogDescriptionConverter.java b/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/api/SelectionDialogDescriptionConverter.java index a4344471f12..e3436a83d03 100644 --- a/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/api/SelectionDialogDescriptionConverter.java +++ b/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/api/SelectionDialogDescriptionConverter.java @@ -32,6 +32,7 @@ import org.eclipse.sirius.components.interpreter.AQLInterpreter; import org.eclipse.sirius.components.interpreter.Result; import org.eclipse.sirius.components.representations.Failure; +import org.eclipse.sirius.components.representations.GetOrCreateRandomIdProvider; import org.eclipse.sirius.components.representations.IRepresentationDescription; import org.eclipse.sirius.components.representations.IStatus; import org.eclipse.sirius.components.representations.VariableManager; @@ -60,8 +61,6 @@ public class SelectionDialogDescriptionConverter implements IDialogDescriptionCo private static final String SELECTION_PREFIX = "selection://"; - private static final String TREE_ID = "treeId"; - private final IObjectService objectService; private final IDiagramIdProvider diagramIdProvider; @@ -174,17 +173,17 @@ private TreeDescription createTreeDescription(SelectionDialogDescription selecti Function hasChildrenProvider = variableManager -> this.hasChildren(interpreter, selectionDialogTreeDescription, variableManager, isSelectableProvider); Predicate canCreatePredicate = variableManager -> { - return variableManager.get(TREE_ID, String.class) + return variableManager.get(GetOrCreateRandomIdProvider.PREVIOUS_REPRESENTATION_ID, String.class) .filter(id -> id.startsWith(SELECTION_PREFIX)) .map(this.urlParser::getParameterValues) .map(parameters -> parameters.get(TREE_DESCRIPTION_ID).get(0)) .filter(treeDescriptionIdParameter -> treeDescriptionId.equals(treeDescriptionIdParameter)) .isPresent(); }; - + String treeId = SELECTION_PREFIX + "?" + TREE_DESCRIPTION_ID + "=" + treeDescriptionId; return TreeDescription.newTreeDescription(treeDescriptionId) .label(DIALOG_DESCRIPTION_TREE_REPRESENTATION_NAME) - .idProvider(variableManager -> variableManager.get(TREE_ID, String.class).orElse(null)) + .idProvider(variableManager -> variableManager.get(GetOrCreateRandomIdProvider.PREVIOUS_REPRESENTATION_ID, String.class).orElse(treeId)) .canCreatePredicate(canCreatePredicate) .childrenProvider(childrenProvider) .deletableProvider(variableManager -> false) @@ -325,7 +324,7 @@ private Function> getElementProvider(AQLInterpreter int } private Optional getTargetObjectId(VariableManager variableManager) { - return variableManager.get(TREE_ID, String.class) + return variableManager.get(GetOrCreateRandomIdProvider.PREVIOUS_REPRESENTATION_ID, String.class) .map(this.urlParser::getParameterValues) .map(parameters -> parameters.get(TARGET_OBJECT_ID).get(0)); }