Skip to content

Commit

Permalink
[3015] Handle lane selection and editing in Deck
Browse files Browse the repository at this point in the history
- Make it possible to select a Lane
- Make the lane editable once selected (as card)
- Handle the title editing mutation

Bug: #3015
Signed-off-by: Florian Barbin <florian.barbin@obeo.fr>
  • Loading branch information
florianbarbin committed Jan 30, 2024
1 parent ed6a71d commit 80becb8
Show file tree
Hide file tree
Showing 28 changed files with 724 additions and 54 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Expand Up @@ -36,6 +36,7 @@ The reference widget graphql schema and the frontend have been updated according
- https://github.com/eclipse-sirius/sirius-web/issues/3010[#3010] [forms] Reference widget has been updated.
Its body operations are now bind on set/add instead of click.
- https://github.com/eclipse-sirius/sirius-web/issues/2942[#2942] [deck] Add the Card Drag and Drop capability in the Deck representation.
- https://github.com/eclipse-sirius/sirius-web/issues/3015[#3015] [deck] Add the Lane Selection and Direct edit capability.

=== Improvements

Expand Down
@@ -0,0 +1,42 @@
/*******************************************************************************
* 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.deck.api;

import org.eclipse.sirius.components.collaborative.deck.dto.input.EditDeckLaneInput;
import org.eclipse.sirius.components.core.api.IEditingContext;
import org.eclipse.sirius.components.core.api.IPayload;
import org.eclipse.sirius.components.deck.Deck;

/**
* Service used to manage deck lane.
*
* @author fbarbin
*/
public interface IDeckLaneService {

IPayload editLane(EditDeckLaneInput editDeckLaneInput, IEditingContext editingContext, Deck deck);

/**
* Implementation which does nothing, used for mocks in unit tests.
*
* @author fbarbin
*/
class NoOp implements IDeckLaneService {

@Override
public IPayload editLane(EditDeckLaneInput editDeckLaneInput, IEditingContext editingContext, Deck deck) {
return null;
}
}

}
@@ -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.deck.dto.input;

import java.util.UUID;

import org.eclipse.sirius.components.collaborative.deck.api.IDeckInput;

