refactor: sorting parameters to maintain a unified API style (#3956)

#### 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 的风格
```
pull/3963/head
guqing 2023-05-25 20:42:17 +08:00 committed by GitHub
parent a6c923d83d
commit d5f6dc2207
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 777 additions and 853 deletions

View File

@ -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<String, String> 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<Post> toComparator() {
var sort = getSort();
var creationTimestampOrder = sort.getOrderFor("creationTimestamp");
List<Comparator<Post>> comparators = new ArrayList<>();
if (creationTimestampOrder != null) {
Comparator<Post> comparator =
comparing(post -> post.getMetadata().getCreationTimestamp());
if (creationTimestampOrder.isDescending()) {
comparator = comparator.reversed();
}
comparators.add(comparator);
}
var publishTimeOrder = sort.getOrderFor("publishTime");
if (publishTimeOrder != null) {
Comparator<Object> nullsComparator = publishTimeOrder.isAscending()
? org.springframework.util.comparator.Comparators.nullsLow()
: org.springframework.util.comparator.Comparators.nullsHigh();
Comparator<Post> 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<Post> toPredicate() {
Predicate<Post> 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<Post> predicate = labelAndFieldSelectorToPredicate(getLabelSelector(),
getFieldSelector());
return predicate.and(paramPredicate);
}
boolean contains(Collection<String> left, List<String> 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);
}
}

View File

@ -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<String, String> 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<SinglePage> toComparator() {
var sort = getSort();
var creationTimestampOrder = sort.getOrderFor("creationTimestamp");
List<Comparator<SinglePage>> comparators = new ArrayList<>();
if (creationTimestampOrder != null) {
Comparator<SinglePage> comparator =
comparing(page -> page.getMetadata().getCreationTimestamp());
if (creationTimestampOrder.isDescending()) {
comparator = comparator.reversed();
}
comparators.add(comparator);
}
var publishTimeOrder = sort.getOrderFor("publishTime");
if (publishTimeOrder != null) {
Comparator<Object> nullsComparator = publishTimeOrder.isAscending()
? org.springframework.util.comparator.Comparators.nullsLow()
: org.springframework.util.comparator.Comparators.nullsHigh();
Comparator<SinglePage> 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<SinglePage> toPredicate() {
Predicate<SinglePage> 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<SinglePage> predicate = labelAndFieldSelectorToPredicate(getLabelSelector(),
getFieldSelector());
return predicate.and(paramPredicate);
}
boolean contains(Collection<String> left, List<String> 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);
}
}

View File

@ -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<SinglePage, String> 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<SinglePage> 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<SinglePage> from(SinglePageSorter sorter) {
if (sorter == null) {
return defaultComparator();
}
if (CREATE_TIME.equals(sorter)) {
Function<SinglePage, Instant> comparatorFunc =
page -> page.getMetadata().getCreationTimestamp();
return Comparator.comparing(comparatorFunc)
.thenComparing(name);
}
if (PUBLISH_TIME.equals(sorter)) {
Function<SinglePage, Instant> 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<SinglePage> defaultComparator() {
Function<SinglePage, Instant> createTime =
page -> page.getMetadata().getCreationTimestamp();
return Comparator.comparing(createTime)
.thenComparing(name);
}
}

View File

@ -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<String, String> queryParams) {
super(queryParams);
private final ServerWebExchange exchange;
static final Function<Comment, Instant> 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<Comment> toComparator() {
var sort = getSort();
var creationTimestampOrder = sort.getOrderFor("creationTimestamp");
List<Comparator<Comment>> comparators = new ArrayList<>();
if (creationTimestampOrder != null) {
Comparator<Comment> comparator =
comparing(comment -> comment.getMetadata().getCreationTimestamp());
if (creationTimestampOrder.isDescending()) {
comparator = comparator.reversed();
}
comparators.add(comparator);
}
var replyCountOrder = sort.getOrderFor("replyCount");
if (replyCountOrder != null) {
Comparator<Comment> 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<Comment> 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<Comment> toPredicate() {
Predicate<Comment> 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<Extension> labelAndFieldSelectorPredicate =
labelAndFieldSelectorToPredicate(getLabelSelector(),
getFieldSelector());
return predicate.and(labelAndFieldSelectorPredicate);
}
private Boolean convertBooleanOrNull(String value) {

View File

@ -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<ListResult<ListedComment>> listComment(CommentQuery commentQuery) {
Comparator<Comment> 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<Comment> commentPredicate(CommentQuery query) {
Predicate<Comment> 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<Extension> labelAndFieldSelectorPredicate =
labelAndFieldSelectorToPredicate(query.getLabelSelector(),
query.getFieldSelector());
return predicate.and(labelAndFieldSelectorPredicate);
}
}

View File

@ -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<Comment, String> name = comment -> comment.getMetadata().getName();
static Comparator<Comment> from(CommentSorter sorter, Boolean ascending) {
if (Objects.equals(true, ascending)) {
return from(sorter);
}
return from(sorter).reversed();
}
static Comparator<Comment> from(CommentSorter sorter) {
if (sorter == null) {
return lastReplyTimeComparator();
}
if (CREATE_TIME.equals(sorter)) {
Function<Comment, Instant> comparatorFunc =
comment -> comment.getSpec().getCreationTime();
return Comparator.comparing(comparatorFunc, Comparators.nullsLow())
.thenComparing(name);
}
if (REPLY_COUNT.equals(sorter)) {
Function<Comment, Integer> comparatorFunc =
comment -> comment.getStatusOrDefault().getReplyCount();
return Comparator.comparing(comparatorFunc, Comparators.nullsLow())
.thenComparing(name);
}
if (LAST_REPLY_TIME.equals(sorter)) {
Function<Comment, Instant> 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<Comment> lastReplyTimeComparator() {
Function<Comment, Instant> comparatorFunc =
comment -> {
Instant lastReplyTime = comment.getStatusOrDefault().getLastReplyTime();
return Optional.ofNullable(lastReplyTime)
.orElse(comment.getSpec().getCreationTime());
};
return Comparator.comparing(comparatorFunc, Comparators.nullsLow())
.thenComparing(name);
}
}

View File

@ -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<ListResult<ListedPost>> listPost(PostQuery query) {
Comparator<Post> 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<Post> postListPredicate(PostQuery query) {
Predicate<Post> 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<Post> predicate = labelAndFieldSelectorToPredicate(query.getLabelSelector(),
query.getFieldSelector());
return predicate.and(paramPredicate);
}
boolean contains(Collection<String> left, List<String> 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<ListedPost> getListedPost(Post post) {
Assert.notNull(post, "The post must not be null.");
return Mono.just(post)

View File

@ -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<ListResult<ListedSinglePage>> list(SinglePageQuery query) {
Comparator<SinglePage> 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<SinglePage> pageListPredicate(SinglePageQuery query) {
Predicate<SinglePage> 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<SinglePage> predicate = labelAndFieldSelectorToPredicate(query.getLabelSelector(),
query.getFieldSelector());
return predicate.and(paramPredicate);
}
private Mono<ListedSinglePage> 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<String> left, List<String> 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);
}
}

View File

@ -96,7 +96,7 @@ public class CommentEndpoint implements CustomEndpoint {
}
Mono<ServerResponse> listComments(ServerRequest request) {
CommentQuery commentQuery = new CommentQuery(request.queryParams());
CommentQuery commentQuery = new CommentQuery(request);
return commentService.listComment(commentQuery)
.flatMap(listedComments -> ServerResponse.ok().bodyValue(listedComments));
}

View File

@ -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<ServerResponse> endpoint() {
final var tag = "api.console.halo.run/v1alpha1/Post";
@ -277,7 +274,7 @@ public class PostEndpoint implements CustomEndpoint {
}
Mono<ServerResponse> listPost(ServerRequest request) {
PostQuery postQuery = new PostQuery(request.queryParams());
PostQuery postQuery = new PostQuery(request);
return postService.listPost(postQuery)
.flatMap(listedPosts -> ServerResponse.ok().bodyValue(listedPosts));
}

View File

@ -229,7 +229,7 @@ public class SinglePageEndpoint implements CustomEndpoint {
}
Mono<ServerResponse> listSinglePage(ServerRequest request) {
var listRequest = new SinglePageQuery(request.queryParams());
var listRequest = new SinglePageQuery(request);
return singlePageService.list(listRequest)
.flatMap(listedPages -> ServerResponse.ok().bodyValue(listedPages));
}

View File

@ -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<Object> nullsComparator(boolean isAscending) {
return isAscending
? org.springframework.util.comparator.Comparators.nullsHigh()
: org.springframework.util.comparator.Comparators.nullsLow();
}
}

View File

@ -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<Object> nullsComparator =
direction.isAscending() ? Comparators.nullsLow() : Comparators.nullsHigh();
Comparator<T> comparator = Comparator.comparing(function, nullsComparator);
Comparator<T> comparator = Comparator.comparing(function,
Comparators.nullsComparator(direction.isAscending()));
if (direction.isDescending()) {
comparator = comparator.reversed();
}

View File

@ -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<String, String> 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() {
}
}
}

View File

@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.set("sort", "creationTimestamp,asc");
CommentQuery commentQuery = getCommentQuery(queryParams);
Comparator<Comment> createTimeSorter = commentQuery.toComparator();
List<String> 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<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("sort", "creationTimestamp,desc");
Comparator<Comment> createTimeSorter = getCommentQuery(queryParams).toComparator();
List<String> 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<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("sort", "replyCount,asc");
Comparator<Comment> comparator = getCommentQuery(queryParams).toComparator();
List<String> commentNames = comments().stream()
.sorted(comparator)
.map(comment -> comment.getMetadata().getName())
.toList();
assertThat(commentNames).isEqualTo(List.of("A", "B", "C"));
}
@Test
void getSortOrder() {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("sortOrder", "true");
CommentQuery commentQuery = new CommentQuery(queryParams);
assertThat(commentQuery.getSortOrder()).isTrue();
queryParams.clear();
@Test
void sortByReplyCountDesc() {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("sort", "replyCount,desc");
Comparator<Comment> comparator = getCommentQuery(queryParams).toComparator();
List<String> 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<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("sort", "lastReplyTime,asc");
Comparator<Comment> comparator = getCommentQuery(queryParams).toComparator();
List<String> 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<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("sort", "lastReplyTime,desc");
Comparator<Comment> comparator = getCommentQuery(queryParams).toComparator();
List<String> commentNames = comments().stream()
.sorted(comparator)
.map(comment -> comment.getMetadata().getName())
.toList();
assertThat(commentNames).isEqualTo(List.of("B", "A", "C"));
}
@Test
void sortByDefaultDesc() {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
Comparator<Comment> comparator = getCommentQuery(queryParams).toComparator();
List<String> commentNames = comments().stream()
.sorted(comparator)
.map(comment -> comment.getMetadata().getName())
.toList();
assertThat(commentNames).isEqualTo(List.of("B", "A", "C"));
List<String> commentList = commentsIncludeNoReply().stream()
.sorted(comparator)
.map(comment -> comment.getMetadata().getName())
.toList();
assertThat(commentList).isEqualTo(List.of("D", "E", "B", "A", "C"));
}
List<Comment> 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<Comment> 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<Comment> comments = new ArrayList<>(comments());
comments.add(commentD);
comments.add(commentE);
return comments;
}
}
}

View File

@ -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<String, String> 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<ListResult<ListedComment>> 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<Comment> predicate =
commentService.commentPredicate(new CommentQuery(queryParams));
MockServerRequest request = MockServerRequest.builder()
.queryParams(queryParams)
.exchange(mock(ServerWebExchange.class))
.build();
final Predicate<Comment> 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<Comment> predicateTwo =
commentService.commentPredicate(new CommentQuery(queryParams));
request = MockServerRequest.builder()
.queryParams(queryParams)
.exchange(mock(ServerWebExchange.class))
.build();
final Predicate<Comment> predicateTwo = new CommentQuery(request).toPredicate();
assertThat(predicateTwo.test(comment)).isFalse();
}

View File

@ -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<Comment> createTimeSorter = CommentSorter.from(CommentSorter.CREATE_TIME);
List<String> 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<Comment> createTimeSorter = CommentSorter.from(CommentSorter.CREATE_TIME, false);
List<String> commentNames = comments().stream()
.sorted(createTimeSorter)
.map(comment -> comment.getMetadata().getName())
.toList();
assertThat(commentNames).isEqualTo(List.of("A", "C", "B"));
}
@Test
void sortByReplyCountAsc() {
Comparator<Comment> createTimeSorter = CommentSorter.from(CommentSorter.REPLY_COUNT);
List<String> 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<Comment> createTimeSorter = CommentSorter.from(CommentSorter.REPLY_COUNT, false);
List<String> commentNames = comments().stream()
.sorted(createTimeSorter)
.map(comment -> comment.getMetadata().getName())
.toList();
assertThat(commentNames).isEqualTo(List.of("C", "B", "A"));
}
@Test
void sortByLastReplyTimeAsc() {
Comparator<Comment> createTimeSorter = CommentSorter.from(CommentSorter.LAST_REPLY_TIME);
List<String> 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<Comment> createTimeSorter =
CommentSorter.from(CommentSorter.LAST_REPLY_TIME, false);
List<String> commentNames = comments().stream()
.sorted(createTimeSorter)
.map(comment -> comment.getMetadata().getName())
.toList();
assertThat(commentNames).isEqualTo(List.of("B", "A", "C"));
}
@Test
void sortByDefaultDesc() {
Comparator<Comment> defaultComparator = CommentSorter.lastReplyTimeComparator().reversed();
List<String> commentNames = comments().stream()
.sorted(defaultComparator)
.map(comment -> comment.getMetadata().getName())
.toList();
assertThat(commentNames).isEqualTo(List.of("B", "A", "C"));
List<String> commentList = commentsIncludeNoReply().stream()
.sorted(defaultComparator)
.map(comment -> comment.getMetadata().getName())
.toList();
assertThat(commentList).isEqualTo(List.of("D", "E", "B", "A", "C"));
}
List<Comment> 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<Comment> 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<Comment> comments = new ArrayList<>(comments());
comments.add(commentD);
comments.add(commentE);
return comments;
}
}

View File

@ -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<string>} [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<string>,
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<string>} [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<string>,
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<string>}
* @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<string>;
/**
* 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,

View File

@ -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<string>} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime
* @param {Array<string>} [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<string>,
tag?: Array<string>,
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<string>} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime
* @param {Array<string>} [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<string>,
tag?: Array<string>,
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<string>}
* @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<string>;
/**
*
@ -1365,7 +1348,6 @@ export class ApiConsoleHaloRunV1alpha1PostApi extends BaseAPI {
requestParameters.publishPhase,
requestParameters.size,
requestParameters.sort,
requestParameters.sortOrder,
requestParameters.tag,
requestParameters.visible,
options

View File

@ -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<string>} [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<string>,
visible?: "PUBLIC" | "INTERNAL" | "PRIVATE",
options: AxiosRequestConfig = {}
): Promise<RequestArgs> => {
@ -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<string>} [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<string>,
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<string>}
* @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<string>;
/**
*
@ -1117,7 +1100,6 @@ export class ApiConsoleHaloRunV1alpha1SinglePageApi extends BaseAPI {
requestParameters.publishPhase,
requestParameters.size,
requestParameters.sort,
requestParameters.sortOrder,
requestParameters.visible,
options
)

View File

@ -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"

View File

@ -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} 条回复"

View File

@ -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} 條回覆"

View File

@ -34,6 +34,12 @@ const selectedCommentNames = ref<string[]>([]);
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<SortItem>();
const selectedUser = ref<User>();
@ -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 () => {
</FilterTag>
<FilterTag
v-if="selectedSortFilterItem.value != undefined"
@close="handleSortFilterItemChange(SortFilterItems[0])"
v-if="selectedSortItem"
@close="handleSortItemChange(SortItems[0])"
>
{{
$t("core.common.filters.results.sort", {
sort: selectedSortFilterItem.label,
sort: selectedSortItem.label,
})
}}
</FilterTag>
@ -437,12 +440,10 @@ const handleApproveInBatch = async () => {
</div>
<template #popper>
<VDropdownItem
v-for="(filterItem, index) in SortFilterItems"
v-for="(filterItem, index) in SortItems"
:key="index"
:selected="
selectedSortFilterItem.value === filterItem.value
"
@click="handleSortFilterItemChange(filterItem)"
:selected="selectedSortItem?.sort === filterItem.sort"
@click="handleSortItemChange(filterItem)"
>
{{ filterItem.label }}
</VDropdownItem>

View File

@ -63,8 +63,7 @@ interface PublishStatusItem {
interface SortItem {
label: string;
sort: "PUBLISH_TIME" | "CREATE_TIME";
sortOrder: boolean;
sort: string;
}
const VisibleItems: VisibleItem[] = [
@ -100,23 +99,19 @@ const PublishStatusItems: PublishStatusItem[] = [
const SortItems: SortItem[] = [
{
label: t("core.page.filters.sort.items.publish_time_desc"),
sort: "PUBLISH_TIME",
sortOrder: false,
sort: "publishTime,desc",
},
{
label: t("core.page.filters.sort.items.publish_time_asc"),
sort: "PUBLISH_TIME",
sortOrder: true,
sort: "publishTime,asc",
},
{
label: t("core.page.filters.sort.items.create_time_desc"),
sort: "CREATE_TIME",
sortOrder: false,
sort: "creationTimestamp,desc",
},
{
label: t("core.page.filters.sort.items.create_time_asc"),
sort: "CREATE_TIME",
sortOrder: true,
sort: "creationTimestamp,asc",
},
];
@ -219,8 +214,7 @@ const {
page: page.value,
size: size.value,
visible: selectedVisibleItem.value.value,
sort: selectedSortItem.value?.sort,
sortOrder: selectedSortItem.value?.sortOrder,
sort: [selectedSortItem.value?.sort].filter(Boolean) as string[],
keyword: keyword.value,
contributor: contributors,
});
@ -652,10 +646,7 @@ const { mutate: changeVisibleMutation } = useMutation({
<VDropdownItem
v-for="(sortItem, index) in SortItems"
:key="index"
:selected="
sortItem.sort === selectedSortItem?.sort &&
sortItem.sortOrder === selectedSortItem?.sortOrder
"
:selected="sortItem.sort === selectedSortItem?.sort"
@click="handleSortItemChange(sortItem)"
>
{{ sortItem.label }}

View File

@ -70,8 +70,7 @@ interface PublishStatusItem {
interface SortItem {
label: string;
sort: "PUBLISH_TIME" | "CREATE_TIME";
sortOrder: boolean;
sort: string;
}
const VisibleItems: VisibleItem[] = [
@ -107,23 +106,19 @@ const PublishStatusItems: PublishStatusItem[] = [
const SortItems: SortItem[] = [
{
label: t("core.post.filters.sort.items.publish_time_desc"),
sort: "PUBLISH_TIME",
sortOrder: false,
sort: "publishTime,desc",
},
{
label: t("core.post.filters.sort.items.publish_time_asc"),
sort: "PUBLISH_TIME",
sortOrder: true,
sort: "publishTime,asc",
},
{
label: t("core.post.filters.sort.items.create_time_desc"),
sort: "CREATE_TIME",
sortOrder: false,
sort: "creationTimestamp,desc",
},
{
label: t("core.post.filters.sort.items.create_time_asc"),
sort: "CREATE_TIME",
sortOrder: true,
sort: "creationTimestamp,asc",
},
];
@ -254,8 +249,7 @@ const {
page: page.value,
size: size.value,
visible: selectedVisibleItem.value?.value,
sort: selectedSortItem.value?.sort,
sortOrder: selectedSortItem.value?.sortOrder,
sort: [selectedSortItem.value?.sort].filter(Boolean) as string[],
keyword: keyword.value,
category: categories,
tag: tags,
@ -715,10 +709,7 @@ const { mutate: changeVisibleMutation } = useMutation({
<VDropdownItem
v-for="(sortItem, index) in SortItems"
:key="index"
:selected="
sortItem.sort === selectedSortItem?.sort &&
sortItem.sortOrder === selectedSortItem?.sortOrder
"
:selected="sortItem.sort === selectedSortItem?.sort"
@click="handleSortItemChange(sortItem)"
>
{{ sortItem.label }}

View File

@ -21,8 +21,7 @@ const { data } = useQuery<ListedPost[]>({
`${postLabels.DELETED}=false`,
`${postLabels.PUBLISHED}=true`,
],
sort: "PUBLISH_TIME",
sortOrder: false,
sort: ["publishTime,desc"],
page: 1,
size: 10,
});