From ddf2699348d2156a70c941d99074ea92e3612d06 Mon Sep 17 00:00:00 2001 From: Boubaker Khanfir Date: Tue, 3 Feb 2026 08:57:44 +0100 Subject: [PATCH] feat: Allow to Retrieve Tasks Ordered by Last Activity - EXO-84071 - Meeds-io/ai#65 This change will allow to retrieve the list of Tasks ordered by the last Activity including Comment Dates, ChangeLog Dates and Task Creation Date. --- .../exoplatform/task/dao/CommentHandler.java | 6 ++ .../org/exoplatform/task/dao/TaskHandler.java | 11 +++- .../task/dao/jpa/CommentDAOImpl.java | 14 ++++- .../task/dao/jpa/CommonJPADAO.java | 42 +++++++++++-- .../exoplatform/task/dao/jpa/TaskDAOImpl.java | 6 ++ .../exoplatform/task/domain/ChangeLog.java | 12 +++- .../org/exoplatform/task/domain/Comment.java | 5 +- .../org/exoplatform/task/domain/Task.java | 3 +- .../exoplatform/task/dto/ChangeLogEntry.java | 4 +- .../task/service/CommentService.java | 6 ++ .../exoplatform/task/service/TaskService.java | 11 ++++ .../task/service/impl/CommentServiceImpl.java | 6 ++ .../task/service/impl/TaskServiceImpl.java | 5 ++ .../task/storage/CommentStorage.java | 6 ++ .../exoplatform/task/storage/TaskStorage.java | 14 ++++- .../task/storage/impl/CommentStorageImpl.java | 5 ++ .../task/storage/impl/TaskStorageImpl.java | 15 ++++- .../exoplatform/task/util/StorageUtil.java | 5 +- .../resources/conf/portal/configuration.xml | 1 + .../db/changelog/task.db.changelog-7.2.0.xml | 61 +++++++++++++++++++ 20 files changed, 218 insertions(+), 20 deletions(-) create mode 100644 services/src/main/resources/db/changelog/task.db.changelog-7.2.0.xml diff --git a/services/src/main/java/org/exoplatform/task/dao/CommentHandler.java b/services/src/main/java/org/exoplatform/task/dao/CommentHandler.java index 909730871..a180a9f9e 100644 --- a/services/src/main/java/org/exoplatform/task/dao/CommentHandler.java +++ b/services/src/main/java/org/exoplatform/task/dao/CommentHandler.java @@ -34,4 +34,10 @@ public interface CommentHandler extends GenericDAO { int countCommentsWithSubs(long taskId); + /** + * @param taskId Task Technical identifier + * @return null if no comments else last created comment on task + */ + Comment getLastComment(long taskId); + } diff --git a/services/src/main/java/org/exoplatform/task/dao/TaskHandler.java b/services/src/main/java/org/exoplatform/task/dao/TaskHandler.java index a998ed437..aeb69bfff 100644 --- a/services/src/main/java/org/exoplatform/task/dao/TaskHandler.java +++ b/services/src/main/java/org/exoplatform/task/dao/TaskHandler.java @@ -108,5 +108,14 @@ public interface TaskHandler extends GenericDAO { * @return a list of task IDs */ List getAllIds(int offset, int limit); -} + /** + * Find tasks switch last update date computed from Task Logs and Task + * Comments after applying the {@link TaskQuery} filtering + * + * @param query {@link TaskQuery} + * @return {@link List} of corresponding Tasks + */ + ListAccess findLastUpdatedTasks(TaskQuery query); + +} diff --git a/services/src/main/java/org/exoplatform/task/dao/jpa/CommentDAOImpl.java b/services/src/main/java/org/exoplatform/task/dao/jpa/CommentDAOImpl.java index 524570421..1610a0f31 100644 --- a/services/src/main/java/org/exoplatform/task/dao/jpa/CommentDAOImpl.java +++ b/services/src/main/java/org/exoplatform/task/dao/jpa/CommentDAOImpl.java @@ -18,7 +18,7 @@ */ package org.exoplatform.task.dao.jpa; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.exoplatform.commons.api.persistence.ExoTransactional; import org.exoplatform.commons.utils.ListAccess; @@ -70,6 +70,18 @@ public ListAccess findComments(long taskId) { return new JPAQueryListAccess(Comment.class, count, query); } + @Override + public Comment getLastComment(long taskId) { + TypedQuery query = getEntityManager().createNamedQuery("Comment.findLastCommentOfTask", Comment.class); + query.setParameter("taskId", taskId); + List result = query.getResultList(); + if (CollectionUtils.isEmpty(result)) { + return null; + } else { + return result.get(0); + } + } + @Override @ExoTransactional public List getSubComments(List comments) { diff --git a/services/src/main/java/org/exoplatform/task/dao/jpa/CommonJPADAO.java b/services/src/main/java/org/exoplatform/task/dao/jpa/CommonJPADAO.java index 02ba4c4f4..839129610 100644 --- a/services/src/main/java/org/exoplatform/task/dao/jpa/CommonJPADAO.java +++ b/services/src/main/java/org/exoplatform/task/dao/jpa/CommonJPADAO.java @@ -22,6 +22,7 @@ import jakarta.persistence.Tuple; import jakarta.persistence.TypedQuery; import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaBuilder.Case; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.Join; @@ -31,10 +32,12 @@ import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; import jakarta.persistence.criteria.Selection; +import jakarta.persistence.criteria.Subquery; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -46,6 +49,9 @@ import org.exoplatform.task.dao.condition.AggregateCondition; import org.exoplatform.task.dao.condition.Condition; import org.exoplatform.task.dao.condition.SingleCondition; +import org.exoplatform.task.domain.ChangeLog; +import org.exoplatform.task.domain.Comment; +import org.exoplatform.task.domain.Task; public abstract class CommonJPADAO extends GenericDAOJPAImpl { @@ -116,17 +122,45 @@ public Class loadClass(String name) throws ClassNotFoundException { List orderby = query.getOrderBy(); if(orderby != null && !orderby.isEmpty()) { List orders = new ArrayList<>(orderby.size()); - for(OrderBy orderBy : orderby) { - Expression p = root.get(orderBy.getFieldName()); + for (OrderBy orderBy : orderby) { Order order; - if (orderBy.isAscending()) { + if (orderBy.getFieldName().equals("lastTaskActivity")) { + Subquery maxCommentSq = q.subquery(Date.class); + Root c = maxCommentSq.from(Comment.class); + + maxCommentSq.select(cb.greatest(c. get("createdTime"))); + maxCommentSq.where(cb.equal(c.get("task"), root)); + + Subquery maxLogSq = q.subquery(Date.class); + Root l = maxLogSq.from(ChangeLog.class); + + maxLogSq.select(cb.greatest(l. get("createdTime"))); + maxLogSq.where(cb.equal(l.get("task"), root)); + + Expression taskCreated = root.get("createdTime"); + Expression maxCommentTime = cb.coalesce(maxCommentSq.getSelection(), taskCreated); + Expression maxLogTime = cb.coalesce(maxLogSq.getSelection(), taskCreated); + + Case tmpSelectCase = cb.selectCase(); + Expression tmpMax = tmpSelectCase.when(cb.greaterThan(taskCreated, maxCommentTime), taskCreated) + .otherwise(maxCommentTime); + + tmpSelectCase = cb.selectCase(); + Expression lastActivity = tmpSelectCase.when(cb.greaterThan(tmpMax, maxLogTime), tmpMax) + .otherwise(maxLogTime); + + selections.add(lastActivity); + + order = orderBy.isAscending() ? cb.asc(lastActivity) : cb.desc(lastActivity); + } else if (orderBy.isAscending()) { + Expression p = root.get(orderBy.getFieldName()); // NULL value should be at last when order by Expression nullCase = cb.selectCase().when(p.isNull(), 1).otherwise(0); selections.add(nullCase); orders.add(cb.asc(nullCase)); - order = cb.asc(p); } else { + Expression p = root.get(orderBy.getFieldName()); order = cb.desc(p); } orders.add(order); diff --git a/services/src/main/java/org/exoplatform/task/dao/jpa/TaskDAOImpl.java b/services/src/main/java/org/exoplatform/task/dao/jpa/TaskDAOImpl.java index cc7317bee..bb3b9748e 100644 --- a/services/src/main/java/org/exoplatform/task/dao/jpa/TaskDAOImpl.java +++ b/services/src/main/java/org/exoplatform/task/dao/jpa/TaskDAOImpl.java @@ -87,6 +87,12 @@ public ListAccess findTasks(TaskQuery query) { return findEntities(query, Task.class); } + @Override + public ListAccess findLastUpdatedTasks(TaskQuery query) { + query.setOrderBy(List.of(new OrderBy("lastTaskActivity", false))); + return findEntities(query, Task.class); + } + @Override public List selectTaskField(TaskQuery query, String fieldName) { EntityManager em = getEntityManager(); diff --git a/services/src/main/java/org/exoplatform/task/domain/ChangeLog.java b/services/src/main/java/org/exoplatform/task/domain/ChangeLog.java index 4a1dc3203..dd9a25e25 100644 --- a/services/src/main/java/org/exoplatform/task/domain/ChangeLog.java +++ b/services/src/main/java/org/exoplatform/task/domain/ChangeLog.java @@ -18,6 +18,9 @@ */ package org.exoplatform.task.domain; +import java.util.Calendar; +import java.util.Date; + import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -28,6 +31,8 @@ import jakarta.persistence.NamedQuery; import jakarta.persistence.SequenceGenerator; import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import lombok.Data; @Entity(name = "TaskChangeLog") @@ -58,12 +63,13 @@ public class ChangeLog implements Comparable { private String target; - @Column(name="CREATED_TIME") - private long createdTime = System.currentTimeMillis(); + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "CREATED_TIME") + private Date createdTime = Calendar.getInstance().getTime(); @Override public int compareTo(ChangeLog o) { - return (int)(getCreatedTime() - o.getCreatedTime()); + return getCreatedTime().compareTo(o.getCreatedTime()); } @Override diff --git a/services/src/main/java/org/exoplatform/task/domain/Comment.java b/services/src/main/java/org/exoplatform/task/domain/Comment.java index 2e3fc9060..8e78fc28d 100644 --- a/services/src/main/java/org/exoplatform/task/domain/Comment.java +++ b/services/src/main/java/org/exoplatform/task/domain/Comment.java @@ -19,6 +19,7 @@ package org.exoplatform.task.domain; import java.util.ArrayList; +import java.util.Calendar; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -52,6 +53,8 @@ query = "SELECT count(c) FROM TaskComment c WHERE c.task.id = :taskId") @NamedQuery(name = "Comment.findCommentsOfTask", query = "SELECT c FROM TaskComment c WHERE c.task.id = :taskId AND c.parentComment IS NULL ORDER BY c.createdTime DESC") +@NamedQuery(name = "Comment.findLastCommentOfTask", + query = "SELECT c FROM TaskComment c WHERE c.task.id = :taskId ORDER BY c.createdTime DESC") @NamedQuery(name = "Comment.findSubCommentsOfComments", query = "SELECT c FROM TaskComment c WHERE c.parentComment IN (:comments) ORDER BY c.createdTime ASC") @NamedQuery(name = "Comment.deleteCommentOfTask", @@ -78,7 +81,7 @@ public class Comment { @Temporal(TemporalType.TIMESTAMP) @Column(name = "CREATED_TIME") - private Date createdTime; + private Date createdTime = Calendar.getInstance().getTime(); @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "TASK_ID") diff --git a/services/src/main/java/org/exoplatform/task/domain/Task.java b/services/src/main/java/org/exoplatform/task/domain/Task.java index 2c533ec8a..0b23ccc68 100755 --- a/services/src/main/java/org/exoplatform/task/domain/Task.java +++ b/services/src/main/java/org/exoplatform/task/domain/Task.java @@ -19,6 +19,7 @@ package org.exoplatform.task.domain; import java.io.Serializable; +import java.util.Calendar; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -206,7 +207,7 @@ public class Task implements Serializable { @Temporal(TemporalType.TIMESTAMP) @Column(name = "CREATED_TIME") - private Date createdTime; + private Date createdTime = Calendar.getInstance().getTime(); @Temporal(TemporalType.TIMESTAMP) @Column(name = "START_DATE") diff --git a/services/src/main/java/org/exoplatform/task/dto/ChangeLogEntry.java b/services/src/main/java/org/exoplatform/task/dto/ChangeLogEntry.java index e9907b09e..0d6ad22d3 100644 --- a/services/src/main/java/org/exoplatform/task/dto/ChangeLogEntry.java +++ b/services/src/main/java/org/exoplatform/task/dto/ChangeLogEntry.java @@ -69,8 +69,8 @@ public ChangeLogEntry(ChangeLog changeLog,UserService userService) { this.targetFullName = userService.loadUser(changeLog.getTarget()).getDisplayName(); - this.createdTime = changeLog.getCreatedTime(); - } + this.createdTime = changeLog.getCreatedTime() == null ? 0 : changeLog.getCreatedTime().getTime(); + } public long getId() { return id; diff --git a/services/src/main/java/org/exoplatform/task/service/CommentService.java b/services/src/main/java/org/exoplatform/task/service/CommentService.java index bc6d7cd37..0efab0745 100644 --- a/services/src/main/java/org/exoplatform/task/service/CommentService.java +++ b/services/src/main/java/org/exoplatform/task/service/CommentService.java @@ -55,4 +55,10 @@ public interface CommentService { * @return {@link Integer} */ int countCommentsWithSubs(long taskId); + + /** + * @param taskId Task identifier + * @return null if no comments else last created comment on task + */ + CommentDto getLastComment(long taskId); } diff --git a/services/src/main/java/org/exoplatform/task/service/TaskService.java b/services/src/main/java/org/exoplatform/task/service/TaskService.java index 27607fd7f..02dce51be 100644 --- a/services/src/main/java/org/exoplatform/task/service/TaskService.java +++ b/services/src/main/java/org/exoplatform/task/service/TaskService.java @@ -192,4 +192,15 @@ public interface TaskService { * @return a list of task IDs */ public List findTasks(TaskSearchFilter taskFilter); + + /** + * Find tasks switch last update date computed from Task Logs and Task + * Comments after applying the {@link TaskQuery} filtering + * + * @param query {@link TaskQuery} + * @param offset result offset + * @param limit result limit + * @return {@link List} of corresponding Tasks + */ + List findLastUpdatedTasks(TaskQuery query, int offset, int limit); } diff --git a/services/src/main/java/org/exoplatform/task/service/impl/CommentServiceImpl.java b/services/src/main/java/org/exoplatform/task/service/impl/CommentServiceImpl.java index 91baf41b8..72355d073 100644 --- a/services/src/main/java/org/exoplatform/task/service/impl/CommentServiceImpl.java +++ b/services/src/main/java/org/exoplatform/task/service/impl/CommentServiceImpl.java @@ -33,6 +33,7 @@ import org.exoplatform.task.exception.EntityNotFoundException; import org.exoplatform.task.service.CommentService; import org.exoplatform.task.storage.CommentStorage; +import org.exoplatform.task.util.StorageUtil; public class CommentServiceImpl implements CommentService { private static final Log LOG = ExoLogger.getExoLogger(CommentServiceImpl.class); @@ -71,6 +72,11 @@ public int countComments(long taskId) { return commentStorage.countComments(taskId); } + @Override + public CommentDto getLastComment(long taskId) { + return commentStorage.getLastComment(taskId); + } + @Override public List loadSubComments(List listComments) { if (listComments == null || listComments.isEmpty()) { diff --git a/services/src/main/java/org/exoplatform/task/service/impl/TaskServiceImpl.java b/services/src/main/java/org/exoplatform/task/service/impl/TaskServiceImpl.java index a731ebd4e..ab97cd958 100644 --- a/services/src/main/java/org/exoplatform/task/service/impl/TaskServiceImpl.java +++ b/services/src/main/java/org/exoplatform/task/service/impl/TaskServiceImpl.java @@ -161,6 +161,11 @@ public List findTasks(TaskQuery query, int offset, int limit) throws Ex return taskStorage.findTasks(query, offset, limit); } + @Override + public List findLastUpdatedTasks(TaskQuery query, int offset, int limit) { + return taskStorage.findLastUpdatedTasks(query, offset, limit); + } + @Override public int countTasks(TaskQuery query) throws Exception { return taskStorage.countTasks(query); diff --git a/services/src/main/java/org/exoplatform/task/storage/CommentStorage.java b/services/src/main/java/org/exoplatform/task/storage/CommentStorage.java index 43b5972b0..27640f397 100644 --- a/services/src/main/java/org/exoplatform/task/storage/CommentStorage.java +++ b/services/src/main/java/org/exoplatform/task/storage/CommentStorage.java @@ -56,4 +56,10 @@ public interface CommentStorage { */ int countCommentsWithSubs(long taskId); + /** + * @param taskId Task identifier + * @return null if no comments else last created comment on task + */ + CommentDto getLastComment(long taskId); + } diff --git a/services/src/main/java/org/exoplatform/task/storage/TaskStorage.java b/services/src/main/java/org/exoplatform/task/storage/TaskStorage.java index 43968b469..c279411f1 100644 --- a/services/src/main/java/org/exoplatform/task/storage/TaskStorage.java +++ b/services/src/main/java/org/exoplatform/task/storage/TaskStorage.java @@ -18,12 +18,9 @@ */ package org.exoplatform.task.storage; -import org.exoplatform.commons.utils.ListAccess; import org.exoplatform.task.dao.OrderBy; import org.exoplatform.task.dao.TaskQuery; -import org.exoplatform.task.domain.ChangeLog; import org.exoplatform.task.domain.Status; -import org.exoplatform.task.domain.Task; import org.exoplatform.task.dto.ChangeLogEntry; import org.exoplatform.task.dto.LabelDto; import org.exoplatform.task.dto.TaskDto; @@ -175,4 +172,15 @@ List findTasksByLabel(LabelDto label, * @return {@link List} of {@link TaskDto} */ List findTasks(TaskSearchFilter filter); + + /** + * Find tasks switch last update date computed from Task Logs and Task + * Comments after applying the {@link TaskQuery} filtering + * + * @param query {@link TaskQuery} + * @param offset result offset + * @param limit result limit + * @return {@link List} of corresponding Tasks + */ + List findLastUpdatedTasks(TaskQuery query, int offset, int limit); } diff --git a/services/src/main/java/org/exoplatform/task/storage/impl/CommentStorageImpl.java b/services/src/main/java/org/exoplatform/task/storage/impl/CommentStorageImpl.java index 0213c0d4c..6894ca27a 100644 --- a/services/src/main/java/org/exoplatform/task/storage/impl/CommentStorageImpl.java +++ b/services/src/main/java/org/exoplatform/task/storage/impl/CommentStorageImpl.java @@ -60,6 +60,11 @@ public CommentDto getComment(long commentId) { return StorageUtil.commentToDto(daoHandler.getCommentHandler().find(commentId),projectStorage); } + @Override + public CommentDto getLastComment(long taskId) { + return StorageUtil.commentToDto(daoHandler.getCommentHandler().getLastComment(taskId), projectStorage); + } + @Override public List getCommentsWithSubs(long taskId, int offset, int limit) { try { diff --git a/services/src/main/java/org/exoplatform/task/storage/impl/TaskStorageImpl.java b/services/src/main/java/org/exoplatform/task/storage/impl/TaskStorageImpl.java index f80405783..9763db082 100644 --- a/services/src/main/java/org/exoplatform/task/storage/impl/TaskStorageImpl.java +++ b/services/src/main/java/org/exoplatform/task/storage/impl/TaskStorageImpl.java @@ -146,8 +146,19 @@ public List findByUser(String user) { @Override public List findTasks(TaskQuery query, int offset, int limit) throws Exception { - List taskEntities = Arrays.asList(daoHandler.getTaskHandler().findTasks(query).load(offset, limit)); - return taskEntities.stream().map((Task taskEntity) -> StorageUtil.taskToDto(taskEntity,projectStorage)).collect(Collectors.toList()); + List taskEntities = Arrays.asList(daoHandler.getTaskHandler().findTasks(query).load(offset, limit)); + return taskEntities.stream() + .map((Task taskEntity) -> StorageUtil.taskToDto(taskEntity, projectStorage)) + .collect(Collectors.toList()); + } + + @Override + @SneakyThrows + public List findLastUpdatedTasks(TaskQuery query, int offset, int limit) { + List taskEntities = Arrays.asList(daoHandler.getTaskHandler().findLastUpdatedTasks(query).load(offset, limit)); + return taskEntities.stream() + .map((Task taskEntity) -> StorageUtil.taskToDto(taskEntity, projectStorage)) + .toList(); } public int countTasks(TaskQuery query) throws Exception { diff --git a/services/src/main/java/org/exoplatform/task/util/StorageUtil.java b/services/src/main/java/org/exoplatform/task/util/StorageUtil.java index 5c8eb65fe..edcc035b9 100644 --- a/services/src/main/java/org/exoplatform/task/util/StorageUtil.java +++ b/services/src/main/java/org/exoplatform/task/util/StorageUtil.java @@ -36,6 +36,7 @@ import org.exoplatform.task.storage.ProjectStorage; import java.util.Arrays; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -52,7 +53,7 @@ public static ChangeLog changeLogToEntity(ChangeLogEntry changeLogEntry, UserSer changeLog.setTask(changeLogEntry.getTask()); changeLog.setAuthor(changeLogEntry.getAuthor()); changeLog.setActionName(changeLogEntry.getActionName()); - changeLog.setCreatedTime(changeLogEntry.getCreatedTime()); + changeLog.setCreatedTime(new Date(changeLogEntry.getCreatedTime())); changeLog.setTarget(changeLogEntry.getTarget()); return changeLog; } @@ -63,7 +64,7 @@ public static ChangeLogEntry changeLogToDto(ChangeLog changeLog, UserService use changeLogEntry.setTask(changeLog.getTask()); changeLogEntry.setAuthor(changeLog.getAuthor()); changeLogEntry.setActionName(changeLog.getActionName()); - changeLogEntry.setCreatedTime(changeLog.getCreatedTime()); + changeLogEntry.setCreatedTime(changeLog.getCreatedTime() == null ? 0 : changeLog.getCreatedTime().getTime()); changeLogEntry.setTarget(changeLog.getTarget()); changeLogEntry.setAuthorFullName(userService.loadUser(changeLog.getAuthor()).getDisplayName()); changeLogEntry.setAuthorAvatarUrl(userService.loadUser(changeLog.getAuthor()).getAvatar()); diff --git a/services/src/main/resources/conf/portal/configuration.xml b/services/src/main/resources/conf/portal/configuration.xml index 053d6a6e6..c66ef9b1d 100644 --- a/services/src/main/resources/conf/portal/configuration.xml +++ b/services/src/main/resources/conf/portal/configuration.xml @@ -123,6 +123,7 @@ db/changelog/task.db.changelog-3.0.0.xml db/changelog/task.db.changelog-3.1.0.xml db/changelog/task.db.changelog-3.2.0.xml + db/changelog/task.db.changelog-7.2.0.xml diff --git a/services/src/main/resources/db/changelog/task.db.changelog-7.2.0.xml b/services/src/main/resources/db/changelog/task.db.changelog-7.2.0.xml new file mode 100644 index 000000000..183e03557 --- /dev/null +++ b/services/src/main/resources/db/changelog/task.db.changelog-7.2.0.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file