mirror of https://github.com/halo-dev/halo
refactor: add creationTime field for comment extension to resolve migration issue (#3341)
#### What type of PR is this? /kind improvement /area core /milestone 2.3.x /kind api-change #### What this PR does / why we need it: 1. spec 中新增 creationTime 2. 旧数据的 spec.creationTime 默认等于 approvedTime 3. 按照 metadata.creationTimestamp 排序的使用 spec.creationTime 代替 how to test it? 1. 使用迁移插件迁移看评论和回复的排序是否正确 2. 使用评论插件创建评论和回复看顺序是否正确 #### Which issue(s) this PR fixes: Fixes #3330 #### Special notes for your reviewer: /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? ```release-note 评论和回复新增创建时间以兼容迁移数据的排序 ```pull/3379/head
parent
6c2064f1e0
commit
3a1587bab5
|
@ -93,6 +93,9 @@ public class CommentServiceImpl implements CommentService {
|
||||||
if (comment.getSpec().getOwner() != null) {
|
if (comment.getSpec().getOwner() != null) {
|
||||||
return Mono.just(comment);
|
return Mono.just(comment);
|
||||||
}
|
}
|
||||||
|
if (comment.getSpec().getCreationTime() == null) {
|
||||||
|
comment.getSpec().setCreationTime(Instant.now());
|
||||||
|
}
|
||||||
// populate owner from current user
|
// populate owner from current user
|
||||||
return fetchCurrentUser()
|
return fetchCurrentUser()
|
||||||
.map(this::toCommentOwner)
|
.map(this::toCommentOwner)
|
||||||
|
|
|
@ -30,12 +30,12 @@ public enum CommentSorter {
|
||||||
|
|
||||||
static Comparator<Comment> from(CommentSorter sorter) {
|
static Comparator<Comment> from(CommentSorter sorter) {
|
||||||
if (sorter == null) {
|
if (sorter == null) {
|
||||||
return defaultCommentComparator();
|
return lastReplyTimeComparator();
|
||||||
}
|
}
|
||||||
if (CREATE_TIME.equals(sorter)) {
|
if (CREATE_TIME.equals(sorter)) {
|
||||||
Function<Comment, Instant> comparatorFunc =
|
Function<Comment, Instant> comparatorFunc =
|
||||||
comment -> comment.getMetadata().getCreationTimestamp();
|
comment -> comment.getSpec().getCreationTime();
|
||||||
return Comparator.comparing(comparatorFunc)
|
return Comparator.comparing(comparatorFunc, Comparators.nullsLow())
|
||||||
.thenComparing(name);
|
.thenComparing(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,12 +65,12 @@ public enum CommentSorter {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Comparator<Comment> defaultCommentComparator() {
|
static Comparator<Comment> lastReplyTimeComparator() {
|
||||||
Function<Comment, Instant> comparatorFunc =
|
Function<Comment, Instant> comparatorFunc =
|
||||||
comment -> {
|
comment -> {
|
||||||
Instant lastReplyTime = comment.getStatusOrDefault().getLastReplyTime();
|
Instant lastReplyTime = comment.getStatusOrDefault().getLastReplyTime();
|
||||||
return Optional.ofNullable(
|
return Optional.ofNullable(lastReplyTime)
|
||||||
lastReplyTime).orElse(comment.getMetadata().getCreationTimestamp());
|
.orElse(comment.getSpec().getCreationTime());
|
||||||
};
|
};
|
||||||
return Comparator.comparing(comparatorFunc, Comparators.nullsLow())
|
return Comparator.comparing(comparatorFunc, Comparators.nullsLow())
|
||||||
.thenComparing(name);
|
.thenComparing(name);
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package run.halo.app.content.comment;
|
package run.halo.app.content.comment;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import org.springframework.util.comparator.Comparators;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Reply;
|
import run.halo.app.core.extension.content.Reply;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
|
@ -15,4 +19,20 @@ public interface ReplyService {
|
||||||
Mono<Reply> create(String commentName, Reply reply);
|
Mono<Reply> create(String commentName, Reply reply);
|
||||||
|
|
||||||
Mono<ListResult<ListedReply>> list(ReplyQuery query);
|
Mono<ListResult<ListedReply>> list(ReplyQuery query);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ascending order by creation time.
|
||||||
|
*
|
||||||
|
* @return reply comparator
|
||||||
|
*/
|
||||||
|
static Comparator<Reply> creationTimeAscComparator() {
|
||||||
|
Function<Reply, Instant> creationTime = reply -> reply.getSpec().getCreationTime();
|
||||||
|
Function<Reply, Instant> metadataCreationTime =
|
||||||
|
reply -> reply.getMetadata().getCreationTimestamp();
|
||||||
|
// ascending order by creation time
|
||||||
|
// asc nulls high will be placed at the end
|
||||||
|
return Comparator.comparing(creationTime, Comparators.nullsHigh())
|
||||||
|
.thenComparing(metadataCreationTime)
|
||||||
|
.thenComparing(reply -> reply.getMetadata().getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package run.halo.app.content.comment;
|
||||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
|
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
|
@ -51,6 +50,9 @@ public class ReplyServiceImpl implements ReplyService {
|
||||||
if (reply.getSpec().getPriority() == null) {
|
if (reply.getSpec().getPriority() == null) {
|
||||||
reply.getSpec().setPriority(0);
|
reply.getSpec().setPriority(0);
|
||||||
}
|
}
|
||||||
|
if (reply.getSpec().getCreationTime() == null) {
|
||||||
|
reply.getSpec().setCreationTime(Instant.now());
|
||||||
|
}
|
||||||
return environmentFetcher.fetchComment()
|
return environmentFetcher.fetchComment()
|
||||||
.map(commentSetting -> {
|
.map(commentSetting -> {
|
||||||
if (Boolean.FALSE.equals(commentSetting.getEnable())) {
|
if (Boolean.FALSE.equals(commentSetting.getEnable())) {
|
||||||
|
@ -104,7 +106,8 @@ public class ReplyServiceImpl implements ReplyService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<ListedReply>> list(ReplyQuery query) {
|
public Mono<ListResult<ListedReply>> list(ReplyQuery query) {
|
||||||
return client.list(Reply.class, getReplyPredicate(query), defaultComparator(),
|
return client.list(Reply.class, getReplyPredicate(query),
|
||||||
|
ReplyService.creationTimeAscComparator(),
|
||||||
query.getPage(), query.getSize())
|
query.getPage(), query.getSize())
|
||||||
.flatMap(list -> Flux.fromStream(list.get()
|
.flatMap(list -> Flux.fromStream(list.get()
|
||||||
.map(this::toListedReply))
|
.map(this::toListedReply))
|
||||||
|
@ -140,12 +143,6 @@ public class ReplyServiceImpl implements ReplyService {
|
||||||
"Unsupported owner kind: " + owner.getKind());
|
"Unsupported owner kind: " + owner.getKind());
|
||||||
}
|
}
|
||||||
|
|
||||||
Comparator<Reply> defaultComparator() {
|
|
||||||
Function<Reply, Instant> createTime = reply -> reply.getMetadata().getCreationTimestamp();
|
|
||||||
return Comparator.comparing(createTime)
|
|
||||||
.thenComparing(reply -> reply.getMetadata().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate<Reply> getReplyPredicate(ReplyQuery query) {
|
Predicate<Reply> getReplyPredicate(ReplyQuery query) {
|
||||||
Predicate<Reply> predicate = reply -> true;
|
Predicate<Reply> predicate = reply -> true;
|
||||||
if (query.getCommentName() != null) {
|
if (query.getCommentName() != null) {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.core.extension.content;
|
package run.halo.app.core.extension.content;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
@ -70,6 +72,11 @@ public class Comment extends AbstractExtension {
|
||||||
|
|
||||||
private Instant approvedTime;
|
private Instant approvedTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user-defined creation time default is <code>metadata.creationTimestamp</code>.
|
||||||
|
*/
|
||||||
|
private Instant creationTime;
|
||||||
|
|
||||||
@Schema(required = true, defaultValue = "0")
|
@Schema(required = true, defaultValue = "0")
|
||||||
private Integer priority;
|
private Integer priority;
|
||||||
|
|
||||||
|
@ -84,6 +91,15 @@ public class Comment extends AbstractExtension {
|
||||||
|
|
||||||
@Schema(required = true, defaultValue = "false")
|
@Schema(required = true, defaultValue = "false")
|
||||||
private Boolean hidden;
|
private Boolean hidden;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the creation time is null, use approvedTime.
|
||||||
|
*
|
||||||
|
* @return if creationTime is null returned approved time, otherwise creationTime.
|
||||||
|
*/
|
||||||
|
public Instant getCreationTime() {
|
||||||
|
return defaultIfNull(creationTime, approvedTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
@ -130,8 +146,9 @@ public class Comment extends AbstractExtension {
|
||||||
if (lastReadTime == null) {
|
if (lastReadTime == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return existingReply.getMetadata().getCreationTimestamp()
|
Instant creationTime = defaultIfNull(existingReply.getSpec().getCreationTime(),
|
||||||
.isAfter(lastReadTime);
|
existingReply.getMetadata().getCreationTimestamp());
|
||||||
|
return creationTime.isAfter(lastReadTime);
|
||||||
})
|
})
|
||||||
.count();
|
.count();
|
||||||
return (int) unreadReplyCount;
|
return (int) unreadReplyCount;
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
package run.halo.app.core.extension.reconciler;
|
package run.halo.app.core.extension.reconciler;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import run.halo.app.content.comment.ReplyService;
|
||||||
import run.halo.app.core.extension.Counter;
|
import run.halo.app.core.extension.Counter;
|
||||||
import run.halo.app.core.extension.content.Comment;
|
import run.halo.app.core.extension.content.Comment;
|
||||||
import run.halo.app.core.extension.content.Constant;
|
import run.halo.app.core.extension.content.Constant;
|
||||||
|
@ -110,7 +109,7 @@ public class CommentReconciler implements Reconciler<Reconciler.Request> {
|
||||||
List<Reply> replies = client.list(Reply.class,
|
List<Reply> replies = client.list(Reply.class,
|
||||||
reply -> commentName.equals(reply.getSpec().getCommentName())
|
reply -> commentName.equals(reply.getSpec().getCommentName())
|
||||||
&& reply.getMetadata().getDeletionTimestamp() == null,
|
&& reply.getMetadata().getDeletionTimestamp() == null,
|
||||||
defaultReplyComparator());
|
ReplyService.creationTimeAscComparator());
|
||||||
|
|
||||||
// calculate unread reply count
|
// calculate unread reply count
|
||||||
comment.getStatusOrDefault()
|
comment.getStatusOrDefault()
|
||||||
|
@ -181,11 +180,4 @@ public class CommentReconciler implements Reconciler<Reconciler.Request> {
|
||||||
}
|
}
|
||||||
return new GroupVersionKind(ref.getGroup(), ref.getVersion(), ref.getKind());
|
return new GroupVersionKind(ref.getGroup(), ref.getVersion(), ref.getKind());
|
||||||
}
|
}
|
||||||
|
|
||||||
Comparator<Reply> defaultReplyComparator() {
|
|
||||||
Function<Reply, Instant> createTime = reply -> reply.getMetadata().getCreationTimestamp();
|
|
||||||
return Comparator.comparing(createTime)
|
|
||||||
.thenComparing(reply -> reply.getMetadata().getName())
|
|
||||||
.reversed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
package run.halo.app.metrics;
|
package run.halo.app.metrics;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.SmartLifecycle;
|
import org.springframework.context.SmartLifecycle;
|
||||||
import org.springframework.context.event.EventListener;
|
import org.springframework.context.event.EventListener;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import run.halo.app.content.comment.ReplyService;
|
||||||
import run.halo.app.core.extension.content.Comment;
|
import run.halo.app.core.extension.content.Comment;
|
||||||
import run.halo.app.core.extension.content.Reply;
|
import run.halo.app.core.extension.content.Reply;
|
||||||
import run.halo.app.event.post.ReplyEvent;
|
import run.halo.app.event.post.ReplyEvent;
|
||||||
|
@ -51,20 +52,24 @@ public class ReplyEventReconciler implements Reconciler<ReplyEvent>, SmartLifecy
|
||||||
.filter(comment -> comment.getMetadata().getDeletionTimestamp() == null)
|
.filter(comment -> comment.getMetadata().getDeletionTimestamp() == null)
|
||||||
.ifPresent(comment -> {
|
.ifPresent(comment -> {
|
||||||
|
|
||||||
|
// order by reply creation time desc to get first as last reply time
|
||||||
List<Reply> replies = client.list(Reply.class,
|
List<Reply> replies = client.list(Reply.class,
|
||||||
record -> commentName.equals(record.getSpec().getCommentName())
|
record -> commentName.equals(record.getSpec().getCommentName())
|
||||||
&& record.getMetadata().getDeletionTimestamp() == null,
|
&& record.getMetadata().getDeletionTimestamp() == null,
|
||||||
defaultReplyComparator());
|
ReplyService.creationTimeAscComparator().reversed());
|
||||||
|
|
||||||
Comment.CommentStatus status = comment.getStatusOrDefault();
|
Comment.CommentStatus status = comment.getStatusOrDefault();
|
||||||
// total reply count
|
// total reply count
|
||||||
status.setReplyCount(replies.size());
|
status.setReplyCount(replies.size());
|
||||||
|
|
||||||
// calculate last reply time
|
// calculate last reply time
|
||||||
if (!replies.isEmpty()) {
|
Instant lastReplyTime = replies.stream()
|
||||||
Instant lastReplyTime = replies.get(0).getMetadata().getCreationTimestamp();
|
.findFirst()
|
||||||
status.setLastReplyTime(lastReplyTime);
|
.map(reply -> defaultIfNull(reply.getSpec().getCreationTime(),
|
||||||
}
|
reply.getMetadata().getCreationTimestamp())
|
||||||
|
)
|
||||||
|
.orElse(null);
|
||||||
|
status.setLastReplyTime(lastReplyTime);
|
||||||
|
|
||||||
Instant lastReadTime = comment.getSpec().getLastReadTime();
|
Instant lastReadTime = comment.getSpec().getLastReadTime();
|
||||||
status.setUnreadReplyCount(Comment.getUnreadReplyCount(replies, lastReadTime));
|
status.setUnreadReplyCount(Comment.getUnreadReplyCount(replies, lastReadTime));
|
||||||
|
@ -74,13 +79,6 @@ public class ReplyEventReconciler implements Reconciler<ReplyEvent>, SmartLifecy
|
||||||
return new Result(false, null);
|
return new Result(false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Comparator<Reply> defaultReplyComparator() {
|
|
||||||
Function<Reply, Instant> createTime = reply -> reply.getMetadata().getCreationTimestamp();
|
|
||||||
return Comparator.comparing(createTime)
|
|
||||||
.thenComparing(reply -> reply.getMetadata().getName())
|
|
||||||
.reversed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Controller setupWith(ControllerBuilder builder) {
|
public Controller setupWith(ControllerBuilder builder) {
|
||||||
return new DefaultController<>(
|
return new DefaultController<>(
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
package run.halo.app.theme.finders.impl;
|
package run.halo.app.theme.finders.impl;
|
||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
|
@ -13,9 +11,11 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.comparator.Comparators;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.content.comment.OwnerInfo;
|
import run.halo.app.content.comment.OwnerInfo;
|
||||||
|
import run.halo.app.content.comment.ReplyService;
|
||||||
import run.halo.app.core.extension.User;
|
import run.halo.app.core.extension.User;
|
||||||
import run.halo.app.core.extension.content.Comment;
|
import run.halo.app.core.extension.content.Comment;
|
||||||
import run.halo.app.core.extension.content.Reply;
|
import run.halo.app.core.extension.content.Reply;
|
||||||
|
@ -70,12 +70,11 @@ public class CommentFinderImpl implements CommentFinder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<ReplyVo>> listReply(String commentName, Integer page, Integer size) {
|
public Mono<ListResult<ReplyVo>> listReply(String commentName, Integer page, Integer size) {
|
||||||
Comparator<Reply> comparator =
|
|
||||||
Comparator.comparing(reply -> reply.getMetadata().getCreationTimestamp());
|
|
||||||
return fixedReplyPredicate(commentName)
|
return fixedReplyPredicate(commentName)
|
||||||
.flatMap(fixedPredicate ->
|
.flatMap(fixedPredicate ->
|
||||||
client.list(Reply.class, fixedPredicate,
|
client.list(Reply.class, fixedPredicate,
|
||||||
comparator.reversed(), pageNullSafe(page), sizeNullSafe(size))
|
ReplyService.creationTimeAscComparator(), pageNullSafe(page),
|
||||||
|
sizeNullSafe(size))
|
||||||
.flatMap(list -> Flux.fromStream(list.get().map(this::toReplyVo))
|
.flatMap(list -> Flux.fromStream(list.get().map(this::toReplyVo))
|
||||||
.concatMap(Function.identity())
|
.concatMap(Function.identity())
|
||||||
.collectList()
|
.collectList()
|
||||||
|
@ -166,19 +165,36 @@ public class CommentFinderImpl implements CommentFinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Comparator<Comment> defaultComparator() {
|
static Comparator<Comment> defaultComparator() {
|
||||||
Function<Comment, Boolean> top =
|
return new CommentComparator();
|
||||||
comment -> Objects.requireNonNullElse(comment.getSpec().getTop(), false);
|
}
|
||||||
Function<Comment, Integer> priority =
|
|
||||||
comment -> Objects.requireNonNullElse(comment.getSpec().getPriority(), 0);
|
static class CommentComparator implements Comparator<Comment> {
|
||||||
Function<Comment, Instant> creationTimestamp =
|
@Override
|
||||||
comment -> comment.getMetadata().getCreationTimestamp();
|
public int compare(Comment c1, Comment c2) {
|
||||||
Function<Comment, String> name =
|
boolean c1Top = BooleanUtils.isTrue(c1.getSpec().getTop());
|
||||||
comment -> comment.getMetadata().getName();
|
boolean c2Top = BooleanUtils.isTrue(c2.getSpec().getTop());
|
||||||
return Comparator.comparing(top)
|
if (c1Top == c2Top) {
|
||||||
.thenComparing(priority)
|
int c1Priority = ObjectUtils.defaultIfNull(c1.getSpec().getPriority(), 0);
|
||||||
.thenComparing(creationTimestamp)
|
int c2Priority = ObjectUtils.defaultIfNull(c2.getSpec().getPriority(), 0);
|
||||||
.thenComparing(name)
|
if (c1Top) {
|
||||||
.reversed();
|
// 都置顶
|
||||||
|
return Integer.compare(c1Priority, c2Priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 两个评论不置顶根据 creationTime 降序排列
|
||||||
|
return Comparator.comparing(
|
||||||
|
(Comment comment) -> comment.getSpec().getCreationTime(),
|
||||||
|
Comparators.nullsLow())
|
||||||
|
.thenComparing((Comment comment) -> comment.getMetadata().getName())
|
||||||
|
.compare(c2, c1);
|
||||||
|
} else if (c1Top) {
|
||||||
|
// 只有 c1 置顶,c1 排前面
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
// 只有c2置顶, c2排在前面
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int pageNullSafe(Integer page) {
|
int pageNullSafe(Integer page) {
|
||||||
|
|
|
@ -127,6 +127,7 @@ class CommentServiceImplTest {
|
||||||
|
|
||||||
verify(client, times(1)).create(captor.capture());
|
verify(client, times(1)).create(captor.capture());
|
||||||
Comment comment = captor.getValue();
|
Comment comment = captor.getValue();
|
||||||
|
comment.getSpec().setCreationTime(null);
|
||||||
JSONAssert.assertEquals("""
|
JSONAssert.assertEquals("""
|
||||||
{
|
{
|
||||||
"spec": {
|
"spec": {
|
||||||
|
|
|
@ -102,7 +102,7 @@ class CommentSorterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void sortByDefaultDesc() {
|
void sortByDefaultDesc() {
|
||||||
Comparator<Comment> defaultComparator = CommentSorter.defaultCommentComparator().reversed();
|
Comparator<Comment> defaultComparator = CommentSorter.lastReplyTimeComparator().reversed();
|
||||||
List<String> commentNames = comments().stream()
|
List<String> commentNames = comments().stream()
|
||||||
.sorted(defaultComparator)
|
.sorted(defaultComparator)
|
||||||
.map(comment -> comment.getMetadata().getName())
|
.map(comment -> comment.getMetadata().getName())
|
||||||
|
@ -124,8 +124,9 @@ class CommentSorterTest {
|
||||||
commentA.getMetadata().setName("A");
|
commentA.getMetadata().setName("A");
|
||||||
// create time
|
// create time
|
||||||
commentA.getMetadata().setCreationTimestamp(now.plusSeconds(10));
|
commentA.getMetadata().setCreationTimestamp(now.plusSeconds(10));
|
||||||
|
|
||||||
commentA.setSpec(new Comment.CommentSpec());
|
commentA.setSpec(new Comment.CommentSpec());
|
||||||
|
commentA.getSpec().setCreationTime(commentA.getMetadata().getCreationTimestamp());
|
||||||
|
|
||||||
commentA.setStatus(new Comment.CommentStatus());
|
commentA.setStatus(new Comment.CommentStatus());
|
||||||
// last reply time
|
// last reply time
|
||||||
commentA.getStatus().setLastReplyTime(now.plusSeconds(5));
|
commentA.getStatus().setLastReplyTime(now.plusSeconds(5));
|
||||||
|
@ -140,6 +141,7 @@ class CommentSorterTest {
|
||||||
commentB.setStatus(new Comment.CommentStatus());
|
commentB.setStatus(new Comment.CommentStatus());
|
||||||
commentB.getStatus().setLastReplyTime(now.plusSeconds(15));
|
commentB.getStatus().setLastReplyTime(now.plusSeconds(15));
|
||||||
commentB.getStatus().setReplyCount(8);
|
commentB.getStatus().setReplyCount(8);
|
||||||
|
commentB.getSpec().setCreationTime(commentB.getMetadata().getCreationTimestamp());
|
||||||
|
|
||||||
Comment commentC = new Comment();
|
Comment commentC = new Comment();
|
||||||
commentC.setMetadata(new Metadata());
|
commentC.setMetadata(new Metadata());
|
||||||
|
@ -151,6 +153,7 @@ class CommentSorterTest {
|
||||||
commentC.setStatus(new Comment.CommentStatus());
|
commentC.setStatus(new Comment.CommentStatus());
|
||||||
commentC.getStatus().setLastReplyTime(now.plusSeconds(3));
|
commentC.getStatus().setLastReplyTime(now.plusSeconds(3));
|
||||||
commentC.getStatus().setReplyCount(10);
|
commentC.getStatus().setReplyCount(10);
|
||||||
|
commentC.getSpec().setCreationTime(commentC.getMetadata().getCreationTimestamp());
|
||||||
|
|
||||||
return List.of(commentA, commentB, commentC);
|
return List.of(commentA, commentB, commentC);
|
||||||
}
|
}
|
||||||
|
@ -165,6 +168,7 @@ class CommentSorterTest {
|
||||||
commentD.getMetadata().setCreationTimestamp(now.plusSeconds(50));
|
commentD.getMetadata().setCreationTimestamp(now.plusSeconds(50));
|
||||||
|
|
||||||
commentD.setSpec(new Comment.CommentSpec());
|
commentD.setSpec(new Comment.CommentSpec());
|
||||||
|
commentD.getSpec().setCreationTime(commentD.getMetadata().getCreationTimestamp());
|
||||||
commentD.setStatus(new Comment.CommentStatus());
|
commentD.setStatus(new Comment.CommentStatus());
|
||||||
|
|
||||||
Comment commentE = new Comment();
|
Comment commentE = new Comment();
|
||||||
|
@ -172,8 +176,8 @@ class CommentSorterTest {
|
||||||
commentE.getMetadata().setName("E");
|
commentE.getMetadata().setName("E");
|
||||||
|
|
||||||
commentE.getMetadata().setCreationTimestamp(now.plusSeconds(20));
|
commentE.getMetadata().setCreationTimestamp(now.plusSeconds(20));
|
||||||
|
|
||||||
commentE.setSpec(new Comment.CommentSpec());
|
commentE.setSpec(new Comment.CommentSpec());
|
||||||
|
commentE.getSpec().setCreationTime(commentE.getMetadata().getCreationTimestamp());
|
||||||
commentE.setStatus(new Comment.CommentStatus());
|
commentE.setStatus(new Comment.CommentStatus());
|
||||||
|
|
||||||
List<Comment> comments = new ArrayList<>(comments());
|
List<Comment> comments = new ArrayList<>(comments());
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package run.halo.app.content.comment;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import run.halo.app.core.extension.content.Reply;
|
||||||
|
import run.halo.app.extension.Metadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ReplyService}.
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
class ReplyServiceTest {
|
||||||
|
private final Instant now = Instant.now();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void creationTimeAscComparator() {
|
||||||
|
// creation time:
|
||||||
|
// 1. now + 5s, name: 1
|
||||||
|
// 2. now + 3s, name: 2
|
||||||
|
// 3. now + 3s, name: 3
|
||||||
|
// 5. now + 1s, name: 4
|
||||||
|
// 6. now - 1s, name: 5
|
||||||
|
// 7. null, name: 6
|
||||||
|
Reply reply1 = createReply("1", now.plusSeconds(5));
|
||||||
|
Reply reply2 = createReply("2", now.plusSeconds(3));
|
||||||
|
Reply reply3 = createReply("3", now.plusSeconds(3));
|
||||||
|
Reply reply4 = createReply("4", now.plusSeconds(1));
|
||||||
|
Reply reply5 = createReply("5", now.minusSeconds(1));
|
||||||
|
Reply reply6 = createReply("6", null);
|
||||||
|
String result = Stream.of(reply1, reply2, reply3, reply4, reply5, reply6)
|
||||||
|
.sorted(ReplyService.creationTimeAscComparator())
|
||||||
|
.map(reply -> reply.getMetadata().getName())
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
assertThat(result).isEqualTo("5, 4, 2, 3, 1, 6");
|
||||||
|
}
|
||||||
|
|
||||||
|
Reply createReply(String name, Instant creationTime) {
|
||||||
|
Reply reply = new Reply();
|
||||||
|
reply.setMetadata(new Metadata());
|
||||||
|
reply.getMetadata().setName(name);
|
||||||
|
reply.getMetadata().setCreationTimestamp(now);
|
||||||
|
reply.setSpec(new Reply.ReplySpec());
|
||||||
|
reply.getSpec().setCreationTime(creationTime);
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,8 +5,10 @@ import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -25,6 +27,7 @@ import run.halo.app.core.extension.content.Reply;
|
||||||
import run.halo.app.extension.GroupVersionKind;
|
import run.halo.app.extension.GroupVersionKind;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
|
import run.halo.app.extension.MetadataOperator;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.Ref;
|
import run.halo.app.extension.Ref;
|
||||||
import run.halo.app.infra.AnonymousUserConst;
|
import run.halo.app.infra.AnonymousUserConst;
|
||||||
|
@ -100,6 +103,60 @@ class CommentFinderImplTest {
|
||||||
.verifyComplete();
|
.verifyComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void commentComparator() {
|
||||||
|
// 1, now + 1s, top, 0
|
||||||
|
// 2, now + 2s, top, 1
|
||||||
|
// 3, now + 3s, top, 2
|
||||||
|
// 4, now + 4s, top, 2
|
||||||
|
// 5, now + 4s, top, 3
|
||||||
|
// 6, now + 1s, no, 0
|
||||||
|
// 7, now + 2s, no, 0
|
||||||
|
// 8, now + 3s, no, 0
|
||||||
|
// 9, now + 3s, no, 0
|
||||||
|
// 10, null, no, 0
|
||||||
|
// 11, null, no, 1
|
||||||
|
// 12, null, no, 3
|
||||||
|
// 13, now + 3s, no, 3
|
||||||
|
Instant now = Instant.now();
|
||||||
|
var comment1 = commentForCompare("1", now.plusSeconds(1), true, 0);
|
||||||
|
var comment2 = commentForCompare("2", now.plusSeconds(2), true, 1);
|
||||||
|
var comment3 = commentForCompare("3", now.plusSeconds(3), true, 2);
|
||||||
|
var comment4 = commentForCompare("4", now.plusSeconds(4), true, 2);
|
||||||
|
var comment5 = commentForCompare("5", now.plusSeconds(4), true, 3);
|
||||||
|
var comment6 = commentForCompare("6", now.plusSeconds(4), true, 3);
|
||||||
|
var comment7 = commentForCompare("7", now.plusSeconds(1), false, 0);
|
||||||
|
var comment8 = commentForCompare("8", now.plusSeconds(2), false, 0);
|
||||||
|
var comment9 = commentForCompare("9", now.plusSeconds(3), false, 0);
|
||||||
|
var comment10 = commentForCompare("10", now.plusSeconds(3), false, 0);
|
||||||
|
var comment11 = commentForCompare("11", null, false, 0);
|
||||||
|
var comment12 = commentForCompare("12", null, false, 1);
|
||||||
|
var comment13 = commentForCompare("13", null, false, 3);
|
||||||
|
var comment14 = commentForCompare("14", now.plusSeconds(3), false, 3);
|
||||||
|
|
||||||
|
var result = Stream.of(comment1, comment2, comment3, comment4, comment5, comment6,
|
||||||
|
comment7, comment8, comment9, comment10, comment11, comment12, comment13,
|
||||||
|
comment14)
|
||||||
|
.sorted(CommentFinderImpl.defaultComparator())
|
||||||
|
.map(Comment::getMetadata)
|
||||||
|
.map(MetadataOperator::getName)
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
assertThat(result).isEqualTo("1, 2, 3, 4, 5, 6, 9, 14, 10, 8, 7, 13, 12, 11");
|
||||||
|
}
|
||||||
|
|
||||||
|
Comment commentForCompare(String name, Instant creationTime, boolean top, int priority) {
|
||||||
|
Comment comment = new Comment();
|
||||||
|
comment.setMetadata(new Metadata());
|
||||||
|
comment.getMetadata().setName(name);
|
||||||
|
comment.getMetadata().setCreationTimestamp(Instant.now());
|
||||||
|
comment.setSpec(new Comment.CommentSpec());
|
||||||
|
comment.getSpec().setCreationTime(creationTime);
|
||||||
|
comment.getSpec().setTop(top);
|
||||||
|
comment.getSpec().setPriority(priority);
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void mockWhenListComment() {
|
private void mockWhenListComment() {
|
||||||
// Mock
|
// Mock
|
||||||
|
|
Loading…
Reference in New Issue