From 70779d1ac769cc14a3528de1781d739dafdf1112 Mon Sep 17 00:00:00 2001 From: Anand Kumar Date: Wed, 27 Oct 2021 15:24:09 +0200 Subject: [PATCH] Feature target type filter (#1197) * Added Target type filter with drag and drop support Signed-off-by: Anand kumar * Removed the unused enums and target type filter button class Signed-off-by: Anand kumar * Resolved merge conflicts Signed-off-by: Anand kumar * Fixed java doc issue with the method link in the comment Signed-off-by: Anand kumar * Fixed the IN query overflow for target Type assignment Signed-off-by: Anand kumar * Fixed Review comments Signed-off-by: Anand kumar --- .../hawkbit/repository/TargetFields.java | 2 +- .../hawkbit/repository/FilterParams.java | 51 +- .../hawkbit/repository/TargetManagement.java | 71 ++- .../model/TargetTypeAssignmentResult.java | 42 ++ .../org/eclipse/hawkbit/event/EventType.java | 8 + .../repository/jpa/JpaTargetManagement.java | 87 +++- .../jpa/JpaTargetTypeManagement.java | 13 +- .../specifications/TargetSpecifications.java | 40 +- .../jpa/AbstractJpaIntegrationTest.java | 8 + .../jpa/TargetManagementSearchTest.java | 481 ++++++++++-------- .../repository/jpa/TargetManagementTest.java | 61 ++- .../test/util/AbstractIntegrationTest.java | 2 +- .../common/builder/FormComponentBuilder.java | 23 +- .../filters/TargetManagementFilterParams.java | 78 ++- .../TargetManagementStateDataProvider.java | 20 +- .../providers/TargetTypeDataProvider.java | 8 + .../hawkbit/ui/common/event/FilterType.java | 2 +- .../AbstractTargetTypeFilterButtons.java | 172 +++++-- .../TargetTypeFilterButtonClick.java | 41 +- ...tTargetsToTargetTypeAssignmentSupport.java | 52 ++ ...argetsToNoTargetTypeAssignmentSupport.java | 83 +++ .../TargetsToTargetTypeAssignmentSupport.java | 98 ++++ .../TypeToTargetAssignmentSupport.java | 85 ++++ .../common/state/TagFilterLayoutUiState.java | 18 + .../targettable/TargetCountMessageLabel.java | 7 + .../ui/management/targettable/TargetGrid.java | 23 +- .../targettable/TargetGridHeader.java | 15 +- .../targettable/TargetGridLayout.java | 14 +- .../targettable/TargetWindowLayout.java | 2 + .../filter/MultipleTargetFilter.java | 46 +- .../filter/TargetTagFilterLayoutUiState.java | 8 + .../filter/TargetTypeFilterButtons.java | 110 +++- .../UpdateTargetTypeWindowController.java | 9 +- .../push/HawkbitEventPermissionChecker.java | 6 +- .../hawkbit/ui/push/HawkbitEventProvider.java | 11 + .../hawkbit/ui/utils/UIMessageIdProvider.java | 2 + .../hawkbit/customstyles/drop-hint.scss | 6 +- .../src/main/resources/messages.properties | 2 + 38 files changed, 1409 insertions(+), 398 deletions(-) create mode 100644 hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetTypeAssignmentResult.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/AbstractTargetsToTargetTypeAssignmentSupport.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToNoTargetTypeAssignmentSupport.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToTargetTypeAssignmentSupport.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TypeToTargetAssignmentSupport.java diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java index ce6b3f391..deca8cd15 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java @@ -140,4 +140,4 @@ public enum TargetFields implements FieldNameProvider { public String getFieldName() { return fieldName; } -} \ No newline at end of file +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/FilterParams.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/FilterParams.java index 8780cd26f..1bcffebc1 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/FilterParams.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/FilterParams.java @@ -27,9 +27,11 @@ public class FilterParams { private final Boolean selectTargetWithNoTag; private final String[] filterByTagNames; private final Long filterByDistributionId; + private final Boolean selectTargetWithNoTargetType; + private final Long filterByTargetType; /** - * Constructor. + * Constructor for the filter parameters of a Simple Filter. * * @param filterByInstalledOrAssignedDistributionSetId * if set, a filter is added for the given @@ -56,6 +58,34 @@ public class FilterParams { this.filterByDistributionId = filterByInstalledOrAssignedDistributionSetId; this.selectTargetWithNoTag = selectTargetWithNoTag; this.filterByTagNames = filterByTagNames; + this.selectTargetWithNoTargetType = false; + this.filterByTargetType = null; + + } + + /** + * Constructor for the filter parameters of a Type Filter. + * + * @param filterBySearchText + * if set, a filter is added for the given search text + * @param filterByInstalledOrAssignedDistributionSetId + * if set, a filter is added for the given + * {@link DistributionSet#getId()} + * @param selectTargetWithNoType + * if true, a filter is added with no type + * @param filterByType + * if set, a filter is added for the given target type + */ + public FilterParams(final String filterBySearchText, final Long filterByInstalledOrAssignedDistributionSetId, + final Boolean selectTargetWithNoType, final Long filterByType) { + this.filterBySearchText = filterBySearchText; + this.filterByDistributionId = filterByInstalledOrAssignedDistributionSetId; + this.filterByStatus = null; + this.overdueState = null; + this.selectTargetWithNoTag = false; + this.filterByTagNames = null; + this.selectTargetWithNoTargetType = selectTargetWithNoType; + this.filterByTargetType = filterByType; } /** @@ -120,4 +150,23 @@ public class FilterParams { public String[] getFilterByTagNames() { return filterByTagNames; } + + /** + * Gets the flag indicating if no target type filter is used.
+ * If set to false this filter is disabled. + * + * @return the flag indicating if no target type filter is used + */ + public Boolean getSelectTargetWithNoTargetType() { + return selectTargetWithNoTargetType; + } + + /** + * Gets the target type + * + * @return the target type that are used to filter for + */ + public Long getFilterByTargetType() { + return filterByTargetType; + } } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index 61b942af5..d7bf8348e 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -8,14 +8,6 @@ */ package org.eclipse.hawkbit.repository; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import javax.validation.ConstraintViolationException; -import javax.validation.Valid; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.repository.builder.TargetCreate; import org.eclipse.hawkbit.repository.builder.TargetUpdate; @@ -35,12 +27,22 @@ import org.eclipse.hawkbit.repository.model.TargetMetadata; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult; import org.eclipse.hawkbit.repository.model.TargetType; +import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.security.access.prepost.PreAuthorize; +import javax.validation.ConstraintViolationException; +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; + /** * Management service for {@link Target}s. * @@ -106,11 +108,29 @@ public interface TargetManagement { * * @throws EntityNotFoundException * if distribution set with given ID does not exist + * + * @deprecated this method {@link TargetManagement#countByFilters(FilterParams)} should be used instead. */ + @Deprecated @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) long countByFilters(Collection status, Boolean overdueState, String searchText, Long installedOrAssignedDistributionSetId, Boolean selectTargetWithNoTag, String... tagNames); + /** + * Count {@link Target}s for all the given filter parameters. + * + * @param filterParams + * the filters to apply; only filters are enabled that have + * non-null value; filters are AND-gated + * + * @return the found number {@link Target}s + * + * @throws EntityNotFoundException + * if distribution set with given ID does not exist + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + long countByFilters(@NotNull final FilterParams filterParams); + /** * Counts number of targets with given with given distribution set Id * @@ -607,6 +627,38 @@ public interface TargetManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) TargetTagAssignmentResult toggleTagAssignment(@NotEmpty Collection controllerIds, @NotEmpty String tagName); + + /** + * Initiates {@link TargetType} assignment to given {@link Target}s. If some + * targets in the list have the {@link TargetType} not yet assigned, they will + * get assigned. If all targets are already of that type, there will be no + * un-assignment. + * + * @param controllerIds + * to set the type to + * @param typeId + * to assign targets to + * @return {@link TargetTypeAssignmentResult} with all meta data of the + * assignment outcome. + * + * @throws EntityNotFoundException + * if target type with given id does not exist + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) + TargetTypeAssignmentResult assignType(@NotEmpty Collection controllerIds, @NotNull Long typeId); + + /** + * Initiates {@link TargetType} un-assignment to given {@link Target}s. The type + * of the targets will be set to {@code null} + * + * @param controllerIds + * to remove the type from + * @return {@link TargetTypeAssignmentResult} with all meta data of the + * assignment outcome. + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) + TargetTypeAssignmentResult unAssignType(@NotEmpty Collection controllerIds); + /** * Un-assign a {@link TargetTag} assignment to given {@link Target}. * @@ -638,6 +690,8 @@ public interface TargetManagement { * * @param controllerID * to un-assign for + * @param targetTypeId + * Target type id * @return the unassigned target * * @throws EntityNotFoundException @@ -854,4 +908,5 @@ public interface TargetManagement { */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) TargetMetadata updateMetadata(@NotEmpty String controllerId, @NotNull MetaData metadata); + } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetTypeAssignmentResult.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetTypeAssignmentResult.java new file mode 100644 index 000000000..379f059d3 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetTypeAssignmentResult.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2021 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +import java.util.List; + +/** + * Result object for {@link TargetType} assignments. + * + */ +public class TargetTypeAssignmentResult extends AbstractAssignmentResult { + + private final TargetType targetType; + + /** + * Constructor. + * + * @param alreadyAssigned + * count of already assigned (ignored) elements + * @param assigned + * {@link List} of assigned {@link Target}s. + * @param unassigned + * {@link List} of unassigned {@link Target}s. + * @param targetType + * the assigned or unassigned tag + */ + public TargetTypeAssignmentResult(final int alreadyAssigned, final List assigned, + final List unassigned, final TargetType targetType) { + super(alreadyAssigned, assigned, unassigned); + this.targetType = targetType; + } + + public TargetType getTargetType() { + return targetType; + } +} diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventType.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventType.java index fe4b626b7..092d3b129 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventType.java +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventType.java @@ -30,6 +30,7 @@ import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.TargetFilterQueryDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent; import org.eclipse.hawkbit.repository.event.remote.TargetTagDeletedEvent; +import org.eclipse.hawkbit.repository.event.remote.TargetTypeDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.TenantConfigurationDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent; @@ -53,6 +54,8 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetFilterQueryCreat import org.eclipse.hawkbit.repository.event.remote.entity.TargetFilterQueryUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetTagCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetTagUpdatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationUpdatedEvent; @@ -150,6 +153,11 @@ public class EventType { // rollout stopped due to invalidated distribution set TYPES.put(43, RolloutStoppedEvent.class); + + // target type + TYPES.put(44, TargetTypeCreatedEvent.class); + TYPES.put(45, TargetTypeUpdatedEvent.class); + TYPES.put(46, TargetTypeDeletedEvent.class); } private int value; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java index b201da777..9efdb6fab 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java @@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.jpa; import static org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications.orderedByLinkedDistributionSet; +import com.google.common.collect.Lists; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; @@ -65,6 +66,7 @@ import org.eclipse.hawkbit.repository.model.TargetMetadata; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult; import org.eclipse.hawkbit.repository.model.TargetType; +import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; @@ -449,11 +451,16 @@ public class JpaTargetManagement implements TargetManagement { } @Override - public long countByFilters(final Collection status, final Boolean overdueState, - final String searchText, final Long installedOrAssignedDistributionSetId, - final Boolean selectTargetWithNoTag, final String... tagNames) { - final List> specList = buildSpecificationList(new FilterParams(status, overdueState, - searchText, installedOrAssignedDistributionSetId, selectTargetWithNoTag, tagNames)); + public long countByFilters(Collection status, Boolean overdueState, String searchText, + Long installedOrAssignedDistributionSetId, Boolean selectTargetWithNoTag, + String... tagNames) { + return countByFilters(new FilterParams(status, overdueState, searchText, installedOrAssignedDistributionSetId, + selectTargetWithNoTag, tagNames)); + } + + @Override + public long countByFilters(final FilterParams filterParams) { + final List> specList = buildSpecificationList(filterParams); return countByCriteriaAPI(specList); } @@ -479,6 +486,13 @@ public class JpaTargetManagement implements TargetManagement { specList.add(TargetSpecifications.hasTags(filterParams.getFilterByTagNames(), filterParams.getSelectTargetWithNoTag())); } + + if (hasTypesFilterActive(filterParams)) { + specList.add(TargetSpecifications.hasTargetType(filterParams.getFilterByTargetType())); + } else if (hasNoTypeFilterActive(filterParams)) { + specList.add(TargetSpecifications.hasNoTargetType()); + } + return specList; } @@ -490,6 +504,14 @@ public class JpaTargetManagement implements TargetManagement { return isNoTagActive || isAtLeastOneTagActive; } + private static boolean hasTypesFilterActive(final FilterParams filterParams) { + return filterParams.getFilterByTargetType() != null; + } + + private static boolean hasNoTypeFilterActive(final FilterParams filterParams) { + return Boolean.TRUE.equals(filterParams.getSelectTargetWithNoTargetType()); + } + private Slice findByCriteriaAPI(final Pageable pageable, final List> specList) { if (CollectionUtils.isEmpty(specList)) { return convertPage(targetRepository.findAllWithoutCount(pageable), pageable); @@ -545,6 +567,59 @@ public class JpaTargetManagement implements TargetManagement { return result; } + @Override + @Transactional + @Retryable(include = { + ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + public TargetTypeAssignmentResult assignType(final Collection controllerIds, final Long typeId) { + final TargetType type = targetTypeRepository.findById(typeId) + .orElseThrow(() -> new EntityNotFoundException(TargetType.class, typeId)); + + final List targetsWithSameType = findTargetsByInSpecification(controllerIds, + TargetSpecifications.hasTargetType(typeId)); + + final List targetsWithoutSameType = findTargetsByInSpecification(controllerIds, + TargetSpecifications.hasTargetTypeNot(typeId)); + + // set new target type to all targets without that type + targetsWithoutSameType.forEach(target -> target.setTargetType(type)); + + final TargetTypeAssignmentResult result = new TargetTypeAssignmentResult(targetsWithSameType.size(), + Collections + .unmodifiableList(targetsWithoutSameType.stream().map(targetRepository::save).collect(Collectors.toList())), + Collections.emptyList(), type); + + // no reason to persist the type + entityManager.detach(type); + return result; + } + + @Override + @Transactional + @Retryable(include = { + ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + public TargetTypeAssignmentResult unAssignType(final Collection controllerIds) { + final List allTargets = findTargetsByInSpecification(controllerIds, null); + + if (allTargets.size() < controllerIds.size()) { + throw new EntityNotFoundException(Target.class, controllerIds, + allTargets.stream().map(Target::getControllerId).collect(Collectors.toList())); + } + + // set new target type to null for all targets + allTargets.forEach(target -> target.setTargetType(null)); + + return new TargetTypeAssignmentResult(0, Collections.emptyList(), Collections + .unmodifiableList(allTargets.stream().map(targetRepository::save).collect(Collectors.toList())), null); + } + + private List findTargetsByInSpecification(Collection controllerIds, + Specification specification) { + return Lists.partition(new ArrayList<>(controllerIds), Constants.MAX_ENTRIES_IN_STATEMENT).stream() + .map(ids -> targetRepository.findAll(TargetSpecifications.hasControllerIdIn(ids).and(specification))) + .flatMap(List::stream).collect(Collectors.toList()); + } + @Override @Transactional @Retryable(include = { @@ -625,7 +700,7 @@ public class JpaTargetManagement implements TargetManagement { /** * Applies orderedByLinkedDistributionSet spec for order and adds additional * specs based on filters - * + * * @param orderByDistributionId * distribution set used for order * @param filterParams diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetTypeManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetTypeManagement.java index 028c9ea83..5f6083a3a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetTypeManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetTypeManagement.java @@ -8,12 +8,6 @@ */ package org.eclipse.hawkbit.repository.jpa; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.TargetTypeFields; import org.eclipse.hawkbit.repository.TargetTypeManagement; @@ -42,9 +36,14 @@ import org.springframework.orm.jpa.vendor.Database; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + /** * JPA implementation of {@link TargetTypeManagement}. * diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java index 3a30eba3b..de8c79e7b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java @@ -392,12 +392,16 @@ public final class TargetSpecifications { return (targetRoot, query, cb) -> { // Since the targetRoot is changed by joining we need to get the // isNull predicate first - final Predicate targetTypeIsNull = targetRoot.get(JpaTarget_.targetType).isNull(); + final Predicate targetTypeIsNull = getTargetTypeIsNullPredicate(targetRoot); return cb.or(targetTypeIsNull, cb.equal(getDsTypeIdPath(targetRoot), distributionSetTypeId)); }; } + private static Predicate getTargetTypeIsNullPredicate(Root targetRoot) { + return targetRoot.get(JpaTarget_.targetType).isNull(); + } + /** * {@link Specification} for retrieving {@link Target}s that are NOT compatible * with given {@link DistributionSetType}. Compatibility is evaluated by @@ -532,13 +536,39 @@ public final class TargetSpecifications { } /** - * {@link Specification} for retrieving {@link Target}s that have a - * {@link org.eclipse.hawkbit.repository.model.TargetType} assigned + * {@link Specification} for retrieving {@link Target}s by target type id + * + * @param typeId + * the id of the target type * * @return the {@link Target} {@link Specification} */ - public static Specification hasTargetType() { - return (targetRoot, query, cb) -> cb.isNotNull(targetRoot.get(JpaTarget_.targetType)); + public static Specification hasTargetType(final long typeId) { + return (targetRoot, query, cb) -> cb.equal(targetRoot.get(JpaTarget_.targetType).get(JpaTargetType_.id), + typeId); + } + + /** + * {@link Specification} for retrieving {@link Target}s by target type id is equal to null + * + * @return the {@link Target} {@link Specification} + */ + public static Specification hasNoTargetType() { + return (targetRoot, query, cb) -> cb.isNull(targetRoot.get(JpaTarget_.targetType)); + } + + /** + * {@link Specification} for retrieving {@link Target}s that don't have target + * type assigned + * + * @param typeId + * the id of the target type + * + * @return the {@link Target} {@link Specification} + */ + public static Specification hasTargetTypeNot(final Long typeId) { + return (targetRoot, query, cb) -> cb.or(getTargetTypeIsNullPredicate(targetRoot), + cb.notEqual(targetRoot.get(JpaTarget_.targetType).get(JpaTargetType_.id), typeId)); } /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java index d3c45fa1f..c7957f8e9 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java @@ -26,6 +26,8 @@ import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult; +import org.eclipse.hawkbit.repository.model.TargetType; +import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult; import org.eclipse.hawkbit.repository.test.TestConfiguration; import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest; import org.eclipse.hawkbit.repository.test.util.RolloutTestApprovalStrategy; @@ -133,4 +135,10 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest return distributionSetManagement.toggleTagAssignment( sets.stream().map(DistributionSet::getId).collect(Collectors.toList()), tag.getName()); } + + protected TargetTypeAssignmentResult initiateTypeAssignment(final Collection targets, final TargetType type) { + return targetManagement.assignType( + targets.stream().map(Target::getControllerId).collect(Collectors.toList()), type.getId()); + } + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java index 667ada5b9..fefe695ae 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java @@ -58,7 +58,11 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest { final TargetTag targTagZ = targetTagManagement.create(entityFactory.tag().create().name("TargTag-Z")); final TargetTag targTagW = targetTagManagement.create(entityFactory.tag().create().name("TargTag-W")); - final DistributionSet setA = testdataFactory.createDistributionSet(""); + final DistributionSet setA = testdataFactory.createDistributionSet("A"); + final DistributionSet setB = testdataFactory.createDistributionSet("B"); + + final TargetType targetTypeX = testdataFactory.createTargetType("TargetTypeX", + Collections.singletonList(setB.getType())); final DistributionSet installedSet = testdataFactory.createDistributionSet("another"); @@ -92,12 +96,17 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest { final List targDs = testdataFactory.createTargets(100, targetDsDIdPref, targetDsDIdPref.concat(" description"), lastTargetNull); + final String targetDsEIdPref = "targ-E"; + final List targEs = testdataFactory.createTargetsWithType(100, targetDsEIdPref, targetTypeX); + final String assignedC = targCs.iterator().next().getControllerId(); assignDistributionSet(setA.getId(), assignedC); final String assignedA = targAs.iterator().next().getControllerId(); assignDistributionSet(setA.getId(), assignedA); final String assignedB = targBs.iterator().next().getControllerId(); assignDistributionSet(setA.getId(), assignedB); + final String assignedE = targEs.iterator().next().getControllerId(); + assignDistributionSet(setB.getId(), assignedE); final String installedC = targCs.iterator().next().getControllerId(); final Long actionId = getFirstAssignedActionId(assignDistributionSet(installedSet.getId(), assignedC)); @@ -136,7 +145,7 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest { verifyThat1TargetHasAttributeValue("%c-attribute%", targAttribute.getControllerId()); verifyThat1TargetHasAttributeValue("%" + targAttributeId.getControllerId() + "%", targAttributeId.getControllerId()); - verifyThatRepositoryContains400Targets(); + verifyThatRepositoryContains500Targets(); verifyThat200TargetsHaveTagD(targTagW, concat(targBs, targCs)); verifyThat100TargetsContainsGivenTextAndHaveTagAssigned(targTagY, targTagW, targBs); verifyThat1TargetHasTagHasDescOrNameAndDs(targTagW, setA, targetManagement.getByControllerID(assignedC).get()); @@ -147,19 +156,19 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest { verifyThat1TargetWithDescOrNameHasDS(setA, targetManagement.getByControllerID(assignedA).get()); List expected = concat(targAs, targBs, targCs, targDs); expected.removeAll(targetManagement.getByControllerID(Arrays.asList(assignedA, assignedB, assignedC))); - verifyThat397TargetsAreInStatusUnknown(unknown, expected); + verifyThat496TargetsAreInStatusUnknown(unknown, expected); expected = concat(targBs, targCs); expected.removeAll(targetManagement.getByControllerID(Arrays.asList(assignedB, assignedC))); verifyThat198TargetsAreInStatusUnknownAndHaveGivenTags(targTagY, targTagW, unknown, expected); - verfyThat0TargetsAreInStatusUnknownAndHaveDSAssigned(setA, unknown); + verifyThat0TargetsAreInStatusUnknownAndHaveDSAssigned(setA, unknown); expected = concat(targAs); expected.remove(targetManagement.getByControllerID(assignedA).get()); verifyThat99TargetsWithNameOrDescriptionAreInGivenStatus(unknown, expected); expected = concat(targBs); expected.remove(targetManagement.getByControllerID(assignedB).get()); verifyThat99TargetsWithGivenNameOrDescAndTagAreInStatusUnknown(targTagW, unknown, expected); - verifyThat3TargetsAreInStatusPending(pending, - targetManagement.getByControllerID(Arrays.asList(assignedA, assignedB, assignedC))); + verifyThat4TargetsAreInStatusPending(pending, + targetManagement.getByControllerID(Arrays.asList(assignedA, assignedB, assignedC, assignedE))); verifyThat3TargetsWithGivenDSAreInPending(setA, pending, targetManagement.getByControllerID(Arrays.asList(assignedA, assignedB, assignedC))); verifyThat1TargetWithGivenNameOrDescAndDSIsInPending(setA, pending, @@ -171,8 +180,13 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest { verifyThat2TargetsWithGivenTagAreInPending(targTagW, pending, targetManagement.getByControllerID(Arrays.asList(assignedB, assignedC))); verifyThat200targetsWithGivenTagAreInStatusPendingorUnknown(targTagW, both, concat(targBs, targCs)); - verfiyThat1TargetAIsInStatusPendingAndHasDSInstalled(installedSet, pending, + verifyThat1TargetAIsInStatusPendingAndHasDSInstalled(installedSet, pending, targetManagement.getByControllerID(installedC).get()); + verifyThat1TargetHasTypeAndDSAssigned(targetTypeX, setB, targetManagement.getByControllerID(assignedE).get()); + verifyThatTargetsHasNoTypeAndDSAssignedOrInstalled(setA, targetManagement.getByControllerID(Arrays.asList(assignedA, assignedB, assignedC))); + verifyThatTargetsHasNoTypeAndDSAssignedOrInstalled(installedSet, targetManagement.getByControllerID(Collections.singletonList(installedC))); + verifyThat100TargetsContainsGivenTextAndHaveTypeAssigned(targetTypeX, targEs); + verifyThat400TargetsContainsGivenTextAndHaveNoTypeAssigned(concat(targAs, targBs, targCs, targDs)); expected = concat(targBs, targCs); expected.removeAll(targetManagement.getByControllerID(Arrays.asList(assignedB, assignedC))); @@ -180,354 +194,314 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest { } @Step - private void verfiyThat1TargetAIsInStatusPendingAndHasDSInstalled(final DistributionSet installedSet, + private void verifyThat1TargetAIsInStatusPendingAndHasDSInstalled(final DistributionSet installedSet, final List pending, final Target expected) { + final FilterParams filterParams = new FilterParams(pending, null, null, installedSet.getId(), Boolean.FALSE); final String query = "updatestatus==pending and installedds.name==" + installedSet.getName(); - assertThat(targetManagement - .findByFilters(PAGE, new FilterParams(pending, null, null, installedSet.getId(), Boolean.FALSE)) - .getContent()) - .as("has number of elements").hasSize(1).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast(targetManagement.countByFilters(pending, null, null, - installedSet.getId(), Boolean.FALSE))) - .as("and contains the following elements").containsExactly(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); - + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(1).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsExactly(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step private void verifyThat200targetsWithGivenTagAreInStatusPendingorUnknown(final TargetTag targTagW, final List both, final List expected) { + final FilterParams filterParams = new FilterParams(both, null, null, null, Boolean.FALSE, targTagW.getName()); final String query = "(updatestatus==pending or updatestatus==unknown) and tag==" + targTagW.getName(); - assertThat(targetManagement - .findByFilters(PAGE, new FilterParams(both, null, null, null, Boolean.FALSE, targTagW.getName())) - .getContent()).as("has number of elements").hasSize(200) - .as("that number is also returned by count query") - .hasSize(Ints.saturatedCast(targetManagement.countByFilters(both, null, null, null, - Boolean.FALSE, targTagW.getName()))) - .as("and contains the following elements").containsAll(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(200).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsAll(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step private void verifyThat2TargetsWithGivenTagAreInPending(final TargetTag targTagW, final List pending, final List expected) { + final FilterParams filterParams = new FilterParams(pending, null, null, null, Boolean.FALSE, + targTagW.getName()); final String query = "updatestatus==pending and tag==" + targTagW.getName(); - assertThat(targetManagement - .findByFilters(PAGE, new FilterParams(pending, null, null, null, Boolean.FALSE, targTagW.getName())) - .getContent()) - .as("has number of elements").hasSize(2).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast(targetManagement.countByFilters(pending, null, null, null, - Boolean.FALSE, targTagW.getName()))) - .as("and contains the following elements").containsAll(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(2).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsAll(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step private void verifyThat2TargetsWithGivenTagAndDSIsInPending(final TargetTag targTagW, final DistributionSet setA, final List pending, final List expected) { + final FilterParams filterParams = new FilterParams(pending, null, null, setA.getId(), Boolean.FALSE, + targTagW.getName()); final String query = "updatestatus==pending and (assignedds.name==" + setA.getName() + " or installedds.name==" + setA.getName() + ") and tag==" + targTagW.getName(); - assertThat(targetManagement - .findByFilters(PAGE, - new FilterParams(pending, null, null, setA.getId(), Boolean.FALSE, targTagW.getName())) - .getContent()) - .as("has number of elements").hasSize(2).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast(targetManagement.countByFilters(pending, null, null, setA.getId(), - Boolean.FALSE, targTagW.getName()))) - .as("and contains the following elements").containsAll(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(2).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsAll(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step private void verifyThat1TargetWithGivenNameOrDescAndTagAndDSIsInPending(final TargetTag targTagW, final DistributionSet setA, final List pending, final Target expected) { + final FilterParams filterParams = new FilterParams(pending, null, "%targ-B%", setA.getId(), Boolean.FALSE, + targTagW.getName()); final String query = "updatestatus==pending and (assignedds.name==" + setA.getName() + " or installedds.name==" + setA.getName() + ") and (name==*targ-B* or description==*targ-B*) and tag==" + targTagW.getName(); - assertThat(targetManagement - .findByFilters(PAGE, - new FilterParams(pending, null, "%targ-B%", setA.getId(), Boolean.FALSE, targTagW.getName())) - .getContent()) - .as("has number of elements").hasSize(1).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast(targetManagement.countByFilters(pending, null, "%targ-B%", - setA.getId(), Boolean.FALSE, targTagW.getName()))) - .as("and contains the following elements").containsExactly(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(1).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsExactly(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step private void verifyThat1TargetWithGivenNameOrDescAndDSIsInPending(final DistributionSet setA, final List pending, final Target expected) { + final FilterParams filterParams = new FilterParams(pending, null, "%targ-A%", setA.getId(), Boolean.FALSE); final String query = "updatestatus==pending and (assignedds.name==" + setA.getName() + " or installedds.name==" + setA.getName() + ") and (name==*targ-A* or description==*targ-A*)"; - assertThat(targetManagement - .findByFilters(PAGE, new FilterParams(pending, null, "%targ-A%", setA.getId(), Boolean.FALSE)) - .getContent()) - .as("has number of elements").hasSize(1).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast(targetManagement.countByFilters(pending, null, "%targ-A%", - setA.getId(), Boolean.FALSE))) - .as("and contains the following elements").containsExactly(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(1).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsExactly(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step private void verifyThat3TargetsWithGivenDSAreInPending(final DistributionSet setA, final List pending, final List expected) { + final FilterParams filterParams = new FilterParams(pending, null, null, setA.getId(), Boolean.FALSE); final String query = "updatestatus==pending and (assignedds.name==" + setA.getName() + " or installedds.name==" + setA.getName() + ")"; - assertThat(targetManagement - .findByFilters(PAGE, new FilterParams(pending, null, null, setA.getId(), Boolean.FALSE)).getContent()) - .as("has number of elements").hasSize(3).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast( - targetManagement.countByFilters(pending, null, null, setA.getId(), Boolean.FALSE))) - .as("and contains the following elements").containsAll(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(3).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsAll(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step - private void verifyThat3TargetsAreInStatusPending(final List pending, + private void verifyThat4TargetsAreInStatusPending(final List pending, final List expected) { + final FilterParams filterParams = new FilterParams(pending, null, null, null, Boolean.FALSE); final String query = "updatestatus==pending"; - assertThat(targetManagement.findByFilters(PAGE, new FilterParams(pending, null, null, null, Boolean.FALSE)) - .getContent()) - .as("has number of elements").hasSize(3).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast(targetManagement.countByFilters(pending, null, null, null, - Boolean.FALSE, new String[0]))) - .as("and contains the following elements").containsAll(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(4).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsAll(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step private void verifyThat99TargetsWithGivenNameOrDescAndTagAreInStatusUnknown(final TargetTag targTagW, final List unknown, final List expected) { + final FilterParams filterParams = new FilterParams(unknown, null, "%targ-B%", null, Boolean.FALSE, + targTagW.getName()); final String query = "updatestatus==unknown and (name==*targ-B* or description==*targ-B*) and tag==" + targTagW.getName(); - assertThat(targetManagement - .findByFilters(PAGE, - new FilterParams(unknown, null, "%targ-B%", null, Boolean.FALSE, targTagW.getName())) - .getContent()).as("has number of elements").hasSize(99) - .as("that number is also returned by count query") - .hasSize(Ints.saturatedCast(targetManagement.countByFilters(unknown, null, "%targ-B%", null, - Boolean.FALSE, targTagW.getName()))) - .as("and contains the following elements").containsAll(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(99).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsAll(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step private void verifyThat99TargetsWithNameOrDescriptionAreInGivenStatus(final List unknown, final List expected) { + final FilterParams filterParams = new FilterParams(unknown, null, "%targ-A%", null, Boolean.FALSE); final String query = "updatestatus==unknown and (name==*targ-A* or description==*targ-A*)"; - assertThat(targetManagement - .findByFilters(PAGE, new FilterParams(unknown, null, "%targ-A%", null, Boolean.FALSE, new String[0])) - .getContent()).as("has number of elements").hasSize(99) - .as("that number is also returned by count query") - .hasSize(Ints.saturatedCast( - targetManagement.countByFilters(unknown, null, "%targ-A%", null, Boolean.FALSE))) - .as("and contains the following elements").containsAll(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); - + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(99).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsAll(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step - private void verfyThat0TargetsAreInStatusUnknownAndHaveDSAssigned(final DistributionSet setA, + private void verifyThat0TargetsAreInStatusUnknownAndHaveDSAssigned(final DistributionSet setA, final List unknown) { + final FilterParams filterParams = new FilterParams(unknown, null, null, setA.getId(), Boolean.FALSE); final String query = "updatestatus==unknown and (assignedds.name==" + setA.getName() + " or installedds.name==" + setA.getName() + ")"; - assertThat(targetManagement - .findByFilters(PAGE, new FilterParams(unknown, null, null, setA.getId(), Boolean.FALSE)).getContent()) - .as("has number of elements").hasSize(0).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast( - targetManagement.countByFilters(unknown, null, null, setA.getId(), Boolean.FALSE))) - .as("and filter query returns the same result") - .hasSize(targetManagement.findByRsql(PAGE, query).getContent().size()); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(0).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and filter query returns the same result") + .hasSize(targetManagement.findByRsql(PAGE, query).getContent().size()); } @Step private void verifyThat198TargetsAreInStatusUnknownAndHaveGivenTags(final TargetTag targTagY, final TargetTag targTagW, final List unknown, final List expected) { + final FilterParams filterParams = new FilterParams(unknown, null, null, null, Boolean.FALSE, targTagY.getName(), + targTagW.getName()); final String query = "updatestatus==unknown and (tag==" + targTagY.getName() + " or tag==" + targTagW.getName() + ")"; - assertThat(targetManagement.findByFilters(PAGE, - new FilterParams(unknown, null, null, null, Boolean.FALSE, targTagY.getName(), targTagW.getName())) - .getContent()).as("has number of elements").hasSize(198) - .as("that number is also returned by count query") - .hasSize(Ints.saturatedCast(targetManagement.countByFilters(unknown, null, null, null, - Boolean.FALSE, targTagY.getName(), targTagW.getName()))) - .as("and contains the following elements").containsAll(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(198).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsAll(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step - private void verifyThat397TargetsAreInStatusUnknown(final List unknown, + private void verifyThat496TargetsAreInStatusUnknown(final List unknown, final List expected) { + final FilterParams filterParams = new FilterParams(unknown, null, null, null, Boolean.FALSE); final String query = "updatestatus==unknown"; - assertThat(targetManagement.findByFilters(PAGE, new FilterParams(unknown, null, null, null, Boolean.FALSE)) - .getContent()).as("has number of elements").hasSize(397) - .as("that number is also returned by count query") - .hasSize(Ints.saturatedCast( - targetManagement.countByFilters(unknown, null, null, null, Boolean.FALSE))) - .as("and contains the following elements").containsAll(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); - + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(496).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsAll(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step private void verifyThat198TargetsAreInStatusUnknownAndOverdue(final List unknown, final List expected) { + final FilterParams filterParams = new FilterParams(unknown, Boolean.TRUE, null, null, Boolean.FALSE); // be careful: simple filters are concatenated using AND-gating final String query = "lastcontrollerrequestat=le=${overdue_ts};updatestatus==UNKNOWN"; - assertThat(targetManagement - .findByFilters(PAGE, new FilterParams(unknown, Boolean.TRUE, null, null, Boolean.FALSE)).getContent()) - .as("has number of elements").hasSize(198).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast( - targetManagement.countByFilters(unknown, Boolean.TRUE, null, null, Boolean.FALSE))) - .as("and contains the following elements").containsAll(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(198).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsAll(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step private void verifyThat1TargetWithDescOrNameHasDS(final DistributionSet setA, final Target expected) { + final FilterParams filterParams = new FilterParams(null, null, "%targ-A%", setA.getId(), Boolean.FALSE); final String query = "(name==*targ-A* or description==*targ-A*) and (assignedds.name==" + setA.getName() + " or installedds.name==" + setA.getName() + ")"; - assertThat(targetManagement - .findByFilters(PAGE, new FilterParams(null, null, "%targ-A%", setA.getId(), Boolean.FALSE)) - .getContent()) - .as("has number of elements").hasSize(1).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast( - targetManagement.countByFilters(null, null, "%targ-A%", setA.getId(), Boolean.FALSE))) - .as("and contains the following elements").containsExactly(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); - + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(1).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsExactly(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step private void verifyThat3TargetsHaveDSAssigned(final DistributionSet setA, final List expected) { + final FilterParams filterParams = new FilterParams(null, null, null, setA.getId(), Boolean.FALSE); final String query = "assignedds.name==" + setA.getName() + " or installedds.name==" + setA.getName(); - assertThat(targetManagement.findByFilters(PAGE, new FilterParams(null, null, null, setA.getId(), Boolean.FALSE)) - .getContent()) - .as("has number of elements").hasSize(3).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast( - targetManagement.countByFilters(null, null, null, setA.getId(), Boolean.FALSE))) - .as("and contains the following elements").containsAll(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); - + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(3).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsAll(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step private void verifyThat0TargetsWithNameOrdescAndDSHaveTag(final TargetTag targTagX, final DistributionSet setA) { + final FilterParams filterParams = new FilterParams(null, null, "%targ-C%", setA.getId(), Boolean.FALSE, + targTagX.getName()); final String query = "(name==*targ-C* or description==*targ-C*) and tag==" + targTagX.getName() + " and (assignedds.name==" + setA.getName() + " or installedds.name==" + setA.getName() + ")"; - assertThat(targetManagement - .findByFilters(PAGE, - new FilterParams(null, null, "%targ-C%", setA.getId(), Boolean.FALSE, targTagX.getName())) - .getContent()) - .as("has number of elements").hasSize(0).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast(targetManagement.countByFilters(null, null, "%targ-C%", - setA.getId(), Boolean.FALSE, targTagX.getName()))) - .as("and filter query returns the same result") - .hasSize(targetManagement.findByRsql(PAGE, query).getContent().size()); - + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(0).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and filter query returns the same result") + .hasSize(targetManagement.findByRsql(PAGE, query).getContent().size()); } @Step private void verifyThat0TargetsWithTagAndDescOrNameHasDS(final TargetTag targTagW, final DistributionSet setA) { + final FilterParams filterParams = new FilterParams(null, null, "%targ-A%", setA.getId(), Boolean.FALSE, + targTagW.getName()); final String query = "(name==*targ-A* or description==*targ-A*) and tag==" + targTagW.getName() + " and (assignedds.name==" + setA.getName() + " or installedds.name==" + setA.getName() + ")"; - assertThat(targetManagement - .findByFilters(PAGE, - new FilterParams(null, null, "%targ-A%", setA.getId(), Boolean.FALSE, targTagW.getName())) - .getContent()) - .as("has number of elements").hasSize(0).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast(targetManagement.countByFilters(null, null, "%targ-A%", - setA.getId(), Boolean.FALSE, targTagW.getName()))) - .as("and filter query returns the same result") - .hasSize(targetManagement.findByRsql(PAGE, query).getContent().size()); - + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(0).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and filter query returns the same result") + .hasSize(targetManagement.findByRsql(PAGE, query).getContent().size()); } @Step private void verifyThat1TargetHasTagHasDescOrNameAndDs(final TargetTag targTagW, final DistributionSet setA, final Target expected) { + final FilterParams filterParams = new FilterParams(null, null, "%targ-C%", setA.getId(), Boolean.FALSE, + targTagW.getName()); final String query = "(name==*targ-c* or description==*targ-C*) and tag==" + targTagW.getName() + " and (assignedds.name==" + setA.getName() + " or installedds.name==" + setA.getName() + ")"; - assertThat(targetManagement - .findByFilters(PAGE, - new FilterParams(null, null, "%targ-C%", setA.getId(), Boolean.FALSE, targTagW.getName())) - .getContent()) - .as("has number of elements").hasSize(1).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast(targetManagement.countByFilters(null, null, "%targ-C%", - setA.getId(), Boolean.FALSE, targTagW.getName()))) - .as("and contains the following elements").containsExactly(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); - + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(1).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsExactly(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step private void verifyThat1TargetHasNameAndId(final String name, final String controllerId) { - assertThat(targetManagement.findByFilters(PAGE, new FilterParams(null, null, name, null, Boolean.FALSE)) - .getContent()).as("has number of elements").hasSize(1).as("that number is also returned by count query") - .hasSize(Ints - .saturatedCast(targetManagement.countByFilters(null, null, name, null, Boolean.FALSE))); + final FilterParams filterParamsByName = new FilterParams(null, null, name, null, Boolean.FALSE); + assertThat(targetManagement.findByFilters(PAGE, filterParamsByName).getContent()).as("has number of elements") + .hasSize(1).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParamsByName))); - assertThat(targetManagement.findByFilters(PAGE, new FilterParams(null, null, controllerId, null, Boolean.FALSE)) - .getContent()).as("has number of elements").hasSize(1).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast( - targetManagement.countByFilters(null, null, controllerId, null, Boolean.FALSE))); + final FilterParams filterParamsByControllerId = new FilterParams(null, null, controllerId, null, Boolean.FALSE); + assertThat(targetManagement.findByFilters(PAGE, filterParamsByControllerId).getContent()) + .as("has number of elements").hasSize(1).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParamsByControllerId))); } @Step private void verifyThat1TargetHasAttributeValue(final String value, final String controllerId) { - assertThat(targetManagement.findByFilters(PAGE, new FilterParams(null, null, value, null, Boolean.FALSE)) - .getContent()).as("has number of elements").hasSize(1).as("that number is also returned by count query") - .hasSize(Ints.saturatedCast( - targetManagement.countByFilters(null, null, value, null, Boolean.FALSE))); + final FilterParams filterParams = new FilterParams(null, null, value, null, Boolean.FALSE); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(1).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))); } @Step private void verifyThat100TargetsContainsGivenTextAndHaveTagAssigned(final TargetTag targTagY, final TargetTag targTagW, final List expected) { + final FilterParams filterParams = new FilterParams(null, null, "%targ-B%", null, Boolean.FALSE, + targTagY.getName(), targTagW.getName()); final String query = "(name==*targ-B* or description==*targ-B*) and (tag==" + targTagY.getName() + " or tag==" + targTagW.getName() + ")"; - assertThat(targetManagement.findByFilters(PAGE, - new FilterParams(null, null, "%targ-B%", null, Boolean.FALSE, targTagY.getName(), targTagW.getName())) - .getContent()).as("has number of elements").hasSize(100) - .as("that number is also returned by count query") - .hasSize(Ints.saturatedCast(targetManagement.countByFilters(null, null, "%targ-B%", null, - Boolean.FALSE, targTagY.getName(), targTagW.getName()))) - .as("and contains the following elements").containsAll(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); - + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(100).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsAll(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @SafeVarargs @@ -539,27 +513,66 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest { @Step private void verifyThat200TargetsHaveTagD(final TargetTag targTagD, final List expected) { + final FilterParams filterParams = new FilterParams(null, null, null, null, Boolean.FALSE, targTagD.getName()); final String query = "tag==" + targTagD.getName(); - assertThat(targetManagement - .findByFilters(PAGE, new FilterParams(null, null, null, null, Boolean.FALSE, targTagD.getName())) - .getContent()).as("Expected number of results is").hasSize(200) - .as("and is expected number of results is equal to ") - .hasSize(Ints.saturatedCast(targetManagement.countByFilters(null, null, null, null, - Boolean.FALSE, targTagD.getName()))) - .as("and contains the following elements").containsAll(expected) - .as("and filter query returns the same result") - .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); - + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("Expected number of results is") + .hasSize(200).as("and is expected number of results is equal to ") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsAll(expected) + .as("and filter query returns the same result") + .containsAll(targetManagement.findByRsql(PAGE, query).getContent()); } @Step - private void verifyThatRepositoryContains400Targets() { - assertThat(targetManagement.findByFilters(PAGE, new FilterParams(null, null, null, null, null)).getContent()) - .as("Overall we expect that many targets in the repository").hasSize(400) + private void verifyThatRepositoryContains500Targets() { + final FilterParams filterParams = new FilterParams(null, null, null, null, null); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()) + .as("Overall we expect that many targets in the repository").hasSize(500) .as("which is also reflected by repository count").hasSize(Ints.saturatedCast(targetManagement.count())) .as("which is also reflected by call without specification") .containsAll(targetManagement.findAll(PAGE).getContent()); + } + @Step + private void verifyThat1TargetHasTypeAndDSAssigned(final TargetType type, final DistributionSet set, + final Target expected) { + final FilterParams filterParams = new FilterParams(null, set.getId(), Boolean.FALSE, type.getId()); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(1).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsExactly(expected); + } + + @Step + private void verifyThatTargetsHasNoTypeAndDSAssignedOrInstalled(final DistributionSet set, final List expected) { + final FilterParams filterParams = new FilterParams(null, set.getId(), Boolean.TRUE, null); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(expected.size()).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsAll(expected); + } + + @Step + private void verifyThat100TargetsContainsGivenTextAndHaveTypeAssigned(final TargetType targetType, + final List expected) { + final FilterParams filterParams = new FilterParams("%targ-E%", null, Boolean.FALSE, targetType.getId()); + List filteredTargets = targetManagement.findByFilters(PAGE, filterParams).getContent(); + assertThat(filteredTargets).as("has number of elements").hasSize(100) + .as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))); + // Comparing the controller ids, as one of the targets was modified, so a 1:1 + // comparison of the objects is not possible + assertThat(filteredTargets.stream().map(Target::getControllerId).collect(Collectors.toList())) + .containsAll(expected.stream().map(Target::getControllerId).collect(Collectors.toList())); + } + + @Step + private void verifyThat400TargetsContainsGivenTextAndHaveNoTypeAssigned(final List expected) { + final FilterParams filterParams = new FilterParams("%targ-%", null, Boolean.TRUE, null); + assertThat(targetManagement.findByFilters(PAGE, filterParams).getContent()).as("has number of elements") + .hasSize(400).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast(targetManagement.countByFilters(filterParams))) + .as("and contains the following elements").containsAll(expected); } @Test @@ -770,4 +783,28 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest { .type(type); return distributionSetManagement.create(dsCreate); } + + @Test + @Description("Verifies that targets with given target type are returned from repository.") + public void findTargetByTargetType() { + TargetType testType = testdataFactory.createTargetType("testType", Collections.singletonList(standardDsType)); + List unassigned = testdataFactory.createTargets(9, "unassigned"); + List assigned = testdataFactory.createTargetsWithType(11, "assigned", testType); + + assertThat(targetManagement.findByFilters(PAGE, + new FilterParams(null,null, false, testType.getId()))) + .as("Contains the targets with set type").containsAll(assigned) + .as("and that means the following expected amount").hasSize(11); + assertThat(targetManagement.countByFilters(new FilterParams(null,null, false, testType.getId()))) + .as("Count the targets with set type").isEqualTo(11); + + assertThat(targetManagement.findByFilters(PAGE, + new FilterParams(null, null, true, null))) + .as("Contains the targets without a type").containsAll(unassigned) + .as("and that means the following expected amount").hasSize(9); + assertThat(targetManagement.countByFilters(new FilterParams(null, null, true, null))) + .as("Counts the targets without a type").isEqualTo(9); + + } + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java index 08a972ba1..bf6323960 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java @@ -28,6 +28,7 @@ import javax.validation.ConstraintViolationException; import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.repository.FilterParams; +import org.eclipse.hawkbit.repository.Identifiable; import org.eclipse.hawkbit.repository.builder.TargetUpdate; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; @@ -39,6 +40,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreated 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.TargetTagCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; @@ -57,6 +59,7 @@ import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetMetadata; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetType; +import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult; import org.eclipse.hawkbit.repository.test.matcher.Expect; import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; import org.eclipse.hawkbit.repository.test.util.WithSpringAuthorityRule; @@ -752,7 +755,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest { toggleTagAssignment(tagABCTargets, tagB); toggleTagAssignment(tagABCTargets, tagC); - assertThat(targetManagement.countByFilters(null, null, null, null, Boolean.FALSE, "X")) + assertThat(targetManagement.countByFilters(new FilterParams(null, null, null, null, Boolean.FALSE, "X"))) .as("Target count is wrong").isEqualTo(0); // search for targets with tag tagA @@ -782,11 +785,11 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest { checkTargetHasNotTags(tagCTargets, tagA, tagB); // check again target lists refreshed from DB - assertThat(targetManagement.countByFilters(null, null, null, null, Boolean.FALSE, "A")) + assertThat(targetManagement.countByFilters(new FilterParams(null, null, null, null, Boolean.FALSE, "A"))) .as("Target count is wrong").isEqualTo(targetWithTagA.size()); - assertThat(targetManagement.countByFilters(null, null, null, null, Boolean.FALSE, "B")) + assertThat(targetManagement.countByFilters(new FilterParams(null, null, null, null, Boolean.FALSE, "B"))) .as("Target count is wrong").isEqualTo(targetWithTagB.size()); - assertThat(targetManagement.countByFilters(null, null, null, null, Boolean.FALSE, "C")) + assertThat(targetManagement.countByFilters(new FilterParams(null, null, null, null, Boolean.FALSE, "C"))) .as("Target count is wrong").isEqualTo(targetWithTagC.size()); } @@ -1107,7 +1110,57 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest { assertThat(targetFound1.get().getOptLockRevision()).isEqualTo(2); assertThat(targetFound1.get().getTargetType().getId()).isEqualTo(targetType.getId()); } + + @Test + @WithUser(allSpPermissions = true) + @Description("Tests the assignment of types to multiple targets.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 20), + @Expect(type = TargetTypeCreatedEvent.class, count = 2), + @Expect(type = TargetUpdatedEvent.class, count = 29), @Expect(type = TargetDeletedEvent.class, count = 1) }) + public void targetTypeBulkAssignments() { + final List typeATargets = testdataFactory.createTargets(10, "typeATargets", "first description"); + final List typeBTargets = testdataFactory.createTargets(10, "typeBTargets", "first description"); + // create a target type + final TargetType typeA = testdataFactory.createTargetType("A", Collections.singletonList(standardDsType)); + final TargetType typeB = testdataFactory.createTargetType("B", Collections.singletonList(standardDsType)); + + // assign target type to target + TargetTypeAssignmentResult resultA = initiateTypeAssignment(typeATargets, typeA); + TargetTypeAssignmentResult resultB = initiateTypeAssignment(typeBTargets, typeB); + assertThat(resultA.getAssigned()).isEqualTo(10); + assertThat(resultB.getAssigned()).isEqualTo(10); + checkTargetsHaveType(typeATargets, typeA); + checkTargetsHaveType(typeBTargets, typeB); + + // double assignment does not unassign + resultA = initiateTypeAssignment(typeATargets, typeA); + resultB = initiateTypeAssignment(typeBTargets, typeB); + assertThat(resultA.getAssigned()).isZero(); + assertThat(resultB.getAssigned()).isZero(); + assertThat(resultA.getAlreadyAssigned()).isEqualTo(10); + assertThat(resultB.getAlreadyAssigned()).isEqualTo(10); + checkTargetsHaveType(typeATargets, typeA); + checkTargetsHaveType(typeBTargets, typeB); + + // verify that type assignment does not throw an error if target list includes an unknown id + targetManagement.deleteByControllerID(typeATargets.get(0).getControllerId()); + final TargetTypeAssignmentResult resultC = initiateTypeAssignment(typeATargets, typeB); + assertThat(resultC.getAssigned()).isEqualTo(9); + assertThat(resultC.getAlreadyAssigned()).isZero(); + checkTargetsHaveType(typeATargets, typeB); + } + + private void checkTargetsHaveType(final List targets, final TargetType type) { + List foundTargets = targetRepository + .findAllById(targets.stream().map(Identifiable::getId).collect(Collectors.toList())); + for (final Target target : foundTargets) { + if (!type.getName().equals(type.getName())) { + fail(String.format("Target %s is not of type %s.", target, type)); + } + } + } + @Test @Description("Queries and loads the metadata related to a given target.") public void findAllTargetMetadataByControllerId() { diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java index 9ac187b89..42eef3a9c 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java @@ -110,7 +110,7 @@ import org.springframework.test.context.TestPropertySource; public abstract class AbstractIntegrationTest { private static final Logger LOG = LoggerFactory.getLogger(AbstractIntegrationTest.class); - protected static final Pageable PAGE = PageRequest.of(0, 400, Sort.by(Direction.ASC, "id")); + protected static final Pageable PAGE = PageRequest.of(0, 500, Sort.by(Direction.ASC, "id")); protected static final URI LOCALHOST = URI.create("http://127.0.0.1"); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/FormComponentBuilder.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/FormComponentBuilder.java index 98e3e18bb..93e5e6358 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/FormComponentBuilder.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/FormComponentBuilder.java @@ -8,9 +8,18 @@ */ package org.eclipse.hawkbit.ui.common.builder; +import com.vaadin.data.Binder; +import com.vaadin.data.Binder.Binding; +import com.vaadin.data.Binder.BindingBuilder; +import com.vaadin.data.Validator; +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.TextArea; +import com.vaadin.ui.TextField; import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.NamedVersionedEntity; -import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.Type; import org.eclipse.hawkbit.ui.common.data.aware.ActionTypeAware; import org.eclipse.hawkbit.ui.common.data.aware.DescriptionAware; @@ -23,7 +32,6 @@ import org.eclipse.hawkbit.ui.common.data.aware.VersionAware; import org.eclipse.hawkbit.ui.common.data.providers.AbstractProxyDataProvider; import org.eclipse.hawkbit.ui.common.data.providers.DistributionSetStatelessDataProvider; import org.eclipse.hawkbit.ui.common.data.providers.TargetFilterQueryDataProvider; -import org.eclipse.hawkbit.ui.common.data.providers.TargetTypeDataProvider; import org.eclipse.hawkbit.ui.common.data.proxies.ProxyDistributionSet; import org.eclipse.hawkbit.ui.common.data.proxies.ProxyDistributionSetInfo; import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTargetFilterQuery; @@ -39,17 +47,6 @@ import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.springframework.util.StringUtils; -import com.vaadin.data.Binder; -import com.vaadin.data.Binder.Binding; -import com.vaadin.data.Binder.BindingBuilder; -import com.vaadin.data.Validator; -import com.vaadin.data.ValueProvider; -import com.vaadin.server.Setter; -import com.vaadin.ui.CheckBox; -import com.vaadin.ui.ComboBox; -import com.vaadin.ui.TextArea; -import com.vaadin.ui.TextField; - /** * Builder class for from components */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/filters/TargetManagementFilterParams.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/filters/TargetManagementFilterParams.java index f95619a58..0c0885b41 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/filters/TargetManagementFilterParams.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/filters/TargetManagementFilterParams.java @@ -8,18 +8,17 @@ */ package org.eclipse.hawkbit.ui.common.data.filters; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; - +import com.google.common.base.MoreObjects; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.ui.common.data.providers.TargetManagementStateDataProvider; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; -import com.google.common.base.MoreObjects; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; /** * Filter params for {@link TargetManagementStateDataProvider}. @@ -35,12 +34,14 @@ public class TargetManagementFilterParams implements Serializable { private boolean noTagClicked; private Collection targetTags; private Long targetFilterQueryId; + private boolean noTargetTypeClicked; + private Long targetTypeId; /** * Constructor for TargetManagementFilterParams to initialize */ public TargetManagementFilterParams() { - this(null, null, Collections.emptyList(), false, null, false, Collections.emptyList(), null); + this(null, null, Collections.emptyList(), false, null, false, Collections.emptyList(), null, false, null); } /** @@ -66,7 +67,7 @@ public class TargetManagementFilterParams implements Serializable { public TargetManagementFilterParams(final Long pinnedDistId, final String searchText, final Collection targetUpdateStatusList, final boolean overdueState, final Long distributionId, final boolean noTagClicked, final Collection targetTags, - final Long targetFilterQueryId) { + final Long targetFilterQueryId, final boolean noTargetTypeClicked, final Long targetTypeId) { this.pinnedDistId = pinnedDistId; this.searchText = searchText; this.targetUpdateStatusList = targetUpdateStatusList; @@ -75,6 +76,8 @@ public class TargetManagementFilterParams implements Serializable { this.noTagClicked = noTagClicked; this.targetTags = targetTags; this.targetFilterQueryId = targetFilterQueryId; + this.noTargetTypeClicked = noTargetTypeClicked; + this.targetTypeId = targetTypeId; } /** @@ -93,6 +96,8 @@ public class TargetManagementFilterParams implements Serializable { this.distributionId = filter.getDistributionId(); this.noTagClicked = filter.isNoTagClicked(); this.targetTags = filter.getTargetTags() != null ? new ArrayList<>(filter.getTargetTags()) : null; + this.noTargetTypeClicked = filter.isNoTargetTypeClicked(); + this.targetTypeId = filter.getTargetTypeId(); this.targetFilterQueryId = filter.getTargetFilterQueryId(); } @@ -119,7 +124,11 @@ public class TargetManagementFilterParams implements Serializable { } private boolean isAnyComplexFilterSelected() { - return distributionId != null || targetFilterQueryId != null; + return distributionId != null || targetFilterQueryId != null || isAnyTypeSelected(); + } + + private boolean isAnyTypeSelected(){ + return targetTypeId != null || isNoTargetTypeClicked(); } /** @@ -278,6 +287,46 @@ public class TargetManagementFilterParams implements Serializable { this.noTagClicked = noTagClicked; } + /** + * Gets the status of no target Type clicked + * + * @return noTargetTypeClicked true if it is clicked, otherwise + * false + */ + public boolean isNoTargetTypeClicked() { + return noTargetTypeClicked; + } + + /** + * Sets the state of no target type filter + * + * @param noTargetTypeClicked + * true if the tag is clicked, otherwise + * false + */ + public void setNoTargetTypeClicked(boolean noTargetTypeClicked) { + this.noTargetTypeClicked = noTargetTypeClicked; + } + + /** + * Gets the targetTypeId + * + * @return targetTypeId for target type filter + */ + public Long getTargetTypeId() { + return targetTypeId; + } + + /** + * Sets the targetType + * + * @param targetTypeId + * Id of targetType + */ + public void setTargetTypeId(Long targetTypeId) { + this.targetTypeId = targetTypeId; + } + // equals requires all fields in condition @SuppressWarnings("squid:S1067") @Override @@ -296,13 +345,15 @@ public class TargetManagementFilterParams implements Serializable { && Objects.equals(this.getDistributionId(), other.getDistributionId()) && Objects.equals(this.isNoTagClicked(), other.isNoTagClicked()) && Objects.equals(this.getTargetTags(), other.getTargetTags()) - && Objects.equals(this.getTargetFilterQueryId(), other.getTargetFilterQueryId()); + && Objects.equals(this.getTargetFilterQueryId(), other.getTargetFilterQueryId()) + && Objects.equals(this.isNoTargetTypeClicked(), other.isNoTargetTypeClicked()) + && Objects.equals(this.getTargetTypeId(), other.getTargetTypeId()); } @Override public int hashCode() { return Objects.hash(getPinnedDistId(), getSearchText(), getTargetUpdateStatusList(), isOverdueState(), - getDistributionId(), isNoTagClicked(), getTargetTags(), getTargetFilterQueryId()); + getDistributionId(), isNoTagClicked(), getTargetTags(), getTargetFilterQueryId(), isNoTargetTypeClicked(), getTargetTypeId()); } @Override @@ -311,6 +362,7 @@ public class TargetManagementFilterParams implements Serializable { .add("searchText", getSearchText()).add("targetUpdateStatusList", getTargetUpdateStatusList()) .add("overdueState", isOverdueState()).add("distributionId", getDistributionId()) .add("noTagClicked", isNoTagClicked()).add("targetTags", getTargetTags()) - .add("targetFilterQueryId", getTargetFilterQueryId()).toString(); + .add("targetFilterQueryId", getTargetFilterQueryId()) + .add("noTargetTypeClicked", isNoTargetTypeClicked()).add("targetTypeId", getTargetTypeId()).toString(); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/TargetManagementStateDataProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/TargetManagementStateDataProvider.java index 945aac3d9..ba85b8604 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/TargetManagementStateDataProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/TargetManagementStateDataProvider.java @@ -64,6 +64,8 @@ public class TargetManagementStateDataProvider final boolean noTagClicked = filter.isNoTagClicked(); final String[] targetTags = filter.getTargetTags().toArray(new String[0]); final Long targetFilterQueryId = filter.getTargetFilterQueryId(); + final boolean noTargetTypeClicked = filter.isNoTargetTypeClicked(); + final Long targetTypeId = filter.getTargetTypeId(); if (pinnedDistId != null) { return targetManagement.findByFilterOrderByLinkedDistributionSet(pageRequest, pinnedDistId, @@ -76,6 +78,12 @@ public class TargetManagementStateDataProvider return targetManagement.findByTargetFilterQuery(pageRequest, targetFilterQueryId); } + // Type Filter of Deployment Management view + if (targetTypeId != null || noTargetTypeClicked) { + return targetManagement.findByFilters(pageRequest, new FilterParams(searchText, distributionId, noTargetTypeClicked, targetTypeId)); + } + + // Simple Filter of Deployment Management view return targetManagement.findByFilters(pageRequest, new FilterParams(targetUpdateStatusList, overdueState, searchText, distributionId, noTagClicked, targetTags)); } @@ -95,6 +103,8 @@ public class TargetManagementStateDataProvider final Long distributionId = filter.getDistributionId(); final boolean noTagClicked = filter.isNoTagClicked(); final String[] targetTags = filter.getTargetTags().toArray(new String[0]); + final boolean noTargetTypeClicked = filter.isNoTargetTypeClicked(); + final Long targetTypeId = filter.getTargetTypeId(); final Long targetFilterQueryId = filter.getTargetFilterQueryId(); if (filter.isAnyFilterSelected()) { @@ -102,8 +112,14 @@ public class TargetManagementStateDataProvider return targetManagement.countByTargetFilterQuery(targetFilterQueryId); } - return targetManagement.countByFilters(targetUpdateStatusList, overdueState, searchText, distributionId, - noTagClicked, targetTags); + // Type Filter of Deployment Management view + if (targetTypeId != null || noTargetTypeClicked) { + return targetManagement.countByFilters(new FilterParams(searchText, distributionId, noTargetTypeClicked, targetTypeId)); + } + + // Simple Filter of Deployment Management view + return targetManagement.countByFilters(new FilterParams(targetUpdateStatusList, overdueState, searchText, distributionId, + noTagClicked, targetTags)); } return targetManagement.count(); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/TargetTypeDataProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/TargetTypeDataProvider.java index ae0b230bf..97eae1327 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/TargetTypeDataProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/TargetTypeDataProvider.java @@ -31,6 +31,14 @@ public class TargetTypeDataProvider private static final long serialVersionUID = 1L; private final transient TargetTypeManagement targetTypeManagement; + /** + * Constructor + * + * @param targetTypeManagement + * TargetTypeManagement + * @param mapper + * Mapper + */ public TargetTypeDataProvider(final TargetTypeManagement targetTypeManagement, IdentifiableEntityToProxyIdentifiableEntityMapper mapper) { super(mapper, Sort.by(Direction.ASC, "name")); this.targetTypeManagement = targetTypeManagement; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/event/FilterType.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/event/FilterType.java index ff64c5f4a..b075dc4f4 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/event/FilterType.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/event/FilterType.java @@ -12,5 +12,5 @@ package org.eclipse.hawkbit.ui.common.event; * Enum constants for filter type */ public enum FilterType { - SEARCH, TYPE, TARGET_TYPE, TAG, NO_TAG, STATUS, OVERDUE, QUERY, DISTRIBUTION, MASTER; + SEARCH, TYPE, TAG, NO_TAG, TARGET_TYPE, NO_TARGET_TYPE, STATUS, OVERDUE, QUERY, DISTRIBUTION, MASTER; } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractTargetTypeFilterButtons.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractTargetTypeFilterButtons.java index 8663b075f..436b4f735 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractTargetTypeFilterButtons.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractTargetTypeFilterButtons.java @@ -9,8 +9,7 @@ package org.eclipse.hawkbit.ui.common.filterlayout; import com.vaadin.ui.Button; -import java.util.Collection; -import java.util.Map; +import org.eclipse.hawkbit.repository.TargetTypeManagement; import org.eclipse.hawkbit.ui.common.CommonUiDependencies; import org.eclipse.hawkbit.ui.common.data.proxies.ProxyIdentifiableEntity; import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTarget; @@ -20,47 +19,57 @@ import org.eclipse.hawkbit.ui.common.event.EventView; import org.eclipse.hawkbit.ui.common.event.FilterChangedEventPayload; import org.eclipse.hawkbit.ui.common.event.FilterType; import org.eclipse.hawkbit.ui.common.filterlayout.AbstractFilterButtonClickBehaviour.ClickBehaviourType; -import org.eclipse.hawkbit.ui.common.state.TagFilterLayoutUiState; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.decorators.SPUITagButtonStyle; +import org.eclipse.hawkbit.ui.management.targettag.filter.TargetTagFilterLayoutUiState; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; import org.eclipse.hawkbit.ui.utils.UINotification; -import org.springframework.util.CollectionUtils; + +import java.util.Collection; /** - * Class for defining the type filter buttons. + * Class for defining the target type filter buttons. */ public abstract class AbstractTargetTypeFilterButtons extends AbstractFilterButtons { private static final long serialVersionUID = 1L; - private final TagFilterLayoutUiState tagFilterLayoutUiState; - + private final TargetTagFilterLayoutUiState targetTagFilterLayoutUiState; protected final UINotification uiNotification; - private final Button noTargetTypeButton; - private final TargetTypeFilterButtonClick targetTypeFilterButtonClick; + private final transient TargetTypeManagement targetTypeManagement; + private final Button noTargetTypeButton; + private boolean preNoTargetTypeBtnState; /** * Constructor for AbstractTargetTypeFilterButtons - * * @param uiDependencies * {@link CommonUiDependencies} - * @param tagFilterLayoutUiState - * TagFilterLayoutUiState + * @param targetTagFilterLayoutUiState + * {@link TargetTagFilterLayoutUiState} + * @param targetTypeManagement + * TargetTypeManagement */ protected AbstractTargetTypeFilterButtons(final CommonUiDependencies uiDependencies, - final TagFilterLayoutUiState tagFilterLayoutUiState) { + final TargetTagFilterLayoutUiState targetTagFilterLayoutUiState, + final TargetTypeManagement targetTypeManagement) { super(uiDependencies.getEventBus(), uiDependencies.getI18n(), uiDependencies.getUiNotification(), uiDependencies.getPermChecker()); + this.preNoTargetTypeBtnState = false; this.uiNotification = uiDependencies.getUiNotification(); - this.tagFilterLayoutUiState = tagFilterLayoutUiState; + this.targetTagFilterLayoutUiState = targetTagFilterLayoutUiState; + this.targetTypeManagement = targetTypeManagement; this.noTargetTypeButton = buildNoTargetTypeButton(); this.targetTypeFilterButtonClick = new TargetTypeFilterButtonClick(this::onFilterChangedEvent); } + @Override + protected TargetTypeFilterButtonClick getFilterButtonClickBehaviour() { + return targetTypeFilterButtonClick; + } + private Button buildNoTargetTypeButton() { final Button noTargetType = SPUIComponentProvider.getButton( getFilterButtonIdPrefix() + "." + SPUIDefinitions.NO_TARGET_TYPE_BUTTON_ID, @@ -71,24 +80,51 @@ public abstract class AbstractTargetTypeFilterButtons extends AbstractFilterButt final ProxyTargetType proxyTargetType = new ProxyTargetType(); proxyTargetType.setNoTargetType(true); + noTargetType.addClickListener(event -> getFilterButtonClickBehaviour().processFilterClick(proxyTargetType)); + + noTargetType.addStyleName("filter-drop-hint-layout"); return noTargetType; } - @Override - protected TargetTypeFilterButtonClick getFilterButtonClickBehaviour(){ - return targetTypeFilterButtonClick; + /** + * @return the noTargetType Button component + */ + public Button getNoTargetTypeButton() { + return noTargetTypeButton; } - private void onFilterChangedEvent(final ProxyTargetType targetType, - final ClickBehaviourType clickType) { - final Long targetTypeId = ClickBehaviourType.CLICKED == clickType ? targetType.getId() + private void onFilterChangedEvent(ProxyTargetType proxyTargetType, ClickBehaviourType clickType) { + getDataCommunicator().reset(); + + final boolean isNoTargetTypeActive = proxyTargetType.isNoTargetType() && clickType == ClickBehaviourType.CLICKED; + + if (isNoTargetTypeActive) { + getNoTargetTypeButton().addStyleName(SPUIStyleDefinitions.SP_NO_TAG_BTN_CLICKED_STYLE); + } else { + getNoTargetTypeButton().removeStyleName(SPUIStyleDefinitions.SP_NO_TAG_BTN_CLICKED_STYLE); + } + + if (preNoTargetTypeBtnState != isNoTargetTypeActive){ + publishNoTargetTypeChangedEvent(isNoTargetTypeActive); + } + + final Long targetTypeId = ClickBehaviourType.CLICKED == clickType ? proxyTargetType.getId() : null; publishFilterChangedEvent(targetTypeId); + preNoTargetTypeBtnState = isNoTargetTypeActive; + } + + private void publishNoTargetTypeChangedEvent(final boolean isNoTargetTypeActivated) { + eventBus.publish(EventTopics.FILTER_CHANGED, this, new FilterChangedEventPayload<>(ProxyTarget.class, + FilterType.NO_TARGET_TYPE, isNoTargetTypeActivated, EventView.DEPLOYMENT)); + targetTagFilterLayoutUiState.setNoTargetTypeClicked(isNoTargetTypeActivated); } private void publishFilterChangedEvent(final Long targetTypeId) { eventBus.publish(EventTopics.FILTER_CHANGED, this, new FilterChangedEventPayload<>(ProxyTarget.class, FilterType.TARGET_TYPE, targetTypeId, EventView.DEPLOYMENT)); + + targetTagFilterLayoutUiState.setClickedTargetTypeFilterId(targetTypeId); } /** @@ -105,52 +141,92 @@ public abstract class AbstractTargetTypeFilterButtons extends AbstractFilterButt */ protected abstract EventView getView(); - /** - * Target type deletion operation. - * - * @param targetTypeToDelete - * target type to delete - * - * @return true if target type is deleted, in error case false. - */ - protected abstract boolean deleteTargetType(final ProxyTargetType targetTypeToDelete); /** - * @return Button component of no target type + * Type deletion operation. + * + * @param typeToDelete + * target type to delete + * @return true if delete target type has no exception */ - public Button getNoTargetTypeButton() { - return noTargetTypeButton; - } + protected abstract boolean deleteTargetType(final ProxyTargetType typeToDelete); @Override public void restoreState() { - final Map targetTypesToRestore = tagFilterLayoutUiState.getClickedTagIdsWithName(); + final Long targetFilterTypeIdToRestore = targetTagFilterLayoutUiState.getClickedTargetTypeFilterId(); - if (!CollectionUtils.isEmpty(targetTypesToRestore)) { - removeNonExistingTargetTypes(targetTypesToRestore); + if (targetFilterTypeIdToRestore != null) { + if (targetTypeExists(targetFilterTypeIdToRestore)) { + targetTypeFilterButtonClick + .setPreviouslyClickedFilterId(targetTagFilterLayoutUiState.getClickedTargetTypeFilterId()); + } else { + targetTagFilterLayoutUiState.setClickedTargetTypeFilterId(null); + } } - if (tagFilterLayoutUiState.isNoTagClicked()) { + if (targetTagFilterLayoutUiState.isNoTargetTypeClicked()) { getNoTargetTypeButton().addStyleName(SPUIStyleDefinitions.SP_NO_TAG_BTN_CLICKED_STYLE); } } - private void removeNonExistingTargetTypes(final Map targetTypeIdsWithName) { - final Collection targetTypeIds = targetTypeIdsWithName.keySet(); - final Collection existingTargetTypeIds = filterExistingTargetTypeIds(targetTypeIds); - if (targetTypeIds.size() != existingTargetTypeIds.size()) { - targetTypeIds.retainAll(existingTargetTypeIds); + /** + * Reset filter on target type updated + * + * @param updatedTargetTypeIds + * Collections of updated target type Ids + */ + public void resetFilterOnTargetTypeUpdated(Collection updatedTargetTypeIds) { + if (isClickedTargetTypeInIds(updatedTargetTypeIds)) { + publishFilterChangedEvent(targetTypeFilterButtonClick.getPreviouslyClickedFilterId()); } - } /** - * Filters out non-existant target type by ids. + * Reset filter on target type deleted * - * @param targetTypeIds - * provided target type ids - * @return filtered list of existing target type ids + * @param deletedTargetTargetTypeIds + * Collections of updated target type Ids */ - protected abstract Collection filterExistingTargetTypeIds(final Collection targetTypeIds); + public void resetFilterOnTargetTypeDeleted(final Collection deletedTargetTargetTypeIds) { + if (isClickedTargetTypeInIds(deletedTargetTargetTypeIds)) { + targetTypeFilterButtonClick.setPreviouslyClickedFilterId(null); + publishFilterChangedEvent(null); + } + } + private boolean isClickedTargetTypeInIds(final Collection targetTypeIds) { + final Long clickedTargetTypeId = targetTypeFilterButtonClick.getPreviouslyClickedFilterId(); + return clickedTargetTypeId != null && targetTypeIds.contains(clickedTargetTypeId); + } + + /** + * Reevaluate filter + */ + public void reevaluateFilter() { + final Long clickedTargetTypeId = targetTypeFilterButtonClick.getPreviouslyClickedFilterId(); + + if (clickedTargetTypeId != null && !targetTypeExists(clickedTargetTypeId)) { + targetTypeFilterButtonClick.setPreviouslyClickedFilterId(null); + publishFilterChangedEvent(null); + } + } + + private boolean targetTypeExists(Long targetTypeId) { + return targetTypeManagement.get(targetTypeId).isPresent(); + } + + /** + * Remove applied target type filter + */ + public void clearAppliedTargetTypeFilter() { + if (targetTagFilterLayoutUiState.isNoTargetTypeClicked()) { + targetTagFilterLayoutUiState.setNoTargetTypeClicked(false); + getNoTargetTypeButton().removeStyleName(SPUIStyleDefinitions.SP_NO_TAG_BTN_CLICKED_STYLE); + } + + if (targetTypeFilterButtonClick.getPreviouslyClickedFilterId() != null) { + targetTypeFilterButtonClick.setPreviouslyClickedFilterId(null); + targetTagFilterLayoutUiState.setClickedTargetTypeFilterId(null); + } + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/TargetTypeFilterButtonClick.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/TargetTypeFilterButtonClick.java index b2e82cbbe..be009b584 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/TargetTypeFilterButtonClick.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/TargetTypeFilterButtonClick.java @@ -17,24 +17,61 @@ import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTargetType; public class TargetTypeFilterButtonClick extends AbstractFilterSingleButtonClick { private static final long serialVersionUID = 1L; + private boolean noTargetTypeBtnClicked; + private final transient BiConsumer filterChangedCallback; - TargetTypeFilterButtonClick(final BiConsumer filterChangedCallback) { + /** + * Constructor + * + * @param filterChangedCallback + * filterChangedCallback + */ + public TargetTypeFilterButtonClick( + final BiConsumer filterChangedCallback) { this.filterChangedCallback = filterChangedCallback; + this.noTargetTypeBtnClicked = false; + } + + @Override + public void processFilterClick(final ProxyTargetType clickedFilter) { + if (isFilterPreviouslyClicked(clickedFilter) || isNoTargetTypePreviouslyClicked(clickedFilter)) { + previouslyClickedFilterId = null; + filterUnClicked(clickedFilter); + } else { + previouslyClickedFilterId = clickedFilter.getId(); + filterClicked(clickedFilter); + } } @Override protected void filterUnClicked(ProxyTargetType clickedFilter) { + if (clickedFilter.isNoTargetType()){ + noTargetTypeBtnClicked = false; + } filterChangedCallback.accept(clickedFilter, ClickBehaviourType.UNCLICKED); } @Override protected void filterClicked(ProxyTargetType clickedFilter) { + noTargetTypeBtnClicked = clickedFilter.isNoTargetType(); filterChangedCallback.accept(clickedFilter, ClickBehaviourType.CLICKED); } @Override public boolean isFilterPreviouslyClicked(final ProxyTargetType clickedFilter) { - return false; + return (previouslyClickedFilterId != null && previouslyClickedFilterId.equals(clickedFilter.getId())); } + + private boolean isNoTargetTypePreviouslyClicked(ProxyTargetType clickedFilter) { + return clickedFilter.isNoTargetType() && isNoTargetTypeBtnClicked(); + } + + /** + * @return true if no target type button clicked + */ + public boolean isNoTargetTypeBtnClicked() { + return noTargetTypeBtnClicked; + } + } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/AbstractTargetsToTargetTypeAssignmentSupport.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/AbstractTargetsToTargetTypeAssignmentSupport.java new file mode 100644 index 000000000..8cb6f8c90 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/AbstractTargetsToTargetTypeAssignmentSupport.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2021 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.common.grid.support.assignment; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.hawkbit.im.authentication.SpPermission; +import org.eclipse.hawkbit.ui.SpPermissionChecker; +import org.eclipse.hawkbit.ui.common.CommonUiDependencies; +import org.eclipse.hawkbit.ui.common.data.proxies.ProxyIdentifiableEntity; +import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTarget; +import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTargetType; +import org.eclipse.hawkbit.ui.common.event.EntityModifiedEventPayload; +import org.eclipse.hawkbit.ui.common.event.EventTopics; +import org.vaadin.spring.events.EventBus; + +/** + * Support for assigning/un-assigning targets to target type. + * + */ +public abstract class AbstractTargetsToTargetTypeAssignmentSupport extends AssignmentSupport { + private final SpPermissionChecker permChecker; + private final EventBus.UIEventBus eventBus; + + protected AbstractTargetsToTargetTypeAssignmentSupport(final CommonUiDependencies uiDependencies) { + super(uiDependencies.getUiNotification(), uiDependencies.getI18n()); + this.permChecker = uiDependencies.getPermChecker(); + this.eventBus = uiDependencies.getEventBus(); + } + + @Override + public List getMissingPermissionsForDrop() { + return permChecker.hasUpdateTargetPermission() ? Collections.emptyList() + : Collections.singletonList(SpPermission.UPDATE_TARGET); + } + + protected void publishTypeAssignmentEvent(final List sourceItemsToAssign) { + final List assignedTargetIds = sourceItemsToAssign.stream().map(ProxyIdentifiableEntity::getId) + .collect(Collectors.toList()); + eventBus.publish(EventTopics.ENTITY_MODIFIED, this, + new EntityModifiedEventPayload(EntityModifiedEventPayload.EntityModifiedEventType.ENTITY_UPDATED, + ProxyTarget.class, assignedTargetIds)); + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToNoTargetTypeAssignmentSupport.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToNoTargetTypeAssignmentSupport.java new file mode 100644 index 000000000..eee84202d --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToNoTargetTypeAssignmentSupport.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2021 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.common.grid.support.assignment; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.hawkbit.repository.TargetManagement; +import org.eclipse.hawkbit.repository.model.AbstractAssignmentResult; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.ui.common.CommonUiDependencies; +import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTarget; +import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTargetType; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; +import org.springframework.util.CollectionUtils; + +/** + * Support for un-assigning the {@link ProxyTarget} items from a {@link ProxyTargetType}. + * + */ +public class TargetsToNoTargetTypeAssignmentSupport extends AbstractTargetsToTargetTypeAssignmentSupport { + private final TargetManagement targetManagement; + + private static final String CAPTION_TYPE = "caption.type"; + private static final String CAPTION_TARGET = "caption.target"; + + /** + * Constructor for TargetsToTargetTypeAssignmentSupport + * + * @param uiDependencies + * {@link CommonUiDependencies} + * @param targetManagement + * TargetManagement + */ + public TargetsToNoTargetTypeAssignmentSupport(final CommonUiDependencies uiDependencies, + final TargetManagement targetManagement) { + super(uiDependencies); + this.targetManagement = targetManagement; + } + + @Override + protected void performAssignment(List sourceItemsToAssign, ProxyTargetType targetItem) { + if (hasRequiredPermissions()) { + final AbstractAssignmentResult typesAssignmentResult = initiateTargetTypeUnAssignment( + sourceItemsToAssign); + + final String assignmentMsg = createAssignmentMessage(typesAssignmentResult, i18n.getMessage(CAPTION_TARGET), + i18n.getMessage(CAPTION_TYPE), ""); + notification.displaySuccess(assignmentMsg); + + publishTypeAssignmentEvent(sourceItemsToAssign); + } + } + + /** + * + * @return false if required permissions are missing + */ + private boolean hasRequiredPermissions() { + final List requiredPermissions = getMissingPermissionsForDrop(); + if (!CollectionUtils.isEmpty(requiredPermissions)) { + notification + .displayValidationError(i18n.getMessage(UIMessageIdProvider.MESSAGE_ERROR_PERMISSION_INSUFFICIENT, requiredPermissions)); + return false; + } + return true; + } + + protected AbstractAssignmentResult initiateTargetTypeUnAssignment(final List sourceItems) { + final Collection controllerIdsToAssign = sourceItems.stream().map(ProxyTarget::getControllerId) + .collect(Collectors.toList()); + + return targetManagement.unAssignType(controllerIdsToAssign); + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToTargetTypeAssignmentSupport.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToTargetTypeAssignmentSupport.java new file mode 100644 index 000000000..73e49f6f4 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToTargetTypeAssignmentSupport.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2021 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.common.grid.support.assignment; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import org.eclipse.hawkbit.repository.TargetManagement; +import org.eclipse.hawkbit.repository.model.AbstractAssignmentResult; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.ui.common.CommonUiDependencies; +import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTarget; +import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTargetType; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; + +/** + * Support for assigning the {@link ProxyTarget} items to {@link ProxyTargetType}. + * + */ +public class TargetsToTargetTypeAssignmentSupport extends AbstractTargetsToTargetTypeAssignmentSupport { + private final TargetManagement targetManagement; + + private static final String CAPTION_TYPE = "caption.type"; + private static final String CAPTION_TARGET = "caption.target"; + + /** + * Constructor for TargetsToTargetTypeAssignmentSupport + * + * @param uiDependencies + * {@link CommonUiDependencies} + * @param targetManagement + * TargetManagement + */ + public TargetsToTargetTypeAssignmentSupport(final CommonUiDependencies uiDependencies, final TargetManagement targetManagement) { + super(uiDependencies); + this.targetManagement = targetManagement; + } + + @Override + protected List getFilteredSourceItems(List sourceItemsToAssign, ProxyTargetType targetItem) { + if (!isAssignmentValid(sourceItemsToAssign, targetItem)) { + return Collections.emptyList(); + } + + return sourceItemsToAssign; + } + + /** + * + * @param sourceItemsToAssign + * @param targetItem + * @return false if some targets already have a type assigned + */ + private boolean isAssignmentValid(List sourceItemsToAssign, ProxyTargetType targetItem) { + if(sourceItemsToAssign.size() > 1) { + List targetsWithDifferentType = sourceItemsToAssign.stream().filter( + target -> target.getTypeInfo() != null && !target.getTypeInfo().getId().equals(targetItem.getId())) + .collect(Collectors.toList()); + + if (!targetsWithDifferentType.isEmpty()) { + notification.displayValidationError(i18n.getMessage(UIMessageIdProvider.MESSAGE_TARGET_TARGETTYPE_ASSIGNED)); + return false; + } + } + return true; + } + + @Override + protected void performAssignment(final List sourceItemsToAssign, final ProxyTargetType targetItem) { + final Long typeId = targetItem.getId(); + + final AbstractAssignmentResult typesAssignmentResult = initiateTargetTypeAssignment(sourceItemsToAssign, + typeId); + + final String assignmentMsg = createAssignmentMessage(typesAssignmentResult, + i18n.getMessage(CAPTION_TARGET), + i18n.getMessage(CAPTION_TYPE), targetItem.getName()); + notification.displaySuccess(assignmentMsg); + + publishTypeAssignmentEvent(sourceItemsToAssign); + } + + private AbstractAssignmentResult initiateTargetTypeAssignment(final List sourceItems, + final Long typeId) { + final Collection controllerIdsToAssign = sourceItems.stream().map(ProxyTarget::getControllerId) + .collect(Collectors.toList()); + + return targetManagement.assignType(controllerIdsToAssign, typeId); + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TypeToTargetAssignmentSupport.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TypeToTargetAssignmentSupport.java new file mode 100644 index 000000000..995468a3d --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TypeToTargetAssignmentSupport.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2021 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.common.grid.support.assignment; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.hawkbit.im.authentication.SpPermission; +import org.eclipse.hawkbit.repository.TargetManagement; +import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult; +import org.eclipse.hawkbit.ui.SpPermissionChecker; +import org.eclipse.hawkbit.ui.common.CommonUiDependencies; +import org.eclipse.hawkbit.ui.common.data.proxies.ProxyIdentifiableEntity; +import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTarget; +import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTargetType; +import org.eclipse.hawkbit.ui.common.event.EntityModifiedEventPayload; +import org.eclipse.hawkbit.ui.common.event.EventTopics; +import org.vaadin.spring.events.EventBus; + +/** + * Support for assigning the {@link ProxyTargetType} items to {@link ProxyTarget}. + * + */ +public class TypeToTargetAssignmentSupport extends AssignmentSupport { + + private final TargetManagement targetManagement; + private final EventBus.UIEventBus eventBus; + private final SpPermissionChecker permChecker; + + private static final String CAPTION_TYPE = "caption.type"; + private static final String CAPTION_TARGET = "caption.target"; + + /** + * Constructor for TypeToTargetAssignmentSupport + * + * @param uiDependencies + * {@link CommonUiDependencies} + * @param targetManagement + * TargetManagement + */ + public TypeToTargetAssignmentSupport(final CommonUiDependencies uiDependencies, final TargetManagement targetManagement) { + super(uiDependencies.getUiNotification(), uiDependencies.getI18n()); + this.eventBus = uiDependencies.getEventBus(); + this.permChecker = uiDependencies.getPermChecker(); + this.targetManagement = targetManagement; + } + + @Override + public List getMissingPermissionsForDrop() { + return permChecker.hasUpdateTargetPermission() ? Collections.emptyList() + : Collections.singletonList(SpPermission.UPDATE_TARGET); + } + + @Override + protected void performAssignment(List sourceItemsToAssign, ProxyTarget targetItem) { + final String controllerId = targetItem.getControllerId(); + // multi-type assignment is not supported + final Long typeId = sourceItemsToAssign.get(0).getId(); + TargetTypeAssignmentResult targetTypeAssignmentResult = initiateTargetTypeAssignment(typeId, controllerId); + + final String assignmentMsg = createAssignmentMessage(targetTypeAssignmentResult, i18n.getMessage(CAPTION_TARGET), + i18n.getMessage(CAPTION_TYPE), sourceItemsToAssign.get(0).getName()); + notification.displaySuccess(assignmentMsg); + + publishTypeAssignmentEvent(sourceItemsToAssign); + } + + protected TargetTypeAssignmentResult initiateTargetTypeAssignment(final Long targetType, final String controllerId) { + return targetManagement.assignType(Collections.singletonList(controllerId), targetType); + } + + protected void publishTypeAssignmentEvent(final List sourceItemsToAssign) { + final List assignedTargetIds = sourceItemsToAssign.stream().map(ProxyIdentifiableEntity::getId) + .collect(Collectors.toList()); + eventBus.publish(EventTopics.ENTITY_MODIFIED, this, new EntityModifiedEventPayload( + EntityModifiedEventPayload.EntityModifiedEventType.ENTITY_UPDATED, ProxyTarget.class, assignedTargetIds)); + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/state/TagFilterLayoutUiState.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/state/TagFilterLayoutUiState.java index 22d79ae4b..9ecba6f92 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/state/TagFilterLayoutUiState.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/state/TagFilterLayoutUiState.java @@ -18,8 +18,26 @@ public class TagFilterLayoutUiState extends HidableLayoutUiState { private static final long serialVersionUID = 1L; private boolean noTagClicked; + private boolean noTargetTypeClicked; private final Map clickedTagIdsWithName = new HashMap<>(); + /** + * @return True if no targetType is clicked or selected + */ + public boolean isNoTargetTypeClicked() { + return noTargetTypeClicked; + } + + /** + * Sets the status of no targetType clicked + * + * @param noTargetTypeClicked + * boolean + */ + public void setNoTargetTypeClicked(boolean noTargetTypeClicked) { + this.noTargetTypeClicked = noTargetTypeClicked; + } + /** * @return True if not tag is clicked or selected */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetCountMessageLabel.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetCountMessageLabel.java index e23b8f7d6..d8bc33586 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetCountMessageLabel.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetCountMessageLabel.java @@ -134,6 +134,7 @@ public class TargetCountMessageLabel extends AbstractFooterSupport implements Co appendSearchMsg(filterMessageBuilder, targetFilterParams.getSearchText()); appendDsMsg(filterMessageBuilder, targetFilterParams.getDistributionId()); appendCustomFilterQueryMsg(filterMessageBuilder, targetFilterParams.getTargetFilterQueryId()); + appendTargetTypeFilterMsg(filterMessageBuilder, targetFilterParams.isNoTargetTypeClicked(), targetFilterParams.getTargetTypeId()); String filterMessage = filterMessageBuilder.toString().trim(); if (filterMessage.endsWith(",")) { @@ -186,6 +187,12 @@ public class TargetCountMessageLabel extends AbstractFooterSupport implements Co } } + private void appendTargetTypeFilterMsg(final StringBuilder filterMessageBuilder, boolean noTargetTypeClicked, final Long targetTypeId) { + if (targetTypeId != null || noTargetTypeClicked) { + appendFilterMsg(filterMessageBuilder, i18n.getMessage("label.filter.target.type")); + } + } + /** * Update pinning details * diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGrid.java index fab8d540a..268748a97 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGrid.java @@ -50,6 +50,7 @@ import org.eclipse.hawkbit.ui.common.grid.support.SelectionSupport; import org.eclipse.hawkbit.ui.common.grid.support.assignment.AssignmentSupport; import org.eclipse.hawkbit.ui.common.grid.support.assignment.DistributionSetsToTargetAssignmentSupport; import org.eclipse.hawkbit.ui.common.grid.support.assignment.TargetTagsToTargetAssignmentSupport; +import org.eclipse.hawkbit.ui.common.grid.support.assignment.TypeToTargetAssignmentSupport; import org.eclipse.hawkbit.ui.management.dstable.DistributionGridLayoutUiState; import org.eclipse.hawkbit.ui.management.miscs.DeploymentAssignmentWindowController; import org.eclipse.hawkbit.ui.management.targettag.filter.TargetTagFilterLayoutUiState; @@ -157,9 +158,12 @@ public class TargetGrid extends AbstractGrid(this, i18n, uiDependencies.getUiNotification(), sourceTargetAssignmentStrategies, eventBus)); @@ -263,6 +267,10 @@ public class TargetGrid extends AbstractGrid { filter.setDistributionId(null); filter.setNoTagClicked(false); + filter.setNoTargetTypeClicked(false); filter.setOverdueState(false); filter.setSearchText(null); filter.setTargetTags(Collections.emptyList()); filter.setTargetUpdateStatusList(Collections.emptyList()); - + filter.setTargetTypeId(null); + filter.setTargetFilterQueryId(null); getFilterSupport().refreshFilter(); }); } - /** - * Update filter on simple tab selection - */ - public void onSimpleTabSelected() { - getFilterSupport().updateFilter(TargetManagementFilterParams::setTargetFilterQueryId, null); - } - @Override public void addColumns() { addNameColumn(); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridHeader.java index c55f1dc13..4e85ddf61 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridHeader.java @@ -140,7 +140,8 @@ public class TargetGridHeader extends AbstractEntityGridHeader { super.restoreState(); if (targetTagFilterLayoutUiState.isCustomFilterTabSelected()) { - onSimpleFilterReset(); + onFilterReset(); + disabledSearchIcon(); } if (isBulkUploadInProgress()) { @@ -153,12 +154,10 @@ public class TargetGridHeader extends AbstractEntityGridHeader { } /** - * Reset the distribution set filer drop area support + * Reset the distribution set filer drop area support and the search */ - public void onSimpleFilterReset() { + public void onFilterReset() { getSearchHeaderSupport().resetSearch(); - getSearchHeaderSupport().disableSearch(); - distributionSetFilterDropAreaSupport.reset(); } @@ -248,4 +247,10 @@ public class TargetGridHeader extends AbstractEntityGridHeader { public void enableSearchIcon() { getSearchHeaderSupport().enableSearch(); } + /** + * Disable search icon in the search header + */ + public void disabledSearchIcon() { + getSearchHeaderSupport().disableSearch(); + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridLayout.java index f5cecc62e..6a08e5b95 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridLayout.java @@ -204,15 +204,21 @@ public class TargetGridLayout extends AbstractGridComponentLayout { */ public void onTargetFilterTabChanged(final TargetFilterTabChangedEventPayload eventPayload) { final boolean isCustomFilterTabSelected = TargetFilterTabChangedEventPayload.CUSTOM == eventPayload; + final boolean isTargetTypeFilterTabSelected = TargetFilterTabChangedEventPayload.TARGET_TYPE == eventPayload; + final boolean isSimpleTypeFilterTabSelected = TargetFilterTabChangedEventPayload.SIMPLE == eventPayload; + targetGridHeader.onFilterReset(); if (isCustomFilterTabSelected) { - targetGridHeader.onSimpleFilterReset(); - targetGrid.onCustomTabSelected(); - } else { + targetGridHeader.disabledSearchIcon(); + } + if(isTargetTypeFilterTabSelected){ + targetGridHeader.enableSearchIcon(); + } + if (isSimpleTypeFilterTabSelected){ targetGridHeader.enableSearchIcon(); - targetGrid.onSimpleTabSelected(); } + targetGrid.resetAllFilters(); countMessageLabel.updateFilteredCount(); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetWindowLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetWindowLayout.java index 477522749..76d474730 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetWindowLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetWindowLayout.java @@ -38,6 +38,8 @@ public class TargetWindowLayout extends AbstractEntityWindowLayout * * @param i18n * I18N + * @param targetTypeManagement + * TargetTypeManagement */ public TargetWindowLayout(final VaadinMessageSource i18n, final TargetTypeManagement targetTypeManagement) { super(); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/MultipleTargetFilter.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/MultipleTargetFilter.java index d1d2a5834..c7a122cc4 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/MultipleTargetFilter.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/MultipleTargetFilter.java @@ -8,12 +8,9 @@ */ package org.eclipse.hawkbit.ui.management.targettag.filter; -import com.vaadin.ui.Accordion; -import com.vaadin.ui.Alignment; -import com.vaadin.ui.VerticalLayout; -import com.vaadin.ui.themes.ValoTheme; import java.util.Arrays; import java.util.List; + import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetTagManagement; @@ -40,6 +37,11 @@ import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.vaadin.spring.events.EventBus.UIEventBus; +import com.vaadin.ui.Accordion; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; + /** * Target filter tabsheet with 'simple' and 'complex' filter options. */ @@ -77,7 +79,8 @@ public class MultipleTargetFilter extends Accordion { this.filterByButtons = new TargetTagFilterButtons(uiDependencies, targetTagManagement, targetManagement, targetTagFilterLayoutUiState, targetTagWindowBuilder); - this.targetTypeFilterButtons = new TargetTypeFilterButtons(uiDependencies, targetTypeManagement, targetTagFilterLayoutUiState, targetTypeWindowBuilder); + this.targetTypeFilterButtons = new TargetTypeFilterButtons(uiDependencies, targetTypeManagement, targetManagement, + targetTagFilterLayoutUiState, targetTypeWindowBuilder); this.filterByStatusFooter = new FilterByStatusLayout(i18n, eventBus, targetTagFilterLayoutUiState); this.simpleFilterTab = buildSimpleFilterTab(); this.targetTypeFilterTab = buildTargetTypeFilterTab(); @@ -98,8 +101,7 @@ public class MultipleTargetFilter extends Accordion { this.targetTypeGridActionsVisibilityListener = new GridActionsVisibilityListener(eventBus, layoutViewAware, targetTypeFilterButtons::hideActionColumns, targetTypeFilterButtons::showEditColumn, targetTypeFilterButtons::showDeleteColumn); this.entityTargetTypeModifiedListener = new EntityModifiedListener.Builder<>(eventBus, ProxyTargetType.class) - .parentEntityType(ProxyTarget.class).viewAware(layoutViewAware) - .entityModifiedAwareSupports(getTargetTypeModifiedAwareSupports()).build(); + .viewAware(layoutViewAware).entityModifiedAwareSupports(getTargetTypeModifiedAwareSupports()).build(); init(); addTabs(); @@ -165,7 +167,8 @@ public class MultipleTargetFilter extends Accordion { private List getTargetTypeModifiedAwareSupports() { return Arrays.asList(EntityModifiedGridRefreshAwareSupport.of(targetTypeFilterButtons::refreshAll), - EntityModifiedGenericSupport.of(null, null, null)); + EntityModifiedGenericSupport.of(null, targetTypeFilterButtons::resetFilterOnTargetTypeUpdated, + targetTypeFilterButtons::resetFilterOnTargetTypeDeleted)); } private List getFilterQueryModifiedAwareSupports() { @@ -191,9 +194,9 @@ public class MultipleTargetFilter extends Accordion { public void selectedTabChanged() { final String selectedTabId = getTab(getSelectedTab()).getId(); - if (UIComponentIdProvider.SIMPLE_FILTER_ACCORDION_TAB.equals(selectedTabId)) { customFilterTab.clearAppliedTargetFilterQuery(); + targetTypeFilterButtons.clearAppliedTargetTypeFilter(); targetTagFilterLayoutUiState.setCustomFilterTabSelected(false); targetTagFilterLayoutUiState.setTargetTypeFilterTabSelected(false); @@ -201,13 +204,19 @@ public class MultipleTargetFilter extends Accordion { eventBus.publish(EventTopics.TARGET_FILTER_TAB_CHANGED, this, TargetFilterTabChangedEventPayload.SIMPLE); } if (UIComponentIdProvider.TARGET_TYPE_FILTER_ACCORDION_TAB.equals(selectedTabId)){ + customFilterTab.clearAppliedTargetFilterQuery(); + filterByButtons.clearTargetTagFilters(); + filterByStatusFooter.clearStatusAndOverdueFilters(); + targetTagFilterLayoutUiState.setTargetTypeFilterTabSelected(true); targetTagFilterLayoutUiState.setCustomFilterTabSelected(false); + eventBus.publish(EventTopics.TARGET_FILTER_TAB_CHANGED, this, TargetFilterTabChangedEventPayload.TARGET_TYPE); } if (UIComponentIdProvider.CUSTOM_FILTER_ACCORDION_TAB.equals(selectedTabId)){ filterByButtons.clearTargetTagFilters(); filterByStatusFooter.clearStatusAndOverdueFilters(); + targetTypeFilterButtons.clearAppliedTargetTypeFilter(); targetTagFilterLayoutUiState.setCustomFilterTabSelected(true); targetTagFilterLayoutUiState.setTargetTypeFilterTabSelected(false); @@ -223,20 +232,14 @@ public class MultipleTargetFilter extends Accordion { public void restoreState() { if (targetTagFilterLayoutUiState.isCustomFilterTabSelected()) { customFilterTab.restoreState(); - setSelectedTab(customFilterTab); - } - if (targetTagFilterLayoutUiState.isCustomFilterTabSelected()) { - filterByButtons.restoreState(); - filterByStatusFooter.restoreState(); - - setSelectedTab(simpleFilterTab); - } - if (targetTagFilterLayoutUiState.isTargetTypeFilterTabSelected()) { - filterByButtons.restoreState(); - filterByStatusFooter.restoreState(); - + } else if (targetTagFilterLayoutUiState.isTargetTypeFilterTabSelected()){ + targetTypeFilterButtons.restoreState(); setSelectedTab(targetTypeFilterTab); + } else { + filterByButtons.restoreState(); + filterByStatusFooter.restoreState(); + setSelectedTab(simpleFilterTab); } } @@ -246,6 +249,7 @@ public class MultipleTargetFilter extends Accordion { public void onViewEnter() { filterByButtons.reevaluateFilter(); customFilterTab.reevaluateFilter(); + targetTypeFilterButtons.reevaluateFilter(); } /** diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/TargetTagFilterLayoutUiState.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/TargetTagFilterLayoutUiState.java index 8b80cedab..4c606a694 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/TargetTagFilterLayoutUiState.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/TargetTagFilterLayoutUiState.java @@ -21,11 +21,19 @@ public class TargetTagFilterLayoutUiState extends TagFilterLayoutUiState { private static final long serialVersionUID = 1L; private Long clickedTargetFilterQueryId; + private Long clickedTargetTypeFilterId; private final Collection clickedTargetUpdateStatusFilters = new ArrayList<>(); private boolean isOverdueFilterClicked; private boolean isCustomFilterTabSelected; private boolean isTargetTypeFilterTabSelected; + public Long getClickedTargetTypeFilterId() { + return clickedTargetTypeFilterId; + } + + public void setClickedTargetTypeFilterId(Long clickedTargetTypeFilterId) { + this.clickedTargetTypeFilterId = clickedTargetTypeFilterId; + } /** * @return Id of clicked target filter query diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/TargetTypeFilterButtons.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/TargetTypeFilterButtons.java index 5775d01dd..4f84e5730 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/TargetTypeFilterButtons.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/TargetTypeFilterButtons.java @@ -8,11 +8,12 @@ */ package org.eclipse.hawkbit.ui.management.targettag.filter; +import com.vaadin.ui.Button; import com.vaadin.ui.UI; import com.vaadin.ui.Window; -import java.util.Collection; -import java.util.stream.Collectors; -import org.eclipse.hawkbit.repository.Identifiable; +import com.vaadin.ui.dnd.DropTargetExtension; +import com.vaadin.ui.dnd.event.DropEvent; +import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetTypeManagement; import org.eclipse.hawkbit.repository.exception.TargetTypeInUseException; import org.eclipse.hawkbit.ui.common.CommonUiDependencies; @@ -25,31 +26,59 @@ import org.eclipse.hawkbit.ui.common.event.EntityModifiedEventPayload; import org.eclipse.hawkbit.ui.common.event.EventTopics; import org.eclipse.hawkbit.ui.common.event.EventView; import org.eclipse.hawkbit.ui.common.filterlayout.AbstractTargetTypeFilterButtons; -import org.eclipse.hawkbit.ui.common.state.TagFilterLayoutUiState; +import org.eclipse.hawkbit.ui.common.grid.support.DragAndDropSupport; +import org.eclipse.hawkbit.ui.common.grid.support.assignment.AssignmentSupport; +import org.eclipse.hawkbit.ui.common.grid.support.assignment.TargetsToNoTargetTypeAssignmentSupport; +import org.eclipse.hawkbit.ui.common.grid.support.assignment.TargetsToTargetTypeAssignmentSupport; +import org.eclipse.hawkbit.ui.common.layout.listener.EntityDraggingListener; import org.eclipse.hawkbit.ui.management.targettag.targettype.TargetTypeWindowBuilder; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + /** * Target Type filter buttons table. */ @SuppressWarnings("squid:S2160") public class TargetTypeFilterButtons extends AbstractTargetTypeFilterButtons { private static final long serialVersionUID = 1L; - - private final transient TargetTypeManagement targetTypeManagement; - private final transient TargetTypeWindowBuilder targetTypeWindowBuilder; - private static final Logger LOG = LoggerFactory.getLogger(TargetTypeFilterButtons.class); - TargetTypeFilterButtons(final CommonUiDependencies uiDependencies, - final TargetTypeManagement targetTypeManagement, final TagFilterLayoutUiState tagFilterLayoutUiState, - final TargetTypeWindowBuilder targetTypeWindowBuilder) { - super(uiDependencies, tagFilterLayoutUiState); + private final transient TargetManagement targetManagement; + private final transient TargetTypeManagement targetTypeManagement; + private final transient TargetTypeWindowBuilder targetTypeWindowBuilder; + private final transient CommonUiDependencies uiDependencies; + private transient EntityDraggingListener draggingListener; + TargetTypeFilterButtons(final CommonUiDependencies uiDependencies, + final TargetTypeManagement targetTypeManagement, final TargetManagement targetManagement, final TargetTagFilterLayoutUiState targetTagFilterLayoutUiState, + final TargetTypeWindowBuilder targetTypeWindowBuilder) { + super(uiDependencies, targetTagFilterLayoutUiState, targetTypeManagement); + + this.targetManagement = targetManagement; this.targetTypeManagement = targetTypeManagement; this.targetTypeWindowBuilder = targetTypeWindowBuilder; + this.uiDependencies = uiDependencies; + + final Map> sourceTargetAssignmentStrategies = new HashMap<>(); + final TargetsToTargetTypeAssignmentSupport targetsToTargetTypeAssignment = new TargetsToTargetTypeAssignmentSupport(uiDependencies, + targetManagement); + + sourceTargetAssignmentStrategies.put(UIComponentIdProvider.TARGET_TABLE_ID, targetsToTargetTypeAssignment); + + setDropSupportToNoType(); + setDragAndDropSupportSupport(new DragAndDropSupport<>(this, i18n, uiNotification, + sourceTargetAssignmentStrategies, eventBus)); + getDragAndDropSupportSupport().ignoreSelection(true); + getDragAndDropSupportSupport().addDragAndDrop(); init(); setDataProvider( @@ -74,7 +103,17 @@ public class TargetTypeFilterButtons extends AbstractTargetTypeFilterButtons { @Override protected boolean deleteFilterButtons(Collection filterButtonsToDelete) { final ProxyTargetType targetTypeToDelete = filterButtonsToDelete.iterator().next(); - return deleteTargetType(targetTypeToDelete); + final String targetTypeToDeleteName = targetTypeToDelete.getName(); + final Long targetTypeToDeleteId = targetTypeToDelete.getId(); + + final Long clickedTargetTypeId = getFilterButtonClickBehaviour().getPreviouslyClickedFilterId(); + + if (clickedTargetTypeId != null && clickedTargetTypeId.equals(targetTypeToDeleteId)) { + uiNotification.displayValidationError(i18n.getMessage("message.targettype.delete", targetTypeToDeleteName)); + } else { + return deleteTargetType(targetTypeToDelete); + } + return false; } @Override @@ -113,12 +152,8 @@ public class TargetTypeFilterButtons extends AbstractTargetTypeFilterButtons { LOG.trace("Target type already in use exception: {}", exception.getMessage()); uiNotification.displayValidationError(i18n.getMessage(exception.getMessage())); } - return false; - } - @Override - protected Collection filterExistingTargetTypeIds(Collection targetTypeIds) { - return targetTypeManagement.get(targetTypeIds).stream().map(Identifiable::getId).collect(Collectors.toSet()); + return false; } @Override @@ -131,4 +166,43 @@ public class TargetTypeFilterButtons extends AbstractTargetTypeFilterButtons { return permissionChecker.hasUpdateRepositoryPermission(); } + private void setDropSupportToNoType() { + final TargetsToNoTargetTypeAssignmentSupport targetsToNoTargetTypeAssignmentSupport = new TargetsToNoTargetTypeAssignmentSupport( + uiDependencies, targetManagement); + + final DropTargetExtension