diff --git a/docs/content/ui.md b/docs/content/ui.md index e5cf6605b..63fa86ab1 100644 --- a/docs/content/ui.md +++ b/docs/content/ui.md @@ -108,6 +108,7 @@ Custom target filter overview and filter management. - Custom target filter allows user to filter targets by defining custom query. - Displays custom target filter list and user can search any particular filter. - Create, update and delete features are supported for target filters. +- Auto assignment of a distribution set to filtered targets. ### How to Filter The basic syntax to filter is: `fieldvalue fieldvalue <...>` @@ -139,3 +140,12 @@ name==CCU* and updatestatus==pending (updatestatus!=error or updatestatus!=pending) and (name==\*CCU\* or description==\*CCU\*) | Gives all targets that either have the term ‘CCU’ in their name or their description and that either have the _update status_ not in state error or pending. ![Target Filter Management view](../images/ui/target_filter.png) + +### Auto assignment +It is possible to assign some distribution set with different action types (_forced_ or _soft_) to all targets that belong to the corresponding custom target filter, including the ones, that are registered later on. + +In order to activate the auto-assignment, one should first click on _Auto assignment_ cell in Custom Filters table, and then check the corresponding checkbox. After that, the action type and distribution set for auto-assignment should be selected and confirmed. + +As long as the auto-assignment stays active, the scheduler will try to assign selected distribution set to corresponding custom filter targets, that have never seen it before. + +![Auto assignment](../images/ui/target_filter_auto_assignment.png) \ No newline at end of file diff --git a/docs/static/images/ui/target_filter_auto_assignment.png b/docs/static/images/ui/target_filter_auto_assignment.png new file mode 100644 index 000000000..2b195bd4c Binary files /dev/null and b/docs/static/images/ui/target_filter_auto_assignment.png differ diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java index 737b6b50f..c7cc34949 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java @@ -22,82 +22,98 @@ public enum SpServerError { /** * */ - SP_REPO_ENTITY_ALRREADY_EXISTS("hawkbit.server.error.repo.entitiyAlreayExists", "The given entity already exists in database"), + SP_REPO_ENTITY_ALRREADY_EXISTS("hawkbit.server.error.repo.entitiyAlreayExists", + "The given entity already exists in database"), /** * */ - SP_REPO_CONSTRAINT_VIOLATION("hawkbit.server.error.repo.constraintViolation", "The given entity cannot be saved due to Constraint Violation"), + SP_REPO_CONSTRAINT_VIOLATION("hawkbit.server.error.repo.constraintViolation", + "The given entity cannot be saved due to Constraint Violation"), /** * */ - SP_REPO_INVALID_TARGET_ADDRESS("hawkbit.server.error.repo.invalidTargetAddress", "The target address is not well formed"), + SP_REPO_INVALID_TARGET_ADDRESS("hawkbit.server.error.repo.invalidTargetAddress", + "The target address is not well formed"), /** * */ - SP_REPO_ENTITY_NOT_EXISTS("hawkbit.server.error.repo.entitiyNotFound", "The given entity does not exist in the repository"), + SP_REPO_ENTITY_NOT_EXISTS("hawkbit.server.error.repo.entitiyNotFound", + "The given entity does not exist in the repository"), /** * */ - SP_REPO_CONCURRENT_MODIFICATION("hawkbit.server.error.repo.concurrentModification", "The given entity has been changed by another user/session"), + SP_REPO_CONCURRENT_MODIFICATION("hawkbit.server.error.repo.concurrentModification", + "The given entity has been changed by another user/session"), /** * */ - SP_TARGET_ATTRIBUTES_INVALID("hawkbit.server.error.repo.invalidTargetAttributes", "The given target attributes are invalid"), + SP_TARGET_ATTRIBUTES_INVALID("hawkbit.server.error.repo.invalidTargetAttributes", + "The given target attributes are invalid"), /** * */ - SP_REST_SORT_PARAM_SYNTAX("hawkbit.server.error.rest.param.sortParamSyntax", "The given sort paramter is not well formed"), + SP_REST_SORT_PARAM_SYNTAX("hawkbit.server.error.rest.param.sortParamSyntax", + "The given sort paramter is not well formed"), /** * */ - SP_REST_RSQL_SEARCH_PARAM_SYNTAX("hawkbit.server.error.rest.param.rsqlParamSyntax", "The given search paramter is not well formed"), + SP_REST_RSQL_SEARCH_PARAM_SYNTAX("hawkbit.server.error.rest.param.rsqlParamSyntax", + "The given search paramter is not well formed"), /** * */ - SP_REST_RSQL_PARAM_INVALID_FIELD("hawkbit.server.error.rest.param.rsqlInvalidField", "The given search parameter field does not exist"), + SP_REST_RSQL_PARAM_INVALID_FIELD("hawkbit.server.error.rest.param.rsqlInvalidField", + "The given search parameter field does not exist"), /** * */ - SP_REST_SORT_PARAM_INVALID_FIELD("hawkbit.server.error.rest.param.invalidField", "The given sort parameter field does not exist"), + SP_REST_SORT_PARAM_INVALID_FIELD("hawkbit.server.error.rest.param.invalidField", + "The given sort parameter field does not exist"), /** * */ - SP_REST_SORT_PARAM_INVALID_DIRECTION("hawkbit.server.error.rest.param.invalidDirection", "The given sort parameter direction does not exist"), + SP_REST_SORT_PARAM_INVALID_DIRECTION("hawkbit.server.error.rest.param.invalidDirection", + "The given sort parameter direction does not exist"), /** * */ - SP_REST_BODY_NOT_READABLE("hawkbit.server.error.rest.body.notReadable", "The given request body is not well formed"), + SP_REST_BODY_NOT_READABLE("hawkbit.server.error.rest.body.notReadable", + "The given request body is not well formed"), /** * */ - SP_ARTIFACT_UPLOAD_FAILED("hawkbit.server.error.artifact.uploadFailed", "Upload of artifact failed with internal server error."), + SP_ARTIFACT_UPLOAD_FAILED("hawkbit.server.error.artifact.uploadFailed", + "Upload of artifact failed with internal server error."), /** * */ - SP_ARTIFACT_UPLOAD_FAILED_MD5_MATCH("hawkbit.server.error.artifact.uploadFailed.checksum.md5.match", "Upload of artifact failed as the provided MD5 checksum did not match with the provided artifact."), + SP_ARTIFACT_UPLOAD_FAILED_MD5_MATCH("hawkbit.server.error.artifact.uploadFailed.checksum.md5.match", + "Upload of artifact failed as the provided MD5 checksum did not match with the provided artifact."), /** * */ - SP_ARTIFACT_UPLOAD_FAILED_SHA1_MATCH("hawkbit.server.error.artifact.uploadFailed.checksum.sha1.match", "Upload of artifact failed as the provided SHA1 checksum did not match with the provided artifact."), + SP_ARTIFACT_UPLOAD_FAILED_SHA1_MATCH("hawkbit.server.error.artifact.uploadFailed.checksum.sha1.match", + "Upload of artifact failed as the provided SHA1 checksum did not match with the provided artifact."), /** * */ - SP_DS_CREATION_FAILED_MISSING_MODULE("hawkbit.server.error.distributionset.creationFailed.missingModule", "Creation if Distribution Set failed as module is missing that is configured as mandatory."), + SP_DS_CREATION_FAILED_MISSING_MODULE("hawkbit.server.error.distributionset.creationFailed.missingModule", + "Creation if Distribution Set failed as module is missing that is configured as mandatory."), /** * @@ -107,12 +123,14 @@ public enum SpServerError { /** * */ - SP_ARTIFACT_DELETE_FAILED("hawkbit.server.error.artifact.deleteFailed", "Deletion of artifact failed with internal server error."), + SP_ARTIFACT_DELETE_FAILED("hawkbit.server.error.artifact.deleteFailed", + "Deletion of artifact failed with internal server error."), /** * */ - SP_ARTIFACT_LOAD_FAILED("hawkbit.server.error.artifact.loadFailed", "Load of artifact failed with internal server error."), + SP_ARTIFACT_LOAD_FAILED("hawkbit.server.error.artifact.loadFailed", + "Load of artifact failed with internal server error."), /** * @@ -123,33 +141,39 @@ public enum SpServerError { * error message, which describes that the action can not be canceled cause * the action is inactive. */ - SP_ACTION_NOT_CANCELABLE("hawkbit.server.error.action.notcancelable", "Only active actions which are in status pending are canceable."), + SP_ACTION_NOT_CANCELABLE("hawkbit.server.error.action.notcancelable", + "Only active actions which are in status pending are canceable."), /** * error message, which describes that the action can not be force quit * cause the action is inactive. */ - SP_ACTION_NOT_FORCE_QUITABLE("hawkbit.server.error.action.notforcequitable", "Only active actions which are in status pending can be force quit."), + SP_ACTION_NOT_FORCE_QUITABLE("hawkbit.server.error.action.notforcequitable", + "Only active actions which are in status pending can be force quit."), /** * */ - SP_DS_INCOMPLETE("hawkbit.server.error.distributionset.incomplete", "Distribution set is assigned to a a target that is incomplete (i.e. mandatory modules are missing)"), + SP_DS_INCOMPLETE("hawkbit.server.error.distributionset.incomplete", + "Distribution set is assigned to a target that is incomplete (i.e. mandatory modules are missing)"), /** * */ - SP_DS_TYPE_UNDEFINED("hawkbit.server.error.distributionset.type.undefined", "Distribution set type is not yet defined. Modules cannot be added until definition."), + SP_DS_TYPE_UNDEFINED("hawkbit.server.error.distributionset.type.undefined", + "Distribution set type is not yet defined. Modules cannot be added until definition."), /** * */ - SP_DS_MODULE_UNSUPPORTED("hawkbit.server.error.distributionset.modules.unsupported", "Distribution set type does not contain the given module, i.e. is incompatible."), + SP_DS_MODULE_UNSUPPORTED("hawkbit.server.error.distributionset.modules.unsupported", + "Distribution set type does not contain the given module, i.e. is incompatible."), /** * */ - SP_REPO_TENANT_NOT_EXISTS("hawkbit.server.error.repo.tenantNotExists", "The entity cannot be inserted due the tenant does not exists"), + SP_REPO_TENANT_NOT_EXISTS("hawkbit.server.error.repo.tenantNotExists", + "The entity cannot be inserted due the tenant does not exists"), /** * @@ -159,12 +183,14 @@ public enum SpServerError { /** * */ - SP_REPO_ENTITY_READ_ONLY("hawkbit.server.error.entityreadonly", "The given entity is read only and the change cannot be completed."), + SP_REPO_ENTITY_READ_ONLY("hawkbit.server.error.entityreadonly", + "The given entity is read only and the change cannot be completed."), /** * */ - SP_CONFIGURATION_VALUE_INVALID("hawkbit.server.error.configValueInvalid", "The given configuration value is invalid."), + SP_CONFIGURATION_VALUE_INVALID("hawkbit.server.error.configValueInvalid", + "The given configuration value is invalid."), /** * */ @@ -173,22 +199,40 @@ public enum SpServerError { /** * */ - SP_ROLLOUT_ILLEGAL_STATE("hawkbit.server.error.rollout.illegalstate", "The rollout is in the wrong state for the requested operation"), + SP_ROLLOUT_ILLEGAL_STATE("hawkbit.server.error.rollout.illegalstate", + "The rollout is in the wrong state for the requested operation"), /** * */ - SP_ROLLOUT_VERIFICATION_FAILED("hawkbit.server.error.rollout.verificationFailed", "The rollout configuration could not be verified successfully"), + SP_ROLLOUT_VERIFICATION_FAILED("hawkbit.server.error.rollout.verificationFailed", + "The rollout configuration could not be verified successfully"), /** * */ - SP_REPO_OPERATION_NOT_SUPPORTED("hawkbit.server.error.operation.notSupported", "Operation or method is (no longer) supported by service."), + SP_REPO_OPERATION_NOT_SUPPORTED("hawkbit.server.error.operation.notSupported", + "Operation or method is (no longer) supported by service."), /** * Error message informing that the maintenance schedule is invalid. */ - SP_MAINTENANCE_SCHEDULE_INVALID("hawkbit.server.error.maintenanceScheduleInvalid", "Information for schedule, duration or timezone is missing; or there is no valid maintenance window available in future."); + SP_MAINTENANCE_SCHEDULE_INVALID("hawkbit.server.error.maintenanceScheduleInvalid", + "Information for schedule, duration or timezone is missing; or there is no valid maintenance window available in future."), + + /** + * Error message informing that the action type for auto-assignment is + * invalid. + */ + SP_AUTO_ASSIGN_ACTION_TYPE_INVALID("hawkbit.server.error.repo.invalidAutoAssignActionType", + "The given action type for auto-assignment is invalid: allowed values are FORCED and SOFT"), + + /** + * Error message informing that the distribution set for auto-assignment is + * invalid. + */ + SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID("hawkbit.server.error.repo.invalidAutoAssignDistributionSet", + "The given distribution set for auto-assignment is invalid: it is either incomplete (i.e. mandatory modules are missing) or soft deleted"); private final String key; private final String message; diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryFields.java index 94c02fd83..7378589a2 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryFields.java @@ -30,7 +30,7 @@ public enum TargetFilterQueryFields implements FieldNameProvider { NAME("name"), /** - * distribution set which is set as auto assign distribution set + * Distribution set for auto-assignment. */ AUTOASSIGNDISTRIBUTIONSET("autoAssignDistributionSet", "name", "version"); diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RepositoryManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RepositoryManagement.java index f5a3a53c6..a210703a1 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RepositoryManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RepositoryManagement.java @@ -23,7 +23,6 @@ import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.model.BaseEntity; -import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -56,7 +55,7 @@ public interface RepositoryManagement { List create(@NotNull @Valid Collection creates); /** - * Creates new {@link SoftwareModuleType}. + * Creates new {@link BaseEntity}. * * @param create * to create diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java index dee780e33..d23c2eea9 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java @@ -18,9 +18,12 @@ import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryUpdate; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException; +import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException; import org.eclipse.hawkbit.repository.exception.QuotaExceededException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; +import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.springframework.data.domain.Page; @@ -213,13 +216,14 @@ public interface TargetFilterQueryManagement { TargetFilterQuery update(@NotNull @Valid TargetFilterQueryUpdate update); /** - * Updates the the auto-assign {@link DistributionSet} of the addressed - * {@link TargetFilterQuery}. + * Updates the the auto-assign {@link DistributionSet} and sets default + * (FORCED) {@link ActionType} of the addressed {@link TargetFilterQuery}. * * @param queryId * of the target filter query to be updated * @param dsId * to be updated or null in order to remove it + * together with the auto-assign {@link ActionType} * * @return the updated {@link TargetFilterQuery} * @@ -230,8 +234,47 @@ public interface TargetFilterQueryManagement { * @throws QuotaExceededException * if the query that is already associated with this filter * query addresses too many targets (auto-assignments only) + * + * @throws InvalidAutoAssignDistributionSetException + * if the provided auto-assign {@link DistributionSet} is not + * valid (incomplete or soft deleted) */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) - TargetFilterQuery updateAutoAssignDS(long queryId, Long dsId); + default TargetFilterQuery updateAutoAssignDS(final long queryId, final Long dsId) { + return updateAutoAssignDSWithActionType(queryId, dsId, null); + } + /** + * Updates the the auto-assign {@link DistributionSet} and + * {@link ActionType} of the addressed {@link TargetFilterQuery}. + * + * @param queryId + * of the target filter query to be updated + * @param dsId + * to be updated or null in order to remove it + * together with the auto-assign {@link ActionType} + * @param actionType + * to be updated or null for default (FORCED) if + * distribution set Id is present + * + * @return the updated {@link TargetFilterQuery} + * + * @throws EntityNotFoundException + * if either {@link TargetFilterQuery} and/or autoAssignDs are + * provided but not found + * + * @throws QuotaExceededException + * if the query that is already associated with this filter + * query addresses too many targets (auto-assignments only) + * + * @throws InvalidAutoAssignActionTypeException + * if the provided auto-assign {@link ActionType} is not valid + * (neither FORCED, nor SOFT) + * + * @throws InvalidAutoAssignDistributionSetException + * if the provided auto-assign {@link DistributionSet} is not + * valid (incomplete or soft deleted) + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) + TargetFilterQuery updateAutoAssignDSWithActionType(long queryId, Long dsId, ActionType actionType); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetFilterQueryCreate.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetFilterQueryCreate.java index 93527350e..187bfbf14 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetFilterQueryCreate.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetFilterQueryCreate.java @@ -13,6 +13,7 @@ import java.util.Optional; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.BaseEntity; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.NamedEntity; @@ -40,20 +41,27 @@ public interface TargetFilterQueryCreate { TargetFilterQueryCreate query(@Size(min = 1, max = TargetFilterQuery.QUERY_MAX_SIZE) @NotNull String query); /** - * @param set + * @param distributionSet * for {@link TargetFilterQuery#getAutoAssignDistributionSet()} * @return updated builder instance */ - default TargetFilterQueryCreate set(final DistributionSet set) { - return set(Optional.ofNullable(set).map(DistributionSet::getId).orElse(null)); + default TargetFilterQueryCreate autoAssignDistributionSet(final DistributionSet distributionSet) { + return autoAssignDistributionSet(Optional.ofNullable(distributionSet).map(DistributionSet::getId).orElse(null)); } /** - * @param setId + * @param dsId * for {@link TargetFilterQuery#getAutoAssignDistributionSet()} * @return updated builder instance */ - TargetFilterQueryCreate set(long setId); + TargetFilterQueryCreate autoAssignDistributionSet(Long dsId); + + /** + * @param actionType + * for {@link TargetFilterQuery#getAutoAssignActionType()} + * @return updated builder instance + */ + TargetFilterQueryCreate autoAssignActionType(ActionType actionType); /** * @return peek on current state of {@link TargetFilterQuery} in the builder diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/EntityNotFoundException.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/EntityNotFoundException.java index 6c9bdbbb8..c292a6576 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/EntityNotFoundException.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/EntityNotFoundException.java @@ -88,7 +88,7 @@ public class EntityNotFoundException extends AbstractServerRtException { * for the {@link MetaData} entry */ public EntityNotFoundException(final Class type, final Long entityId, final String key) { - this(type.getSimpleName() + " for given entity {" + entityId + "} and with key {" + key + "} does not exist."); + this(type, String.valueOf(entityId), key); } /** @@ -96,13 +96,13 @@ public class EntityNotFoundException extends AbstractServerRtException { * * @param type * of the entity that was not found - * @param enityId + * @param entityId * of the {@link BaseEntity} the {@link MetaData} was for * @param key * for the {@link MetaData} entry */ - public EntityNotFoundException(final Class type, final String enityId, final String key) { - this(type.getSimpleName() + " for given entity {" + enityId + "} and with key {" + key + "} does not exist."); + public EntityNotFoundException(final Class type, final String entityId, final String key) { + this(type.getSimpleName() + " for given entity {" + entityId + "} and with key {" + key + "} does not exist."); } /** diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/InvalidAutoAssignActionTypeException.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/InvalidAutoAssignActionTypeException.java new file mode 100644 index 000000000..6db9b901b --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/InvalidAutoAssignActionTypeException.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2018 Bosch Software Innovations 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.exception; + +import org.eclipse.hawkbit.exception.AbstractServerRtException; +import org.eclipse.hawkbit.exception.SpServerError; + +/** + * Thrown if an action type for auto-assignment is neither 'forced', nor 'soft'. + */ +public class InvalidAutoAssignActionTypeException extends AbstractServerRtException { + + private static final long serialVersionUID = 1L; + private static final SpServerError THIS_ERROR = SpServerError.SP_AUTO_ASSIGN_ACTION_TYPE_INVALID; + + /** + * Default constructor. + */ + public InvalidAutoAssignActionTypeException() { + super(THIS_ERROR); + } +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/InvalidAutoAssignDistributionSetException.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/InvalidAutoAssignDistributionSetException.java new file mode 100644 index 000000000..2c798d286 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/InvalidAutoAssignDistributionSetException.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2019 Bosch Software Innovations 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.exception; + +import org.eclipse.hawkbit.exception.AbstractServerRtException; +import org.eclipse.hawkbit.exception.SpServerError; + +/** + * Thrown if a distribution set for auto-assignment is incomplete (i.e. + * mandatory modules are missing) or soft deleted. + */ +public class InvalidAutoAssignDistributionSetException extends AbstractServerRtException { + + private static final long serialVersionUID = 1L; + private static final SpServerError THIS_ERROR = SpServerError.SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID; + + /** + * Default constructor. + */ + public InvalidAutoAssignDistributionSetException() { + super(THIS_ERROR); + } +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetFilter.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetFilter.java index da8c68347..27565c305 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetFilter.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetFilter.java @@ -22,6 +22,7 @@ public final class DistributionSetFilter { private Boolean isComplete; private DistributionSetType type; private String searchText; + private String filterString; private Boolean selectDSWithNoTag; private Collection tagNames; private String assignedTargetId; @@ -61,6 +62,11 @@ public final class DistributionSetFilter { return this; } + public DistributionSetFilterBuilder setFilterString(final String filterString) { + this.filterString = filterString; + return this; + } + public DistributionSetFilterBuilder setSelectDSWithNoTag(final Boolean selectDSWithNoTag) { this.selectDSWithNoTag = selectDSWithNoTag; return this; @@ -82,6 +88,7 @@ public final class DistributionSetFilter { private final Boolean isComplete; private final DistributionSetType type; private final String searchText; + private final String filterString; private final Boolean selectDSWithNoTag; private final Collection tagNames; private final String assignedTargetId; @@ -99,6 +106,7 @@ public final class DistributionSetFilter { this.isComplete = builder.isComplete; this.type = builder.type; this.searchText = builder.searchText; + this.filterString = builder.filterString; this.selectDSWithNoTag = builder.selectDSWithNoTag; this.tagNames = builder.tagNames; this.assignedTargetId = builder.assignedTargetId; @@ -125,6 +133,10 @@ public final class DistributionSetFilter { return searchText; } + public String getFilterString() { + return filterString; + } + public Boolean getSelectDSWithNoTag() { return selectDSWithNoTag; } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java index 931031603..d8f7ab2ca 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java @@ -8,6 +8,12 @@ */ package org.eclipse.hawkbit.repository.model; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Set; + +import org.eclipse.hawkbit.repository.model.Action.ActionType; + /** * Managed filter entity. * @@ -38,6 +44,12 @@ public interface TargetFilterQuery extends TenantAwareBaseEntity { */ int QUERY_MAX_SIZE = 1024; + /** + * Allowed values for auto-assign action type + */ + Set ALLOWED_AUTO_ASSIGN_ACTION_TYPES = Collections + .unmodifiableSet(EnumSet.of(ActionType.FORCED, ActionType.SOFT)); + /** * @return name of the {@link TargetFilterQuery}. */ @@ -53,4 +65,9 @@ public interface TargetFilterQuery extends TenantAwareBaseEntity { */ DistributionSet getAutoAssignDistributionSet(); + /** + * @return the auto assign {@link ActionType} if given. + */ + ActionType getAutoAssignActionType(); + } diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractTargetFilterQueryUpdateCreate.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractTargetFilterQueryUpdateCreate.java index 1b4938901..c7693e464 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractTargetFilterQueryUpdateCreate.java +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractTargetFilterQueryUpdateCreate.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.builder; import java.util.Optional; import org.eclipse.hawkbit.repository.ValidString; +import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.springframework.util.StringUtils; /** @@ -26,15 +27,26 @@ public abstract class AbstractTargetFilterQueryUpdateCreate extends AbstractB @ValidString protected String query; - protected Long set; + protected Long distributionSetId; - public T set(final long set) { - this.set = set; + protected ActionType actionType; + + public T autoAssignDistributionSet(final Long distributionSetId) { + this.distributionSetId = distributionSetId; return (T) this; } - public Optional getSet() { - return Optional.ofNullable(set); + public Optional getAutoAssignDistributionSetId() { + return Optional.ofNullable(distributionSetId); + } + + public T autoAssignActionType(final ActionType actionType) { + this.actionType = actionType; + return (T) this; + } + + public Optional getAutoAssignActionType() { + return Optional.ofNullable(actionType); } public T name(final String name) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java index 76a96efa8..d89e85d9a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java @@ -267,7 +267,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { if (!assigned.isEmpty()) { final Long[] dsIds = assigned.toArray(new Long[assigned.size()]); distributionSetRepository.deleteDistributionSet(dsIds); - targetFilterQueryRepository.unsetAutoAssignDistributionSet(dsIds); + targetFilterQueryRepository.unsetAutoAssignDistributionSetAndActionType(dsIds); } // mark the rest as hard delete @@ -276,6 +276,8 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { // hard delete the rest if exists if (!toHardDelete.isEmpty()) { + targetFilterQueryRepository + .unsetAutoAssignDistributionSetAndActionType(toHardDelete.toArray(new Long[toHardDelete.size()])); // don't give the delete statement an empty list, JPA/Oracle cannot // handle the empty list distributionSetRepository.deleteByIdIn(toHardDelete); @@ -602,7 +604,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { private static List> buildDistributionSetSpecifications( final DistributionSetFilter distributionSetFilter) { - final List> specList = Lists.newArrayListWithExpectedSize(7); + final List> specList = Lists.newArrayListWithExpectedSize(8); Specification spec; @@ -626,6 +628,14 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { specList.add(spec); } + if (!StringUtils.isEmpty(distributionSetFilter.getFilterString())) { + final String[] dsFilterNameAndVersionEntries = getDsFilterNameAndVersionEntries( + distributionSetFilter.getFilterString().trim()); + spec = DistributionSetSpecification.likeNameAndVersion(dsFilterNameAndVersionEntries[0], + dsFilterNameAndVersionEntries[1]); + specList.add(spec); + } + if (isDSWithNoTagSelected(distributionSetFilter) || isTagsSelected(distributionSetFilter)) { spec = DistributionSetSpecification.hasTags(distributionSetFilter.getTagNames(), distributionSetFilter.getSelectDSWithNoTag()); @@ -642,6 +652,19 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { return specList; } + // the format of filter string is 'name:version'. 'name' and 'version' + // fields follow the starts_with semantic, that changes to equal for 'name' + // field when the semicolon is present + private static String[] getDsFilterNameAndVersionEntries(final String filterString) { + final int semicolonIndex = filterString.indexOf(':'); + + final String dsFilterName = semicolonIndex != -1 ? filterString.substring(0, semicolonIndex) + : (filterString + "%"); + final String dsFilterVersion = semicolonIndex != -1 ? (filterString.substring(semicolonIndex + 1) + "%") : "%"; + + return new String[] { !StringUtils.isEmpty(dsFilterName) ? dsFilterName : "%", dsFilterVersion }; + } + private void assertDistributionSetIsNotAssignedToTargets(final Long distributionSet) { if (actionRepository.countByDistributionSetId(distributionSet) > 0) { throw new EntityReadOnlyException(String.format( diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java index 7a11ed34e..c3b767077 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java @@ -22,6 +22,8 @@ import org.eclipse.hawkbit.repository.builder.GenericTargetFilterQueryUpdate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryUpdate; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException; +import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException; import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetFilterQueryCreate; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; @@ -30,6 +32,7 @@ import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; +import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; @@ -88,7 +91,9 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme // enforce the 'max targets per auto assign' quota right here even if // the result of the filter query can vary over time - create.getSet().flatMap(set -> create.getQuery()).ifPresent(this::assertMaxTargetsQuota); + if (create.getAutoAssignDistributionSetId().isPresent()) { + create.getQuery().ifPresent(this::assertMaxTargetsQuota); + } return targetFilterQueryRepository.save(create.build()); } @@ -217,24 +222,53 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme @Override @Transactional - public TargetFilterQuery updateAutoAssignDS(final long queryId, final Long dsId) { + public TargetFilterQuery updateAutoAssignDSWithActionType(final long queryId, final Long dsId, + final ActionType actionType) { final JpaTargetFilterQuery targetFilterQuery = findTargetFilterQueryOrThrowExceptionIfNotFound(queryId); - targetFilterQuery.setAutoAssignDistributionSet( - Optional.ofNullable(dsId).map(this::findDistributionSetAndThrowExceptionIfNotFound).orElse(null)); - - // we cannot be sure that the quota was enforced at creation time - // because the Target Filter Query REST API does not allow to specify an - // auto-assign distribution set when creating a target filter query - if (dsId != null) { + if (dsId == null) { + targetFilterQuery.setAutoAssignDistributionSet(null); + targetFilterQuery.setAutoAssignActionType(null); + } else { + // we cannot be sure that the quota was enforced at creation time + // because the Target Filter Query REST API does not allow to + // specify an + // auto-assign distribution set when creating a target filter query assertMaxTargetsQuota(targetFilterQuery.getQuery()); + + final JpaDistributionSet distributionSetToAutoAssign = findDistributionSetAndThrowExceptionIfNotFound(dsId); + // must be completed and not soft deleted + verifyDistributionSetAndThrowExceptionIfNotValid(distributionSetToAutoAssign); + + targetFilterQuery.setAutoAssignDistributionSet(distributionSetToAutoAssign); + // the action type is set to FORCED per default (when not explicitly + // specified) + targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(actionType)); } return targetFilterQueryRepository.save(targetFilterQuery); } + private static void verifyDistributionSetAndThrowExceptionIfNotValid(final DistributionSet distributionSet) { + if (!distributionSet.isComplete() || distributionSet.isDeleted()) { + throw new InvalidAutoAssignDistributionSetException(); + } + } + + private static ActionType sanitizeAutoAssignActionType(final ActionType actionType) { + if (actionType == null) { + return ActionType.FORCED; + } + + if (!TargetFilterQuery.ALLOWED_AUTO_ASSIGN_ACTION_TYPES.contains(actionType)) { + throw new InvalidAutoAssignActionTypeException(); + } + + return actionType; + } + private JpaDistributionSet findDistributionSetAndThrowExceptionIfNotFound(final Long setId) { - return (JpaDistributionSet) distributionSetManagement.getWithDetails(setId) + return (JpaDistributionSet) distributionSetManagement.get(setId) .orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, setId)); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryRepository.java index ab05474ad..cc3d70895 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryRepository.java @@ -45,15 +45,16 @@ public interface TargetFilterQueryRepository Page findAll(); /** - * Sets the auto assign distribution sets to null which match the ds ids. + * Sets the auto assign distribution sets and action types to null which + * match the ds ids. * * @param dsIds * distribution set ids to be set to null */ @Modifying @Transactional - @Query("update JpaTargetFilterQuery d set d.autoAssignDistributionSet = NULL where d.autoAssignDistributionSet in :ids") - void unsetAutoAssignDistributionSet(@Param("ids") Long... dsIds); + @Query("update JpaTargetFilterQuery d set d.autoAssignDistributionSet = NULL, d.autoAssignActionType = NULL where d.autoAssignDistributionSet in :ids") + void unsetAutoAssignDistributionSetAndActionType(@Param("ids") Long... dsIds); /** * Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java index a0c1c080e..6f98f0460 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java @@ -17,7 +17,8 @@ import org.eclipse.hawkbit.exception.AbstractServerRtException; import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetManagement; -import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper; +import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.RepositoryModelConstants; import org.eclipse.hawkbit.repository.model.Target; @@ -28,12 +29,9 @@ import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.DefaultTransactionDefinition; -import org.springframework.transaction.support.TransactionTemplate; /** * Checks if targets need a new distribution set (DS) based on the target filter @@ -52,7 +50,7 @@ public class AutoAssignChecker { private final DeploymentManagement deploymentManagement; - private final TransactionTemplate transactionTemplate; + private final PlatformTransactionManager transactionManager; /** * Maximum for target filter queries with auto assign DS Maximum for targets @@ -84,13 +82,7 @@ public class AutoAssignChecker { this.targetFilterQueryManagement = targetFilterQueryManagement; this.targetManagement = targetManagement; this.deploymentManagement = deploymentManagement; - - final DefaultTransactionDefinition def = new DefaultTransactionDefinition(); - def.setName("autoAssignDSToTargets"); - def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - def.setReadOnly(false); - def.setIsolationLevel(Isolation.READ_COMMITTED.value()); - transactionTemplate = new TransactionTemplate(transactionManager, def); + this.transactionManager = transactionManager; } /** @@ -104,8 +96,7 @@ public class AutoAssignChecker { final PageRequest pageRequest = PageRequest.of(0, PAGE_SIZE); - final Page filterQueries = targetFilterQueryManagement - .findWithAutoAssignDS(pageRequest); + final Page filterQueries = targetFilterQueryManagement.findWithAutoAssignDS(pageRequest); for (final TargetFilterQuery filterQuery : filterQueries) { checkByTargetFilterQueryAndAssignDS(filterQuery); @@ -149,15 +140,17 @@ public class AutoAssignChecker { */ private int runTransactionalAssignment(final TargetFilterQuery targetFilterQuery, final Long dsId) { final String actionMessage = String.format(ACTION_MESSAGE, targetFilterQuery.getName()); - return transactionTemplate.execute(status -> { - final List targets = getTargetsWithActionType(targetFilterQuery.getQuery(), dsId, - PAGE_SIZE); - final int count = targets.size(); - if (count > 0) { - deploymentManagement.assignDistributionSet(dsId, targets, actionMessage); - } - return count; - }); + + return DeploymentHelper.runInNewTransaction(transactionManager, "autoAssignDSToTargets", + Isolation.READ_COMMITTED.value(), status -> { + final List targets = getTargetsWithActionType(targetFilterQuery.getQuery(), + dsId, targetFilterQuery.getAutoAssignActionType(), PAGE_SIZE); + final int count = targets.size(); + if (count > 0) { + deploymentManagement.assignDistributionSet(dsId, targets, actionMessage); + } + return count; + }); } /** @@ -169,17 +162,22 @@ public class AutoAssignChecker { * @param dsId * dsId the targets are not allowed to have in their action * history + * @param type + * action type for targets auto assignment * @param count * maximum amount of targets to retrieve * @return list of targets with action type */ private List getTargetsWithActionType(final String targetFilterQuery, final Long dsId, - final int count) { - final Page targets = targetManagement - .findByTargetFilterQueryAndNonDS(PageRequest.of(0, count), dsId, targetFilterQuery); + final ActionType type, final int count) { + final Page targets = targetManagement.findByTargetFilterQueryAndNonDS(PageRequest.of(0, count), dsId, + targetFilterQuery); + // the action type is set to FORCED per default (when not explicitly + // specified) + final ActionType autoAssignActionType = type == null ? ActionType.FORCED : type; return targets.getContent().stream().map(t -> new TargetWithActionType(t.getControllerId(), - Action.ActionType.FORCED, RepositoryModelConstants.NO_FORCE_TIME)).collect(Collectors.toList()); + autoAssignActionType, RepositoryModelConstants.NO_FORCE_TIME)).collect(Collectors.toList()); } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetFilterQueryCreate.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetFilterQueryCreate.java index 96f06379d..163aa6efb 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetFilterQueryCreate.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetFilterQueryCreate.java @@ -12,8 +12,11 @@ import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.builder.AbstractTargetFilterQueryUpdateCreate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; +import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; /** * Create/build implementation. @@ -32,7 +35,8 @@ public class JpaTargetFilterQueryCreate extends AbstractTargetFilterQueryUpdateC public JpaTargetFilterQuery build() { return new JpaTargetFilterQuery(name, query, - getSet().map(this::findDistributionSetAndThrowExceptionIfNotFound).orElse(null)); + getAutoAssignDistributionSetId().map(this::findDistributionSetAndThrowExceptionIfNotFound).orElse(null), + getAutoAssignActionType().filter(JpaTargetFilterQueryCreate::isAutoAssignActionTypeValid).orElse(null)); } private DistributionSet findDistributionSetAndThrowExceptionIfNotFound(final Long setId) { @@ -40,4 +44,12 @@ public class JpaTargetFilterQueryCreate extends AbstractTargetFilterQueryUpdateC .orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, setId)); } + private static boolean isAutoAssignActionTypeValid(final ActionType actionType) { + if (!TargetFilterQuery.ALLOWED_AUTO_ASSIGN_ACTION_TYPES.contains(actionType)) { + throw new InvalidAutoAssignActionTypeException(); + } + + return true; + } + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java index 6755bf5a6..693247dd0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java @@ -23,10 +23,15 @@ import javax.validation.constraints.Size; import org.eclipse.hawkbit.repository.event.remote.TargetFilterQueryDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetFilterQueryCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetFilterQueryUpdatedEvent; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder; +import org.eclipse.persistence.annotations.ConversionValue; +import org.eclipse.persistence.annotations.Convert; +import org.eclipse.persistence.annotations.ObjectTypeConverter; import org.eclipse.persistence.descriptors.DescriptorEvent; /** @@ -41,7 +46,7 @@ import org.eclipse.persistence.descriptors.DescriptorEvent; @SuppressWarnings("squid:S2160") public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity implements TargetFilterQuery, EventAwareEntity { - private static final long serialVersionUID = 7493966984413479089L; + private static final long serialVersionUID = 1L; @Column(name = "name", length = NamedEntity.NAME_MAX_SIZE, nullable = false) @Size(max = NamedEntity.NAME_MAX_SIZE) @@ -57,6 +62,13 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity @JoinColumn(name = "auto_assign_distribution_set", nullable = true, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_filter_auto_assign_ds")) private JpaDistributionSet autoAssignDistributionSet; + @Column(name = "auto_assign_action_type", nullable = true) + @ObjectTypeConverter(name = "autoAssignActionType", objectType = Action.ActionType.class, dataType = Integer.class, conversionValues = { + @ConversionValue(objectValue = "FORCED", dataValue = "0"), + @ConversionValue(objectValue = "SOFT", dataValue = "1") }) + @Convert("autoAssignActionType") + private ActionType autoAssignActionType; + public JpaTargetFilterQuery() { // Default constructor for JPA. } @@ -70,12 +82,15 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity * of the {@link TargetFilterQuery}. * @param autoAssignDistributionSet * of the {@link TargetFilterQuery}. + * @param autoAssignActionType + * of the {@link TargetFilterQuery}. */ - public JpaTargetFilterQuery(final String name, final String query, - final DistributionSet autoAssignDistributionSet) { + public JpaTargetFilterQuery(final String name, final String query, final DistributionSet autoAssignDistributionSet, + final ActionType autoAssignActionType) { this.name = name; this.query = query; this.autoAssignDistributionSet = (JpaDistributionSet) autoAssignDistributionSet; + this.autoAssignActionType = autoAssignActionType; } @Override @@ -105,6 +120,15 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity this.autoAssignDistributionSet = distributionSet; } + @Override + public ActionType getAutoAssignActionType() { + return autoAssignActionType; + } + + public void setAutoAssignActionType(final ActionType actionType) { + this.autoAssignActionType = actionType; + } + @Override public void fireCreateEvent(final DescriptorEvent descriptorEvent) { EventPublisherHolder.getInstance().getEventPublisher().publishEvent( diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetSpecification.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetSpecification.java index b7a63765d..828554271 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetSpecification.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetSpecification.java @@ -120,6 +120,22 @@ public final class DistributionSetSpecification { cb.like(cb.lower(targetRoot. get(JpaDistributionSet_.description)), subString.toLowerCase())); } + /** + * {@link Specification} for retrieving {@link DistributionSet}s by "like + * name and like version". + * + * @param name + * to be filtered on + * @param version + * to be filtered on + * @return the {@link DistributionSet} {@link Specification} + */ + public static Specification likeNameAndVersion(final String name, final String version) { + return (targetRoot, query, cb) -> cb.and( + cb.like(cb.lower(targetRoot. get(JpaDistributionSet_.name)), name.toLowerCase()), + cb.like(cb.lower(targetRoot. get(JpaDistributionSet_.version)), version.toLowerCase())); + } + /** * {@link Specification} for retrieving {@link DistributionSet}s by "has at * least one of the given tag names". diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/utils/DeploymentHelper.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/utils/DeploymentHelper.java index cd248f9ca..d2fea0f27 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/utils/DeploymentHelper.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/utils/DeploymentHelper.java @@ -22,6 +22,7 @@ import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; @@ -83,10 +84,30 @@ public final class DeploymentHelper { */ public static T runInNewTransaction(@NotNull final PlatformTransactionManager txManager, final String transactionName, @NotNull final TransactionCallback action) { + return runInNewTransaction(txManager, transactionName, Isolation.DEFAULT.value(), action); + } + + /** + * Executes the modifying action in new transaction + * + * @param txManager + * transaction manager interface + * @param transactionName + * the name of the new transaction + * @param isolationLevel + * isolation level of the new transaction + * @param action + * the callback to execute in new tranaction + * + * @return the result of the action + */ + public static T runInNewTransaction(@NotNull final PlatformTransactionManager txManager, + final String transactionName, final int isolationLevel, @NotNull final TransactionCallback action) { final DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setName(transactionName); def.setReadOnly(false); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + def.setIsolationLevel(isolationLevel); return new TransactionTemplate(txManager, def).execute(action); } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/DB2/V1_12_11__add_auto_assign_action_type___DB2.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/DB2/V1_12_11__add_auto_assign_action_type___DB2.sql new file mode 100644 index 000000000..9c6b67bf3 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/DB2/V1_12_11__add_auto_assign_action_type___DB2.sql @@ -0,0 +1 @@ +ALTER TABLE sp_target_filter_query ADD COLUMN auto_assign_action_type INTEGER; \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_12_11__add_auto_assign_action_type___H2.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_12_11__add_auto_assign_action_type___H2.sql new file mode 100644 index 000000000..16cb03ef2 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_12_11__add_auto_assign_action_type___H2.sql @@ -0,0 +1 @@ +ALTER TABLE sp_target_filter_query ADD COLUMN auto_assign_action_type integer; \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_12_11__add_auto_assign_action_type___MYSQL.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_12_11__add_auto_assign_action_type___MYSQL.sql new file mode 100644 index 000000000..16cb03ef2 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_12_11__add_auto_assign_action_type___MYSQL.sql @@ -0,0 +1 @@ +ALTER TABLE sp_target_filter_query ADD COLUMN auto_assign_action_type integer; \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/SQL_SERVER/V1_12_11__add_auto_assign_action_type___SQL_SERVER.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/SQL_SERVER/V1_12_11__add_auto_assign_action_type___SQL_SERVER.sql new file mode 100644 index 000000000..0e8c37bda --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/SQL_SERVER/V1_12_11__add_auto_assign_action_type___SQL_SERVER.sql @@ -0,0 +1 @@ +ALTER TABLE sp_target_filter_query ADD auto_assign_action_type INTEGER; \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetManagementTest.java index 26807130b..cbd2d5f7e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetManagementTest.java @@ -14,10 +14,12 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.validation.ConstraintViolationException; @@ -627,9 +629,10 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest { .create(entityFactory.tag().create().name("DistributionSetTag-C")); distributionSetTagManagement.create(entityFactory.tag().create().name("DistributionSetTag-D")); - List ds5Group1 = testdataFactory.createDistributionSets("", 5); - List dsGroup2 = testdataFactory.createDistributionSets("test2", 5); - DistributionSet dsDeleted = testdataFactory.createDistributionSet("deleted"); + List dsGroup1 = testdataFactory.createDistributionSets("", 5); + final String dsGroup2Prefix = "test"; + List dsGroup2 = testdataFactory.createDistributionSets(dsGroup2Prefix, 5); + DistributionSet dsDeleted = testdataFactory.createDistributionSet("testDeleted"); final DistributionSet dsInComplete = distributionSetManagement.create(entityFactory.distributionSet().create() .name("notcomplete").version("1").type(standardDsType.getKey())); @@ -649,185 +652,268 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest { distributionSetManagement.delete(dsDeleted.getId()); dsDeleted = distributionSetManagement.get(dsDeleted.getId()).get(); - ds5Group1 = toggleTagAssignment(ds5Group1, dsTagA).getAssignedEntity(); + dsGroup1 = toggleTagAssignment(dsGroup1, dsTagA).getAssignedEntity(); dsTagA = distributionSetTagRepository.findByNameEquals(dsTagA.getName()).get(); - ds5Group1 = toggleTagAssignment(ds5Group1, dsTagB).getAssignedEntity(); + dsGroup1 = toggleTagAssignment(dsGroup1, dsTagB).getAssignedEntity(); dsTagA = distributionSetTagRepository.findByNameEquals(dsTagA.getName()).get(); dsGroup2 = toggleTagAssignment(dsGroup2, dsTagA).getAssignedEntity(); dsTagA = distributionSetTagRepository.findByNameEquals(dsTagA.getName()).get(); + final List allDistributionSets = Stream + .of(dsGroup1, dsGroup2, Arrays.asList(dsDeleted, dsInComplete, dsNewType)).flatMap(Collection::stream) + .collect(Collectors.toList()); + final List dsGroup1WithGroup2 = Stream.of(dsGroup1, dsGroup2).flatMap(Collection::stream) + .collect(Collectors.toList()); + final int sizeOfAllDistributionSets = allDistributionSets.size(); + // check setup - assertThat(distributionSetRepository.findAll()).hasSize(13); + assertThat(distributionSetRepository.findAll()).hasSize(sizeOfAllDistributionSets); - // Find all - List expected = Lists.newArrayListWithExpectedSize(13); - expected.addAll(ds5Group1); - expected.addAll(dsGroup2); - expected.add(dsDeleted); - expected.add(dsInComplete); - expected.add(dsNewType); + validateFindAll(allDistributionSets); + validateDeleted(dsDeleted, sizeOfAllDistributionSets - 1); + validateCompleted(dsInComplete, sizeOfAllDistributionSets - 1); + validateType(newType, dsNewType, sizeOfAllDistributionSets - 1); + validateSearchText(dsGroup2, "%" + dsGroup2Prefix); + validateFilterString(allDistributionSets, dsGroup2Prefix); + validateTags(dsTagA, dsTagB, dsTagC, dsGroup1WithGroup2, dsGroup1); + validateDeletedAndCompleted(dsGroup1WithGroup2, dsNewType, dsDeleted); + validateDeletedAndCompletedAndType(dsGroup1WithGroup2, dsDeleted, newType, dsNewType); + validateDeletedAndCompletedAndTypeAndSearchText(dsGroup2, newType, "%" + dsGroup2Prefix); + validateDeletedAndCompletedAndTypeAndFilterString(dsGroup1WithGroup2, dsDeleted, dsInComplete, dsNewType, + newType, ":1"); + validateDeletedAndCompletedAndTypeAndSearchTextAndTag(dsGroup2, dsTagA, "%" + dsGroup2Prefix); + } - assertThat(distributionSetManagement - .findByDistributionSetFilter(PAGE, getDistributionSetFilterBuilder().build()).getContent()).hasSize(13) - .containsOnly(expected.toArray(new DistributionSet[0])); + @Step + private void validateFindAll(final List expectedDistributionsets) { - DistributionSetFilterBuilder distributionSetFilterBuilder; + assertThatFilterContainsOnlyGivenDistributionSets(getDistributionSetFilterBuilder(), expectedDistributionsets); + } - // search for not deleted - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsDeleted(Boolean.TRUE); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(1); + @Step + private void validateDeleted(final DistributionSet deletedDistributionSet, final int notDeletedSize) { - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsDeleted(false); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(12); + assertThatFilterContainsOnlyGivenDistributionSets(getDistributionSetFilterBuilder().setIsDeleted(Boolean.TRUE), + Arrays.asList(deletedDistributionSet)); - // search for completed - expected = new ArrayList<>(); - expected.addAll(ds5Group1); - expected.addAll(dsGroup2); - expected.add(dsDeleted); - expected.add(dsNewType); + assertThatFilterHasSizeAndDoesNotContainDistributionSet( + getDistributionSetFilterBuilder().setIsDeleted(Boolean.FALSE), notDeletedSize, deletedDistributionSet); + } - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(true); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(12).containsOnly(expected.toArray(new DistributionSet[0])); + @Step + private void validateCompleted(final DistributionSet dsIncomplete, final int completedSize) { - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(Boolean.FALSE); - expected = new ArrayList<>(); - expected.add(dsInComplete); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(1).containsOnly(expected.toArray(new DistributionSet[0])); + assertThatFilterHasSizeAndDoesNotContainDistributionSet( + getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE), completedSize, dsIncomplete); - // search for type - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setType(newType); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(1); + assertThatFilterContainsOnlyGivenDistributionSets( + getDistributionSetFilterBuilder().setIsComplete(Boolean.FALSE), Arrays.asList(dsIncomplete)); + } - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setType(standardDsType); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(12); + @Step + private void validateType(final DistributionSetType newType, final DistributionSet dsNewType, + final int standardDsTypeSize) { - // search for text - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setSearchText("%test2"); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(5); + assertThatFilterContainsOnlyGivenDistributionSets(getDistributionSetFilterBuilder().setType(newType), + Arrays.asList(dsNewType)); - // search for tags - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setTagNames(Arrays.asList(dsTagA.getName())); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(10); + assertThatFilterHasSizeAndDoesNotContainDistributionSet( + getDistributionSetFilterBuilder().setType(standardDsType), standardDsTypeSize, dsNewType); + } - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setTagNames(Arrays.asList(dsTagB.getName())); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(5); + @Step + private void validateSearchText(final List withText, final String text) { - distributionSetFilterBuilder = getDistributionSetFilterBuilder() - .setTagNames(Arrays.asList(dsTagA.getName(), dsTagB.getName())); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(10); + assertThatFilterContainsOnlyGivenDistributionSets(getDistributionSetFilterBuilder().setSearchText(text), + withText); + } - distributionSetFilterBuilder = getDistributionSetFilterBuilder() - .setTagNames(Arrays.asList(dsTagC.getName(), dsTagB.getName())); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(5); + @Step + private void validateFilterString(final List allDistributionSets, final String dsNamePrefix) { - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setTagNames(Arrays.asList(dsTagC.getName())); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(0); + final List withTestNamePrefix = allDistributionSets.stream() + .filter(ds -> ds.getName().startsWith(dsNamePrefix)).collect(Collectors.toList()); + assertThatFilterContainsOnlyGivenDistributionSets( + getDistributionSetFilterBuilder().setFilterString(dsNamePrefix), withTestNamePrefix); - // combine deleted and complete - expected = new ArrayList<>(); - expected.addAll(ds5Group1); - expected.addAll(dsGroup2); - expected.add(dsNewType); + final List withTestNameExact = withTestNamePrefix.stream() + .filter(ds -> ds.getName().equals(dsNamePrefix)).collect(Collectors.toList()); + assertThatFilterContainsOnlyGivenDistributionSets( + getDistributionSetFilterBuilder().setFilterString(dsNamePrefix + ":"), withTestNameExact); - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE) - .setIsDeleted(Boolean.FALSE); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(11).containsOnly(expected.toArray(new DistributionSet[0])); + final List withTestNameExactAndVersionPrefix = withTestNameExact.stream() + .filter(ds -> ds.getVersion().startsWith("1")).collect(Collectors.toList()); + assertThatFilterContainsOnlyGivenDistributionSets( + getDistributionSetFilterBuilder().setFilterString(dsNamePrefix + ":1"), + withTestNameExactAndVersionPrefix); - expected = Arrays.asList(dsInComplete); - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(Boolean.FALSE); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(1).containsOnly(expected.toArray(new DistributionSet[0])); + final List dsWithExactNameAndVersion = withTestNameExactAndVersionPrefix.stream() + .filter(ds -> ds.getVersion().equals("1.0.0")).collect(Collectors.toList()); + assertThat(dsWithExactNameAndVersion).hasSize(1); + assertThatFilterContainsOnlyGivenDistributionSets( + getDistributionSetFilterBuilder().setFilterString(dsNamePrefix + ":1.0.0"), dsWithExactNameAndVersion); - expected = Arrays.asList(dsDeleted); - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE) - .setIsDeleted(Boolean.TRUE); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(1).containsOnly(expected.toArray(new DistributionSet[0])); + final List withVersionPrefix = allDistributionSets.stream() + .filter(ds -> ds.getVersion().startsWith("1.0.")).collect(Collectors.toList()); + assertThatFilterContainsOnlyGivenDistributionSets(getDistributionSetFilterBuilder().setFilterString(":1.0."), + withVersionPrefix); - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsDeleted(Boolean.TRUE) - .setIsComplete(Boolean.FALSE); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(0); + final List withVersionExact = withVersionPrefix.stream() + .filter(ds -> ds.getVersion().equals("1.0.0")).collect(Collectors.toList()); + assertThatFilterContainsOnlyGivenDistributionSets(getDistributionSetFilterBuilder().setFilterString(":1.0.0"), + withVersionExact); - // combine deleted and complete and type - expected = new ArrayList<>(); - expected.addAll(ds5Group1); - expected.addAll(dsGroup2); - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsDeleted(Boolean.FALSE) - .setIsComplete(Boolean.TRUE).setType(standardDsType); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(10).containsOnly(expected.toArray(new DistributionSet[0])); + assertThatFilterContainsOnlyGivenDistributionSets(getDistributionSetFilterBuilder().setFilterString(":"), + allDistributionSets); - expected = Arrays.asList(dsDeleted); - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE) - .setType(standardDsType).setIsDeleted(Boolean.TRUE); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(1).containsOnly(expected.toArray(new DistributionSet[0])); + assertThatFilterContainsOnlyGivenDistributionSets(getDistributionSetFilterBuilder().setFilterString(" : "), + allDistributionSets); + } - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsDeleted(Boolean.TRUE) - .setIsComplete(Boolean.FALSE).setType(standardDsType); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(0); + @Step + private void validateTags(final DistributionSetTag dsTagA, final DistributionSetTag dsTagB, + final DistributionSetTag dsTagC, final List dsWithTagA, + final List dsWithTagB) { - expected = Arrays.asList(dsNewType); - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE).setType(newType); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(1).containsOnly(expected.toArray(new DistributionSet[0])); + assertThatFilterContainsOnlyGivenDistributionSets( + getDistributionSetFilterBuilder().setTagNames(Arrays.asList(dsTagA.getName())), dsWithTagA); - // combine deleted and complete and type and text - expected = dsGroup2; - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE) - .setType(standardDsType).setSearchText("%test2"); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(5).containsOnly(expected.toArray(new DistributionSet[0])); + assertThatFilterContainsOnlyGivenDistributionSets( + getDistributionSetFilterBuilder().setTagNames(Arrays.asList(dsTagB.getName())), dsWithTagB); - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE) - .setIsDeleted(Boolean.TRUE).setType(standardDsType).setSearchText("%test2"); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(0); + assertThatFilterContainsOnlyGivenDistributionSets( + getDistributionSetFilterBuilder().setTagNames(Arrays.asList(dsTagA.getName(), dsTagB.getName())), + dsWithTagA); - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setType(standardDsType).setSearchText("%test2") - .setIsComplete(false).setIsDeleted(false); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(0); + assertThatFilterContainsOnlyGivenDistributionSets( + getDistributionSetFilterBuilder().setTagNames(Arrays.asList(dsTagC.getName(), dsTagB.getName())), + dsWithTagB); - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setType(newType).setSearchText("%test2") - .setIsComplete(Boolean.TRUE).setIsDeleted(false); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(0); + assertThatFilterDoesNotContainAnyDistributionSet( + getDistributionSetFilterBuilder().setTagNames(Arrays.asList(dsTagC.getName()))); + } - // combine deleted and complete and type and text and tag - expected = dsGroup2; - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(true).setType(standardDsType) - .setSearchText("%test2").setTagNames(Arrays.asList(dsTagA.getName())); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(5).containsOnly(expected.toArray(new DistributionSet[0])); + @Step + private void validateDeletedAndCompleted(final List completedStandardType, + final DistributionSet dsNewType, final DistributionSet dsDeleted) { - distributionSetFilterBuilder = getDistributionSetFilterBuilder().setType(standardDsType).setSearchText("%test2") - .setTagNames(Arrays.asList(dsTagA.getName())).setIsComplete(Boolean.FALSE).setIsDeleted(Boolean.FALSE); - assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, distributionSetFilterBuilder.build()) - .getContent()).hasSize(0); + final List completedNotDeleted = new ArrayList<>(completedStandardType); + completedNotDeleted.add(dsNewType); + assertThatFilterContainsOnlyGivenDistributionSets( + getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE).setIsDeleted(Boolean.FALSE), + completedNotDeleted); + assertThatFilterContainsOnlyGivenDistributionSets( + getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE).setIsDeleted(Boolean.TRUE), + Arrays.asList(dsDeleted)); + + assertThatFilterDoesNotContainAnyDistributionSet( + getDistributionSetFilterBuilder().setIsComplete(Boolean.FALSE).setIsDeleted(Boolean.TRUE)); + } + + @Step + private void validateDeletedAndCompletedAndType(final List deletedAndCompletedAndStandardType, + final DistributionSet dsDeleted, final DistributionSetType newType, final DistributionSet dsNewType) { + + assertThatFilterContainsOnlyGivenDistributionSets(getDistributionSetFilterBuilder().setIsDeleted(Boolean.FALSE) + .setIsComplete(Boolean.TRUE).setType(standardDsType), deletedAndCompletedAndStandardType); + + assertThatFilterContainsOnlyGivenDistributionSets(getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE) + .setType(standardDsType).setIsDeleted(Boolean.TRUE), Arrays.asList(dsDeleted)); + + assertThatFilterDoesNotContainAnyDistributionSet(getDistributionSetFilterBuilder().setIsDeleted(Boolean.TRUE) + .setIsComplete(Boolean.FALSE).setType(standardDsType)); + + assertThatFilterContainsOnlyGivenDistributionSets( + getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE).setType(newType), + Arrays.asList(dsNewType)); + } + + @Step + private void validateDeletedAndCompletedAndTypeAndSearchText( + final List completedAndStandardTypeAndSearchText, final DistributionSetType newType, + final String text) { + + assertThatFilterContainsOnlyGivenDistributionSets(getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE) + .setType(standardDsType).setSearchText(text), completedAndStandardTypeAndSearchText); + + assertThatFilterDoesNotContainAnyDistributionSet(getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE) + .setIsDeleted(Boolean.TRUE).setType(standardDsType).setSearchText(text)); + + assertThatFilterDoesNotContainAnyDistributionSet(getDistributionSetFilterBuilder().setType(standardDsType) + .setSearchText(text).setIsComplete(Boolean.FALSE).setIsDeleted(Boolean.FALSE)); + + assertThatFilterDoesNotContainAnyDistributionSet(getDistributionSetFilterBuilder().setType(newType) + .setSearchText(text).setIsComplete(Boolean.TRUE).setIsDeleted(Boolean.FALSE)); + } + + @Step + private void validateDeletedAndCompletedAndTypeAndFilterString( + final List completedAndNotDeletedStandardTypeAndFilterString, + final DistributionSet dsDeleted, final DistributionSet dsInComplete, final DistributionSet dsNewType, + final DistributionSetType newType, final String filterString) { + + final List completedAndStandardTypeAndFilterString = new ArrayList<>( + completedAndNotDeletedStandardTypeAndFilterString); + completedAndStandardTypeAndFilterString.add(dsDeleted); + assertThatFilterContainsOnlyGivenDistributionSets(getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE) + .setType(standardDsType).setFilterString(filterString), completedAndStandardTypeAndFilterString); + + assertThatFilterContainsOnlyGivenDistributionSets( + getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE).setIsDeleted(Boolean.FALSE) + .setType(standardDsType).setFilterString(filterString), + completedAndNotDeletedStandardTypeAndFilterString); + + assertThatFilterContainsOnlyGivenDistributionSets(getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE) + .setIsDeleted(Boolean.TRUE).setType(standardDsType).setFilterString(filterString), + Arrays.asList(dsDeleted)); + + assertThatFilterContainsOnlyGivenDistributionSets(getDistributionSetFilterBuilder().setType(standardDsType) + .setFilterString(filterString).setIsComplete(Boolean.FALSE).setIsDeleted(Boolean.FALSE), + Arrays.asList(dsInComplete)); + + assertThatFilterContainsOnlyGivenDistributionSets(getDistributionSetFilterBuilder().setType(newType) + .setFilterString(filterString).setIsComplete(Boolean.TRUE).setIsDeleted(Boolean.FALSE), + Arrays.asList(dsNewType)); + } + + @Step + private void validateDeletedAndCompletedAndTypeAndSearchTextAndTag( + final List completedAndStandartTypeAndSearchTextAndTagA, final DistributionSetTag dsTagA, + final String text) { + + assertThatFilterContainsOnlyGivenDistributionSets( + getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE).setType(standardDsType) + .setSearchText(text).setTagNames(Arrays.asList(dsTagA.getName())), + completedAndStandartTypeAndSearchTextAndTagA); + + assertThatFilterDoesNotContainAnyDistributionSet(getDistributionSetFilterBuilder().setType(standardDsType) + .setSearchText(text).setTagNames(Arrays.asList(dsTagA.getName())).setIsComplete(Boolean.FALSE) + .setIsDeleted(Boolean.FALSE)); } private DistributionSetFilterBuilder getDistributionSetFilterBuilder() { return new DistributionSetFilterBuilder(); } + private void assertThatFilterContainsOnlyGivenDistributionSets(final DistributionSetFilterBuilder filterBuilder, + final List distributionSets) { + final int expectedDsSize = distributionSets.size(); + assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, filterBuilder.build()).getContent()) + .hasSize(expectedDsSize).containsOnly(distributionSets.toArray(new DistributionSet[expectedDsSize])); + } + + private void assertThatFilterDoesNotContainAnyDistributionSet(final DistributionSetFilterBuilder filterBuilder) { + assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, filterBuilder.build()).getContent()) + .hasSize(0); + } + + private void assertThatFilterHasSizeAndDoesNotContainDistributionSet( + final DistributionSetFilterBuilder filterBuilder, final int size, final DistributionSet ds) { + assertThat(distributionSetManagement.findByDistributionSetFilter(PAGE, filterBuilder.build()).getContent()) + .hasSize(size).doesNotContain(ds); + } + @Test @Description("Simple DS load without the related data that should be loaded lazy.") public void findDistributionSetsWithoutLazy() { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagementTest.java index 4cab3b8b6..b50362434 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagementTest.java @@ -17,7 +17,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import java.util.Iterator; +import java.util.Arrays; import java.util.List; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; @@ -26,8 +26,11 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedE import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetFilterQueryCreatedEvent; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; +import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException; +import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException; import org.eclipse.hawkbit.repository.exception.QuotaExceededException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; +import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; @@ -39,6 +42,7 @@ import org.springframework.data.domain.PageRequest; import io.qameta.allure.Description; import io.qameta.allure.Feature; +import io.qameta.allure.Step; import io.qameta.allure.Story; /** @@ -108,8 +112,9 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest final DistributionSet set = testdataFactory.createDistributionSet(); // creation is supposed to work as there is no distribution set - assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> targetFilterQueryManagement.create( - entityFactory.targetFilterQuery().create().name("testfilter").set(set.getId()).query("name==target*"))); + assertThatExceptionOfType(QuotaExceededException.class) + .isThrownBy(() -> targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create() + .name("testfilter").autoAssignDistributionSet(set.getId()).query("name==target*"))); } @Test @@ -180,20 +185,76 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest } @Test - @Description("Test assigning a distribution set") + @Description("Test assigning a distribution set for auto assignment with different action types") public void assignDistributionSet() { final String filterName = "target_filter_02"; final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement .create(entityFactory.targetFilterQuery().create().name(filterName).query("name==PendingTargets001")); - final DistributionSet distributionSet = testdataFactory.createDistributionSet(); - targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(), distributionSet.getId()); + verifyAutoAssignmentWithDefaultActionType(filterName, targetFilterQuery, distributionSet); + verifyAutoAssignmentWithSoftActionType(filterName, targetFilterQuery, distributionSet); + + verifyAutoAssignmentWithInvalidActionType(targetFilterQuery, distributionSet); + + verifyAutoAssignmentWithIncompleteDs(targetFilterQuery); + + verifyAutoAssignmentWithSoftDeletedDs(targetFilterQuery); + } + + @Step + private void verifyAutoAssignmentWithDefaultActionType(final String filterName, + final TargetFilterQuery targetFilterQuery, final DistributionSet distributionSet) { + targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(), distributionSet.getId()); + verifyAutoAssignDsAndActionType(filterName, distributionSet, ActionType.FORCED); + } + + @Step + private void verifyAutoAssignmentWithSoftActionType(final String filterName, + final TargetFilterQuery targetFilterQuery, final DistributionSet distributionSet) { + targetFilterQueryManagement.updateAutoAssignDSWithActionType(targetFilterQuery.getId(), distributionSet.getId(), + ActionType.SOFT); + verifyAutoAssignDsAndActionType(filterName, distributionSet, ActionType.SOFT); + } + + @Step + private void verifyAutoAssignmentWithInvalidActionType(final TargetFilterQuery targetFilterQuery, + final DistributionSet distributionSet) { + // assigning a distribution set with TIMEFORCED action is supposed to + // fail as only FORCED and SOFT action types are allowed + assertThatExceptionOfType(InvalidAutoAssignActionTypeException.class).isThrownBy( + () -> targetFilterQueryManagement.updateAutoAssignDSWithActionType(targetFilterQuery.getId(), + distributionSet.getId(), ActionType.TIMEFORCED)); + } + + @Step + private void verifyAutoAssignmentWithIncompleteDs(final TargetFilterQuery targetFilterQuery) { + final DistributionSet incompleteDistributionSet = distributionSetManagement + .create(entityFactory.distributionSet().create().name("incomplete").version("1") + .type(testdataFactory.findOrCreateDefaultTestDsType())); + + assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class) + .isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(), + incompleteDistributionSet.getId())); + } + + @Step + private void verifyAutoAssignmentWithSoftDeletedDs(final TargetFilterQuery targetFilterQuery) { + final DistributionSet softDeletedDs = testdataFactory.createDistributionSet("softDeleted"); + assignDistributionSet(softDeletedDs, testdataFactory.createTarget("forSoftDeletedDs")); + distributionSetManagement.delete(softDeletedDs.getId()); + + assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class).isThrownBy( + () -> targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(), softDeletedDs.getId())); + } + + private void verifyAutoAssignDsAndActionType(final String filterName, final DistributionSet distributionSet, + final ActionType actionType) { final TargetFilterQuery tfq = targetFilterQueryManagement.getByName(filterName).get(); assertEquals("Returns correct distribution set", distributionSet, tfq.getAutoAssignDistributionSet()); - + assertEquals("Return correct action type", actionType, tfq.getAutoAssignActionType()); } @Test @@ -225,8 +286,8 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest final DistributionSet set = testdataFactory.createDistributionSet(); // creation is supposed to work as the query does not exceed the quota - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create( - entityFactory.targetFilterQuery().create().name("testfilter").set(set.getId()).query("name==foo")); + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create(entityFactory.targetFilterQuery() + .create().name("testfilter").autoAssignDistributionSet(set.getId()).query("name==foo")); // update with a query string that addresses too many targets assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> targetFilterQueryManagement @@ -247,6 +308,7 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest // Check if target filter query is there TargetFilterQuery tfq = targetFilterQueryManagement.getByName(filterName).get(); assertEquals("Returns correct distribution set", distributionSet, tfq.getAutoAssignDistributionSet()); + assertEquals("Return correct action type", ActionType.FORCED, tfq.getAutoAssignActionType()); distributionSetManagement.delete(distributionSet.getId()); @@ -254,7 +316,7 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest tfq = targetFilterQueryManagement.getByName(filterName).get(); assertNotNull("Returns target filter query", tfq); assertNull("Returns distribution set as null", tfq.getAutoAssignDistributionSet()); - + assertNull("Returns action type as null", tfq.getAutoAssignActionType()); } @Test @@ -275,6 +337,7 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest // Check if target filter query is there with the distribution set TargetFilterQuery tfq = targetFilterQueryManagement.getByName(filterName).get(); assertEquals("Returns correct distribution set", distributionSet, tfq.getAutoAssignDistributionSet()); + assertEquals("Return correct action type", ActionType.FORCED, tfq.getAutoAssignActionType()); distributionSetManagement.delete(distributionSet.getId()); @@ -286,66 +349,66 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest tfq = targetFilterQueryManagement.getByName(filterName).get(); assertNotNull("Returns target filter query", tfq); assertNull("Returns distribution set as null", tfq.getAutoAssignDistributionSet()); - + assertNull("Returns action type as null", tfq.getAutoAssignActionType()); } @Test @Description("Test finding and auto assign distribution set") public void findFiltersWithDistributionSet() { - final String filterName = "d"; - assertEquals(0L, targetFilterQueryManagement.count()); - targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("a").query("name==*")); targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("b").query("name==*")); - final DistributionSet distributionSet = testdataFactory.createDistributionSet(); final DistributionSet distributionSet2 = testdataFactory.createDistributionSet("2"); - final TargetFilterQuery tfq = targetFilterQueryManagement.updateAutoAssignDS( + final TargetFilterQuery tfq = targetFilterQueryManagement.updateAutoAssignDSWithActionType( targetFilterQueryManagement .create(entityFactory.targetFilterQuery().create().name("c").query("name==x")).getId(), - distributionSet.getId()); - + distributionSet.getId(), ActionType.SOFT); final TargetFilterQuery tfq2 = targetFilterQueryManagement.updateAutoAssignDS( targetFilterQueryManagement .create(entityFactory.targetFilterQuery().create().name(filterName).query("name==z*")).getId(), distributionSet2.getId()); - assertEquals(4L, targetFilterQueryManagement.count()); // check if find works - Page tfqList = targetFilterQueryManagement.findByAutoAssignDSAndRsql(PageRequest.of(0, 500), - distributionSet.getId(), null); - assertThat(1L).as("Target filter query").isEqualTo(tfqList.getTotalElements()); - - assertEquals("Returns correct target filter query", tfq.getId(), tfqList.iterator().next().getId()); + verifyFindByDistributionSetAndRsql(distributionSet, null, tfq); targetFilterQueryManagement.updateAutoAssignDS(tfq2.getId(), distributionSet.getId()); // check if find works for two - tfqList = targetFilterQueryManagement.findByAutoAssignDSAndRsql(PageRequest.of(0, 500), distributionSet.getId(), - null); - assertThat(2L).as("Target filter query count").isEqualTo(tfqList.getTotalElements()); - Iterator iterator = tfqList.iterator(); - assertEquals("Returns correct target filter query 1", tfq.getId(), iterator.next().getId()); - assertEquals("Returns correct target filter query 2", tfq2.getId(), iterator.next().getId()); + verifyFindByDistributionSetAndRsql(distributionSet, null, tfq, tfq2); // check if find works with name filter - tfqList = targetFilterQueryManagement.findByAutoAssignDSAndRsql(PageRequest.of(0, 500), distributionSet.getId(), - "name==" + filterName); - assertThat(1L).as("Target filter query count").isEqualTo(tfqList.getTotalElements()); - - assertEquals("Returns correct target filter query", tfq2.getId(), tfqList.iterator().next().getId()); - - // check if find works for all with auto assign DS - tfqList = targetFilterQueryManagement.findWithAutoAssignDS(PageRequest.of(0, 500)); - assertThat(2L).as("Target filter query count").isEqualTo(tfqList.getTotalElements()); - iterator = tfqList.iterator(); - assertEquals("Returns correct target filter query 1", tfq.getId(), iterator.next().getId()); - assertEquals("Returns correct target filter query 2", tfq2.getId(), iterator.next().getId()); + verifyFindByDistributionSetAndRsql(distributionSet, "name==" + filterName, tfq2); + verifyFindForAllWithAutoAssignDs(tfq, tfq2); } + @Step + private void verifyFindByDistributionSetAndRsql(final DistributionSet distributionSet, final String rsql, + final TargetFilterQuery... expectedFilterQueries) { + final Page tfqList = targetFilterQueryManagement + .findByAutoAssignDSAndRsql(PageRequest.of(0, 500), distributionSet.getId(), rsql); + + verifyExpectedFilterQueriesInList(tfqList, expectedFilterQueries); + } + + private void verifyExpectedFilterQueriesInList(final Page tfqList, + final TargetFilterQuery... expectedFilterQueries) { + assertThat(expectedFilterQueries.length).as("Target filter query count") + .isEqualTo((int) tfqList.getTotalElements()); + + assertThat(tfqList.map(TargetFilterQuery::getId)).containsExactly( + Arrays.stream(expectedFilterQueries).map(TargetFilterQuery::getId).toArray(Long[]::new)); + } + + @Step + private void verifyFindForAllWithAutoAssignDs(final TargetFilterQuery... expectedFilterQueries) { + final Page tfqList = targetFilterQueryManagement + .findWithAutoAssignDS(PageRequest.of(0, 500)); + + verifyExpectedFilterQueriesInList(tfqList, expectedFilterQueries); + } } 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 817068e38..e15e62a07 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 @@ -1036,10 +1036,10 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest { final Target target2 = createTargetWithMetadata("target2", 8); final Page metadataOfTarget1 = targetManagement - .findMetaDataByControllerId(new PageRequest(0, 100), target1.getControllerId()); + .findMetaDataByControllerId(PageRequest.of(0, 100), target1.getControllerId()); final Page metadataOfTarget2 = targetManagement - .findMetaDataByControllerId(new PageRequest(0, 100), target2.getControllerId()); + .findMetaDataByControllerId(PageRequest.of(0, 100), target2.getControllerId()); assertThat(metadataOfTarget1.getNumberOfElements()).isEqualTo(10); assertThat(metadataOfTarget1.getTotalElements()).isEqualTo(10); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java index 7771078dc..bfa9885ec 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java @@ -9,11 +9,13 @@ package org.eclipse.hawkbit.repository.jpa.autoassign; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException; import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; @@ -80,7 +82,7 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest { final Action rolloutCreatedAction = actionsByKnownTarget.stream() .filter(action -> !action.getId().equals(manuallyAssignedActionId)).findAny().get(); assertThat(rolloutCreatedAction.getStatus()).isEqualTo(Status.RUNNING); - + assertThat(rolloutCreatedAction.getActionType()).isEqualTo(ActionType.FORCED); } finally { tenantConfigurationManagement .addOrUpdateConfiguration(TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, false); @@ -149,9 +151,13 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest { // target filter query that matches first bunch of targets, that should // fail - targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement.create( - entityFactory.targetFilterQuery().create().name("filterA").query("id==" + targetDsFIdPref + "*")) - .getId(), setF.getId()); + assertThatExceptionOfType( + InvalidAutoAssignDistributionSetException.class) + .isThrownBy( + () -> targetFilterQueryManagement.updateAutoAssignDS( + targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create() + .name("filterA").query("id==" + targetDsFIdPref + "*")).getId(), + setF.getId())); // target filter query that matches failed bunch of targets targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement.create( @@ -204,4 +210,47 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest { } + @Test + @Description("Test auto assignment of a distribution set with FORCED and SOFT action types") + public void checkAutoAssignWithDifferentActionTypes() { + final DistributionSet distributionSet = testdataFactory.createDistributionSet(); + final String targetDsAIdPref = "targA"; + final String targetDsBIdPref = "targB"; + + final List targetsA = testdataFactory.createTargets(5, targetDsAIdPref, + targetDsAIdPref.concat(" description")); + final List targetsB = testdataFactory.createTargets(10, targetDsBIdPref, + targetDsBIdPref.concat(" description")); + final int targetsCount = targetsA.size() + targetsB.size(); + + targetFilterQueryManagement + .updateAutoAssignDSWithActionType( + targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("filterA") + .query("id==" + targetDsAIdPref + "*")).getId(), + distributionSet.getId(), ActionType.FORCED); + targetFilterQueryManagement + .updateAutoAssignDSWithActionType( + targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("filterB") + .query("id==" + targetDsBIdPref + "*")).getId(), + distributionSet.getId(), ActionType.SOFT); + + autoAssignChecker.check(); + + verifyThatTargetsHaveDistributionSetAssignment(distributionSet, targetsA, targetsCount); + verifyThatTargetsHaveDistributionSetAssignment(distributionSet, targetsB, targetsCount); + + verifyThatTargetsHaveAssignmentActionType(ActionType.FORCED, targetsA); + verifyThatTargetsHaveAssignmentActionType(ActionType.SOFT, targetsB); + } + + @Step + private void verifyThatTargetsHaveAssignmentActionType(final ActionType actionType, final List targets) { + final List actions = targets.stream().map(Target::getControllerId).flatMap( + controllerId -> deploymentManagement.findActionsByTarget(controllerId, PAGE).getContent().stream()) + .collect(Collectors.toList()); + + assertThat(actions).hasSize(targets.size()); + assertThat(actions).allMatch(action -> action.getActionType().equals(actionType)); + } + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFilterQueryFieldsTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFilterQueryFieldsTest.java new file mode 100644 index 000000000..17d9dacdc --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFilterQueryFieldsTest.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2019 Bosch Software Innovations 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.jpa.rsql; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; + +import org.eclipse.hawkbit.repository.TargetFilterQueryFields; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; +import org.eclipse.hawkbit.repository.test.util.TestdataFactory; +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.domain.Page; + +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; + +@Feature("Component Tests - Repository") +@Story("RSQL filter target filter query") +public class RSQLTargetFilterQueryFieldsTest extends AbstractJpaIntegrationTest { + + private TargetFilterQuery filter1; + private TargetFilterQuery filter2; + + @Before + public void setupBeforeTest() throws InterruptedException { + final String filterName1 = "filter_a"; + final String filterName2 = "filter_b"; + final String filterName3 = "filter_c"; + + final DistributionSet ds1 = testdataFactory.createDistributionSet("AutoAssignedDs_1"); + final DistributionSet ds2 = testdataFactory.createDistributionSet("AutoAssignedDs_2"); + + filter1 = targetFilterQueryManagement.updateAutoAssignDSWithActionType( + targetFilterQueryManagement + .create(entityFactory.targetFilterQuery().create().name(filterName1).query("name==*")).getId(), + ds1.getId(), ActionType.SOFT); + filter2 = targetFilterQueryManagement.updateAutoAssignDS( + targetFilterQueryManagement + .create(entityFactory.targetFilterQuery().create().name(filterName2).query("name==*")).getId(), + ds2.getId()); + targetFilterQueryManagement + .create(entityFactory.targetFilterQuery().create().name(filterName3).query("name==*")); + + assertEquals(3L, targetFilterQueryManagement.count()); + } + + @Test + @Description("Test filter target filter query by id") + public void testFilterByParameterId() { + assertRSQLQuery(TargetFilterQueryFields.ID.name() + "==*", 3); + } + + @Test + @Description("Test filter target filter query by name") + public void testFilterByParameterName() { + assertRSQLQuery(TargetFilterQueryFields.NAME.name() + "==" + filter1.getName(), 1); + assertRSQLQuery(TargetFilterQueryFields.NAME.name() + "==" + filter2.getName(), 1); + assertRSQLQuery(TargetFilterQueryFields.NAME.name() + "==filter_*", 3); + assertRSQLQuery(TargetFilterQueryFields.NAME.name() + "==noExist*", 0); + assertRSQLQuery(TargetFilterQueryFields.NAME.name() + "=in=(" + filter1.getName() + ",notexist)", 1); + assertRSQLQuery(TargetFilterQueryFields.NAME.name() + "=out=(" + filter1.getName() + ",notexist)", 2); + } + + @Test + @Description("Test filter target filter query by auto assigned ds name") + public void testFilterByAutoAssignedDsName() { + assertRSQLQuery(TargetFilterQueryFields.AUTOASSIGNDISTRIBUTIONSET.name() + ".name==" + + filter1.getAutoAssignDistributionSet().getName(), 1); + assertRSQLQuery(TargetFilterQueryFields.AUTOASSIGNDISTRIBUTIONSET.name() + ".name==" + + filter2.getAutoAssignDistributionSet().getName(), 1); + assertRSQLQuery(TargetFilterQueryFields.AUTOASSIGNDISTRIBUTIONSET.name() + ".name==AutoAssignedDs_*", 2); + assertRSQLQuery(TargetFilterQueryFields.AUTOASSIGNDISTRIBUTIONSET.name() + ".name==noExist*", 0); + assertRSQLQuery(TargetFilterQueryFields.AUTOASSIGNDISTRIBUTIONSET.name() + ".name=in=(" + + filter1.getAutoAssignDistributionSet().getName() + ",notexist)", 1); + assertRSQLQuery(TargetFilterQueryFields.AUTOASSIGNDISTRIBUTIONSET.name() + ".name=out=(" + + filter1.getAutoAssignDistributionSet().getName() + ",notexist)", 1); + } + + @Test + @Description("Test filter target filter query by auto assigned ds version") + public void testFilterByAutoAssignedDsVersion() { + assertRSQLQuery(TargetFilterQueryFields.AUTOASSIGNDISTRIBUTIONSET.name() + ".version==" + + TestdataFactory.DEFAULT_VERSION, 2); + assertRSQLQuery(TargetFilterQueryFields.AUTOASSIGNDISTRIBUTIONSET.name() + ".version==*1*", 2); + assertRSQLQuery(TargetFilterQueryFields.AUTOASSIGNDISTRIBUTIONSET.name() + ".version==noExist*", 0); + assertRSQLQuery(TargetFilterQueryFields.AUTOASSIGNDISTRIBUTIONSET.name() + ".version=in=(" + + TestdataFactory.DEFAULT_VERSION + ",notexist)", 2); + assertRSQLQuery(TargetFilterQueryFields.AUTOASSIGNDISTRIBUTIONSET.name() + ".version=out=(" + + TestdataFactory.DEFAULT_VERSION + ",notexist)", 0); + } + + private void assertRSQLQuery(final String rsqlParam, final long expectedFilterQueriesSize) { + final Page findTargetFilterQueryPage = targetFilterQueryManagement.findByRsql(PAGE, + rsqlParam); + assertThat(findTargetFilterQueryPage).isNotNull(); + assertThat(findTargetFilterQueryPage.getTotalElements()).isEqualTo(expectedFilterQueriesSize); + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetMetadataFieldsTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetMetadataFieldsTest.java index cefba21bf..b02050656 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetMetadataFieldsTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetMetadataFieldsTest.java @@ -72,7 +72,7 @@ public class RSQLTargetMetadataFieldsTest extends AbstractJpaIntegrationTest { private void assertRSQLQuery(final String rsqlParam, final long expectedEntities) { final Page findEnitity = targetManagement - .findMetaDataByControllerIdAndRsql(new PageRequest(0, 100), controllerId, rsqlParam); + .findMetaDataByControllerIdAndRsql(PageRequest.of(0, 100), controllerId, rsqlParam); final long countAllEntities = findEnitity.getTotalElements(); assertThat(findEnitity).isNotNull(); assertThat(countAllEntities).isEqualTo(expectedEntities); diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtDistributionSetAutoAssignment.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtDistributionSetAutoAssignment.java new file mode 100644 index 000000000..ce0d8494c --- /dev/null +++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtDistributionSetAutoAssignment.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2018 Bosch Software Innovations 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.mgmt.json.model.targetfilter; + +import org.eclipse.hawkbit.mgmt.json.model.MgmtId; +import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Request Body of DistributionSet Id and Action Type for target filter auto + * assignment operation. + */ +public class MgmtDistributionSetAutoAssignment extends MgmtId { + + @JsonProperty(required = false) + private MgmtActionType type; + + public MgmtActionType getType() { + return type; + } + + public void setType(final MgmtActionType type) { + this.type = type; + } +} diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQuery.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQuery.java index 2a3acbe53..e32ad2c8b 100644 --- a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQuery.java +++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQuery.java @@ -9,6 +9,7 @@ package org.eclipse.hawkbit.mgmt.json.model.targetfilter; import org.eclipse.hawkbit.mgmt.json.model.MgmtBaseEntity; +import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; @@ -36,6 +37,9 @@ public class MgmtTargetFilterQuery extends MgmtBaseEntity { @JsonProperty private Long autoAssignDistributionSet; + @JsonProperty + private MgmtActionType autoAssignActionType; + public Long getFilterId() { return filterId; } @@ -68,4 +72,12 @@ public class MgmtTargetFilterQuery extends MgmtBaseEntity { this.autoAssignDistributionSet = autoAssignDistributionSet; } + public MgmtActionType getAutoAssignActionType() { + return autoAssignActionType; + } + + public void setAutoAssignActionType(final MgmtActionType actionType) { + this.autoAssignActionType = actionType; + } + } diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetFilterQueryRestApi.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetFilterQueryRestApi.java index b04ad5fa2..8c8703d4a 100644 --- a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetFilterQueryRestApi.java +++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetFilterQueryRestApi.java @@ -8,9 +8,9 @@ */ package org.eclipse.hawkbit.mgmt.rest.api; -import org.eclipse.hawkbit.mgmt.json.model.MgmtId; import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet; +import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtDistributionSetAutoAssignment; import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery; import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQueryRequestBody; import org.springframework.hateoas.MediaTypes; @@ -135,15 +135,16 @@ public interface MgmtTargetFilterQueryRestApi { * * @param filterId * of the target to change - * @param dsId - * of the Id of the auto assign distribution set + * @param dsIdWithActionType + * id of the distribution set and the action type for auto + * assignment * @return http status */ @PostMapping(value = "/{filterId}/autoAssignDS", consumes = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) ResponseEntity postAssignedDistributionSet(@PathVariable("filterId") Long filterId, - @RequestBody MgmtId dsId); + @RequestBody MgmtDistributionSetAutoAssignment dsIdWithActionType); /** * Handles the DELETE request for removing the distribution set for auto diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryMapper.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryMapper.java index 5525fadaa..0309b8552 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryMapper.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryMapper.java @@ -57,6 +57,7 @@ public final class MgmtTargetFilterQueryMapper { final DistributionSet distributionSet = filter.getAutoAssignDistributionSet(); if (distributionSet != null) { targetRest.setAutoAssignDistributionSet(distributionSet.getId()); + targetRest.setAutoAssignActionType(MgmtRestModelMapper.convertActionType(filter.getAutoAssignActionType())); } targetRest.add(linkTo(methodOn(MgmtTargetFilterQueryRestApi.class).getFilter(filter.getId())).withSelfRel()); diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResource.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResource.java index bcb797865..17006fdcd 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResource.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResource.java @@ -10,9 +10,9 @@ package org.eclipse.hawkbit.mgmt.rest.resource; import java.util.List; -import org.eclipse.hawkbit.mgmt.json.model.MgmtId; import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet; +import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtDistributionSetAutoAssignment; import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery; import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQueryRequestBody; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; @@ -126,9 +126,11 @@ public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestA @Override public ResponseEntity postAssignedDistributionSet( - @PathVariable("filterId") final Long filterId, @RequestBody final MgmtId dsId) { + @PathVariable("filterId") final Long filterId, + @RequestBody final MgmtDistributionSetAutoAssignment dsIdWithActionType) { - final TargetFilterQuery updateFilter = filterManagement.updateAutoAssignDS(filterId, dsId.getId()); + final TargetFilterQuery updateFilter = filterManagement.updateAutoAssignDSWithActionType(filterId, + dsIdWithActionType.getId(), MgmtRestModelMapper.convertActionType(dsIdWithActionType.getType())); final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(updateFilter); MgmtTargetFilterQueryMapper.addLinks(response); diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResourceTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResourceTest.java index 70e20d2fc..da2523ec9 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResourceTest.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResourceTest.java @@ -20,8 +20,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.eclipse.hawkbit.exception.SpServerError; +import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; +import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException; +import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException; import org.eclipse.hawkbit.repository.exception.QuotaExceededException; +import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.rest.exception.MessageNotReadableException; @@ -34,6 +38,7 @@ import org.springframework.test.web.servlet.MvcResult; import io.qameta.allure.Description; import io.qameta.allure.Feature; +import io.qameta.allure.Step; import io.qameta.allure.Story; /** @@ -54,6 +59,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte private static final String JSON_PATH_FIELD_SIZE = ".size"; private static final String JSON_PATH_FIELD_TOTAL = ".total"; private static final String JSON_PATH_FIELD_AUTO_ASSIGN_DS = ".autoAssignDistributionSet"; + private static final String JSON_PATH_FIELD_AUTO_ASSIGN_ACTION_TYPE = ".autoAssignActionType"; private static final String JSON_PATH_FIELD_EXCEPTION_CLASS = ".exceptionClass"; private static final String JSON_PATH_FIELD_ERROR_CODE = ".errorCode"; @@ -67,6 +73,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte private static final String JSON_PATH_ID = JSON_PATH_ROOT + JSON_PATH_FIELD_ID; private static final String JSON_PATH_QUERY = JSON_PATH_ROOT + JSON_PATH_FIELD_QUERY; private static final String JSON_PATH_AUTO_ASSIGN_DS = JSON_PATH_ROOT + JSON_PATH_FIELD_AUTO_ASSIGN_DS; + private static final String JSON_PATH_AUTO_ASSIGN_ACTION_TYPE = JSON_PATH_ROOT + + JSON_PATH_FIELD_AUTO_ASSIGN_ACTION_TYPE; private static final String JSON_PATH_EXCEPTION_CLASS = JSON_PATH_ROOT + JSON_PATH_FIELD_EXCEPTION_CLASS; private static final String JSON_PATH_ERROR_CODE = JSON_PATH_ROOT + JSON_PATH_FIELD_ERROR_CODE; @@ -113,9 +121,9 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId()).content(body) .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) - .andExpect(jsonPath("$.id", equalTo(tfq.getId().intValue()))) - .andExpect(jsonPath("$.query", equalTo(filterQuery2))) - .andExpect(jsonPath("$.name", equalTo(filterName))); + .andExpect(jsonPath(JSON_PATH_ID, equalTo(tfq.getId().intValue()))) + .andExpect(jsonPath(JSON_PATH_QUERY, equalTo(filterQuery2))) + .andExpect(jsonPath(JSON_PATH_NAME, equalTo(filterName))); final TargetFilterQuery tfqCheck = targetFilterQueryManagement.get(tfq.getId()).get(); assertThat(tfqCheck.getQuery()).isEqualTo(filterQuery2); @@ -136,9 +144,9 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId()).content(body) .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) - .andExpect(jsonPath("$.id", equalTo(tfq.getId().intValue()))) - .andExpect(jsonPath("$.query", equalTo(filterQuery))) - .andExpect(jsonPath("$.name", equalTo(filterName2))); + .andExpect(jsonPath(JSON_PATH_ID, equalTo(tfq.getId().intValue()))) + .andExpect(jsonPath(JSON_PATH_QUERY, equalTo(filterQuery))) + .andExpect(jsonPath(JSON_PATH_NAME, equalTo(filterName2))); final TargetFilterQuery tfqCheck = targetFilterQueryManagement.get(tfq.getId()).get(); assertThat(tfqCheck.getQuery()).isEqualTo(filterQuery); @@ -294,7 +302,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte // create targets final int maxTargets = quotaManagement.getMaxTargetsPerAutoAssignment(); - testdataFactory.createTargets(maxTargets + 1, "target%s"); + testdataFactory.createTargets(maxTargets + 1, "target"); // create the filter query and the distribution set final DistributionSet set = testdataFactory.createDistributionSet(); @@ -315,7 +323,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte // create targets final int maxTargets = quotaManagement.getMaxTargetsPerAutoAssignment(); - testdataFactory.createTargets(maxTargets + 1, "target%s"); + testdataFactory.createTargets(maxTargets + 1, "target"); // create the filter query and the distribution set final DistributionSet set = testdataFactory.createDistributionSet(); @@ -327,8 +335,10 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte .content("{\"id\":" + set.getId() + "}").contentType(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); - assertThat(targetFilterQueryManagement.get(filterQuery.getId()).get().getAutoAssignDistributionSet()) - .isEqualTo(set); + final TargetFilterQuery updatedFilterQuery = targetFilterQueryManagement.get(filterQuery.getId()).get(); + + assertThat(updatedFilterQuery.getAutoAssignDistributionSet()).isEqualTo(set); + assertThat(updatedFilterQuery.getAutoAssignActionType()).isEqualTo(ActionType.FORCED); // update the query of the filter query to trigger a quota hit mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + filterQuery.getId()) @@ -340,33 +350,131 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte } @Test + @Description("Ensures that the distribution set auto-assignment works as intended with distribution set and action type validation") public void setAutoAssignDistributionSetToTargetFilterQuery() throws Exception { - final String knownQuery = "name==test05"; final String knownName = "filter05"; final DistributionSet set = testdataFactory.createDistributionSet(); final TargetFilterQuery tfq = createSingleTargetFilterQuery(knownName, knownQuery); - mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS") - .content("{\"id\":" + set.getId() + "}").contentType(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + verifyAutoAssignmentWithoutActionType(tfq, set); - assertThat(targetFilterQueryManagement.get(tfq.getId()).get().getAutoAssignDistributionSet()).isEqualTo(set); + verifyAutoAssignmentWithForcedActionType(tfq, set); + verifyAutoAssignmentWithSoftActionType(tfq, set); + + verifyAutoAssignmentWithTimeForcedActionType(tfq, set); + + verifyAutoAssignmentWithUnknownActionType(tfq, set); + + verifyAutoAssignmentWithIncompleteDs(tfq); + + verifyAutoAssignmentWithSoftDeletedDs(tfq); + } + + @Step + private void verifyAutoAssignmentWithoutActionType(final TargetFilterQuery tfq, final DistributionSet set) + throws Exception { + verifyAutoAssignmentByActionType(tfq, set, null); + } + + @Step + private void verifyAutoAssignmentWithForcedActionType(final TargetFilterQuery tfq, final DistributionSet set) + throws Exception { + verifyAutoAssignmentByActionType(tfq, set, MgmtActionType.FORCED); + } + + @Step + private void verifyAutoAssignmentWithSoftActionType(final TargetFilterQuery tfq, final DistributionSet set) + throws Exception { + verifyAutoAssignmentByActionType(tfq, set, MgmtActionType.SOFT); + } + + private void verifyAutoAssignmentByActionType(final TargetFilterQuery tfq, final DistributionSet set, + final MgmtActionType actionType) throws Exception { final String hrefPrefix = "http://localhost" + MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId(); + final String payload = actionType != null + ? "{\"id\":" + set.getId() + ", \"type\":\"" + actionType.getName() + "\"}" + : "{\"id\":" + set.getId() + "}"; + mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS") + .content(payload).contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + final TargetFilterQuery updatedFilterQuery = targetFilterQueryManagement.get(tfq.getId()).get(); + final MgmtActionType expectedActionType = actionType != null ? actionType : MgmtActionType.FORCED; + + assertThat(updatedFilterQuery.getAutoAssignDistributionSet()).isEqualTo(set); + assertThat(updatedFilterQuery.getAutoAssignActionType()) + .isEqualTo(MgmtRestModelMapper.convertActionType(expectedActionType)); + mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId())) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) - .andExpect(jsonPath(JSON_PATH_NAME, equalTo(knownName))) - .andExpect(jsonPath(JSON_PATH_QUERY, equalTo(knownQuery))) + .andExpect(jsonPath(JSON_PATH_NAME, equalTo(tfq.getName()))) + .andExpect(jsonPath(JSON_PATH_QUERY, equalTo(tfq.getQuery()))) .andExpect(jsonPath(JSON_PATH_AUTO_ASSIGN_DS, equalTo(set.getId().intValue()))) + .andExpect(jsonPath(JSON_PATH_AUTO_ASSIGN_ACTION_TYPE, equalTo(expectedActionType.getName()))) .andExpect(jsonPath("$._links.self.href", equalTo(hrefPrefix))) .andExpect(jsonPath("$._links.autoAssignDS.href", equalTo(hrefPrefix + "/autoAssignDS"))); } + @Step + private void verifyAutoAssignmentWithTimeForcedActionType(final TargetFilterQuery tfq, final DistributionSet set) + throws Exception { + mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS") + .content("{\"id\":" + set.getId() + ", \"type\":\"" + MgmtActionType.TIMEFORCED.getName() + "\"}") + .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS, + equalTo(InvalidAutoAssignActionTypeException.class.getName()))) + .andExpect(jsonPath(JSON_PATH_ERROR_CODE, + equalTo(SpServerError.SP_AUTO_ASSIGN_ACTION_TYPE_INVALID.getKey()))); + } + + @Step + private void verifyAutoAssignmentWithUnknownActionType(final TargetFilterQuery tfq, final DistributionSet set) + throws Exception { + mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS") + .content("{\"id\":" + set.getId() + ", \"type\":\"unknown\"}").contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()) + .andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS, equalTo(MessageNotReadableException.class.getName()))) + .andExpect(jsonPath(JSON_PATH_ERROR_CODE, equalTo(SpServerError.SP_REST_BODY_NOT_READABLE.getKey()))); + } + + @Step + private void verifyAutoAssignmentWithIncompleteDs(final TargetFilterQuery tfq) throws Exception { + final DistributionSet incompleteDistributionSet = distributionSetManagement + .create(entityFactory.distributionSet().create().name("incomplete").version("1") + .type(testdataFactory.findOrCreateDefaultTestDsType())); + + mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS") + .content("{\"id\":" + incompleteDistributionSet.getId() + "}").contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()) + .andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS, + equalTo(InvalidAutoAssignDistributionSetException.class.getName()))) + .andExpect(jsonPath(JSON_PATH_ERROR_CODE, + equalTo(SpServerError.SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID.getKey()))); + } + + @Step + private void verifyAutoAssignmentWithSoftDeletedDs(final TargetFilterQuery tfq) throws Exception { + final DistributionSet softDeletedDs = testdataFactory.createDistributionSet("softDeleted"); + assignDistributionSet(softDeletedDs, testdataFactory.createTarget("forSoftDeletedDs")); + distributionSetManagement.delete(softDeletedDs.getId()); + + mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS") + .content("{\"id\":" + softDeletedDs.getId() + "}").contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()) + .andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS, + equalTo(InvalidAutoAssignDistributionSetException.class.getName()))) + .andExpect(jsonPath(JSON_PATH_ERROR_CODE, + equalTo(SpServerError.SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID.getKey()))); + } + @Test + @Description("Ensures that the deletion of auto-assignment distribution set works as intended, deleting the auto-assignment action type as well") public void deleteAutoAssignDistributionSetOfTargetFilterQuery() throws Exception { final String knownQuery = "name==test06"; @@ -377,7 +485,10 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte final TargetFilterQuery tfq = createSingleTargetFilterQuery(knownName, knownQuery); targetFilterQueryManagement.updateAutoAssignDS(tfq.getId(), set.getId()); - assertThat(targetFilterQueryManagement.get(tfq.getId()).get().getAutoAssignDistributionSet()).isEqualTo(set); + final TargetFilterQuery updatedFilterQuery = targetFilterQueryManagement.get(tfq.getId()).get(); + + assertThat(updatedFilterQuery.getAutoAssignDistributionSet()).isEqualTo(set); + assertThat(updatedFilterQuery.getAutoAssignActionType()).isEqualTo(ActionType.FORCED); mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS")) .andExpect(status().isOk()).andExpect(jsonPath(JSON_PATH_NAME, equalTo(dsName))); @@ -385,7 +496,10 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte mvc.perform(delete(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS")) .andExpect(status().isNoContent()); - assertThat(targetFilterQueryManagement.get(tfq.getId()).get().getAutoAssignDistributionSet()).isNull(); + final TargetFilterQuery filterQueryWithDeletedDs = targetFilterQueryManagement.get(tfq.getId()).get(); + + assertThat(filterQueryWithDeletedDs.getAutoAssignDistributionSet()).isNull(); + assertThat(filterQueryWithDeletedDs.getAutoAssignActionType()).isNull(); mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS")) .andExpect(status().isNoContent()); diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java index f550a5973..9e9159cbb 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java @@ -1674,9 +1674,8 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest metaData1.put(new JSONObject().put("key", knownKey1).put("value", knownValue1)); metaData1.put(new JSONObject().put("key", knownKey2).put("value", knownValue2)); - mvc.perform( - post("/rest/v1/targets/{targetId}/metadata", knownControllerId).accept(MediaType.APPLICATION_JSON) - .contentType(MediaType.APPLICATION_JSON).content(metaData1.toString())) + mvc.perform(post("/rest/v1/targets/{targetId}/metadata", knownControllerId).accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON).content(metaData1.toString())) .andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .andExpect(jsonPath("[0]key", equalTo(knownKey1))).andExpect(jsonPath("[0]value", equalTo(knownValue1))) @@ -1697,14 +1696,13 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest metaData2.put(new JSONObject().put("key", knownKey1 + i).put("value", knownValue1 + i)); } - mvc.perform( - post("/rest/v1/targets/{targetId}/metadata", knownControllerId).accept(MediaType.APPLICATION_JSON) - .contentType(MediaType.APPLICATION_JSON).content(metaData2.toString())) + mvc.perform(post("/rest/v1/targets/{targetId}/metadata", knownControllerId).accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON).content(metaData2.toString())) .andDo(MockMvcResultPrinter.print()).andExpect(status().isForbidden()); // verify that the number of meta data entries has not changed // (we cannot use the PAGE constant here as it tries to sort by ID) - assertThat(targetManagement.findMetaDataByControllerId(new PageRequest(0, Integer.MAX_VALUE), knownControllerId) + assertThat(targetManagement.findMetaDataByControllerId(PageRequest.of(0, Integer.MAX_VALUE), knownControllerId) .getTotalElements()).isEqualTo(metaData1.length()); } diff --git a/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java b/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java index 817733a9d..856e3a39e 100644 --- a/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java +++ b/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java @@ -76,7 +76,8 @@ public class ResponseExceptionHandler { ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_CONCURRENT_MODIFICATION, HttpStatus.CONFLICT); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MAINTENANCE_SCHEDULE_INVALID, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_ATTRIBUTES_INVALID, HttpStatus.BAD_REQUEST); - + ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_ACTION_TYPE_INVALID, HttpStatus.BAD_REQUEST); + ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID, HttpStatus.BAD_REQUEST); } private static HttpStatus getStatusOrDefault(final SpServerError error) { diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/DistributionSetsDocumentationTest.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/DistributionSetsDocumentationTest.java index db455c6f9..eb680bb3f 100644 --- a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/DistributionSetsDocumentationTest.java +++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/DistributionSetsDocumentationTest.java @@ -232,10 +232,10 @@ public class DistributionSetsDocumentationTest extends AbstractApiRestDocumentat + SpPermission.READ_REPOSITORY + " and " + SpPermission.READ_TARGET) public void getAutoAssignTargetFilterQueries() throws Exception { final DistributionSet set = testdataFactory.createUpdatedDistributionSet(); - targetFilterQueryManagement - .create(entityFactory.targetFilterQuery().create().name("filter1").query("name==a").set(set)); - targetFilterQueryManagement - .create(entityFactory.targetFilterQuery().create().name("filter2").query("name==b").set(set)); + targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("filter1").query("name==a") + .autoAssignDistributionSet(set)); + targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("filter2").query("name==b") + .autoAssignDistributionSet(set)); mockMvc.perform(get( MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/autoAssignTargetFilters", @@ -257,8 +257,8 @@ public class DistributionSetsDocumentationTest extends AbstractApiRestDocumentat + SpPermission.READ_REPOSITORY + " and " + SpPermission.READ_TARGET) public void getAutoAssignTargetFilterQueriesWithParameters() throws Exception { final DistributionSet set = testdataFactory.createUpdatedDistributionSet(); - targetFilterQueryManagement - .create(entityFactory.targetFilterQuery().create().name("filter1").query("name==a").set(set)); + targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("filter1").query("name==a") + .autoAssignDistributionSet(set)); mockMvc.perform(get( MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + set.getId() + "/autoAssignTargetFilters") diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/TargetFilterQueriesResourceDocumentationTest.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/TargetFilterQueriesResourceDocumentationTest.java index fa5c277c8..831122a97 100644 --- a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/TargetFilterQueriesResourceDocumentationTest.java +++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/TargetFilterQueriesResourceDocumentationTest.java @@ -23,6 +23,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.HashMap; import java.util.Map; +import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; @@ -76,7 +77,11 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes fieldWithPath("content[].query").description(MgmtApiModelProperties.TARGET_FILTER_QUERY), fieldWithPath("content[].autoAssignDistributionSet") .description(MgmtApiModelProperties.TARGET_FILTER_QUERY_AUTO_ASSIGN_DS_ID) - .type("Number"), + .type(JsonFieldType.NUMBER.toString()), + fieldWithPath("content[].autoAssignActionType") + .description(MgmtApiModelProperties.ACTION_FORCE_TYPE) + .type(JsonFieldType.STRING.toString()) + .attributes(key("value").value("['forced', 'soft']")), fieldWithPath("content[].createdAt").description(ApiModelPropertiesGeneric.CREATED_AT), fieldWithPath("content[].createdBy").description(ApiModelPropertiesGeneric.CREATED_BY), fieldWithPath("content[].lastModifiedAt") @@ -180,7 +185,8 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes public void postAutoAssignDS() throws Exception { final TargetFilterQuery tfq = createTargetFilterQuery(); final DistributionSet distributionSet = createDistributionSet(); - final String filterByDistSet = "{\"id\":\"" + distributionSet.getId() + "\"}"; + final String filterByDistSet = "{\"id\":\"" + distributionSet.getId() + "\", \"type\":\"" + + MgmtActionType.SOFT.getName() + "\"}"; this.mockMvc .perform( @@ -190,7 +196,10 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes .andDo(this.document.document( pathParameters(parameterWithName("targetFilterQueryId") .description(ApiModelPropertiesGeneric.ITEM_ID)), - requestFields(requestFieldWithPath("id").description(MgmtApiModelProperties.DS_ID)), + requestFields(requestFieldWithPath("id").description(MgmtApiModelProperties.DS_ID), + optionalRequestFieldWithPath("type") + .description(MgmtApiModelProperties.ACTION_FORCE_TYPE) + .attributes(key("value").value("['forced', 'soft']"))), getResponseFieldTargetFilterQuery(false))); } @@ -213,7 +222,11 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes fieldWithPath(arrayPrefix + "name").description(ApiModelPropertiesGeneric.NAME), fieldWithPath(arrayPrefix + "query").description(MgmtApiModelProperties.TARGET_FILTER_QUERY), fieldWithPath(arrayPrefix + "autoAssignDistributionSet") - .description(MgmtApiModelProperties.TARGET_FILTER_QUERY_AUTO_ASSIGN_DS_ID).type("Number"), + .description(MgmtApiModelProperties.TARGET_FILTER_QUERY_AUTO_ASSIGN_DS_ID) + .type(JsonFieldType.NUMBER.toString()), + fieldWithPath(arrayPrefix + "autoAssignActionType") + .description(MgmtApiModelProperties.ACTION_FORCE_TYPE).type(JsonFieldType.STRING.toString()) + .attributes(key("value").value("['forced', 'soft']")), fieldWithPath(arrayPrefix + "createdAt").description(ApiModelPropertiesGeneric.CREATED_AT), fieldWithPath(arrayPrefix + "createdBy").description(ApiModelPropertiesGeneric.CREATED_BY), fieldWithPath(arrayPrefix + "lastModifiedAt").description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT), diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/AbstractMetadataPopupLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/AbstractMetadataPopupLayout.java index 8df39a88e..1217dc9ce 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/AbstractMetadataPopupLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/AbstractMetadataPopupLayout.java @@ -62,33 +62,24 @@ import com.vaadin.ui.renderers.ClickableRenderer.RendererClickEvent; * */ public abstract class AbstractMetadataPopupLayout extends CustomComponent { + private static final long serialVersionUID = 1L; private static final String DELETE_BUTTON = "DELETE_BUTTON"; - - private static final long serialVersionUID = -1491218218453167613L; + private static final int INPUT_DEBOUNCE_TIMEOUT = 250; protected static final String VALUE = "value"; - protected static final String KEY = "key"; - protected static final int MAX_METADATA_QUERY = 500; protected VaadinMessageSource i18n; - private final UINotification uiNotification; - protected transient EventBus.UIEventBus eventBus; private TextField keyTextField; - private TextArea valueTextArea; - private Button addIcon; - private Grid metaDataGrid; - private Label headerCaption; - private CommonDialogWindow metadataWindow; private E selectedEntity; @@ -238,7 +229,8 @@ public abstract class AbstractMetadataPopupLayout field) { Object currentValue = field.getValue(); if (field instanceof Table) { @@ -333,7 +333,7 @@ public class CommonDialogWindow extends Window { requiredComponents.addAll(allComponents.stream().filter(this::hasNullValidator).collect(Collectors.toList())); for (final AbstractField field : requiredComponents) { - Object value = getCurrentVaue(currentChangedComponent, newValue, field); + Object value = getCurrentValue(currentChangedComponent, newValue, field); if (Set.class.equals(field.getType())) { value = emptyToNull((Collection) value); @@ -343,10 +343,12 @@ public class CommonDialogWindow extends Window { return false; } - // We need to loop through the entire loop for validity testing. - // Otherwise the UI will only mark the - // first field with errors and then stop. If there are several - // fields with errors, this is bad. + // We need to loop through all of components for validity testing. + // Otherwise the UI will only mark the first field with errors and + // then stop. Setting the value is necessary because not all + // required input fields have empty string validator, but emptiness + // is checked during isValid() call. Setting the value could be + // redundant, check if it could be removed in the future. field.setValue(value); if (!field.isValid()) { valid = false; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/TargetMetadataDetailsLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/TargetMetadataDetailsLayout.java index de77fd50e..aae34d46f 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/TargetMetadataDetailsLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/TargetMetadataDetailsLayout.java @@ -64,7 +64,7 @@ public class TargetMetadataDetailsLayout extends AbstractMetadataDetailsLayout { } selectedTargetId = target.getId(); final List targetMetadataList = targetManagement - .findMetaDataByControllerId(new PageRequest(0, MAX_METADATA_QUERY), target.getControllerId()) + .findMetaDataByControllerId(PageRequest.of(0, MAX_METADATA_QUERY), target.getControllerId()) .getContent(); if (targetMetadataList != null && !targetMetadataList.isEmpty()) { targetMetadataList.forEach(this::setMetadataProperties); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyTargetFilter.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyTargetFilter.java index 75d8d343c..538855ea2 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyTargetFilter.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyTargetFilter.java @@ -8,6 +8,8 @@ */ package org.eclipse.hawkbit.ui.components; +import org.eclipse.hawkbit.repository.model.Action.ActionType; + /** * * @@ -15,7 +17,7 @@ package org.eclipse.hawkbit.ui.components; */ public class ProxyTargetFilter { - private static final long serialVersionUID = 6622060929679084419L; + private static final long serialVersionUID = 1L; private String createdDate; @@ -27,6 +29,7 @@ public class ProxyTargetFilter { private String lastModifiedBy; private String query; private ProxyDistribution autoAssignDistributionSet; + private ActionType autoAssignActionType; public String getCreatedDate() { return createdDate; @@ -95,7 +98,15 @@ public class ProxyTargetFilter { return autoAssignDistributionSet; } - public void setAutoAssignDistributionSet(ProxyDistribution autoAssignDistributionSet) { + public void setAutoAssignDistributionSet(final ProxyDistribution autoAssignDistributionSet) { this.autoAssignDistributionSet = autoAssignDistributionSet; } + + public ActionType getAutoAssignActionType() { + return autoAssignActionType; + } + + public void setAutoAssignActionType(final ActionType autoAssignActionType) { + this.autoAssignActionType = autoAssignActionType; + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/ManageDistBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/ManageDistBeanQuery.java index 582724f79..051e4d65c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/ManageDistBeanQuery.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/ManageDistBeanQuery.java @@ -8,14 +8,11 @@ */ package org.eclipse.hawkbit.ui.distributions.dstable; -import java.io.IOException; -import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.eclipse.hawkbit.repository.DistributionSetManagement; -import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetFilter; import org.eclipse.hawkbit.repository.model.DistributionSetFilter.DistributionSetFilterBuilder; @@ -26,12 +23,15 @@ import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SpringContextHelper; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.util.StringUtils; import org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery; import org.vaadin.addons.lazyquerycontainer.QueryDefinition; +import com.vaadin.data.util.filter.SimpleStringFilter; + /** * Manage Distributions table bean query. * @@ -42,6 +42,7 @@ public class ManageDistBeanQuery extends AbstractBeanQuery { private Sort sort = new Sort(Direction.ASC, "id"); private String searchText; + private String filterString; private transient DistributionSetManagement distributionSetManagement; private transient Page firstPageDistributionSets; @@ -59,6 +60,17 @@ public class ManageDistBeanQuery extends AbstractBeanQuery { final Object[] sortPropertyIds, final boolean[] sortStates) { super(definition, queryConfig, sortPropertyIds, sortStates); + init(definition, queryConfig, sortPropertyIds, sortStates); + } + + private void init(final QueryDefinition definition, final Map queryConfig, + final Object[] sortPropertyIds, final boolean[] sortStates) { + populateDataFromQueryConfig(queryConfig); + setFilterString(definition); + setupSorting(sortPropertyIds, sortStates); + } + + private void populateDataFromQueryConfig(final Map queryConfig) { if (HawkbitCommonUtil.isNotNullOrEmpty(queryConfig)) { searchText = (String) queryConfig.get(SPUIDefinitions.FILTER_BY_TEXT); if (!StringUtils.isEmpty(searchText)) { @@ -72,13 +84,24 @@ public class ManageDistBeanQuery extends AbstractBeanQuery { dsComplete = (Boolean) queryConfig.get(SPUIDefinitions.FILTER_BY_DS_COMPLETE); } } + } + private void setFilterString(final QueryDefinition definition) { + // if search text is set, we do not want to apply the filter + if (StringUtils.isEmpty(searchText)) { + filterString = definition.getFilters().stream().filter(SimpleStringFilter.class::isInstance) + .map(SimpleStringFilter.class::cast).map(SimpleStringFilter::getFilterString).findAny() + .orElse(null); + } + } + + private void setupSorting(final Object[] sortPropertyIds, final boolean[] sortStates) { if (sortStates != null && sortStates.length > 0) { // Initialize sort sort = new Sort(sortStates[0] ? Direction.ASC : Direction.DESC, (String) sortPropertyIds[0]); // Add sort for (int distId = 1; distId < sortPropertyIds.length; distId++) { - sort.and(new Sort(sortStates[distId] ? Direction.ASC : Direction.DESC, + sort = sort.and(new Sort(sortStates[distId] ? Direction.ASC : Direction.DESC, (String) sortPropertyIds[distId])); } } @@ -93,18 +116,11 @@ public class ManageDistBeanQuery extends AbstractBeanQuery { protected List loadBeans(final int startIndex, final int count) { Page distBeans; final List proxyDistributions = new ArrayList<>(); + if (startIndex == 0 && firstPageDistributionSets != null) { distBeans = firstPageDistributionSets; - } else if (StringUtils.isEmpty(searchText)) { - // if no search filters available - distBeans = getDistributionSetManagement() - .findByCompleted(new OffsetBasedPageRequest(startIndex, count, sort), dsComplete); } else { - final DistributionSetFilter distributionSetFilter = new DistributionSetFilterBuilder().setIsDeleted(false) - .setIsComplete(dsComplete).setSearchText(searchText).setSelectDSWithNoTag(Boolean.FALSE) - .setType(distributionSetType).build(); - distBeans = getDistributionSetManagement().findByDistributionSetFilter( - PageRequest.of(startIndex / count, count, sort), distributionSetFilter); + distBeans = findDistBeans(PageRequest.of(startIndex / count, count, sort)); } for (final DistributionSet distributionSet : distBeans) { @@ -121,17 +137,7 @@ public class ManageDistBeanQuery extends AbstractBeanQuery { @Override public int size() { - if (StringUtils.isEmpty(searchText) && distributionSetType == null) { - // if no search filters available - firstPageDistributionSets = getDistributionSetManagement() - .findByCompleted(PageRequest.of(0, SPUIDefinitions.PAGE_SIZE, sort), dsComplete); - } else { - final DistributionSetFilter distributionSetFilter = new DistributionSetFilterBuilder().setIsDeleted(false) - .setIsComplete(dsComplete).setSearchText(searchText).setSelectDSWithNoTag(Boolean.FALSE) - .setType(distributionSetType).build(); - firstPageDistributionSets = getDistributionSetManagement().findByDistributionSetFilter( - PageRequest.of(0, SPUIDefinitions.PAGE_SIZE, sort), distributionSetFilter); - } + firstPageDistributionSets = findDistBeans(PageRequest.of(0, SPUIDefinitions.PAGE_SIZE, sort)); final long size = firstPageDistributionSets.getTotalElements(); if (size > Integer.MAX_VALUE) { @@ -141,16 +147,23 @@ public class ManageDistBeanQuery extends AbstractBeanQuery { return (int) size; } + private Page findDistBeans(final Pageable pageable) { + if (StringUtils.isEmpty(filterString) && StringUtils.isEmpty(searchText) && distributionSetType == null) { + return getDistributionSetManagement().findByCompleted(pageable, dsComplete); + } else { + final DistributionSetFilter distributionSetFilter = new DistributionSetFilterBuilder() + .setIsDeleted(Boolean.FALSE).setIsComplete(dsComplete).setSearchText(searchText) + .setFilterString(filterString).setSelectDSWithNoTag(Boolean.FALSE).setType(distributionSetType) + .build(); + + return getDistributionSetManagement().findByDistributionSetFilter(pageable, distributionSetFilter); + } + } + private DistributionSetManagement getDistributionSetManagement() { if (distributionSetManagement == null) { distributionSetManagement = SpringContextHelper.getBean(DistributionSetManagement.class); } return distributionSetManagement; } - - @SuppressWarnings("unchecked") - private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - firstPageDistributionSets = (Page) in.readObject(); - } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectComboBox.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectComboBox.java new file mode 100644 index 000000000..5ef33775d --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectComboBox.java @@ -0,0 +1,336 @@ +/** + * Copyright (c) 2018 Bosch Software Innovations 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.filtermanagement; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.hawkbit.ui.distributions.dstable.ManageDistBeanQuery; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; +import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; +import org.springframework.util.StringUtils; +import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory; +import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer; +import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition; +import org.vaadin.addons.lazyquerycontainer.LazyQueryView; +import org.vaadin.addons.lazyquerycontainer.QueryDefinition; +import org.vaadin.addons.lazyquerycontainer.QueryView; + +import com.vaadin.data.Container; +import com.vaadin.data.Item; +import com.vaadin.data.Property; +import com.vaadin.shared.ui.combobox.FilteringMode; +import com.vaadin.ui.ComboBox; + +/** + * Creates a combobox in order to select the distribution set for a target + * filter query auto assignment. + */ +public class DistributionSetSelectComboBox extends ComboBox { + private static final long serialVersionUID = 1L; + + private final VaadinMessageSource i18n; + private String selectedValueCaption; + private Long previousValue; + private String lastFilterString; + + DistributionSetSelectComboBox(final VaadinMessageSource i18n) { + super(); + this.i18n = i18n; + + init(); + initDataSource(); + } + + private void init() { + setScrollToSelectedItem(false); + setNullSelectionAllowed(false); + setSizeFull(); + setId(UIComponentIdProvider.DIST_SET_SELECT_COMBO_ID); + setCaption(i18n.getMessage(UIMessageIdProvider.HEADER_DISTRIBUTION_SET)); + } + + private void initDataSource() { + final Container container = createContainer(); + container.addContainerProperty(SPUILabelDefinitions.VAR_NAME_VERSION, String.class, null); + + setItemCaptionMode(ItemCaptionMode.PROPERTY); + setItemCaptionPropertyId(SPUILabelDefinitions.VAR_NAME_VERSION); + setFilteringMode(FilteringMode.CONTAINS); + + setContainerDataSource(container); + } + + private static Container createContainer() { + final Map queryConfig = new HashMap<>(); + queryConfig.put(SPUIDefinitions.FILTER_BY_DS_COMPLETE, Boolean.TRUE); + + final BeanQueryFactory distributionQF = new BeanQueryFactory<>(ManageDistBeanQuery.class); + distributionQF.setQueryConfiguration(queryConfig); + + final LazyQueryDefinition distribtuinQD = new LazyQueryDefinition(false, SPUIDefinitions.PAGE_SIZE, + SPUILabelDefinitions.VAR_ID); + + final QueryView distributionSetFilterLazyQueryView = new DistributionSetFilterQueryView( + new LazyQueryView(distribtuinQD, distributionQF)); + distributionSetFilterLazyQueryView.sort( + new Object[] { SPUILabelDefinitions.VAR_NAME, SPUILabelDefinitions.VAR_VERSION }, + new boolean[] { true, true }); + + return new LazyQueryContainer(distributionSetFilterLazyQueryView); + } + + /** + * The custom QueryView implementation is only needed to modify the behavior + * when removing the filter (do not refresh the container). In all other + * cases the default LazyQueryView implementation is being reused. + */ + private static class DistributionSetFilterQueryView implements QueryView { + private final QueryView defaultQueryView; + + DistributionSetFilterQueryView(final QueryView defaultQueryView) { + this.defaultQueryView = defaultQueryView; + } + + @Override + public void addFilter(final Filter arg0) { + defaultQueryView.addFilter(arg0); + } + + @Override + public int addItem() { + return defaultQueryView.addItem(); + } + + @Override + public void commit() { + defaultQueryView.commit(); + } + + @Override + public void discard() { + defaultQueryView.discard(); + } + + @Override + public List getAddedItems() { + return defaultQueryView.getAddedItems(); + } + + @Override + public Collection getFilters() { + return defaultQueryView.getFilters(); + } + + @Override + public Item getItem(final int arg0) { + return defaultQueryView.getItem(arg0); + } + + @Override + public List getItemIdList() { + return defaultQueryView.getItemIdList(); + } + + @Override + public int getMaxCacheSize() { + return defaultQueryView.getMaxCacheSize(); + } + + @Override + public List getModifiedItems() { + return defaultQueryView.getModifiedItems(); + } + + @Override + public QueryDefinition getQueryDefinition() { + return defaultQueryView.getQueryDefinition(); + } + + @Override + public List getRemovedItems() { + return defaultQueryView.getRemovedItems(); + } + + @Override + public boolean isModified() { + return defaultQueryView.isModified(); + } + + @Override + public void refresh() { + defaultQueryView.refresh(); + } + + @Override + public void removeAllItems() { + defaultQueryView.removeAllItems(); + } + + /** + * Default implementation of the combobox removes the filter each time + * it builds the options during repaint. However, container should not + * be refreshed here (default LazyQueryView implementation), as this + * would clear all filtered cache entries following by multiple database + * queries. + */ + @Override + public void removeFilter(final Filter filter) { + defaultQueryView.getQueryDefinition().removeFilter(filter); + // no refresh here + } + + @Override + public void removeFilters() { + defaultQueryView.removeFilters(); + } + + @Override + public void removeItem(final int arg0) { + defaultQueryView.removeItem(arg0); + } + + @Override + public void setMaxCacheSize(final int arg0) { + defaultQueryView.setMaxCacheSize(arg0); + } + + @Override + public int size() { + return defaultQueryView.size(); + } + + @Override + public void sort(final Object[] arg0, final boolean[] arg1) { + defaultQueryView.sort(arg0, arg1); + } + } + + /** + * Overriden in order to get the selected distibution set's option caption + * (name:version) from container and preventing multiple calls by saving the + * selected Id. + * + * @param selectedItemId + * the Id of the selected distribution set + */ + @Override + public void setValue(final Object selectedItemId) { + if (selectedItemId != null) { + // Can happen during validation, leading to multiple database + // queries, in order to get the caption property + if (selectedItemId.equals(previousValue)) { + return; + } + selectedValueCaption = Optional.ofNullable(getContainerProperty(selectedItemId, getItemCaptionPropertyId())) + .map(Property::getValue).map(String.class::cast).orElse(""); + } + + super.setValue(selectedItemId); + previousValue = (Long) selectedItemId; + } + + /** + * Overriden in order to return the caption for the selected distribution + * set from cache. Otherwise, it could lead to multiple database queries, + * trying to retrieve the caption from container, when it is not present in + * filtered options. + * + * @param itemId + * the Id of the selected distribution set + * @return the option caption (name:version) of the selected distribution + * set + */ + @Override + public String getItemCaption(final Object itemId) { + if (itemId != null && itemId.equals(getValue()) && !StringUtils.isEmpty(selectedValueCaption)) { + return selectedValueCaption; + } + + return super.getItemCaption(itemId); + } + + /** + * Overriden not to update the filter when the filterstring (value of + * combobox input) was not changed. Otherwise, it would lead to additional + * database requests during combobox page change while scrolling instead of + * retreiving items from container cache. + * + * @param filterString + * value of combobox input + * @param filteringMode + * the filtering mode (starts_with, contains) + * @return SimpleStringFilter to transfer filterstring in container + */ + @Override + protected Filter buildFilter(final String filterString, final FilteringMode filteringMode) { + if (filterStringIsNotChanged(filterString)) { + return null; + } + + final Filter filter = super.buildFilter(filterString, filteringMode); + + refreshContainerIfFilterStringBecomesEmpty(filterString); + + lastFilterString = filterString; + return filter; + } + + private boolean filterStringIsNotChanged(final String filterString) { + return !StringUtils.isEmpty(filterString) && !StringUtils.isEmpty(lastFilterString) + && filterString.equals(lastFilterString); + } + + private void refreshContainerIfFilterStringBecomesEmpty(final String filterString) { + if (StringUtils.isEmpty(filterString) && !StringUtils.isEmpty(lastFilterString)) { + refreshContainer(); + } + } + + /** + * Before setting the value of the selected distribution set we need to + * initialize the container and apply the right filter in order to limit the + * number of entities and save them in container cache. Otherwise, combobox + * will try to find the corresponding id from container, leading to multiple + * database queries. + * + * @param initialFilterString + * value of initial distribution set caption (name:version) + * @return the size of filtered options + */ + public int setInitialValueFilter(final String initialFilterString) { + final Filter filter = buildFilter(initialFilterString, getFilteringMode()); + + if (filter != null) { + final LazyQueryContainer container = (LazyQueryContainer) getContainerDataSource(); + try { + container.addContainerFilter(filter); + return container.size(); + } finally { + container.removeContainerFilter(filter); + } + } + + return 0; + } + + /** + * Refreshes the underlying container, clearing all the caches. + */ + public void refreshContainer() { + final LazyQueryContainer container = (LazyQueryContainer) getContainerDataSource(); + container.refresh(); + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectTable.java deleted file mode 100644 index fd296d241..000000000 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectTable.java +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations 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.filtermanagement; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.eclipse.hawkbit.repository.event.remote.DistributionSetDeletedEvent; -import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent; -import org.eclipse.hawkbit.ui.distributions.dstable.ManageDistBeanQuery; -import org.eclipse.hawkbit.ui.distributions.state.ManageDistUIState; -import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; -import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; -import org.eclipse.hawkbit.ui.utils.TableColumn; -import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; -import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; -import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; -import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory; -import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer; -import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition; -import org.vaadin.spring.events.EventBus.UIEventBus; -import org.vaadin.spring.events.EventScope; -import org.vaadin.spring.events.annotation.EventBusListenerMethod; - -import com.vaadin.data.Container; -import com.vaadin.ui.Table; -import com.vaadin.ui.themes.ValoTheme; - -/** - * Table for selecting a distribution set. - */ -public class DistributionSetSelectTable extends Table { - - private static final long serialVersionUID = -4307487829435471759L; - - private final VaadinMessageSource i18n; - - private final ManageDistUIState manageDistUIState; - - private Container container; - - DistributionSetSelectTable(final VaadinMessageSource i18n, final UIEventBus eventBus, - final ManageDistUIState manageDistUIState) { - this.i18n = i18n; - this.manageDistUIState = manageDistUIState; - setStyleName("sp-table"); - setSizeFull(); - setSelectable(true); - setMultiSelect(false); - setImmediate(true); - addStyleName(ValoTheme.TABLE_NO_VERTICAL_LINES); - addStyleName(ValoTheme.TABLE_SMALL); - populateTableData(); - setColumnCollapsingAllowed(false); - setColumnProperties(); - setId(UIComponentIdProvider.DIST_SET_SELECT_TABLE_ID); - eventBus.subscribe(this); - } - - @EventBusListenerMethod(scope = EventScope.UI) - void onEvents(final List events) { - final Object firstEvent = events.get(0); - if (DistributionSetCreatedEvent.class.isInstance(firstEvent) - || DistributionSetDeletedEvent.class.isInstance(firstEvent)) { - refreshDistributions(); - } - } - - private void populateTableData() { - container = createContainer(); - addContainerproperties(); - setContainerDataSource(container); - setColumnProperties(); - - } - - protected Container createContainer() { - - final Map queryConfiguration = prepareQueryConfigFilters(); - final BeanQueryFactory distributionQF = new BeanQueryFactory<>(ManageDistBeanQuery.class); - - distributionQF.setQueryConfiguration(queryConfiguration); - return new LazyQueryContainer( - new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, SPUILabelDefinitions.VAR_ID), distributionQF); - } - - private void addContainerproperties() { - /* Create HierarchicalContainer container */ - container.addContainerProperty(SPUILabelDefinitions.NAME, String.class, null); - container.addContainerProperty(SPUILabelDefinitions.VAR_VERSION, String.class, null); - } - - private List getVisbleColumns() { - final List columnList = new ArrayList<>(2); - columnList.add(new TableColumn(SPUILabelDefinitions.NAME, i18n.getMessage("header.name"), 0.6F)); - columnList.add(new TableColumn(SPUILabelDefinitions.VAR_VERSION, i18n.getMessage("header.version"), 0.4F)); - return columnList; - - } - - private void setColumnProperties() { - setVisibleColumns(getVisbleColumns().stream().map(column -> { - setColumnHeader(column.getColumnPropertyId(), column.getColumnHeader()); - setColumnExpandRatio(column.getColumnPropertyId(), column.getExpandRatio()); - return column.getColumnPropertyId(); - }).toArray()); - } - - private Map prepareQueryConfigFilters() { - final Map queryConfig = new HashMap<>(); - manageDistUIState.getManageDistFilters().getSearchText() - .ifPresent(value -> queryConfig.put(SPUIDefinitions.FILTER_BY_TEXT, value)); - - if (null != manageDistUIState.getManageDistFilters().getClickedDistSetType()) { - queryConfig.put(SPUIDefinitions.FILTER_BY_DISTRIBUTION_SET_TYPE, - manageDistUIState.getManageDistFilters().getClickedDistSetType()); - } - - queryConfig.put(SPUIDefinitions.FILTER_BY_DS_COMPLETE, Boolean.TRUE); - - return queryConfig; - } - - private void refreshDistributions() { - final LazyQueryContainer dsContainer = (LazyQueryContainer) getContainerDataSource(); - final int size = dsContainer.size(); - if (size < SPUIDefinitions.MAX_TABLE_ENTRIES) { - refreshTablecontainer(); - } - if (size != 0) { - setData(i18n.getMessage(UIMessageIdProvider.MESSAGE_DATA_AVAILABLE)); - } - } - - private Object getItemIdToSelect() { - return manageDistUIState.getSelectedDistributions().isEmpty() ? null - : manageDistUIState.getSelectedDistributions(); - } - - private void selectRow() { - setValue(getItemIdToSelect()); - } - - private void refreshTablecontainer() { - final LazyQueryContainer dsContainer = (LazyQueryContainer) getContainerDataSource(); - dsContainer.refresh(); - selectRow(); - } - -} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectWindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectWindow.java index 4445b0268..ad3a9c59f 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectWindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectWindow.java @@ -8,27 +8,30 @@ */ package org.eclipse.hawkbit.ui.filtermanagement; -import java.io.Serializable; +import java.util.function.Consumer; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.ui.common.CommonDialogWindow; import org.eclipse.hawkbit.ui.common.builder.WindowBuilder; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleNoBorderWithIcon; -import org.eclipse.hawkbit.ui.distributions.state.ManageDistUIState; import org.eclipse.hawkbit.ui.filtermanagement.event.CustomFilterUIEvent; +import org.eclipse.hawkbit.ui.management.miscs.AbstractActionTypeOptionGroupLayout.ActionTypeOption; +import org.eclipse.hawkbit.ui.management.miscs.ActionTypeOptionGroupAutoAssignmentLayout; +import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; +import org.eclipse.hawkbit.ui.utils.UINotification; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.vaadin.spring.events.EventBus; import org.vaadin.spring.events.EventBus.UIEventBus; -import com.vaadin.data.Property; import com.vaadin.server.FontAwesome; import com.vaadin.server.Sizeable; import com.vaadin.ui.Alignment; @@ -44,59 +47,50 @@ import com.vaadin.ui.Window; * Creates a dialog window to select the distribution set for a target filter * query. */ -public class DistributionSetSelectWindow - implements CommonDialogWindow.SaveDialogCloseListener, Property.ValueChangeListener { - - private static final long serialVersionUID = 4752345414134989396L; +public class DistributionSetSelectWindow implements CommonDialogWindow.SaveDialogCloseListener { private final VaadinMessageSource i18n; - - private final DistributionSetSelectTable dsTable; - - private final transient EventBus.UIEventBus eventBus; - - private final transient TargetManagement targetManagement; - - private final transient TargetFilterQueryManagement targetFilterQueryManagement; + private final UINotification notification; + private final EventBus.UIEventBus eventBus; + private final TargetManagement targetManagement; + private final TargetFilterQueryManagement targetFilterQueryManagement; private CheckBox checkBox; + private ActionTypeOptionGroupAutoAssignmentLayout actionTypeOptionGroupLayout; + private DistributionSetSelectComboBox dsCombo; private Long tfqId; DistributionSetSelectWindow(final VaadinMessageSource i18n, final UIEventBus eventBus, - final TargetManagement targetManagement, final TargetFilterQueryManagement targetFilterQueryManagement, - final ManageDistUIState manageDistUIState) { + final UINotification notification, final TargetManagement targetManagement, + final TargetFilterQueryManagement targetFilterQueryManagement) { this.i18n = i18n; - this.dsTable = new DistributionSetSelectTable(i18n, eventBus, manageDistUIState); + this.notification = notification; this.eventBus = eventBus; this.targetManagement = targetManagement; this.targetFilterQueryManagement = targetFilterQueryManagement; } private VerticalLayout initView() { - final Label label = new Label(i18n.getMessage("label.auto.assign.description")); + final Label label = new Label(i18n.getMessage(UIMessageIdProvider.LABEL_AUTO_ASSIGNMENT_DESC)); - checkBox = new CheckBox(i18n.getMessage("label.auto.assign.enable")); + checkBox = new CheckBox(i18n.getMessage(UIMessageIdProvider.LABEL_AUTO_ASSIGNMENT_ENABLE)); checkBox.setId(UIComponentIdProvider.DIST_SET_SELECT_ENABLE_ID); checkBox.setImmediate(true); - checkBox.addValueChangeListener(this); + checkBox.addValueChangeListener( + event -> switchAutoAssignmentInputsVisibility((boolean) event.getProperty().getValue())); - setTableEnabled(false); + actionTypeOptionGroupLayout = new ActionTypeOptionGroupAutoAssignmentLayout(i18n); + dsCombo = new DistributionSetSelectComboBox(i18n); final VerticalLayout verticalLayout = new VerticalLayout(); verticalLayout.addComponent(label); verticalLayout.addComponent(checkBox); - verticalLayout.addComponent(dsTable); + verticalLayout.addComponent(actionTypeOptionGroupLayout); + verticalLayout.addComponent(dsCombo); return verticalLayout; } - private void setValue(final Long distSet) { - checkBox.setValue(distSet != null); - dsTable.setValue(distSet); - dsTable.setCurrentPageFirstItemId(distSet); - dsTable.setNullSelectionAllowed(false); - } - /** * Shows a distribution set select window for the given target filter query * @@ -111,15 +105,13 @@ public class DistributionSetSelectWindow final VerticalLayout verticalLayout = initView(); final DistributionSet distributionSet = tfq.getAutoAssignDistributionSet(); - if (distributionSet != null) { - setValue(distributionSet.getId()); - } else { - setValue(null); - } + final ActionType actionType = tfq.getAutoAssignActionType(); + + setInitialControlValues(distributionSet, actionType); // build window after values are set to view elements final CommonDialogWindow window = new WindowBuilder(SPUIDefinitions.CREATE_UPDATE_WINDOW) - .caption(i18n.getMessage("caption.select.auto.assign.dist")).content(verticalLayout) + .caption(i18n.getMessage(UIMessageIdProvider.CAPTION_SELECT_AUTO_ASSIGN_DS)).content(verticalLayout) .layout(verticalLayout).i18n(i18n).saveDialogCloseListener(this).buildCommonDialogWindow(); window.setId(UIComponentIdProvider.DIST_SET_SELECT_WINDOW_ID); @@ -128,25 +120,36 @@ public class DistributionSetSelectWindow window.setVisible(true); } - /** - * Is triggered when the checkbox value changes - * - * @param event - * change event - */ - @Override - public void valueChange(final Property.ValueChangeEvent event) { - if (checkBox.getValue()) { - setTableEnabled(true); - } else { - dsTable.select(null); - setTableEnabled(false); + private void setInitialControlValues(final DistributionSet distributionSet, final ActionType actionType) { + checkBox.setValue(distributionSet != null); + switchAutoAssignmentInputsVisibility(distributionSet != null); + + final ActionTypeOption actionTypeToSet = ActionTypeOption.getOptionForActionType(actionType) + .orElse(ActionTypeOption.FORCED); + actionTypeOptionGroupLayout.getActionTypeOptionGroup().select(actionTypeToSet); + + if (distributionSet != null) { + final String initialFilterString = HawkbitCommonUtil.getFormattedNameVersion(distributionSet.getName(), + distributionSet.getVersion()); + final int filteredSize = dsCombo.setInitialValueFilter(initialFilterString); + + if (filteredSize <= 0) { + notification.displayValidationError( + i18n.getMessage(UIMessageIdProvider.MESSAGE_SELECTED_DS_NOT_FOUND, initialFilterString)); + dsCombo.refreshContainer(); + dsCombo.setValue(null); + return; + } } + dsCombo.setValue(distributionSet != null ? distributionSet.getId() : null); } - private void setTableEnabled(final boolean enabled) { - dsTable.setEnabled(enabled); - dsTable.setRequired(enabled); + private void switchAutoAssignmentInputsVisibility(final boolean autoAssignmentEnabled) { + actionTypeOptionGroupLayout.setVisible(autoAssignmentEnabled); + + dsCombo.setVisible(autoAssignmentEnabled); + dsCombo.setEnabled(autoAssignmentEnabled); + dsCombo.setRequired(autoAssignmentEnabled); } /** @@ -164,7 +167,7 @@ public class DistributionSetSelectWindow } private boolean isAutoAssignmentEnabledAndDistributionSetSelected() { - return checkBox.getValue() && dsTable.getValue() != null; + return checkBox.getValue() && dsCombo.getValue() != null; } private boolean isAutoAssignmentDisabled() { @@ -177,38 +180,34 @@ public class DistributionSetSelectWindow */ @Override public void saveOrUpdate() { - if (checkBox.getValue() && dsTable.getValue() != null) { - updateTargetFilterQueryDS(tfqId, (Long) dsTable.getValue()); - + if (checkBox.getValue() && dsCombo.getValue() != null) { + final ActionType autoAssignActionType = ((ActionTypeOption) actionTypeOptionGroupLayout + .getActionTypeOptionGroup().getValue()).getActionType(); + updateTargetFilterQueryDS(tfqId, (Long) dsCombo.getValue(), autoAssignActionType); } else if (!checkBox.getValue()) { - updateTargetFilterQueryDS(tfqId, null); - + updateTargetFilterQueryDS(tfqId, null, null); } - } - private void updateTargetFilterQueryDS(final Long targetFilterQueryId, final Long dsId) { + private void updateTargetFilterQueryDS(final Long targetFilterQueryId, final Long dsId, + final ActionType actionType) { final TargetFilterQuery tfq = targetFilterQueryManagement.get(targetFilterQueryId) .orElseThrow(() -> new EntityNotFoundException(TargetFilterQuery.class, targetFilterQueryId)); if (dsId != null) { - confirmWithConsequencesDialog(tfq, dsId); + confirmWithConsequencesDialog(tfq, dsId, actionType); } else { targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryId, null); eventBus.publish(this, CustomFilterUIEvent.UPDATED_TARGET_FILTER_QUERY); } - } - private void confirmWithConsequencesDialog(final TargetFilterQuery tfq, final Long dsId) { - - final ConfirmConsequencesDialog dialog = new ConfirmConsequencesDialog(tfq, dsId, new ConfirmCallback() { - @Override - public void onConfirmResult(final boolean accepted) { - if (accepted) { - targetFilterQueryManagement.updateAutoAssignDS(tfq.getId(), dsId); - eventBus.publish(this, CustomFilterUIEvent.UPDATED_TARGET_FILTER_QUERY); - } + private void confirmWithConsequencesDialog(final TargetFilterQuery tfq, final Long dsId, + final ActionType actionType) { + final ConfirmConsequencesDialog dialog = new ConfirmConsequencesDialog(tfq, dsId, accepted -> { + if (accepted) { + targetFilterQueryManagement.updateAutoAssignDSWithActionType(tfq.getId(), dsId, actionType); + eventBus.publish(this, CustomFilterUIEvent.UPDATED_TARGET_FILTER_QUERY); } }); @@ -216,7 +215,6 @@ public class DistributionSetSelectWindow UI.getCurrent().addWindow(dialog); dialog.setVisible(true); - } /** @@ -224,26 +222,21 @@ public class DistributionSetSelectWindow * the */ private class ConfirmConsequencesDialog extends Window implements Button.ClickListener { - - private static final long serialVersionUID = 7738545414137389326L; + private static final long serialVersionUID = 1L; private final TargetFilterQuery targetFilterQuery; private final Long distributionSetId; - - private Button okButton; - - private final ConfirmCallback callback; + private final transient Consumer callback; public ConfirmConsequencesDialog(final TargetFilterQuery targetFilterQuery, final Long dsId, - final ConfirmCallback callback) { - super(i18n.getMessage("caption.confirm.assign.consequences")); + final Consumer callback) { + super(i18n.getMessage(UIMessageIdProvider.CAPTION_CONFIRM_AUTO_ASSIGN_CONSEQUENCES)); this.callback = callback; this.targetFilterQuery = targetFilterQuery; this.distributionSetId = dsId; init(); - } private void init() { @@ -260,9 +253,11 @@ public class DistributionSetSelectWindow targetFilterQuery.getQuery()); Label mainTextLabel; if (targetsCount == 0) { - mainTextLabel = new Label(i18n.getMessage("message.confirm.assign.consequences.none")); + mainTextLabel = new Label( + i18n.getMessage(UIMessageIdProvider.MESSAGE_CONFIRM_AUTO_ASSIGN_CONSEQUENCES_NONE)); } else { - mainTextLabel = new Label(i18n.getMessage("message.confirm.assign.consequences.text", targetsCount)); + mainTextLabel = new Label(i18n + .getMessage(UIMessageIdProvider.MESSAGE_CONFIRM_AUTO_ASSIGN_CONSEQUENCES_TEXT, targetsCount)); } layout.addComponent(mainTextLabel); @@ -273,7 +268,7 @@ public class DistributionSetSelectWindow buttonsLayout.addStyleName("actionButtonsMargin"); layout.addComponent(buttonsLayout); - okButton = SPUIComponentProvider.getButton(UIComponentIdProvider.SAVE_BUTTON, + final Button okButton = SPUIComponentProvider.getButton(UIComponentIdProvider.SAVE_BUTTON, i18n.getMessage(UIMessageIdProvider.BUTTON_OK), "", "", true, FontAwesome.SAVE, SPUIButtonStyleNoBorderWithIcon.class); okButton.setSizeUndefined(); @@ -292,24 +287,17 @@ public class DistributionSetSelectWindow buttonsLayout.addComponent(cancelButton); buttonsLayout.setComponentAlignment(cancelButton, Alignment.MIDDLE_LEFT); buttonsLayout.setExpandRatio(cancelButton, 1.0F); - } @Override public void buttonClick(final Button.ClickEvent event) { - if (event.getButton().getId().equals(okButton.getId())) { - callback.onConfirmResult(true); + if (event.getButton().getId().equals(UIComponentIdProvider.SAVE_BUTTON)) { + callback.accept(true); } else { - callback.onConfirmResult(false); + callback.accept(false); } close(); - } } - - @FunctionalInterface - private interface ConfirmCallback extends Serializable { - void onConfirmResult(boolean accepted); - } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterManagementView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterManagementView.java index 634198fa9..0c1a000ca 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterManagementView.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterManagementView.java @@ -17,7 +17,6 @@ import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.ui.AbstractHawkbitUI; import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.UiProperties; -import org.eclipse.hawkbit.ui.distributions.state.ManageDistUIState; import org.eclipse.hawkbit.ui.filtermanagement.event.CustomFilterUIEvent; import org.eclipse.hawkbit.ui.filtermanagement.footer.TargetFilterCountMessageLabel; import org.eclipse.hawkbit.ui.filtermanagement.state.FilterManagementUIState; @@ -68,11 +67,10 @@ public class FilterManagementView extends VerticalLayout implements View { final FilterManagementUIState filterManagementUIState, final TargetFilterQueryManagement targetFilterQueryManagement, final SpPermissionChecker permissionChecker, final UINotification notification, final UiProperties uiProperties, final EntityFactory entityFactory, - final AutoCompleteTextFieldComponent queryTextField, final ManageDistUIState manageDistUIState, - final TargetManagement targetManagement) { + final AutoCompleteTextFieldComponent queryTextField, final TargetManagement targetManagement) { this.targetFilterHeader = new TargetFilterHeader(eventBus, filterManagementUIState, permissionChecker, i18n); this.targetFilterTable = new TargetFilterTable(i18n, notification, eventBus, filterManagementUIState, - targetFilterQueryManagement, manageDistUIState, targetManagement, permissionChecker); + targetFilterQueryManagement, targetManagement, permissionChecker); this.createNewFilterHeader = new CreateOrUpdateFilterHeader(i18n, eventBus, filterManagementUIState, targetFilterQueryManagement, permissionChecker, notification, uiProperties, entityFactory, queryTextField); @@ -113,10 +111,10 @@ public class FilterManagementView extends VerticalLayout implements View { if (custFilterUIEvent == CustomFilterUIEvent.TARGET_FILTER_DETAIL_VIEW) { viewTargetFilterDetailLayout(); } else if (custFilterUIEvent == CustomFilterUIEvent.CREATE_NEW_FILTER_CLICK) { - this.getUI().access(() -> viewCreateTargetFilterLayout()); + this.getUI().access(this::viewCreateTargetFilterLayout); } else if (custFilterUIEvent == CustomFilterUIEvent.EXIT_CREATE_OR_UPDATE_FILTRER_VIEW || custFilterUIEvent == CustomFilterUIEvent.SHOW_FILTER_MANAGEMENT) { - UI.getCurrent().access(() -> viewListView()); + UI.getCurrent().access(this::viewListView); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterBeanQuery.java index c71e12d75..5f17f83fe 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterBeanQuery.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterBeanQuery.java @@ -104,6 +104,7 @@ public class TargetFilterBeanQuery extends AbstractBeanQuery final DistributionSet distributionSet = tarFilterQuery.getAutoAssignDistributionSet(); if (distributionSet != null) { proxyTarFilter.setAutoAssignDistributionSet(new ProxyDistribution(distributionSet)); + proxyTarFilter.setAutoAssignActionType(tarFilterQuery.getAutoAssignActionType()); } proxyTargetFilter.add(proxyTarFilter); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTable.java index 7f76af9ec..32d836a39 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTable.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTable.java @@ -16,12 +16,13 @@ import java.util.Map; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetManagement; +import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.common.ConfirmationDialog; import org.eclipse.hawkbit.ui.components.ProxyDistribution; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleNoBorder; -import org.eclipse.hawkbit.ui.distributions.state.ManageDistUIState; +import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleNoBorderWithIcon; import org.eclipse.hawkbit.ui.filtermanagement.event.CustomFilterUIEvent; import org.eclipse.hawkbit.ui.filtermanagement.state.FilterManagementUIState; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; @@ -68,7 +69,7 @@ public class TargetFilterTable extends Table { private final transient TargetFilterQueryManagement targetFilterQueryManagement; - private final DistributionSetSelectWindow dsSelectWindow; + private final transient DistributionSetSelectWindow dsSelectWindow; private final SpPermissionChecker permChecker; @@ -78,8 +79,8 @@ public class TargetFilterTable extends Table { public TargetFilterTable(final VaadinMessageSource i18n, final UINotification notification, final UIEventBus eventBus, final FilterManagementUIState filterManagementUIState, - final TargetFilterQueryManagement targetFilterQueryManagement, final ManageDistUIState manageDistUIState, - final TargetManagement targetManagement, final SpPermissionChecker permChecker) { + final TargetFilterQueryManagement targetFilterQueryManagement, final TargetManagement targetManagement, + final SpPermissionChecker permChecker) { this.i18n = i18n; this.notification = notification; this.eventBus = eventBus; @@ -87,8 +88,8 @@ public class TargetFilterTable extends Table { this.targetFilterQueryManagement = targetFilterQueryManagement; this.permChecker = permChecker; - this.dsSelectWindow = new DistributionSetSelectWindow(i18n, eventBus, targetManagement, - targetFilterQueryManagement, manageDistUIState); + this.dsSelectWindow = new DistributionSetSelectWindow(i18n, eventBus, notification, targetManagement, + targetFilterQueryManagement); setStyleName("sp-table"); setSizeFull(); @@ -110,7 +111,7 @@ public class TargetFilterTable extends Table { || filterEvent == CustomFilterUIEvent.FILTER_BY_CUST_FILTER_TEXT_REMOVE || filterEvent == CustomFilterUIEvent.CREATE_TARGET_FILTER_QUERY || filterEvent == CustomFilterUIEvent.UPDATED_TARGET_FILTER_QUERY) { - UI.getCurrent().access(() -> refreshContainer()); + UI.getCurrent().access(this::refreshContainer); } } @@ -237,14 +238,25 @@ public class TargetFilterTable extends Table { final Item row1 = getItem(itemId); final ProxyDistribution distSet = (ProxyDistribution) row1 .getItemProperty(SPUILabelDefinitions.AUTO_ASSIGN_DISTRIBUTION_SET).getValue(); + final ActionType actionType = (ActionType) row1.getItemProperty(SPUILabelDefinitions.AUTO_ASSIGN_ACTION_TYPE) + .getValue(); + final String buttonId = "distSetButton"; Button updateIcon; if (distSet == null) { - updateIcon = SPUIComponentProvider.getButton(buttonId, i18n.getMessage("button.no.auto.assignment"), - i18n.getMessage("button.auto.assignment.desc"), null, false, null, SPUIButtonStyleNoBorder.class); + updateIcon = SPUIComponentProvider.getButton(buttonId, + i18n.getMessage(UIMessageIdProvider.BUTTON_NO_AUTO_ASSIGNMENT), + i18n.getMessage(UIMessageIdProvider.BUTTON_AUTO_ASSIGNMENT_DESCRIPTION), null, false, null, + SPUIButtonStyleNoBorder.class); } else { - updateIcon = SPUIComponentProvider.getButton(buttonId, distSet.getNameVersion(), - i18n.getMessage("button.auto.assignment.desc"), null, false, null, SPUIButtonStyleNoBorder.class); + updateIcon = actionType.equals(ActionType.FORCED) + ? SPUIComponentProvider.getButton(buttonId, distSet.getNameVersion(), + i18n.getMessage(UIMessageIdProvider.BUTTON_AUTO_ASSIGNMENT_DESCRIPTION), null, false, + FontAwesome.BOLT, SPUIButtonStyleNoBorderWithIcon.class) + : SPUIComponentProvider.getButton(buttonId, distSet.getNameVersion(), + i18n.getMessage(UIMessageIdProvider.BUTTON_AUTO_ASSIGNMENT_DESCRIPTION), null, false, null, + SPUIButtonStyleNoBorder.class); + updateIcon.setSizeUndefined(); } updateIcon.addClickListener(this::onClickOfDistributionSetButton); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionTable.java index 2ce756834..095af4519 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionTable.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionTable.java @@ -50,8 +50,8 @@ import org.eclipse.hawkbit.ui.management.event.ManagementUIEvent; import org.eclipse.hawkbit.ui.management.event.PinUnpinEvent; import org.eclipse.hawkbit.ui.management.event.RefreshDistributionTableByFilterEvent; import org.eclipse.hawkbit.ui.management.event.SaveActionWindowEvent; -import org.eclipse.hawkbit.ui.management.miscs.ActionTypeOptionGroupLayout; -import org.eclipse.hawkbit.ui.management.miscs.ActionTypeOptionGroupLayout.ActionTypeOption; +import org.eclipse.hawkbit.ui.management.miscs.AbstractActionTypeOptionGroupLayout.ActionTypeOption; +import org.eclipse.hawkbit.ui.management.miscs.ActionTypeOptionGroupAssignmentLayout; import org.eclipse.hawkbit.ui.management.miscs.MaintenanceWindowLayout; import org.eclipse.hawkbit.ui.management.state.ManagementUIState; import org.eclipse.hawkbit.ui.management.targettable.TargetTable; @@ -125,7 +125,7 @@ public class DistributionTable extends AbstractNamedVersionTable targetIdSetList; List tempIdList; - final ActionType actionType = ((ActionTypeOptionGroupLayout.ActionTypeOption) actionTypeOptionGroupLayout - .getActionTypeOptionGroup().getValue()).getActionType(); - final long forcedTimeStamp = (((ActionTypeOptionGroupLayout.ActionTypeOption) actionTypeOptionGroupLayout - .getActionTypeOptionGroup().getValue()) == ActionTypeOption.AUTO_FORCED) + final ActionType actionType = ((ActionTypeOption) actionTypeOptionGroupLayout.getActionTypeOptionGroup() + .getValue()).getActionType(); + final long forcedTimeStamp = (((ActionTypeOption) actionTypeOptionGroupLayout.getActionTypeOptionGroup() + .getValue()) == ActionTypeOption.AUTO_FORCED) ? actionTypeOptionGroupLayout.getForcedTimeDateField().getValue().getTime() : RepositoryModelConstants.NO_FORCE_TIME; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/AbstractActionTypeOptionGroupLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/AbstractActionTypeOptionGroupLayout.java new file mode 100644 index 000000000..ddbc4ca7d --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/AbstractActionTypeOptionGroupLayout.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.management.miscs; + +import java.util.Arrays; +import java.util.Optional; + +import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; +import org.vaadin.hene.flexibleoptiongroup.FlexibleOptionGroup; +import org.vaadin.hene.flexibleoptiongroup.FlexibleOptionGroupItemComponent; + +import com.vaadin.server.FontAwesome; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; + +/** + * Action type option group abstract layout. + */ +public abstract class AbstractActionTypeOptionGroupLayout extends HorizontalLayout { + private static final long serialVersionUID = 1L; + + protected static final String STYLE_DIST_WINDOW_ACTIONTYPE = "dist-window-actiontype"; + private static final String STYLE_DIST_WINDOW_ACTIONTYPE_LAYOUT = "dist-window-actiontype-horz-layout"; + + protected final VaadinMessageSource i18n; + protected FlexibleOptionGroup actionTypeOptionGroup; + + /** + * Constructor + * + * @param i18n + * VaadinMessageSource + */ + protected AbstractActionTypeOptionGroupLayout(final VaadinMessageSource i18n) { + this.i18n = i18n; + init(); + } + + private void init() { + createOptionGroup(); + setStyleName(STYLE_DIST_WINDOW_ACTIONTYPE_LAYOUT); + setSizeUndefined(); + } + + protected abstract void createOptionGroup(); + + protected void addForcedItemWithLabel() { + final FlexibleOptionGroupItemComponent forceItem = actionTypeOptionGroup + .getItemComponent(ActionTypeOption.FORCED); + forceItem.setStyleName(STYLE_DIST_WINDOW_ACTIONTYPE); + forceItem.setId(UIComponentIdProvider.SAVE_ACTION_RADIO_FORCED); + addComponent(forceItem); + final Label forceLabel = new Label(); + forceLabel.setStyleName("statusIconPending"); + forceLabel.setIcon(FontAwesome.BOLT); + forceLabel.setCaption(i18n.getMessage(UIMessageIdProvider.CAPTION_ACTION_FORCED)); + forceLabel.setDescription(i18n.getMessage(UIMessageIdProvider.TOOLTIP_FORCED_ITEM)); + forceLabel.setStyleName("padding-right-style"); + addComponent(forceLabel); + } + + protected void addSoftItemWithLabel() { + final FlexibleOptionGroupItemComponent softItem = actionTypeOptionGroup.getItemComponent(ActionTypeOption.SOFT); + softItem.setId(UIComponentIdProvider.ACTION_DETAILS_SOFT_ID); + softItem.setStyleName(STYLE_DIST_WINDOW_ACTIONTYPE); + addComponent(softItem); + final Label softLabel = new Label(); + softLabel.setSizeFull(); + softLabel.setCaption(i18n.getMessage(UIMessageIdProvider.CAPTION_ACTION_SOFT)); + softLabel.setDescription(i18n.getMessage(UIMessageIdProvider.TOOLTIP_SOFT_ITEM)); + softLabel.setStyleName("padding-right-style"); + addComponent(softLabel); + } + + /** + * To Set Default option for save. + */ + public void selectDefaultOption() { + actionTypeOptionGroup.select(ActionTypeOption.FORCED); + } + + /** + * Enum which described the options for the action type + * + */ + public enum ActionTypeOption { + FORCED(ActionType.FORCED), SOFT(ActionType.SOFT), AUTO_FORCED(ActionType.TIMEFORCED); + + private final ActionType actionType; + + ActionTypeOption(final ActionType actionType) { + this.actionType = actionType; + } + + public ActionType getActionType() { + return actionType; + } + + /** + * Matches the action type to the option + * + * @param actionType + * the action type to get option for + * @return action type option if matches, otherwise empty Optional + */ + public static Optional getOptionForActionType(final ActionType actionType) { + return Arrays.stream(ActionTypeOption.values()).filter(option -> option.getActionType().equals(actionType)) + .findFirst(); + } + } + + public FlexibleOptionGroup getActionTypeOptionGroup() { + return actionTypeOptionGroup; + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/ActionTypeOptionGroupLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/ActionTypeOptionGroupAssignmentLayout.java similarity index 59% rename from hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/ActionTypeOptionGroupLayout.java rename to hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/ActionTypeOptionGroupAssignmentLayout.java index 96321906b..6773ef522 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/ActionTypeOptionGroupLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/ActionTypeOptionGroupAssignmentLayout.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * Copyright (c) 2019 Bosch Software Innovations 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 @@ -12,7 +12,6 @@ import java.time.LocalDateTime; import java.util.Date; import java.util.TimeZone; -import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; @@ -26,23 +25,15 @@ import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.server.FontAwesome; import com.vaadin.shared.ui.datefield.Resolution; import com.vaadin.ui.DateField; -import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.themes.ValoTheme; /** - * Action type option group layout. + * Action type option group layout for manual assignment. */ -public class ActionTypeOptionGroupLayout extends HorizontalLayout { - +public class ActionTypeOptionGroupAssignmentLayout extends AbstractActionTypeOptionGroupLayout { private static final long serialVersionUID = 1L; - private static final String STYLE_DIST_WINDOW_ACTIONTYPE = "dist-window-actiontype"; - - private final VaadinMessageSource i18n; - - private FlexibleOptionGroup actionTypeOptionGroup; - private DateField forcedTimeDateField; /** @@ -51,13 +42,9 @@ public class ActionTypeOptionGroupLayout extends HorizontalLayout { * @param i18n * VaadinMessageSource */ - public ActionTypeOptionGroupLayout(final VaadinMessageSource i18n) { - this.i18n = i18n; - - createOptionGroup(); + public ActionTypeOptionGroupAssignmentLayout(final VaadinMessageSource i18n) { + super(i18n); addValueChangeListener(); - setStyleName("dist-window-actiontype-horz-layout"); - setSizeUndefined(); } private void addValueChangeListener() { @@ -77,37 +64,20 @@ public class ActionTypeOptionGroupLayout extends HorizontalLayout { }); } - private void createOptionGroup() { + @Override + protected void createOptionGroup() { actionTypeOptionGroup = new FlexibleOptionGroup(); actionTypeOptionGroup.addItem(ActionTypeOption.SOFT); actionTypeOptionGroup.addItem(ActionTypeOption.FORCED); actionTypeOptionGroup.addItem(ActionTypeOption.AUTO_FORCED); selectDefaultOption(); - final FlexibleOptionGroupItemComponent forceItem = actionTypeOptionGroup - .getItemComponent(ActionTypeOption.FORCED); - forceItem.setStyleName(STYLE_DIST_WINDOW_ACTIONTYPE); - forceItem.setId(UIComponentIdProvider.SAVE_ACTION_RADIO_FORCED); - addComponent(forceItem); - final Label forceLabel = new Label(); - forceLabel.setStyleName("statusIconPending"); - forceLabel.setIcon(FontAwesome.BOLT); - forceLabel.setCaption(i18n.getMessage(UIMessageIdProvider.CAPTION_ACTION_FORCED)); - forceLabel.setDescription(i18n.getMessage(UIMessageIdProvider.TOOLTIP_FORCED_ITEM)); - forceLabel.setStyleName("padding-right-style"); - addComponent(forceLabel); - - final FlexibleOptionGroupItemComponent softItem = actionTypeOptionGroup.getItemComponent(ActionTypeOption.SOFT); - softItem.setId(UIComponentIdProvider.ACTION_DETAILS_SOFT_ID); - softItem.setStyleName(STYLE_DIST_WINDOW_ACTIONTYPE); - addComponent(softItem); - final Label softLabel = new Label(); - softLabel.setSizeFull(); - softLabel.setCaption(i18n.getMessage(UIMessageIdProvider.CAPTION_ACTION_SOFT)); - softLabel.setDescription(i18n.getMessage(UIMessageIdProvider.TOOLTIP_SOFT_ITEM)); - softLabel.setStyleName("padding-right-style"); - addComponent(softLabel); + addForcedItemWithLabel(); + addSoftItemWithLabel(); + addAutoForceItemWithLabelAndDateField(); + } + private void addAutoForceItemWithLabelAndDateField() { final FlexibleOptionGroupItemComponent autoForceItem = actionTypeOptionGroup .getItemComponent(ActionTypeOption.AUTO_FORCED); autoForceItem.setStyleName(STYLE_DIST_WINDOW_ACTIONTYPE); @@ -138,38 +108,7 @@ public class ActionTypeOptionGroupLayout extends HorizontalLayout { addComponent(forcedTimeDateField); } - /** - * To Set Default option for save. - */ - - public void selectDefaultOption() { - actionTypeOptionGroup.select(ActionTypeOption.FORCED); - } - - /** - * Enum which described the options for the action type - * - */ - public enum ActionTypeOption { - FORCED(ActionType.FORCED), SOFT(ActionType.SOFT), AUTO_FORCED(ActionType.TIMEFORCED); - - private final ActionType actionType; - - ActionTypeOption(final ActionType actionType) { - this.actionType = actionType; - } - - public ActionType getActionType() { - return actionType; - } - } - - public FlexibleOptionGroup getActionTypeOptionGroup() { - return actionTypeOptionGroup; - } - public DateField getForcedTimeDateField() { return forcedTimeDateField; } - } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/ActionTypeOptionGroupAutoAssignmentLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/ActionTypeOptionGroupAutoAssignmentLayout.java new file mode 100644 index 000000000..d7b518524 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/miscs/ActionTypeOptionGroupAutoAssignmentLayout.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2019 Bosch Software Innovations 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.management.miscs; + +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; +import org.vaadin.hene.flexibleoptiongroup.FlexibleOptionGroup; + +/** + * Action type option group layout for auto assignment. + */ +public class ActionTypeOptionGroupAutoAssignmentLayout extends AbstractActionTypeOptionGroupLayout { + private static final long serialVersionUID = 1L; + + /** + * Constructor + * + * @param i18n + * VaadinMessageSource + */ + public ActionTypeOptionGroupAutoAssignmentLayout(final VaadinMessageSource i18n) { + super(i18n); + } + + @Override + protected void createOptionGroup() { + actionTypeOptionGroup = new FlexibleOptionGroup(); + actionTypeOptionGroup.addItem(ActionTypeOption.SOFT); + actionTypeOptionGroup.addItem(ActionTypeOption.FORCED); + selectDefaultOption(); + + addForcedItemWithLabel(); + addSoftItemWithLabel(); + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetMetadataPopupLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetMetadataPopupLayout.java index baad6b186..0317c4e88 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetMetadataPopupLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetMetadataPopupLayout.java @@ -66,7 +66,7 @@ public class TargetMetadataPopupLayout extends AbstractMetadataPopupLayout getMetadataList() { return Collections.unmodifiableList(targetManagement - .findMetaDataByControllerId(new PageRequest(0, 500), getSelectedEntity().getControllerId()) + .findMetaDataByControllerId(PageRequest.of(0, 500), getSelectedEntity().getControllerId()) .getContent()); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTable.java index 49e17d00f..cc48ea94f 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTable.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTable.java @@ -60,8 +60,8 @@ import org.eclipse.hawkbit.ui.management.event.TargetAddUpdateWindowEvent; import org.eclipse.hawkbit.ui.management.event.TargetFilterEvent; import org.eclipse.hawkbit.ui.management.event.TargetTableEvent; import org.eclipse.hawkbit.ui.management.event.TargetTableEvent.TargetComponentEvent; -import org.eclipse.hawkbit.ui.management.miscs.ActionTypeOptionGroupLayout; -import org.eclipse.hawkbit.ui.management.miscs.ActionTypeOptionGroupLayout.ActionTypeOption; +import org.eclipse.hawkbit.ui.management.miscs.AbstractActionTypeOptionGroupLayout.ActionTypeOption; +import org.eclipse.hawkbit.ui.management.miscs.ActionTypeOptionGroupAssignmentLayout; import org.eclipse.hawkbit.ui.management.miscs.MaintenanceWindowLayout; import org.eclipse.hawkbit.ui.management.state.ManagementUIState; import org.eclipse.hawkbit.ui.management.state.TargetTableFilters; @@ -142,7 +142,7 @@ public class TargetTable extends AbstractTable { private ConfirmationDialog confirmDialog; - private final ActionTypeOptionGroupLayout actionTypeOptionGroupLayout; + private final ActionTypeOptionGroupAssignmentLayout actionTypeOptionGroupLayout; private final MaintenanceWindowLayout maintenanceWindowLayout; @@ -159,7 +159,7 @@ public class TargetTable extends AbstractTable { this.tagManagement = tagManagement; this.deploymentManagement = deploymentManagement; this.uiProperties = uiProperties; - this.actionTypeOptionGroupLayout = new ActionTypeOptionGroupLayout(i18n); + this.actionTypeOptionGroupLayout = new ActionTypeOptionGroupAssignmentLayout(i18n); this.maintenanceWindowLayout = new MaintenanceWindowLayout(i18n); setItemDescriptionGenerator(new AssignInstalledDSTooltipGenerator()); @@ -864,10 +864,10 @@ public class TargetTable extends AbstractTable { Long distId; List targetIdSetList; List tempIdList; - final ActionType actionType = ((ActionTypeOptionGroupLayout.ActionTypeOption) actionTypeOptionGroupLayout - .getActionTypeOptionGroup().getValue()).getActionType(); - final long forcedTimeStamp = (((ActionTypeOptionGroupLayout.ActionTypeOption) actionTypeOptionGroupLayout - .getActionTypeOptionGroup().getValue()) == ActionTypeOption.AUTO_FORCED) + final ActionType actionType = ((ActionTypeOption) actionTypeOptionGroupLayout.getActionTypeOptionGroup() + .getValue()).getActionType(); + final long forcedTimeStamp = (((ActionTypeOption) actionTypeOptionGroupLayout.getActionTypeOptionGroup() + .getValue()) == ActionTypeOption.AUTO_FORCED) ? actionTypeOptionGroupLayout.getForcedTimeDateField().getValue().getTime() : RepositoryModelConstants.NO_FORCE_TIME; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/AddUpdateRolloutWindowLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/AddUpdateRolloutWindowLayout.java index 2a2fd98a9..e44ffc9d9 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/AddUpdateRolloutWindowLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/AddUpdateRolloutWindowLayout.java @@ -48,8 +48,8 @@ import org.eclipse.hawkbit.ui.common.builder.TextAreaBuilder; import org.eclipse.hawkbit.ui.common.builder.TextFieldBuilder; import org.eclipse.hawkbit.ui.common.builder.WindowBuilder; import org.eclipse.hawkbit.ui.filtermanagement.TargetFilterBeanQuery; -import org.eclipse.hawkbit.ui.management.miscs.ActionTypeOptionGroupLayout; -import org.eclipse.hawkbit.ui.management.miscs.ActionTypeOptionGroupLayout.ActionTypeOption; +import org.eclipse.hawkbit.ui.management.miscs.AbstractActionTypeOptionGroupLayout.ActionTypeOption; +import org.eclipse.hawkbit.ui.management.miscs.ActionTypeOptionGroupAssignmentLayout; import org.eclipse.hawkbit.ui.rollout.event.RolloutEvent; import org.eclipse.hawkbit.ui.rollout.groupschart.GroupsPieChart; import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; @@ -112,7 +112,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { private static final String DENY_BUTTON_LABEL = "button.deny"; - private final ActionTypeOptionGroupLayout actionTypeOptionGroupLayout; + private final ActionTypeOptionGroupAssignmentLayout actionTypeOptionGroupLayout; private final AutoStartOptionGroupLayout autoStartOptionGroupLayout; @@ -190,7 +190,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { final VaadinMessageSource i18n, final UIEventBus eventBus, final TargetFilterQueryManagement targetFilterQueryManagement, final RolloutGroupManagement rolloutGroupManagement, final QuotaManagement quotaManagement) { - actionTypeOptionGroupLayout = new ActionTypeOptionGroupLayout(i18n); + actionTypeOptionGroupLayout = new ActionTypeOptionGroupAssignmentLayout(i18n); autoStartOptionGroupLayout = new AutoStartOptionGroupLayout(i18n); this.rolloutManagement = rolloutManagement; this.rolloutGroupManagement = rolloutGroupManagement; @@ -371,8 +371,8 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { } private ActionType getActionType() { - return ((ActionTypeOptionGroupLayout.ActionTypeOption) actionTypeOptionGroupLayout - .getActionTypeOptionGroup().getValue()).getActionType(); + return ((ActionTypeOption) actionTypeOptionGroupLayout.getActionTypeOptionGroup().getValue()) + .getActionType(); } private AutoStartOptionGroupLayout.AutoStartOption getAutoStartOption() { @@ -1078,8 +1078,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { } private void setActionType(final Rollout rollout) { - for (final ActionTypeOptionGroupLayout.ActionTypeOption groupAction : ActionTypeOptionGroupLayout.ActionTypeOption - .values()) { + for (final ActionTypeOption groupAction : ActionTypeOption.values()) { if (groupAction.getActionType() == rollout.getActionType()) { actionTypeOptionGroupLayout.getActionTypeOptionGroup().setValue(groupAction); final SimpleDateFormat format = new SimpleDateFormat(SPUIDefinitions.LAST_QUERY_DATE_FORMAT); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUILabelDefinitions.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUILabelDefinitions.java index ce84dcd33..8e4d26a59 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUILabelDefinitions.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUILabelDefinitions.java @@ -133,6 +133,10 @@ public final class SPUILabelDefinitions { * AUTO ASSIGN DISTRIBUTION SET ID */ public static final String AUTO_ASSIGN_DISTRIBUTION_SET = "autoAssignDistributionSet"; + /** + * AUTO ASSIGN ACTION TYPE + */ + public static final String AUTO_ASSIGN_ACTION_TYPE = "autoAssignActionType"; /** * ASSIGNED DISTRIBUTION Name and Version. */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java index 7cb73df05..408a36427 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java @@ -1179,9 +1179,9 @@ public final class UIComponentIdProvider { public static final String FILTER_SEARCH_ICON_ID = "filter.search.icon"; /** - * Distribution set select table id + * Distribution set select combobox id */ - public static final String DIST_SET_SELECT_TABLE_ID = "distribution.set.select.table"; + public static final String DIST_SET_SELECT_COMBO_ID = "distribution.set.select.combo"; /** * Distribution set select window id diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java index 3fbec4b6b..f9986359b 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java @@ -21,6 +21,12 @@ public final class UIMessageIdProvider { public static final String BUTTON_SAVE = "button.save"; + public static final String BUTTON_NO_AUTO_ASSIGNMENT = "button.no.auto.assignment"; + + public static final String BUTTON_AUTO_ASSIGNMENT_DESCRIPTION = "button.auto.assignment.desc"; + + public static final String HEADER_DISTRIBUTION_SET = "header.distributionset"; + public static final String CAPTION_ACTION_FORCED = "label.action.forced"; public static final String CAPTION_ACTION_SOFT = "label.action.soft"; @@ -47,6 +53,10 @@ public final class UIMessageIdProvider { public static final String CAPTION_ARTIFACT_DETAILS_OF = "caption.artifact.details.of"; + public static final String CAPTION_SELECT_AUTO_ASSIGN_DS = "caption.select.auto.assign.dist"; + + public static final String CAPTION_CONFIRM_AUTO_ASSIGN_CONSEQUENCES = "caption.confirm.assign.consequences"; + public static final String CAPTION_CONFIG_CREATE = "caption.config.create"; public static final String CAPTION_CONFIG_EDIT = "caption.config.edit"; @@ -59,6 +69,10 @@ public final class UIMessageIdProvider { public static final String LABEL_CREATE_FILTER = "label.create.filter"; + public static final String LABEL_AUTO_ASSIGNMENT_DESC = "label.auto.assign.description"; + + public static final String LABEL_AUTO_ASSIGNMENT_ENABLE = "label.auto.assign.enable"; + public static final String MESSAGE_NO_DATA = "message.no.data"; public static final String MESSAGE_DATA_AVAILABLE = "message.data.available"; @@ -67,6 +81,12 @@ public final class UIMessageIdProvider { public static final String MESSAGE_ACTION_NOT_ALLOWED = "message.action.not.allowed"; + public static final String MESSAGE_SELECTED_DS_NOT_FOUND = "message.selected.distributionset.not.found"; + + public static final String MESSAGE_CONFIRM_AUTO_ASSIGN_CONSEQUENCES_NONE = "message.confirm.assign.consequences.none"; + + public static final String MESSAGE_CONFIRM_AUTO_ASSIGN_CONSEQUENCES_TEXT = "message.confirm.assign.consequences.text"; + public static final String TOOLTIP_OVERDUE = "tooltip.overdue"; public static final String TOOLTIP_MAXIMIZE = "tooltip.maximize"; diff --git a/hawkbit-ui/src/main/resources/messages.properties b/hawkbit-ui/src/main/resources/messages.properties index 940bf1e1a..433a6d147 100644 --- a/hawkbit-ui/src/main/resources/messages.properties +++ b/hawkbit-ui/src/main/resources/messages.properties @@ -228,7 +228,7 @@ label.target.controller.attrs = Controller attributes label.target.attributes.update.pending = Update pending.. label.target.lastpolldate = Last poll : label.tag.name = Tag name -label.configuration.auth.header = Allow targets to authenticate via a certificate authenticated by an reverse proxy +label.configuration.auth.header = Allow targets to authenticate via a certificate authenticated by a reverse proxy label.configuration.auth.gatewaytoken = Allow a gateway to authenticate and manage multiple targets through a gateway security token label.configuration.auth.targettoken = Allow targets to authenticate directly with their target security token label.configuration.repository.autoclose.action = Autoclose running actions when a new distribution set is assigned @@ -670,6 +670,7 @@ target.not.exists=Target {0} does not exists. Maybe the target was deleted. targets.not.exists=Targets does not exists. Maybe the targets was deleted. distributionsets.not.exists=Distribution sets do not exists. Maybe the sets were deleted. +message.selected.distributionset.not.found=Distribution set {0} does not exist in the repository, is incomplete or deleted. Please select a new one. targettag.not.exists=Target tag {0} does not exists. Maybe the target tag was deleted. caption.entity.target.tag = Target Tag