Skip to content

Commit

Permalink
[3833] Enhance Gantt to support Date and DateTime
Browse files Browse the repository at this point in the history
Bug: #3833
Signed-off-by: Laurent Fasani <[email protected]>
  • Loading branch information
lfasani committed Aug 28, 2024
1 parent f97b698 commit fb29053
Show file tree
Hide file tree
Showing 18 changed files with 228 additions and 101 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ Introduce new `IRewriteProxiesResourceFilter` interface, to register resource fi
- https://github.com/eclipse-sirius/sirius-web/issues/2204[#2204] [core] Added `getStyledLabel` in `IDefaultLabelService` and `IObjectService` that can be used to display styled string in the explorer for example.
+
image:doc/screenshots/treeItemLabelStyled.jpg[StyledString, 70%]
- https://github.com/eclipse-sirius/sirius-web/issues/3833[#3833] [gantt] Enhance Gantt to support Date and DateTime

=== Improvements

Expand Down
5 changes: 3 additions & 2 deletions doc/specifier/representation-gantt.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The string contains
** an integer which is the number of time unit.
** a character among {D,H,m} corresponding to a Day, Hour or minute time unit.
Example: to round to half a day, the expression must returns `12H`
Note: To be coherent, if `Start Time Expression` and `End Time Expression` of Task description return a `LocaDate`, `Date Rounding Expression` should be in days.
## Task description

Expand All @@ -45,8 +46,8 @@ All the following expressions are called as many times as the number of semantic

* `Name Expression`: An expression that defines the name of the task
* `Description Expression`: An expression that defines the description of the task
* `Start Time Expression`: An expression that returns a `java.time.Instant` to define the start date of the task.
* `End Time Expression`: An expression that returns a `java.time.Instant` to define the end date of the task.
* `Start Time Expression`: An expression that returns a `java.time.Instant` or `java.time.LocalDate` a to define the start date of the task.
* `End Time Expression`: An expression that returns a `java.time.Instant` or `java.time.LocalDate` to define the end date of the task.
* `Progress Expression`: An expression that returns an integer between 0 and 100 to define the percentage progress of the task.
* `Compute Start End Dynamically Expression`: An expression that returns a boolean.
If true and if the task contains sub-tasks, the start and end dates displayed in the Gantt are computed from the sub-tasks.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
*******************************************************************************/
package org.eclipse.sirius.components.collaborative.gantt.dto.input;

import java.time.Instant;
import org.eclipse.sirius.components.gantt.TemporalType;

