Enable sorting in tables (#1279)

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

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

View File

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

View File

@@ -374,14 +374,14 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
@Override
public Slice<DistributionSet> findByDistributionSetFilterOrderByLinkedTarget(final Pageable pageable,
final DistributionSetFilter distributionSetFilter, final String assignedOrInstalled) {
final List<Specification<JpaDistributionSet>> specList = buildDistributionSetSpecifications(
distributionSetFilter);
specList.add(DistributionSetSpecification.orderedByLinkedTarget(assignedOrInstalled));
// remove default sort from pageable to not overwrite sorted spec
final OffsetBasedPageRequest unsortedPage = new OffsetBasedPageRequest(pageable.getOffset(),
pageable.getPageSize(), Sort.unsorted());
final List<Specification<JpaDistributionSet>> specList = buildDistributionSetSpecifications(
distributionSetFilter);
specList.add(DistributionSetSpecification.orderedByLinkedTarget(assignedOrInstalled, pageable.getSort()));
return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetRepository, unsortedPage, specList);
}

View File

@@ -21,6 +21,7 @@ import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Root;
import org.eclipse.hawkbit.repository.RolloutGroupFields;
@@ -51,7 +52,9 @@ import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@@ -259,7 +262,8 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
final CriteriaQuery<Object[]> multiselect = query.multiselect(targetJoin, actionJoin.get(JpaAction_.status))
.where(cb.equal(targetRoot.get(RolloutTargetGroup_.rolloutGroup).get(JpaRolloutGroup_.id),
rolloutGroupId));
rolloutGroupId))
.orderBy(getOrderBy(pageRequest, cb, targetJoin, actionJoin));
final List<TargetWithActionStatus> targetWithActionStatus = entityManager.createQuery(multiselect)
.setFirstResult((int) pageRequest.getOffset()).setMaxResults(pageRequest.getPageSize()).getResultList()
.stream().map(o -> new TargetWithActionStatus((Target) o[0], (Action.Status) o[1]))
@@ -268,6 +272,25 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
return new PageImpl<>(targetWithActionStatus, pageRequest, totalCount);
}
private List<Order> getOrderBy(final Pageable pageRequest, final CriteriaBuilder cb,
final Join<RolloutTargetGroup, JpaTarget> targetJoin,
final ListJoin<RolloutTargetGroup, JpaAction> actionJoin) {
return pageRequest.getSort().get().flatMap(order -> {
final List<Order> orders;
final String property = order.getProperty();
// we consider status as property from JpaAction ...
if ("status".equals(property)) {
orders = QueryUtils.toOrders(Sort.by(order.getDirection(), property), actionJoin, cb);
}
// ... and every other property from JpaTarget
else {
orders = QueryUtils.toOrders(Sort.by(order.getDirection(), property), targetJoin, cb);
}
return orders.stream();
}).collect(Collectors.toList());
}
@Override
public long countTargetsOfRolloutsGroup(final long rolloutGroupId) {
throwExceptionIfRolloutGroupDoesNotExist(rolloutGroupId);

View File

@@ -47,6 +47,7 @@ import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
@@ -75,6 +76,8 @@ import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.orm.jpa.vendor.Database;
@@ -162,7 +165,8 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override
public Page<Rollout> findAll(final Pageable pageable, final boolean deleted) {
return JpaManagementHelper.findAllWithCountBySpec(rolloutRepository, pageable,
Collections.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted)));
Collections
.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort())));
}
@Override
@@ -170,7 +174,7 @@ public class JpaRolloutManagement implements RolloutManagement {
final List<Specification<JpaRollout>> specList = Lists.newArrayListWithExpectedSize(2);
specList.add(
RSQLUtility.buildRsqlSpecification(rsqlParam, RolloutFields.class, virtualPropertyReplacer, database));
specList.add(RolloutSpecification.isDeletedWithDistributionSet(deleted));
specList.add(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort()));
return JpaManagementHelper.findAllWithCountBySpec(rolloutRepository, pageable, specList);
}
@@ -298,7 +302,7 @@ public class JpaRolloutManagement implements RolloutManagement {
/**
* In case the given group is missing conditions or actions, they will be
* set from the supplied default conditions.
*
*
* @param create
* group to check
* @param conditions
@@ -481,7 +485,8 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override
public long count() {
return rolloutRepository.count(RolloutSpecification.isDeletedWithDistributionSet(false));
return rolloutRepository.count(
RolloutSpecification.isDeletedWithDistributionSet(false, Sort.by(Direction.DESC, JpaRollout_.ID)));
}
@Override
@@ -553,7 +558,8 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override
public Slice<Rollout> findAllWithDetailedStatus(final Pageable pageable, final boolean deleted) {
final Slice<Rollout> rollouts = JpaManagementHelper.findAllWithoutCountBySpec(rolloutRepository, pageable,
Collections.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted)));
Collections
.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort())));
setRolloutStatusDetails(rollouts);
return rollouts;
}

View File

@@ -27,6 +27,7 @@ import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
@@ -73,7 +74,9 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
@@ -308,6 +311,10 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
}
@Override
// In the interface org.springframework.data.domain.Pageable.getSort the
// return value is not guaranteed to be non-null, therefore a null check is
// necessary otherwise we rely on the implementation but this could change.
@SuppressWarnings({ "squid:S2583", "squid:S2589" })
public Slice<AssignedSoftwareModule> findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc(
final Pageable pageable, final long dsId, final String searchText, final Long smTypeId) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
@@ -331,8 +338,16 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
query.groupBy(smRoot);
query.orderBy(cb.desc(assignedCaseMax), cb.asc(smRoot.get(JpaSoftwareModule_.name)),
cb.asc(smRoot.get(JpaSoftwareModule_.version)));
final Sort sort = pageable.getSort();
final List<Order> orders = new ArrayList<>();
orders.add(cb.desc(assignedCaseMax));
if (sort == null || sort.isEmpty()) {
orders.add(cb.asc(smRoot.get(JpaSoftwareModule_.name)));
orders.add(cb.asc(smRoot.get(JpaSoftwareModule_.version)));
} else {
orders.addAll(QueryUtils.toOrders(sort, smRoot, cb));
}
query.orderBy(orders);
final int pageSize = pageable.getPageSize();
final List<Tuple> smWithAssignedFlagList = entityManager.createQuery(query)
@@ -486,7 +501,7 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
/**
* Asserts the meta data quota for the software module with the given ID.
*
*
* @param moduleId
* The software module ID.
* @param requested

View File

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

View File

@@ -16,6 +16,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
@@ -29,7 +30,9 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.util.CollectionUtils;
/**
@@ -265,7 +268,8 @@ public final class DistributionSetSpecification {
* Can be added to specification chain to order result by provided target
*
* Order: 1. Distribution set installed on target, 2. Distribution set(s)
* assigned to target, 3. Based on distribution set id
* assigned to target, 3. Based on requested sorting or id if
* <code>null</code>.
*
* NOTE: Other specs, pagables and sort objects may alter the queries
* orderBy entry too, possibly invalidating the applied order, keep in mind
@@ -273,16 +277,26 @@ public final class DistributionSetSpecification {
*
* @param linkedControllerId
* controller id to get installed/assigned DS for
* @param sort
* @return specification that applies order by target, may be overwritten
*/
public static Specification<JpaDistributionSet> orderedByLinkedTarget(final String linkedControllerId) {
public static Specification<JpaDistributionSet> orderedByLinkedTarget(final String linkedControllerId,
final Sort sort) {
return (dsRoot, query, cb) -> {
final Root<JpaTarget> targetRoot = query.from(JpaTarget.class);
final Expression<Object> assignedInstalledCase = cb.selectCase()
.when(cb.equal(targetRoot.get(JpaTarget_.installedDistributionSet), dsRoot), 1)
.when(cb.equal(targetRoot.get(JpaTarget_.assignedDistributionSet), dsRoot), 2).otherwise(3);
query.orderBy(cb.asc(assignedInstalledCase), cb.asc(dsRoot.get(JpaDistributionSet_.id)));
final List<Order> orders = new ArrayList<>();
orders.add(cb.asc(assignedInstalledCase));
if (sort == null || sort.isEmpty()) {
orders.add(cb.asc(dsRoot.get(JpaDistributionSet_.id)));
} else {
orders.addAll(QueryUtils.toOrders(sort, dsRoot, cb));
}
query.orderBy(orders);
return cb.equal(targetRoot.get(JpaTarget_.controllerId), linkedControllerId);
};

View File

@@ -13,7 +13,9 @@ import javax.persistence.criteria.Predicate;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.query.QueryUtils;
/**
* Specifications class for {@link Rollout}s. The class provides Spring Data
@@ -29,17 +31,18 @@ public final class RolloutSpecification {
* {@link Specification} for retrieving {@link Rollout}s by its DELETED
* attribute. Includes fetch for stuff that is required for {@link Rollout}
* queries.
*
*
* @param isDeleted
* TRUE/FALSE are compared to the attribute DELETED. If NULL the
* attribute is ignored
* @return the {@link Rollout} {@link Specification}
*/
public static Specification<JpaRollout> isDeletedWithDistributionSet(final Boolean isDeleted) {
public static Specification<JpaRollout> isDeletedWithDistributionSet(final Boolean isDeleted, final Sort sort) {
return (root, query, cb) -> {
final Predicate predicate = cb.equal(root.<Boolean> get(JpaRollout_.deleted), isDeleted);
root.fetch(JpaRollout_.distributionSet);
query.orderBy(QueryUtils.toOrders(sort, root, cb));
return predicate;
};
@@ -47,14 +50,14 @@ public final class RolloutSpecification {
/**
* Builds a {@link Specification} to search a rollout by name.
*
*
* @param searchText
* search string
* @param isDeleted
* <code>true</code> if deleted rollouts should be included in
* the search. Otherwise <code>false</code>
* @return criteria specification with a query for name of a rollout
*
*
*/
public static Specification<JpaRollout> likeName(final String searchText, final boolean isDeleted) {
return (rolloutRoot, query, criteriaBuilder) -> {

View File

@@ -18,6 +18,7 @@ import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.MapJoin;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
@@ -47,7 +48,9 @@ import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.query.QueryUtils;
/**
* Specifications class for {@link Target}s. The class provides Spring Data JPQL
@@ -571,7 +574,7 @@ public final class TargetSpecifications {
* distribution set
*
* Order: 1. Targets with DS installed, 2. Targets with DS assigned, 3.
* Based on target id
* Based on requested sorting or id if <code>null</code>.
*
* NOTE: Other specs, pagables and sort objects may alter the queries
* orderBy entry too, possibly invalidating the applied order, keep in mind
@@ -579,9 +582,12 @@ public final class TargetSpecifications {
*
* @param distributionSetIdForOrder
* distribution set to consider
* @param sort
* the sorting requested
* @return specification that applies order by ds, may be overwritten
*/
public static Specification<JpaTarget> orderedByLinkedDistributionSet(final long distributionSetIdForOrder) {
public static Specification<JpaTarget> orderedByLinkedDistributionSet(final long distributionSetIdForOrder,
final Sort sort) {
return (targetRoot, query, cb) -> {
// Enhance query with custom select based sort
final Expression<Object> selectCase = cb.selectCase()
@@ -590,7 +596,14 @@ public final class TargetSpecifications {
.when(cb.equal(targetRoot.get(JpaTarget_.assignedDistributionSet).get(JpaDistributionSet_.id),
distributionSetIdForOrder), 2)
.otherwise(100);
query.orderBy(cb.asc(selectCase), cb.desc(targetRoot.get(JpaTarget_.id)));
final List<Order> orders = new ArrayList<>();
orders.add(cb.asc(selectCase));
if (sort == null || sort.isEmpty()) {
orders.add(cb.desc(targetRoot.get(JpaTarget_.id)));
} else {
orders.addAll(QueryUtils.toOrders(sort, targetRoot, cb));
}
query.orderBy(orders);
// Spec only provides order, so no further filtering
return query.getRestriction();

View File

@@ -63,6 +63,8 @@ import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -627,7 +629,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
final Iterator<DistributionSet> dsIterator = buildDistributionSets.iterator();
final Iterator<Target> tIterator = buildTargetFixtures.iterator();
dsIterator.next();
final DistributionSet dsFirst = dsIterator.next();
final DistributionSet dsSecond = dsIterator.next();
final DistributionSet dsThree = dsIterator.next();
final DistributionSet dsFour = dsIterator.next();
@@ -650,17 +652,50 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
final List<DistributionSet> tFirstPin = distributionSetManagement
.findByDistributionSetFilterOrderByLinkedTarget(PAGE, distributionSetFilter, tFirst.getControllerId())
.getContent();
assertThat(tFirstPin.get(0)).isEqualTo(dsThree);
assertThat(tFirstPin).hasSize(10);
// assigned
assertThat(tFirstPin.get(0)).isEqualTo(dsThree);
// remaining id:ASC
assertThat(tFirstPin.get(1)).isEqualTo(dsFirst);
assertThat(tFirstPin.get(2)).isEqualTo(dsSecond);
assertThat(tFirstPin.get(3)).isEqualTo(dsFour);
// target second has installed DS-2 and assigned DS-4 so check order
// correct
final List<DistributionSet> tSecondPin = distributionSetManagement
.findByDistributionSetFilterOrderByLinkedTarget(PAGE, distributionSetFilter, tSecond.getControllerId())
.getContent();
assertThat(tSecondPin).hasSize(10);
// installed
assertThat(tSecondPin.get(0)).isEqualTo(dsSecond);
// assigned
assertThat(tSecondPin.get(1)).isEqualTo(dsFour);
assertThat(tFirstPin).hasSize(10);
// remaining id:ASC
assertThat(tSecondPin.get(2)).isEqualTo(dsFirst);
assertThat(tSecondPin.get(3)).isEqualTo(dsThree);
// target second has installed DS-2 and assigned DS-4 so check order
// correct
final List<DistributionSet> tSecondPinOrderedByName = distributionSetManagement
.findByDistributionSetFilterOrderByLinkedTarget(
PageRequest.of(0, 500, Sort.by(Direction.DESC, "version")),
distributionSetFilter, tSecond.getControllerId())
.getContent();
assertThat(tSecondPinOrderedByName).hasSize(10);
// installed
assertThat(tSecondPinOrderedByName.get(0)).isEqualTo(buildDistributionSets.get(1));
// assigned
assertThat(tSecondPinOrderedByName.get(1)).isEqualTo(buildDistributionSets.get(3));
// remaining version:DESC
assertThat(tSecondPinOrderedByName.get(2)).isEqualTo(buildDistributionSets.get(9));
assertThat(tSecondPinOrderedByName.get(3)).isEqualTo(buildDistributionSets.get(8));
assertThat(tSecondPinOrderedByName.get(4)).isEqualTo(buildDistributionSets.get(7));
assertThat(tSecondPinOrderedByName.get(5)).isEqualTo(buildDistributionSets.get(6));
assertThat(tSecondPinOrderedByName.get(6)).isEqualTo(buildDistributionSets.get(5));
assertThat(tSecondPinOrderedByName.get(7)).isEqualTo(buildDistributionSets.get(4));
assertThat(tSecondPinOrderedByName.get(8)).isEqualTo(buildDistributionSets.get(2));
assertThat(tSecondPinOrderedByName.get(9)).isEqualTo(buildDistributionSets.get(0));
}
@Test

View File

@@ -10,6 +10,9 @@ package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutCreatedEvent;
@@ -18,9 +21,17 @@ import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupUpdatedEve
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetWithActionStatus;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
@@ -28,13 +39,13 @@ import io.qameta.allure.Story;
@Feature("Component Tests - Repository")
@Story("Rollout Management")
public class RolloutGroupManagementTest extends AbstractJpaIntegrationTest {
class RolloutGroupManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Verifies that management get access reacts as specfied on calls for non existing entities by means "
+ "of Optional not present.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
public void nonExistingEntityAccessReturnsNotPresent() {
void nonExistingEntityAccessReturnsNotPresent() {
assertThat(rolloutGroupManagement.get(NOT_EXIST_IDL)).isNotPresent();
assertThat(rolloutGroupManagement.getWithDetailedStatus(NOT_EXIST_IDL)).isNotPresent();
@@ -48,11 +59,11 @@ public class RolloutGroupManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = RolloutGroupUpdatedEvent.class, count = 5),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
@Expect(type = RolloutUpdatedEvent.class, count = 1),
@Expect(type = RolloutUpdatedEvent.class, count = 1),
@Expect(type = TargetCreatedEvent.class, count = 125),
@Expect(type = RolloutCreatedEvent.class, count = 1) })
public void entityQueriesReferringToNotExistingEntitiesThrowsException() {
void entityQueriesReferringToNotExistingEntitiesThrowsException() {
testdataFactory.createRollout("xxx");
verifyThrownExceptionBy(() -> rolloutGroupManagement.countByRollout(NOT_EXIST_IDL), "Rollout");
@@ -73,4 +84,71 @@ public class RolloutGroupManagementTest extends AbstractJpaIntegrationTest {
"RolloutGroup");
}
@Test
@Description("Verifies that the returned result considers the provided sort parameters.")
void findAllTargetsOfRolloutGroupWithActionStatusConsidersSorting() {
final String prefix = RandomStringUtils.randomAlphanumeric(5);
final Rollout rollout = testdataFactory.createRollout(prefix);
final List<RolloutGroup> rolloutGroups = rolloutGroupManagement.findByRollout(PAGE, rollout.getId())
.getContent();
final RolloutGroup rolloutGroup = rolloutGroups.get(0);
rolloutManagement.handleRollouts();
rolloutManagement.start(rollout.getId());
rolloutManagement.handleRollouts();
rolloutManagement.pauseRollout(rollout.getId());
rolloutManagement.handleRollouts();
final List<Target> targets = rolloutGroupManagement.findTargetsOfRolloutGroup(PAGE, rolloutGroup.getId())
.getContent();
Target targetCancelled = targets.get(0);
final Action actionCancelled = deploymentManagement.findActionsByTarget(targetCancelled.getControllerId(), PAGE)
.getContent().get(0);
deploymentManagement.cancelAction(actionCancelled.getId());
deploymentManagement.forceQuitAction(actionCancelled.getId());
targetCancelled = reloadTarget(targetCancelled);
Target targetCancelling = targets.get(1);
final Action actionCancelling = deploymentManagement
.findActionsByTarget(targetCancelling.getControllerId(), PAGE).getContent().get(0);
deploymentManagement.cancelAction(actionCancelling.getId());
targetCancelling = reloadTarget(targetCancelling);
final List<TargetWithActionStatus> targetsWithActionStatus = rolloutGroupManagement
.findAllTargetsOfRolloutGroupWithActionStatus(PageRequest.of(0, 500, Sort.by(Direction.DESC, "status")),
rolloutGroup.getId())
.getContent();
assertThat(targetsWithActionStatus.get(0).getTarget()).isEqualTo(targetCancelling);
assertThat(targetsWithActionStatus.get(1).getTarget()).isEqualTo(targetCancelled);
final List<TargetWithActionStatus> targetsWithActionStatusOrderedByNameDesc = rolloutGroupManagement
.findAllTargetsOfRolloutGroupWithActionStatus(PageRequest.of(0, 500, Sort.by(Direction.DESC, "name")),
rolloutGroup.getId())
.getContent();
assertThatListIsSortedByTargetName(targetsWithActionStatusOrderedByNameDesc, Direction.DESC);
final List<TargetWithActionStatus> targetsWithActionStatusOrderedByNameAsc = rolloutGroupManagement
.findAllTargetsOfRolloutGroupWithActionStatus(PageRequest.of(0, 500, Sort.by(Direction.ASC, "name")),
rolloutGroup.getId())
.getContent();
assertThatListIsSortedByTargetName(targetsWithActionStatusOrderedByNameAsc, Direction.ASC);
}
private void assertThatListIsSortedByTargetName(final List<TargetWithActionStatus> targets,
final Direction sortDirection) {
String previousName = null;
for (final TargetWithActionStatus targetWithActionStatus : targets) {
final String actualName = targetWithActionStatus.getTarget().getName();
if (previousName != null) {
if (Direction.ASC == sortDirection) {
assertThat(actualName).isGreaterThan(previousName);
} else {
assertThat(actualName).isLessThan(previousName);
}
}
previousName = actualName;
}
}
private Target reloadTarget(final Target targetCancelled) {
return targetManagement.get(targetCancelled.getId()).orElseThrow();
}
}

View File

@@ -26,6 +26,7 @@ import java.util.stream.Collectors;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import org.apache.commons.lang3.RandomStringUtils;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.Condition;
import org.awaitility.Awaitility;
@@ -81,6 +82,7 @@ import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.T
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
@@ -1761,6 +1763,47 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
.getNumberOfElements()).isEqualTo(2);
}
@Test
@Description("Verifies that returned result considers provided sort parameter.")
void findAllRolloutsConsidersSorting() {
final String randomString = RandomStringUtils.randomAlphanumeric(5);
final DistributionSet testDs = testdataFactory.createDistributionSet(randomString + "-testDs");
testdataFactory.createTargets(10, randomString + "-testTarget-");
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults().build();
final String prefixRolloutRunning = randomString + "1";
final RolloutCreate rolloutRunningCreate = entityFactory.rollout().create()
.name(prefixRolloutRunning + "-testRollout").targetFilterQuery("name==" + randomString + "*")
.set(testDs);
Rollout rolloutRunning = rolloutManagement.create(rolloutRunningCreate, 1, conditions);
// Let the executor handle created Rollout
rolloutManagement.handleRollouts();
// start the rollout, so it has active running actions and a group which
// has been started
rolloutManagement.start(rolloutRunning.getId());
rolloutManagement.handleRollouts();
rolloutRunning = reloadRollout(rolloutRunning);
final String prefixRolloutReady = randomString + "2";
final RolloutCreate rolloutReadyCreate = entityFactory.rollout().create()
.name(prefixRolloutReady + "-testRollout").targetFilterQuery("name==" + randomString + "*")
.set(testDs);
Rollout rolloutReady = rolloutManagement.create(rolloutReadyCreate, 1, conditions);
// Let the executor handle created Rollout
rolloutManagement.handleRollouts();
rolloutReady = reloadRollout(rolloutReady);
final List<Rollout> rolloutsOrderedByStatus = rolloutManagement
.findAll(PageRequest.of(0, 500, Sort.by(Direction.ASC, "status")), false).getContent();
assertThat(rolloutsOrderedByStatus).containsSubsequence(List.of(rolloutReady, rolloutRunning));
final List<Rollout> rolloutsOrderedByName = rolloutManagement
.findAll(PageRequest.of(0, 500, Sort.by(Direction.ASC, "name")), false).getContent();
assertThat(rolloutsOrderedByName).containsSubsequence(List.of(rolloutRunning, rolloutReady));
}
@Test
@Description("Creating a rollout without weight value when multi assignment in enabled.")
void weightNotRequiredInMultiAssignmentMode() {

View File

@@ -46,6 +46,8 @@ import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -545,19 +547,28 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
.containsExactly(new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true),
new AssignedSoftwareModule(unassigned, false));
// with filter on name, version and module type, sorting defined by
// Pagerequest
assertThat(softwareModuleManagement.findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc(
PageRequest.of(0, 500, Sort.by(Direction.DESC, "name")), set.getId(), "%found%", testType.getId())
.getContent()).as(
"Found modules with given name, given module type, the assigned ones first, ordered by name DESC")
.containsExactly(new AssignedSoftwareModule(two, true), new AssignedSoftwareModule(one, true),
new AssignedSoftwareModule(unassigned, false));
// with filter on module type only
assertThat(softwareModuleManagement
.findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc(PAGE, set.getId(), null, testType.getId())
.getContent()).as("Found modules with given module type and the assigned ones first").containsExactly(
new AssignedSoftwareModule(differentName, true), new AssignedSoftwareModule(one, true),
new AssignedSoftwareModule(two, true), new AssignedSoftwareModule(unassigned, false));
new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true),
new AssignedSoftwareModule(differentName, true), new AssignedSoftwareModule(unassigned, false));
// without any filter
assertThat(softwareModuleManagement
.findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc(PAGE, set.getId(), null, null)
.getContent()).as("Found modules with the assigned ones first").containsExactly(
new AssignedSoftwareModule(differentName, true), new AssignedSoftwareModule(one, true),
new AssignedSoftwareModule(two, true), new AssignedSoftwareModule(four, true),
new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true),
new AssignedSoftwareModule(differentName, true), new AssignedSoftwareModule(four, true),
new AssignedSoftwareModule(unassigned, false));
}

View File

@@ -31,7 +31,10 @@ import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
@@ -573,7 +576,7 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest {
final Slice<Target> result = targetManagement.findByFilterOrderByLinkedDistributionSet(PAGE, ds.getId(),
new FilterParams(null, null, null, null, Boolean.FALSE));
final Comparator<TenantAwareBaseEntity> byId = (e1, e2) -> Long.compare(e2.getId(), e1.getId());
final Comparator<TenantAwareBaseEntity> byId = (e1, e2) -> Long.compare(e1.getId(), e2.getId());
assertThat(result.getNumberOfElements()).isEqualTo(9);
final List<Target> expected = new ArrayList<>();
@@ -586,7 +589,45 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest {
assertThat(result.getContent()).usingElementComparator(controllerIdComparator())
.containsExactly(expected.toArray(new Target[0]));
}
@Test
@Description("Tests the correct order of targets based on selected distribution set and sort parameter. The system expects to have an order based on installed, assigned DS.")
void targetSearchWithOrderByDistributionSetAndSortParam() {
final List<Target> notAssigned = testdataFactory.createTargets(3, "not", "first description");
List<Target> targAssigned = testdataFactory.createTargets(3, "assigned", "first description");
List<Target> targInstalled = testdataFactory.createTargets(3, "installed", "first description");
final DistributionSet ds = testdataFactory.createDistributionSet("a");
targAssigned = assignDistributionSet(ds, targAssigned).getAssignedEntity().stream().map(Action::getTarget)
.collect(Collectors.toList());
targInstalled = assignDistributionSet(ds, targInstalled).getAssignedEntity().stream().map(Action::getTarget)
.collect(Collectors.toList());
targInstalled = testdataFactory
.sendUpdateActionStatusToTargets(targInstalled, Status.FINISHED, Collections.singletonList("installed"))
.stream().map(Action::getTarget).collect(Collectors.toList());
final List<Target> targetsOrderedByDistAndName = targetManagement
.findByFilterOrderByLinkedDistributionSet(PageRequest.of(0, 500, Sort.by(Direction.DESC, "name")),
ds.getId(), new FilterParams(null, null, null, null, Boolean.FALSE))
.getContent();
assertThat(targetsOrderedByDistAndName).hasSize(9);
assertThatTargetNameEquals(targetsOrderedByDistAndName, 0, targInstalled, 2);
assertThatTargetNameEquals(targetsOrderedByDistAndName, 1, targInstalled, 1);
assertThatTargetNameEquals(targetsOrderedByDistAndName, 2, targInstalled, 0);
assertThatTargetNameEquals(targetsOrderedByDistAndName, 3, targAssigned, 2);
assertThatTargetNameEquals(targetsOrderedByDistAndName, 4, targAssigned, 1);
assertThatTargetNameEquals(targetsOrderedByDistAndName, 5, targAssigned, 0);
assertThatTargetNameEquals(targetsOrderedByDistAndName, 6, notAssigned, 2);
assertThatTargetNameEquals(targetsOrderedByDistAndName, 7, notAssigned, 1);
assertThatTargetNameEquals(targetsOrderedByDistAndName, 8, notAssigned, 0);
}
private void assertThatTargetNameEquals(final List<Target> targets1, final int index1, final List<Target> targets2,
final int index2) {
assertThat(targets1.get(index1).getName()).isEqualTo(targets2.get(index2).getName());
}
@Test
@@ -626,7 +667,7 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest {
final Slice<Target> result = targetManagement.findByFilterOrderByLinkedDistributionSet(PAGE, ds.getId(),
new FilterParams(null, Boolean.TRUE, null, null, Boolean.FALSE));
final Comparator<TenantAwareBaseEntity> byId = (e1, e2) -> Long.compare(e2.getId(), e1.getId());
final Comparator<TenantAwareBaseEntity> byId = (e1, e2) -> Long.compare(e1.getId(), e2.getId());
assertThat(result.getNumberOfElements()).isEqualTo(9);
final List<Target> expected = new ArrayList<>();

View File

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

View File

@@ -8,7 +8,9 @@
*/
package org.eclipse.hawkbit.ui.common.data.providers;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
@@ -18,9 +20,12 @@ import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.util.CollectionUtils;
import com.vaadin.data.provider.AbstractBackEndDataProvider;
import com.vaadin.data.provider.Query;
import com.vaadin.data.provider.QuerySortOrder;
/**
* Base class for loading a batch of entities from backend mapping them to UI
@@ -54,7 +59,38 @@ public abstract class AbstractGenericDataProvider<T extends ProxyIdentifiableEnt
@Override
protected Stream<T> fetchFromBackEnd(final Query<T, F> query) {
return getProxyEntities(
loadBackendEntities(convertToPageRequest(query, defaultSortOrder), query.getFilter().orElse(null)));
loadBackendEntities(
convertToPageRequest(query, convertToSortCriteria(query.getSortOrders())),
query.getFilter().orElse(null)));
}
private Sort convertToSortCriteria(final List<QuerySortOrder> querySortOrders) {
if (CollectionUtils.isEmpty(querySortOrders)) {
return defaultSortOrder;
} else {
return Sort.by(convertToListOfOrders(querySortOrders));
}
}
private List<Order> convertToListOfOrders(final List<QuerySortOrder> querySortOrders) {
return querySortOrders.stream()
.map(querySortOrder -> convertToOrderCriteria(querySortOrder))
.collect(Collectors.toList());
}
private Order convertToOrderCriteria(final QuerySortOrder querySortOrder) {
final Sort.Direction sortDirection;
switch (querySortOrder.getDirection()) {
case ASCENDING:
sortDirection = Sort.Direction.ASC;
break;
case DESCENDING:
// fall through intended to get default behavior
default:
sortDirection = Sort.Direction.DESC;
break;
}
return new Sort.Order(sortDirection, querySortOrder.getSorted());
}
private PageRequest convertToPageRequest(final Query<T, F> query, final Sort sort) {
@@ -67,7 +103,7 @@ public abstract class AbstractGenericDataProvider<T extends ProxyIdentifiableEnt
@Override
protected int sizeInBackEnd(final Query<T, F> query) {
final long size = sizeInBackEnd(convertToPageRequest(query, defaultSortOrder), query.getFilter().orElse(null));
final long size = sizeInBackEnd(convertToPageRequest(query, convertToSortCriteria(query.getSortOrders())), query.getFilter().orElse(null));
try {
return Math.toIntExact(size);
@@ -85,4 +121,4 @@ public abstract class AbstractGenericDataProvider<T extends ProxyIdentifiableEnt
Objects.requireNonNull(item, "Cannot provide an id for a null item.");
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.vaadin.spring.events.EventBus.UIEventBus;
import com.vaadin.data.ValueProvider;
import com.vaadin.data.provider.DataCommunicator;
import com.vaadin.data.provider.DataProviderListener;
import com.vaadin.data.provider.Query;
import com.vaadin.ui.Component;
import com.vaadin.ui.Grid;
import com.vaadin.ui.Grid.Column.NestedNullBehavior;
import com.vaadin.ui.components.grid.GridSelectionModel;
import com.vaadin.ui.renderers.AbstractRenderer;
/**
* Abstract grid that offers various capabilities (aka support) to offer
@@ -123,7 +127,6 @@ public abstract class AbstractGrid<T extends ProxyIdentifiableEntity, F> extends
setId(getGridId());
setColumnReorderingAllowed(false);
addColumns();
disableColumnSorting();
setFrozenColumnCount(-1);
if (selectionSupport == null) {
@@ -176,10 +179,48 @@ public abstract class AbstractGrid<T extends ProxyIdentifiableEntity, F> extends
*/
public abstract void addColumns();
private void disableColumnSorting() {
for (final Column<T, ?> c : getColumns()) {
c.setSortable(false);
}
@Override
public Column<T, ?> addColumn(final String propertyName) {
return super.addColumn(propertyName).setSortable(false);
}
@Override
public Column<T, ?> addColumn(final String propertyName, final AbstractRenderer<? super T, ?> renderer) {
return super.addColumn(propertyName, renderer).setSortable(false);
}
@Override
public Column<T, ?> addColumn(final String propertyName, final AbstractRenderer<? super T, ?> renderer,
final NestedNullBehavior nestedNullBehavior) {
return super.addColumn(propertyName, renderer, nestedNullBehavior).setSortable(false);
}
@Override
public <V> Column<T, V> addColumn(final ValueProvider<T, V> valueProvider) {
return super.addColumn(valueProvider).setSortable(false);
}
@Override
public <V> Column<T, V> addColumn(final ValueProvider<T, V> valueProvider,
final AbstractRenderer<? super T, ? super V> renderer) {
return super.addColumn(valueProvider, renderer).setSortable(false);
}
@Override
public <V, P> Column<T, V> addColumn(final ValueProvider<T, V> valueProvider,
final ValueProvider<V, P> presentationProvider, final AbstractRenderer<? super T, ? super P> renderer) {
return super.addColumn(valueProvider, presentationProvider, renderer).setSortable(false);
}
@Override
public <V> Column<T, V> addColumn(final ValueProvider<T, V> valueProvider,
final ValueProvider<V, String> presentationProvider) {
return super.addColumn(valueProvider, presentationProvider).setSortable(false);
}
@Override
public <V extends Component> Column<T, V> addComponentColumn(final ValueProvider<T, V> componentProvider) {
return super.addComponentColumn(componentProvider).setSortable(false);
}
/**

View File

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

View File

@@ -19,6 +19,8 @@ import org.eclipse.hawkbit.ui.common.grid.support.FilterSupport;
import org.eclipse.hawkbit.ui.filtermanagement.state.TargetFilterDetailsLayoutUiState;
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
import com.vaadin.ui.Label;
/**
* Shows the targets as a result of the executed filter query.
*/
@@ -88,8 +90,10 @@ public class TargetFilterTargetGrid extends AbstractGrid<ProxyTarget, String> {
GridComponentBuilder.addDescriptionColumn(this, i18n, TARGET_DESCRIPTION_ID);
GridComponentBuilder.addIconColumn(this, targetStatusIconSupplier::getLabel, TARGET_STATUS_ID,
final Column<ProxyTarget, Label> statusColumn = GridComponentBuilder.addIconColumn(this,
targetStatusIconSupplier::getLabel, TARGET_STATUS_ID,
i18n.getMessage("header.status"));
GridComponentBuilder.setColumnSortable(statusColumn, "updateStatus");
GridComponentBuilder.addCreatedAndModifiedColumns(this, i18n);

View File

@@ -61,6 +61,7 @@ import com.vaadin.data.ValueProvider;
import com.vaadin.icons.VaadinIcons;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.UI;
import com.vaadin.ui.Window;
import com.vaadin.ui.renderers.HtmlRenderer;
@@ -75,6 +76,7 @@ public class RolloutGrid extends AbstractGrid<ProxyRollout, String> {
private static final String ROLLOUT_LINK_ID = "rollout";
private static final String DIST_NAME_VERSION_ID = "distNameVersion";
private static final String STATUS_ID = "status";
private static final String STATUS_PROPERTY_NAME = "status";
private static final String TOTAL_TARGETS_COUNT_STATUS_ID = "totalTargetsCountStatus";
private static final String NUMBER_OF_GROUPS_ID = "numberOfGroups";
private static final String TOTAL_TARGETS_ID = "totalTargets";
@@ -263,8 +265,10 @@ public class RolloutGrid extends AbstractGrid<ProxyRollout, String> {
@Override
public void addColumns() {
GridComponentBuilder.addComponentColumn(this, this::buildRolloutLink).setId(ROLLOUT_LINK_ID)
final Column<ProxyRollout, Button> nameColumn = GridComponentBuilder
.addComponentColumn(this, this::buildRolloutLink).setId(ROLLOUT_LINK_ID)
.setCaption(i18n.getMessage("header.name")).setHidable(false).setExpandRatio(3);
GridComponentBuilder.setColumnSortable(nameColumn, "name");
GridComponentBuilder.addDescriptionColumn(this, i18n, DESC_ID).setHidable(true).setHidden(true);
@@ -272,9 +276,10 @@ public class RolloutGrid extends AbstractGrid<ProxyRollout, String> {
.setId(DIST_NAME_VERSION_ID).setCaption(i18n.getMessage("header.distributionset"))
.setDescriptionGenerator(this::createDSTooltipText).setHidable(true).setExpandRatio(2);
GridComponentBuilder
final Column<ProxyRollout, Label> statusColumn = GridComponentBuilder
.addIconColumn(this, rolloutStatusIconSupplier::getLabel, STATUS_ID, i18n.getMessage("header.status"))
.setHidable(true);
GridComponentBuilder.setColumnSortable(statusColumn, STATUS_PROPERTY_NAME);
GridComponentBuilder
.addIconColumn(this, actionTypeIconSupplier::getLabel, ACTION_TYPE_ID, i18n.getMessage("header.type"))

View File

@@ -40,6 +40,7 @@ import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
import com.google.common.base.Predicates;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.renderers.HtmlRenderer;
/**
@@ -112,14 +113,17 @@ public class RolloutGroupGrid extends AbstractGrid<ProxyRolloutGroup, Long> {
@Override
public void addColumns() {
GridComponentBuilder.addComponentColumn(this, this::buildRolloutGroupLink).setId(ROLLOUT_GROUP_LINK_ID)
final Column<ProxyRolloutGroup, Button> nameColumn = GridComponentBuilder.addComponentColumn(this, this::buildRolloutGroupLink).setId(ROLLOUT_GROUP_LINK_ID)
.setCaption(i18n.getMessage("header.name")).setHidable(false).setExpandRatio(3);
GridComponentBuilder.setColumnSortable(nameColumn, "name");
GridComponentBuilder.addDescriptionColumn(this, i18n, SPUILabelDefinitions.VAR_DESC).setHidable(true)
.setHidden(true);
GridComponentBuilder.addIconColumn(this, rolloutGroupStatusIconSupplier::getLabel,
final Column<ProxyRolloutGroup, Label> statusColumn = GridComponentBuilder
.addIconColumn(this, rolloutGroupStatusIconSupplier::getLabel,
SPUILabelDefinitions.VAR_STATUS, i18n.getMessage("header.status")).setHidable(true);
GridComponentBuilder.setColumnSortable(statusColumn, "status");
addColumn(rolloutGroup -> DistributionBarHelper
.getDistributionBarAsHTMLString(rolloutGroup.getTotalTargetCountStatus().getStatusTotalCountMap()),
@@ -155,7 +159,7 @@ public class RolloutGroupGrid extends AbstractGrid<ProxyRolloutGroup, Long> {
final boolean enableButton = RolloutGroupStatus.CREATING != rolloutGroup.getStatus()
&& permissionChecker.hasRolloutTargetsReadPermission();
return GridComponentBuilder.buildLink(rolloutGroup, "rolloutgroup.link.", rolloutGroup.getName(), enableButton,
return GridComponentBuilder.buildLink(rolloutGroup, "rolloutgroup.link", rolloutGroup.getName(), enableButton,
clickEvent -> onClickOfRolloutGroupName(rolloutGroup));
}

View File

@@ -24,6 +24,8 @@ import org.eclipse.hawkbit.ui.rollout.RolloutManagementUIState;
import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions;
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
import com.vaadin.ui.Label;
/**
* Grid component with targets of rollout group.
*/
@@ -73,8 +75,10 @@ public class RolloutGroupTargetGrid extends AbstractGrid<ProxyTarget, Long> {
GridComponentBuilder.addDescriptionColumn(this, i18n, SPUILabelDefinitions.VAR_DESC).setExpandRatio(2);
GridComponentBuilder.addIconColumn(this, actionStatusIconSupplier::getLabel, SPUILabelDefinitions.VAR_STATUS,
final Column<ProxyTarget, Label> statusColumn = GridComponentBuilder.addIconColumn(this,
actionStatusIconSupplier::getLabel, SPUILabelDefinitions.VAR_STATUS,
i18n.getMessage("header.status"));
GridComponentBuilder.setColumnSortable(statusColumn, "status");
GridComponentBuilder.addCreatedAndModifiedColumns(this, i18n);

View File

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