diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java index 0fcb44804..752111b3d 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java @@ -53,7 +53,7 @@ public interface SoftwareModuleManagement * @param typeId * to filter the result by type * @return number of found {@link SoftwareModule}s - * + * * @throws EntityNotFoundException * if software module type with given ID does not exist */ @@ -62,19 +62,19 @@ public interface SoftwareModuleManagement /** * Creates a list of software module meta data entries. - * + * * @param metadata * the meta data entries to create - * + * * @return the updated or created software module meta data entries - * + * * @throws EntityAlreadyExistsException * in case one of the meta data entry already exists for the * specific key - * + * * @throws EntityNotFoundException * if software module with given ID does not exist - * + * * @throws AssignmentQuotaExceededException * if the maximum number of {@link SoftwareModuleMetadata} * entries is exceeded for the addressed {@link SoftwareModule} @@ -84,19 +84,19 @@ public interface SoftwareModuleManagement /** * Creates or updates a single software module meta data entry. - * + * * @param metadata * the meta data entry to create - * + * * @return the updated or created software module meta data entry - * + * * @throws EntityAlreadyExistsException * in case the meta data entry already exists for the specific * key - * + * * @throws EntityNotFoundException * if software module with given ID does not exist - * + * * @throws AssignmentQuotaExceededException * if the maximum number of {@link SoftwareModuleMetadata} * entries is exceeded for the addressed {@link SoftwareModule} @@ -111,7 +111,7 @@ public interface SoftwareModuleManagement * where meta data has to be deleted * @param key * of the metda data element - * + * * @throws EntityNotFoundException * of module or metadata entry does not exist */ @@ -120,15 +120,15 @@ public interface SoftwareModuleManagement /** * Returns all modules assigned to given {@link DistributionSet}. - * + * * @param pageable * the page request to page the result set * @param setId * to search for - * + * * @return all {@link SoftwareModule}s that are assigned to given * {@link DistributionSet}. - * + * * @throws EntityNotFoundException * if distribution set with given ID does not exist */ @@ -137,13 +137,13 @@ public interface SoftwareModuleManagement /** * Returns count of all modules assigned to given {@link DistributionSet}. - * + * * @param setId * to search for - * + * * @return count of {@link SoftwareModule}s that are assigned to given * {@link DistributionSet}. - * + * * @throws EntityNotFoundException * if distribution set with given ID does not exist */ @@ -161,9 +161,9 @@ public interface SoftwareModuleManagement * to be filtered as "like" on {@link SoftwareModule#getName()} * @param typeId * to be filtered as "like" on {@link SoftwareModule#getType()} - * + * * @return the page of found {@link SoftwareModule} - * + * * @throws EntityNotFoundException * if given software module type does not exist */ @@ -179,9 +179,9 @@ public interface SoftwareModuleManagement * of the {@link SoftwareModule} * @param typeId * of the {@link SoftwareModule} - * + * * @return the found {@link SoftwareModule} - * + * * @throws EntityNotFoundException * if software module type with given ID does not exist */ @@ -196,7 +196,7 @@ public interface SoftwareModuleManagement * @param key * of the meta data element * @return the found SoftwareModuleMetadata or {@code null} if not exits - * + * * @throws EntityNotFoundException * is module with given ID does not exist */ @@ -205,7 +205,7 @@ public interface SoftwareModuleManagement /** * Finds all meta data by the given software module id. - * + * * @param pageable * the page request to page the result * @param moduleId @@ -213,7 +213,7 @@ public interface SoftwareModuleManagement * * @return a paged result of all meta data entries for a given software * module id - * + * * @throws EntityNotFoundException * if software module with given ID does not exist */ @@ -222,12 +222,12 @@ public interface SoftwareModuleManagement /** * Counts all meta data by the given software module id. - * + * * @param moduleId * the software module id to retrieve the meta data count from * * @return count of all meta data entries for a given software module id - * + * * @throws EntityNotFoundException * if software module with given ID does not exist */ @@ -237,7 +237,7 @@ public interface SoftwareModuleManagement /** * Finds all meta data by the given software module id where * {@link SoftwareModuleMetadata#isTargetVisible()}. - * + * * @param pageable * the page request to page the result * @param moduleId @@ -245,7 +245,7 @@ public interface SoftwareModuleManagement * * @return a paged result of all meta data entries for a given software * module id - * + * * @throws EntityNotFoundException * if software module with given ID does not exist */ @@ -255,7 +255,7 @@ public interface SoftwareModuleManagement /** * Finds all meta data by the given software module id. - * + * * @param pageable * the page request to page the result * @param moduleId @@ -265,14 +265,14 @@ public interface SoftwareModuleManagement * * @return a paged result of all meta data entries for a given software * module id - * + * * @throws RSQLParameterUnsupportedFieldException * if a field in the RSQL string is used but not provided by the * given {@code fieldNameProvider} - * + * * @throws RSQLParameterSyntaxException * if the RSQL syntax is wrong - * + * * @throws EntityNotFoundException * if software module with given ID does not exist */ @@ -286,9 +286,12 @@ public interface SoftwareModuleManagement * search text and {@link SoftwareModule#getType()} that are not marked as * deleted and sort them by means of given distribution set related modules * on top of the list. - * - * After that the modules are sorted by {@link SoftwareModule#getName()} and - * {@link SoftwareModule#getVersion()} in ascending order. + * + * After that the modules are sorted by by default by + * {@link SoftwareModule#getName()} and {@link SoftwareModule#getVersion()} + * in ascending order if no other sorting is provided in {@link Pageable}. + * If sorting is provided in {@link Pageable} parameter the provided sorting + * is used. * * @param pageable * page parameter @@ -298,9 +301,9 @@ public interface SoftwareModuleManagement * filtered as "like" on {@link SoftwareModule#getName()} * @param typeId * filtered as "equal" on {@link SoftwareModule#getType()} - * + * * @return the page of found {@link SoftwareModule} - * + * * @throws EntityNotFoundException * if given software module type does not exist */ @@ -316,9 +319,9 @@ public interface SoftwareModuleManagement * page parameters * @param typeId * to be filtered on - * + * * @return the found {@link SoftwareModule}s - * + * * @throws EntityNotFoundException * if software module type with given ID does not exist */ @@ -327,12 +330,12 @@ public interface SoftwareModuleManagement /** * Updates a distribution set meta data value if corresponding entry exists. - * + * * @param update * the meta data entry to be updated - * + * * @return the updated meta data entry - * + * * @throws EntityNotFoundException * in case the meta data entry does not exists and cannot be * updated diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java index d82fb7152..4ef04ce55 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java @@ -374,14 +374,14 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Override public Slice findByDistributionSetFilterOrderByLinkedTarget(final Pageable pageable, final DistributionSetFilter distributionSetFilter, final String assignedOrInstalled) { - final List> specList = buildDistributionSetSpecifications( - distributionSetFilter); - specList.add(DistributionSetSpecification.orderedByLinkedTarget(assignedOrInstalled)); - // remove default sort from pageable to not overwrite sorted spec final OffsetBasedPageRequest unsortedPage = new OffsetBasedPageRequest(pageable.getOffset(), pageable.getPageSize(), Sort.unsorted()); + final List> specList = buildDistributionSetSpecifications( + distributionSetFilter); + specList.add(DistributionSetSpecification.orderedByLinkedTarget(assignedOrInstalled, pageable.getSort())); + return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetRepository, unsortedPage, specList); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutGroupManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutGroupManagement.java index 3b1a4fde6..7a051c0ca 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutGroupManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutGroupManagement.java @@ -21,6 +21,7 @@ import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.ListJoin; +import javax.persistence.criteria.Order; import javax.persistence.criteria.Root; import org.eclipse.hawkbit.repository.RolloutGroupFields; @@ -51,7 +52,9 @@ import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.query.QueryUtils; import org.springframework.orm.jpa.vendor.Database; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @@ -259,7 +262,8 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement { final CriteriaQuery multiselect = query.multiselect(targetJoin, actionJoin.get(JpaAction_.status)) .where(cb.equal(targetRoot.get(RolloutTargetGroup_.rolloutGroup).get(JpaRolloutGroup_.id), - rolloutGroupId)); + rolloutGroupId)) + .orderBy(getOrderBy(pageRequest, cb, targetJoin, actionJoin)); final List targetWithActionStatus = entityManager.createQuery(multiselect) .setFirstResult((int) pageRequest.getOffset()).setMaxResults(pageRequest.getPageSize()).getResultList() .stream().map(o -> new TargetWithActionStatus((Target) o[0], (Action.Status) o[1])) @@ -268,6 +272,25 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement { return new PageImpl<>(targetWithActionStatus, pageRequest, totalCount); } + private List getOrderBy(final Pageable pageRequest, final CriteriaBuilder cb, + final Join targetJoin, + final ListJoin actionJoin) { + + return pageRequest.getSort().get().flatMap(order -> { + final List orders; + final String property = order.getProperty(); + // we consider status as property from JpaAction ... + if ("status".equals(property)) { + orders = QueryUtils.toOrders(Sort.by(order.getDirection(), property), actionJoin, cb); + } + // ... and every other property from JpaTarget + else { + orders = QueryUtils.toOrders(Sort.by(order.getDirection(), property), targetJoin, cb); + } + return orders.stream(); + }).collect(Collectors.toList()); + } + @Override public long countTargetsOfRolloutsGroup(final long rolloutGroupId) { throwExceptionIfRolloutGroupDoesNotExist(rolloutGroupId); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java index cb9718ada..d90fb19ec 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java @@ -47,6 +47,7 @@ import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; +import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification; import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper; @@ -75,6 +76,8 @@ import org.springframework.dao.ConcurrencyFailureException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; import org.springframework.data.jpa.domain.Specification; import org.springframework.integration.support.locks.LockRegistry; import org.springframework.orm.jpa.vendor.Database; @@ -162,7 +165,8 @@ public class JpaRolloutManagement implements RolloutManagement { @Override public Page findAll(final Pageable pageable, final boolean deleted) { return JpaManagementHelper.findAllWithCountBySpec(rolloutRepository, pageable, - Collections.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted))); + Collections + .singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort()))); } @Override @@ -170,7 +174,7 @@ public class JpaRolloutManagement implements RolloutManagement { final List> specList = Lists.newArrayListWithExpectedSize(2); specList.add( RSQLUtility.buildRsqlSpecification(rsqlParam, RolloutFields.class, virtualPropertyReplacer, database)); - specList.add(RolloutSpecification.isDeletedWithDistributionSet(deleted)); + specList.add(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort())); return JpaManagementHelper.findAllWithCountBySpec(rolloutRepository, pageable, specList); } @@ -298,7 +302,7 @@ public class JpaRolloutManagement implements RolloutManagement { /** * In case the given group is missing conditions or actions, they will be * set from the supplied default conditions. - * + * * @param create * group to check * @param conditions @@ -481,7 +485,8 @@ public class JpaRolloutManagement implements RolloutManagement { @Override public long count() { - return rolloutRepository.count(RolloutSpecification.isDeletedWithDistributionSet(false)); + return rolloutRepository.count( + RolloutSpecification.isDeletedWithDistributionSet(false, Sort.by(Direction.DESC, JpaRollout_.ID))); } @Override @@ -553,7 +558,8 @@ public class JpaRolloutManagement implements RolloutManagement { @Override public Slice findAllWithDetailedStatus(final Pageable pageable, final boolean deleted) { final Slice rollouts = JpaManagementHelper.findAllWithoutCountBySpec(rolloutRepository, pageable, - Collections.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted))); + Collections + .singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort()))); setRolloutStatusDetails(rollouts); return rollouts; } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareModuleManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareModuleManagement.java index 4477cd51a..86a168bd7 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareModuleManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareModuleManagement.java @@ -27,6 +27,7 @@ import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Expression; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.ListJoin; +import javax.persistence.criteria.Order; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; @@ -73,7 +74,9 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; +import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.query.QueryUtils; import org.springframework.orm.jpa.vendor.Database; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; @@ -308,6 +311,10 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { } @Override + // In the interface org.springframework.data.domain.Pageable.getSort the + // return value is not guaranteed to be non-null, therefore a null check is + // necessary otherwise we rely on the implementation but this could change. + @SuppressWarnings({ "squid:S2583", "squid:S2589" }) public Slice findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc( final Pageable pageable, final long dsId, final String searchText, final Long smTypeId) { final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); @@ -331,8 +338,16 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { query.groupBy(smRoot); - query.orderBy(cb.desc(assignedCaseMax), cb.asc(smRoot.get(JpaSoftwareModule_.name)), - cb.asc(smRoot.get(JpaSoftwareModule_.version))); + final Sort sort = pageable.getSort(); + final List orders = new ArrayList<>(); + orders.add(cb.desc(assignedCaseMax)); + if (sort == null || sort.isEmpty()) { + orders.add(cb.asc(smRoot.get(JpaSoftwareModule_.name))); + orders.add(cb.asc(smRoot.get(JpaSoftwareModule_.version))); + } else { + orders.addAll(QueryUtils.toOrders(sort, smRoot, cb)); + } + query.orderBy(orders); final int pageSize = pageable.getPageSize(); final List smWithAssignedFlagList = entityManager.createQuery(query) @@ -486,7 +501,7 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { /** * Asserts the meta data quota for the software module with the given ID. - * + * * @param moduleId * The software module ID. * @param requested diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java index 8430e098f..cffb0f5df 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java @@ -638,7 +638,7 @@ public class JpaTargetManagement implements TargetManagement { pageable.getPageSize(), Sort.unsorted()); final List> specList = buildSpecificationList(filterParams); - specList.add(TargetSpecifications.orderedByLinkedDistributionSet(orderByDistributionId)); + specList.add(TargetSpecifications.orderedByLinkedDistributionSet(orderByDistributionId, pageable.getSort())); return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, unsortedPage, specList); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetSpecification.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetSpecification.java index b645f19a4..65795ed18 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetSpecification.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetSpecification.java @@ -16,6 +16,7 @@ import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.Expression; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.ListJoin; +import javax.persistence.criteria.Order; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; @@ -29,7 +30,9 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.query.QueryUtils; import org.springframework.util.CollectionUtils; /** @@ -265,7 +268,8 @@ public final class DistributionSetSpecification { * Can be added to specification chain to order result by provided target * * Order: 1. Distribution set installed on target, 2. Distribution set(s) - * assigned to target, 3. Based on distribution set id + * assigned to target, 3. Based on requested sorting or id if + * null. * * NOTE: Other specs, pagables and sort objects may alter the queries * orderBy entry too, possibly invalidating the applied order, keep in mind @@ -273,16 +277,26 @@ public final class DistributionSetSpecification { * * @param linkedControllerId * controller id to get installed/assigned DS for + * @param sort * @return specification that applies order by target, may be overwritten */ - public static Specification orderedByLinkedTarget(final String linkedControllerId) { + public static Specification orderedByLinkedTarget(final String linkedControllerId, + final Sort sort) { return (dsRoot, query, cb) -> { final Root targetRoot = query.from(JpaTarget.class); final Expression assignedInstalledCase = cb.selectCase() .when(cb.equal(targetRoot.get(JpaTarget_.installedDistributionSet), dsRoot), 1) .when(cb.equal(targetRoot.get(JpaTarget_.assignedDistributionSet), dsRoot), 2).otherwise(3); - query.orderBy(cb.asc(assignedInstalledCase), cb.asc(dsRoot.get(JpaDistributionSet_.id))); + + final List orders = new ArrayList<>(); + orders.add(cb.asc(assignedInstalledCase)); + if (sort == null || sort.isEmpty()) { + orders.add(cb.asc(dsRoot.get(JpaDistributionSet_.id))); + } else { + orders.addAll(QueryUtils.toOrders(sort, dsRoot, cb)); + } + query.orderBy(orders); return cb.equal(targetRoot.get(JpaTarget_.controllerId), linkedControllerId); }; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/RolloutSpecification.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/RolloutSpecification.java index 68b95cad9..2d4abca19 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/RolloutSpecification.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/RolloutSpecification.java @@ -13,7 +13,9 @@ import javax.persistence.criteria.Predicate; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_; import org.eclipse.hawkbit.repository.model.Rollout; +import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.query.QueryUtils; /** * Specifications class for {@link Rollout}s. The class provides Spring Data @@ -29,17 +31,18 @@ public final class RolloutSpecification { * {@link Specification} for retrieving {@link Rollout}s by its DELETED * attribute. Includes fetch for stuff that is required for {@link Rollout} * queries. - * + * * @param isDeleted * TRUE/FALSE are compared to the attribute DELETED. If NULL the * attribute is ignored * @return the {@link Rollout} {@link Specification} */ - public static Specification isDeletedWithDistributionSet(final Boolean isDeleted) { + public static Specification isDeletedWithDistributionSet(final Boolean isDeleted, final Sort sort) { return (root, query, cb) -> { final Predicate predicate = cb.equal(root. get(JpaRollout_.deleted), isDeleted); root.fetch(JpaRollout_.distributionSet); + query.orderBy(QueryUtils.toOrders(sort, root, cb)); return predicate; }; @@ -47,14 +50,14 @@ public final class RolloutSpecification { /** * Builds a {@link Specification} to search a rollout by name. - * + * * @param searchText * search string * @param isDeleted * true if deleted rollouts should be included in * the search. Otherwise false * @return criteria specification with a query for name of a rollout - * + * */ public static Specification likeName(final String searchText, final boolean isDeleted) { return (rolloutRoot, query, criteriaBuilder) -> { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java index ff988638b..cdf595d54 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java @@ -18,6 +18,7 @@ import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.ListJoin; import javax.persistence.criteria.MapJoin; +import javax.persistence.criteria.Order; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; @@ -47,7 +48,9 @@ import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.query.QueryUtils; /** * Specifications class for {@link Target}s. The class provides Spring Data JPQL @@ -571,7 +574,7 @@ public final class TargetSpecifications { * distribution set * * Order: 1. Targets with DS installed, 2. Targets with DS assigned, 3. - * Based on target id + * Based on requested sorting or id if null. * * NOTE: Other specs, pagables and sort objects may alter the queries * orderBy entry too, possibly invalidating the applied order, keep in mind @@ -579,9 +582,12 @@ public final class TargetSpecifications { * * @param distributionSetIdForOrder * distribution set to consider + * @param sort + * the sorting requested * @return specification that applies order by ds, may be overwritten */ - public static Specification orderedByLinkedDistributionSet(final long distributionSetIdForOrder) { + public static Specification orderedByLinkedDistributionSet(final long distributionSetIdForOrder, + final Sort sort) { return (targetRoot, query, cb) -> { // Enhance query with custom select based sort final Expression selectCase = cb.selectCase() @@ -590,7 +596,14 @@ public final class TargetSpecifications { .when(cb.equal(targetRoot.get(JpaTarget_.assignedDistributionSet).get(JpaDistributionSet_.id), distributionSetIdForOrder), 2) .otherwise(100); - query.orderBy(cb.asc(selectCase), cb.desc(targetRoot.get(JpaTarget_.id))); + final List orders = new ArrayList<>(); + orders.add(cb.asc(selectCase)); + if (sort == null || sort.isEmpty()) { + orders.add(cb.desc(targetRoot.get(JpaTarget_.id))); + } else { + orders.addAll(QueryUtils.toOrders(sort, targetRoot, cb)); + } + query.orderBy(orders); // Spec only provides order, so no further filtering return query.getRestriction(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetManagementTest.java index 568e9e836..d1acc8234 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetManagementTest.java @@ -63,6 +63,8 @@ import org.eclipse.hawkbit.repository.test.util.WithUser; import org.junit.jupiter.api.Test; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -627,7 +629,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { final Iterator dsIterator = buildDistributionSets.iterator(); final Iterator tIterator = buildTargetFixtures.iterator(); - dsIterator.next(); + final DistributionSet dsFirst = dsIterator.next(); final DistributionSet dsSecond = dsIterator.next(); final DistributionSet dsThree = dsIterator.next(); final DistributionSet dsFour = dsIterator.next(); @@ -650,17 +652,50 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { final List tFirstPin = distributionSetManagement .findByDistributionSetFilterOrderByLinkedTarget(PAGE, distributionSetFilter, tFirst.getControllerId()) .getContent(); - assertThat(tFirstPin.get(0)).isEqualTo(dsThree); assertThat(tFirstPin).hasSize(10); + // assigned + assertThat(tFirstPin.get(0)).isEqualTo(dsThree); + // remaining id:ASC + assertThat(tFirstPin.get(1)).isEqualTo(dsFirst); + assertThat(tFirstPin.get(2)).isEqualTo(dsSecond); + assertThat(tFirstPin.get(3)).isEqualTo(dsFour); // target second has installed DS-2 and assigned DS-4 so check order // correct final List tSecondPin = distributionSetManagement .findByDistributionSetFilterOrderByLinkedTarget(PAGE, distributionSetFilter, tSecond.getControllerId()) .getContent(); + assertThat(tSecondPin).hasSize(10); + // installed assertThat(tSecondPin.get(0)).isEqualTo(dsSecond); + // assigned assertThat(tSecondPin.get(1)).isEqualTo(dsFour); - assertThat(tFirstPin).hasSize(10); + // remaining id:ASC + assertThat(tSecondPin.get(2)).isEqualTo(dsFirst); + assertThat(tSecondPin.get(3)).isEqualTo(dsThree); + + // target second has installed DS-2 and assigned DS-4 so check order + // correct + final List tSecondPinOrderedByName = distributionSetManagement + .findByDistributionSetFilterOrderByLinkedTarget( + PageRequest.of(0, 500, Sort.by(Direction.DESC, "version")), + distributionSetFilter, tSecond.getControllerId()) + .getContent(); + assertThat(tSecondPinOrderedByName).hasSize(10); + // installed + assertThat(tSecondPinOrderedByName.get(0)).isEqualTo(buildDistributionSets.get(1)); + // assigned + assertThat(tSecondPinOrderedByName.get(1)).isEqualTo(buildDistributionSets.get(3)); + // remaining version:DESC + assertThat(tSecondPinOrderedByName.get(2)).isEqualTo(buildDistributionSets.get(9)); + assertThat(tSecondPinOrderedByName.get(3)).isEqualTo(buildDistributionSets.get(8)); + assertThat(tSecondPinOrderedByName.get(4)).isEqualTo(buildDistributionSets.get(7)); + assertThat(tSecondPinOrderedByName.get(5)).isEqualTo(buildDistributionSets.get(6)); + assertThat(tSecondPinOrderedByName.get(6)).isEqualTo(buildDistributionSets.get(5)); + assertThat(tSecondPinOrderedByName.get(7)).isEqualTo(buildDistributionSets.get(4)); + assertThat(tSecondPinOrderedByName.get(8)).isEqualTo(buildDistributionSets.get(2)); + assertThat(tSecondPinOrderedByName.get(9)).isEqualTo(buildDistributionSets.get(0)); + } @Test diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutGroupManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutGroupManagementTest.java index a871387bc..c5ebe75a2 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutGroupManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutGroupManagementTest.java @@ -10,6 +10,9 @@ package org.eclipse.hawkbit.repository.jpa; import static org.assertj.core.api.Assertions.assertThat; +import java.util.List; + +import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.RolloutCreatedEvent; @@ -18,9 +21,17 @@ import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupUpdatedEve import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetWithActionStatus; import org.eclipse.hawkbit.repository.test.matcher.Expect; import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; import io.qameta.allure.Description; import io.qameta.allure.Feature; @@ -28,13 +39,13 @@ import io.qameta.allure.Story; @Feature("Component Tests - Repository") @Story("Rollout Management") -public class RolloutGroupManagementTest extends AbstractJpaIntegrationTest { +class RolloutGroupManagementTest extends AbstractJpaIntegrationTest { @Test @Description("Verifies that management get access reacts as specfied on calls for non existing entities by means " + "of Optional not present.") @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) - public void nonExistingEntityAccessReturnsNotPresent() { + void nonExistingEntityAccessReturnsNotPresent() { assertThat(rolloutGroupManagement.get(NOT_EXIST_IDL)).isNotPresent(); assertThat(rolloutGroupManagement.getWithDetailedStatus(NOT_EXIST_IDL)).isNotPresent(); @@ -48,11 +59,11 @@ public class RolloutGroupManagementTest extends AbstractJpaIntegrationTest { @Expect(type = RolloutGroupUpdatedEvent.class, count = 5), @Expect(type = DistributionSetCreatedEvent.class, count = 1), @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), - @Expect(type = RolloutUpdatedEvent.class, count = 1), + @Expect(type = RolloutUpdatedEvent.class, count = 1), @Expect(type = TargetCreatedEvent.class, count = 125), @Expect(type = RolloutCreatedEvent.class, count = 1) }) - - public void entityQueriesReferringToNotExistingEntitiesThrowsException() { + + void entityQueriesReferringToNotExistingEntitiesThrowsException() { testdataFactory.createRollout("xxx"); verifyThrownExceptionBy(() -> rolloutGroupManagement.countByRollout(NOT_EXIST_IDL), "Rollout"); @@ -73,4 +84,71 @@ public class RolloutGroupManagementTest extends AbstractJpaIntegrationTest { "RolloutGroup"); } + @Test + @Description("Verifies that the returned result considers the provided sort parameters.") + void findAllTargetsOfRolloutGroupWithActionStatusConsidersSorting() { + final String prefix = RandomStringUtils.randomAlphanumeric(5); + final Rollout rollout = testdataFactory.createRollout(prefix); + final List rolloutGroups = rolloutGroupManagement.findByRollout(PAGE, rollout.getId()) + .getContent(); + final RolloutGroup rolloutGroup = rolloutGroups.get(0); + rolloutManagement.handleRollouts(); + rolloutManagement.start(rollout.getId()); + rolloutManagement.handleRollouts(); + rolloutManagement.pauseRollout(rollout.getId()); + rolloutManagement.handleRollouts(); + final List targets = rolloutGroupManagement.findTargetsOfRolloutGroup(PAGE, rolloutGroup.getId()) + .getContent(); + Target targetCancelled = targets.get(0); + final Action actionCancelled = deploymentManagement.findActionsByTarget(targetCancelled.getControllerId(), PAGE) + .getContent().get(0); + deploymentManagement.cancelAction(actionCancelled.getId()); + deploymentManagement.forceQuitAction(actionCancelled.getId()); + targetCancelled = reloadTarget(targetCancelled); + Target targetCancelling = targets.get(1); + final Action actionCancelling = deploymentManagement + .findActionsByTarget(targetCancelling.getControllerId(), PAGE).getContent().get(0); + deploymentManagement.cancelAction(actionCancelling.getId()); + targetCancelling = reloadTarget(targetCancelling); + + final List targetsWithActionStatus = rolloutGroupManagement + .findAllTargetsOfRolloutGroupWithActionStatus(PageRequest.of(0, 500, Sort.by(Direction.DESC, "status")), + rolloutGroup.getId()) + .getContent(); + assertThat(targetsWithActionStatus.get(0).getTarget()).isEqualTo(targetCancelling); + assertThat(targetsWithActionStatus.get(1).getTarget()).isEqualTo(targetCancelled); + + final List targetsWithActionStatusOrderedByNameDesc = rolloutGroupManagement + .findAllTargetsOfRolloutGroupWithActionStatus(PageRequest.of(0, 500, Sort.by(Direction.DESC, "name")), + rolloutGroup.getId()) + .getContent(); + assertThatListIsSortedByTargetName(targetsWithActionStatusOrderedByNameDesc, Direction.DESC); + + final List targetsWithActionStatusOrderedByNameAsc = rolloutGroupManagement + .findAllTargetsOfRolloutGroupWithActionStatus(PageRequest.of(0, 500, Sort.by(Direction.ASC, "name")), + rolloutGroup.getId()) + .getContent(); + assertThatListIsSortedByTargetName(targetsWithActionStatusOrderedByNameAsc, Direction.ASC); + } + + private void assertThatListIsSortedByTargetName(final List targets, + final Direction sortDirection) { + String previousName = null; + for (final TargetWithActionStatus targetWithActionStatus : targets) { + final String actualName = targetWithActionStatus.getTarget().getName(); + if (previousName != null) { + if (Direction.ASC == sortDirection) { + assertThat(actualName).isGreaterThan(previousName); + } else { + assertThat(actualName).isLessThan(previousName); + } + } + previousName = actualName; + } + } + + private Target reloadTarget(final Target targetCancelled) { + return targetManagement.get(targetCancelled.getId()).orElseThrow(); + } + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java index bbe4a3baa..8ea383f00 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java @@ -26,6 +26,7 @@ import java.util.stream.Collectors; import javax.validation.ConstraintViolationException; import javax.validation.ValidationException; +import org.apache.commons.lang3.RandomStringUtils; import org.assertj.core.api.Assertions; import org.assertj.core.api.Condition; import org.awaitility.Awaitility; @@ -81,6 +82,7 @@ import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.T import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; @@ -1761,6 +1763,47 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest { .getNumberOfElements()).isEqualTo(2); } + @Test + @Description("Verifies that returned result considers provided sort parameter.") + void findAllRolloutsConsidersSorting() { + final String randomString = RandomStringUtils.randomAlphanumeric(5); + final DistributionSet testDs = testdataFactory.createDistributionSet(randomString + "-testDs"); + testdataFactory.createTargets(10, randomString + "-testTarget-"); + final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults().build(); + + final String prefixRolloutRunning = randomString + "1"; + final RolloutCreate rolloutRunningCreate = entityFactory.rollout().create() + .name(prefixRolloutRunning + "-testRollout").targetFilterQuery("name==" + randomString + "*") + .set(testDs); + + Rollout rolloutRunning = rolloutManagement.create(rolloutRunningCreate, 1, conditions); + // Let the executor handle created Rollout + rolloutManagement.handleRollouts(); + // start the rollout, so it has active running actions and a group which + // has been started + rolloutManagement.start(rolloutRunning.getId()); + rolloutManagement.handleRollouts(); + rolloutRunning = reloadRollout(rolloutRunning); + + final String prefixRolloutReady = randomString + "2"; + final RolloutCreate rolloutReadyCreate = entityFactory.rollout().create() + .name(prefixRolloutReady + "-testRollout").targetFilterQuery("name==" + randomString + "*") + .set(testDs); + Rollout rolloutReady = rolloutManagement.create(rolloutReadyCreate, 1, conditions); + // Let the executor handle created Rollout + rolloutManagement.handleRollouts(); + rolloutReady = reloadRollout(rolloutReady); + + final List rolloutsOrderedByStatus = rolloutManagement + .findAll(PageRequest.of(0, 500, Sort.by(Direction.ASC, "status")), false).getContent(); + assertThat(rolloutsOrderedByStatus).containsSubsequence(List.of(rolloutReady, rolloutRunning)); + + final List rolloutsOrderedByName = rolloutManagement + .findAll(PageRequest.of(0, 500, Sort.by(Direction.ASC, "name")), false).getContent(); + assertThat(rolloutsOrderedByName).containsSubsequence(List.of(rolloutRunning, rolloutReady)); + } + + @Test @Description("Creating a rollout without weight value when multi assignment in enabled.") void weightNotRequiredInMultiAssignmentMode() { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleManagementTest.java index 2aee2467c..ec11988be 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleManagementTest.java @@ -46,6 +46,8 @@ import org.eclipse.hawkbit.repository.test.util.WithUser; import org.junit.jupiter.api.Test; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -545,19 +547,28 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { .containsExactly(new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true), new AssignedSoftwareModule(unassigned, false)); + // with filter on name, version and module type, sorting defined by + // Pagerequest + assertThat(softwareModuleManagement.findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc( + PageRequest.of(0, 500, Sort.by(Direction.DESC, "name")), set.getId(), "%found%", testType.getId()) + .getContent()).as( + "Found modules with given name, given module type, the assigned ones first, ordered by name DESC") + .containsExactly(new AssignedSoftwareModule(two, true), new AssignedSoftwareModule(one, true), + new AssignedSoftwareModule(unassigned, false)); + // with filter on module type only assertThat(softwareModuleManagement .findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc(PAGE, set.getId(), null, testType.getId()) .getContent()).as("Found modules with given module type and the assigned ones first").containsExactly( - new AssignedSoftwareModule(differentName, true), new AssignedSoftwareModule(one, true), - new AssignedSoftwareModule(two, true), new AssignedSoftwareModule(unassigned, false)); + new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true), + new AssignedSoftwareModule(differentName, true), new AssignedSoftwareModule(unassigned, false)); // without any filter assertThat(softwareModuleManagement .findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc(PAGE, set.getId(), null, null) .getContent()).as("Found modules with the assigned ones first").containsExactly( - new AssignedSoftwareModule(differentName, true), new AssignedSoftwareModule(one, true), - new AssignedSoftwareModule(two, true), new AssignedSoftwareModule(four, true), + new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true), + new AssignedSoftwareModule(differentName, true), new AssignedSoftwareModule(four, true), new AssignedSoftwareModule(unassigned, false)); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java index 324c5719a..f60b91093 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java @@ -31,7 +31,10 @@ import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; import com.google.common.collect.Lists; import com.google.common.primitives.Ints; @@ -573,7 +576,7 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest { final Slice result = targetManagement.findByFilterOrderByLinkedDistributionSet(PAGE, ds.getId(), new FilterParams(null, null, null, null, Boolean.FALSE)); - final Comparator byId = (e1, e2) -> Long.compare(e2.getId(), e1.getId()); + final Comparator byId = (e1, e2) -> Long.compare(e1.getId(), e2.getId()); assertThat(result.getNumberOfElements()).isEqualTo(9); final List expected = new ArrayList<>(); @@ -586,7 +589,45 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest { assertThat(result.getContent()).usingElementComparator(controllerIdComparator()) .containsExactly(expected.toArray(new Target[0])); + } + @Test + @Description("Tests the correct order of targets based on selected distribution set and sort parameter. The system expects to have an order based on installed, assigned DS.") + void targetSearchWithOrderByDistributionSetAndSortParam() { + + final List notAssigned = testdataFactory.createTargets(3, "not", "first description"); + List targAssigned = testdataFactory.createTargets(3, "assigned", "first description"); + List targInstalled = testdataFactory.createTargets(3, "installed", "first description"); + + final DistributionSet ds = testdataFactory.createDistributionSet("a"); + + targAssigned = assignDistributionSet(ds, targAssigned).getAssignedEntity().stream().map(Action::getTarget) + .collect(Collectors.toList()); + targInstalled = assignDistributionSet(ds, targInstalled).getAssignedEntity().stream().map(Action::getTarget) + .collect(Collectors.toList()); + targInstalled = testdataFactory + .sendUpdateActionStatusToTargets(targInstalled, Status.FINISHED, Collections.singletonList("installed")) + .stream().map(Action::getTarget).collect(Collectors.toList()); + + final List targetsOrderedByDistAndName = targetManagement + .findByFilterOrderByLinkedDistributionSet(PageRequest.of(0, 500, Sort.by(Direction.DESC, "name")), + ds.getId(), new FilterParams(null, null, null, null, Boolean.FALSE)) + .getContent(); + assertThat(targetsOrderedByDistAndName).hasSize(9); + assertThatTargetNameEquals(targetsOrderedByDistAndName, 0, targInstalled, 2); + assertThatTargetNameEquals(targetsOrderedByDistAndName, 1, targInstalled, 1); + assertThatTargetNameEquals(targetsOrderedByDistAndName, 2, targInstalled, 0); + assertThatTargetNameEquals(targetsOrderedByDistAndName, 3, targAssigned, 2); + assertThatTargetNameEquals(targetsOrderedByDistAndName, 4, targAssigned, 1); + assertThatTargetNameEquals(targetsOrderedByDistAndName, 5, targAssigned, 0); + assertThatTargetNameEquals(targetsOrderedByDistAndName, 6, notAssigned, 2); + assertThatTargetNameEquals(targetsOrderedByDistAndName, 7, notAssigned, 1); + assertThatTargetNameEquals(targetsOrderedByDistAndName, 8, notAssigned, 0); + } + + private void assertThatTargetNameEquals(final List targets1, final int index1, final List targets2, + final int index2) { + assertThat(targets1.get(index1).getName()).isEqualTo(targets2.get(index2).getName()); } @Test @@ -626,7 +667,7 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest { final Slice result = targetManagement.findByFilterOrderByLinkedDistributionSet(PAGE, ds.getId(), new FilterParams(null, Boolean.TRUE, null, null, Boolean.FALSE)); - final Comparator byId = (e1, e2) -> Long.compare(e2.getId(), e1.getId()); + final Comparator byId = (e1, e2) -> Long.compare(e1.getId(), e2.getId()); assertThat(result.getNumberOfElements()).isEqualTo(9); final List expected = new ArrayList<>(); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/GridComponentBuilder.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/GridComponentBuilder.java index cebeb9ae1..4a0a55286 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/GridComponentBuilder.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/GridComponentBuilder.java @@ -1,4 +1,4 @@ -/** +/** * Copyright (c) 2020 Bosch.IO GmbH and others. * * All rights reserved. This program and the accompanying materials @@ -20,7 +20,7 @@ import org.eclipse.hawkbit.ui.common.data.proxies.ProxyIdentifiableEntity; import org.eclipse.hawkbit.ui.common.data.proxies.ProxyNamedEntity; import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTarget; import org.eclipse.hawkbit.ui.common.grid.support.DeleteSupport; -import org.eclipse.hawkbit.ui.utils.ControllerIdHtmlEncoder; +import org.eclipse.hawkbit.ui.utils.StringHtmlEncoder; import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; @@ -43,7 +43,6 @@ import com.vaadin.ui.themes.ValoTheme; * Builder class for grid components */ public final class GridComponentBuilder { - public static final double DEFAULT_MIN_WIDTH = 100D; public static final String CREATED_BY_ID = "createdBy"; @@ -51,12 +50,20 @@ public final class GridComponentBuilder { public static final String MODIFIED_BY_ID = "modifiedBy"; public static final String MODIFIED_DATE_ID = "modifiedDate"; + public static final String CREATED_BY_PROPERTY_NAME = "createdBy"; + public static final String CREATED_AT_PROPERTY_NAME = "createdAt"; + public static final String LAST_MODIFIED_BY_PROPERTY_NAME = "lastModifiedBy"; + public static final String LAST_MODIFIED_AT_PROPERTY_NAME = "lastModifiedAt"; + public static final String VERSION_PROPERTY_NAME = "version"; + public static final String NAME_PROPERTY_NAME = "name"; + public static final String CONTROLLER_ID_PROPERTY_NAME = "controllerId"; + private GridComponentBuilder() { } /** * Create a {@link Button} with link optic - * + * * @param idSuffix * suffix to build the button ID * @param idPrefix @@ -90,7 +97,7 @@ public final class GridComponentBuilder { /** * Create a {@link Button} with link optic - * + * * @param entity * to build the button ID * @param idPrefix @@ -103,14 +110,16 @@ public final class GridComponentBuilder { * execute on button click (null for none) * @return the button */ - public static Button buildLink(final E entity, final String idPrefix, + public static Button buildLink(final E entity, final String idPrefix, final String caption, final boolean enabled, final ClickListener clickListener) { - return buildLink(entity.getId().toString(), idPrefix, caption, enabled, clickListener); + final String idSuffix = StringHtmlEncoder.encode(entity.getName()); + return buildLink(idSuffix, idPrefix, caption, enabled, clickListener); } /** - * Add name column to grid - * + * Add name column to grid. The column is set to sortable which implies a + * JPA field "name" that is used for sorting. + * * @param * entity type of the grid * @param grid @@ -123,11 +132,15 @@ public final class GridComponentBuilder { */ public static Column addNameColumn(final Grid grid, final VaadinMessageSource i18n, final String columnId) { - return addColumn(i18n, grid, E::getName, "header.name", columnId, DEFAULT_MIN_WIDTH); + final Column nameColumn = addColumn(i18n, grid, E::getName, "header.name", columnId, + DEFAULT_MIN_WIDTH); + setColumnSortable(nameColumn, NAME_PROPERTY_NAME); + return nameColumn; } /** - * Add controllerId column to grid + * Add controllerId column to grid. The column is set to sortable which implies a + * JPA field "controllerId" that is used for sorting. * * @param grid * to add the column to @@ -139,20 +152,23 @@ public final class GridComponentBuilder { */ public static Column addControllerIdColumn(final Grid grid, final VaadinMessageSource i18n, final String columnId) { - return addComponentColumn(grid, t -> GridComponentBuilder.buildControllerIdLink(t, columnId)).setId(columnId) - .setCaption(i18n.getMessage("header.controllerId")).setHidable(false) - .setMinimumWidth(DEFAULT_MIN_WIDTH); + final Column column = addComponentColumn(grid, + t -> buildControllerIdLink(t, columnId)).setId(columnId) + .setCaption(i18n.getMessage("header.controllerId")).setHidable(false) + .setMinimumWidth(DEFAULT_MIN_WIDTH); + setColumnSortable(column, CONTROLLER_ID_PROPERTY_NAME); + return column; } private static Button buildControllerIdLink(final ProxyTarget target, final String linkIdPrefix) { - final String idSuffix = ControllerIdHtmlEncoder.encode(target.getControllerId()); + final String idSuffix = StringHtmlEncoder.encode(target.getControllerId()); return buildLink(idSuffix, linkIdPrefix, target.getControllerId(), true, clickEvent -> UI.getCurrent() .getNavigator().navigateTo("deployment/target=" + target.getControllerId())); } /** * Add description column to grid - * + * * @param * entity type of the grid * @param grid @@ -170,7 +186,7 @@ public final class GridComponentBuilder { /** * Add "created by", "created at", "modified by" and "modified at" column - * + * * @param * entity type of the grid * @param grid @@ -182,18 +198,30 @@ public final class GridComponentBuilder { public static List> addCreatedAndModifiedColumns(final Grid grid, final VaadinMessageSource i18n) { final List> columns = new ArrayList<>(); - columns.add(addColumn(i18n, grid, E::getCreatedBy, "header.createdBy", CREATED_BY_ID, DEFAULT_MIN_WIDTH)); - columns.add(addColumn(i18n, grid, E::getCreatedDate, "header.createdDate", CREATED_DATE_ID, DEFAULT_MIN_WIDTH)); - columns.add( - addColumn(i18n, grid, E::getLastModifiedBy, "header.modifiedBy", MODIFIED_BY_ID, DEFAULT_MIN_WIDTH)); - columns.add( - addColumn(i18n, grid, E::getModifiedDate, "header.modifiedDate", MODIFIED_DATE_ID, DEFAULT_MIN_WIDTH)); + + final Column createdByColumn = addColumn(i18n, grid, E::getCreatedBy, "header.createdBy", + CREATED_BY_ID, DEFAULT_MIN_WIDTH); + setColumnSortable(createdByColumn, CREATED_BY_PROPERTY_NAME); + columns.add(createdByColumn); + + final Column createdDate = addColumn(i18n, grid, E::getCreatedDate, "header.createdDate", CREATED_DATE_ID, DEFAULT_MIN_WIDTH); + setColumnSortable(createdDate, CREATED_AT_PROPERTY_NAME); + columns.add(createdDate); + + final Column modifiedBy = addColumn(i18n, grid, E::getLastModifiedBy, "header.modifiedBy", MODIFIED_BY_ID, DEFAULT_MIN_WIDTH); + setColumnSortable(modifiedBy, LAST_MODIFIED_BY_PROPERTY_NAME); + columns.add(modifiedBy); + + final Column modifiedDate = addColumn(i18n, grid, E::getModifiedDate, "header.modifiedDate", MODIFIED_DATE_ID, DEFAULT_MIN_WIDTH); + setColumnSortable(modifiedDate, LAST_MODIFIED_AT_PROPERTY_NAME); + columns.add(modifiedDate); + return columns; } /** * Add version column to grid - * + * * @param * entity type of the grid * @param grid @@ -208,7 +236,10 @@ public final class GridComponentBuilder { */ public static Column addVersionColumn(final Grid grid, final VaadinMessageSource i18n, final ValueProvider valueProvider, final String columnId) { - return addColumn(i18n, grid, valueProvider, "header.version", columnId, DEFAULT_MIN_WIDTH); + final Column column = addColumn(i18n, grid, valueProvider, "header.version", columnId, + DEFAULT_MIN_WIDTH); + setColumnSortable(column, VERSION_PROPERTY_NAME); + return column; } private static Column addColumn(final VaadinMessageSource i18n, final Grid grid, @@ -225,7 +256,7 @@ public final class GridComponentBuilder { /** * Add column to grid with the standard settings - * + * * @param * entity type of the grid * @param grid @@ -240,7 +271,7 @@ public final class GridComponentBuilder { /** * Add column to grid with the standard settings - * + * * @param * entity type of the grid * @param grid @@ -253,16 +284,14 @@ public final class GridComponentBuilder { */ public static Column addColumn(final Grid grid, final ValueProvider valueProvider, final StyleGenerator styleGenerator) { - final Column column = grid.addColumn(valueProvider).setMinimumWidthFromContent(false).setExpandRatio(1); - if (styleGenerator != null) { - column.setStyleGenerator(styleGenerator); - } + final Column column = grid.addColumn(valueProvider); + commonColumnConfiguration(column, styleGenerator); return column; } /** * Add column to grid with the standard settings - * + * * @param * entity type of the grid * @param grid @@ -278,7 +307,7 @@ public final class GridComponentBuilder { /** * Add column to grid with the standard settings - * + * * @param * entity type of the grid * @param grid @@ -291,8 +320,17 @@ public final class GridComponentBuilder { */ public static Column addComponentColumn(final Grid grid, final ValueProvider componentProvider, final StyleGenerator styleGenerator) { - final Column column = grid.addComponentColumn(componentProvider).setMinimumWidthFromContent(false) - .setExpandRatio(1); + final Column column = grid.addComponentColumn(componentProvider); + commonColumnConfiguration(column, styleGenerator); + return column; + } + + private static Column commonColumnConfiguration(final Column column, + final StyleGenerator styleGenerator) { + column.setMinimumWidthFromContent(false); + column.setExpandRatio(1); + column.setSortable(false); + if (styleGenerator != null) { column.setStyleGenerator(styleGenerator); } @@ -301,7 +339,7 @@ public final class GridComponentBuilder { /** * Add delete button column to grid - * + * * @param * entity type of the grid * @param grid @@ -331,7 +369,7 @@ public final class GridComponentBuilder { /** * Add an action button column to a grid - * + * * @param * type of the entity displayed by the grid * @param grid @@ -351,7 +389,7 @@ public final class GridComponentBuilder { /** * Add an action button column to a grid - * + * * @param * type of the entity displayed by the grid * @param grid @@ -374,13 +412,32 @@ public final class GridComponentBuilder { final StyleGenerator finalStyleGenerator = merge(Arrays.asList(styleGenerator, additionalStyleGenerator)); final Column column = grid.addComponentColumn(iconProvider).setId(columnId) - .setStyleGenerator(finalStyleGenerator).setWidth(60D).setResizable(false); + .setStyleGenerator(finalStyleGenerator).setWidth(60D).setResizable(false).setSortable(false); if (!StringUtils.isEmpty(caption)) { column.setCaption(caption); } return column; } + /** + * Makes the column sortable. + * + * @param + * type of the entity displayed by the grid + * @param + * type of column value + * @param column + * the column to set sortable + * @param sortPropertyName + * the jpa property name to sort by + * @return the provided column + */ + public static Column setColumnSortable(final Column column, + final String sortPropertyName) { + column.setSortable(true).setSortProperty(sortPropertyName); + return column; + } + private static StyleGenerator merge(final Collection> generators) { return item -> generators.stream().filter(Objects::nonNull).map(gen -> gen.apply(item)).filter(Objects::nonNull) .collect(Collectors.joining(" ")); @@ -388,7 +445,7 @@ public final class GridComponentBuilder { /** * Join columns to form an action column - * + * * @param i18n * message source for internationalization * @param headerRow @@ -403,7 +460,7 @@ public final class GridComponentBuilder { /** * Join columns to form an icon column - * + * * @param headerRow * header row * @param headerCaption @@ -423,7 +480,7 @@ public final class GridComponentBuilder { /** * Create an action button (e.g. a delete button) - * + * * @param i18n * message source for internationalization * @param clickListener @@ -458,5 +515,4 @@ public final class GridComponentBuilder { return actionButton; } - -} \ No newline at end of file +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/AbstractGenericDataProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/AbstractGenericDataProvider.java index 1445be75a..319390e2c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/AbstractGenericDataProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/AbstractGenericDataProvider.java @@ -8,7 +8,9 @@ */ package org.eclipse.hawkbit.ui.common.data.providers; +import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; @@ -18,9 +20,12 @@ import org.slf4j.LoggerFactory; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Order; +import org.springframework.util.CollectionUtils; import com.vaadin.data.provider.AbstractBackEndDataProvider; import com.vaadin.data.provider.Query; +import com.vaadin.data.provider.QuerySortOrder; /** * Base class for loading a batch of entities from backend mapping them to UI @@ -54,7 +59,38 @@ public abstract class AbstractGenericDataProvider fetchFromBackEnd(final Query query) { return getProxyEntities( - loadBackendEntities(convertToPageRequest(query, defaultSortOrder), query.getFilter().orElse(null))); + loadBackendEntities( + convertToPageRequest(query, convertToSortCriteria(query.getSortOrders())), + query.getFilter().orElse(null))); + } + + private Sort convertToSortCriteria(final List querySortOrders) { + if (CollectionUtils.isEmpty(querySortOrders)) { + return defaultSortOrder; + } else { + return Sort.by(convertToListOfOrders(querySortOrders)); + } + } + + private List convertToListOfOrders(final List querySortOrders) { + return querySortOrders.stream() + .map(querySortOrder -> convertToOrderCriteria(querySortOrder)) + .collect(Collectors.toList()); + } + + private Order convertToOrderCriteria(final QuerySortOrder querySortOrder) { + final Sort.Direction sortDirection; + switch (querySortOrder.getDirection()) { + case ASCENDING: + sortDirection = Sort.Direction.ASC; + break; + case DESCENDING: + // fall through intended to get default behavior + default: + sortDirection = Sort.Direction.DESC; + break; + } + return new Sort.Order(sortDirection, querySortOrder.getSorted()); } private PageRequest convertToPageRequest(final Query query, final Sort sort) { @@ -67,7 +103,7 @@ public abstract class AbstractGenericDataProvider query) { - final long size = sizeInBackEnd(convertToPageRequest(query, defaultSortOrder), query.getFilter().orElse(null)); + final long size = sizeInBackEnd(convertToPageRequest(query, convertToSortCriteria(query.getSortOrders())), query.getFilter().orElse(null)); try { return Math.toIntExact(size); @@ -85,4 +121,4 @@ public abstract class AbstractGenericDataProvider extends setId(getGridId()); setColumnReorderingAllowed(false); addColumns(); - disableColumnSorting(); setFrozenColumnCount(-1); if (selectionSupport == null) { @@ -176,10 +179,48 @@ public abstract class AbstractGrid extends */ public abstract void addColumns(); - private void disableColumnSorting() { - for (final Column c : getColumns()) { - c.setSortable(false); - } + @Override + public Column addColumn(final String propertyName) { + return super.addColumn(propertyName).setSortable(false); + } + + @Override + public Column addColumn(final String propertyName, final AbstractRenderer renderer) { + return super.addColumn(propertyName, renderer).setSortable(false); + } + + @Override + public Column addColumn(final String propertyName, final AbstractRenderer renderer, + final NestedNullBehavior nestedNullBehavior) { + return super.addColumn(propertyName, renderer, nestedNullBehavior).setSortable(false); + } + + @Override + public Column addColumn(final ValueProvider valueProvider) { + return super.addColumn(valueProvider).setSortable(false); + } + + @Override + public Column addColumn(final ValueProvider valueProvider, + final AbstractRenderer renderer) { + return super.addColumn(valueProvider, renderer).setSortable(false); + } + + @Override + public Column addColumn(final ValueProvider valueProvider, + final ValueProvider presentationProvider, final AbstractRenderer renderer) { + return super.addColumn(valueProvider, presentationProvider, renderer).setSortable(false); + } + + @Override + public Column addColumn(final ValueProvider valueProvider, + final ValueProvider presentationProvider) { + return super.addColumn(valueProvider, presentationProvider).setSortable(false); + } + + @Override + public Column addComponentColumn(final ValueProvider componentProvider) { + return super.addComponentColumn(componentProvider).setSortable(false); } /** diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterGrid.java index 3f377c2d5..21444f2e9 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterGrid.java @@ -128,8 +128,11 @@ public class TargetFilterGrid extends AbstractGrid nameColumn = GridComponentBuilder + .addComponentColumn(this, this::buildFilterLink).setId(FILTER_NAME_ID) .setCaption(i18n.getMessage("header.name")); + GridComponentBuilder.setColumnSortable(nameColumn, "name"); + GridComponentBuilder.addCreatedAndModifiedColumns(this, i18n); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTargetGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTargetGrid.java index e54609fd2..8d4c14322 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTargetGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTargetGrid.java @@ -19,6 +19,8 @@ import org.eclipse.hawkbit.ui.common.grid.support.FilterSupport; import org.eclipse.hawkbit.ui.filtermanagement.state.TargetFilterDetailsLayoutUiState; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import com.vaadin.ui.Label; + /** * Shows the targets as a result of the executed filter query. */ @@ -88,8 +90,10 @@ public class TargetFilterTargetGrid extends AbstractGrid { GridComponentBuilder.addDescriptionColumn(this, i18n, TARGET_DESCRIPTION_ID); - GridComponentBuilder.addIconColumn(this, targetStatusIconSupplier::getLabel, TARGET_STATUS_ID, + final Column statusColumn = GridComponentBuilder.addIconColumn(this, + targetStatusIconSupplier::getLabel, TARGET_STATUS_ID, i18n.getMessage("header.status")); + GridComponentBuilder.setColumnSortable(statusColumn, "updateStatus"); GridComponentBuilder.addCreatedAndModifiedColumns(this, i18n); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutGrid.java index b2c9d62d8..d0ce9eaf6 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutGrid.java @@ -61,6 +61,7 @@ import com.vaadin.data.ValueProvider; import com.vaadin.icons.VaadinIcons; import com.vaadin.shared.ui.ContentMode; import com.vaadin.ui.Button; +import com.vaadin.ui.Label; import com.vaadin.ui.UI; import com.vaadin.ui.Window; import com.vaadin.ui.renderers.HtmlRenderer; @@ -75,6 +76,7 @@ public class RolloutGrid extends AbstractGrid { private static final String ROLLOUT_LINK_ID = "rollout"; private static final String DIST_NAME_VERSION_ID = "distNameVersion"; private static final String STATUS_ID = "status"; + private static final String STATUS_PROPERTY_NAME = "status"; private static final String TOTAL_TARGETS_COUNT_STATUS_ID = "totalTargetsCountStatus"; private static final String NUMBER_OF_GROUPS_ID = "numberOfGroups"; private static final String TOTAL_TARGETS_ID = "totalTargets"; @@ -263,8 +265,10 @@ public class RolloutGrid extends AbstractGrid { @Override public void addColumns() { - GridComponentBuilder.addComponentColumn(this, this::buildRolloutLink).setId(ROLLOUT_LINK_ID) + final Column nameColumn = GridComponentBuilder + .addComponentColumn(this, this::buildRolloutLink).setId(ROLLOUT_LINK_ID) .setCaption(i18n.getMessage("header.name")).setHidable(false).setExpandRatio(3); + GridComponentBuilder.setColumnSortable(nameColumn, "name"); GridComponentBuilder.addDescriptionColumn(this, i18n, DESC_ID).setHidable(true).setHidden(true); @@ -272,9 +276,10 @@ public class RolloutGrid extends AbstractGrid { .setId(DIST_NAME_VERSION_ID).setCaption(i18n.getMessage("header.distributionset")) .setDescriptionGenerator(this::createDSTooltipText).setHidable(true).setExpandRatio(2); - GridComponentBuilder + final Column statusColumn = GridComponentBuilder .addIconColumn(this, rolloutStatusIconSupplier::getLabel, STATUS_ID, i18n.getMessage("header.status")) .setHidable(true); + GridComponentBuilder.setColumnSortable(statusColumn, STATUS_PROPERTY_NAME); GridComponentBuilder .addIconColumn(this, actionTypeIconSupplier::getLabel, ACTION_TYPE_ID, i18n.getMessage("header.type")) diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupGrid.java index 8ac36317a..11e68dd79 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupGrid.java @@ -40,6 +40,7 @@ import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; import com.google.common.base.Predicates; import com.vaadin.shared.ui.ContentMode; import com.vaadin.ui.Button; +import com.vaadin.ui.Label; import com.vaadin.ui.renderers.HtmlRenderer; /** @@ -112,14 +113,17 @@ public class RolloutGroupGrid extends AbstractGrid { @Override public void addColumns() { - GridComponentBuilder.addComponentColumn(this, this::buildRolloutGroupLink).setId(ROLLOUT_GROUP_LINK_ID) + final Column nameColumn = GridComponentBuilder.addComponentColumn(this, this::buildRolloutGroupLink).setId(ROLLOUT_GROUP_LINK_ID) .setCaption(i18n.getMessage("header.name")).setHidable(false).setExpandRatio(3); + GridComponentBuilder.setColumnSortable(nameColumn, "name"); GridComponentBuilder.addDescriptionColumn(this, i18n, SPUILabelDefinitions.VAR_DESC).setHidable(true) .setHidden(true); - GridComponentBuilder.addIconColumn(this, rolloutGroupStatusIconSupplier::getLabel, + final Column statusColumn = GridComponentBuilder + .addIconColumn(this, rolloutGroupStatusIconSupplier::getLabel, SPUILabelDefinitions.VAR_STATUS, i18n.getMessage("header.status")).setHidable(true); + GridComponentBuilder.setColumnSortable(statusColumn, "status"); addColumn(rolloutGroup -> DistributionBarHelper .getDistributionBarAsHTMLString(rolloutGroup.getTotalTargetCountStatus().getStatusTotalCountMap()), @@ -155,7 +159,7 @@ public class RolloutGroupGrid extends AbstractGrid { final boolean enableButton = RolloutGroupStatus.CREATING != rolloutGroup.getStatus() && permissionChecker.hasRolloutTargetsReadPermission(); - return GridComponentBuilder.buildLink(rolloutGroup, "rolloutgroup.link.", rolloutGroup.getName(), enableButton, + return GridComponentBuilder.buildLink(rolloutGroup, "rolloutgroup.link", rolloutGroup.getName(), enableButton, clickEvent -> onClickOfRolloutGroupName(rolloutGroup)); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetGrid.java index af2b8dced..18c3f3731 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetGrid.java @@ -24,6 +24,8 @@ import org.eclipse.hawkbit.ui.rollout.RolloutManagementUIState; import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import com.vaadin.ui.Label; + /** * Grid component with targets of rollout group. */ @@ -73,8 +75,10 @@ public class RolloutGroupTargetGrid extends AbstractGrid { GridComponentBuilder.addDescriptionColumn(this, i18n, SPUILabelDefinitions.VAR_DESC).setExpandRatio(2); - GridComponentBuilder.addIconColumn(this, actionStatusIconSupplier::getLabel, SPUILabelDefinitions.VAR_STATUS, + final Column statusColumn = GridComponentBuilder.addIconColumn(this, + actionStatusIconSupplier::getLabel, SPUILabelDefinitions.VAR_STATUS, i18n.getMessage("header.status")); + GridComponentBuilder.setColumnSortable(statusColumn, "status"); GridComponentBuilder.addCreatedAndModifiedColumns(this, i18n); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/ControllerIdHtmlEncoder.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/StringHtmlEncoder.java similarity index 53% rename from hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/ControllerIdHtmlEncoder.java rename to hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/StringHtmlEncoder.java index 45611e62d..07b03f09c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/ControllerIdHtmlEncoder.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/StringHtmlEncoder.java @@ -13,33 +13,34 @@ import java.util.Base64.Encoder; import java.util.Objects; /** - * Encodes controller IDs to make them embeddable into HTML as element - * identifiers. + * Encodes a string attribute of an entity (e.g. name) to make it embeddable + * into HTML as element identifiers. */ -public class ControllerIdHtmlEncoder { +public class StringHtmlEncoder { /** * Base64 encoder which suppresses trailing padding characters. */ private static Encoder BASE64 = Base64.getEncoder().withoutPadding(); - private ControllerIdHtmlEncoder() { + private StringHtmlEncoder() { // class should not be instantiated } /** - * Encodes the given controller ID so that it can be used as part of DOM + * Encodes the given string attribute so that it can be used as part of DOM * element IDs. - * - * @param controllerId - * The controller ID to be encoded. Must not be + * + * @param attribute + * The attribute of an entity to be encoded. Must not be * null. - * - * @return The encoded controller ID. + * + * @return The encoded string attribute to be used as element identifier in + * DOM tree. */ - public static String encode(final String controllerId) { - Objects.requireNonNull(controllerId); - return BASE64.encodeToString(controllerId.getBytes()); + public static String encode(final String attribute) { + Objects.requireNonNull(attribute); + return BASE64.encodeToString(attribute.getBytes()); } }