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

@@ -287,8 +287,11 @@ public interface SoftwareModuleManagement
* 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

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

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
@@ -35,11 +37,12 @@ public final class RolloutSpecification {
* 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;
};

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();
@@ -52,7 +63,7 @@ public class RolloutGroupManagementTest extends AbstractJpaIntegrationTest {
@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<>();

View File

@@ -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,6 +50,14 @@ 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() {
}
@@ -103,13 +110,15 @@ public final class GridComponentBuilder {
* execute on button click (null for none)
* @return the button
*/
public static <E extends ProxyIdentifiableEntity> Button buildLink(final E entity, final String idPrefix,
public static <E extends ProxyNamedEntity> 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 <E>
* entity type of the grid
@@ -123,11 +132,15 @@ public final class GridComponentBuilder {
*/
public static <E extends ProxyNamedEntity> Column<E, String> addNameColumn(final Grid<E> grid,
final VaadinMessageSource i18n, final String columnId) {
return addColumn(i18n, grid, E::getName, "header.name", columnId, DEFAULT_MIN_WIDTH);
final Column<E, String> 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,13 +152,16 @@ public final class GridComponentBuilder {
*/
public static Column<ProxyTarget, Button> addControllerIdColumn(final Grid<ProxyTarget> 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<ProxyTarget, Button> 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()));
}
@@ -182,12 +198,24 @@ public final class GridComponentBuilder {
public static <E extends ProxyNamedEntity> List<Column<E, String>> addCreatedAndModifiedColumns(final Grid<E> grid,
final VaadinMessageSource i18n) {
final List<Column<E, String>> 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<E, String> 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<E, String> 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<E, String> 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<E, String> 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;
}
@@ -208,7 +236,10 @@ public final class GridComponentBuilder {
*/
public static <E> Column<E, String> addVersionColumn(final Grid<E> grid, final VaadinMessageSource i18n,
final ValueProvider<E, String> valueProvider, final String columnId) {
return addColumn(i18n, grid, valueProvider, "header.version", columnId, DEFAULT_MIN_WIDTH);
final Column<E, String> column = addColumn(i18n, grid, valueProvider, "header.version", columnId,
DEFAULT_MIN_WIDTH);
setColumnSortable(column, VERSION_PROPERTY_NAME);
return column;
}
private static <E, T> Column<E, T> addColumn(final VaadinMessageSource i18n, final Grid<E> grid,
@@ -253,10 +284,8 @@ public final class GridComponentBuilder {
*/
public static <E, T> Column<E, T> addColumn(final Grid<E> grid, final ValueProvider<E, T> valueProvider,
final StyleGenerator<E> styleGenerator) {
final Column<E, T> column = grid.addColumn(valueProvider).setMinimumWidthFromContent(false).setExpandRatio(1);
if (styleGenerator != null) {
column.setStyleGenerator(styleGenerator);
}
final Column<E, T> column = grid.addColumn(valueProvider);
commonColumnConfiguration(column, styleGenerator);
return column;
}
@@ -291,8 +320,17 @@ public final class GridComponentBuilder {
*/
public static <E, T extends Component> Column<E, T> addComponentColumn(final Grid<E> grid,
final ValueProvider<E, T> componentProvider, final StyleGenerator<E> styleGenerator) {
final Column<E, T> column = grid.addComponentColumn(componentProvider).setMinimumWidthFromContent(false)
.setExpandRatio(1);
final Column<E, T> column = grid.addComponentColumn(componentProvider);
commonColumnConfiguration(column, styleGenerator);
return column;
}
private static <E, T> Column<E, T> commonColumnConfiguration(final Column<E, T> column,
final StyleGenerator<E> styleGenerator) {
column.setMinimumWidthFromContent(false);
column.setExpandRatio(1);
column.setSortable(false);
if (styleGenerator != null) {
column.setStyleGenerator(styleGenerator);
}
@@ -374,13 +412,32 @@ public final class GridComponentBuilder {
final StyleGenerator<T> finalStyleGenerator = merge(Arrays.asList(styleGenerator, additionalStyleGenerator));
final Column<T, V> 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 <T>
* type of the entity displayed by the grid
* @param <V>
* 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 <T, V> Column<T, V> setColumnSortable(final Column<T, V> column,
final String sortPropertyName) {
column.setSortable(true).setSortProperty(sortPropertyName);
return column;
}
private static <T> StyleGenerator<T> merge(final Collection<StyleGenerator<T>> generators) {
return item -> generators.stream().filter(Objects::nonNull).map(gen -> gen.apply(item)).filter(Objects::nonNull)
.collect(Collectors.joining(" "));
@@ -458,5 +515,4 @@ public final class GridComponentBuilder {
return actionButton;
}
}

View File

@@ -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<T extends ProxyIdentifiableEnt
@Override
protected Stream<T> fetchFromBackEnd(final Query<T, F> 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<QuerySortOrder> querySortOrders) {
if (CollectionUtils.isEmpty(querySortOrders)) {
return defaultSortOrder;
} else {
return Sort.by(convertToListOfOrders(querySortOrders));
}
}
private List<Order> convertToListOfOrders(final List<QuerySortOrder> 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<T, F> query, final Sort sort) {
@@ -67,7 +103,7 @@ public abstract class AbstractGenericDataProvider<T extends ProxyIdentifiableEnt
@Override
protected int sizeInBackEnd(final Query<T, F> 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);

View File

@@ -19,11 +19,15 @@ import org.eclipse.hawkbit.ui.common.grid.support.SelectionSupport;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import org.vaadin.spring.events.EventBus.UIEventBus;
import com.vaadin.data.ValueProvider;
import com.vaadin.data.provider.DataCommunicator;
import com.vaadin.data.provider.DataProviderListener;
import com.vaadin.data.provider.Query;
import com.vaadin.ui.Component;
import com.vaadin.ui.Grid;
import com.vaadin.ui.Grid.Column.NestedNullBehavior;
import com.vaadin.ui.components.grid.GridSelectionModel;
import com.vaadin.ui.renderers.AbstractRenderer;
/**
* Abstract grid that offers various capabilities (aka support) to offer
@@ -123,7 +127,6 @@ public abstract class AbstractGrid<T extends ProxyIdentifiableEntity, F> extends
setId(getGridId());
setColumnReorderingAllowed(false);
addColumns();
disableColumnSorting();
setFrozenColumnCount(-1);
if (selectionSupport == null) {
@@ -176,10 +179,48 @@ public abstract class AbstractGrid<T extends ProxyIdentifiableEntity, F> extends
*/
public abstract void addColumns();
private void disableColumnSorting() {
for (final Column<T, ?> c : getColumns()) {
c.setSortable(false);
}
@Override
public Column<T, ?> addColumn(final String propertyName) {
return super.addColumn(propertyName).setSortable(false);
}
@Override
public Column<T, ?> addColumn(final String propertyName, final AbstractRenderer<? super T, ?> renderer) {
return super.addColumn(propertyName, renderer).setSortable(false);
}
@Override
public Column<T, ?> addColumn(final String propertyName, final AbstractRenderer<? super T, ?> renderer,
final NestedNullBehavior nestedNullBehavior) {
return super.addColumn(propertyName, renderer, nestedNullBehavior).setSortable(false);
}
@Override
public <V> Column<T, V> addColumn(final ValueProvider<T, V> valueProvider) {
return super.addColumn(valueProvider).setSortable(false);
}
@Override
public <V> Column<T, V> addColumn(final ValueProvider<T, V> valueProvider,
final AbstractRenderer<? super T, ? super V> renderer) {
return super.addColumn(valueProvider, renderer).setSortable(false);
}
@Override
public <V, P> Column<T, V> addColumn(final ValueProvider<T, V> valueProvider,
final ValueProvider<V, P> presentationProvider, final AbstractRenderer<? super T, ? super P> renderer) {
return super.addColumn(valueProvider, presentationProvider, renderer).setSortable(false);
}
@Override
public <V> Column<T, V> addColumn(final ValueProvider<T, V> valueProvider,
final ValueProvider<V, String> presentationProvider) {
return super.addColumn(valueProvider, presentationProvider).setSortable(false);
}
@Override
public <V extends Component> Column<T, V> addComponentColumn(final ValueProvider<T, V> componentProvider) {
return super.addComponentColumn(componentProvider).setSortable(false);
}
/**

View File

@@ -128,8 +128,11 @@ public class TargetFilterGrid extends AbstractGrid<ProxyTargetFilterQuery, Strin
@Override
public void addColumns() {
GridComponentBuilder.addComponentColumn(this, this::buildFilterLink).setId(FILTER_NAME_ID)
final Column<ProxyTargetFilterQuery, Button> nameColumn = GridComponentBuilder
.addComponentColumn(this, this::buildFilterLink).setId(FILTER_NAME_ID)
.setCaption(i18n.getMessage("header.name"));
GridComponentBuilder.setColumnSortable(nameColumn, "name");
GridComponentBuilder.addCreatedAndModifiedColumns(this, i18n);

View File

@@ -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<ProxyTarget, String> {
GridComponentBuilder.addDescriptionColumn(this, i18n, TARGET_DESCRIPTION_ID);
GridComponentBuilder.addIconColumn(this, targetStatusIconSupplier::getLabel, TARGET_STATUS_ID,
final Column<ProxyTarget, Label> statusColumn = GridComponentBuilder.addIconColumn(this,
targetStatusIconSupplier::getLabel, TARGET_STATUS_ID,
i18n.getMessage("header.status"));
GridComponentBuilder.setColumnSortable(statusColumn, "updateStatus");
GridComponentBuilder.addCreatedAndModifiedColumns(this, i18n);

View File

@@ -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<ProxyRollout, String> {
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<ProxyRollout, String> {
@Override
public void addColumns() {
GridComponentBuilder.addComponentColumn(this, this::buildRolloutLink).setId(ROLLOUT_LINK_ID)
final Column<ProxyRollout, Button> 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<ProxyRollout, String> {
.setId(DIST_NAME_VERSION_ID).setCaption(i18n.getMessage("header.distributionset"))
.setDescriptionGenerator(this::createDSTooltipText).setHidable(true).setExpandRatio(2);
GridComponentBuilder
final Column<ProxyRollout, Label> 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"))

View File

@@ -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<ProxyRolloutGroup, Long> {
@Override
public void addColumns() {
GridComponentBuilder.addComponentColumn(this, this::buildRolloutGroupLink).setId(ROLLOUT_GROUP_LINK_ID)
final Column<ProxyRolloutGroup, Button> 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<ProxyRolloutGroup, Label> 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<ProxyRolloutGroup, Long> {
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));
}

View File

@@ -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<ProxyTarget, Long> {
GridComponentBuilder.addDescriptionColumn(this, i18n, SPUILabelDefinitions.VAR_DESC).setExpandRatio(2);
GridComponentBuilder.addIconColumn(this, actionStatusIconSupplier::getLabel, SPUILabelDefinitions.VAR_STATUS,
final Column<ProxyTarget, Label> statusColumn = GridComponentBuilder.addIconColumn(this,
actionStatusIconSupplier::getLabel, SPUILabelDefinitions.VAR_STATUS,
i18n.getMessage("header.status"));
GridComponentBuilder.setColumnSortable(statusColumn, "status");
GridComponentBuilder.addCreatedAndModifiedColumns(this, i18n);

View File

@@ -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
* <code>null</code>.
*
* @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());
}
}