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 * @param typeId
* to filter the result by type * to filter the result by type
* @return number of found {@link SoftwareModule}s * @return number of found {@link SoftwareModule}s
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if software module type with given ID does not exist * 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. * Creates a list of software module meta data entries.
* *
* @param metadata * @param metadata
* the meta data entries to create * the meta data entries to create
* *
* @return the updated or created software module meta data entries * @return the updated or created software module meta data entries
* *
* @throws EntityAlreadyExistsException * @throws EntityAlreadyExistsException
* in case one of the meta data entry already exists for the * in case one of the meta data entry already exists for the
* specific key * specific key
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if software module with given ID does not exist * if software module with given ID does not exist
* *
* @throws AssignmentQuotaExceededException * @throws AssignmentQuotaExceededException
* if the maximum number of {@link SoftwareModuleMetadata} * if the maximum number of {@link SoftwareModuleMetadata}
* entries is exceeded for the addressed {@link SoftwareModule} * 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. * Creates or updates a single software module meta data entry.
* *
* @param metadata * @param metadata
* the meta data entry to create * the meta data entry to create
* *
* @return the updated or created software module meta data entry * @return the updated or created software module meta data entry
* *
* @throws EntityAlreadyExistsException * @throws EntityAlreadyExistsException
* in case the meta data entry already exists for the specific * in case the meta data entry already exists for the specific
* key * key
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if software module with given ID does not exist * if software module with given ID does not exist
* *
* @throws AssignmentQuotaExceededException * @throws AssignmentQuotaExceededException
* if the maximum number of {@link SoftwareModuleMetadata} * if the maximum number of {@link SoftwareModuleMetadata}
* entries is exceeded for the addressed {@link SoftwareModule} * entries is exceeded for the addressed {@link SoftwareModule}
@@ -111,7 +111,7 @@ public interface SoftwareModuleManagement
* where meta data has to be deleted * where meta data has to be deleted
* @param key * @param key
* of the metda data element * of the metda data element
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* of module or metadata entry does not exist * of module or metadata entry does not exist
*/ */
@@ -120,15 +120,15 @@ public interface SoftwareModuleManagement
/** /**
* Returns all modules assigned to given {@link DistributionSet}. * Returns all modules assigned to given {@link DistributionSet}.
* *
* @param pageable * @param pageable
* the page request to page the result set * the page request to page the result set
* @param setId * @param setId
* to search for * to search for
* *
* @return all {@link SoftwareModule}s that are assigned to given * @return all {@link SoftwareModule}s that are assigned to given
* {@link DistributionSet}. * {@link DistributionSet}.
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if distribution set with given ID does not exist * 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}. * Returns count of all modules assigned to given {@link DistributionSet}.
* *
* @param setId * @param setId
* to search for * to search for
* *
* @return count of {@link SoftwareModule}s that are assigned to given * @return count of {@link SoftwareModule}s that are assigned to given
* {@link DistributionSet}. * {@link DistributionSet}.
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if distribution set with given ID does not exist * 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()} * to be filtered as "like" on {@link SoftwareModule#getName()}
* @param typeId * @param typeId
* to be filtered as "like" on {@link SoftwareModule#getType()} * to be filtered as "like" on {@link SoftwareModule#getType()}
* *
* @return the page of found {@link SoftwareModule} * @return the page of found {@link SoftwareModule}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if given software module type does not exist * if given software module type does not exist
*/ */
@@ -179,9 +179,9 @@ public interface SoftwareModuleManagement
* of the {@link SoftwareModule} * of the {@link SoftwareModule}
* @param typeId * @param typeId
* of the {@link SoftwareModule} * of the {@link SoftwareModule}
* *
* @return the found {@link SoftwareModule} * @return the found {@link SoftwareModule}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if software module type with given ID does not exist * if software module type with given ID does not exist
*/ */
@@ -196,7 +196,7 @@ public interface SoftwareModuleManagement
* @param key * @param key
* of the meta data element * of the meta data element
* @return the found SoftwareModuleMetadata or {@code null} if not exits * @return the found SoftwareModuleMetadata or {@code null} if not exits
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* is module with given ID does not exist * 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. * Finds all meta data by the given software module id.
* *
* @param pageable * @param pageable
* the page request to page the result * the page request to page the result
* @param moduleId * @param moduleId
@@ -213,7 +213,7 @@ public interface SoftwareModuleManagement
* *
* @return a paged result of all meta data entries for a given software * @return a paged result of all meta data entries for a given software
* module id * module id
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if software module with given ID does not exist * 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. * Counts all meta data by the given software module id.
* *
* @param moduleId * @param moduleId
* the software module id to retrieve the meta data count from * the software module id to retrieve the meta data count from
* *
* @return count of all meta data entries for a given software module id * @return count of all meta data entries for a given software module id
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if software module with given ID does not exist * 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 * Finds all meta data by the given software module id where
* {@link SoftwareModuleMetadata#isTargetVisible()}. * {@link SoftwareModuleMetadata#isTargetVisible()}.
* *
* @param pageable * @param pageable
* the page request to page the result * the page request to page the result
* @param moduleId * @param moduleId
@@ -245,7 +245,7 @@ public interface SoftwareModuleManagement
* *
* @return a paged result of all meta data entries for a given software * @return a paged result of all meta data entries for a given software
* module id * module id
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if software module with given ID does not exist * 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. * Finds all meta data by the given software module id.
* *
* @param pageable * @param pageable
* the page request to page the result * the page request to page the result
* @param moduleId * @param moduleId
@@ -265,14 +265,14 @@ public interface SoftwareModuleManagement
* *
* @return a paged result of all meta data entries for a given software * @return a paged result of all meta data entries for a given software
* module id * module id
* *
* @throws RSQLParameterUnsupportedFieldException * @throws RSQLParameterUnsupportedFieldException
* if a field in the RSQL string is used but not provided by the * if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider} * given {@code fieldNameProvider}
* *
* @throws RSQLParameterSyntaxException * @throws RSQLParameterSyntaxException
* if the RSQL syntax is wrong * if the RSQL syntax is wrong
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if software module with given ID does not exist * 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 * search text and {@link SoftwareModule#getType()} that are not marked as
* deleted and sort them by means of given distribution set related modules * deleted and sort them by means of given distribution set related modules
* on top of the list. * on top of the list.
* *
* After that the modules are sorted by {@link SoftwareModule#getName()} and * After that the modules are sorted by by default by
* {@link SoftwareModule#getVersion()} in ascending order. * {@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 * @param pageable
* page parameter * page parameter
@@ -298,9 +301,9 @@ public interface SoftwareModuleManagement
* filtered as "like" on {@link SoftwareModule#getName()} * filtered as "like" on {@link SoftwareModule#getName()}
* @param typeId * @param typeId
* filtered as "equal" on {@link SoftwareModule#getType()} * filtered as "equal" on {@link SoftwareModule#getType()}
* *
* @return the page of found {@link SoftwareModule} * @return the page of found {@link SoftwareModule}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if given software module type does not exist * if given software module type does not exist
*/ */
@@ -316,9 +319,9 @@ public interface SoftwareModuleManagement
* page parameters * page parameters
* @param typeId * @param typeId
* to be filtered on * to be filtered on
* *
* @return the found {@link SoftwareModule}s * @return the found {@link SoftwareModule}s
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if software module type with given ID does not exist * 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. * Updates a distribution set meta data value if corresponding entry exists.
* *
* @param update * @param update
* the meta data entry to be updated * the meta data entry to be updated
* *
* @return the updated meta data entry * @return the updated meta data entry
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* in case the meta data entry does not exists and cannot be * in case the meta data entry does not exists and cannot be
* updated * updated

View File

@@ -374,14 +374,14 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
@Override @Override
public Slice<DistributionSet> findByDistributionSetFilterOrderByLinkedTarget(final Pageable pageable, public Slice<DistributionSet> findByDistributionSetFilterOrderByLinkedTarget(final Pageable pageable,
final DistributionSetFilter distributionSetFilter, final String assignedOrInstalled) { 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 // remove default sort from pageable to not overwrite sorted spec
final OffsetBasedPageRequest unsortedPage = new OffsetBasedPageRequest(pageable.getOffset(), final OffsetBasedPageRequest unsortedPage = new OffsetBasedPageRequest(pageable.getOffset(),
pageable.getPageSize(), Sort.unsorted()); pageable.getPageSize(), Sort.unsorted());
final List<Specification<JpaDistributionSet>> specList = buildDistributionSetSpecifications(
distributionSetFilter);
specList.add(DistributionSetSpecification.orderedByLinkedTarget(assignedOrInstalled, pageable.getSort()));
return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetRepository, unsortedPage, specList); 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.Join;
import javax.persistence.criteria.JoinType; import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ListJoin; import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Root; import javax.persistence.criteria.Root;
import org.eclipse.hawkbit.repository.RolloutGroupFields; 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.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification; 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.orm.jpa.vendor.Database;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; 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)) final CriteriaQuery<Object[]> multiselect = query.multiselect(targetJoin, actionJoin.get(JpaAction_.status))
.where(cb.equal(targetRoot.get(RolloutTargetGroup_.rolloutGroup).get(JpaRolloutGroup_.id), .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) final List<TargetWithActionStatus> targetWithActionStatus = entityManager.createQuery(multiselect)
.setFirstResult((int) pageRequest.getOffset()).setMaxResults(pageRequest.getPageSize()).getResultList() .setFirstResult((int) pageRequest.getOffset()).setMaxResults(pageRequest.getPageSize()).getResultList()
.stream().map(o -> new TargetWithActionStatus((Target) o[0], (Action.Status) o[1])) .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); 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 @Override
public long countTargetsOfRolloutsGroup(final long rolloutGroupId) { public long countTargetsOfRolloutsGroup(final long rolloutGroupId) {
throwExceptionIfRolloutGroupDoesNotExist(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.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; 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.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification; import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper; 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.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice; 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.data.jpa.domain.Specification;
import org.springframework.integration.support.locks.LockRegistry; import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.orm.jpa.vendor.Database; import org.springframework.orm.jpa.vendor.Database;
@@ -162,7 +165,8 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override @Override
public Page<Rollout> findAll(final Pageable pageable, final boolean deleted) { public Page<Rollout> findAll(final Pageable pageable, final boolean deleted) {
return JpaManagementHelper.findAllWithCountBySpec(rolloutRepository, pageable, return JpaManagementHelper.findAllWithCountBySpec(rolloutRepository, pageable,
Collections.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted))); Collections
.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort())));
} }
@Override @Override
@@ -170,7 +174,7 @@ public class JpaRolloutManagement implements RolloutManagement {
final List<Specification<JpaRollout>> specList = Lists.newArrayListWithExpectedSize(2); final List<Specification<JpaRollout>> specList = Lists.newArrayListWithExpectedSize(2);
specList.add( specList.add(
RSQLUtility.buildRsqlSpecification(rsqlParam, RolloutFields.class, virtualPropertyReplacer, database)); 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); 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 * In case the given group is missing conditions or actions, they will be
* set from the supplied default conditions. * set from the supplied default conditions.
* *
* @param create * @param create
* group to check * group to check
* @param conditions * @param conditions
@@ -481,7 +485,8 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override @Override
public long count() { public long count() {
return rolloutRepository.count(RolloutSpecification.isDeletedWithDistributionSet(false)); return rolloutRepository.count(
RolloutSpecification.isDeletedWithDistributionSet(false, Sort.by(Direction.DESC, JpaRollout_.ID)));
} }
@Override @Override
@@ -553,7 +558,8 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override @Override
public Slice<Rollout> findAllWithDetailedStatus(final Pageable pageable, final boolean deleted) { public Slice<Rollout> findAllWithDetailedStatus(final Pageable pageable, final boolean deleted) {
final Slice<Rollout> rollouts = JpaManagementHelper.findAllWithoutCountBySpec(rolloutRepository, pageable, final Slice<Rollout> rollouts = JpaManagementHelper.findAllWithoutCountBySpec(rolloutRepository, pageable,
Collections.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted))); Collections
.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort())));
setRolloutStatusDetails(rollouts); setRolloutStatusDetails(rollouts);
return rollouts; return rollouts;
} }

