Enable sorting in tables (#1279)

* provided infrastructure to enable sorting in grids
* Fixed sorting in RolloutGroupTarget
* fixed sorting with pinning and assigned SoftwareModules
* Added sorting for columns createdBy, createdAt, lastModifiedBy and lastModifiedAt
* Adapted status columns to be sortable
* fixed unit tests
* fixing sonar findings
* making Sonar happy
* added testcases for management classes regarding sorting
* added testcases for management classes regarding sorting
* using name for element ids in DOM
* incorporated code review remarks

Signed-off-by: Markus Block <markus.block@bosch-si.com>
This commit is contained in:
Markus Block
2022-09-30 09:54:17 +02:00
committed by GitHub
parent 3d3481110e
commit 60e33afca4
23 changed files with 593 additions and 154 deletions

View File

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

View File

@@ -374,14 +374,14 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
@Override
public Slice<DistributionSet> findByDistributionSetFilterOrderByLinkedTarget(final Pageable pageable,
final DistributionSetFilter distributionSetFilter, final String assignedOrInstalled) {
final List<Specification<JpaDistributionSet>> 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<Specification<JpaDistributionSet>> specList = buildDistributionSetSpecifications(
distributionSetFilter);
specList.add(DistributionSetSpecification.orderedByLinkedTarget(assignedOrInstalled, pageable.getSort()));
return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetRepository, unsortedPage, specList);
}

View File

@@ -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<Object[]> 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> 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<Order> getOrderBy(final Pageable pageRequest, final CriteriaBuilder cb,
final Join<RolloutTargetGroup, JpaTarget> targetJoin,
final ListJoin<RolloutTargetGroup, JpaAction> actionJoin) {
return pageRequest.getSort().get().flatMap(order -> {
final List<Order> 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);

View File

@@ -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<Rollout> 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<Specification<JpaRollout>> 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<Rollout> findAllWithDetailedStatus(final Pageable pageable, final boolean deleted) {
final Slice<Rollout> rollouts = JpaManagementHelper.findAllWithoutCountBySpec(rolloutRepository, pageable,
Collections.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted)));
Collections
.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort())));
setRolloutStatusDetails(rollouts);
return rollouts;
}

View File

@@ -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<AssignedSoftwareModule> 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<Order> 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<Tuple> 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

View File

@@ -638,7 +638,7 @@ public class JpaTargetManagement implements TargetManagement {
pageable.getPageSize(), Sort.unsorted());
final List<Specification<JpaTarget>> specList = buildSpecificationList(filterParams);
specList.add(TargetSpecifications.orderedByLinkedDistributionSet(orderByDistributionId));
specList.add(TargetSpecifications.orderedByLinkedDistributionSet(orderByDistributionId, pageable.getSort()));
return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, unsortedPage, specList);
}

View File

@@ -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
* <code>null</code>.
*
* 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<JpaDistributionSet> orderedByLinkedTarget(final String linkedControllerId) {
public static Specification<JpaDistributionSet> orderedByLinkedTarget(final String linkedControllerId,
final Sort sort) {
return (dsRoot, query, cb) -> {
final Root<JpaTarget> targetRoot = query.from(JpaTarget.class);
final Expression<Object> 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<Order> 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);
};

View File

@@ -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<JpaRollout> isDeletedWithDistributionSet(final Boolean isDeleted) {
public static Specification<JpaRollout> isDeletedWithDistributionSet(final Boolean isDeleted, final Sort sort) {
return (root, query, cb) -> {
final Predicate predicate = cb.equal(root.<Boolean> 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
* <code>true</code> if deleted rollouts should be included in
* the search. Otherwise <code>false</code>
* @return criteria specification with a query for name of a rollout
*
*
*/
public static Specification<JpaRollout> likeName(final String searchText, final boolean isDeleted) {
return (rolloutRoot, query, criteriaBuilder) -> {

View File

@@ -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 <code>null</code>.
*
* 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<JpaTarget> orderedByLinkedDistributionSet(final long distributionSetIdForOrder) {
public static Specification<JpaTarget> orderedByLinkedDistributionSet(final long distributionSetIdForOrder,
final Sort sort) {
return (targetRoot, query, cb) -> {
// Enhance query with custom select based sort
final Expression<Object> 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<Order> 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();

View File

@@ -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<DistributionSet> dsIterator = buildDistributionSets.iterator();
final Iterator<Target> 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<DistributionSet> 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<DistributionSet> 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<DistributionSet> 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

View File

@@ -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<RolloutGroup> 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<Target> 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<TargetWithActionStatus> 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<TargetWithActionStatus> targetsWithActionStatusOrderedByNameDesc = rolloutGroupManagement
.findAllTargetsOfRolloutGroupWithActionStatus(PageRequest.of(0, 500, Sort.by(Direction.DESC, "name")),
rolloutGroup.getId())
.getContent();
assertThatListIsSortedByTargetName(targetsWithActionStatusOrderedByNameDesc, Direction.DESC);
final List<TargetWithActionStatus> targetsWithActionStatusOrderedByNameAsc = rolloutGroupManagement
.findAllTargetsOfRolloutGroupWithActionStatus(PageRequest.of(0, 500, Sort.by(Direction.ASC, "name")),
rolloutGroup.getId())
.getContent();
assertThatListIsSortedByTargetName(targetsWithActionStatusOrderedByNameAsc, Direction.ASC);
}
private void assertThatListIsSortedByTargetName(final List<TargetWithActionStatus> 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();
}
}

View File

@@ -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<Rollout> rolloutsOrderedByStatus = rolloutManagement
.findAll(PageRequest.of(0, 500, Sort.by(Direction.ASC, "status")), false).getContent();
assertThat(rolloutsOrderedByStatus).containsSubsequence(List.of(rolloutReady, rolloutRunning));
final List<Rollout> 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() {

View File

@@ -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));
}

View File

@@ -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<Target> result = targetManagement.findByFilterOrderByLinkedDistributionSet(PAGE, ds.getId(),
new FilterParams(null, null, null, null, Boolean.FALSE));
final Comparator<TenantAwareBaseEntity> byId = (e1, e2) -> Long.compare(e2.getId(), e1.getId());
final Comparator<TenantAwareBaseEntity> byId = (e1, e2) -> Long.compare(e1.getId(), e2.getId());
assertThat(result.getNumberOfElements()).isEqualTo(9);
final List<Target> 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<Target> notAssigned = testdataFactory.createTargets(3, "not", "first description");
List<Target> targAssigned = testdataFactory.createTargets(3, "assigned", "first description");
List<Target> 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<Target> 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<Target> targets1, final int index1, final List<Target> 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<Target> result = targetManagement.findByFilterOrderByLinkedDistributionSet(PAGE, ds.getId(),
new FilterParams(null, Boolean.TRUE, null, null, Boolean.FALSE));
final Comparator<TenantAwareBaseEntity> byId = (e1, e2) -> Long.compare(e2.getId(), e1.getId());
final Comparator<TenantAwareBaseEntity> byId = (e1, e2) -> Long.compare(e1.getId(), e2.getId());
assertThat(result.getNumberOfElements()).isEqualTo(9);
final List<Target> expected = new ArrayList<>();