/**
* The input of the "Edit lane" mutation.
*
* @author fbarbin
*/
public record EditDeckLaneInput(UUID id, String editingContextId, String representationId, String laneId, String newTitle, String newLabel, String newDescription)
implements IDeckInput {
}
@@ -0,0 +1,81 @@
/*******************************************************************************
* 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.deck.handlers;

import java.util.Objects;

import org.eclipse.sirius.components.collaborative.api.ChangeDescription;
import org.eclipse.sirius.components.collaborative.api.ChangeKind;
import org.eclipse.sirius.components.collaborative.api.Monitoring;
import org.eclipse.sirius.components.collaborative.deck.api.IDeckEventHandler;
import org.eclipse.sirius.components.collaborative.deck.api.IDeckInput;
import org.eclipse.sirius.components.collaborative.deck.api.IDeckLaneService;
import org.eclipse.sirius.components.collaborative.deck.dto.input.EditDeckLaneInput;
import org.eclipse.sirius.components.collaborative.deck.message.ICollaborativeDeckMessageService;
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.deck.Deck;
import org.springframework.stereotype.Service;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import reactor.core.publisher.Sinks.Many;
import reactor.core.publisher.Sinks.One;

/**
* Handle "Edit Lane" events.
*
* @author fbarbin
*/
@Service
public class EditLaneEventHandler implements IDeckEventHandler {

private final IDeckLaneService deckLaneService;

private final ICollaborativeDeckMessageService messageService;

private final Counter counter;

public EditLaneEventHandler(IDeckLaneService deckLaneService, ICollaborativeDeckMessageService messageService, MeterRegistry meterRegistry) {
this.messageService = Objects.requireNonNull(messageService);
this.deckLaneService = Objects.requireNonNull(deckLaneService);

this.counter = Counter.builder(Monitoring.EVENT_HANDLER)
.tag(Monitoring.NAME, this.getClass().getSimpleName())
.register(meterRegistry);
}

@Override
public boolean canHandle(IDeckInput deckInput) {
return deckInput instanceof EditDeckLaneInput;
}

@Override
public void handle(One<IPayload> payloadSink, Many<ChangeDescription> changeDescriptionSink, IEditingContext editingContext, Deck deck, IDeckInput deckInput) {
this.counter.increment();

String message = this.messageService.invalidInput(deckInput.getClass().getSimpleName(), EditDeckLaneInput.class.getSimpleName());
IPayload payload = new ErrorPayload(deckInput.id(), message);
ChangeDescription changeDescription = new ChangeDescription(ChangeKind.NOTHING, deckInput.representationId(), deckInput);

if (deckInput instanceof EditDeckLaneInput input) {
payload = this.deckLaneService.editLane(input, editingContext, deck);

changeDescription = new ChangeDescription(ChangeKind.SEMANTIC_CHANGE, deckInput.representationId(), deckInput);
}

payloadSink.tryEmitValue(payload);
changeDescriptionSink.tryEmitNext(changeDescription);
}
}
@@ -0,0 +1,116 @@
/*******************************************************************************
* 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.deck.service;

import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Predicate;

import org.eclipse.sirius.components.collaborative.deck.api.IDeckLaneService;
import org.eclipse.sirius.components.collaborative.deck.dto.input.EditDeckLaneInput;
import org.eclipse.sirius.components.core.api.ErrorPayload;
import org.eclipse.sirius.components.core.api.IEditingContext;
import org.eclipse.sirius.components.core.api.IFeedbackMessageService;
import org.eclipse.sirius.components.core.api.IObjectService;
import org.eclipse.sirius.components.core.api.IPayload;
import org.eclipse.sirius.components.core.api.IRepresentationDescriptionSearchService;
import org.eclipse.sirius.components.core.api.SuccessPayload;
import org.eclipse.sirius.components.deck.Deck;
import org.eclipse.sirius.components.deck.Lane;
import org.eclipse.sirius.components.deck.description.DeckDescription;
import org.eclipse.sirius.components.deck.description.LaneDescription;
import org.eclipse.sirius.components.representations.Message;
import org.eclipse.sirius.components.representations.MessageLevel;
import org.eclipse.sirius.components.representations.VariableManager;
import org.springframework.stereotype.Service;

/**
* Service used to manage lanes.
*
* @author fbarbin
*/
@Service
public class DeckLaneService implements IDeckLaneService {

private final IRepresentationDescriptionSearchService representationDescriptionSearchService;

private final IFeedbackMessageService feedbackMessageService;

private final IObjectService objectService;

public DeckLaneService(IRepresentationDescriptionSearchService representationDescriptionSearchService, IFeedbackMessageService feedbackMessageService, IObjectService objectService) {
this.representationDescriptionSearchService = Objects.requireNonNull(representationDescriptionSearchService);
this.feedbackMessageService = Objects.requireNonNull(feedbackMessageService);
this.objectService = Objects.requireNonNull(objectService);
}



@Override
public IPayload editLane(EditDeckLaneInput editDeckLaneInput, IEditingContext editingContext, Deck deck) {
IPayload payload = new ErrorPayload(editDeckLaneInput.id(), "Edit lane failed");

Optional<Lane> optionalLane = this.findLane(lane -> Objects.equals(lane.id(), editDeckLaneInput.laneId()), deck);
Optional<LaneDescription> optionalLaneDescription = optionalLane.flatMap(lane -> this.findLaneDescription(lane.descriptionId(), deck, editingContext));

if (optionalLane.isPresent() && optionalLaneDescription.isPresent()) {
Optional<Object> optionalTargetObject = this.objectService.getObject(editingContext, optionalLane.get().targetObjectId());
if (optionalTargetObject.isPresent()) {
VariableManager variableManager = new VariableManager();
variableManager.put(VariableManager.SELF, optionalTargetObject.get());
variableManager.put(DeckDescription.NEW_TITLE, editDeckLaneInput.newTitle());
optionalLaneDescription.get().editLaneProvider().accept(variableManager);

payload = this.getPayload(editDeckLaneInput.id());
}
}

return payload;
}

private IPayload getPayload(UUID payloadId) {
IPayload payload = null;
List<Message> feedbackMessages = this.feedbackMessageService.getFeedbackMessages();
Optional<Message> optionalErrorMsg = feedbackMessages.stream().filter(msg -> MessageLevel.ERROR.equals(msg.level())).findFirst();
if (optionalErrorMsg.isPresent()) {
payload = new ErrorPayload(payloadId, optionalErrorMsg.get().body(), feedbackMessages);
} else {
payload = new SuccessPayload(payloadId, feedbackMessages);
}
return payload;
}
private Optional<Lane> findLane(Predicate<Lane> condition, Deck deck) {
return deck.lanes().stream()
.filter(condition)
.findFirst();
}

private Optional<DeckDescription> findDeckDescription(String descriptionId, IEditingContext editingContext) {
return this.representationDescriptionSearchService.findById(editingContext, descriptionId)
.filter(DeckDescription.class::isInstance)
.map(DeckDescription.class::cast);
}

private Optional<LaneDescription> findLaneDescription(String descriptionId, Deck deck, IEditingContext editingContext) {
return this.findDeckDescription(deck.descriptionId(), editingContext)
.stream()
.map(DeckDescription::laneDescriptions)
.flatMap(Collection::stream)
.filter(laneDesc -> laneDesc.id().equals(descriptionId))
.findFirst();
}

}
Expand Up @@ -47,6 +47,7 @@ extend type Mutation {
deleteDeckCard(input: DeleteDeckCardInput!): DeleteDeckCardPayload
editDeckCard(input: EditDeckCardInput!): EditDeckCardPayload
dropDeckCard(input: DropDeckCardInput!): DropDeckCardPayload
editDeckLane(input: EditDeckLaneInput!): EditDeckLanePayload
}