/**
* The input of the "Edit task" mutation.
*
* @author lfasani
*/
public record EditGanttTaskDetailInput(String name, String description, Instant startTime, Instant endTime, int progress) {
public record EditGanttTaskDetailInput(String name, String description, String startTime, String endTime, TemporalType temporalType, int progress) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
package org.eclipse.sirius.components.collaborative.gantt.service;

import java.text.MessageFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.temporal.Temporal;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
Expand All @@ -38,6 +41,7 @@
import org.eclipse.sirius.components.core.api.SuccessPayload;
import org.eclipse.sirius.components.gantt.Gantt;
import org.eclipse.sirius.components.gantt.Task;
import org.eclipse.sirius.components.gantt.TemporalType;
import org.eclipse.sirius.components.gantt.description.GanttDescription;
import org.eclipse.sirius.components.gantt.renderer.events.ChangeGanttColumnEvent;
import org.eclipse.sirius.components.gantt.renderer.events.ChangeGanttTaskCollapseStateEvent;
Expand Down Expand Up @@ -129,8 +133,8 @@ public IPayload editTask(EditGanttTaskInput editGanttTaskInput, IEditingContext
variableManager.put(IEditingContext.EDITING_CONTEXT, editingContext);
variableManager.put(GanttDescription.NEW_NAME, editGanttTaskInput.newDetail().name());
variableManager.put(GanttDescription.NEW_DESCRIPTION, editGanttTaskInput.newDetail().description());
variableManager.put(GanttDescription.NEW_START_TIME, editGanttTaskInput.newDetail().startTime());
variableManager.put(GanttDescription.NEW_END_TIME, editGanttTaskInput.newDetail().endTime());
variableManager.put(GanttDescription.NEW_START_TIME, getTemporal(editGanttTaskInput.newDetail().startTime(), editGanttTaskInput.newDetail().temporalType()));
variableManager.put(GanttDescription.NEW_END_TIME, getTemporal(editGanttTaskInput.newDetail().endTime(), editGanttTaskInput.newDetail().temporalType()));
variableManager.put(GanttDescription.NEW_PROGRESS, editGanttTaskInput.newDetail().progress());
ganttDescriptionOpt.get().editTaskProvider().accept(variableManager);

Expand All @@ -141,6 +145,17 @@ public IPayload editTask(EditGanttTaskInput editGanttTaskInput, IEditingContext
return payload;
}

private Temporal getTemporal(String temporalString, TemporalType temporalType) {
Temporal temporal = null;
if (TemporalType.DATE.equals(temporalType)) {
temporal = LocalDate.parse(temporalString);
} else if (TemporalType.DATE_TIME.equals(temporalType)) {
temporal = Instant.parse(temporalString);
}

return temporal;
}

private IPayload getPayload(UUID payloadId) {
IPayload payload = null;
List<Message> feedbackMessages = this.feedbackMessageService.getFeedbackMessages();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,17 @@ type Task {
taskDependencyIds: [String!]
}

enum TemporalType {
DATE_TIME
DATE
}

type TaskDetail {
name: String!
description: String!
startTime: Instant
endTime: Instant
startTime: String
endTime: String
temporalType: TemporalType
progress: Int!
computeStartEndDynamically: Boolean!
collapsed: Boolean!
Expand All @@ -71,12 +77,6 @@ type TaskStyle {
progressColor: String!
}

enum TaskType {
TASK
TASK_GROUP
MILESTONE
}

extend type Mutation {
createGanttTask(input: CreateGanttTaskInput!): CreateGanttTaskPayload
deleteGanttTask(input: DeleteGanttTaskInput!): DeleteGanttTaskPayload
Expand Down Expand Up @@ -107,7 +107,7 @@ union DeleteGanttTaskPayload = SuccessPayload | ErrorPayload
input EditGanttTaskDetailInput {
name: String
description: String
type: TaskType
temporalType: TemporalType
startTime: String
endTime: String
progress: Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@
*******************************************************************************/
package org.eclipse.sirius.components.gantt;

import java.time.Instant;
import java.util.Objects;

/**
* Detail of task concept.
*
* @author lfasani
*/
public record TaskDetail(String name, String description, Instant startTime, Instant endTime, int progress, boolean computeStartEndDynamically, boolean collapsed) {
public record TaskDetail(String name, String description, String startTime, String endTime, TemporalType temporalType, int progress, boolean computeStartEndDynamically, boolean collapsed) {

public TaskDetail {
Objects.requireNonNull(name);
Expand Down
Original file line number Diff line number Diff line change
@@ -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
*******************************************************************************/

package org.eclipse.sirius.components.gantt;

/**
* Enum representing a type of temporal
* @author lfasani
*/
public enum TemporalType {
DATE_TIME,
DATE
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
package org.eclipse.sirius.components.gantt.description;

import java.text.MessageFormat;
import java.time.Instant;
import java.time.temporal.Temporal;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
Expand All @@ -29,7 +29,7 @@
@PublicApi
public record TaskDescription(String id, Function<VariableManager, String> targetObjectIdProvider, Function<VariableManager, String> targetObjectKindProvider,
Function<VariableManager, String> targetObjectLabelProvider, Function<VariableManager, List<?>> semanticElementsProvider, Function<VariableManager, String> nameProvider,
Function<VariableManager, String> descriptionProvider, Function<VariableManager, Instant> startTimeProvider, Function<VariableManager, Instant> endTimeProvider,
Function<VariableManager, String> descriptionProvider, Function<VariableManager, Temporal> startTimeProvider, Function<VariableManager, Temporal> endTimeProvider,
Function<VariableManager, Integer> progressProvider, Function<VariableManager, Boolean> computeDatesDynamicallyProvider, Function<VariableManager, List<Object>> taskDependenciesProvider,
List<String> reusedTaskDescriptionIds, List<TaskDescription> subTaskDescriptions) {

Expand Down Expand Up @@ -82,9 +82,9 @@ public static final class Builder {

private Function<VariableManager, String> descriptionProvider;

private Function<VariableManager, Instant> startTimeProvider;
private Function<VariableManager, Temporal> startTimeProvider;

private Function<VariableManager, Instant> endTimeProvider;
private Function<VariableManager, Temporal> endTimeProvider;

private Function<VariableManager, Integer> progressProvider;

Expand Down Expand Up @@ -130,12 +130,12 @@ public Builder descriptionProvider(Function<VariableManager, String> description
return this;
}

public Builder startTimeProvider(Function<VariableManager, Instant> startTimeProvider) {
public Builder startTimeProvider(Function<VariableManager, Temporal> startTimeProvider) {
this.startTimeProvider = Objects.requireNonNull(startTimeProvider);
return this;
}

public Builder endTimeProvider(Function<VariableManager, Instant> endTimeProvider) {
public Builder endTimeProvider(Function<VariableManager, Temporal> endTimeProvider) {
this.endTimeProvider = Objects.requireNonNull(endTimeProvider);
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@
package org.eclipse.sirius.components.gantt.renderer;

import java.time.Instant;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.util.Comparator;
import java.util.List;

import org.eclipse.sirius.components.gantt.Gantt;
import org.eclipse.sirius.components.gantt.Task;
import org.eclipse.sirius.components.gantt.TaskDetail;
import org.eclipse.sirius.components.gantt.TemporalType;
import org.eclipse.sirius.components.gantt.renderer.elements.GanttElementProps;
import org.eclipse.sirius.components.gantt.renderer.elements.TaskElementProps;
import org.eclipse.sirius.components.representations.IElementFactory;
Expand Down Expand Up @@ -60,38 +64,64 @@ private Task instantiateTask(TaskElementProps props, List<Object> children) {
TaskDetail detail = props.detail();
if (detail.computeStartEndDynamically() && !subTasks.isEmpty()) {

Instant startTime = subTasks.stream()
.filter(task -> task.detail().startTime() != null)
.min(Comparator.comparing(task -> task.detail().startTime()))
String startTime = subTasks.stream()
.map(task -> task.detail().startTime())
.min(Comparator.comparing(timeString -> timeString))
.orElse(props.detail().startTime());

Instant endTime = subTasks.stream()
.filter(task -> task.detail().endTime() != null)
.max(Comparator.comparing(task -> task.detail().endTime()))
.map(task-> task.detail().endTime())
String endTime = subTasks.stream()
.map(task -> task.detail().endTime())
.max(Comparator.comparing(timeString -> timeString))
.orElse(props.detail().endTime());

// compute the ratio
var numerator = subTasks.stream()
.filter(task -> task.detail().startTime() != null && task.detail().endTime() != null)
.mapToLong(task -> task.detail().progress() * (task.detail().endTime().getEpochSecond() - task.detail().startTime().getEpochSecond()))
.mapToLong(task -> task.detail().progress() * getTemporal(task.detail().startTime(), task.detail().temporalType()).until(getTemporal(task.detail().endTime(), task.detail().temporalType()), ChronoUnit.SECONDS))
.sum();

var denominator = subTasks.stream()
.filter(task -> task.detail().startTime() != null && task.detail().endTime() != null)
.mapToLong(task -> task.detail().endTime().getEpochSecond() - task.detail().startTime().getEpochSecond())
.mapToLong(task -> getTemporal(task.detail().startTime(), task.detail().temporalType()).until(getTemporal(task.detail().endTime(), task.detail().temporalType()), ChronoUnit.SECONDS))
.sum();

int newProgress = 0;
if (denominator > 0) {
newProgress = (int) (numerator / denominator);
}

detail = new TaskDetail(detail.name(), detail.description(), startTime, endTime, newProgress, detail.computeStartEndDynamically(), detail.collapsed());
detail = new TaskDetail(detail.name(), detail.description(), startTime, endTime, getTemporalType(subTasks), newProgress, detail.computeStartEndDynamically(), detail.collapsed());
}

return new Task(props.id(), props.descriptionId(), props.targetObjectId(), props.targetObjectKind(), props.targetObjectLabel(), detail, props.dependencyObjectIds(), subTasks);
}

private Temporal getTemporal(String temporalString, TemporalType temporalType) {
Temporal temporal = null;
if (TemporalType.DATE.equals(temporalType)) {
temporal = LocalDate.parse(temporalString);
} else if (TemporalType.DATE_TIME.equals(temporalType)) {
temporal = Instant.parse(temporalString);
}

return temporal;
}

private TemporalType getTemporalType(List<Task> subTasks) {
return subTasks.stream()
.map(Task::detail)
.map(TaskDetail::temporalType)
.findFirst()
.orElse(null);
}

private TemporalType getTemporalType(Temporal startTime, Temporal endTime) {
TemporalType temporalType = null;
if (startTime instanceof Instant || endTime instanceof Instant) {
temporalType = TemporalType.DATE_TIME;
} else if (startTime instanceof LocalDate || endTime instanceof LocalDate) {
temporalType = TemporalType.DATE;
}
return temporalType;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
package org.eclipse.sirius.components.gantt.renderer.component;

import java.time.Instant;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
Expand All @@ -22,6 +25,7 @@

import org.eclipse.sirius.components.gantt.Task;
import org.eclipse.sirius.components.gantt.TaskDetail;
import org.eclipse.sirius.components.gantt.TemporalType;
import org.eclipse.sirius.components.gantt.description.TaskDescription;
import org.eclipse.sirius.components.gantt.renderer.elements.TaskElementProps;
import org.eclipse.sirius.components.gantt.renderer.events.ChangeGanttTaskCollapseStateEvent;
Expand Down Expand Up @@ -71,8 +75,8 @@ private Element doRender(VariableManager childVariableManager, String targetObje
TaskDescription taskDescription = this.props.taskDescription();
String name = taskDescription.nameProvider().apply(childVariableManager);
String description = taskDescription.descriptionProvider().apply(childVariableManager);
Instant startTime = taskDescription.startTimeProvider().apply(childVariableManager);
Instant endTime = taskDescription.endTimeProvider().apply(childVariableManager);
Temporal startTime = taskDescription.startTimeProvider().apply(childVariableManager);
Temporal endTime = taskDescription.endTimeProvider().apply(childVariableManager);
Integer progress = taskDescription.progressProvider().apply(childVariableManager);
Boolean computeDatesDynamicallyProvider = taskDescription.computeDatesDynamicallyProvider().apply(childVariableManager);
List<Object> dependencyObjects = taskDescription.taskDependenciesProvider().apply(childVariableManager);
Expand All @@ -96,11 +100,31 @@ private Element doRender(VariableManager childVariableManager, String targetObje

boolean collapsed = this.computeCollapsed(previousTaskOptional);

TaskDetail detail = new TaskDetail(name, description, startTime, endTime, progress, computeDatesDynamicallyProvider, collapsed);
TaskDetail detail = new TaskDetail(name, description, getTemporalString(startTime), getTemporalString(endTime), getTemporalType(startTime, endTime), progress, computeDatesDynamicallyProvider, collapsed);
TaskElementProps taskElementProps = new TaskElementProps(UUID.nameUUIDFromBytes(targetObjectId.getBytes()).toString(), taskDescription.id(), targetObjectId, targetObjectKind, targetObjectLabel, detail, dependencyObjectIds, childrenElements);
return new Element(TaskElementProps.TYPE, taskElementProps);
}

private TemporalType getTemporalType(Temporal startTime, Temporal endTime) {
TemporalType temporalType = null;
if (startTime instanceof Instant || endTime instanceof Instant) {
temporalType = TemporalType.DATE_TIME;
} else if (startTime instanceof LocalDate || endTime instanceof LocalDate) {
temporalType = TemporalType.DATE;
}
return temporalType;
}

private String getTemporalString(Temporal temporal) {
String temporalString = "";
if (temporal instanceof Instant instant) {
temporalString = DateTimeFormatter.ISO_INSTANT.format(instant);
} else if (temporal instanceof LocalDate localDate) {
temporalString = DateTimeFormatter.ISO_LOCAL_DATE.format(localDate);
}
return temporalString;
}

private List<Element> getChildren(VariableManager variableManager, TaskDescription taskDescription, Optional<Task> previousTaskOptional) {
List<Task> previousSubTasks = previousTaskOptional.map(Task::subTasks).orElseGet(ArrayList::new);
Stream<TaskDescription> childrenTaskDescription = Optional.ofNullable(taskDescription.subTaskDescriptions()).orElse(List.of()).stream();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@

import { Task, TaskOrEmpty } from '@ObeoNetwork/gantt-task-react';
import { GQLErrorPayload, GQLSuccessPayload } from '@eclipse-sirius/sirius-components-core';
import { GQLTaskDetail, TemporalType } from '../subscription/GanttSubscription.types';

export interface UseGanttMutations {
editTask: (task: TaskOrEmpty) => void;
editTask: (taskId: string, taskDetail: GQLTaskDetail) => void;
createTask: (task: Task) => void;
deleteTask: (tasks: readonly TaskOrEmpty[]) => void;
dropTask: (droppedTask: TaskOrEmpty, targetTask: TaskOrEmpty | undefined, dropIndex: number) => void;
Expand Down Expand Up @@ -48,6 +49,7 @@ export interface GQLEditGanttTaskDetailInput {
description: string;
startTime?: string;
endTime?: string;
temporalType?: TemporalType;
progress: number;
}
export interface GQLEditGanttTaskInput {
Expand Down
Loading

0 comments on commit fb29053

Please sign in to comment.