From d5f6dc2207bc154b8baf4cf4324fd5bbe4ae833d Mon Sep 17 00:00:00 2001 From: guqing <38999863+guqing@users.noreply.github.com> Date: Thu, 25 May 2023 20:42:17 +0800 Subject: [PATCH] refactor: sorting parameters to maintain a unified API style (#3956) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind improvement /area core /area console /kind api-change /milestone 2.6.x #### What this PR does / why we need it: 重构排序参数以统一自定义 APIs 的风格 - 文章的排序参数字段名改为 `creationTimestamp`、`publishTime` 查询参数示例为 sort=creationTimestamp,desc - 自定义页面排序参数字段名同文章 - 评论排序参数字段名为 `creationTimestamp`,`replyCount`,`lastReplyTime` 查询参数示例为 sort=creationTimestamp,desc 需要 Console 适配 #### Which issue(s) this PR fixes: Fixes #3464 #### Does this PR introduce a user-facing change? ```release-note 重构排序参数以统一自定义 APIs 的风格 ``` --- .../java/run/halo/app/content/PostQuery.java | 141 +++++++++-- .../run/halo/app/content/SinglePageQuery.java | 135 +++++++++- .../halo/app/content/SinglePageSorter.java | 78 ------ .../app/content/comment/CommentQuery.java | 179 +++++++++++++- .../content/comment/CommentServiceImpl.java | 91 +------ .../app/content/comment/CommentSorter.java | 78 ------ .../app/content/impl/PostServiceImpl.java | 71 +----- .../content/impl/SinglePageServiceImpl.java | 69 +----- .../extension/endpoint/CommentEndpoint.java | 2 +- .../core/extension/endpoint/PostEndpoint.java | 5 +- .../endpoint/SinglePageEndpoint.java | 2 +- .../run/halo/app/extension/Comparators.java | 11 + .../app/theme/endpoint/SortableRequest.java | 7 +- ...erviceImplTest.java => PostQueryTest.java} | 54 ++-- .../app/content/comment/CommentQueryTest.java | 230 +++++++++++++++--- .../comment/CommentServiceImplTest.java | 29 ++- .../content/comment/CommentSorterTest.java | 190 --------------- ...i-console-halo-run-v1alpha1-comment-api.ts | 36 +-- .../api-console-halo-run-v1alpha1-post-api.ts | 36 +-- ...nsole-halo-run-v1alpha1-single-page-api.ts | 36 +-- console/src/locales/en.yaml | 10 +- console/src/locales/zh-CN.yaml | 10 +- console/src/locales/zh-TW.yaml | 10 +- .../modules/contents/comments/CommentList.vue | 71 +++--- .../modules/contents/pages/SinglePageList.vue | 23 +- .../src/modules/contents/posts/PostList.vue | 23 +- .../posts/widgets/RecentPublishedWidget.vue | 3 +- 27 files changed, 777 insertions(+), 853 deletions(-) delete mode 100644 application/src/main/java/run/halo/app/content/SinglePageSorter.java delete mode 100644 application/src/main/java/run/halo/app/content/comment/CommentSorter.java rename application/src/test/java/run/halo/app/content/{impl/PostServiceImplTest.java => PostQueryTest.java} (59%) delete mode 100644 application/src/test/java/run/halo/app/content/comment/CommentSorterTest.java diff --git a/application/src/main/java/run/halo/app/content/PostQuery.java b/application/src/main/java/run/halo/app/content/PostQuery.java index 9aa33f4bd..3f9a6963d 100644 --- a/application/src/main/java/run/halo/app/content/PostQuery.java +++ b/application/src/main/java/run/halo/app/content/PostQuery.java @@ -1,12 +1,24 @@ package run.halo.app.content; +import static java.util.Comparator.comparing; +import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate; + +import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; import java.util.List; import java.util.Set; +import java.util.function.Predicate; import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Sort; import org.springframework.lang.Nullable; -import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.server.ServerWebExchange; import run.halo.app.core.extension.content.Post; +import run.halo.app.core.extension.endpoint.SortResolver; +import run.halo.app.extension.Comparators; import run.halo.app.extension.router.IListRequest; /** @@ -17,8 +29,11 @@ import run.halo.app.extension.router.IListRequest; */ public class PostQuery extends IListRequest.QueryListRequest { - public PostQuery(MultiValueMap queryParams) { - super(queryParams); + private final ServerWebExchange exchange; + + public PostQuery(ServerRequest request) { + super(request.queryParams()); + this.exchange = request.exchange(); } @Nullable @@ -57,16 +72,15 @@ public class PostQuery extends IListRequest.QueryListRequest { return StringUtils.defaultIfBlank(queryParams.getFirst("keyword"), null); } - @Schema(description = "Post collation.") - public PostSorter getSort() { - String sort = queryParams.getFirst("sort"); - return PostSorter.convertFrom(sort); - } - - @Schema(description = "ascending order If it is true; otherwise, it is in descending order.") - public Boolean getSortOrder() { - String sortOrder = queryParams.getFirst("sortOrder"); - return convertBooleanOrNull(sortOrder); + @ArraySchema(uniqueItems = true, + arraySchema = @Schema(name = "sort", + description = "Sort property and direction of the list result. Supported fields: " + + "creationTimestamp,publishTime"), + schema = @Schema(description = "like field,asc or field,desc", + implementation = String.class, + example = "creationTimestamp,desc")) + public Sort getSort() { + return SortResolver.defaultInstance.resolve(exchange); } @Nullable @@ -74,7 +88,104 @@ public class PostQuery extends IListRequest.QueryListRequest { return param == null ? null : Set.copyOf(param); } - private Boolean convertBooleanOrNull(String value) { - return StringUtils.isBlank(value) ? null : Boolean.parseBoolean(value); + /** + * Build a comparator from the query object. + * + * @return a comparator + */ + public Comparator toComparator() { + var sort = getSort(); + var creationTimestampOrder = sort.getOrderFor("creationTimestamp"); + List> comparators = new ArrayList<>(); + if (creationTimestampOrder != null) { + Comparator comparator = + comparing(post -> post.getMetadata().getCreationTimestamp()); + if (creationTimestampOrder.isDescending()) { + comparator = comparator.reversed(); + } + comparators.add(comparator); + } + + var publishTimeOrder = sort.getOrderFor("publishTime"); + if (publishTimeOrder != null) { + Comparator nullsComparator = publishTimeOrder.isAscending() + ? org.springframework.util.comparator.Comparators.nullsLow() + : org.springframework.util.comparator.Comparators.nullsHigh(); + Comparator comparator = + comparing(post -> post.getSpec().getPublishTime(), nullsComparator); + if (publishTimeOrder.isDescending()) { + comparator = comparator.reversed(); + } + comparators.add(comparator); + } + comparators.add(Comparators.compareCreationTimestamp(false)); + comparators.add(Comparators.compareName(true)); + return comparators.stream() + .reduce(Comparator::thenComparing) + .orElse(null); + } + + /** + * Build a predicate from the query object. + * + * @return a predicate + */ + public Predicate toPredicate() { + Predicate paramPredicate = post -> + contains(getCategories(), post.getSpec().getCategories()) + && contains(getTags(), post.getSpec().getTags()) + && contains(getContributors(), post.getStatusOrDefault().getContributors()); + + String keyword = getKeyword(); + if (keyword != null) { + paramPredicate = paramPredicate.and(post -> { + String excerpt = post.getStatusOrDefault().getExcerpt(); + return StringUtils.containsIgnoreCase(excerpt, keyword) + || StringUtils.containsIgnoreCase(post.getSpec().getSlug(), keyword) + || StringUtils.containsIgnoreCase(post.getSpec().getTitle(), keyword); + }); + } + + Post.PostPhase publishPhase = getPublishPhase(); + if (publishPhase != null) { + paramPredicate = paramPredicate.and(post -> { + if (Post.PostPhase.PENDING_APPROVAL.equals(publishPhase)) { + return !post.isPublished() + && Post.PostPhase.PENDING_APPROVAL.name() + .equalsIgnoreCase(post.getStatusOrDefault().getPhase()); + } + // published + if (Post.PostPhase.PUBLISHED.equals(publishPhase)) { + return post.isPublished(); + } + // draft + return !post.isPublished(); + }); + } + + Post.VisibleEnum visible = getVisible(); + if (visible != null) { + paramPredicate = + paramPredicate.and(post -> visible.equals(post.getSpec().getVisible())); + } + + Predicate predicate = labelAndFieldSelectorToPredicate(getLabelSelector(), + getFieldSelector()); + return predicate.and(paramPredicate); + } + + boolean contains(Collection left, List right) { + // parameter is null, it means that ignore this condition + if (left == null) { + return true; + } + // else, it means that right is empty + if (left.isEmpty()) { + return right.isEmpty(); + } + if (right == null) { + return false; + } + return right.stream().anyMatch(left::contains); } } diff --git a/application/src/main/java/run/halo/app/content/SinglePageQuery.java b/application/src/main/java/run/halo/app/content/SinglePageQuery.java index f056ad497..e59c07302 100644 --- a/application/src/main/java/run/halo/app/content/SinglePageQuery.java +++ b/application/src/main/java/run/halo/app/content/SinglePageQuery.java @@ -1,13 +1,25 @@ package run.halo.app.content; +import static java.util.Comparator.comparing; +import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate; + +import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; import java.util.List; import java.util.Set; +import java.util.function.Predicate; import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Sort; import org.springframework.lang.Nullable; -import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.server.ServerWebExchange; import run.halo.app.core.extension.content.Post; import run.halo.app.core.extension.content.SinglePage; +import run.halo.app.core.extension.endpoint.SortResolver; +import run.halo.app.extension.Comparators; import run.halo.app.extension.router.IListRequest; /** @@ -18,8 +30,11 @@ import run.halo.app.extension.router.IListRequest; */ public class SinglePageQuery extends IListRequest.QueryListRequest { - public SinglePageQuery(MultiValueMap queryParams) { - super(queryParams); + private final ServerWebExchange exchange; + + public SinglePageQuery(ServerRequest request) { + super(request.queryParams()); + this.exchange = request.exchange(); } @Nullable @@ -47,19 +62,113 @@ public class SinglePageQuery extends IListRequest.QueryListRequest { return StringUtils.defaultIfBlank(queryParams.getFirst("keyword"), null); } - @Schema(description = "SinglePage collation.") - public SinglePageSorter getSort() { - String sort = queryParams.getFirst("sort"); - return SinglePageSorter.convertFrom(sort); + @ArraySchema(uniqueItems = true, + arraySchema = @Schema(name = "sort", + description = "Sort property and direction of the list result. Supported fields: " + + "creationTimestamp,publishTime"), + schema = @Schema(description = "like field,asc or field,desc", + implementation = String.class, + example = "creationTimestamp,desc")) + public Sort getSort() { + return SortResolver.defaultInstance.resolve(exchange); } - @Schema(description = "ascending order If it is true; otherwise, it is in descending order.") - public Boolean getSortOrder() { - String sortOrder = queryParams.getFirst("sortOrder"); - return convertBooleanOrNull(sortOrder); + /** + * Build a comparator for {@link SinglePageQuery}. + * + * @return comparator + */ + public Comparator toComparator() { + var sort = getSort(); + var creationTimestampOrder = sort.getOrderFor("creationTimestamp"); + List> comparators = new ArrayList<>(); + if (creationTimestampOrder != null) { + Comparator comparator = + comparing(page -> page.getMetadata().getCreationTimestamp()); + if (creationTimestampOrder.isDescending()) { + comparator = comparator.reversed(); + } + comparators.add(comparator); + } + + var publishTimeOrder = sort.getOrderFor("publishTime"); + if (publishTimeOrder != null) { + Comparator nullsComparator = publishTimeOrder.isAscending() + ? org.springframework.util.comparator.Comparators.nullsLow() + : org.springframework.util.comparator.Comparators.nullsHigh(); + Comparator comparator = + comparing(page -> page.getSpec().getPublishTime(), nullsComparator); + if (publishTimeOrder.isDescending()) { + comparator = comparator.reversed(); + } + comparators.add(comparator); + } + comparators.add(Comparators.compareCreationTimestamp(false)); + comparators.add(Comparators.compareName(true)); + return comparators.stream() + .reduce(Comparator::thenComparing) + .orElse(null); } - private Boolean convertBooleanOrNull(String value) { - return StringUtils.isBlank(value) ? null : Boolean.parseBoolean(value); + /** + * Build a predicate for {@link SinglePageQuery}. + * + * @return predicate + */ + public Predicate toPredicate() { + Predicate paramPredicate = singlePage -> contains(getContributors(), + singlePage.getStatusOrDefault().getContributors()); + + String keyword = getKeyword(); + if (keyword != null) { + paramPredicate = paramPredicate.and(page -> { + String excerpt = page.getStatusOrDefault().getExcerpt(); + return StringUtils.containsIgnoreCase(excerpt, keyword) + || StringUtils.containsIgnoreCase(page.getSpec().getSlug(), keyword) + || StringUtils.containsIgnoreCase(page.getSpec().getTitle(), keyword); + }); + } + + Post.PostPhase publishPhase = getPublishPhase(); + if (publishPhase != null) { + paramPredicate = paramPredicate.and(page -> { + if (Post.PostPhase.PENDING_APPROVAL.equals(publishPhase)) { + return !page.isPublished() + && Post.PostPhase.PENDING_APPROVAL.name() + .equalsIgnoreCase(page.getStatusOrDefault().getPhase()); + } + // published + if (Post.PostPhase.PUBLISHED.equals(publishPhase)) { + return page.isPublished(); + } + // draft + return !page.isPublished(); + }); + } + + Post.VisibleEnum visible = getVisible(); + if (visible != null) { + paramPredicate = + paramPredicate.and(post -> visible.equals(post.getSpec().getVisible())); + } + + Predicate predicate = labelAndFieldSelectorToPredicate(getLabelSelector(), + getFieldSelector()); + return predicate.and(paramPredicate); + } + + boolean contains(Collection left, List right) { + // parameter is null, it means that ignore this condition + if (left == null) { + return true; + } + // else, it means that right is empty + if (left.isEmpty()) { + return right.isEmpty(); + } + if (right == null) { + return false; + } + return right.stream().anyMatch(left::contains); } } diff --git a/application/src/main/java/run/halo/app/content/SinglePageSorter.java b/application/src/main/java/run/halo/app/content/SinglePageSorter.java deleted file mode 100644 index 318faa83c..000000000 --- a/application/src/main/java/run/halo/app/content/SinglePageSorter.java +++ /dev/null @@ -1,78 +0,0 @@ -package run.halo.app.content; - -import java.time.Instant; -import java.util.Comparator; -import java.util.Objects; -import java.util.function.Function; -import org.springframework.util.comparator.Comparators; -import run.halo.app.core.extension.content.SinglePage; - -/** - * A sorter for {@link SinglePage}. - * - * @author guqing - * @since 2.0.0 - */ -public enum SinglePageSorter { - PUBLISH_TIME, - CREATE_TIME; - - static final Function name = page -> page.getMetadata().getName(); - - /** - * Converts {@link Comparator} from {@link SinglePageSorter} and ascending. - * - * @param sorter a {@link SinglePageSorter} - * @param ascending ascending if true, otherwise descending - * @return a {@link Comparator} of {@link SinglePage} - */ - public static Comparator from(SinglePageSorter sorter, Boolean ascending) { - if (Objects.equals(true, ascending)) { - return from(sorter); - } - return from(sorter).reversed(); - } - - /** - * Converts {@link Comparator} from {@link SinglePageSorter}. - * - * @param sorter a {@link SinglePageSorter} - * @return a {@link Comparator} of {@link SinglePage} - */ - public static Comparator from(SinglePageSorter sorter) { - if (sorter == null) { - return defaultComparator(); - } - if (CREATE_TIME.equals(sorter)) { - Function comparatorFunc = - page -> page.getMetadata().getCreationTimestamp(); - return Comparator.comparing(comparatorFunc) - .thenComparing(name); - } - - if (PUBLISH_TIME.equals(sorter)) { - Function comparatorFunc = - page -> page.getSpec().getPublishTime(); - return Comparator.comparing(comparatorFunc, Comparators.nullsLow()) - .thenComparing(name); - } - - throw new IllegalArgumentException("Unsupported sort value: " + sorter); - } - - static SinglePageSorter convertFrom(String sort) { - for (SinglePageSorter sorter : values()) { - if (sorter.name().equalsIgnoreCase(sort)) { - return sorter; - } - } - return null; - } - - static Comparator defaultComparator() { - Function createTime = - page -> page.getMetadata().getCreationTimestamp(); - return Comparator.comparing(createTime) - .thenComparing(name); - } -} diff --git a/application/src/main/java/run/halo/app/content/comment/CommentQuery.java b/application/src/main/java/run/halo/app/content/comment/CommentQuery.java index a106a42f9..a5bc893a2 100644 --- a/application/src/main/java/run/halo/app/content/comment/CommentQuery.java +++ b/application/src/main/java/run/halo/app/content/comment/CommentQuery.java @@ -1,8 +1,28 @@ package run.halo.app.content.comment; +import static java.util.Comparator.comparing; +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; +import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate; + +import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; import org.apache.commons.lang3.StringUtils; -import org.springframework.util.MultiValueMap; +import org.springframework.data.domain.Sort; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.server.ServerWebExchange; +import run.halo.app.core.extension.content.Comment; +import run.halo.app.core.extension.endpoint.SortResolver; +import run.halo.app.extension.Comparators; +import run.halo.app.extension.Extension; +import run.halo.app.extension.Ref; import run.halo.app.extension.router.IListRequest; /** @@ -13,8 +33,17 @@ import run.halo.app.extension.router.IListRequest; */ public class CommentQuery extends IListRequest.QueryListRequest { - public CommentQuery(MultiValueMap queryParams) { - super(queryParams); + private final ServerWebExchange exchange; + static final Function LAST_REPLY_TIME_FUNC = + comment -> { + Instant lastReplyTime = comment.getStatusOrDefault().getLastReplyTime(); + return Optional.ofNullable(lastReplyTime) + .orElse(comment.getSpec().getCreationTime()); + }; + + public CommentQuery(ServerRequest request) { + super(request.queryParams()); + this.exchange = request.exchange(); } @Schema(description = "Comments filtered by keyword.") @@ -67,16 +96,144 @@ public class CommentQuery extends IListRequest.QueryListRequest { return StringUtils.isBlank(subjectName) ? null : subjectName; } - @Schema(description = "Comment collation.") - public CommentSorter getSort() { - String sort = queryParams.getFirst("sort"); - return CommentSorter.convertFrom(sort); + @ArraySchema(uniqueItems = true, + arraySchema = @Schema(name = "sort", + description = "Sort property and direction of the list result. Supported fields: " + + "creationTimestamp,replyCount,lastReplyTime"), + schema = @Schema(description = "like field,asc or field,desc", + implementation = String.class, + example = "creationTimestamp,desc")) + public Sort getSort() { + return SortResolver.defaultInstance.resolve(exchange); } - @Schema(description = "ascending order If it is true; otherwise, it is in descending order.") - public Boolean getSortOrder() { - String sortOrder = queryParams.getFirst("sortOrder"); - return convertBooleanOrNull(sortOrder); + /** + * Build a comparator from the query. + * + * @return comparator + */ + public Comparator toComparator() { + var sort = getSort(); + var creationTimestampOrder = sort.getOrderFor("creationTimestamp"); + List> comparators = new ArrayList<>(); + if (creationTimestampOrder != null) { + Comparator comparator = + comparing(comment -> comment.getMetadata().getCreationTimestamp()); + if (creationTimestampOrder.isDescending()) { + comparator = comparator.reversed(); + } + comparators.add(comparator); + } + + var replyCountOrder = sort.getOrderFor("replyCount"); + if (replyCountOrder != null) { + Comparator comparator = comparing( + comment -> defaultIfNull(comment.getStatusOrDefault().getReplyCount(), 0)); + if (replyCountOrder.isDescending()) { + comparator = comparator.reversed(); + } + comparators.add(comparator); + } + + var lastReplyTimeOrder = sort.getOrderFor("lastReplyTime"); + if (lastReplyTimeOrder == null) { + lastReplyTimeOrder = new Sort.Order(Sort.Direction.DESC, "lastReplyTime"); + } + Comparator comparator = comparing(LAST_REPLY_TIME_FUNC, + Comparators.nullsComparator(lastReplyTimeOrder.isAscending())); + if (lastReplyTimeOrder.isDescending()) { + comparator = comparator.reversed(); + } + comparators.add(comparator); + comparators.add(Comparators.compareCreationTimestamp(false)); + comparators.add(Comparators.compareName(true)); + return comparators.stream() + .reduce(Comparator::thenComparing) + .orElse(null); + } + + /** + * Build a predicate from the query. + * + * @return predicate + */ + Predicate toPredicate() { + Predicate predicate = comment -> true; + + String keyword = getKeyword(); + if (keyword != null) { + predicate = predicate.and(comment -> { + String raw = comment.getSpec().getRaw(); + return StringUtils.containsIgnoreCase(raw, keyword); + }); + } + + Boolean approved = getApproved(); + if (approved != null) { + predicate = + predicate.and(comment -> Objects.equals(comment.getSpec().getApproved(), approved)); + } + Boolean hidden = getHidden(); + if (hidden != null) { + predicate = + predicate.and(comment -> Objects.equals(comment.getSpec().getHidden(), hidden)); + } + + Boolean top = getTop(); + if (top != null) { + predicate = predicate.and(comment -> Objects.equals(comment.getSpec().getTop(), top)); + } + + Boolean allowNotification = getAllowNotification(); + if (allowNotification != null) { + predicate = predicate.and( + comment -> Objects.equals(comment.getSpec().getAllowNotification(), + allowNotification)); + } + + String ownerKind = getOwnerKind(); + if (ownerKind != null) { + predicate = predicate.and(comment -> { + Comment.CommentOwner owner = comment.getSpec().getOwner(); + return Objects.equals(owner.getKind(), ownerKind); + }); + } + + String ownerName = getOwnerName(); + if (ownerName != null) { + predicate = predicate.and(comment -> { + Comment.CommentOwner owner = comment.getSpec().getOwner(); + if (Comment.CommentOwner.KIND_EMAIL.equals(owner.getKind())) { + return Objects.equals(owner.getKind(), ownerKind) + && (StringUtils.containsIgnoreCase(owner.getName(), ownerName) + || StringUtils.containsIgnoreCase(owner.getDisplayName(), ownerName)); + } + return Objects.equals(owner.getKind(), ownerKind) + && StringUtils.containsIgnoreCase(owner.getName(), ownerName); + }); + } + + String subjectKind = getSubjectKind(); + if (subjectKind != null) { + predicate = predicate.and(comment -> { + Ref subjectRef = comment.getSpec().getSubjectRef(); + return Objects.equals(subjectRef.getKind(), subjectKind); + }); + } + + String subjectName = getSubjectName(); + if (subjectName != null) { + predicate = predicate.and(comment -> { + Ref subjectRef = comment.getSpec().getSubjectRef(); + return Objects.equals(subjectRef.getKind(), subjectKind) + && StringUtils.containsIgnoreCase(subjectRef.getName(), subjectName); + }); + } + + Predicate labelAndFieldSelectorPredicate = + labelAndFieldSelectorToPredicate(getLabelSelector(), + getFieldSelector()); + return predicate.and(labelAndFieldSelectorPredicate); } private Boolean convertBooleanOrNull(String value) { diff --git a/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java b/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java index 9c1030011..51762ae32 100644 --- a/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java +++ b/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java @@ -1,14 +1,8 @@ package run.halo.app.content.comment; -import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate; - import java.time.Instant; -import java.util.Comparator; -import java.util.Objects; import java.util.function.Function; -import java.util.function.Predicate; import org.apache.commons.lang3.BooleanUtils; -import org.apache.commons.lang3.StringUtils; import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.util.Assert; @@ -56,10 +50,8 @@ public class CommentServiceImpl implements CommentService { @Override public Mono> listComment(CommentQuery commentQuery) { - Comparator comparator = - CommentSorter.from(commentQuery.getSort(), commentQuery.getSortOrder()); - return this.client.list(Comment.class, commentPredicate(commentQuery), - comparator, + return this.client.list(Comment.class, commentQuery.toPredicate(), + commentQuery.toComparator(), commentQuery.getPage(), commentQuery.getSize()) .flatMap(comments -> Flux.fromStream(comments.get() .map(this::toListedComment)) @@ -200,83 +192,4 @@ public class CommentServiceImpl implements CommentService { .map(commentSubject -> commentSubject.get(ref.getName())) .orElseGet(Mono::empty); } - - Predicate commentPredicate(CommentQuery query) { - Predicate predicate = comment -> true; - - String keyword = query.getKeyword(); - if (keyword != null) { - predicate = predicate.and(comment -> { - String raw = comment.getSpec().getRaw(); - return StringUtils.containsIgnoreCase(raw, keyword); - }); - } - - Boolean approved = query.getApproved(); - if (approved != null) { - predicate = - predicate.and(comment -> Objects.equals(comment.getSpec().getApproved(), approved)); - } - Boolean hidden = query.getHidden(); - if (hidden != null) { - predicate = - predicate.and(comment -> Objects.equals(comment.getSpec().getHidden(), hidden)); - } - - Boolean top = query.getTop(); - if (top != null) { - predicate = predicate.and(comment -> Objects.equals(comment.getSpec().getTop(), top)); - } - - Boolean allowNotification = query.getAllowNotification(); - if (allowNotification != null) { - predicate = predicate.and( - comment -> Objects.equals(comment.getSpec().getAllowNotification(), - allowNotification)); - } - - String ownerKind = query.getOwnerKind(); - if (ownerKind != null) { - predicate = predicate.and(comment -> { - Comment.CommentOwner owner = comment.getSpec().getOwner(); - return Objects.equals(owner.getKind(), ownerKind); - }); - } - - String ownerName = query.getOwnerName(); - if (ownerName != null) { - predicate = predicate.and(comment -> { - Comment.CommentOwner owner = comment.getSpec().getOwner(); - if (Comment.CommentOwner.KIND_EMAIL.equals(owner.getKind())) { - return Objects.equals(owner.getKind(), ownerKind) - && (StringUtils.containsIgnoreCase(owner.getName(), ownerName) - || StringUtils.containsIgnoreCase(owner.getDisplayName(), ownerName)); - } - return Objects.equals(owner.getKind(), ownerKind) - && StringUtils.containsIgnoreCase(owner.getName(), ownerName); - }); - } - - String subjectKind = query.getSubjectKind(); - if (subjectKind != null) { - predicate = predicate.and(comment -> { - Ref subjectRef = comment.getSpec().getSubjectRef(); - return Objects.equals(subjectRef.getKind(), subjectKind); - }); - } - - String subjectName = query.getSubjectName(); - if (subjectName != null) { - predicate = predicate.and(comment -> { - Ref subjectRef = comment.getSpec().getSubjectRef(); - return Objects.equals(subjectRef.getKind(), subjectKind) - && StringUtils.containsIgnoreCase(subjectRef.getName(), subjectName); - }); - } - - Predicate labelAndFieldSelectorPredicate = - labelAndFieldSelectorToPredicate(query.getLabelSelector(), - query.getFieldSelector()); - return predicate.and(labelAndFieldSelectorPredicate); - } } diff --git a/application/src/main/java/run/halo/app/content/comment/CommentSorter.java b/application/src/main/java/run/halo/app/content/comment/CommentSorter.java deleted file mode 100644 index 5c7e8be9d..000000000 --- a/application/src/main/java/run/halo/app/content/comment/CommentSorter.java +++ /dev/null @@ -1,78 +0,0 @@ -package run.halo.app.content.comment; - -import java.time.Instant; -import java.util.Comparator; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Function; -import org.springframework.util.comparator.Comparators; -import run.halo.app.core.extension.content.Comment; - -/** - * Comment sorter. - * - * @author guqing - * @since 2.0.0 - */ -public enum CommentSorter { - LAST_REPLY_TIME, - REPLY_COUNT, - CREATE_TIME; - - static final Function name = comment -> comment.getMetadata().getName(); - - static Comparator from(CommentSorter sorter, Boolean ascending) { - if (Objects.equals(true, ascending)) { - return from(sorter); - } - return from(sorter).reversed(); - } - - static Comparator from(CommentSorter sorter) { - if (sorter == null) { - return lastReplyTimeComparator(); - } - if (CREATE_TIME.equals(sorter)) { - Function comparatorFunc = - comment -> comment.getSpec().getCreationTime(); - return Comparator.comparing(comparatorFunc, Comparators.nullsLow()) - .thenComparing(name); - } - - if (REPLY_COUNT.equals(sorter)) { - Function comparatorFunc = - comment -> comment.getStatusOrDefault().getReplyCount(); - return Comparator.comparing(comparatorFunc, Comparators.nullsLow()) - .thenComparing(name); - } - - if (LAST_REPLY_TIME.equals(sorter)) { - Function comparatorFunc = - comment -> comment.getStatusOrDefault().getLastReplyTime(); - return Comparator.comparing(comparatorFunc, Comparators.nullsLow()) - .thenComparing(name); - } - - throw new IllegalStateException("Unsupported sort value: " + sorter); - } - - static CommentSorter convertFrom(String sort) { - for (CommentSorter sorter : values()) { - if (sorter.name().equalsIgnoreCase(sort)) { - return sorter; - } - } - return null; - } - - static Comparator lastReplyTimeComparator() { - Function comparatorFunc = - comment -> { - Instant lastReplyTime = comment.getStatusOrDefault().getLastReplyTime(); - return Optional.ofNullable(lastReplyTime) - .orElse(comment.getSpec().getCreationTime()); - }; - return Comparator.comparing(comparatorFunc, Comparators.nullsLow()) - .thenComparing(name); - } -} diff --git a/application/src/main/java/run/halo/app/content/impl/PostServiceImpl.java b/application/src/main/java/run/halo/app/content/impl/PostServiceImpl.java index ea1cd77e2..b8149c7c2 100644 --- a/application/src/main/java/run/halo/app/content/impl/PostServiceImpl.java +++ b/application/src/main/java/run/halo/app/content/impl/PostServiceImpl.java @@ -1,15 +1,10 @@ package run.halo.app.content.impl; -import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate; - import java.time.Duration; import java.time.Instant; -import java.util.Collection; -import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.function.Function; -import java.util.function.Predicate; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.dao.OptimisticLockingFailureException; @@ -26,7 +21,6 @@ import run.halo.app.content.ListedPost; import run.halo.app.content.PostQuery; import run.halo.app.content.PostRequest; import run.halo.app.content.PostService; -import run.halo.app.content.PostSorter; import run.halo.app.content.Stats; import run.halo.app.core.extension.content.Category; import run.halo.app.core.extension.content.Post; @@ -63,10 +57,8 @@ public class PostServiceImpl extends AbstractContentService implements PostServi @Override public Mono> listPost(PostQuery query) { - Comparator comparator = - PostSorter.from(query.getSort(), query.getSortOrder()); - return client.list(Post.class, postListPredicate(query), - comparator, query.getPage(), query.getSize()) + return client.list(Post.class, query.toPredicate(), + query.toComparator(), query.getPage(), query.getSize()) .flatMap(listResult -> Flux.fromStream( listResult.get().map(this::getListedPost) ) @@ -92,65 +84,6 @@ public class PostServiceImpl extends AbstractContentService implements PostServi .defaultIfEmpty(Stats.empty()); } - Predicate postListPredicate(PostQuery query) { - Predicate paramPredicate = post -> - contains(query.getCategories(), post.getSpec().getCategories()) - && contains(query.getTags(), post.getSpec().getTags()) - && contains(query.getContributors(), post.getStatusOrDefault().getContributors()); - - String keyword = query.getKeyword(); - if (keyword != null) { - paramPredicate = paramPredicate.and(post -> { - String excerpt = post.getStatusOrDefault().getExcerpt(); - return StringUtils.containsIgnoreCase(excerpt, keyword) - || StringUtils.containsIgnoreCase(post.getSpec().getSlug(), keyword) - || StringUtils.containsIgnoreCase(post.getSpec().getTitle(), keyword); - }); - } - - Post.PostPhase publishPhase = query.getPublishPhase(); - if (publishPhase != null) { - paramPredicate = paramPredicate.and(post -> { - if (Post.PostPhase.PENDING_APPROVAL.equals(publishPhase)) { - return !post.isPublished() - && Post.PostPhase.PENDING_APPROVAL.name() - .equalsIgnoreCase(post.getStatusOrDefault().getPhase()); - } - // published - if (Post.PostPhase.PUBLISHED.equals(publishPhase)) { - return post.isPublished(); - } - // draft - return !post.isPublished(); - }); - } - - Post.VisibleEnum visible = query.getVisible(); - if (visible != null) { - paramPredicate = - paramPredicate.and(post -> visible.equals(post.getSpec().getVisible())); - } - - Predicate predicate = labelAndFieldSelectorToPredicate(query.getLabelSelector(), - query.getFieldSelector()); - return predicate.and(paramPredicate); - } - - boolean contains(Collection left, List right) { - // parameter is null, it means that ignore this condition - if (left == null) { - return true; - } - // else, it means that right is empty - if (left.isEmpty()) { - return right.isEmpty(); - } - if (right == null) { - return false; - } - return right.stream().anyMatch(left::contains); - } - private Mono getListedPost(Post post) { Assert.notNull(post, "The post must not be null."); return Mono.just(post) diff --git a/application/src/main/java/run/halo/app/content/impl/SinglePageServiceImpl.java b/application/src/main/java/run/halo/app/content/impl/SinglePageServiceImpl.java index 66128992e..d0bd4199c 100644 --- a/application/src/main/java/run/halo/app/content/impl/SinglePageServiceImpl.java +++ b/application/src/main/java/run/halo/app/content/impl/SinglePageServiceImpl.java @@ -1,15 +1,10 @@ package run.halo.app.content.impl; -import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate; - import java.time.Duration; import java.time.Instant; -import java.util.Collection; -import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.function.Function; -import java.util.function.Predicate; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.dao.OptimisticLockingFailureException; @@ -26,7 +21,6 @@ import run.halo.app.content.ListedSinglePage; import run.halo.app.content.SinglePageQuery; import run.halo.app.content.SinglePageRequest; import run.halo.app.content.SinglePageService; -import run.halo.app.content.SinglePageSorter; import run.halo.app.content.Stats; import run.halo.app.core.extension.content.Post; import run.halo.app.core.extension.content.SinglePage; @@ -81,10 +75,8 @@ public class SinglePageServiceImpl extends AbstractContentService implements Sin @Override public Mono> list(SinglePageQuery query) { - Comparator comparator = - SinglePageSorter.from(query.getSort(), query.getSortOrder()); - return client.list(SinglePage.class, pageListPredicate(query), - comparator, query.getPage(), query.getSize()) + return client.list(SinglePage.class, query.toPredicate(), + query.toComparator(), query.getPage(), query.getSize()) .flatMap(listResult -> Flux.fromStream( listResult.get().map(this::getListedSinglePage) ) @@ -176,48 +168,6 @@ public class SinglePageServiceImpl extends AbstractContentService implements Sin .filter(throwable -> throwable instanceof OptimisticLockingFailureException)); } - Predicate pageListPredicate(SinglePageQuery query) { - Predicate paramPredicate = singlePage -> contains(query.getContributors(), - singlePage.getStatusOrDefault().getContributors()); - - String keyword = query.getKeyword(); - if (keyword != null) { - paramPredicate = paramPredicate.and(page -> { - String excerpt = page.getStatusOrDefault().getExcerpt(); - return StringUtils.containsIgnoreCase(excerpt, keyword) - || StringUtils.containsIgnoreCase(page.getSpec().getSlug(), keyword) - || StringUtils.containsIgnoreCase(page.getSpec().getTitle(), keyword); - }); - } - - Post.PostPhase publishPhase = query.getPublishPhase(); - if (publishPhase != null) { - paramPredicate = paramPredicate.and(page -> { - if (Post.PostPhase.PENDING_APPROVAL.equals(publishPhase)) { - return !page.isPublished() - && Post.PostPhase.PENDING_APPROVAL.name() - .equalsIgnoreCase(page.getStatusOrDefault().getPhase()); - } - // published - if (Post.PostPhase.PUBLISHED.equals(publishPhase)) { - return page.isPublished(); - } - // draft - return !page.isPublished(); - }); - } - - Post.VisibleEnum visible = query.getVisible(); - if (visible != null) { - paramPredicate = - paramPredicate.and(post -> visible.equals(post.getSpec().getVisible())); - } - - Predicate predicate = labelAndFieldSelectorToPredicate(query.getLabelSelector(), - query.getFieldSelector()); - return predicate.and(paramPredicate); - } - private Mono getListedSinglePage(SinglePage singlePage) { Assert.notNull(singlePage, "The singlePage must not be null."); return Mono.just(singlePage) @@ -285,19 +235,4 @@ public class SinglePageServiceImpl extends AbstractContentService implements Sin return contributor; }); } - - boolean contains(Collection left, List right) { - // parameter is null, it means that ignore this condition - if (left == null) { - return true; - } - // else, it means that right is empty - if (left.isEmpty()) { - return right.isEmpty(); - } - if (right == null) { - return false; - } - return right.stream().anyMatch(left::contains); - } } diff --git a/application/src/main/java/run/halo/app/core/extension/endpoint/CommentEndpoint.java b/application/src/main/java/run/halo/app/core/extension/endpoint/CommentEndpoint.java index ff75e4cce..d0f364093 100644 --- a/application/src/main/java/run/halo/app/core/extension/endpoint/CommentEndpoint.java +++ b/application/src/main/java/run/halo/app/core/extension/endpoint/CommentEndpoint.java @@ -96,7 +96,7 @@ public class CommentEndpoint implements CustomEndpoint { } Mono listComments(ServerRequest request) { - CommentQuery commentQuery = new CommentQuery(request.queryParams()); + CommentQuery commentQuery = new CommentQuery(request); return commentService.listComment(commentQuery) .flatMap(listedComments -> ServerResponse.ok().bodyValue(listedComments)); } diff --git a/application/src/main/java/run/halo/app/core/extension/endpoint/PostEndpoint.java b/application/src/main/java/run/halo/app/core/extension/endpoint/PostEndpoint.java index d228a866b..c15247bd6 100644 --- a/application/src/main/java/run/halo/app/core/extension/endpoint/PostEndpoint.java +++ b/application/src/main/java/run/halo/app/core/extension/endpoint/PostEndpoint.java @@ -12,7 +12,6 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springdoc.core.fn.builders.schema.Builder; import org.springdoc.webflux.core.fn.SpringdocRouteBuilder; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.http.MediaType; import org.springframework.retry.RetryException; @@ -49,8 +48,6 @@ public class PostEndpoint implements CustomEndpoint { private final PostService postService; private final ReactiveExtensionClient client; - private final ApplicationEventPublisher eventPublisher; - @Override public RouterFunction endpoint() { final var tag = "api.console.halo.run/v1alpha1/Post"; @@ -277,7 +274,7 @@ public class PostEndpoint implements CustomEndpoint { } Mono listPost(ServerRequest request) { - PostQuery postQuery = new PostQuery(request.queryParams()); + PostQuery postQuery = new PostQuery(request); return postService.listPost(postQuery) .flatMap(listedPosts -> ServerResponse.ok().bodyValue(listedPosts)); } diff --git a/application/src/main/java/run/halo/app/core/extension/endpoint/SinglePageEndpoint.java b/application/src/main/java/run/halo/app/core/extension/endpoint/SinglePageEndpoint.java index cd7725d0b..13eff25f5 100644 --- a/application/src/main/java/run/halo/app/core/extension/endpoint/SinglePageEndpoint.java +++ b/application/src/main/java/run/halo/app/core/extension/endpoint/SinglePageEndpoint.java @@ -229,7 +229,7 @@ public class SinglePageEndpoint implements CustomEndpoint { } Mono listSinglePage(ServerRequest request) { - var listRequest = new SinglePageQuery(request.queryParams()); + var listRequest = new SinglePageQuery(request); return singlePageService.list(listRequest) .flatMap(listedPages -> ServerResponse.ok().bodyValue(listedPages)); } diff --git a/application/src/main/java/run/halo/app/extension/Comparators.java b/application/src/main/java/run/halo/app/extension/Comparators.java index 779840324..d375a3160 100644 --- a/application/src/main/java/run/halo/app/extension/Comparators.java +++ b/application/src/main/java/run/halo/app/extension/Comparators.java @@ -17,4 +17,15 @@ public enum Comparators { return asc ? comparator : comparator.reversed(); } + /** + * Get a nulls comparator. + * + * @param isAscending is ascending + * @return if ascending, return nulls high, else return nulls low + */ + public static Comparator nullsComparator(boolean isAscending) { + return isAscending + ? org.springframework.util.comparator.Comparators.nullsHigh() + : org.springframework.util.comparator.Comparators.nullsLow(); + } } diff --git a/application/src/main/java/run/halo/app/theme/endpoint/SortableRequest.java b/application/src/main/java/run/halo/app/theme/endpoint/SortableRequest.java index d20ad9218..74073d59e 100644 --- a/application/src/main/java/run/halo/app/theme/endpoint/SortableRequest.java +++ b/application/src/main/java/run/halo/app/theme/endpoint/SortableRequest.java @@ -11,9 +11,9 @@ import java.util.stream.Stream; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.data.domain.Sort; -import org.springframework.util.comparator.Comparators; import org.springframework.web.server.ServerWebExchange; import run.halo.app.core.extension.endpoint.SortResolver; +import run.halo.app.extension.Comparators; import run.halo.app.extension.Extension; import run.halo.app.extension.router.IListRequest; @@ -66,9 +66,8 @@ public class SortableRequest extends IListRequest.QueryListRequest { BeanWrapper beanWrapper = new BeanWrapperImpl(extension); return beanWrapper.getPropertyValue(property); }; - Comparator nullsComparator = - direction.isAscending() ? Comparators.nullsLow() : Comparators.nullsHigh(); - Comparator comparator = Comparator.comparing(function, nullsComparator); + Comparator comparator = Comparator.comparing(function, + Comparators.nullsComparator(direction.isAscending())); if (direction.isDescending()) { comparator = comparator.reversed(); } diff --git a/application/src/test/java/run/halo/app/content/impl/PostServiceImplTest.java b/application/src/test/java/run/halo/app/content/PostQueryTest.java similarity index 59% rename from application/src/test/java/run/halo/app/content/impl/PostServiceImplTest.java rename to application/src/test/java/run/halo/app/content/PostQueryTest.java index 975dd1456..5c77a103e 100644 --- a/application/src/test/java/run/halo/app/content/impl/PostServiceImplTest.java +++ b/application/src/test/java/run/halo/app/content/PostQueryTest.java @@ -1,78 +1,72 @@ -package run.halo.app.content.impl; - +package run.halo.app.content; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.reactive.function.server.MockServerRequest; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import run.halo.app.content.PostQuery; -import run.halo.app.content.TestPost; +import org.springframework.web.server.ServerWebExchange; import run.halo.app.core.extension.content.Post; -import run.halo.app.extension.ReactiveExtensionClient; /** - * Tests for {@link PostServiceImpl}. + * Tests for {@link PostQuery}. * * @author guqing - * @since 2.0.0 + * @since 2.6.0 */ @ExtendWith(MockitoExtension.class) -class PostServiceImplTest { - @Mock - private ReactiveExtensionClient client; - - @InjectMocks - private PostServiceImpl postService; +class PostQueryTest { @Test - void listPredicate() { + void toPredicate() { MultiValueMap multiValueMap = new LinkedMultiValueMap<>(); multiValueMap.put("category", List.of("category1", "category2")); - PostQuery postQuery = new PostQuery(multiValueMap); + MockServerRequest request = MockServerRequest.builder() + .queryParams(multiValueMap) + .exchange(mock(ServerWebExchange.class)) + .build(); + PostQuery postQuery = new PostQuery(request); Post post = TestPost.postV1(); post.getSpec().setTags(null); post.getStatusOrDefault().setContributors(null); post.getSpec().setCategories(List.of("category1")); - boolean test = postService.postListPredicate(postQuery).test(post); + boolean test = postQuery.toPredicate().test(post); assertThat(test).isTrue(); post.getSpec().setTags(List.of("tag1")); - test = postService.postListPredicate(postQuery).test(post); + test = postQuery.toPredicate().test(post); assertThat(test).isTrue(); // Do not include tags multiValueMap.put("tag", List.of("tag2")); post.getSpec().setTags(List.of("tag1")); post.getSpec().setCategories(null); - test = postService.postListPredicate(postQuery).test(post); + test = postQuery.toPredicate().test(post); assertThat(test).isFalse(); multiValueMap.put("tag", List.of()); multiValueMap.remove("category"); - postQuery = new PostQuery(multiValueMap); + request = MockServerRequest.builder() + .exchange(mock(ServerWebExchange.class)) + .queryParams(multiValueMap).build(); + postQuery = new PostQuery(request); post.getSpec().setTags(List.of()); - test = postService.postListPredicate(postQuery).test(post); + test = postQuery.toPredicate().test(post); assertThat(test).isTrue(); multiValueMap.put("labelSelector", List.of("hello")); - test = postService.postListPredicate(postQuery).test(post); + test = postQuery.toPredicate().test(post); assertThat(test).isFalse(); post.getMetadata().setLabels(Map.of("hello", "world")); - test = postService.postListPredicate(postQuery).test(post); + test = postQuery.toPredicate().test(post); assertThat(test).isTrue(); } - - @Test - void draftPost() { - - } -} \ No newline at end of file +} diff --git a/application/src/test/java/run/halo/app/content/comment/CommentQueryTest.java b/application/src/test/java/run/halo/app/content/comment/CommentQueryTest.java index 5eee2aada..6fb78b1bd 100644 --- a/application/src/test/java/run/halo/app/content/comment/CommentQueryTest.java +++ b/application/src/test/java/run/halo/app/content/comment/CommentQueryTest.java @@ -1,10 +1,25 @@ package run.halo.app.content.comment; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import lombok.NonNull; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.mock.web.reactive.function.server.MockServerRequest; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; +import run.halo.app.core.extension.content.Comment; +import run.halo.app.extension.Metadata; /** * Tests for {@link CommentQuery}. @@ -12,13 +27,14 @@ import org.springframework.util.MultiValueMap; * @author guqing * @since 2.0.0 */ +@ExtendWith(MockitoExtension.class) class CommentQueryTest { @Test void getKeyword() { MultiValueMap queryParams = new LinkedMultiValueMap<>(); queryParams.add("keyword", "test"); - CommentQuery commentQuery = new CommentQuery(queryParams); + CommentQuery commentQuery = getCommentQuery(queryParams); assertThat(commentQuery.getKeyword()).isEqualTo("test"); queryParams.clear(); @@ -35,7 +51,7 @@ class CommentQueryTest { void getApproved() { MultiValueMap queryParams = new LinkedMultiValueMap<>(); queryParams.add("approved", "true"); - CommentQuery commentQuery = new CommentQuery(queryParams); + CommentQuery commentQuery = getCommentQuery(queryParams); assertThat(commentQuery.getApproved()).isTrue(); queryParams.clear(); @@ -48,11 +64,24 @@ class CommentQueryTest { queryParams.clear(); } + @NonNull + private CommentQuery getCommentQuery(MultiValueMap queryParams) { + ServerWebExchange exchange = mock(ServerWebExchange.class); + MockServerRequest request = MockServerRequest.builder() + .queryParams(queryParams) + .exchange(exchange) + .build(); + ServerHttpRequest httpRequest = mock(ServerHttpRequest.class); + lenient().when(exchange.getRequest()).thenReturn(httpRequest); + lenient().when(httpRequest.getQueryParams()).thenReturn(queryParams); + return new CommentQuery(request); + } + @Test void getHidden() { MultiValueMap queryParams = new LinkedMultiValueMap<>(); queryParams.add("hidden", "true"); - CommentQuery commentQuery = new CommentQuery(queryParams); + CommentQuery commentQuery = getCommentQuery(queryParams); assertThat(commentQuery.getHidden()).isTrue(); queryParams.clear(); @@ -69,7 +98,7 @@ class CommentQueryTest { void getAllowNotification() { MultiValueMap queryParams = new LinkedMultiValueMap<>(); queryParams.add("allowNotification", "true"); - CommentQuery commentQuery = new CommentQuery(queryParams); + CommentQuery commentQuery = getCommentQuery(queryParams); assertThat(commentQuery.getAllowNotification()).isTrue(); queryParams.clear(); @@ -86,7 +115,7 @@ class CommentQueryTest { void getTop() { MultiValueMap queryParams = new LinkedMultiValueMap<>(); queryParams.add("top", "true"); - CommentQuery commentQuery = new CommentQuery(queryParams); + CommentQuery commentQuery = getCommentQuery(queryParams); assertThat(commentQuery.getTop()).isTrue(); queryParams.clear(); @@ -103,7 +132,7 @@ class CommentQueryTest { void getOwnerKind() { MultiValueMap queryParams = new LinkedMultiValueMap<>(); queryParams.add("ownerKind", "test-owner-kind"); - CommentQuery commentQuery = new CommentQuery(queryParams); + CommentQuery commentQuery = getCommentQuery(queryParams); assertThat(commentQuery.getOwnerKind()).isEqualTo("test-owner-kind"); queryParams.clear(); @@ -120,7 +149,7 @@ class CommentQueryTest { void getOwnerName() { MultiValueMap queryParams = new LinkedMultiValueMap<>(); queryParams.add("ownerName", "test-owner-name"); - CommentQuery commentQuery = new CommentQuery(queryParams); + CommentQuery commentQuery = getCommentQuery(queryParams); assertThat(commentQuery.getOwnerName()).isEqualTo("test-owner-name"); queryParams.clear(); @@ -137,7 +166,7 @@ class CommentQueryTest { void getSubjectKind() { MultiValueMap queryParams = new LinkedMultiValueMap<>(); queryParams.add("subjectKind", "test-subject-kind"); - CommentQuery commentQuery = new CommentQuery(queryParams); + CommentQuery commentQuery = getCommentQuery(queryParams); assertThat(commentQuery.getSubjectKind()).isEqualTo("test-subject-kind"); queryParams.clear(); @@ -154,7 +183,7 @@ class CommentQueryTest { void getSubjectName() { MultiValueMap queryParams = new LinkedMultiValueMap<>(); queryParams.add("subjectName", "test-subject-name"); - CommentQuery commentQuery = new CommentQuery(queryParams); + CommentQuery commentQuery = getCommentQuery(queryParams); assertThat(commentQuery.getSubjectName()).isEqualTo("test-subject-name"); queryParams.clear(); @@ -167,37 +196,166 @@ class CommentQueryTest { queryParams.clear(); } - @Test - void getSort() { - MultiValueMap queryParams = new LinkedMultiValueMap<>(); - queryParams.add("sort", CommentSorter.REPLY_COUNT.name()); - CommentQuery commentQuery = new CommentQuery(queryParams); - assertThat(commentQuery.getSort()).isEqualTo(CommentSorter.REPLY_COUNT); - queryParams.clear(); + @Nested + class CommentSortTest { + @Test + void sortByCreateTimeAsc() { + MultiValueMap queryParams = new LinkedMultiValueMap<>(); + queryParams.set("sort", "creationTimestamp,asc"); + CommentQuery commentQuery = getCommentQuery(queryParams); + Comparator createTimeSorter = commentQuery.toComparator(); + List commentNames = comments().stream() + .sorted(createTimeSorter) + .map(comment -> comment.getMetadata().getName()) + .toList(); + assertThat(commentNames).isEqualTo(List.of("B", "C", "A")); + } - queryParams.add("sort", ""); - assertThat(commentQuery.getSort()).isNull(); - queryParams.clear(); + @Test + void sortByCreateTimeDesc() { + MultiValueMap queryParams = new LinkedMultiValueMap<>(); + queryParams.add("sort", "creationTimestamp,desc"); + Comparator createTimeSorter = getCommentQuery(queryParams).toComparator(); + List commentNames = comments().stream() + .sorted(createTimeSorter) + .map(comment -> comment.getMetadata().getName()) + .toList(); + assertThat(commentNames).isEqualTo(List.of("A", "B", "C")); + } - queryParams.add("sort", "nothing"); - assertThat(commentQuery.getSort()).isNull(); - queryParams.clear(); - } + @Test + void sortByReplyCountAsc() { + MultiValueMap queryParams = new LinkedMultiValueMap<>(); + queryParams.add("sort", "replyCount,asc"); + Comparator comparator = getCommentQuery(queryParams).toComparator(); + List commentNames = comments().stream() + .sorted(comparator) + .map(comment -> comment.getMetadata().getName()) + .toList(); + assertThat(commentNames).isEqualTo(List.of("A", "B", "C")); + } - @Test - void getSortOrder() { - MultiValueMap queryParams = new LinkedMultiValueMap<>(); - queryParams.add("sortOrder", "true"); - CommentQuery commentQuery = new CommentQuery(queryParams); - assertThat(commentQuery.getSortOrder()).isTrue(); - queryParams.clear(); + @Test + void sortByReplyCountDesc() { + MultiValueMap queryParams = new LinkedMultiValueMap<>(); + queryParams.add("sort", "replyCount,desc"); + Comparator comparator = getCommentQuery(queryParams).toComparator(); + List commentNames = comments().stream() + .sorted(comparator) + .map(comment -> comment.getMetadata().getName()) + .toList(); + assertThat(commentNames).isEqualTo(List.of("C", "B", "A")); + } - queryParams.add("sortOrder", ""); - assertThat(commentQuery.getSortOrder()).isNull(); - queryParams.clear(); + @Test + void sortByLastReplyTimeAsc() { + MultiValueMap queryParams = new LinkedMultiValueMap<>(); + queryParams.add("sort", "lastReplyTime,asc"); + Comparator comparator = getCommentQuery(queryParams).toComparator(); + List commentNames = comments().stream() + .sorted(comparator) + .map(comment -> comment.getMetadata().getName()) + .toList(); + assertThat(commentNames).isEqualTo(List.of("C", "A", "B")); + } - queryParams.add("sortOrder", null); - assertThat(commentQuery.getSortOrder()).isNull(); - queryParams.clear(); + @Test + void sortByLastReplyTimeDesc() { + MultiValueMap queryParams = new LinkedMultiValueMap<>(); + queryParams.add("sort", "lastReplyTime,desc"); + Comparator comparator = getCommentQuery(queryParams).toComparator(); + List commentNames = comments().stream() + .sorted(comparator) + .map(comment -> comment.getMetadata().getName()) + .toList(); + assertThat(commentNames).isEqualTo(List.of("B", "A", "C")); + } + + @Test + void sortByDefaultDesc() { + MultiValueMap queryParams = new LinkedMultiValueMap<>(); + Comparator comparator = getCommentQuery(queryParams).toComparator(); + List commentNames = comments().stream() + .sorted(comparator) + .map(comment -> comment.getMetadata().getName()) + .toList(); + assertThat(commentNames).isEqualTo(List.of("B", "A", "C")); + + List commentList = commentsIncludeNoReply().stream() + .sorted(comparator) + .map(comment -> comment.getMetadata().getName()) + .toList(); + assertThat(commentList).isEqualTo(List.of("D", "E", "B", "A", "C")); + } + + List comments() { + final Instant now = Instant.now(); + Comment commentA = new Comment(); + commentA.setMetadata(new Metadata()); + commentA.getMetadata().setName("A"); + // create time + commentA.getMetadata().setCreationTimestamp(now.plusSeconds(10)); + commentA.setSpec(new Comment.CommentSpec()); + commentA.getSpec().setCreationTime(commentA.getMetadata().getCreationTimestamp()); + + commentA.setStatus(new Comment.CommentStatus()); + // last reply time + commentA.getStatus().setLastReplyTime(now.plusSeconds(5)); + // reply count + commentA.getStatus().setReplyCount(3); + + Comment commentB = new Comment(); + commentB.setMetadata(new Metadata()); + commentB.getMetadata().setName("B"); + commentB.getMetadata().setCreationTimestamp(now.plusSeconds(5)); + commentB.setSpec(new Comment.CommentSpec()); + commentB.setStatus(new Comment.CommentStatus()); + commentB.getStatus().setLastReplyTime(now.plusSeconds(15)); + commentB.getStatus().setReplyCount(8); + commentB.getSpec().setCreationTime(commentB.getMetadata().getCreationTimestamp()); + + Comment commentC = new Comment(); + commentC.setMetadata(new Metadata()); + commentC.getMetadata().setName("C"); + + commentC.getMetadata().setCreationTimestamp(now.plusSeconds(5)); + + commentC.setSpec(new Comment.CommentSpec()); + commentC.setStatus(new Comment.CommentStatus()); + commentC.getStatus().setLastReplyTime(now.plusSeconds(3)); + commentC.getStatus().setReplyCount(10); + commentC.getSpec().setCreationTime(commentC.getMetadata().getCreationTimestamp()); + + return List.of(commentA, commentB, commentC); + } + + List commentsIncludeNoReply() { + + final Instant now = Instant.now(); + Comment commentD = new Comment(); + commentD.setMetadata(new Metadata()); + commentD.getMetadata().setName("D"); + + commentD.getMetadata().setCreationTimestamp(now.plusSeconds(50)); + + commentD.setSpec(new Comment.CommentSpec()); + commentD.getSpec().setCreationTime(commentD.getMetadata().getCreationTimestamp()); + commentD.setStatus(new Comment.CommentStatus()); + + Comment commentE = new Comment(); + commentE.setMetadata(new Metadata()); + commentE.getMetadata().setName("E"); + + commentE.getMetadata().setCreationTimestamp(now.plusSeconds(20)); + commentE.setSpec(new Comment.CommentSpec()); + commentE.getSpec().setCreationTime(commentE.getMetadata().getCreationTimestamp()); + commentE.setStatus(new Comment.CommentStatus()); + + List comments = new ArrayList<>(comments()); + comments.add(commentD); + comments.add(commentE); + + return comments; + } } } \ No newline at end of file diff --git a/application/src/test/java/run/halo/app/content/comment/CommentServiceImplTest.java b/application/src/test/java/run/halo/app/content/comment/CommentServiceImplTest.java index 09a29b74b..c7b8e965d 100644 --- a/application/src/test/java/run/halo/app/content/comment/CommentServiceImplTest.java +++ b/application/src/test/java/run/halo/app/content/comment/CommentServiceImplTest.java @@ -5,6 +5,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -21,10 +22,13 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.mock.web.reactive.function.server.MockServerRequest; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import run.halo.app.content.TestPost; @@ -115,8 +119,17 @@ class CommentServiceImplTest { when(userService.getUserOrGhost("B-owner")) .thenReturn(Mono.just(createUser("B-owner"))); + ServerWebExchange exchange = mock(ServerWebExchange.class); + MultiValueMap queryParams = new LinkedMultiValueMap<>(); + MockServerRequest request = MockServerRequest.builder() + .queryParams(queryParams) + .exchange(exchange) + .build(); + ServerHttpRequest httpRequest = mock(ServerHttpRequest.class); + when(exchange.getRequest()).thenReturn(httpRequest); + when(httpRequest.getQueryParams()).thenReturn(queryParams); Mono> listResultMono = - commentService.listComment(new CommentQuery(new LinkedMultiValueMap<>())); + commentService.listComment(new CommentQuery(request)); Counter counterA = new Counter(); counterA.setUpvote(3); String commentACounter = MeterUtils.nameOf(Comment.class, "A"); @@ -214,8 +227,11 @@ class CommentServiceImplTest { queryParams.add("subjectKind", "Post"); queryParams.add("subjectName", "fake-post"); - final Predicate predicate = - commentService.commentPredicate(new CommentQuery(queryParams)); + MockServerRequest request = MockServerRequest.builder() + .queryParams(queryParams) + .exchange(mock(ServerWebExchange.class)) + .build(); + final Predicate predicate = new CommentQuery(request).toPredicate(); Comment comment = comment("A"); comment.getSpec().setRaw("hello-world"); @@ -233,8 +249,11 @@ class CommentServiceImplTest { queryParams.remove("keyword"); queryParams.add("keyword", "nothing"); - final Predicate predicateTwo = - commentService.commentPredicate(new CommentQuery(queryParams)); + request = MockServerRequest.builder() + .queryParams(queryParams) + .exchange(mock(ServerWebExchange.class)) + .build(); + final Predicate predicateTwo = new CommentQuery(request).toPredicate(); assertThat(predicateTwo.test(comment)).isFalse(); } diff --git a/application/src/test/java/run/halo/app/content/comment/CommentSorterTest.java b/application/src/test/java/run/halo/app/content/comment/CommentSorterTest.java deleted file mode 100644 index 0fa7bc020..000000000 --- a/application/src/test/java/run/halo/app/content/comment/CommentSorterTest.java +++ /dev/null @@ -1,190 +0,0 @@ -package run.halo.app.content.comment; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import org.junit.jupiter.api.Test; -import run.halo.app.core.extension.content.Comment; -import run.halo.app.extension.Metadata; - -/** - * Tests for {@link CommentSorter}. - * - * @author guqing - * @since 2.0.0 - */ -class CommentSorterTest { - - @Test - void sortByCreateTimeAsc() { - Comparator createTimeSorter = CommentSorter.from(CommentSorter.CREATE_TIME); - List commentNames = comments().stream() - .sorted(createTimeSorter) - .map(comment -> comment.getMetadata().getName()) - .toList(); - assertThat(commentNames).isEqualTo(List.of("B", "C", "A")); - - createTimeSorter = CommentSorter.from(CommentSorter.CREATE_TIME, true); - commentNames = comments().stream() - .sorted(createTimeSorter) - .map(comment -> comment.getMetadata().getName()) - .toList(); - assertThat(commentNames).isEqualTo(List.of("B", "C", "A")); - } - - @Test - void sortByCreateTimeDesc() { - Comparator createTimeSorter = CommentSorter.from(CommentSorter.CREATE_TIME, false); - List commentNames = comments().stream() - .sorted(createTimeSorter) - .map(comment -> comment.getMetadata().getName()) - .toList(); - assertThat(commentNames).isEqualTo(List.of("A", "C", "B")); - } - - @Test - void sortByReplyCountAsc() { - Comparator createTimeSorter = CommentSorter.from(CommentSorter.REPLY_COUNT); - List commentNames = comments().stream() - .sorted(createTimeSorter) - .map(comment -> comment.getMetadata().getName()) - .toList(); - assertThat(commentNames).isEqualTo(List.of("A", "B", "C")); - - createTimeSorter = CommentSorter.from(CommentSorter.REPLY_COUNT, true); - commentNames = comments().stream() - .sorted(createTimeSorter) - .map(comment -> comment.getMetadata().getName()) - .toList(); - assertThat(commentNames).isEqualTo(List.of("A", "B", "C")); - } - - @Test - void sortByReplyCountDesc() { - Comparator createTimeSorter = CommentSorter.from(CommentSorter.REPLY_COUNT, false); - List commentNames = comments().stream() - .sorted(createTimeSorter) - .map(comment -> comment.getMetadata().getName()) - .toList(); - assertThat(commentNames).isEqualTo(List.of("C", "B", "A")); - } - - @Test - void sortByLastReplyTimeAsc() { - Comparator createTimeSorter = CommentSorter.from(CommentSorter.LAST_REPLY_TIME); - List commentNames = comments().stream() - .sorted(createTimeSorter) - .map(comment -> comment.getMetadata().getName()) - .toList(); - assertThat(commentNames).isEqualTo(List.of("C", "A", "B")); - - createTimeSorter = CommentSorter.from(CommentSorter.LAST_REPLY_TIME, true); - commentNames = comments().stream() - .sorted(createTimeSorter) - .map(comment -> comment.getMetadata().getName()) - .toList(); - assertThat(commentNames).isEqualTo(List.of("C", "A", "B")); - } - - @Test - void sortByLastReplyTimeDesc() { - Comparator createTimeSorter = - CommentSorter.from(CommentSorter.LAST_REPLY_TIME, false); - List commentNames = comments().stream() - .sorted(createTimeSorter) - .map(comment -> comment.getMetadata().getName()) - .toList(); - assertThat(commentNames).isEqualTo(List.of("B", "A", "C")); - } - - @Test - void sortByDefaultDesc() { - Comparator defaultComparator = CommentSorter.lastReplyTimeComparator().reversed(); - List commentNames = comments().stream() - .sorted(defaultComparator) - .map(comment -> comment.getMetadata().getName()) - .toList(); - assertThat(commentNames).isEqualTo(List.of("B", "A", "C")); - - - List commentList = commentsIncludeNoReply().stream() - .sorted(defaultComparator) - .map(comment -> comment.getMetadata().getName()) - .toList(); - assertThat(commentList).isEqualTo(List.of("D", "E", "B", "A", "C")); - } - - List comments() { - final Instant now = Instant.now(); - Comment commentA = new Comment(); - commentA.setMetadata(new Metadata()); - commentA.getMetadata().setName("A"); - // create time - commentA.getMetadata().setCreationTimestamp(now.plusSeconds(10)); - commentA.setSpec(new Comment.CommentSpec()); - commentA.getSpec().setCreationTime(commentA.getMetadata().getCreationTimestamp()); - - commentA.setStatus(new Comment.CommentStatus()); - // last reply time - commentA.getStatus().setLastReplyTime(now.plusSeconds(5)); - // reply count - commentA.getStatus().setReplyCount(3); - - Comment commentB = new Comment(); - commentB.setMetadata(new Metadata()); - commentB.getMetadata().setName("B"); - commentB.getMetadata().setCreationTimestamp(now.plusSeconds(5)); - commentB.setSpec(new Comment.CommentSpec()); - commentB.setStatus(new Comment.CommentStatus()); - commentB.getStatus().setLastReplyTime(now.plusSeconds(15)); - commentB.getStatus().setReplyCount(8); - commentB.getSpec().setCreationTime(commentB.getMetadata().getCreationTimestamp()); - - Comment commentC = new Comment(); - commentC.setMetadata(new Metadata()); - commentC.getMetadata().setName("C"); - - commentC.getMetadata().setCreationTimestamp(now.plusSeconds(5)); - - commentC.setSpec(new Comment.CommentSpec()); - commentC.setStatus(new Comment.CommentStatus()); - commentC.getStatus().setLastReplyTime(now.plusSeconds(3)); - commentC.getStatus().setReplyCount(10); - commentC.getSpec().setCreationTime(commentC.getMetadata().getCreationTimestamp()); - - return List.of(commentA, commentB, commentC); - } - - List commentsIncludeNoReply() { - - final Instant now = Instant.now(); - Comment commentD = new Comment(); - commentD.setMetadata(new Metadata()); - commentD.getMetadata().setName("D"); - - commentD.getMetadata().setCreationTimestamp(now.plusSeconds(50)); - - commentD.setSpec(new Comment.CommentSpec()); - commentD.getSpec().setCreationTime(commentD.getMetadata().getCreationTimestamp()); - commentD.setStatus(new Comment.CommentStatus()); - - Comment commentE = new Comment(); - commentE.setMetadata(new Metadata()); - commentE.getMetadata().setName("E"); - - commentE.getMetadata().setCreationTimestamp(now.plusSeconds(20)); - commentE.setSpec(new Comment.CommentSpec()); - commentE.getSpec().setCreationTime(commentE.getMetadata().getCreationTimestamp()); - commentE.setStatus(new Comment.CommentStatus()); - - List comments = new ArrayList<>(comments()); - comments.add(commentD); - comments.add(commentE); - - return comments; - - } -} \ No newline at end of file diff --git a/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-comment-api.ts b/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-comment-api.ts index 012cac8a8..15335c66d 100644 --- a/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-comment-api.ts +++ b/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-comment-api.ts @@ -189,8 +189,7 @@ export const ApiConsoleHaloRunV1alpha1CommentApiAxiosParamCreator = function ( * @param {string} [ownerName] Commenter name. * @param {number} [page] The page number. Zero indicates no page. * @param {number} [size] Size of one page. Zero indicates no limit. - * @param {'LAST_REPLY_TIME' | 'REPLY_COUNT' | 'CREATE_TIME'} [sort] Comment collation. - * @param {boolean} [sortOrder] ascending order If it is true; otherwise, it is in descending order. + * @param {Array} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,replyCount,lastReplyTime * @param {string} [subjectKind] Comment subject kind. * @param {string} [subjectName] Comment subject name. * @param {boolean} [top] Comment top display. @@ -208,8 +207,7 @@ export const ApiConsoleHaloRunV1alpha1CommentApiAxiosParamCreator = function ( ownerName?: string, page?: number, size?: number, - sort?: "LAST_REPLY_TIME" | "REPLY_COUNT" | "CREATE_TIME", - sortOrder?: boolean, + sort?: Array, subjectKind?: string, subjectName?: string, top?: boolean, @@ -279,12 +277,8 @@ export const ApiConsoleHaloRunV1alpha1CommentApiAxiosParamCreator = function ( localVarQueryParameter["size"] = size; } - if (sort !== undefined) { - localVarQueryParameter["sort"] = sort; - } - - if (sortOrder !== undefined) { - localVarQueryParameter["sortOrder"] = sortOrder; + if (sort) { + localVarQueryParameter["sort"] = Array.from(sort); } if (subjectKind !== undefined) { @@ -387,8 +381,7 @@ export const ApiConsoleHaloRunV1alpha1CommentApiFp = function ( * @param {string} [ownerName] Commenter name. * @param {number} [page] The page number. Zero indicates no page. * @param {number} [size] Size of one page. Zero indicates no limit. - * @param {'LAST_REPLY_TIME' | 'REPLY_COUNT' | 'CREATE_TIME'} [sort] Comment collation. - * @param {boolean} [sortOrder] ascending order If it is true; otherwise, it is in descending order. + * @param {Array} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,replyCount,lastReplyTime * @param {string} [subjectKind] Comment subject kind. * @param {string} [subjectName] Comment subject name. * @param {boolean} [top] Comment top display. @@ -406,8 +399,7 @@ export const ApiConsoleHaloRunV1alpha1CommentApiFp = function ( ownerName?: string, page?: number, size?: number, - sort?: "LAST_REPLY_TIME" | "REPLY_COUNT" | "CREATE_TIME", - sortOrder?: boolean, + sort?: Array, subjectKind?: string, subjectName?: string, top?: boolean, @@ -430,7 +422,6 @@ export const ApiConsoleHaloRunV1alpha1CommentApiFp = function ( page, size, sort, - sortOrder, subjectKind, subjectName, top, @@ -512,7 +503,6 @@ export const ApiConsoleHaloRunV1alpha1CommentApiFactory = function ( requestParameters.page, requestParameters.size, requestParameters.sort, - requestParameters.sortOrder, requestParameters.subjectKind, requestParameters.subjectName, requestParameters.top, @@ -635,18 +625,11 @@ export interface ApiConsoleHaloRunV1alpha1CommentApiListCommentsRequest { readonly size?: number; /** - * Comment collation. - * @type {'LAST_REPLY_TIME' | 'REPLY_COUNT' | 'CREATE_TIME'} + * Sort property and direction of the list result. Supported fields: creationTimestamp,replyCount,lastReplyTime + * @type {Array} * @memberof ApiConsoleHaloRunV1alpha1CommentApiListComments */ - readonly sort?: "LAST_REPLY_TIME" | "REPLY_COUNT" | "CREATE_TIME"; - - /** - * ascending order If it is true; otherwise, it is in descending order. - * @type {boolean} - * @memberof ApiConsoleHaloRunV1alpha1CommentApiListComments - */ - readonly sortOrder?: boolean; + readonly sort?: Array; /** * Comment subject kind. @@ -737,7 +720,6 @@ export class ApiConsoleHaloRunV1alpha1CommentApi extends BaseAPI { requestParameters.page, requestParameters.size, requestParameters.sort, - requestParameters.sortOrder, requestParameters.subjectKind, requestParameters.subjectName, requestParameters.top, diff --git a/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-post-api.ts b/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-post-api.ts index c27c1d4e4..f2f820624 100644 --- a/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-post-api.ts +++ b/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-post-api.ts @@ -230,8 +230,7 @@ export const ApiConsoleHaloRunV1alpha1PostApiAxiosParamCreator = function ( * @param {number} [page] The page number. Zero indicates no page. * @param {'DRAFT' | 'PENDING_APPROVAL' | 'PUBLISHED' | 'FAILED'} [publishPhase] * @param {number} [size] Size of one page. Zero indicates no limit. - * @param {'PUBLISH_TIME' | 'CREATE_TIME'} [sort] Post collation. - * @param {boolean} [sortOrder] ascending order If it is true; otherwise, it is in descending order. + * @param {Array} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime * @param {Array} [tag] * @param {'PUBLIC' | 'INTERNAL' | 'PRIVATE'} [visible] * @param {*} [options] Override http request option. @@ -246,8 +245,7 @@ export const ApiConsoleHaloRunV1alpha1PostApiAxiosParamCreator = function ( page?: number, publishPhase?: "DRAFT" | "PENDING_APPROVAL" | "PUBLISHED" | "FAILED", size?: number, - sort?: "PUBLISH_TIME" | "CREATE_TIME", - sortOrder?: boolean, + sort?: Array, tag?: Array, visible?: "PUBLIC" | "INTERNAL" | "PRIVATE", options: AxiosRequestConfig = {} @@ -308,12 +306,8 @@ export const ApiConsoleHaloRunV1alpha1PostApiAxiosParamCreator = function ( localVarQueryParameter["size"] = size; } - if (sort !== undefined) { - localVarQueryParameter["sort"] = sort; - } - - if (sortOrder !== undefined) { - localVarQueryParameter["sortOrder"] = sortOrder; + if (sort) { + localVarQueryParameter["sort"] = Array.from(sort); } if (tag) { @@ -724,8 +718,7 @@ export const ApiConsoleHaloRunV1alpha1PostApiFp = function ( * @param {number} [page] The page number. Zero indicates no page. * @param {'DRAFT' | 'PENDING_APPROVAL' | 'PUBLISHED' | 'FAILED'} [publishPhase] * @param {number} [size] Size of one page. Zero indicates no limit. - * @param {'PUBLISH_TIME' | 'CREATE_TIME'} [sort] Post collation. - * @param {boolean} [sortOrder] ascending order If it is true; otherwise, it is in descending order. + * @param {Array} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime * @param {Array} [tag] * @param {'PUBLIC' | 'INTERNAL' | 'PRIVATE'} [visible] * @param {*} [options] Override http request option. @@ -740,8 +733,7 @@ export const ApiConsoleHaloRunV1alpha1PostApiFp = function ( page?: number, publishPhase?: "DRAFT" | "PENDING_APPROVAL" | "PUBLISHED" | "FAILED", size?: number, - sort?: "PUBLISH_TIME" | "CREATE_TIME", - sortOrder?: boolean, + sort?: Array, tag?: Array, visible?: "PUBLIC" | "INTERNAL" | "PRIVATE", options?: AxiosRequestConfig @@ -758,7 +750,6 @@ export const ApiConsoleHaloRunV1alpha1PostApiFp = function ( publishPhase, size, sort, - sortOrder, tag, visible, options @@ -972,7 +963,6 @@ export const ApiConsoleHaloRunV1alpha1PostApiFactory = function ( requestParameters.publishPhase, requestParameters.size, requestParameters.sort, - requestParameters.sortOrder, requestParameters.tag, requestParameters.visible, options @@ -1169,18 +1159,11 @@ export interface ApiConsoleHaloRunV1alpha1PostApiListPostsRequest { readonly size?: number; /** - * Post collation. - * @type {'PUBLISH_TIME' | 'CREATE_TIME'} + * Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime + * @type {Array} * @memberof ApiConsoleHaloRunV1alpha1PostApiListPosts */ - readonly sort?: "PUBLISH_TIME" | "CREATE_TIME"; - - /** - * ascending order If it is true; otherwise, it is in descending order. - * @type {boolean} - * @memberof ApiConsoleHaloRunV1alpha1PostApiListPosts - */ - readonly sortOrder?: boolean; + readonly sort?: Array; /** * @@ -1365,7 +1348,6 @@ export class ApiConsoleHaloRunV1alpha1PostApi extends BaseAPI { requestParameters.publishPhase, requestParameters.size, requestParameters.sort, - requestParameters.sortOrder, requestParameters.tag, requestParameters.visible, options diff --git a/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-single-page-api.ts b/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-single-page-api.ts index fe594681e..ff8940004 100644 --- a/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-single-page-api.ts +++ b/console/packages/api-client/src/api/api-console-halo-run-v1alpha1-single-page-api.ts @@ -234,8 +234,7 @@ export const ApiConsoleHaloRunV1alpha1SinglePageApiAxiosParamCreator = * @param {number} [page] The page number. Zero indicates no page. * @param {'DRAFT' | 'PENDING_APPROVAL' | 'PUBLISHED' | 'FAILED'} [publishPhase] * @param {number} [size] Size of one page. Zero indicates no limit. - * @param {'PUBLISH_TIME' | 'CREATE_TIME'} [sort] SinglePage collation. - * @param {boolean} [sortOrder] ascending order If it is true; otherwise, it is in descending order. + * @param {Array} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime * @param {'PUBLIC' | 'INTERNAL' | 'PRIVATE'} [visible] * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -248,8 +247,7 @@ export const ApiConsoleHaloRunV1alpha1SinglePageApiAxiosParamCreator = page?: number, publishPhase?: "DRAFT" | "PENDING_APPROVAL" | "PUBLISHED" | "FAILED", size?: number, - sort?: "PUBLISH_TIME" | "CREATE_TIME", - sortOrder?: boolean, + sort?: Array, visible?: "PUBLIC" | "INTERNAL" | "PRIVATE", options: AxiosRequestConfig = {} ): Promise => { @@ -305,12 +303,8 @@ export const ApiConsoleHaloRunV1alpha1SinglePageApiAxiosParamCreator = localVarQueryParameter["size"] = size; } - if (sort !== undefined) { - localVarQueryParameter["sort"] = sort; - } - - if (sortOrder !== undefined) { - localVarQueryParameter["sortOrder"] = sortOrder; + if (sort) { + localVarQueryParameter["sort"] = Array.from(sort); } if (visible !== undefined) { @@ -612,8 +606,7 @@ export const ApiConsoleHaloRunV1alpha1SinglePageApiFp = function ( * @param {number} [page] The page number. Zero indicates no page. * @param {'DRAFT' | 'PENDING_APPROVAL' | 'PUBLISHED' | 'FAILED'} [publishPhase] * @param {number} [size] Size of one page. Zero indicates no limit. - * @param {'PUBLISH_TIME' | 'CREATE_TIME'} [sort] SinglePage collation. - * @param {boolean} [sortOrder] ascending order If it is true; otherwise, it is in descending order. + * @param {Array} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime * @param {'PUBLIC' | 'INTERNAL' | 'PRIVATE'} [visible] * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -626,8 +619,7 @@ export const ApiConsoleHaloRunV1alpha1SinglePageApiFp = function ( page?: number, publishPhase?: "DRAFT" | "PENDING_APPROVAL" | "PUBLISHED" | "FAILED", size?: number, - sort?: "PUBLISH_TIME" | "CREATE_TIME", - sortOrder?: boolean, + sort?: Array, visible?: "PUBLIC" | "INTERNAL" | "PRIVATE", options?: AxiosRequestConfig ): Promise< @@ -645,7 +637,6 @@ export const ApiConsoleHaloRunV1alpha1SinglePageApiFp = function ( publishPhase, size, sort, - sortOrder, visible, options ); @@ -807,7 +798,6 @@ export const ApiConsoleHaloRunV1alpha1SinglePageApiFactory = function ( requestParameters.publishPhase, requestParameters.size, requestParameters.sort, - requestParameters.sortOrder, requestParameters.visible, options ) @@ -964,18 +954,11 @@ export interface ApiConsoleHaloRunV1alpha1SinglePageApiListSinglePagesRequest { readonly size?: number; /** - * SinglePage collation. - * @type {'PUBLISH_TIME' | 'CREATE_TIME'} + * Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime + * @type {Array} * @memberof ApiConsoleHaloRunV1alpha1SinglePageApiListSinglePages */ - readonly sort?: "PUBLISH_TIME" | "CREATE_TIME"; - - /** - * ascending order If it is true; otherwise, it is in descending order. - * @type {boolean} - * @memberof ApiConsoleHaloRunV1alpha1SinglePageApiListSinglePages - */ - readonly sortOrder?: boolean; + readonly sort?: Array; /** * @@ -1117,7 +1100,6 @@ export class ApiConsoleHaloRunV1alpha1SinglePageApi extends BaseAPI { requestParameters.publishPhase, requestParameters.size, requestParameters.sort, - requestParameters.sortOrder, requestParameters.visible, options ) diff --git a/console/src/locales/en.yaml b/console/src/locales/en.yaml index c914a7e2a..17160f20b 100644 --- a/console/src/locales/en.yaml +++ b/console/src/locales/en.yaml @@ -427,10 +427,12 @@ core: result: "Owner: {owner}" sort: items: - default: Default - last_reply_time: Last reply time - reply_count: Reply count - creation_time: Creation time + last_reply_time_desc: Recent reply + last_reply_time_asc: Earlier reply + reply_count_desc: More replies + reply_count_asc: Fewer replies + create_time_desc: Latest Created + create_time_asc: Earliest Created list: fields: reply_count: "{count} Replies" diff --git a/console/src/locales/zh-CN.yaml b/console/src/locales/zh-CN.yaml index 70f1659b4..9beca1fbf 100644 --- a/console/src/locales/zh-CN.yaml +++ b/console/src/locales/zh-CN.yaml @@ -427,10 +427,12 @@ core: result: "评论者:{owner}" sort: items: - default: 默认 - last_reply_time: 最后回复时间 - reply_count: 回复数 - creation_time: 创建时间 + last_reply_time_desc: 较近回复 + last_reply_time_asc: 较早回复 + reply_count_desc: 较多回复数 + reply_count_asc: 较少回复数 + create_time_desc: 较近创建 + create_time_asc: 较早创建 list: fields: reply_count: "{count} 条回复" diff --git a/console/src/locales/zh-TW.yaml b/console/src/locales/zh-TW.yaml index 825b4fdd0..3a10d16d8 100644 --- a/console/src/locales/zh-TW.yaml +++ b/console/src/locales/zh-TW.yaml @@ -427,10 +427,12 @@ core: result: "留言者:{owner}" sort: items: - default: 預設 - last_reply_time: 最後回覆時間 - reply_count: 回覆數 - creation_time: 創建時間 + last_reply_time_desc: 較近回覆 + last_reply_time_asc: 較早回覆 + reply_count_desc: 較多回覆數 + reply_count_asc: 較少回覆數 + create_time_desc: 較近創建 + create_time_asc: 較早創建 list: fields: reply_count: "{count} 條回覆" diff --git a/console/src/modules/contents/comments/CommentList.vue b/console/src/modules/contents/comments/CommentList.vue index d320c81e9..25fca9c9a 100644 --- a/console/src/modules/contents/comments/CommentList.vue +++ b/console/src/modules/contents/comments/CommentList.vue @@ -34,6 +34,12 @@ const selectedCommentNames = ref([]); const keyword = ref(""); // Filters + +interface SortItem { + label: string; + sort: string; +} + const ApprovedFilterItems: { label: string; value?: boolean }[] = [ { label: t("core.comment.filters.status.items.all"), @@ -49,27 +55,30 @@ const ApprovedFilterItems: { label: string; value?: boolean }[] = [ }, ]; -type Sort = "LAST_REPLY_TIME" | "REPLY_COUNT" | "CREATE_TIME"; - -const SortFilterItems: { - label: string; - value?: Sort; -}[] = [ +const SortItems: SortItem[] = [ { - label: t("core.comment.filters.sort.items.default"), - value: undefined, + label: t("core.comment.filters.sort.items.last_reply_time_desc"), + sort: "lastReplyTime,desc", }, { - label: t("core.comment.filters.sort.items.last_reply_time"), - value: "LAST_REPLY_TIME", + label: t("core.comment.filters.sort.items.last_reply_time_asc"), + sort: "lastReplyTime,asc", }, { - label: t("core.comment.filters.sort.items.reply_count"), - value: "REPLY_COUNT", + label: t("core.comment.filters.sort.items.reply_count_desc"), + sort: "replyCount,desc", }, { - label: t("core.comment.filters.sort.items.creation_time"), - value: "CREATE_TIME", + label: t("core.comment.filters.sort.items.reply_count_asc"), + sort: "replyCount,asc", + }, + { + label: t("core.comment.filters.sort.items.create_time_desc"), + sort: "creationTimestamp,desc", + }, + { + label: t("core.comment.filters.sort.items.create_time_asc"), + sort: "creationTimestamp,asc", }, ]; @@ -77,10 +86,7 @@ const selectedApprovedFilterItem = ref<{ label: string; value?: boolean }>( ApprovedFilterItems[0] ); -const selectedSortFilterItem = ref<{ - label: string; - value?: Sort; -}>(SortFilterItems[0]); +const selectedSortItem = ref(); const selectedUser = ref(); @@ -93,11 +99,8 @@ const handleApprovedFilterItemChange = (filterItem: { page.value = 1; }; -const handleSortFilterItemChange = (filterItem: { - label: string; - value?: Sort; -}) => { - selectedSortFilterItem.value = filterItem; +const handleSortItemChange = (sortItem: SortItem) => { + selectedSortItem.value = sortItem; selectedCommentNames.value = []; page.value = 1; }; @@ -123,7 +126,7 @@ function handleClearKeyword() { const hasFilters = computed(() => { return ( selectedApprovedFilterItem.value.value !== undefined || - selectedSortFilterItem.value.value !== undefined || + selectedSortItem.value || selectedUser.value || keyword.value ); @@ -131,7 +134,7 @@ const hasFilters = computed(() => { function handleClearFilters() { selectedApprovedFilterItem.value = ApprovedFilterItems[0]; - selectedSortFilterItem.value = SortFilterItems[0]; + selectedSortItem.value = undefined; selectedUser.value = undefined; keyword.value = ""; page.value = 1; @@ -152,7 +155,7 @@ const { page, size, selectedApprovedFilterItem, - selectedSortFilterItem, + selectedSortItem, selectedUser, keyword, ], @@ -161,7 +164,7 @@ const { page: page.value, size: size.value, approved: selectedApprovedFilterItem.value.value, - sort: selectedSortFilterItem.value.value, + sort: [selectedSortItem.value?.sort].filter(Boolean) as string[], keyword: keyword.value, ownerName: selectedUser.value?.metadata.name, }); @@ -355,12 +358,12 @@ const handleApproveInBatch = async () => { {{ $t("core.common.filters.results.sort", { - sort: selectedSortFilterItem.label, + sort: selectedSortItem.label, }) }} @@ -437,12 +440,10 @@ const handleApproveInBatch = async () => {