input CreateDeckCardInput {
Expand Down Expand Up @@ -92,3 +93,12 @@ input DropDeckCardInput {
}

union DropDeckCardPayload = SuccessPayload | ErrorPayload

input EditDeckLaneInput {
id: ID!
editingContextId: ID!
representationId: ID!
laneId: ID!
newTitle: String!
}
union EditDeckLanePayload = SuccessPayload | ErrorPayload
@@ -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.deck.graphql.datafetchers.mutation;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Objects;
import java.util.concurrent.CompletableFuture;

import org.eclipse.sirius.components.annotations.spring.graphql.MutationDataFetcher;
import org.eclipse.sirius.components.collaborative.deck.dto.input.EditDeckLaneInput;
import org.eclipse.sirius.components.core.api.IPayload;
import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates;
import org.eclipse.sirius.components.graphql.api.IEditingContextDispatcher;
import org.eclipse.sirius.components.graphql.api.IExceptionWrapper;

import graphql.schema.DataFetchingEnvironment;

/**
* The data fetcher used to change a Deck lane.
*
* @author fbarbin
*/
@MutationDataFetcher(type = "Mutation", field = "editDeckLane")
public class MutationEditLaneDataFetcher implements IDataFetcherWithFieldCoordinates<CompletableFuture<IPayload>> {

private static final String INPUT_ARGUMENT = "input";

private final ObjectMapper objectMapper;

private final IExceptionWrapper exceptionWrapper;

private final IEditingContextDispatcher editingContextDispatcher;

public MutationEditLaneDataFetcher(ObjectMapper objectMapper, IExceptionWrapper exceptionWrapper, IEditingContextDispatcher editingContextDispatcher) {
this.objectMapper = Objects.requireNonNull(objectMapper);
this.exceptionWrapper = Objects.requireNonNull(exceptionWrapper);
this.editingContextDispatcher = Objects.requireNonNull(editingContextDispatcher);
}

@Override
public CompletableFuture<IPayload> get(DataFetchingEnvironment environment) throws Exception {
Object argument = environment.getArgument(INPUT_ARGUMENT);
var input = this.objectMapper.convertValue(argument, EditDeckLaneInput.class);

return this.exceptionWrapper.wrapMono(() -> this.editingContextDispatcher.dispatchMutation(input.editingContextId(), input), input).toFuture();
}

}
6 changes: 6 additions & 0 deletions packages/deck/frontend/sirius-components-deck/src/Deck.tsx
Expand Up @@ -14,24 +14,28 @@ import Board from '@ObeoNetwork/react-trello';
import { Theme, useTheme } from '@material-ui/core/styles';
import { DeckProps } from './Deck.types';
import { DeckCard } from './card/DeckCard';
import { DeckLaneHeader } from './laneHeader/DeckLaneHeader';
import { Toolbar } from './toolbar/Toolbar';

export const Deck = ({
editingContextId,
representationId,
data,
onCardClick,
onLaneClick,
onCardDelete,
onCardAdd,
onCardUpdate,
onCardMoveAcrossLanes,
onLaneUpdate,
}: DeckProps) => {
const theme: Theme = useTheme();
const boardStyle = {
backgroundColor: theme.palette.background.default,
};
const components = {
Card: DeckCard,
LaneHeader: DeckLaneHeader,
};
return (
<div>
Expand All @@ -40,10 +44,12 @@ export const Deck = ({
data={data}
draggable={true}
onCardClick={onCardClick}
onLaneClick={onLaneClick}
components={components}
onCardDelete={onCardDelete}
onCardAdd={onCardAdd}
onCardUpdate={onCardUpdate}
onLaneUpdate={onLaneUpdate}
onCardMoveAcrossLanes={onCardMoveAcrossLanes}
data-testid={`deck-representation`}
style={boardStyle}
Expand Down

0 comments on commit 80becb8

Please sign in to comment.