View File

@@ -27,6 +27,7 @@ import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression; import javax.persistence.criteria.Expression;
import javax.persistence.criteria.JoinType; import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ListJoin; import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root; 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.Pageable;
import org.springframework.data.domain.Slice; import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl; import org.springframework.data.domain.SliceImpl;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification; 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.orm.jpa.vendor.Database;
import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable; import org.springframework.retry.annotation.Retryable;
@@ -308,6 +311,10 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
} }
@Override @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( public Slice<AssignedSoftwareModule> findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc(
final Pageable pageable, final long dsId, final String searchText, final Long smTypeId) { final Pageable pageable, final long dsId, final String searchText, final Long smTypeId) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
@@ -331,8 +338,16 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
query.groupBy(smRoot); query.groupBy(smRoot);
query.orderBy(cb.desc(assignedCaseMax), cb.asc(smRoot.get(JpaSoftwareModule_.name)), final Sort sort = pageable.getSort();
cb.asc(smRoot.get(JpaSoftwareModule_.version))); 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 int pageSize = pageable.getPageSize();
final List<Tuple> smWithAssignedFlagList = entityManager.createQuery(query) 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. * Asserts the meta data quota for the software module with the given ID.
* *
* @param moduleId * @param moduleId
* The software module ID. * The software module ID.
* @param requested * @param requested

View File

@@ -638,7 +638,7 @@ public class JpaTargetManagement implements TargetManagement {
pageable.getPageSize(), Sort.unsorted()); pageable.getPageSize(), Sort.unsorted());
final List<Specification<JpaTarget>> specList = buildSpecificationList(filterParams); 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); 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.Expression;
import javax.persistence.criteria.JoinType; import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ListJoin; import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path; import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root; 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.jpa.model.JpaTarget_; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.model.DistributionSet; 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.domain.Specification;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.util.CollectionUtils; 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 * Can be added to specification chain to order result by provided target
* *
* Order: 1. Distribution set installed on target, 2. Distribution set(s) * 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 * NOTE: Other specs, pagables and sort objects may alter the queries
* orderBy entry too, possibly invalidating the applied order, keep in mind * orderBy entry too, possibly invalidating the applied order, keep in mind
@@ -273,16 +277,26 @@ public final class DistributionSetSpecification {
* *
* @param linkedControllerId * @param linkedControllerId
* controller id to get installed/assigned DS for * controller id to get installed/assigned DS for
* @param sort
* @return specification that applies order by target, may be overwritten * @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) -> { return (dsRoot, query, cb) -> {
final Root<JpaTarget> targetRoot = query.from(JpaTarget.class); final Root<JpaTarget> targetRoot = query.from(JpaTarget.class);
final Expression<Object> assignedInstalledCase = cb.selectCase() final Expression<Object> assignedInstalledCase = cb.selectCase()
.when(cb.equal(targetRoot.get(JpaTarget_.installedDistributionSet), dsRoot), 1) .when(cb.equal(targetRoot.get(JpaTarget_.installedDistributionSet), dsRoot), 1)
.when(cb.equal(targetRoot.get(JpaTarget_.assignedDistributionSet), dsRoot), 2).otherwise(3); .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); 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.jpa.model.JpaRollout_; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
import org.eclipse.hawkbit.repository.model.Rollout; 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.domain.Specification;
import org.springframework.data.jpa.repository.query.QueryUtils;
/** /**
* Specifications class for {@link Rollout}s. The class provides Spring Data * 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 * {@link Specification} for retrieving {@link Rollout}s by its DELETED
* attribute. Includes fetch for stuff that is required for {@link Rollout} * attribute. Includes fetch for stuff that is required for {@link Rollout}
* queries. * queries.
* *
* @param isDeleted * @param isDeleted
* TRUE/FALSE are compared to the attribute DELETED. If NULL the * TRUE/FALSE are compared to the attribute DELETED. If NULL the
* attribute is ignored * attribute is ignored
* @return the {@link Rollout} {@link Specification} * @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) -> { return (root, query, cb) -> {
final Predicate predicate = cb.equal(root.<Boolean> get(JpaRollout_.deleted), isDeleted); final Predicate predicate = cb.equal(root.<Boolean> get(JpaRollout_.deleted), isDeleted);
root.fetch(JpaRollout_.distributionSet); root.fetch(JpaRollout_.distributionSet);
query.orderBy(QueryUtils.toOrders(sort, root, cb));
return predicate; return predicate;
}; };
@@ -47,14 +50,14 @@ public final class RolloutSpecification {
/** /**
* Builds a {@link Specification} to search a rollout by name. * Builds a {@link Specification} to search a rollout by name.
* *
* @param searchText * @param searchText
* search string * search string
* @param isDeleted * @param isDeleted
* <code>true</code> if deleted rollouts should be included in * <code>true</code> if deleted rollouts should be included in
* the search. Otherwise <code>false</code> * the search. Otherwise <code>false</code>
* @return criteria specification with a query for name of a rollout * @return criteria specification with a query for name of a rollout
* *
*/ */
public static Specification<JpaRollout> likeName(final String searchText, final boolean isDeleted) { public static Specification<JpaRollout> likeName(final String searchText, final boolean isDeleted) {
return (rolloutRoot, query, criteriaBuilder) -> { return (rolloutRoot, query, criteriaBuilder) -> {

View File

@@ -18,6 +18,7 @@ import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType; import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ListJoin; import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.MapJoin; import javax.persistence.criteria.MapJoin;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path; import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root; 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.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; 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.domain.Specification;
import org.springframework.data.jpa.repository.query.QueryUtils;
/** /**
* Specifications class for {@link Target}s. The class provides Spring Data JPQL * Specifications class for {@link Target}s. The class provides Spring Data JPQL
@@ -571,7 +574,7 @@ public final class TargetSpecifications {
* distribution set * distribution set
* *
* Order: 1. Targets with DS installed, 2. Targets with DS assigned, 3. * 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 * NOTE: Other specs, pagables and sort objects may alter the queries
* orderBy entry too, possibly invalidating the applied order, keep in mind * orderBy entry too, possibly invalidating the applied order, keep in mind
@@ -579,9 +582,12 @@ public final class TargetSpecifications {
* *
* @param distributionSetIdForOrder * @param distributionSetIdForOrder
* distribution set to consider * distribution set to consider
* @param sort
* the sorting requested
* @return specification that applies order by ds, may be overwritten * @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) -> { return (targetRoot, query, cb) -> {
// Enhance query with custom select based sort // Enhance query with custom select based sort
final Expression<Object> selectCase = cb.selectCase() final Expression<Object> selectCase = cb.selectCase()
@@ -590,7 +596,14 @@ public final class TargetSpecifications {
.when(cb.equal(targetRoot.get(JpaTarget_.assignedDistributionSet).get(JpaDistributionSet_.id), .when(cb.equal(targetRoot.get(JpaTarget_.assignedDistributionSet).get(JpaDistributionSet_.id),
distributionSetIdForOrder), 2) distributionSetIdForOrder), 2)
.otherwise(100); .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 // Spec only provides order, so no further filtering
return query.getRestriction(); 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.junit.jupiter.api.Test;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; 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.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@@ -627,7 +629,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
final Iterator<DistributionSet> dsIterator = buildDistributionSets.iterator(); final Iterator<DistributionSet> dsIterator = buildDistributionSets.iterator();
final Iterator<Target> tIterator = buildTargetFixtures.iterator(); final Iterator<Target> tIterator = buildTargetFixtures.iterator();
dsIterator.next(); final DistributionSet dsFirst = dsIterator.next();
final DistributionSet dsSecond = dsIterator.next(); final DistributionSet dsSecond = dsIterator.next();
final DistributionSet dsThree = dsIterator.next(); final DistributionSet dsThree = dsIterator.next();
final DistributionSet dsFour = dsIterator.next(); final DistributionSet dsFour = dsIterator.next();
@@ -650,17 +652,50 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
final List<DistributionSet> tFirstPin = distributionSetManagement final List<DistributionSet> tFirstPin = distributionSetManagement
.findByDistributionSetFilterOrderByLinkedTarget(PAGE, distributionSetFilter, tFirst.getControllerId()) .findByDistributionSetFilterOrderByLinkedTarget(PAGE, distributionSetFilter, tFirst.getControllerId())
.getContent(); .getContent();
assertThat(tFirstPin.get(0)).isEqualTo(dsThree);
assertThat(tFirstPin).hasSize(10); 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 // target second has installed DS-2 and assigned DS-4 so check order
// correct // correct
final List<DistributionSet> tSecondPin = distributionSetManagement final List<DistributionSet> tSecondPin = distributionSetManagement
.findByDistributionSetFilterOrderByLinkedTarget(PAGE, distributionSetFilter, tSecond.getControllerId()) .findByDistributionSetFilterOrderByLinkedTarget(PAGE, distributionSetFilter, tSecond.getControllerId())
.getContent(); .getContent();
assertThat(tSecondPin).hasSize(10);
// installed
assertThat(tSecondPin.get(0)).isEqualTo(dsSecond); assertThat(tSecondPin.get(0)).isEqualTo(dsSecond);
// assigned
assertThat(tSecondPin.get(1)).isEqualTo(dsFour); 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 @Test

View File

@@ -10,6 +10,9 @@ package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat; 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.RolloutDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutCreatedEvent; 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.RolloutUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; 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.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.junit.jupiter.api.Test; 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.Description;
import io.qameta.allure.Feature; import io.qameta.allure.Feature;
@@ -28,13 +39,13 @@ import io.qameta.allure.Story;
@Feature("Component Tests - Repository") @Feature("Component Tests - Repository")
@Story("Rollout Management") @Story("Rollout Management")
public class RolloutGroupManagementTest extends AbstractJpaIntegrationTest { class RolloutGroupManagementTest extends AbstractJpaIntegrationTest {
@Test @Test
@Description("Verifies that management get access reacts as specfied on calls for non existing entities by means " @Description("Verifies that management get access reacts as specfied on calls for non existing entities by means "
+ "of Optional not present.") + "of Optional not present.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
public void nonExistingEntityAccessReturnsNotPresent() { void nonExistingEntityAccessReturnsNotPresent() {
assertThat(rolloutGroupManagement.get(NOT_EXIST_IDL)).isNotPresent(); assertThat(rolloutGroupManagement.get(NOT_EXIST_IDL)).isNotPresent();
assertThat(rolloutGroupManagement.getWithDetailedStatus(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 = RolloutGroupUpdatedEvent.class, count = 5),
@Expect(type = DistributionSetCreatedEvent.class, count = 1), @Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3), @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 = TargetCreatedEvent.class, count = 125),
@Expect(type = RolloutCreatedEvent.class, count = 1) }) @Expect(type = RolloutCreatedEvent.class, count = 1) })
public void entityQueriesReferringToNotExistingEntitiesThrowsException() { void entityQueriesReferringToNotExistingEntitiesThrowsException() {
testdataFactory.createRollout("xxx"); testdataFactory.createRollout("xxx");
verifyThrownExceptionBy(() -> rolloutGroupManagement.countByRollout(NOT_EXIST_IDL), "Rollout"); verifyThrownExceptionBy(() -> rolloutGroupManagement.countByRollout(NOT_EXIST_IDL), "Rollout");
@@ -73,4 +84,71 @@ public class RolloutGroupManagementTest extends AbstractJpaIntegrationTest {
"RolloutGroup"); "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.ConstraintViolationException;
import javax.validation.ValidationException; import javax.validation.ValidationException;
import org.apache.commons.lang3.RandomStringUtils;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.assertj.core.api.Condition; import org.assertj.core.api.Condition;
import org.awaitility.Awaitility; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice; import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
@@ -1761,6 +1763,47 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
.getNumberOfElements()).isEqualTo(2); .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 @Test
@Description("Creating a rollout without weight value when multi assignment in enabled.") @Description("Creating a rollout without weight value when multi assignment in enabled.")
void weightNotRequiredInMultiAssignmentMode() { void weightNotRequiredInMultiAssignmentMode() {

View File

@@ -46,6 +46,8 @@ import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; 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.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@@ -545,19 +547,28 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
.containsExactly(new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true), .containsExactly(new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true),
new AssignedSoftwareModule(unassigned, false)); 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 // with filter on module type only
assertThat(softwareModuleManagement assertThat(softwareModuleManagement
.findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc(PAGE, set.getId(), null, testType.getId()) .findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc(PAGE, set.getId(), null, testType.getId())
.getContent()).as("Found modules with given module type and the assigned ones first").containsExactly( .getContent()).as("Found modules with given module type and the assigned ones first").containsExactly(
new AssignedSoftwareModule(differentName, true), new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true),
new AssignedSoftwareModule(two, true), new AssignedSoftwareModule(unassigned, false)); new AssignedSoftwareModule(differentName, true), new AssignedSoftwareModule(unassigned, false));
// without any filter // without any filter
assertThat(softwareModuleManagement assertThat(softwareModuleManagement
.findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc(PAGE, set.getId(), null, null) .findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc(PAGE, set.getId(), null, null)
.getContent()).as("Found modules with the assigned ones first").containsExactly( .getContent()).as("Found modules with the assigned ones first").containsExactly(
new AssignedSoftwareModule(differentName, true), new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true),
new AssignedSoftwareModule(two, true), new AssignedSoftwareModule(four, true), new AssignedSoftwareModule(differentName, true), new AssignedSoftwareModule(four, true),
new AssignedSoftwareModule(unassigned, false)); 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.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice; 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.collect.Lists;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
@@ -573,7 +576,7 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest {
final Slice<Target> result = targetManagement.findByFilterOrderByLinkedDistributionSet(PAGE, ds.getId(), final Slice<Target> result = targetManagement.findByFilterOrderByLinkedDistributionSet(PAGE, ds.getId(),
new FilterParams(null, null, null, null, Boolean.FALSE)); 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); assertThat(result.getNumberOfElements()).isEqualTo(9);
final List<Target> expected = new ArrayList<>(); final List<Target> expected = new ArrayList<>();
@@ -586,7 +589,45 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest {
assertThat(result.getContent()).usingElementComparator(controllerIdComparator()) assertThat(result.getContent()).usingElementComparator(controllerIdComparator())
.containsExactly(expected.toArray(new Target[0])); .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 @Test
@@ -626,7 +667,7 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest {
final Slice<Target> result = targetManagement.findByFilterOrderByLinkedDistributionSet(PAGE, ds.getId(), final Slice<Target> result = targetManagement.findByFilterOrderByLinkedDistributionSet(PAGE, ds.getId(),
new FilterParams(null, Boolean.TRUE, null, null, Boolean.FALSE)); 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); assertThat(result.getNumberOfElements()).isEqualTo(9);
final List<Target> expected = new ArrayList<>(); final List<Target> expected = new ArrayList<>();

View File

@@ -1,4 +1,4 @@
/** /**
* Copyright (c) 2020 Bosch.IO GmbH and others. * Copyright (c) 2020 Bosch.IO GmbH and others.
* *
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
@@ -20,7 +20,7 @@ import org.eclipse.hawkbit.ui.common.data.proxies.ProxyIdentifiableEntity;
import org.eclipse.hawkbit.ui.common.data.proxies.ProxyNamedEntity; import org.eclipse.hawkbit.ui.common.data.proxies.ProxyNamedEntity;
import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTarget; import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTarget;
import org.eclipse.hawkbit.ui.common.grid.support.DeleteSupport; 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.SPUIStyleDefinitions;
import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
@@ -43,7 +43,6 @@ import com.vaadin.ui.themes.ValoTheme;
* Builder class for grid components * Builder class for grid components
*/ */
public final class GridComponentBuilder { public final class GridComponentBuilder {
public static final double DEFAULT_MIN_WIDTH = 100D; public static final double DEFAULT_MIN_WIDTH = 100D;
public static final String CREATED_BY_ID = "createdBy"; public static final String CREATED_BY_ID = "createdBy";
@@ -51,12 +50,20 @@ public final class GridComponentBuilder {
public static final String MODIFIED_BY_ID = "modifiedBy"; public static final String MODIFIED_BY_ID = "modifiedBy";
public static final String MODIFIED_DATE_ID = "modifiedDate"; 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() { private GridComponentBuilder() {
} }
/** /**
* Create a {@link Button} with link optic * Create a {@link Button} with link optic
* *
* @param idSuffix * @param idSuffix
* suffix to build the button ID * suffix to build the button ID
* @param idPrefix * @param idPrefix
@@ -90,7 +97,7 @@ public final class GridComponentBuilder {
/** /**
* Create a {@link Button} with link optic * Create a {@link Button} with link optic
* *
* @param entity * @param entity
* to build the button ID * to build the button ID
* @param idPrefix * @param idPrefix
@@ -103,14 +110,16 @@ public final class GridComponentBuilder {
* execute on button click (null for none) * execute on button click (null for none)
* @return the button * @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) { 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> * @param <E>
* entity type of the grid * entity type of the grid
* @param grid * @param grid
@@ -123,11 +132,15 @@ public final class GridComponentBuilder {
*/ */
public static <E extends ProxyNamedEntity> Column<E, String> addNameColumn(final Grid<E> grid, public static <E extends ProxyNamedEntity> Column<E, String> addNameColumn(final Grid<E> grid,
final VaadinMessageSource i18n, final String columnId) { 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 * @param grid
* to add the column to * to add the column to
@@ -139,20 +152,23 @@ public final class GridComponentBuilder {
*/ */
public static Column<ProxyTarget, Button> addControllerIdColumn(final Grid<ProxyTarget> grid, public static Column<ProxyTarget, Button> addControllerIdColumn(final Grid<ProxyTarget> grid,
final VaadinMessageSource i18n, final String columnId) { final VaadinMessageSource i18n, final String columnId) {
return addComponentColumn(grid, t -> GridComponentBuilder.buildControllerIdLink(t, columnId)).setId(columnId) final Column<ProxyTarget, Button> column = addComponentColumn(grid,
.setCaption(i18n.getMessage("header.controllerId")).setHidable(false) t -> buildControllerIdLink(t, columnId)).setId(columnId)
.setMinimumWidth(DEFAULT_MIN_WIDTH); .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) { 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() return buildLink(idSuffix, linkIdPrefix, target.getControllerId(), true, clickEvent -> UI.getCurrent()
.getNavigator().navigateTo("deployment/target=" + target.getControllerId())); .getNavigator().navigateTo("deployment/target=" + target.getControllerId()));
} }
/** /**
* Add description column to grid * Add description column to grid
* *
* @param <E> * @param <E>
* entity type of the grid * entity type of the grid
* @param grid * @param grid
@@ -170,7 +186,7 @@ public final class GridComponentBuilder {
/** /**
* Add "created by", "created at", "modified by" and "modified at" column * Add "created by", "created at", "modified by" and "modified at" column
* *
* @param <E> * @param <E>
* entity type of the grid * entity type of the grid
* @param grid * @param grid
@@ -182,18 +198,30 @@ public final class GridComponentBuilder {
public static <E extends ProxyNamedEntity> List<Column<E, String>> addCreatedAndModifiedColumns(final Grid<E> grid, public static <E extends ProxyNamedEntity> List<Column<E, String>> addCreatedAndModifiedColumns(final Grid<E> grid,
final VaadinMessageSource i18n) { final VaadinMessageSource i18n) {
final List<Column<E, String>> columns = new ArrayList<>(); 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)); final Column<E, String> createdByColumn = addColumn(i18n, grid, E::getCreatedBy, "header.createdBy",
columns.add( CREATED_BY_ID, DEFAULT_MIN_WIDTH);
addColumn(i18n, grid, E::getLastModifiedBy, "header.modifiedBy", MODIFIED_BY_ID, DEFAULT_MIN_WIDTH)); setColumnSortable(createdByColumn, CREATED_BY_PROPERTY_NAME);
columns.add( columns.add(createdByColumn);
addColumn(i18n, grid, E::getModifiedDate, "header.modifiedDate", MODIFIED_DATE_ID, DEFAULT_MIN_WIDTH));
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; return columns;
} }
/** /**
* Add version column to grid * Add version column to grid
* *
* @param <E> * @param <E>
* entity type of the grid * entity type of the grid
* @param grid * @param grid
@@ -208,7 +236,10 @@ public final class GridComponentBuilder {
*/ */
public static <E> Column<E, String> addVersionColumn(final Grid<E> grid, final VaadinMessageSource i18n, public static <E> Column<E, String> addVersionColumn(final Grid<E> grid, final VaadinMessageSource i18n,
final ValueProvider<E, String> valueProvider, final String columnId) { 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, private static <E, T> Column<E, T> addColumn(final VaadinMessageSource i18n, final Grid<E> grid,
@@ -225,7 +256,7 @@ public final class GridComponentBuilder {
/** /**
* Add column to grid with the standard settings * Add column to grid with the standard settings
* *
* @param <E> * @param <E>
* entity type of the grid * entity type of the grid
* @param grid * @param grid
@@ -240,7 +271,7 @@ public final class GridComponentBuilder {
/** /**
* Add column to grid with the standard settings * Add column to grid with the standard settings
* *
* @param <E> * @param <E>
* entity type of the grid * entity type of the grid
* @param grid * @param grid
@@ -253,16 +284,14 @@ public final class GridComponentBuilder {
*/ */
public static <E, T> Column<E, T> addColumn(final Grid<E> grid, final ValueProvider<E, T> valueProvider, public static <E, T> Column<E, T> addColumn(final Grid<E> grid, final ValueProvider<E, T> valueProvider,
final StyleGenerator<E> styleGenerator) { final StyleGenerator<E> styleGenerator) {
final Column<E, T> column = grid.addColumn(valueProvider).setMinimumWidthFromContent(false).setExpandRatio(1); final Column<E, T> column = grid.addColumn(valueProvider);
if (styleGenerator != null) { commonColumnConfiguration(column, styleGenerator);
column.setStyleGenerator(styleGenerator);
}
return column; return column;
} }
/** /**
* Add column to grid with the standard settings * Add column to grid with the standard settings
* *
* @param <E> * @param <E>
* entity type of the grid * entity type of the grid
* @param grid * @param grid
@@ -278,7 +307,7 @@ public final class GridComponentBuilder {
/** /**
* Add column to grid with the standard settings * Add column to grid with the standard settings
* *
* @param <E> * @param <E>
* entity type of the grid * entity type of the grid
* @param grid * @param grid
@@ -291,8 +320,17 @@ public final class GridComponentBuilder {
*/ */
public static <E, T extends Component> Column<E, T> addComponentColumn(final Grid<E> grid, public static <E, T extends Component> Column<E, T> addComponentColumn(final Grid<E> grid,
final ValueProvider<E, T> componentProvider, final StyleGenerator<E> styleGenerator) { final ValueProvider<E, T> componentProvider, final StyleGenerator<E> styleGenerator) {
final Column<E, T> column = grid.addComponentColumn(componentProvider).setMinimumWidthFromContent(false) final Column<E, T> column = grid.addComponentColumn(componentProvider);
.setExpandRatio(1); 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) { if (styleGenerator != null) {
column.setStyleGenerator(styleGenerator); column.setStyleGenerator(styleGenerator);
} }
@@ -301,7 +339,7 @@ public final class GridComponentBuilder {
/** /**
* Add delete button column to grid * Add delete button column to grid
* *
* @param <E> * @param <E>
* entity type of the grid * entity type of the grid
* @param grid * @param grid
@@ -331,7 +369,7 @@ public final class GridComponentBuilder {
/** /**
* Add an action button column to a grid * Add an action button column to a grid
* *
* @param <T> * @param <T>
* type of the entity displayed by the grid * type of the entity displayed by the grid
* @param grid * @param grid
@@ -351,7 +389,7 @@ public final class GridComponentBuilder {
/** /**
* Add an action button column to a grid * Add an action button column to a grid
* *
* @param <T> * @param <T>
* type of the entity displayed by the grid * type of the entity displayed by the grid
* @param grid * @param grid
@@ -374,13 +412,32 @@ public final class GridComponentBuilder {
final StyleGenerator<T> finalStyleGenerator = merge(Arrays.asList(styleGenerator, additionalStyleGenerator)); final StyleGenerator<T> finalStyleGenerator = merge(Arrays.asList(styleGenerator, additionalStyleGenerator));
final Column<T, V> column = grid.addComponentColumn(iconProvider).setId(columnId) 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)) { if (!StringUtils.isEmpty(caption)) {
column.setCaption(caption); column.setCaption(caption);
} }
return column; 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) { 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) return item -> generators.stream().filter(Objects::nonNull).map(gen -> gen.apply(item)).filter(Objects::nonNull)
.collect(Collectors.joining(" ")); .collect(Collectors.joining(" "));
@@ -388,7 +445,7 @@ public final class GridComponentBuilder {
/** /**
* Join columns to form an action column * Join columns to form an action column
* *
* @param i18n * @param i18n
* message source for internationalization * message source for internationalization
* @param headerRow * @param headerRow
@@ -403,7 +460,7 @@ public final class GridComponentBuilder {
/** /**
* Join columns to form an icon column * Join columns to form an icon column
* *
* @param headerRow * @param headerRow
* header row * header row
* @param headerCaption * @param headerCaption
@@ -423,7 +480,7 @@ public final class GridComponentBuilder {
/** /**
* Create an action button (e.g. a delete button) * Create an action button (e.g. a delete button)
* *
* @param i18n * @param i18n
* message source for internationalization * message source for internationalization
* @param clickListener * @param clickListener
@@ -458,5 +515,4 @@ public final class GridComponentBuilder {
return actionButton; return actionButton;
} }
}
}

View File

@@ -8,7 +8,9 @@
*/ */
package org.eclipse.hawkbit.ui.common.data.providers; package org.eclipse.hawkbit.ui.common.data.providers;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; 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.PageRequest;
import org.springframework.data.domain.Slice; import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort; 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.AbstractBackEndDataProvider;
import com.vaadin.data.provider.Query; 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 * 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 @Override
protected Stream<T> fetchFromBackEnd(final Query<T, F> query) { protected Stream<T> fetchFromBackEnd(final Query<T, F> query) {
return getProxyEntities( 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) { private PageRequest convertToPageRequest(final Query<T, F> query, final Sort sort) {
@@ -67,7 +103,7 @@ public abstract class AbstractGenericDataProvider<T extends ProxyIdentifiableEnt
@Override @Override
protected int sizeInBackEnd(final Query<T, F> query) { 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 { try {
return Math.toIntExact(size); return Math.toIntExact(size);
@@ -85,4 +121,4 @@ public abstract class AbstractGenericDataProvider<T extends ProxyIdentifiableEnt
Objects.requireNonNull(item, "Cannot provide an id for a null item."); Objects.requireNonNull(item, "Cannot provide an id for a null item.");
return item.getId(); return item.getId();
} }
} }

View File

@@ -19,11 +19,15 @@ import org.eclipse.hawkbit.ui.common.grid.support.SelectionSupport;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import org.vaadin.spring.events.EventBus.UIEventBus; import org.vaadin.spring.events.EventBus.UIEventBus;
import com.vaadin.data.ValueProvider;
import com.vaadin.data.provider.DataCommunicator; import com.vaadin.data.provider.DataCommunicator;
import com.vaadin.data.provider.DataProviderListener; import com.vaadin.data.provider.DataProviderListener;
import com.vaadin.data.provider.Query; import com.vaadin.data.provider.Query;
import com.vaadin.ui.Component;
import com.vaadin.ui.Grid; import com.vaadin.ui.Grid;
import com.vaadin.ui.Grid.Column.NestedNullBehavior;
import com.vaadin.ui.components.grid.GridSelectionModel; import com.vaadin.ui.components.grid.GridSelectionModel;
import com.vaadin.ui.renderers.AbstractRenderer;
/** /**
* Abstract grid that offers various capabilities (aka support) to offer * 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()); setId(getGridId());
setColumnReorderingAllowed(false); setColumnReorderingAllowed(false);
addColumns(); addColumns();
disableColumnSorting();
setFrozenColumnCount(-1); setFrozenColumnCount(-1);
if (selectionSupport == null) { if (selectionSupport == null) {
@@ -176,10 +179,48 @@ public abstract class AbstractGrid<T extends ProxyIdentifiableEntity, F> extends
*/ */
public abstract void addColumns(); public abstract void addColumns();
private void disableColumnSorting() { @Override
for (final Column<T, ?> c : getColumns()) { public Column<T, ?> addColumn(final String propertyName) {
c.setSortable(false); 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 @Override
public void addColumns() { 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")); .setCaption(i18n.getMessage("header.name"));
GridComponentBuilder.setColumnSortable(nameColumn, "name");
GridComponentBuilder.addCreatedAndModifiedColumns(this, i18n); 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.filtermanagement.state.TargetFilterDetailsLayoutUiState;
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
import com.vaadin.ui.Label;
/** /**
* Shows the targets as a result of the executed filter query. * 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.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")); i18n.getMessage("header.status"));
GridComponentBuilder.setColumnSortable(statusColumn, "updateStatus");
GridComponentBuilder.addCreatedAndModifiedColumns(this, i18n); GridComponentBuilder.addCreatedAndModifiedColumns(this, i18n);

View File

@@ -61,6 +61,7 @@ import com.vaadin.data.ValueProvider;
import com.vaadin.icons.VaadinIcons; import com.vaadin.icons.VaadinIcons;
import com.vaadin.shared.ui.ContentMode; import com.vaadin.shared.ui.ContentMode;
import com.vaadin.ui.Button; import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.UI; import com.vaadin.ui.UI;
import com.vaadin.ui.Window; import com.vaadin.ui.Window;
import com.vaadin.ui.renderers.HtmlRenderer; 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 ROLLOUT_LINK_ID = "rollout";
private static final String DIST_NAME_VERSION_ID = "distNameVersion"; private static final String DIST_NAME_VERSION_ID = "distNameVersion";
private static final String STATUS_ID = "status"; 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 TOTAL_TARGETS_COUNT_STATUS_ID = "totalTargetsCountStatus";
private static final String NUMBER_OF_GROUPS_ID = "numberOfGroups"; private static final String NUMBER_OF_GROUPS_ID = "numberOfGroups";
private static final String TOTAL_TARGETS_ID = "totalTargets"; private static final String TOTAL_TARGETS_ID = "totalTargets";
@@ -263,8 +265,10 @@ public class RolloutGrid extends AbstractGrid<ProxyRollout, String> {
@Override @Override
public void addColumns() { 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); .setCaption(i18n.getMessage("header.name")).setHidable(false).setExpandRatio(3);
GridComponentBuilder.setColumnSortable(nameColumn, "name");
GridComponentBuilder.addDescriptionColumn(this, i18n, DESC_ID).setHidable(true).setHidden(true); 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")) .setId(DIST_NAME_VERSION_ID).setCaption(i18n.getMessage("header.distributionset"))
.setDescriptionGenerator(this::createDSTooltipText).setHidable(true).setExpandRatio(2); .setDescriptionGenerator(this::createDSTooltipText).setHidable(true).setExpandRatio(2);
GridComponentBuilder final Column<ProxyRollout, Label> statusColumn = GridComponentBuilder
.addIconColumn(this, rolloutStatusIconSupplier::getLabel, STATUS_ID, i18n.getMessage("header.status")) .addIconColumn(this, rolloutStatusIconSupplier::getLabel, STATUS_ID, i18n.getMessage("header.status"))
.setHidable(true); .setHidable(true);
GridComponentBuilder.setColumnSortable(statusColumn, STATUS_PROPERTY_NAME);
GridComponentBuilder GridComponentBuilder
.addIconColumn(this, actionTypeIconSupplier::getLabel, ACTION_TYPE_ID, i18n.getMessage("header.type")) .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.google.common.base.Predicates;
import com.vaadin.shared.ui.ContentMode; import com.vaadin.shared.ui.ContentMode;
import com.vaadin.ui.Button; import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.renderers.HtmlRenderer; import com.vaadin.ui.renderers.HtmlRenderer;
/** /**
@@ -112,14 +113,17 @@ public class RolloutGroupGrid extends AbstractGrid<ProxyRolloutGroup, Long> {
@Override @Override
public void addColumns() { 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); .setCaption(i18n.getMessage("header.name")).setHidable(false).setExpandRatio(3);
GridComponentBuilder.setColumnSortable(nameColumn, "name");
GridComponentBuilder.addDescriptionColumn(this, i18n, SPUILabelDefinitions.VAR_DESC).setHidable(true) GridComponentBuilder.addDescriptionColumn(this, i18n, SPUILabelDefinitions.VAR_DESC).setHidable(true)
.setHidden(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); SPUILabelDefinitions.VAR_STATUS, i18n.getMessage("header.status")).setHidable(true);
GridComponentBuilder.setColumnSortable(statusColumn, "status");
addColumn(rolloutGroup -> DistributionBarHelper addColumn(rolloutGroup -> DistributionBarHelper
.getDistributionBarAsHTMLString(rolloutGroup.getTotalTargetCountStatus().getStatusTotalCountMap()), .getDistributionBarAsHTMLString(rolloutGroup.getTotalTargetCountStatus().getStatusTotalCountMap()),
@@ -155,7 +159,7 @@ public class RolloutGroupGrid extends AbstractGrid<ProxyRolloutGroup, Long> {
final boolean enableButton = RolloutGroupStatus.CREATING != rolloutGroup.getStatus() final boolean enableButton = RolloutGroupStatus.CREATING != rolloutGroup.getStatus()
&& permissionChecker.hasRolloutTargetsReadPermission(); && permissionChecker.hasRolloutTargetsReadPermission();
return GridComponentBuilder.buildLink(rolloutGroup, "rolloutgroup.link.", rolloutGroup.getName(), enableButton, return GridComponentBuilder.buildLink(rolloutGroup, "rolloutgroup.link", rolloutGroup.getName(), enableButton,
clickEvent -> onClickOfRolloutGroupName(rolloutGroup)); 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.SPUILabelDefinitions;
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
import com.vaadin.ui.Label;
/** /**
* Grid component with targets of rollout group. * 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.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")); i18n.getMessage("header.status"));
GridComponentBuilder.setColumnSortable(statusColumn, "status");
GridComponentBuilder.addCreatedAndModifiedColumns(this, i18n); GridComponentBuilder.addCreatedAndModifiedColumns(this, i18n);

View File

@@ -13,33 +13,34 @@ import java.util.Base64.Encoder;
import java.util.Objects; import java.util.Objects;
/** /**
* Encodes controller IDs to make them embeddable into HTML as element * Encodes a string attribute of an entity (e.g. name) to make it embeddable
* identifiers. * into HTML as element identifiers.
*/ */
public class ControllerIdHtmlEncoder { public class StringHtmlEncoder {
/** /**
* Base64 encoder which suppresses trailing padding characters. * Base64 encoder which suppresses trailing padding characters.
*/ */
private static Encoder BASE64 = Base64.getEncoder().withoutPadding(); private static Encoder BASE64 = Base64.getEncoder().withoutPadding();
private ControllerIdHtmlEncoder() { private StringHtmlEncoder() {
// class should not be instantiated // 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. * element IDs.
* *
* @param controllerId * @param attribute
* The controller ID to be encoded. Must not be * The attribute of an entity to be encoded. Must not be
* <code>null</code>. * <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) { public static String encode(final String attribute) {
Objects.requireNonNull(controllerId); Objects.requireNonNull(attribute);
return BASE64.encodeToString(controllerId.getBytes()); return BASE64.encodeToString(attribute.getBytes());
} }
} }