From fb290534bc034124a88ee9f175658caf940db380 Mon Sep 17 00:00:00 2001 From: Laurent Fasani Date: Wed, 28 Aug 2024 17:58:07 +0200 Subject: [PATCH] [3833] Enhance Gantt to support Date and DateTime Bug: https://github.com/eclipse-sirius/sirius-web/issues/3833 Signed-off-by: Laurent Fasani --- CHANGELOG.adoc | 1 + doc/specifier/representation-gantt.adoc | 5 +- .../dto/input/EditGanttTaskDetailInput.java | 4 +- .../gantt/service/GanttTaskService.java | 19 +++++- .../src/main/resources/schema/gantt.graphqls | 18 ++--- .../sirius/components/gantt/TaskDetail.java | 3 +- .../sirius/components/gantt/TemporalType.java | 23 +++++++ .../gantt/description/TaskDescription.java | 12 ++-- .../gantt/renderer/GanttElementFactory.java | 50 +++++++++++--- .../component/TaskDescriptionComponent.java | 30 ++++++++- .../graphql/mutation/GanttMutation.types.ts | 4 +- .../src/graphql/mutation/useGanttMutations.ts | 12 +--- .../subscription/GanttSubscription.types.ts | 4 ++ .../graphql/subscription/ganttSubscription.ts | 1 + .../src/helper/helper.tsx | 55 +++++++++++++-- .../representation/GanttRepresentation.tsx | 13 ++-- .../gantt/PapayaGanttDescriptionProvider.java | 8 +-- .../gantt/ViewGanttDescriptionConverter.java | 67 ++++++++----------- 18 files changed, 228 insertions(+), 101 deletions(-) create mode 100644 packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/TemporalType.java diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 700a7e43789..944c3466df6 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -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 diff --git a/doc/specifier/representation-gantt.adoc b/doc/specifier/representation-gantt.adoc index 5e62638b7d8..1b18deff22d 100644 --- a/doc/specifier/representation-gantt.adoc +++ b/doc/specifier/representation-gantt.adoc @@ -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 @@ -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. diff --git a/packages/gantt/backend/sirius-components-collaborative-gantt/src/main/java/org/eclipse/sirius/components/collaborative/gantt/dto/input/EditGanttTaskDetailInput.java b/packages/gantt/backend/sirius-components-collaborative-gantt/src/main/java/org/eclipse/sirius/components/collaborative/gantt/dto/input/EditGanttTaskDetailInput.java index 435c867fbf6..75cf8feed9e 100644 --- a/packages/gantt/backend/sirius-components-collaborative-gantt/src/main/java/org/eclipse/sirius/components/collaborative/gantt/dto/input/EditGanttTaskDetailInput.java +++ b/packages/gantt/backend/sirius-components-collaborative-gantt/src/main/java/org/eclipse/sirius/components/collaborative/gantt/dto/input/EditGanttTaskDetailInput.java @@ -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) { } diff --git a/packages/gantt/backend/sirius-components-collaborative-gantt/src/main/java/org/eclipse/sirius/components/collaborative/gantt/service/GanttTaskService.java b/packages/gantt/backend/sirius-components-collaborative-gantt/src/main/java/org/eclipse/sirius/components/collaborative/gantt/service/GanttTaskService.java index a479e9cefb4..90ec6c2837e 100644 --- a/packages/gantt/backend/sirius-components-collaborative-gantt/src/main/java/org/eclipse/sirius/components/collaborative/gantt/service/GanttTaskService.java +++ b/packages/gantt/backend/sirius-components-collaborative-gantt/src/main/java/org/eclipse/sirius/components/collaborative/gantt/service/GanttTaskService.java @@ -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; @@ -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; @@ -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); @@ -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 feedbackMessages = this.feedbackMessageService.getFeedbackMessages(); diff --git a/packages/gantt/backend/sirius-components-collaborative-gantt/src/main/resources/schema/gantt.graphqls b/packages/gantt/backend/sirius-components-collaborative-gantt/src/main/resources/schema/gantt.graphqls index 768aee222c6..2a416935466 100644 --- a/packages/gantt/backend/sirius-components-collaborative-gantt/src/main/resources/schema/gantt.graphqls +++ b/packages/gantt/backend/sirius-components-collaborative-gantt/src/main/resources/schema/gantt.graphqls @@ -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! @@ -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 @@ -107,7 +107,7 @@ union DeleteGanttTaskPayload = SuccessPayload | ErrorPayload input EditGanttTaskDetailInput { name: String description: String - type: TaskType + temporalType: TemporalType startTime: String endTime: String progress: Int diff --git a/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/TaskDetail.java b/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/TaskDetail.java index e53d1eecf6d..9d21685aea5 100644 --- a/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/TaskDetail.java +++ b/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/TaskDetail.java @@ -12,7 +12,6 @@ *******************************************************************************/ package org.eclipse.sirius.components.gantt; -import java.time.Instant; import java.util.Objects; /** @@ -20,7 +19,7 @@ * * @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); diff --git a/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/TemporalType.java b/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/TemporalType.java new file mode 100644 index 00000000000..1a2d16128c9 --- /dev/null +++ b/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/TemporalType.java @@ -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 +} diff --git a/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/description/TaskDescription.java b/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/description/TaskDescription.java index 609738aa9a2..9f6432ed902 100644 --- a/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/description/TaskDescription.java +++ b/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/description/TaskDescription.java @@ -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; @@ -29,7 +29,7 @@ @PublicApi public record TaskDescription(String id, Function targetObjectIdProvider, Function targetObjectKindProvider, Function targetObjectLabelProvider, Function> semanticElementsProvider, Function nameProvider, - Function descriptionProvider, Function startTimeProvider, Function endTimeProvider, + Function descriptionProvider, Function startTimeProvider, Function endTimeProvider, Function progressProvider, Function computeDatesDynamicallyProvider, Function> taskDependenciesProvider, List reusedTaskDescriptionIds, List subTaskDescriptions) { @@ -82,9 +82,9 @@ public static final class Builder { private Function descriptionProvider; - private Function startTimeProvider; + private Function startTimeProvider; - private Function endTimeProvider; + private Function endTimeProvider; private Function progressProvider; @@ -130,12 +130,12 @@ public Builder descriptionProvider(Function description return this; } - public Builder startTimeProvider(Function startTimeProvider) { + public Builder startTimeProvider(Function startTimeProvider) { this.startTimeProvider = Objects.requireNonNull(startTimeProvider); return this; } - public Builder endTimeProvider(Function endTimeProvider) { + public Builder endTimeProvider(Function endTimeProvider) { this.endTimeProvider = Objects.requireNonNull(endTimeProvider); return this; } diff --git a/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/renderer/GanttElementFactory.java b/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/renderer/GanttElementFactory.java index fdc816eecbb..90dff1d5006 100644 --- a/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/renderer/GanttElementFactory.java +++ b/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/renderer/GanttElementFactory.java @@ -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; @@ -60,27 +64,25 @@ private Task instantiateTask(TaskElementProps props, List 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; @@ -88,10 +90,38 @@ private Task instantiateTask(TaskElementProps props, List children) { 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 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; + } } diff --git a/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/renderer/component/TaskDescriptionComponent.java b/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/renderer/component/TaskDescriptionComponent.java index 71df9a2c1cd..8b36c9bf0c9 100644 --- a/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/renderer/component/TaskDescriptionComponent.java +++ b/packages/gantt/backend/sirius-components-gantt/src/main/java/org/eclipse/sirius/components/gantt/renderer/component/TaskDescriptionComponent.java @@ -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; @@ -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; @@ -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 dependencyObjects = taskDescription.taskDependenciesProvider().apply(childVariableManager); @@ -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 getChildren(VariableManager variableManager, TaskDescription taskDescription, Optional previousTaskOptional) { List previousSubTasks = previousTaskOptional.map(Task::subTasks).orElseGet(ArrayList::new); Stream childrenTaskDescription = Optional.ofNullable(taskDescription.subTaskDescriptions()).orElse(List.of()).stream(); diff --git a/packages/gantt/frontend/sirius-components-gantt/src/graphql/mutation/GanttMutation.types.ts b/packages/gantt/frontend/sirius-components-gantt/src/graphql/mutation/GanttMutation.types.ts index 576b14f5dec..1d9607ab133 100644 --- a/packages/gantt/frontend/sirius-components-gantt/src/graphql/mutation/GanttMutation.types.ts +++ b/packages/gantt/frontend/sirius-components-gantt/src/graphql/mutation/GanttMutation.types.ts @@ -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; @@ -48,6 +49,7 @@ export interface GQLEditGanttTaskDetailInput { description: string; startTime?: string; endTime?: string; + temporalType?: TemporalType; progress: number; } export interface GQLEditGanttTaskInput { diff --git a/packages/gantt/frontend/sirius-components-gantt/src/graphql/mutation/useGanttMutations.ts b/packages/gantt/frontend/sirius-components-gantt/src/graphql/mutation/useGanttMutations.ts index 7e2dc0a7b01..235190a6afd 100644 --- a/packages/gantt/frontend/sirius-components-gantt/src/graphql/mutation/useGanttMutations.ts +++ b/packages/gantt/frontend/sirius-components-gantt/src/graphql/mutation/useGanttMutations.ts @@ -92,20 +92,12 @@ export const useGanttMutations = (editingContextId: string, representationId: st ); useReporting(mutationEditTaskResult, (data: GQLEditTaskData) => data.editGanttTask); - const editTask = (task: TaskOrEmpty) => { - const newDetail: GQLTaskDetail = { - name: task.name, - description: '', - startTime: (task as Task)?.start?.toISOString(), - endTime: (task as Task)?.end?.toISOString(), - progress: (task as Task)?.progress, - computeStartEndDynamically: task.isDisabled, - }; + const editTask = (taskId: string, newDetail: GQLTaskDetail) => { const input: GQLEditGanttTaskInput = { id: crypto.randomUUID(), editingContextId, representationId, - taskId: task.id, + taskId, newDetail, }; diff --git a/packages/gantt/frontend/sirius-components-gantt/src/graphql/subscription/GanttSubscription.types.ts b/packages/gantt/frontend/sirius-components-gantt/src/graphql/subscription/GanttSubscription.types.ts index f7094d79a37..281f3ed7ee6 100644 --- a/packages/gantt/frontend/sirius-components-gantt/src/graphql/subscription/GanttSubscription.types.ts +++ b/packages/gantt/frontend/sirius-components-gantt/src/graphql/subscription/GanttSubscription.types.ts @@ -55,6 +55,8 @@ export enum GQLGanttDateRoundingTimeUnit { DAY, } +export type TemporalType = 'DATE' | 'DATE_TIME'; + export interface GQLTask { id: string; descriptionId: string; @@ -76,6 +78,7 @@ export interface SelectableTask extends Task { targetObjectId: string; targetObjectKind: string; targetObjectLabel: string; + temporalType?: TemporalType; } export interface SelectableEmptyTask extends EmptyTask { targetObjectId: string; @@ -88,6 +91,7 @@ export interface GQLTaskDetail { description: string; startTime?: string; endTime?: string; + temporalType?: TemporalType; progress: number; computeStartEndDynamically?: boolean; collapsed?: boolean; diff --git a/packages/gantt/frontend/sirius-components-gantt/src/graphql/subscription/ganttSubscription.ts b/packages/gantt/frontend/sirius-components-gantt/src/graphql/subscription/ganttSubscription.ts index 8ec0463d33c..987fb1b2387 100644 --- a/packages/gantt/frontend/sirius-components-gantt/src/graphql/subscription/ganttSubscription.ts +++ b/packages/gantt/frontend/sirius-components-gantt/src/graphql/subscription/ganttSubscription.ts @@ -68,6 +68,7 @@ export const ganttEventSubscription = gql` description startTime endTime + temporalType progress computeStartEndDynamically collapsed diff --git a/packages/gantt/frontend/sirius-components-gantt/src/helper/helper.tsx b/packages/gantt/frontend/sirius-components-gantt/src/helper/helper.tsx index 70c734763a5..b0298fa1d63 100644 --- a/packages/gantt/frontend/sirius-components-gantt/src/helper/helper.tsx +++ b/packages/gantt/frontend/sirius-components-gantt/src/helper/helper.tsx @@ -29,6 +29,7 @@ import { GQLTaskDetail, SelectableEmptyTask, SelectableTask, + TemporalType, } from '../graphql/subscription/GanttSubscription.types'; import { TaskListColumnEnum } from '../representation/Gantt.types'; @@ -54,8 +55,8 @@ export function getTaskFromGQLTask(gQLTasks: GQLTask[], parentId: string): TaskO task = { id: gQLTask.id, name: gQLTask.detail.name, - start: new Date(gQLTask.detail.startTime), - end: new Date(gQLTask.detail.endTime), + start: getDateFromString(gQLTask.detail.startTime, gQLTask.detail.temporalType, false), + end: getDateFromString(gQLTask.detail.endTime, gQLTask.detail.temporalType, true), progress: gQLTask.detail.progress, type, dependencies, @@ -64,6 +65,7 @@ export function getTaskFromGQLTask(gQLTasks: GQLTask[], parentId: string): TaskO targetObjectId: gQLTask.targetObjectId, targetObjectKind: gQLTask.targetObjectKind, targetObjectLabel: gQLTask.targetObjectLabel, + temporalType: gQLTask.detail.temporalType, }; } else { task = { @@ -89,6 +91,45 @@ export function getTaskFromGQLTask(gQLTasks: GQLTask[], parentId: string): TaskO return tasks; } +const getDateFromString = ( + dateTimeString: string, + temporalType: TemporalType | undefined, + isEndDate: boolean +): Date => { + if (temporalType === 'DATE') { + const date = new Date(dateTimeString); + if (isEndDate) { + date.setDate(date.getDate() + 1); + } + // beware of the hourly time shifting + date.setHours(0); + return date; + } + return new Date(dateTimeString); +}; + +export const formatDate = ( + date: Date, + temporalType: TemporalType | undefined, + isEndDate: boolean +): string | undefined => { + if (date && temporalType) { + if (temporalType === 'DATE') { + const realDate = new Date(date); + if (isEndDate) { + realDate.setDate(realDate.getDate() - 1); + } + const year = String(realDate.getFullYear()).padStart(4, '0'); + const month = String(realDate.getMonth() + 1).padStart(2, '0'); + const day = String(realDate.getDate()).padStart(2, '0'); + + return `${year}-${month}-${day}`; + } + return date.toISOString(); + } + return undefined; +}; + export const updateTask = (gantt: GQLGantt | null, taskId: string, newDetail: GQLTaskDetail) => { if (gantt?.tasks) { const task = findTask(gantt.tasks, taskId); @@ -131,9 +172,15 @@ const StartDateColumn: React.FC = ({ data: { task } }) => { const EndDateColumn: React.FC = ({ data: { task } }) => { if (task.type !== 'empty') { - return
{getFormattedDate(task.end)}
; + let endDate = new Date(task.end); + const selectableTask = task as SelectableTask; + if (selectableTask?.temporalType === 'DATE') { + if (task.end.getHours() == 0 && task.end.getMinutes() == 0) { + endDate.setDate(endDate.getDate() - 1); + } + } + return
{getFormattedDate(endDate)}
; } - return null; }; diff --git a/packages/gantt/frontend/sirius-components-gantt/src/representation/GanttRepresentation.tsx b/packages/gantt/frontend/sirius-components-gantt/src/representation/GanttRepresentation.tsx index 238ae5cb58d..568f7455a69 100644 --- a/packages/gantt/frontend/sirius-components-gantt/src/representation/GanttRepresentation.tsx +++ b/packages/gantt/frontend/sirius-components-gantt/src/representation/GanttRepresentation.tsx @@ -23,9 +23,10 @@ import { GQLGanttEventSubscription, GQLGanttRefreshedEventPayload, GQLTaskDetail, + SelectableTask, } from '../graphql/subscription/GanttSubscription.types'; import { ganttEventSubscription } from '../graphql/subscription/ganttSubscription'; -import { getTaskFromGQLTask, updateTask } from '../helper/helper'; +import { formatDate, getTaskFromGQLTask, updateTask } from '../helper/helper'; import { Gantt } from './Gantt'; import { GanttRepresentationState } from './GanttRepresentation.types'; const useGanttRepresentationStyles = makeStyles()((theme) => ({ @@ -125,16 +126,16 @@ export const GanttRepresentation = ({ editingContextId, representationId }: Repr const newDetail: GQLTaskDetail = { name: task.name, description: '', - startTime: (task as Task)?.start?.toISOString(), - endTime: (task as Task)?.end?.toISOString(), + startTime: formatDate((task as Task)?.start, (task as SelectableTask)?.temporalType, false), + endTime: formatDate((task as Task)?.end, (task as SelectableTask)?.temporalType, true), progress: (task as Task)?.progress, computeStartEndDynamically: task.isDisabled, - collapsed: (task as Task)?.hideChildren, + temporalType: (task as SelectableTask)?.temporalType, }; // to avoid blink because useMutation implies a re-render as the task value is the old one - updateTask(gantt, task.id, newDetail); - editTask(task); + updateTask(gantt, task.id, { ...newDetail, collapsed: (task as Task)?.hideChildren }); + editTask(task.id, newDetail); }; const onExpandCollapse = () => {}; diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/services/gantt/PapayaGanttDescriptionProvider.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/services/gantt/PapayaGanttDescriptionProvider.java index d41c6d019bd..1b80114d32d 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/services/gantt/PapayaGanttDescriptionProvider.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/services/gantt/PapayaGanttDescriptionProvider.java @@ -122,8 +122,8 @@ private TaskDescription createTaskDescriptionInProject() { .domainType("papaya::Iteration") .semanticCandidatesExpression("aql:self.iterations") .nameExpression("aql:self.name") - .startTimeExpression("aql:self.startTime") - .endTimeExpression("aql:self.endTime") + .startTimeExpression("aql:self.startDate") + .endTimeExpression("aql:self.endDate") .subTaskElementDescriptions(taskDescriptionInTask) .build(); } @@ -135,8 +135,8 @@ private TaskDescription createTaskDescriptionInTask() { .semanticCandidatesExpression("aql:self.tasks") .nameExpression("aql:self.name") .descriptionExpression("aql:self.description") - .startTimeExpression("aql:self.startTime") - .endTimeExpression("aql:self.endTime") + .startTimeExpression("aql:self.startDate") + .endTimeExpression("aql:self.endDate") .progressExpression("aql:self.progress") .taskDependenciesExpression("aql:self.dependencies") .build(); diff --git a/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/gantt/ViewGanttDescriptionConverter.java b/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/gantt/ViewGanttDescriptionConverter.java index cfd608c4f36..c0df44274bd 100644 --- a/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/gantt/ViewGanttDescriptionConverter.java +++ b/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/gantt/ViewGanttDescriptionConverter.java @@ -13,7 +13,9 @@ package org.eclipse.sirius.components.view.emf.gantt; import java.time.Instant; +import java.time.LocalDate; import java.time.format.DateTimeParseException; +import java.time.temporal.Temporal; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; @@ -145,50 +147,12 @@ private TaskDescription convert(org.eclipse.sirius.components.view.gantt.TaskDes .map(viewTaskDesc -> this.convert(viewTaskDesc, interpreter, taskDescription2Ids)) .toList(); - Function startTimeProvider = variableManager -> { - Instant result = null; - - var optionalObject = interpreter.evaluateExpression(variableManager.getVariables(), viewTaskDescription.getStartTimeExpression()).asObject(); - if (optionalObject.isPresent()) { - var object = optionalObject.get(); - if (object instanceof Instant instant) { - result = instant; - } else if (object instanceof String string) { - try { - result = Instant.parse(string); - } catch (DateTimeParseException exception) { - // Not logged on purpose, the user can enter anything - } - } - } - return result; - }; - - Function endTimeProvider = variableManager -> { - Instant result = null; - - var optionalObject = interpreter.evaluateExpression(variableManager.getVariables(), viewTaskDescription.getEndTimeExpression()).asObject(); - if (optionalObject.isPresent()) { - var object = optionalObject.get(); - if (object instanceof Instant instant) { - result = instant; - } else if (object instanceof String string) { - try { - result = Instant.parse(string); - } catch (DateTimeParseException exception) { - // Not logged on purpose, the user can enter anything - } - } - } - return result; - }; - TaskDescription taskDescription = TaskDescription.newTaskDescription(taskDescription2Ids.get(viewTaskDescription)) .semanticElementsProvider(variableManager -> this.getSemanticCandidateElements(variableManager, interpreter, viewTaskDescription)) .nameProvider(variableManager -> this.evaluateExpression(variableManager, interpreter, viewTaskDescription.getNameExpression(), String.class, "")) .descriptionProvider(variableManager -> this.evaluateExpression(variableManager, interpreter, viewTaskDescription.getDescriptionExpression(), String.class, "")) - .startTimeProvider(startTimeProvider) - .endTimeProvider(endTimeProvider) + .startTimeProvider(variableManager -> this.getTemporalFromExpression(variableManager, interpreter, viewTaskDescription.getStartTimeExpression())) + .endTimeProvider(variableManager -> this.getTemporalFromExpression(variableManager, interpreter, viewTaskDescription.getEndTimeExpression())) .progressProvider(variableManager -> this.evaluateExpression(variableManager, interpreter, viewTaskDescription.getProgressExpression(), Integer.class, 0)) .computeStartEndDynamicallyProvider(variableManager -> this.evaluateExpression(variableManager, interpreter, viewTaskDescription.getComputeStartEndDynamicallyExpression(), Boolean.class, false)) .taskDependenciesProvider(variableManager -> this.getTaskDependencies(variableManager, interpreter, viewTaskDescription.getTaskDependenciesExpression())) @@ -201,6 +165,29 @@ private TaskDescription convert(org.eclipse.sirius.components.view.gantt.TaskDes return taskDescription; } + private Temporal getTemporalFromExpression(VariableManager variableManager, AQLInterpreter interpreter, String expression) { + Temporal result = null; + + var optionalObject = interpreter.evaluateExpression(variableManager.getVariables(), expression).asObject(); + if (optionalObject.isPresent()) { + var object = optionalObject.get(); + if (object instanceof Temporal temporal) { + result = temporal; + } else if (object instanceof String string) { + try { + result = Instant.parse(string); + } catch (DateTimeParseException e) { + try { + result = LocalDate.parse(string); + } catch (DateTimeParseException e2) { + } + } + } + } + return result; + } + + private T evaluateExpression(VariableManager variableManager, AQLInterpreter interpreter, String expression, Class type, T defaultValue) { T value = interpreter.evaluateExpression(variableManager.getVariables(), expression) .asObject()