diff --git a/.gitignore b/.gitignore index e49219859..9ccbafa9d 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,10 @@ local.properties .settings/ .loadpath +# IDEA +*.iml +.idea + # External tool builders .externalToolBuilders/ 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 new file mode 100644 index 000000000..1cee4c369 --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryFields.java @@ -0,0 +1,70 @@ +/** + * 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.repository; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Describing the fields of the Target model which can be used in the REST API + * e.g. for sorting etc. + * + */ +public enum TargetFilterQueryFields implements FieldNameProvider { + + /** + * The id field. + */ + ID("id"), + + /** + * The name field. + */ + NAME("name"), + + /** + * distribution set which is set as auto assign distribution set + */ + AUTOASSIGNDISTRIBUTIONSET("autoAssignDistributionSet", "name", "version"); + + + private final String fieldName; + private List subEntityAttributes; + private boolean mapField; + + TargetFilterQueryFields(final String fieldName) { + this(fieldName, false, Collections.emptyList()); + } + + TargetFilterQueryFields(final String fieldName, final String... subEntityAttribues) { + this(fieldName, false, Arrays.asList(subEntityAttribues)); + } + + TargetFilterQueryFields(final String fieldName, final boolean mapField, final List subEntityAttribues) { + this.fieldName = fieldName; + this.mapField = mapField; + this.subEntityAttributes = subEntityAttribues; + } + + @Override + public List getSubEntityAttributes() { + return subEntityAttributes; + } + + @Override + public boolean isMap() { + return mapField; + } + + @Override + public String getFieldName() { + return fieldName; + } +} diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQuery.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQuery.java new file mode 100644 index 000000000..c5378a195 --- /dev/null +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQuery.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2011-2015 Bosch Software Innovations GmbH, Germany. All rights reserved. + */ +package org.eclipse.hawkbit.mgmt.json.model.targetfilter; + +import org.eclipse.hawkbit.mgmt.json.model.MgmtBaseEntity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A json annotated rest model for Target to RESTful API representation. + * + */ +@JsonInclude(Include.ALWAYS) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MgmtTargetFilterQuery extends MgmtBaseEntity { + + @JsonProperty(value = "id", required = true) + private Long filterId; + + @JsonProperty + private String name; + + @JsonProperty + private String query; + + @JsonProperty + private Long autoAssignDistributionSet; + + /** + * @return the filterId + */ + public Long getFilterId() { + return filterId; + } + + /** + * @param filterId + * the filterId to set + */ + public void setFilterId(final Long filterId) { + this.filterId = filterId; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name + * the name to set + */ + public void setName(final String name) { + this.name = name; + } + + /** + * @return the query + */ + public String getQuery() { + return query; + } + + /** + * @param query + * the query to set + */ + @JsonIgnore + public void setQuery(final String query) { + this.query = query; + } + + public Long getAutoAssignDistributionSet() { + return autoAssignDistributionSet; + } + + @JsonIgnore + public void setAutoAssignDistributionSet(final Long autoAssignDistributionSet) { + this.autoAssignDistributionSet = autoAssignDistributionSet; + } + +} diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQueryRequestBody.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQueryRequestBody.java new file mode 100644 index 000000000..a87c6fd2f --- /dev/null +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQueryRequestBody.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2011-2015 Bosch Software Innovations GmbH, Germany. All rights reserved. + */ +package org.eclipse.hawkbit.mgmt.json.model.targetfilter; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Request body for target PUT/POST commands. + * + */ +public class MgmtTargetFilterQueryRequestBody { + @JsonProperty(required = true) + private String name; + + @JsonProperty(required = true) + private String query; + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name + * the name to set + */ + public MgmtTargetFilterQueryRequestBody setName(final String name) { + this.name = name; + return this; + } + + /** + * @return the filter query + */ + public String getQuery() { + return query; + } + + /** + * @param query + * the filter query + */ + public void setQuery(String query) { + this.query = query; + } + +} diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java index d3439adc6..13eff9831 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java @@ -20,6 +20,7 @@ import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentR import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule; import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleAssigment; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget; +import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; @@ -179,6 +180,37 @@ public interface MgmtDistributionSetRestApi { @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + /** + * Handles the GET request to retrieve target filter queries that have the + * given distribution set as auto assign DS. + * + * @param distributionSetId + * the ID of the distribution set to retrieve the assigned + * targets + * @param pagingOffsetParam + * the offset of list of targets for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search name parameter in the request URL, syntax + * {@code q=myFilter} + * @return status OK if get request is successful with the paged list of + * targets + */ + @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/autoAssignTargetFilters", produces = { + MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + ResponseEntity> getAutoAssignTargetFilterQueries( + @PathVariable("distributionSetId") final Long distributionSetId, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + /** * Handles the POST request of assigning multiple targets to a single * distribution set. diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java index e8c25403f..a46fbf79a 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java @@ -104,6 +104,11 @@ public final class MgmtRestConstants { public static final String DISTRIBUTIONSET_TAG_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/distributionsettags"; + /** + * The target URL mapping rest resource. + */ + public static final String TARGET_FILTER_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/targetfilters"; + /** * The tag URL mapping rest resource. */ diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetFilterQueryRestApi.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetFilterQueryRestApi.java new file mode 100644 index 000000000..7b7d51353 --- /dev/null +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetFilterQueryRestApi.java @@ -0,0 +1,156 @@ +/** + * 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.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.MgmtTargetFilterQuery; +import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQueryRequestBody; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * Api for handling target operations. + */ +@RequestMapping(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING) +public interface MgmtTargetFilterQueryRestApi { + + /** + * Handles the GET request of retrieving a single target filter. + * + * @param filterId + * the ID of the target filter to retrieve + * @return a single target with status OK. + */ + + @RequestMapping(method = RequestMethod.GET, value = "/{filterId}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity getFilter(@PathVariable("filterId") final Long filterId); + + /** + * Handles the GET request of retrieving all filters. + * + * @param pagingOffsetParam + * the offset of list of targets for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * @return a list of all targets for a defined or default page request with + * status OK. The response is always paged. In any failure the + * JsonResponseExceptionHandler is handling the response. + */ + + @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity> getFilters( + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Handles the POST request of creating new target filters. The request body + * must always be a list of target filters. + * + * @param filter + * the filters to be created. + * @return In case all filters were successfully created the ResponseEntity + * with status code 201 with a list of successfully created entities + * is returned. In any failure the JsonResponseExceptionHandler is + * handling the response. + */ + @RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity createFilter(@RequestBody final MgmtTargetFilterQueryRequestBody filter); + + /** + * Handles the PUT request of updating a target filter. The ID is within the + * URL path of the request. A given ID in the request body is ignored. It's + * not possible to set fields to {@code null} values. + * + * @param filterId + * the path parameter which contains the ID of the target filter + * @param targetFilterRest + * the request body which contains the fields which should be + * updated, fields which are not given are ignored for the + * update. + * @return the updated target filter response which contains all fields + * including fields which have not been updated + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{filterId}", consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity updateFilter(@PathVariable("filterId") final Long filterId, + @RequestBody final MgmtTargetFilterQueryRequestBody targetFilterRest); + + /** + * Handles the DELETE request of deleting a target filter. + * + * @param filterId + * the ID of the target filter to be deleted + * @return If the given controllerId could exists and could be deleted Http + * OK. In any failure the JsonResponseExceptionHandler is handling + * the response. + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{filterId}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity deleteFilter(@PathVariable("filterId") final Long filterId); + + /** + * Handles the GET request of retrieving the distribution set for auto + * assignment of an specific target filter. + * + * @param filterId + * the ID of the target to retrieve the assigned distribution + * @return the assigned distribution set with status OK, if none is assigned + * than {@code null} content (e.g. "{}") + */ + @RequestMapping(method = RequestMethod.GET, value = "/{filterId}/autoAssignDS", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity getAssignedDistributionSet(@PathVariable("filterId") final Long filterId); + + /** + * Handles the POST request for changing distribution set for auto + * assignment of a target filter. + * + * @param filterId + * of the target to change + * @param dsId + * of the Id of the auto assign distribution set + * @return http status + */ + @RequestMapping(method = RequestMethod.POST, value = "/{filterId}/autoAssignDS", consumes = { + "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity postAssignedDistributionSet(@PathVariable("filterId") final Long filterId, + @RequestBody final MgmtId dsId); + + /** + * Handles the DELETE request for removing the distribution set for auto + * assignment of a target filter. + * + * @param filterId + * of the target to change + * @return http status + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{filterId}/autoAssignDS") + ResponseEntity deleteAssignedDistributionSet(@PathVariable("filterId") final Long filterId); + +} diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java index 721995d88..1da8d8a5f 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java @@ -23,6 +23,7 @@ import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentR import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule; import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleAssigment; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget; +import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery; import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetRestApi; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.repository.DeploymentManagement; @@ -32,12 +33,14 @@ import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; import org.eclipse.hawkbit.repository.SoftwareManagement; import org.eclipse.hawkbit.repository.SystemManagement; +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.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetWithActionType; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.slf4j.Logger; @@ -66,6 +69,9 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi { @Autowired private TargetManagement targetManagement; + @Autowired + private TargetFilterQueryManagement targetFilterQueryManagement; + @Autowired private DeploymentManagement deployManagament; @@ -223,6 +229,37 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi { HttpStatus.OK); } + @Override + public ResponseEntity> getAutoAssignTargetFilterQueries( + @PathVariable("distributionSetId") Long distributionSetId, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) int pagingOffsetParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) int pagingLimitParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) String sortParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) String rsqlParam) { + // check if distribution set exists otherwise throw exception + // immediately + DistributionSet distributionSet = findDistributionSetWithExceptionIfNotFound(distributionSetId); + + final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); + final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); + final Sort sorting = PagingUtility.sanitizeTargetSortParam(sortParam); + + final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); + final Page targetFilterQueries; + if (rsqlParam != null) { + targetFilterQueries = this.targetFilterQueryManagement.findTargetFilterQueryByAutoAssignDS(pageable, + distributionSet, rsqlParam); + } else { + targetFilterQueries = this.targetFilterQueryManagement.findTargetFilterQueryByAutoAssignDS(pageable, + distributionSet); + } + + return new ResponseEntity<>( + new PagedList<>(MgmtTargetFilterQueryMapper.toResponse(targetFilterQueries.getContent()), + targetFilterQueries.getTotalElements()), + HttpStatus.OK); + } + @Override public ResponseEntity createAssignedTarget( @PathVariable("distributionSetId") final Long distributionSetId, diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryMapper.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryMapper.java new file mode 100644 index 000000000..629a1bc4e --- /dev/null +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryMapper.java @@ -0,0 +1,95 @@ +/** + * 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.mgmt.rest.resource; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + +import java.util.ArrayList; +import java.util.List; + +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.MgmtTargetFilterQueryRestApi; +import org.eclipse.hawkbit.repository.EntityFactory; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; + +/** + * A mapper which maps repository model to RESTful model representation and + * back. + * + */ +public final class MgmtTargetFilterQueryMapper { + + private MgmtTargetFilterQueryMapper() { + // Utility class + } + + /** + * Create a response for targets. + * + * @param filters + * list of targets + * @return the response + */ + public static List toResponse(final Iterable filters) { + final List mappedList = new ArrayList<>(); + if (filters != null) { + for (final TargetFilterQuery filter : filters) { + final MgmtTargetFilterQuery response = toResponse(filter); + mappedList.add(response); + } + } + return mappedList; + } + + /** + * Create a response for target. + * + * @param filter + * the target + * @return the response + */ + public static MgmtTargetFilterQuery toResponse(final TargetFilterQuery filter) { + if (filter == null) { + return null; + } + final MgmtTargetFilterQuery targetRest = new MgmtTargetFilterQuery(); + targetRest.setFilterId(filter.getId()); + targetRest.setName(filter.getName()); + targetRest.setQuery(filter.getQuery()); + + targetRest.setCreatedBy(filter.getCreatedBy()); + targetRest.setLastModifiedBy(filter.getLastModifiedBy()); + + targetRest.setCreatedAt(filter.getCreatedAt()); + targetRest.setLastModifiedAt(filter.getLastModifiedAt()); + + DistributionSet distributionSet = filter.getAutoAssignDistributionSet(); + if (distributionSet != null) { + targetRest.setAutoAssignDistributionSet(distributionSet.getId()); + } + + targetRest.add(linkTo(methodOn(MgmtTargetFilterQueryRestApi.class).getFilter(filter.getId())).withRel("self")); + targetRest.add(linkTo(methodOn(MgmtTargetFilterQueryRestApi.class).postAssignedDistributionSet(filter.getId(),null)).withRel("autoAssignDS")); + + return targetRest; + } + + static TargetFilterQuery fromRequest(final EntityFactory entityFactory, + final MgmtTargetFilterQueryRequestBody filterRest) { + final TargetFilterQuery filter = entityFactory.generateTargetFilterQuery(); + filter.setName(filterRest.getName()); + filter.setQuery(filterRest.getQuery()); + + return filter; + } + +} diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResource.java new file mode 100644 index 000000000..cee14cf2c --- /dev/null +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResource.java @@ -0,0 +1,180 @@ +/** + * 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.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.MgmtTargetFilterQuery; +import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQueryRequestBody; +import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; +import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetFilterQueryRestApi; +import org.eclipse.hawkbit.repository.DistributionSetManagement; +import org.eclipse.hawkbit.repository.EntityFactory; +import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; +import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * REST Resource handling target CRUD operations. + */ +@RestController +public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestApi { + private static final Logger LOG = LoggerFactory.getLogger(MgmtTargetFilterQueryResource.class); + + @Autowired + private TargetFilterQueryManagement filterManagement; + + @Autowired + private DistributionSetManagement distributionSetManagement; + + @Autowired + private EntityFactory entityFactory; + + @Override + public ResponseEntity getFilter(@PathVariable("filterId") Long filterId) { + final TargetFilterQuery findTarget = findFilterWithExceptionIfNotFound(filterId); + // to single response include poll status + final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(findTarget); + + return new ResponseEntity<>(response, HttpStatus.OK); + } + + @Override + public ResponseEntity> getFilters( + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) int pagingOffsetParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) int pagingLimitParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) String sortParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) String rsqlParam) { + + final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); + final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); + final Sort sorting = PagingUtility.sanitizeTargetSortParam(sortParam); + + final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); + final Slice findTargetFiltersAll; + final Long countTargetsAll; + if (rsqlParam != null) { + final Page findFilterPage = this.filterManagement + .findTargetFilterQueryByFilter(pageable, rsqlParam); + countTargetsAll = findFilterPage.getTotalElements(); + findTargetFiltersAll = findFilterPage; + } else { + findTargetFiltersAll = this.filterManagement.findAllTargetFilterQuery(pageable); + countTargetsAll = this.filterManagement.countAllTargetFilterQuery(); + } + + final List rest = MgmtTargetFilterQueryMapper + .toResponse(findTargetFiltersAll.getContent()); + return new ResponseEntity<>(new PagedList(rest, countTargetsAll), HttpStatus.OK); + } + + @Override + public ResponseEntity createFilter(@RequestBody MgmtTargetFilterQueryRequestBody filter) { + final TargetFilterQuery createdTarget = this.filterManagement + .createTargetFilterQuery(MgmtTargetFilterQueryMapper.fromRequest(entityFactory, filter)); + + return new ResponseEntity<>(MgmtTargetFilterQueryMapper.toResponse(createdTarget), HttpStatus.CREATED); + } + + @Override + public ResponseEntity updateFilter(@PathVariable("filterId") Long filterId, + @RequestBody MgmtTargetFilterQueryRequestBody targetFilterRest) { + + final TargetFilterQuery existingFilter = findFilterWithExceptionIfNotFound(filterId); + LOG.debug("updating target filter query {}", existingFilter.getId()); + if (targetFilterRest.getName() != null) { + existingFilter.setName(targetFilterRest.getName()); + } + if (targetFilterRest.getQuery() != null) { + existingFilter.setQuery(targetFilterRest.getQuery()); + } + + final TargetFilterQuery updateFilter = this.filterManagement.updateTargetFilterQuery(existingFilter); + + return new ResponseEntity<>(MgmtTargetFilterQueryMapper.toResponse(updateFilter), HttpStatus.OK); + } + + @Override + public ResponseEntity deleteFilter(@PathVariable("filterId") Long filterId) { + final TargetFilterQuery filter = findFilterWithExceptionIfNotFound(filterId); + this.filterManagement.deleteTargetFilterQuery(filter.getId()); + LOG.debug("{} target filter query deleted, return status {}", filterId, HttpStatus.OK); + return new ResponseEntity<>(HttpStatus.OK); + } + + @Override + public ResponseEntity postAssignedDistributionSet(@PathVariable("filterId") Long filterId, + @RequestBody MgmtId dsId) { + final TargetFilterQuery filter = findFilterWithExceptionIfNotFound(filterId); + + DistributionSet distributionSet; + distributionSet = distributionSetManagement.findDistributionSetById(dsId.getId()); + if (distributionSet == null) { + throw new EntityNotFoundException("DistributionSet with Id {" + dsId + "} does not exist"); + } + + filter.setAutoAssignDistributionSet(distributionSet); + + final TargetFilterQuery updateFilter = this.filterManagement.updateTargetFilterQuery(filter); + + return new ResponseEntity<>(MgmtTargetFilterQueryMapper.toResponse(updateFilter), HttpStatus.OK); + } + + @Override + public ResponseEntity getAssignedDistributionSet(@PathVariable("filterId") Long filterId) { + final TargetFilterQuery filter = findFilterWithExceptionIfNotFound(filterId); + DistributionSet autoAssignDistributionSet = filter.getAutoAssignDistributionSet(); + MgmtDistributionSet distributionSetRest = MgmtDistributionSetMapper.toResponse(autoAssignDistributionSet); + final HttpStatus retStatus; + if (distributionSetRest == null) { + retStatus = HttpStatus.NO_CONTENT; + } else { + retStatus = HttpStatus.OK; + } + return new ResponseEntity<>(distributionSetRest, retStatus); + } + + @Override + public ResponseEntity deleteAssignedDistributionSet(@PathVariable("filterId") Long filterId) { + final TargetFilterQuery filter = findFilterWithExceptionIfNotFound(filterId); + + filter.setAutoAssignDistributionSet(null); + + this.filterManagement.updateTargetFilterQuery(filter); + + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + private TargetFilterQuery findFilterWithExceptionIfNotFound(final Long filterId) { + final TargetFilterQuery filter = this.filterManagement.findTargetFilterQueryById(filterId); + if (filter == null) { + throw new EntityNotFoundException("TargetFilterQuery with Id {" + filterId + "} does not exist"); + } + return filter; + } + +} diff --git a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java index 725a10538..d59c114e5 100644 --- a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java +++ b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java @@ -30,11 +30,8 @@ import java.util.Set; import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.model.*; import org.eclipse.hawkbit.repository.model.Action.Status; -import org.eclipse.hawkbit.repository.model.DistributionSet; -import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; -import org.eclipse.hawkbit.repository.model.SoftwareModule; -import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.test.util.TestdataFactory; import org.eclipse.hawkbit.repository.test.util.WithUser; import org.eclipse.hawkbit.rest.AbstractRestIntegrationTest; @@ -297,6 +294,25 @@ public class MgmtDistributionSetResourceTest extends AbstractRestIntegrationTest .andExpect(jsonPath("$.content[0].controllerId", equalTo(knownTargetId))); } + @Test + @Description("Ensures that target filters with auto assign DS are returned as persisted in the repository.") + public void getAutoAssignTargetFiltersOfDistributionSet() throws Exception { + // prepare distribution set + final String knownFilterName = "a"; + final Set createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1); + final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next(); + + final TargetFilterQuery tfq = targetFilterQueryManagement + .createTargetFilterQuery(entityFactory.generateTargetFilterQuery(knownFilterName, "x==y", createdDs)); + // create some dummy targets which are not assigned or installed + targetFilterQueryManagement.createTargetFilterQuery(entityFactory.generateTargetFilterQuery("b", "x==y")); + targetFilterQueryManagement.createTargetFilterQuery(entityFactory.generateTargetFilterQuery("c", "x==y")); + + mvc.perform(get(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId() + + "/autoAssignTargetFilters")).andExpect(status().isOk()).andExpect(jsonPath("$.size", equalTo(1))) + .andExpect(jsonPath("$.content[0].name", equalTo(knownFilterName))); + } + @Test @Description("Ensures that DS in repository are listed with proper paging properties.") public void getDistributionSetsWithoutAddtionalRequestParameters() throws Exception { diff --git a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResourceTest.java b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResourceTest.java new file mode 100644 index 000000000..5694c9bfb --- /dev/null +++ b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResourceTest.java @@ -0,0 +1,362 @@ +/** + * 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.mgmt.rest.resource; + +import static com.google.common.collect.Lists.newArrayList; +import static org.fest.assertions.api.Assertions.assertThat; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.Matchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.*; +import java.util.stream.Collectors; + +import org.eclipse.hawkbit.exception.SpServerError; +import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; +import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; +import org.eclipse.hawkbit.repository.model.*; +import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.rest.AbstractRestIntegrationTest; +import org.eclipse.hawkbit.rest.exception.MessageNotReadableException; +import org.eclipse.hawkbit.rest.json.model.ExceptionInfo; +import org.eclipse.hawkbit.rest.util.JsonBuilder; +import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter; +import org.json.JSONObject; +import org.junit.Test; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; + +import com.jayway.jsonpath.JsonPath; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +/** + * Spring MVC Tests against the MgmtTargetResource. + * + */ +@Features("Component Tests - Management API") +@Stories("Target Filter Query Resource") +public class MgmtTargetFilterQueryResourceTest extends AbstractRestIntegrationTest { + + private static final String TARGET_DESCRIPTION_TEST = "created in test"; + + private static final String JSON_PATH_ROOT = "$"; + + // fields, attributes + private static final String JSON_PATH_FIELD_ID = ".id"; + private static final String JSON_PATH_FIELD_NAME = ".name"; + private static final String JSON_PATH_FIELD_QUERY = ".query"; + private static final String JSON_PATH_FIELD_CONTENT = ".content"; + 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"; + + // target + // $.field + static final String JSON_PATH_PAGED_LIST_CONTENT = JSON_PATH_ROOT + JSON_PATH_FIELD_CONTENT; + static final String JSON_PATH_PAGED_LIST_SIZE = JSON_PATH_ROOT + JSON_PATH_FIELD_SIZE; + static final String JSON_PATH_PAGED_LIST_TOTAL = JSON_PATH_ROOT + JSON_PATH_FIELD_TOTAL; + + private static final String JSON_PATH_NAME = JSON_PATH_ROOT + JSON_PATH_FIELD_NAME; + 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; + + + @Test + @Description("Ensures that deletion is executed if permitted.") + public void deleteTargetFilterQueryReturnsOK() throws Exception { + final String filterName = "filter_01"; + TargetFilterQuery filterQuery = createSingleTargetFilterQuery(filterName, "name=test_01"); + + mvc.perform(delete(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + filterQuery.getId())) + .andExpect(status().isOk()); + + TargetFilterQuery tfq = targetFilterQueryManagement.findTargetFilterQueryById(filterQuery.getId()); + assertThat(tfq).isNull(); + } + + @Test + @Description("Ensures that deletion is refused with not found if target does not exist.") + public void deleteTargetWhichDoesNotExistsLeadsToEntityNotFound() throws Exception { + final String notExistingId = "4395"; + + mvc.perform(delete(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + notExistingId)) + .andExpect(status().isNotFound()); + } + + @Test + @Description("Ensures that update is refused with not found if target does not exist.") + public void updateTargetWhichDoesNotExistsLeadsToEntityNotFound() throws Exception { + final String notExistingId = "4395"; + mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + notExistingId).content("{}") + .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isNotFound()); + } + + @Test + @Description("Ensures that update request is reflected by repository.") + public void updateTargetFilterQueryQuery() throws Exception { + final String filterName = "filter_02"; + final String filterQuery = "name=test_02"; + final String filterQuery2 = "name=test_02_changed"; + final String body = new JSONObject().put("query", filterQuery2).toString(); + + // prepare + TargetFilterQuery tfq = createSingleTargetFilterQuery(filterName, filterQuery); + + 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))); + + TargetFilterQuery tfqCheck = targetFilterQueryManagement.findTargetFilterQueryById(tfq.getId()); + assertThat(tfqCheck.getQuery()).isEqualTo(filterQuery2); + assertThat(tfqCheck.getName()).isEqualTo(filterName); + } + + @Test + @Description("Ensures that update request is reflected by repository.") + public void updateTargetFilterQueryName() throws Exception { + final String filterName = "filter_03"; + final String filterName2 = "filter_03_changed"; + final String filterQuery = "name=test_03"; + final String body = new JSONObject().put("name", filterName2).toString(); + + // prepare + TargetFilterQuery tfq = entityFactory.generateTargetFilterQuery(); + tfq.setName(filterName); + tfq.setQuery(filterQuery); + tfq = targetFilterQueryManagement.createTargetFilterQuery(tfq); + + 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))); + + TargetFilterQuery tfqCheck = targetFilterQueryManagement.findTargetFilterQueryById(tfq.getId()); + assertThat(tfqCheck.getQuery()).isEqualTo(filterQuery); + assertThat(tfqCheck.getName()).isEqualTo(filterName2); + } + + + @Test + @Description("Ensures that request returns list of filters in defined format.") + public void getTargetFilterQueryWithoutAdditionalRequestParameters() throws Exception { + final int knownTargetAmount = 3; + final String idA = "a"; + final String idB = "b"; + final String idC = "c"; + final String testQuery = "name=test"; + + createSingleTargetFilterQuery(idA, testQuery); + createSingleTargetFilterQuery(idB, testQuery); + createSingleTargetFilterQuery(idC, testQuery); + + mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING)).andExpect(status().isOk()) + .andDo(MockMvcResultPrinter.print()) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(knownTargetAmount))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(knownTargetAmount))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(knownTargetAmount))) + // idA + .andExpect(jsonPath("$.content.[?(@.name==" + idA + ")].name", contains(idA))) + .andExpect(jsonPath("$.content.[?(@.name==" + idA + ")].query", contains(testQuery))) + // idB + .andExpect(jsonPath("$.content.[?(@.name==" + idB + ")].name", contains(idB))) + .andExpect(jsonPath("$.content.[?(@.name==" + idB + ")].query", contains(testQuery))) + // idC + .andExpect(jsonPath("$.content.[?(@.name==" + idC + ")].name", contains(idC))) + .andExpect(jsonPath("$.content.[?(@.name==" + idC + ")].query", contains(testQuery))); + } + + @Test + @Description("Ensures that request returns list of filters in defined format in size reduced by given limit parameter.") + public void getTargetWithPagingLimitRequestParameter() throws Exception { + final int limitSize = 1; + final int knownTargetAmount = 3; + final String idA = "a"; + final String idB = "b"; + final String idC = "c"; + final String testQuery = "name=test"; + + createSingleTargetFilterQuery(idA, testQuery); + createSingleTargetFilterQuery(idB, testQuery); + createSingleTargetFilterQuery(idC, testQuery); + + mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING) + .param(MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(limitSize))) + .andExpect(status().isOk()).andDo(MockMvcResultPrinter.print()) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(knownTargetAmount))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(limitSize))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(limitSize))) + // idA + .andExpect(jsonPath("$.content.[?(@.name==" + idA + ")].name", contains(idA))) + .andExpect(jsonPath("$.content.[?(@.name==" + idA + ")].query", contains(testQuery))); + } + + @Test + @Description("Ensures that request returns list of filters in defined format in size reduced by given limit and offset parameter.") + public void getTargetWithPagingLimitAndOffsetRequestParameter() throws Exception { + final int knownTargetAmount = 5; + final int offsetParam = 2; + final int expectedSize = knownTargetAmount - offsetParam; + final String idC = "c"; + final String idD = "d"; + final String idE = "e"; + final String testQuery = "name=test"; + + createSingleTargetFilterQuery("a", testQuery); + createSingleTargetFilterQuery("b", testQuery); + createSingleTargetFilterQuery(idC, testQuery); + createSingleTargetFilterQuery(idD, testQuery); + createSingleTargetFilterQuery(idE, testQuery); + + mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING) + .param(MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, String.valueOf(offsetParam)) + .param(MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(knownTargetAmount))) + .andExpect(status().isOk()).andDo(MockMvcResultPrinter.print()) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(knownTargetAmount))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(expectedSize))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(expectedSize))) + // idA + .andExpect(jsonPath("$.content.[?(@.name==" + idC + ")].name", contains(idC))) + .andExpect(jsonPath("$.content.[?(@.name==" + idC + ")].query", contains(testQuery))) + // idB + .andExpect(jsonPath("$.content.[?(@.name==" + idD + ")].name", contains(idD))) + .andExpect(jsonPath("$.content.[?(@.name==" + idD + ")].query", contains(testQuery))) + // idC + .andExpect(jsonPath("$.content.[?(@.name==" + idE + ")].name", contains(idE))) + .andExpect(jsonPath("$.content.[?(@.name==" + idE + ")].query", contains(testQuery))); + } + + @Test + public void getSingleTarget() throws Exception { + // create first a target which can be retrieved by rest interface + final String knownQuery = "name=test01"; + final String knownName = "someName"; + final TargetFilterQuery tfq = createSingleTargetFilterQuery(knownName, knownQuery); + final String hrefPrefix = "http://localhost"+MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING+"/" + tfq.getId(); + // test + 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("$._links.self.href", equalTo(hrefPrefix))) + .andExpect(jsonPath("$._links.autoAssignDS.href", equalTo(hrefPrefix + "/autoAssignDS"))); + } + + @Test + public void getSingleTargetNoExistsResponseNotFound() throws Exception { + final String targetIdNotExists = "546546"; + // test + + final MvcResult mvcResult = mvc + .perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + targetIdNotExists)) + .andExpect(status().isNotFound()).andReturn(); + + // verify response json exception message + final ExceptionInfo exceptionInfo = ResourceUtility + .convertException(mvcResult.getResponse().getContentAsString()); + assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_REPO_ENTITY_NOT_EXISTS.getKey()); + } + + @Test + public void createTargetFilterQueryWithBadPayloadBadRequest() throws Exception { + final String notJson = "abc"; + + final MvcResult mvcResult = mvc + .perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING).content(notJson) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()).andReturn(); + + assertThat(targetFilterQueryManagement.countAllTargetFilterQuery()).isEqualTo(0); + + // verify response json exception message + final ExceptionInfo exceptionInfo = ResourceUtility + .convertException(mvcResult.getResponse().getContentAsString()); + assertThat(exceptionInfo.getExceptionClass()).isEqualTo(MessageNotReadableException.class.getName()); + assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_REST_BODY_NOT_READABLE.getKey()); + } + + @Test + public void setAutoAssignDistributionSetToTargetFilterQuery() throws Exception { + + final String knownQuery = "name=test05"; + final String knownName = "filter05"; + + final DistributionSet set = testdataFactory.createDistributionSet("one"); + 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()); + + assertThat(targetFilterQueryManagement.findTargetFilterQueryById(tfq.getId()).getAutoAssignDistributionSet()).isEqualTo(set); + + final String hrefPrefix = "http://localhost"+MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING+"/" + tfq.getId(); + + 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_AUTO_ASSIGN_DS, equalTo(set.getId().intValue()))) + .andExpect(jsonPath("$._links.self.href", equalTo(hrefPrefix))) + .andExpect(jsonPath("$._links.autoAssignDS.href", equalTo(hrefPrefix + "/autoAssignDS"))); + } + + @Test + public void deleteAutoAssignDistributionSetOfTargetFilterQuery() throws Exception { + + final String knownQuery = "name=test06"; + final String knownName = "filter06"; + final String dsName = "testDS"; + + final DistributionSet set = testdataFactory.createDistributionSet(dsName); + TargetFilterQuery tfq = entityFactory.generateTargetFilterQuery(); + tfq.setName(knownName); + tfq.setQuery(knownQuery); + tfq.setAutoAssignDistributionSet(set); + tfq = targetFilterQueryManagement.createTargetFilterQuery(tfq); + + assertThat(targetFilterQueryManagement.findTargetFilterQueryById(tfq.getId()).getAutoAssignDistributionSet()) + .isEqualTo(set); + + mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/"+tfq.getId()+"/autoAssignDS")) + .andExpect(status().isOk()) + .andExpect(jsonPath(JSON_PATH_NAME, equalTo(dsName))); + + mvc.perform(delete(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/"+tfq.getId()+"/autoAssignDS")) + .andExpect(status().isNoContent()); + + assertThat(targetFilterQueryManagement.findTargetFilterQueryById(tfq.getId()).getAutoAssignDistributionSet()) + .isNull(); + + mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/"+tfq.getId()+"/autoAssignDS")) + .andExpect(status().isNoContent()); + + + } + + private TargetFilterQuery createSingleTargetFilterQuery(final String name, final String query) { + final TargetFilterQuery target = entityFactory.generateTargetFilterQuery(); + target.setName(name); + target.setQuery(query); + return targetFilterQueryManagement.createTargetFilterQuery(target); + } + +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/AutoAssignProperties.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/AutoAssignProperties.java new file mode 100644 index 000000000..9f6c85c00 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/AutoAssignProperties.java @@ -0,0 +1,50 @@ +/** + * 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.repository; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * Rollout Management properties. + * + */ +@Component +@ConfigurationProperties("hawkbit.autoassign") +public class AutoAssignProperties { + /** + * Autoassign scheduler configuration. + */ + public static class Scheduler { + // used by @Scheduled annotation which needs constant + public static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.autoassign.scheduler.fixedDelay:60000}"; + + /** + * Schedule where the autoassign scheduler looks necessary state changes in + * milliseconds. + */ + private long fixedDelay = 60000L; + + public long getFixedDelay() { + return fixedDelay; + } + + public void setFixedDelay(final long fixedDelay) { + this.fixedDelay = fixedDelay; + } + + } + + private final Scheduler scheduler = new Scheduler(); + + public Scheduler getScheduler() { + return scheduler; + } + +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java index 270de3e74..ab1a680ba 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java @@ -104,6 +104,27 @@ public interface DeploymentManagement { DistributionSetAssignmentResult assignDistributionSet(@NotNull Long dsID, @NotEmpty Collection targets); + /** + * method assigns the {@link DistributionSet} to all {@link Target}s by + * their IDs with a specific {@link ActionType} and an action message. + * + * @param dsID + * the ID of the distribution set to assign + * @param targets + * a list of all targets and their action type + * @param actionMessage + * an optional message for the action status + * @return the assignment result + * + * @throw IncompleteDistributionSetException if mandatory + * {@link SoftwareModuleType} are not assigned as define by the + * {@link DistributionSetType}. + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) + DistributionSetAssignmentResult assignDistributionSet(@NotNull Long dsID, + @NotEmpty Collection targets, + String actionMessage); + /** * method assigns the {@link DistributionSet} to all {@link Target}s by * their IDs with a specific {@link ActionType} and {@code forcetime}. @@ -364,9 +385,6 @@ public interface DeploymentManagement { * pagination parameter * @param action * to be filtered on - * @param withMessages - * to true if {@link ActionStatus#getMessages()} - * need to be fetched. * @return the corresponding {@link Page} of {@link ActionStatus} */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @@ -463,8 +481,6 @@ public interface DeploymentManagement { * * @param action * to be canceled - * @param target - * for which the action needs cancellation * * @return generated {@link Action} or null if not active on * {@link Target}. @@ -478,8 +494,6 @@ public interface DeploymentManagement { * Updates a {@link Action} and forces the {@link Action} if it's not * already forced. * - * @param targetId - * the ID of the target * @param actionId * the ID of the action * @return the updated or the found {@link Action} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java index 87919729e..0491eac0c 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java @@ -316,6 +316,30 @@ public interface EntityFactory { */ TargetFilterQuery generateTargetFilterQuery(); + /** + * Generates an {@link TargetFilterQuery} without persisting it. + * + * @param name + * name for the filter + * @param query + * query of the filter + * @return {@link TargetFilterQuery} object + */ + TargetFilterQuery generateTargetFilterQuery(String name, String query); + + /** + * Generates an {@link TargetFilterQuery} without persisting it. + * + * @param name + * name for the filter + * @param query + * query of the filter + * @param autoAssignDS + * auto assign distribution set + * @return {@link TargetFilterQuery} object + */ + TargetFilterQuery generateTargetFilterQuery(String name, String query, DistributionSet autoAssignDS); + /** * Generates an empty {@link TargetTag} without persisting it. * 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 d748ed470..bd7bec4e5 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 @@ -13,6 +13,7 @@ import javax.validation.constraints.NotNull; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; +import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -70,6 +71,13 @@ public interface TargetFilterQueryManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) Page findAllTargetFilterQuery(@NotNull Pageable pageable); + /** + * Counts all target filter queries + * @return the number of all target filter queries + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Long countAllTargetFilterQuery(); + /** * Retrieves all target filter query which {@link TargetFilterQuery}. * @@ -77,11 +85,64 @@ public interface TargetFilterQueryManagement { * @param pageable * pagination parameter * @param name - * target filter query name + * name filter * @return the page with the found {@link TargetFilterQuery} */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - Page findTargetFilterQueryByFilters(@NotNull Pageable pageable, String name); + Page findTargetFilterQueryByName(@NotNull Pageable pageable, String name); + + /** + * Retrieves all target filter query which {@link TargetFilterQuery}. + * + * + * @param pageable + * pagination parameter + * @param rsqlFilter + * RSQL filter string + * @return the page with the found {@link TargetFilterQuery} + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Page findTargetFilterQueryByFilter(@NotNull Pageable pageable, String rsqlFilter); + + /** + * Retrieves all target filter query which {@link TargetFilterQuery}. + * + * + * @param pageable + * pagination parameter + * @param distributionSet + * the auto assign distribution set + * @return the page with the found {@link TargetFilterQuery} + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Page findTargetFilterQueryByAutoAssignDS(@NotNull Pageable pageable, + DistributionSet distributionSet); + + /** + * Retrieves all target filter query which {@link TargetFilterQuery}. + * + * + * @param pageable + * pagination parameter + * @param distributionSet + * the auto assign distribution set + * @param rsqlParam + * RSQL filter + * @return the page with the found {@link TargetFilterQuery} + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Page findTargetFilterQueryByAutoAssignDS(@NotNull Pageable pageable, + DistributionSet distributionSet, String rsqlParam); + + /** + * Retrieves all target filter query with auto assign DS which {@link TargetFilterQuery}. + * + * + * @return the page with the found {@link TargetFilterQuery} + * @param pageable + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Page findTargetFilterQueryWithAutoAssignDS(@NotNull Pageable pageable); /** * Find target filter query by id. diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index 5fdfb318e..d8d9e3b20 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -242,6 +242,35 @@ public interface TargetManagement { List findAllTargetIdsByTargetFilterQuery(@NotNull Pageable pageRequest, @NotNull TargetFilterQuery targetFilterQuery); + /** + * Finds all targets for all the given parameter {@link TargetFilterQuery} + * and that don't have the specified assigned distribution set. + * and returns not the full target but {@link TargetIdName}. + * + * @param pageRequest + * the pageRequest to enhance the query for paging and sorting + * @param targetFilterQuery + * {@link TargetFilterQuery} + * @return the found {@link TargetIdName}s + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Page findAllTargetIdsByTargetFilterQueryAndNonDS(@NotNull Pageable pageRequest, + Long distributionSetId, + @NotNull TargetFilterQuery targetFilterQuery); + + /** + * Counts all targets for all the given parameter {@link TargetFilterQuery} + * and that don't have the specified assigned distribution set. and returns + * not the full target but {@link TargetIdName}. + * + * @param targetFilterQuery + * {@link TargetFilterQuery} + * @return the found {@link TargetIdName}s + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Long countTargetByTargetFilterQueryAndNonDS(Long distributionSetId, + @NotNull TargetFilterQuery targetFilterQuery); + /** * retrieves {@link Target}s by the assigned {@link DistributionSet} without * details, i.e. NO {@link Target#getTags()} and {@link Target#getActions()} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSet.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSet.java index 5747b38f3..9284ce5cf 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSet.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSet.java @@ -73,6 +73,11 @@ public interface DistributionSet extends NamedVersionedEntity { */ List getAssignedTargets(); + /** + * @return the auto assign target filters + */ + List getAutoAssignFilters(); + /** * @return the installedTargets */ 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 5052a0060..5db7593a1 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 @@ -56,4 +56,16 @@ public interface TargetFilterQuery extends TenantAwareBaseEntity { */ void setQuery(String query); + /** + * @return the auto assign {@link DistributionSet} if given. + */ + DistributionSet getAutoAssignDistributionSet(); + + /** + * @param distributionSet + * the {@link DistributionSet} that should be assigned to a + * target when this filter matches. + */ + void setAutoAssignDistributionSet(DistributionSet distributionSet); + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java index 5a2c6fdda..68560724b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java @@ -175,13 +175,23 @@ public class JpaDeploymentManagement implements DeploymentManagement { @CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true) public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, final Collection targets) { + return assignDistributionSet(dsID, targets, null); + } + + @Override + @Modifying + @Transactional(isolation = Isolation.READ_COMMITTED) + @CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true) + public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, + final Collection targets, + final String actionMessage) { final JpaDistributionSet set = distributoinSetRepository.findOne(dsID); if (set == null) { throw new EntityNotFoundException( String.format("no %s with id %d found", DistributionSet.class.getSimpleName(), dsID)); } - return assignDistributionSetToTargets(set, targets, null, null); + return assignDistributionSetToTargets(set, targets, null, null, actionMessage); } @Override @@ -196,21 +206,23 @@ public class JpaDeploymentManagement implements DeploymentManagement { String.format("no %s with id %d found", DistributionSet.class.getSimpleName(), dsID)); } - return assignDistributionSetToTargets(set, targets, (JpaRollout) rollout, (JpaRolloutGroup) rolloutGroup); + return assignDistributionSetToTargets(set, targets, (JpaRollout) rollout, (JpaRolloutGroup) rolloutGroup, null); } /** * method assigns the {@link DistributionSet} to all {@link Target}s by * their IDs with a specific {@link ActionType} and {@code forcetime}. * - * @param dsID + * @param set * the ID of the distribution set to assign - * @param targets + * @param targetsWithActionType * a list of all targets and their action type * @param rollout * the rollout for this assignment * @param rolloutGroup * the rollout group for this assignment + * @param actionMessage + * an optional message to be written into the action status * @return the assignment result * * @throw IncompleteDistributionSetException if mandatory @@ -219,7 +231,7 @@ public class JpaDeploymentManagement implements DeploymentManagement { */ private DistributionSetAssignmentResult assignDistributionSetToTargets(@NotNull final JpaDistributionSet set, final Collection targetsWithActionType, final JpaRollout rollout, - final JpaRolloutGroup rolloutGroup) { + final JpaRolloutGroup rolloutGroup, final String actionMessage) { if (!set.isComplete()) { throw new IncompleteDistributionSetException( @@ -292,6 +304,9 @@ public class JpaDeploymentManagement implements DeploymentManagement { actionStatus.setAction(action); actionStatus.setOccurredAt(action.getCreatedAt()); actionStatus.setStatus(Status.RUNNING); + if(actionMessage != null) { + actionStatus.addMessage(actionMessage); + } actionStatusRepository.save(actionStatus); }); @@ -346,7 +361,7 @@ public class JpaDeploymentManagement implements DeploymentManagement { * the Target which has been assigned to a distribution set * @param actionId * the action id of the assignment - * @param softwareModules + * @param modules * the software modules which have been assigned */ private void assignDistributionSetEvent(final JpaTarget target, final Long actionId, @@ -364,11 +379,11 @@ public class JpaDeploymentManagement implements DeploymentManagement { } /** - * Removes {@link UpdateAction}s that are no longer necessary and sends + * Removes {@link Action}s that are no longer necessary and sends * cancellations to the controller. * - * @param myTarget - * to override {@link UpdateAction}s + * @param targetsIds + * to override {@link Action}s */ private Set overrideObsoleteUpdateActions(final List targetsIds) { @@ -403,7 +418,7 @@ public class JpaDeploymentManagement implements DeploymentManagement { return assignDistributionSetToTargets(set, tIDs.stream() .map(t -> new TargetWithActionType(t, actionType, forcedTime)).collect(Collectors.toList()), null, - null); + null, null); } @Override 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 e170ccad7..0459e50ac 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 @@ -95,6 +95,9 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Autowired private DistributionSetMetadataRepository distributionSetMetadataRepository; + @Autowired + private TargetFilterQueryRepository targetFilterQueryRepository; + @Autowired private ActionRepository actionRepository; @@ -182,7 +185,9 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { // soft delete assigned if (!assigned.isEmpty()) { - distributionSetRepository.deleteDistributionSet(assigned.toArray(new Long[assigned.size()])); + Long[] dsIds = assigned.toArray(new Long[assigned.size()]); + distributionSetRepository.deleteDistributionSet(dsIds); + targetFilterQueryRepository.unsetAutoAssignDistributionSet(dsIds); } // mark the rest as hard delete diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java index 51b59f08f..8079859f2 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java @@ -132,6 +132,16 @@ public class JpaEntityFactory implements EntityFactory { return new JpaTargetFilterQuery(); } + @Override + public TargetFilterQuery generateTargetFilterQuery(String name, String query) { + return new JpaTargetFilterQuery(name, query); + } + + @Override + public TargetFilterQuery generateTargetFilterQuery(String name, String query, DistributionSet autoAssignDS) { + return new JpaTargetFilterQuery(name, query, (JpaDistributionSet) autoAssignDS); + } + @Override public SoftwareModuleType generateSoftwareModuleType() { return new JpaSoftwareModuleType(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java index 3b7f77b47..10361eaea 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java @@ -56,6 +56,9 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst @Autowired private TargetRepository targetRepository; + @Autowired + private TargetFilterQueryRepository targetFilterQueryRepository; + @Autowired private ActionRepository actionRepository; @@ -221,6 +224,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst tenantMetaDataRepository.deleteByTenantIgnoreCase(tenant); tenantConfigurationRepository.deleteByTenantIgnoreCase(tenant); targetRepository.deleteByTenantIgnoreCase(tenant); + targetFilterQueryRepository.deleteByTenantIgnoreCase(tenant); actionRepository.deleteByTenantIgnoreCase(tenant); rolloutGroupRepository.deleteByTenantIgnoreCase(tenant); rolloutRepository.deleteByTenantIgnoreCase(tenant); 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 6438e19f6..27f803a00 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 @@ -12,12 +12,14 @@ import java.util.ArrayList; import java.util.List; import org.eclipse.hawkbit.repository.TargetFields; +import org.eclipse.hawkbit.repository.TargetFilterQueryFields; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; 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.model.DistributionSet; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -33,6 +35,8 @@ import org.springframework.validation.annotation.Validated; import com.google.common.base.Strings; +import javax.validation.constraints.NotNull; + /** * JPA implementation of {@link TargetFilterQueryManagement}. * @@ -67,13 +71,18 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme return convertPage(targetFilterQueryRepository.findAll(pageable), pageable); } + @Override + public Long countAllTargetFilterQuery() { + return targetFilterQueryRepository.count(); + } + private static Page convertPage(final Page findAll, final Pageable pageable) { return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements()); } @Override - public Page findTargetFilterQueryByFilters(final Pageable pageable, final String name) { + public Page findTargetFilterQueryByName(final Pageable pageable, final String name) { final List> specList = new ArrayList<>(); if (!Strings.isNullOrEmpty(name)) { specList.add(TargetFilterQuerySpecification.likeName(name)); @@ -81,6 +90,41 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable); } + @Override + public Page findTargetFilterQueryByFilter(@NotNull Pageable pageable, String rsqlFilter) { + final List> specList = new ArrayList<>(); + if (!Strings.isNullOrEmpty(rsqlFilter)) { + specList.add(RSQLUtility.parse(rsqlFilter, TargetFilterQueryFields.class)); + } + return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable); + } + + @Override + public Page findTargetFilterQueryByAutoAssignDS(@NotNull Pageable pageable, + DistributionSet distributionSet) { + return findTargetFilterQueryByAutoAssignDS(pageable, distributionSet, null); + } + + @Override + public Page findTargetFilterQueryByAutoAssignDS(@NotNull Pageable pageable, + DistributionSet distributionSet, String rsqlFilter) { + final List> specList = new ArrayList<>(); + if (distributionSet != null) { + specList.add(TargetFilterQuerySpecification.byAutoAssignDS(distributionSet)); + } + if (!Strings.isNullOrEmpty(rsqlFilter)) { + specList.add(RSQLUtility.parse(rsqlFilter, TargetFilterQueryFields.class)); + } + return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable); + } + + @Override + public Page findTargetFilterQueryWithAutoAssignDS(@NotNull Pageable pageable) { + final List> specList = new ArrayList<>(); + specList.add(TargetFilterQuerySpecification.withAutoAssignDS()); + return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable); + } + private Page findTargetFilterQueryByCriteriaAPI(final Pageable pageable, final List> specList) { if (specList == null || specList.isEmpty()) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java index 225c0b4b2..530d48d58 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java @@ -26,6 +26,7 @@ import javax.persistence.criteria.JoinType; import javax.persistence.criteria.Order; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import javax.validation.constraints.NotNull; import org.eclipse.hawkbit.repository.TargetFields; import org.eclipse.hawkbit.repository.TargetManagement; @@ -561,6 +562,31 @@ public class JpaTargetManagement implements TargetManagement { .collect(Collectors.toList()); } + @Override + public Page findAllTargetIdsByTargetFilterQueryAndNonDS(@NotNull Pageable pageRequest, + Long distributionSetId, @NotNull TargetFilterQuery targetFilterQuery) { + + final Specification spec = RSQLUtility.parse(targetFilterQuery.getQuery(), TargetFields.class); + + return findTargetsBySpec( + (root, cq, + cb) -> cb.and(spec.toPredicate(root, cq, cb), TargetSpecifications + .hasNotDistributionSetInActions(distributionSetId).toPredicate(root, cq, cb)), + pageRequest); + + } + + @Override + public Long countTargetByTargetFilterQueryAndNonDS(Long distributionSetId, @NotNull TargetFilterQuery targetFilterQuery) { + final Specification spec = RSQLUtility.parse(targetFilterQuery.getQuery(), TargetFields.class); + final List> specList = new ArrayList<>(); + specList.add(spec); + + specList.add(TargetSpecifications.hasNotDistributionSetInActions(distributionSetId)); + + return countByCriteriaAPI(specList); + } + @PreDestroy void destroy() { eventBus.unregister(this); 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 1f2fec8d8..5ebee7619 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 @@ -13,6 +13,8 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.springframework.data.domain.Page; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; @@ -43,4 +45,15 @@ public interface TargetFilterQueryRepository @Transactional S save(S entity); + /** + * Sets the auto assign distribution sets to null which match the ds ids. + * + * @param dsIds + * distribution set ids to be set to null + */ + @Modifying + @Transactional(isolation = Isolation.READ_UNCOMMITTED) + @Query("update JpaTargetFilterQuery d set d.autoAssignDistributionSet = NULL where d.autoAssignDistributionSet in :ids") + void unsetAutoAssignDistributionSet(@Param("ids") Long... dsIds); + } 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 new file mode 100644 index 000000000..f35aab453 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java @@ -0,0 +1,168 @@ +/** + * 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.repository.jpa.autoassign; + +import java.util.List; +import java.util.stream.Collectors; + +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.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.RepositoryModelConstants; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; +import org.eclipse.hawkbit.repository.model.TargetWithActionType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Component; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.support.DefaultTransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.persistence.PersistenceException; + +/** + * Checks if targets need a new distribution set (DS) based on the target filter queries and + * assigns the new DS when necessary. + * First all target filter queries are listed. For every target filter query (TFQ) the auto assign DS + * is retrieved. + * All targets get listed per target filter query, that match the TFQ and that don't have the + * auto assign DS in their action history. + */ +@Component +public class AutoAssignChecker { + + private static final Logger LOGGER = LoggerFactory.getLogger(AutoAssignChecker.class); + + @Autowired + private TargetFilterQueryManagement targetFilterQueryManagement; + + @Autowired + private TargetManagement targetManagement; + + @Autowired + private DeploymentManagement deploymentManagement; + + @Autowired + private PlatformTransactionManager transactionManager; + + private TransactionTemplate transactionTemplate; + + + /** + * Maximum for target filter queries with auto assign DS + * Maximum for targets that are fetched in one turn + */ + private static final int PAGE_SIZE = 1000; + + /** + * The message which is added to the action status when a distribution set is assigned + * to an target. First %s is the name of the target filter. + */ + private static final String ACTION_MESSAGE = "Auto assignment by target filter: %s"; + + /** + * Checks all target filter queries with an auto assign distribution set + * and triggers the check and assignment to targets that don't have the design DS yet + */ + public void check() { + if(transactionTemplate == null) { + final DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + def.setName("autoAssignDSToTargets"); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + transactionTemplate = new TransactionTemplate(transactionManager, def); + } + + PageRequest pageRequest = new PageRequest(0, PAGE_SIZE); + + Page filterQueries = targetFilterQueryManagement + .findTargetFilterQueryWithAutoAssignDS(pageRequest); + + for (TargetFilterQuery filterQuery : filterQueries) { + checkByTargetFilterQueryAndAssignDS(filterQuery); + } + + } + + /** + * Fetches the distribution set, gets all controllerIds and assigns the DS + * to them. + * Catches PersistenceException and own exceptions derived from AbstractServerRtException + * + * @param targetFilterQuery + * the target filter query + */ + private void checkByTargetFilterQueryAndAssignDS(TargetFilterQuery targetFilterQuery) { + try { + DistributionSet distributionSet = targetFilterQuery.getAutoAssignDistributionSet(); + + int count; + do { + + count = runTransactionalAssignment(targetFilterQuery, distributionSet.getId()); + + } while (count == PAGE_SIZE); + + } catch (PersistenceException | AbstractServerRtException e) { + LOGGER.error("Error during auto assign check of target filter query " + targetFilterQuery.getId(), e); + } + + } + + /** + * Runs one page of target assignments within a dedicated transaction + * + * @param targetFilterQuery + * the target filter query + * @param dsId + * distribution set id to assign + * @return count of targets + */ + private int runTransactionalAssignment(TargetFilterQuery targetFilterQuery, Long dsId) { + final String actionMessage = String.format(ACTION_MESSAGE, targetFilterQuery.getName()); + return transactionTemplate.execute(status -> { + List targets = getTargetsWithActionType(targetFilterQuery, dsId, PAGE_SIZE); + int count = targets.size(); + if (count > 0) { + deploymentManagement.assignDistributionSet(dsId, targets, actionMessage); + } + return count; + }); + } + + /** + * Gets all matching targets with the designated action from the target management + * + * @param targetFilterQuery + * the query the targets have to match + * @param dsId + * dsId the targets are not allowed to have in their action + * history + * @param count + * maximum amount of targets to retrieve + * @return list of targets with action type + */ + private List getTargetsWithActionType(TargetFilterQuery targetFilterQuery, Long dsId, int count) { + Page targets = targetManagement.findAllTargetIdsByTargetFilterQueryAndNonDS(new PageRequest(0, count), + dsId, targetFilterQuery); + + return targets.getContent().stream() + .map(t -> new TargetWithActionType(t.getControllerId(), Action.ActionType.FORCED, + RepositoryModelConstants.NO_FORCE_TIME)) + .collect(Collectors.toList()); + } + +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java new file mode 100644 index 000000000..81ec130dc --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java @@ -0,0 +1,77 @@ +/** + * 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.repository.jpa.autoassign; + +import java.util.List; + +import org.eclipse.hawkbit.repository.AutoAssignProperties; +import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.eclipse.hawkbit.tenancy.TenantAware; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * Scheduler to check target filters for auto assignment of distribution sets + */ +@Component +// don't active the auto assign scheduler in test, otherwise it is hard to test +@Profile("!test") +public class AutoAssignScheduler { + + private static final Logger LOGGER = LoggerFactory.getLogger(AutoAssignScheduler.class); + + @Autowired + private TenantAware tenantAware; + + @Autowired + private SystemManagement systemManagement; + + @Autowired + private SystemSecurityContext systemSecurityContext; + + @Autowired + private AutoAssignChecker autoAssignChecker; + + /** + * Scheduler method called by the spring-async mechanism. Retrieves all + * tenants from the {@link SystemManagement#findTenants()} and runs for each + * tenant the auto assignments defined in the target filter queries + * {@link SystemSecurityContext}. + */ + @Scheduled(initialDelayString = AutoAssignProperties.Scheduler.PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = AutoAssignProperties.Scheduler.PROP_SCHEDULER_DELAY_PLACEHOLDER) + public void autoAssignScheduler() { + LOGGER.debug("auto assign schedule checker has been triggered."); + // run this code in system code privileged to have the necessary + // permission to query and create entities. + systemSecurityContext.runAsSystem(() -> { + // workaround eclipselink that is currently not possible to + // execute a query without multitenancy if MultiTenant + // annotation is used. + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So + // iterate through all tenants and execute the rollout check for + // each tenant separately. + final List tenants = systemManagement.findTenants(); + LOGGER.info("Checking target filter queries for tenants: {}", tenants.size()); + for (final String tenant : tenants) { + tenantAware.runAsTenant(tenant, () -> { + + autoAssignChecker.check(); + + return null; + }); + } + return null; + }); + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java index 9fc27f1e8..8b5da1f68 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java @@ -51,6 +51,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetInfo; import org.eclipse.persistence.annotations.CascadeOnDelete; import org.eclipse.persistence.descriptors.DescriptorEvent; @@ -96,6 +97,9 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen @OneToMany(mappedBy = "assignedDistributionSet", targetEntity = JpaTarget.class, fetch = FetchType.LAZY) private List assignedToTargets; + @OneToMany(mappedBy = "autoAssignDistributionSet", targetEntity = JpaTargetFilterQuery.class, fetch = FetchType.LAZY) + private List autoAssignFilters; + @OneToMany(mappedBy = "installedDistributionSet", targetEntity = JpaTargetInfo.class, fetch = FetchType.LAZY) private List installedAtTargets; @@ -196,6 +200,11 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen return assignedToTargets; } + @Override + public List getAutoAssignFilters() { + return autoAssignFilters; + } + @Override public List getInstalledTargets() { return installedAtTargets; 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 2a40619b2..b7e80cbb3 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 @@ -9,13 +9,19 @@ package org.eclipse.hawkbit.repository.jpa.model; import javax.persistence.Column; +import javax.persistence.ConstraintMode; import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import javax.persistence.Table; import javax.persistence.UniqueConstraint; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; /** @@ -42,6 +48,10 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity imple @NotNull private String query; + @ManyToOne(optional = true, fetch = FetchType.LAZY, targetEntity = JpaDistributionSet.class) + @JoinColumn(name = "auto_assign_distribution_set", nullable = true, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_filter_auto_assign_ds")) + private JpaDistributionSet autoAssignDistributionSet; + public JpaTargetFilterQuery() { // Default constructor for JPA. } @@ -59,6 +69,22 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity imple this.query = query; } + /** + * Construct a Target filter query with auto assign distribution set + * + * @param name + * of the {@link TargetFilterQuery}. + * @param query + * of the {@link TargetFilterQuery}. + * @param autoAssignDistributionSet + * of the {@link TargetFilterQuery}. + */ + public JpaTargetFilterQuery(String name, String query, JpaDistributionSet autoAssignDistributionSet) { + this.name = name; + this.query = query; + this.autoAssignDistributionSet = autoAssignDistributionSet; + } + @Override public String getName() { return name; @@ -78,4 +104,14 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity imple public void setQuery(final String query) { this.query = query; } + + @Override + public DistributionSet getAutoAssignDistributionSet() { + return autoAssignDistributionSet; + } + + @Override + public void setAutoAssignDistributionSet(DistributionSet distributionSet) { + autoAssignDistributionSet = (JpaDistributionSet)distributionSet; + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetFilterQuerySpecification.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetFilterQuerySpecification.java index 4b27c7fc7..d783738ae 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetFilterQuerySpecification.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetFilterQuerySpecification.java @@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.jpa.specifications; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery_; +import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.springframework.data.jpa.domain.Specification; @@ -37,4 +38,28 @@ public final class TargetFilterQuerySpecification { return cb.like(cb.lower(targetFilterQueryRoot.get(JpaTargetFilterQuery_.name)), searchTextToLower); }; } + + /** + * {@link Specification} for retrieving {@link JpaTargetFilterQuery}s based + * on is {@link JpaTargetFilterQuery#getName()}. + * + * @param distributionSet + * of the filter + * @return the {@link JpaTargetFilterQuery} {@link Specification} + */ + public static Specification byAutoAssignDS(final DistributionSet distributionSet) { + return (targetFilterQueryRoot, query, cb) -> cb + .equal(targetFilterQueryRoot.get(JpaTargetFilterQuery_.autoAssignDistributionSet), distributionSet); + } + + /** + * {@link Specification} for retrieving {@link JpaTargetFilterQuery}s based + * on is {@link JpaTargetFilterQuery#getName()}. + * + * @return the {@link JpaTargetFilterQuery} {@link Specification} + */ + public static Specification withAutoAssignDS() { + return (targetFilterQueryRoot, query, cb) -> cb + .isNotNull(targetFilterQueryRoot.get(JpaTargetFilterQuery_.autoAssignDistributionSet)); + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java index 6571f8668..dbf0e4e23 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java @@ -14,12 +14,15 @@ import java.util.List; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.ListJoin; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.persistence.criteria.SetJoin; import javax.validation.constraints.NotNull; +import org.eclipse.hawkbit.repository.jpa.model.JpaAction; +import org.eclipse.hawkbit.repository.jpa.model.JpaAction_; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; @@ -210,6 +213,24 @@ public final class TargetSpecifications { distributionSetId); } + /** + * {@link Specification} for retrieving {@link Target}s that don't have the given + * distribution set in their action history + * + * @param distributionSetId + * the ID of the distribution set which must not be assigned + * @return the {@link Target} {@link Specification} + */ + public static Specification hasNotDistributionSetInActions(final Long distributionSetId) { + return (targetRoot, query, cb) -> { + final ListJoin actionsJoin = targetRoot.join(JpaTarget_.actions, JoinType.LEFT); + actionsJoin.on(cb.equal(actionsJoin.get(JpaAction_.distributionSet).get(JpaDistributionSet_.id), + distributionSetId)); + + return cb.isNull(actionsJoin.get(JpaAction_.id)); + }; + } + /** * {@link Specification} for retrieving {@link Target}s by assigned * distribution set. diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_8_0__auto_assign_ds_filter__H2.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_8_0__auto_assign_ds_filter__H2.sql new file mode 100644 index 000000000..2d56f27f2 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_8_0__auto_assign_ds_filter__H2.sql @@ -0,0 +1,8 @@ +ALTER TABLE sp_target_filter_query + ADD column auto_assign_distribution_set BIGINT; + +ALTER TABLE sp_target_filter_query + ADD CONSTRAINT fk_filter_auto_assign_ds +FOREIGN KEY (auto_assign_distribution_set) +REFERENCES sp_distribution_set +ON DELETE SET NULL; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_8_0__auto_assign_ds_filter__MYSQL.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_8_0__auto_assign_ds_filter__MYSQL.sql new file mode 100644 index 000000000..e56a6d242 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_8_0__auto_assign_ds_filter__MYSQL.sql @@ -0,0 +1,8 @@ +ALTER TABLE sp_target_filter_query + ADD COLUMN auto_assign_distribution_set BIGINT; + +ALTER TABLE sp_target_filter_query + ADD CONSTRAINT fk_filter_auto_assign_ds +FOREIGN KEY (auto_assign_distribution_set) +REFERENCES sp_distribution_set (id) +ON DELETE SET NULL; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagenmentTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagenmentTest.java index 07b92ebeb..b51511d17 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagenmentTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagenmentTest.java @@ -8,19 +8,27 @@ */ package org.eclipse.hawkbit.repository.jpa; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.junit.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; +import java.util.Iterator; +import java.util.List; + +import static org.fest.assertions.api.Assertions.assertThat; +import static org.junit.Assert.*; + /** * Test class for {@link TargetFilterQueryManagement}. * @@ -39,6 +47,22 @@ public class TargetFilterQueryManagenmentTest extends AbstractJpaIntegrationTest targetFilterQueryManagement.findTargetFilterQueryByName(filterName)); } + @Test + @Description("Test searching a target filter query.") + public void searchTargetFilterQuery() { + final String filterName = "targetFilterQueryName"; + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); + + targetFilterQueryManagement + .createTargetFilterQuery(new JpaTargetFilterQuery("someOtherFilter", "name==PendingTargets002")); + + List results = targetFilterQueryManagement + .findTargetFilterQueryByFilter(new PageRequest(0, 10), "name==" + filterName).getContent(); + assertEquals("Search result should have 1 result", 1, results.size()); + assertEquals("Retrieved newly created custom target filter", targetFilterQuery, results.get(0)); + } + @Test @Description("Checks if the EntityAlreadyExistsException is thrown if a targetfilterquery with the same name are created more than once.") public void createDuplicateTargetFilterQuery() { @@ -82,4 +106,142 @@ public class TargetFilterQueryManagenmentTest extends AbstractJpaIntegrationTest } + @Test + @Description("Test assigning a distribution set") + public void assignDistributionSet() { + final String filterName = "target_filter_02"; + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); + + final DistributionSet distributionSet = distributionSetManagement.createDistributionSet(new JpaDistributionSet( + "dist_Set_01", "0.1", "", null, null + )); + + targetFilterQuery.setAutoAssignDistributionSet(distributionSet); + targetFilterQueryManagement.updateTargetFilterQuery(targetFilterQuery); + + TargetFilterQuery tfq = targetFilterQueryManagement.findTargetFilterQueryByName(filterName); + + assertEquals("Returns correct distribution set", distributionSet, + tfq.getAutoAssignDistributionSet()); + + } + + @Test + @Description("Test removing distribution set while it has a relation to a target filter query") + public void removeAssignDistributionSet() { + final String filterName = "target_filter_03"; + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); + + final DistributionSet distributionSet = distributionSetManagement.createDistributionSet(new JpaDistributionSet( + "dist_Set_02", "0.1", "", null, null + )); + + targetFilterQuery.setAutoAssignDistributionSet(distributionSet); + targetFilterQueryManagement.updateTargetFilterQuery(targetFilterQuery); + + // Check if target filter query is there + TargetFilterQuery tfq = targetFilterQueryManagement.findTargetFilterQueryByName(filterName); + assertEquals("Returns correct distribution set", distributionSet, + tfq.getAutoAssignDistributionSet()); + + distributionSetManagement.deleteDistributionSet(distributionSet); + + // Check if auto assign distribution set is null + tfq = targetFilterQueryManagement.findTargetFilterQueryByName(filterName); + assertNotNull("Returns target filter query", tfq); + assertNull("Returns distribution set as null", tfq.getAutoAssignDistributionSet()); + + } + + @Test + @Description("Test to implicitly remove the auto assign distribution set when the ds is soft deleted") + public void implicitlyRemoveAssignDistributionSet() { + final String filterName = "target_filter_03"; + DistributionSet distributionSet = testdataFactory.createDistributionSet("dist_set"); + Target target = testdataFactory.createTarget(); + + // Assign the distribution set to an target, to force a soft delete in a + // later step + deploymentManagement.assignDistributionSet(distributionSet.getId(), target.getControllerId()); + + targetFilterQueryManagement.createTargetFilterQuery( + new JpaTargetFilterQuery(filterName, "name==PendingTargets001", (JpaDistributionSet) distributionSet)); + + // Check if target filter query is there with the distribution set + TargetFilterQuery tfq = targetFilterQueryManagement.findTargetFilterQueryByName(filterName); + assertEquals("Returns correct distribution set", distributionSet, tfq.getAutoAssignDistributionSet()); + + distributionSetManagement.deleteDistributionSet(distributionSet); + + // Check if distribution set is still in the database with deleted flag + assertTrue("Distribution set should be deleted", + distributionSetManagement.findDistributionSetById(distributionSet.getId()).isDeleted()); + + // Check if auto assign distribution set is null + tfq = targetFilterQueryManagement.findTargetFilterQueryByName(filterName); + assertNotNull("Returns target filter query", tfq); + assertNull("Returns distribution set as null", tfq.getAutoAssignDistributionSet()); + + } + + @Test + @Description("Test finding and auto assign distribution set") + public void findFiltersWithDistributionSet() { + + final String filterName = "d"; + + assertEquals(0L, targetFilterQueryManagement.countAllTargetFilterQuery().longValue()); + + targetFilterQueryManagement.createTargetFilterQuery(new JpaTargetFilterQuery("a", "name==*")); + targetFilterQueryManagement.createTargetFilterQuery(new JpaTargetFilterQuery("b", "name==*")); + + final DistributionSet distributionSet = distributionSetManagement + .createDistributionSet(new JpaDistributionSet("dist_Set_01", "0.1", "", null, null)); + final DistributionSet distributionSet2 = distributionSetManagement + .createDistributionSet(new JpaDistributionSet("dist_Set_02", "0.1", "", null, null)); + + final TargetFilterQuery tfq = targetFilterQueryManagement.createTargetFilterQuery( + new JpaTargetFilterQuery("c", "name==x", (JpaDistributionSet) distributionSet)); + + final TargetFilterQuery tfq2 = targetFilterQueryManagement.createTargetFilterQuery( + new JpaTargetFilterQuery(filterName, "name==z*", (JpaDistributionSet) distributionSet2)); + + assertEquals(4L, targetFilterQueryManagement.countAllTargetFilterQuery().longValue()); + + // check if find works + Page tfqList = targetFilterQueryManagement + .findTargetFilterQueryByAutoAssignDS(new PageRequest(0, 500), distributionSet); + assertThat(1L).as("Target filter query").isEqualTo(tfqList.getTotalElements()); + + assertEquals("Returns correct target filter query", tfq.getId(), tfqList.iterator().next().getId()); + + tfq2.setAutoAssignDistributionSet(distributionSet); + targetFilterQueryManagement.updateTargetFilterQuery(tfq2); + + // check if find works for two + tfqList = targetFilterQueryManagement.findTargetFilterQueryByAutoAssignDS(new PageRequest(0, 500), + distributionSet); + 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()); + + // check if find works with name filter + tfqList = targetFilterQueryManagement.findTargetFilterQueryByAutoAssignDS(new PageRequest(0, 500), + distributionSet, "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.findTargetFilterQueryWithAutoAssignDS(new PageRequest(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()); + + } + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java index 1e8f102c7..58856f16c 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java @@ -22,16 +22,10 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag; -import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.*; import org.eclipse.hawkbit.repository.model.Action.Status; -import org.eclipse.hawkbit.repository.model.ActionStatus; -import org.eclipse.hawkbit.repository.model.DistributionSet; -import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.repository.model.TargetIdName; -import org.eclipse.hawkbit.repository.model.TargetTag; -import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; -import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; import org.junit.Test; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; import com.google.common.collect.Lists; @@ -727,6 +721,26 @@ public class TargetManagementSearchTest extends AbstractJpaIntegrationTest { } + @Test + @Description("Verifies that targets without given assigned DS are returned from repository.") + public void findTargetWithoutAssignedDistributionSet() { + final DistributionSet assignedSet = testdataFactory.createDistributionSet(""); + final TargetFilterQuery tfq = targetFilterQueryManagement + .createTargetFilterQuery(entityFactory.generateTargetFilterQuery("tfq", "name==*")); + List unassignedTargets = targetManagement + .createTargets(testdataFactory.generateTargets(12, "unassigned")); + List assignedTargets = targetManagement.createTargets(testdataFactory.generateTargets(10, "assigned")); + + deploymentManagement.assignDistributionSet(assignedSet, assignedTargets); + + List result = targetManagement.findAllTargetIdsByTargetFilterQueryAndNonDS(pageReq, + assignedSet.getId(), tfq).getContent(); + assertThat(result) + .as("count of targets").hasSize(unassignedTargets.size()) + .as("contains all targets").containsAll(unassignedTargets); + + } + @Test @Description("Verfies that targets with given installed DS are returned from repository.") public void findTargetByInstalledDistributionSet() { 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 new file mode 100644 index 000000000..dd7293fb2 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java @@ -0,0 +1,157 @@ +/** + * 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.repository.jpa.autoassign; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; +import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.DistributionSetType; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; +import org.junit.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Step; +import ru.yandex.qatools.allure.annotations.Stories; + +import static org.fest.assertions.api.Assertions.assertThat; + +/** + * Test class for {@link AutoAssignChecker}. + * + */ +@Features("Component Tests - Repository") +@Stories("Auto assign checker") +public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest { + + @Autowired + private AutoAssignChecker autoAssignChecker; + + @Test + @Description("Test auto assignment of a DS to filtered targets") + public void checkAutoAssign() { + + final DistributionSet setA = testdataFactory.createDistributionSet("dsA"); // will be auto assigned + final DistributionSet setB = testdataFactory.createDistributionSet("dsB"); + + // target filter query that matches all targets + TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .createTargetFilterQuery(new JpaTargetFilterQuery("filterA", "name==*")); + targetFilterQuery.setAutoAssignDistributionSet(setA); + targetFilterQueryManagement.updateTargetFilterQuery(targetFilterQuery); + + + final String targetDsAIdPref = "targ"; + List targets = targetManagement.createTargets( + testdataFactory.generateTargets(100, targetDsAIdPref, targetDsAIdPref.concat(" description"))); + int targetsCount = targets.size(); + + // assign set A to first 10 targets + deploymentManagement.assignDistributionSet(setA, targets.subList(0, 10)); + verifyThatTargetsHaveDistributionSetAssignment(setA, targets.subList(0, 10), targetsCount); + + // assign set B to first 5 targets + // they have now 2 DS in their action history and should not get updated with dsA + deploymentManagement.assignDistributionSet(setB, targets.subList(0, 5)); + verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(0, 5), targetsCount); + + // assign set B to next 10 targets + deploymentManagement.assignDistributionSet(setB, targets.subList(10, 20)); + verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(10, 20), targetsCount); + + // Count the number of targets that will be assigned with setA + assertThat(targetManagement.countTargetByTargetFilterQueryAndNonDS(setA.getId(), targetFilterQuery)) + .isEqualTo(90); + + // Run the check + autoAssignChecker.check(); + + verifyThatTargetsHaveDistributionSetAssignment(setA, targets.subList(5, 100), targetsCount); + + // first 5 should keep their dsB, because they already had the dsA once + verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(0, 5), targetsCount); + + } + + @Test + @Description("Test auto assignment of an incomplete DS to filtered targets, that causes failures") + public void checkAutoAssignWithFailures() { + + // incomplete distribution set that will be assigned + final DistributionSet setF = distributionSetManagement + .createDistributionSet(entityFactory.generateDistributionSet("dsA", "1", "incomplete ds", + testdataFactory.findOrCreateDefaultTestDsType(), null)); + final DistributionSet setA = testdataFactory.createDistributionSet("dsA"); + final DistributionSet setB = testdataFactory.createDistributionSet("dsB"); + + final String targetDsAIdPref = "targA"; + final String targetDsFIdPref = "targB"; + + // target filter query that matches first bunch of targets, that should + // fail + targetFilterQueryManagement.createTargetFilterQuery( + new JpaTargetFilterQuery("filterA", "id==" + targetDsFIdPref + "*", (JpaDistributionSet) setF)); + + // target filter query that matches failed bunch of targets + targetFilterQueryManagement.createTargetFilterQuery( + new JpaTargetFilterQuery("filterB", "id==" + targetDsAIdPref + "*", (JpaDistributionSet) setA)); + + List targetsF = targetManagement.createTargets( + testdataFactory.generateTargets(10, targetDsFIdPref, targetDsFIdPref.concat(" description"))); + + List targetsA = targetManagement.createTargets( + testdataFactory.generateTargets(10, targetDsAIdPref, targetDsAIdPref.concat(" description"))); + + int targetsCount = targetsA.size() + targetsF.size(); + + // assign set B to first 5 targets of fail group + deploymentManagement.assignDistributionSet(setB, targetsF.subList(0, 5)); + verifyThatTargetsHaveDistributionSetAssignment(setB, targetsF.subList(0, 5), targetsCount); + + // Run the check + autoAssignChecker.check(); + + // first 5 targets of the fail group should still have setB + verifyThatTargetsHaveDistributionSetAssignment(setB, targetsF.subList(0, 5), targetsCount); + + // all targets of A group should have received setA + verifyThatTargetsHaveDistributionSetAssignment(setA, targetsA, targetsCount); + + } + + /** + * @param set the expected distribution set + * @param targets the targets that should have it + */ + @Step + private void verifyThatTargetsHaveDistributionSetAssignment(final DistributionSet set, List targets, int count) { + List targetIds = targets.stream().map(Target::getId).collect(Collectors.toList()); + + Slice targetsAll = targetManagement.findTargetsAll(new PageRequest(0, 1000)); + assertThat(targetsAll).as("Count of targets").hasSize(count); + + for (Target target : targetsAll) { + if(targetIds.contains(target.getId())) { + assertThat(target.getAssignedDistributionSet()).as("assigned DS").isEqualTo(set); + } + } + + } + +} \ No newline at end of file diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java index 418944582..f73c1875a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java @@ -263,9 +263,9 @@ public class CommonDialogWindow extends Window { if (field instanceof Table) { ((Table) field).addItemSetChangeListener(new ChangeListener(field)); - } else { - field.addValueChangeListener(new ChangeListener(field)); } + field.addValueChangeListener(new ChangeListener(field)); + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/TargetFilterQueryDetailsTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/TargetFilterQueryDetailsTable.java new file mode 100644 index 000000000..3371e0d64 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/TargetFilterQueryDetailsTable.java @@ -0,0 +1,107 @@ +/** + * 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.common.detailslayout; + +import java.util.List; + +import com.vaadin.data.Container; +import com.vaadin.data.Item; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; + +import com.vaadin.data.util.IndexedContainer; +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.VaadinSessionScope; +import com.vaadin.ui.Table; +import com.vaadin.ui.themes.ValoTheme; +import org.eclipse.hawkbit.ui.utils.I18N; +import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; + +/** + * + * DistributionSet TargetFilterQuery table + * + */ + +@SpringComponent +@VaadinSessionScope +public class TargetFilterQueryDetailsTable extends Table { + + private static final long serialVersionUID = 2913758299611837718L; + + private static final String TFQ_NAME = "name"; + private static final String TFQ_QUERY = "query"; + + private I18N i18n; + + /** + * + * @param i18n + */ + public void init(final I18N i18n) { + this.i18n = i18n; + createTable(); + } + + /** + * Populate software module metadata. + * + * @param distributionSet the selected distribution set + */ + public void populateTableByDistributionSet(final DistributionSet distributionSet) { + removeAllItems(); + if (null == distributionSet) { + return; + } + + Container dataSource = getContainerDataSource(); + List filters = distributionSet.getAutoAssignFilters(); + filters.forEach(query -> { + Object itemId = dataSource.addItem(); + Item item = dataSource.getItem(itemId); + item.getItemProperty(TFQ_NAME).setValue(query.getName()); + item.getItemProperty(TFQ_QUERY).setValue(query.getQuery()); + }); + + } + + private void createTable() { + addStyleName(ValoTheme.TABLE_NO_HORIZONTAL_LINES); + addStyleName(ValoTheme.TABLE_NO_STRIPES); + addStyleName(SPUIStyleDefinitions.SW_MODULE_TABLE); + addStyleName("details-layout"); + setSelectable(false); + setImmediate(true); + setContainerDataSource(getDistSetContainer()); + setColumnHeaderMode(ColumnHeaderMode.EXPLICIT); + addTableHeader(); + setSizeFull(); + // same as height of other tabs in details tabsheet + setHeight(116, Unit.PIXELS); + } + + private IndexedContainer getDistSetContainer() { + final IndexedContainer container = new IndexedContainer(); + container.addContainerProperty(TFQ_NAME, String.class, ""); + container.addContainerProperty(TFQ_QUERY, String.class, ""); + setColumnExpandRatio(TFQ_NAME, 0.4F); + setColumnAlignment(TFQ_NAME, Align.LEFT); + setColumnExpandRatio(TFQ_QUERY, 0.6F); + setColumnAlignment(TFQ_QUERY, Align.LEFT); + + return container; + } + + private void addTableHeader() { + setColumnHeader(TFQ_NAME, i18n.get("header.target.filter.name")); + setColumnHeader(TFQ_QUERY, i18n.get("header.target.filter.query")); + } + + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyDistribution.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyDistribution.java index 0420f9e4b..fccf7d6a7 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyDistribution.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyDistribution.java @@ -10,6 +10,9 @@ package org.eclipse.hawkbit.ui.components; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.ui.common.DistributionSetIdName; +import org.eclipse.hawkbit.ui.common.UserDetailsFormatter; +import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; +import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; /** * Proxy for {@link DistributionSet}. @@ -39,6 +42,33 @@ public class ProxyDistribution { private String version; private String description; + /** + * Creates an empty proxy distribution set + */ + public ProxyDistribution() { + // Default constructor + } + + /** + * Creates a new proxy distribution set by using the values from a distribution set + * @param distributionSet the source distribution set + */ + public ProxyDistribution(DistributionSet distributionSet) { + setName(distributionSet.getName()); + setDescription(distributionSet.getDescription()); + setDistId(distributionSet.getId()); + setId(distributionSet.getId()); + setVersion(distributionSet.getVersion()); + setCreatedDate(SPDateTimeUtil.getFormattedDate(distributionSet.getCreatedAt())); + setLastModifiedDate(SPDateTimeUtil.getFormattedDate(distributionSet.getLastModifiedAt())); + setCreatedByUser(UserDetailsFormatter.loadAndFormatCreatedBy(distributionSet)); + setModifiedByUser(UserDetailsFormatter.loadAndFormatLastModifiedBy(distributionSet)); + setNameVersion( + HawkbitCommonUtil.getFormattedNameVersion(distributionSet.getName(), distributionSet.getVersion())); + setIsComplete(distributionSet.isComplete()); + } + + /** * @return the nameVersion */ 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 9faf501e0..75d8d343c 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 @@ -26,6 +26,7 @@ public class ProxyTargetFilter { private String createdBy; private String lastModifiedBy; private String query; + private ProxyDistribution autoAssignDistributionSet; public String getCreatedDate() { return createdDate; @@ -90,4 +91,11 @@ public class ProxyTargetFilter { this.createdBy = createdBy; } + public ProxyDistribution getAutoAssignDistributionSet() { + return autoAssignDistributionSet; + } + + public void setAutoAssignDistributionSet(ProxyDistribution autoAssignDistributionSet) { + this.autoAssignDistributionSet = autoAssignDistributionSet; + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetDetails.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetDetails.java index a2ded014d..969b2e63a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetDetails.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetDetails.java @@ -25,6 +25,7 @@ import org.eclipse.hawkbit.ui.common.DistributionSetIdName; import org.eclipse.hawkbit.ui.common.detailslayout.AbstractNamedVersionedEntityTableDetailsLayout; import org.eclipse.hawkbit.ui.common.detailslayout.DistributionSetMetadatadetailslayout; import org.eclipse.hawkbit.ui.common.detailslayout.SoftwareModuleDetailsTable; +import org.eclipse.hawkbit.ui.common.detailslayout.TargetFilterQueryDetailsTable; import org.eclipse.hawkbit.ui.common.tagdetails.DistributionTagToken; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder; @@ -91,6 +92,8 @@ public class DistributionSetDetails extends AbstractNamedVersionedEntityTableDet private DistributionSetMetadatadetailslayout dsMetadataTable; + private TargetFilterQueryDetailsTable tfqDetailsTable; + private VerticalLayout tagsLayout; private final Map assignedSWModule = new HashMap<>(); @@ -103,9 +106,14 @@ public class DistributionSetDetails extends AbstractNamedVersionedEntityTableDet softwareModuleTable = new SoftwareModuleDetailsTable(); softwareModuleTable.init(getI18n(), true, getPermissionChecker(), distributionSetManagement, getEventBus(), manageDistUIState); + dsMetadataTable = new DistributionSetMetadatadetailslayout(); dsMetadataTable.init(getI18n(), getPermissionChecker(), distributionSetManagement, dsMetadataPopupLayout, entityFactory); + + tfqDetailsTable = new TargetFilterQueryDetailsTable(); + tfqDetailsTable.init(getI18n()); + super.init(); } @@ -120,6 +128,7 @@ public class DistributionSetDetails extends AbstractNamedVersionedEntityTableDet populateModule(); populateTags(); populateMetadataDetails(); + populateTargetFilterQueries(); } private void populateModule() { @@ -268,6 +277,10 @@ public class DistributionSetDetails extends AbstractNamedVersionedEntityTableDet dsMetadataTable.populateDSMetadata(getSelectedBaseEntity()); } + protected void populateTargetFilterQueries() { + tfqDetailsTable.populateTableByDistributionSet(getSelectedBaseEntity()); + } + private void updateDistributionSetDetailsLayout(final String type, final Boolean isMigrationRequired) { final VerticalLayout detailsTabLayout = getDetailsLayout(); detailsTabLayout.removeAllComponents(); @@ -323,6 +336,7 @@ public class DistributionSetDetails extends AbstractNamedVersionedEntityTableDet detailsTab.addTab(createTagsLayout(), getI18n().get("caption.tags.tab"), null); detailsTab.addTab(createLogLayout(), getI18n().get("caption.logs.tab"), null); detailsTab.addTab(dsMetadataTable, getI18n().get("caption.metadata"), null); + detailsTab.addTab(tfqDetailsTable, getI18n().get("caption.auto.assignment.ds"), null); } @Override 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 a3a08f2b7..c6443d042 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 @@ -20,10 +20,8 @@ import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetFilter; import org.eclipse.hawkbit.repository.model.DistributionSetFilter.DistributionSetFilterBuilder; import org.eclipse.hawkbit.repository.model.DistributionSetType; -import org.eclipse.hawkbit.ui.common.UserDetailsFormatter; import org.eclipse.hawkbit.ui.components.ProxyDistribution; import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; -import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SpringContextHelper; import org.springframework.data.domain.Page; @@ -48,6 +46,7 @@ public class ManageDistBeanQuery extends AbstractBeanQuery { private transient Page firstPageDistributionSets = null; private DistributionSetType distributionSetType = null; + private Boolean dsComplete = null; /** * @@ -69,6 +68,9 @@ public class ManageDistBeanQuery extends AbstractBeanQuery { distributionSetType = (DistributionSetType) queryConfig .get(SPUIDefinitions.FILTER_BY_DISTRIBUTION_SET_TYPE); } + if(null != queryConfig.get(SPUIDefinitions.FILTER_BY_DS_COMPLETE)) { + dsComplete = (Boolean)queryConfig.get(SPUIDefinitions.FILTER_BY_DS_COMPLETE); + } } if (sortStates.length > 0) { @@ -96,27 +98,17 @@ public class ManageDistBeanQuery extends AbstractBeanQuery { } else if (Strings.isNullOrEmpty(searchText)) { // if no search filters available distBeans = getDistributionSetManagement().findDistributionSetsByDeletedAndOrCompleted( - new OffsetBasedPageRequest(startIndex, count, sort), false, null); + new OffsetBasedPageRequest(startIndex, count, sort), false, dsComplete); } else { final DistributionSetFilter distributionSetFilter = new DistributionSetFilterBuilder().setIsDeleted(false) + .setIsComplete(dsComplete) .setSearchText(searchText).setSelectDSWithNoTag(Boolean.FALSE).setType(distributionSetType).build(); distBeans = getDistributionSetManagement().findDistributionSetsByFilters( new PageRequest(startIndex / count, count, sort), distributionSetFilter); } for (final DistributionSet distributionSet : distBeans) { - final ProxyDistribution proxyDistribution = new ProxyDistribution(); - proxyDistribution.setName(distributionSet.getName()); - proxyDistribution.setDescription(distributionSet.getDescription()); - proxyDistribution.setDistId(distributionSet.getId()); - proxyDistribution.setId(distributionSet.getId()); - proxyDistribution.setVersion(distributionSet.getVersion()); - proxyDistribution.setCreatedDate(SPDateTimeUtil.getFormattedDate(distributionSet.getCreatedAt())); - proxyDistribution.setLastModifiedDate(SPDateTimeUtil.getFormattedDate(distributionSet.getLastModifiedAt())); - proxyDistribution.setCreatedByUser(UserDetailsFormatter.loadAndFormatCreatedBy(distributionSet)); - proxyDistribution.setModifiedByUser(UserDetailsFormatter.loadAndFormatLastModifiedBy(distributionSet)); - proxyDistribution.setIsComplete(distributionSet.isComplete()); - proxyDistributions.add(proxyDistribution); + proxyDistributions.add(new ProxyDistribution(distributionSet)); } return proxyDistributions; } @@ -132,9 +124,10 @@ public class ManageDistBeanQuery extends AbstractBeanQuery { if (Strings.isNullOrEmpty(searchText) && null == distributionSetType) { // if no search filters available firstPageDistributionSets = getDistributionSetManagement().findDistributionSetsByDeletedAndOrCompleted( - new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), false, null); + new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), false, dsComplete); } else { final DistributionSetFilter distributionSetFilter = new DistributionSetFilterBuilder().setIsDeleted(false) + .setIsComplete(dsComplete) .setSearchText(searchText).setSelectDSWithNoTag(Boolean.FALSE).setType(distributionSetType).build(); firstPageDistributionSets = getDistributionSetManagement().findDistributionSetsByFilters( new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), distributionSetFilter); 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 new file mode 100644 index 000000000..516a5449b --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectTable.java @@ -0,0 +1,182 @@ +/** + * 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 javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.eclipse.hawkbit.repository.eventbus.event.DistributionCreatedEvent; +import org.eclipse.hawkbit.repository.eventbus.event.DistributionDeletedEvent; +import org.eclipse.hawkbit.ui.distributions.dstable.ManageDistBeanQuery; +import org.eclipse.hawkbit.ui.distributions.state.ManageDistUIState; +import org.eclipse.hawkbit.ui.utils.I18N; +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.springframework.beans.factory.annotation.Autowired; +import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory; +import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer; +import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition; +import org.vaadin.spring.events.EventBus; +import org.vaadin.spring.events.EventScope; +import org.vaadin.spring.events.annotation.EventBusListenerMethod; + +import com.vaadin.data.Container; +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.Table; +import com.vaadin.ui.themes.ValoTheme; + +/** + * Table for selecting a distribution set. + */ +@SpringComponent +@ViewScope +public class DistributionSetSelectTable extends Table { + + private static final long serialVersionUID = -4307487829435471759L; + + @Autowired + private I18N i18n; + + @Autowired + private transient EventBus.SessionEventBus eventBus; + + @Autowired + private ManageDistUIState manageDistUIState; + + private Container container; + + /** + * Initialize the component. + */ + @PostConstruct + protected void init() { + 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); + } + + @PreDestroy + void destroy() { + eventBus.unsubscribe(this); + } + + @EventBusListenerMethod(scope = EventScope.SESSION) + void onEvents(final List events) { + final Object firstEvent = events.get(0); + if (DistributionCreatedEvent.class.isInstance(firstEvent) + || DistributionDeletedEvent.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_DIST_ID_NAME), + 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<>(); + columnList.add(new TableColumn(SPUILabelDefinitions.NAME, i18n.get("header.name"), 0.6F)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_VERSION, i18n.get("header.version"), 0.4F)); + return columnList; + + } + + private void setColumnProperties() { + final List columnList = getVisbleColumns(); + final List swColumnIds = new ArrayList<>(); + for (final TableColumn column : columnList) { + setColumnHeader(column.getColumnPropertyId(), column.getColumnHeader()); + setColumnExpandRatio(column.getColumnPropertyId(), column.getExpandRatio()); + swColumnIds.add(column.getColumnPropertyId()); + } + setVisibleColumns(swColumnIds.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(SPUIDefinitions.DATA_AVAILABLE); + } + } + + private Object getItemIdToSelect() { + if (manageDistUIState.getSelectedDistributions().isPresent()) { + return manageDistUIState.getSelectedDistributions().get(); + } + return null; + } + + 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 new file mode 100644 index 000000000..d1e77e529 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectWindow.java @@ -0,0 +1,307 @@ +/** + * 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 com.vaadin.server.FontAwesome; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.Button; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.Window; +import org.eclipse.hawkbit.repository.DistributionSetManagement; +import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; +import org.eclipse.hawkbit.repository.TargetManagement; +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.DistributionSetIdName; +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.filtermanagement.event.CustomFilterUIEvent; +import org.eclipse.hawkbit.ui.utils.I18N; +import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.springframework.beans.factory.annotation.Autowired; +import org.vaadin.spring.events.EventBus; + +import com.vaadin.data.Property; +import com.vaadin.server.Sizeable; +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; + +import java.io.Serializable; + +/** + * Creates a dialog window to select the distribution set for a target filter query. + */ +@SpringComponent +@ViewScope +public class DistributionSetSelectWindow + implements CommonDialogWindow.SaveDialogCloseListener, Property.ValueChangeListener { + + private static final long serialVersionUID = 4752345414134989396L; + + @Autowired + private I18N i18n; + + @Autowired + private DistributionSetSelectTable dsTable; + + @Autowired + private transient EventBus.SessionEventBus eventBus; + + @Autowired + private transient DistributionSetManagement distributionSetManagement; + + @Autowired + private transient TargetManagement targetManagement; + + @Autowired + private transient TargetFilterQueryManagement targetFilterQueryManagement; + + private CommonDialogWindow window; + private CheckBox checkBox; + private VerticalLayout verticalLayout; + private Long tfqId; + + private void init() { + Label label = new Label(i18n.get("label.auto.assign.description")); + + checkBox = new CheckBox(i18n.get("label.auto.assign.enable")); + checkBox.setId(UIComponentIdProvider.DIST_SET_SELECT_ENABLE_ID); + checkBox.setImmediate(true); + checkBox.addValueChangeListener(this); + + verticalLayout = new VerticalLayout(); + verticalLayout.addComponent(label); + verticalLayout.addComponent(checkBox); + verticalLayout.addComponent(dsTable); + + window = new WindowBuilder(SPUIDefinitions.CREATE_UPDATE_WINDOW) + .caption(i18n.get("caption.select.auto.assign.dist")).content(verticalLayout).layout(verticalLayout) + .i18n(i18n).saveDialogCloseListener(this).buildCommonDialogWindow(); + window.setId(UIComponentIdProvider.DIST_SET_SELECT_WINDOW_ID); + } + + public void setValue(DistributionSetIdName distSet) { + dsTable.setVisible(distSet != null); + checkBox.setValue(distSet != null); + dsTable.setValue(distSet); + dsTable.setCurrentPageFirstItemId(distSet); + } + + public DistributionSetIdName getValue() { + if (checkBox.getValue()) { + return (DistributionSetIdName) dsTable.getValue(); + } + return null; + } + + /** + * Shows a distribution set select window for the given target filter query + * + * @param tfqId + * target filter query id + */ + public void showForTargetFilter(Long tfqId) { + this.tfqId = tfqId; + TargetFilterQuery tfq = targetFilterQueryManagement.findTargetFilterQueryById(tfqId); + if(tfq == null) { + throw new IllegalStateException("TargetFilterQuery does not exist for the given id"); + } + + init(); + + DistributionSet distributionSet = tfq.getAutoAssignDistributionSet(); + if(distributionSet != null) { + setValue(DistributionSetIdName.generate(distributionSet)); + } else { + setValue(null); + } + + window.setWidth(40.0F, Sizeable.Unit.PERCENTAGE); + UI.getCurrent().addWindow(window); + window.setVisible(true); + } + + /** + * Is triggered when the checkbox value changes + * + * @param event + * change event + */ + @Override + public void valueChange(Property.ValueChangeEvent event) { + dsTable.setVisible(checkBox.getValue()); + if (window != null) { + window.center(); + + } + } + + /** + * Is triggered when the save button is clicked + * + * @return whether the click should be allowed + */ + @Override + public boolean canWindowSaveOrUpdate() { + return !checkBox.getValue() || dsTable.getValue() != null; + } + + /** + * Is called when the new value should be saved after the save button has + * been clicked + */ + @Override + public void saveOrUpdate() { + if(checkBox.getValue() && dsTable.getValue() != null) { + DistributionSetIdName ds = (DistributionSetIdName) dsTable.getValue(); + updateTargetFilterQueryDS(tfqId, ds.getId()); + + } else if(!checkBox.getValue()) { + updateTargetFilterQueryDS(tfqId, null); + + } + + } + + private void updateTargetFilterQueryDS(final Long targetFilterQueryId, final Long dsId) { + TargetFilterQuery tfq = targetFilterQueryManagement.findTargetFilterQueryById(targetFilterQueryId); + + + + if(dsId != null) { + confirmWithConsequencesDialog(tfq, dsId); + } else { + tfq.setAutoAssignDistributionSet(null); + targetFilterQueryManagement.updateTargetFilterQuery(tfq); + eventBus.publish(this, CustomFilterUIEvent.UPDATED_TARGET_FILTER_QUERY); + } + + + } + + private void confirmWithConsequencesDialog(TargetFilterQuery tfq, final Long dsId) { + + ConfirmConsequencesDialog dialog = new ConfirmConsequencesDialog(tfq, dsId, new ConfirmCallback() { + @Override + public void onConfirmResult(boolean accepted) { + if(accepted) { + tfq.setAutoAssignDistributionSet(distributionSetManagement.findDistributionSetById(dsId)); + targetFilterQueryManagement.updateTargetFilterQuery(tfq); + eventBus.publish(this, CustomFilterUIEvent.UPDATED_TARGET_FILTER_QUERY); + } + } + }); + + dialog.setWidth(40.0F, Sizeable.Unit.PERCENTAGE); + + UI.getCurrent().addWindow(dialog); + dialog.setVisible(true); + + } + + /** + * A dialog that displays how many targets will be assigned immediately with the + */ + private class ConfirmConsequencesDialog extends Window implements Button.ClickListener { + + private static final long serialVersionUID = 7738545414137389326L; + + private TargetFilterQuery targetFilterQuery; + private Long distributionSetId; + + private Button okButton; + private Button cancelButton; + + private ConfirmCallback callback; + + public ConfirmConsequencesDialog(TargetFilterQuery targetFilterQuery, final Long dsId, ConfirmCallback callback) { + super(i18n.get("caption.confirm.assign.consequences")); + + this.callback = callback; + this.targetFilterQuery = targetFilterQuery; + this.distributionSetId = dsId; + + init(); + + } + + private void init() { + setId(UIComponentIdProvider.DIST_SET_SELECT_CONS_WINDOW_ID); + setModal(true); + setResizable(false); + + VerticalLayout layout = new VerticalLayout(); + layout.setSpacing(true); + layout.setMargin(true); + setContent(layout); + + Long targetsCount = targetManagement.countTargetByTargetFilterQueryAndNonDS(distributionSetId, targetFilterQuery); + Label mainTextLabel; + if(targetsCount == 0) { + mainTextLabel = new Label(i18n.get("message.confirm.assign.consequences.none")); + } else { + mainTextLabel = new Label(i18n.get("message.confirm.assign.consequences.text", new Object[]{targetsCount})); + } + + layout.addComponent(mainTextLabel); + + HorizontalLayout buttonsLayout = new HorizontalLayout(); + buttonsLayout.setSizeFull(); + buttonsLayout.setSpacing(true); + buttonsLayout.addStyleName("actionButtonsMargin"); + layout.addComponent(buttonsLayout); + + + okButton = SPUIComponentProvider.getButton(UIComponentIdProvider.SAVE_BUTTON, i18n.get("button.ok"), "", "", true, + FontAwesome.SAVE, SPUIButtonStyleNoBorderWithIcon.class); + okButton.setSizeUndefined(); + okButton.addStyleName("default-color"); + okButton.addClickListener(this); + buttonsLayout.addComponent(okButton); + buttonsLayout.setComponentAlignment(okButton, Alignment.MIDDLE_RIGHT); + buttonsLayout.setExpandRatio(okButton, 1.0F); + + + cancelButton = SPUIComponentProvider.getButton(UIComponentIdProvider.CANCEL_BUTTON, i18n.get("button.cancel"), "", "", true, + FontAwesome.TIMES, SPUIButtonStyleNoBorderWithIcon.class); + cancelButton.setSizeUndefined(); + cancelButton.addStyleName("default-color"); + cancelButton.addClickListener(this); + buttonsLayout.addComponent(cancelButton); + buttonsLayout.setComponentAlignment(cancelButton, Alignment.MIDDLE_LEFT); + buttonsLayout.setExpandRatio(cancelButton, 1.0F); + + + } + + @Override + public void buttonClick(Button.ClickEvent event) { + if(event.getButton().getId().equals(okButton.getId())) { + callback.onConfirmResult(true); + } else { + callback.onConfirmResult(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/TargetFilterBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterBeanQuery.java index 9231b1658..c38dd694b 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 @@ -13,8 +13,10 @@ import java.util.List; import java.util.Map; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; +import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.ui.common.UserDetailsFormatter; +import org.eclipse.hawkbit.ui.components.ProxyDistribution; import org.eclipse.hawkbit.ui.components.ProxyTargetFilter; import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; @@ -85,7 +87,7 @@ public class TargetFilterBeanQuery extends AbstractBeanQuery targetFilterQuery = getTargetFilterQueryManagement().findAllTargetFilterQuery( new PageRequest(startIndex / SPUIDefinitions.PAGE_SIZE, SPUIDefinitions.PAGE_SIZE, sort)); } else { - targetFilterQuery = getTargetFilterQueryManagement().findTargetFilterQueryByFilters( + targetFilterQuery = getTargetFilterQueryManagement().findTargetFilterQueryByName( new PageRequest(startIndex / SPUIDefinitions.PAGE_SIZE, SPUIDefinitions.PAGE_SIZE, sort), searchText); } @@ -98,6 +100,11 @@ public class TargetFilterBeanQuery extends AbstractBeanQuery proxyTarFilter.setModifiedDate(SPDateTimeUtil.getFormattedDate(tarFilterQuery.getLastModifiedAt())); proxyTarFilter.setLastModifiedBy(UserDetailsFormatter.loadAndFormatLastModifiedBy(tarFilterQuery)); proxyTarFilter.setQuery(tarFilterQuery.getQuery()); + + final DistributionSet distributionSet = tarFilterQuery.getAutoAssignDistributionSet(); + if (distributionSet != null) { + proxyTarFilter.setAutoAssignDistributionSet(new ProxyDistribution(distributionSet)); + } proxyTargetFilter.add(proxyTarFilter); } return proxyTargetFilter; @@ -117,7 +124,7 @@ public class TargetFilterBeanQuery extends AbstractBeanQuery .findAllTargetFilterQuery(new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort)); } else { firstPageTargetFilter = getTargetFilterQueryManagement() - .findTargetFilterQueryByFilters(new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), searchText); + .findTargetFilterQueryByName(new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), searchText); } final long size = firstPageTargetFilter.getTotalElements(); 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 40c28a40e..1e2d8a9e2 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 @@ -17,9 +17,15 @@ import java.util.Map; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; +import com.vaadin.ui.Button; +import com.vaadin.ui.Link; +import com.vaadin.ui.Table; +import com.vaadin.ui.UI; +import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; 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.SPUIButtonStyleSmallNoBorder; import org.eclipse.hawkbit.ui.filtermanagement.event.CustomFilterUIEvent; @@ -43,15 +49,11 @@ import com.vaadin.data.Item; import com.vaadin.server.FontAwesome; import com.vaadin.spring.annotation.SpringComponent; import com.vaadin.spring.annotation.ViewScope; -import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; -import com.vaadin.ui.Link; -import com.vaadin.ui.Table; -import com.vaadin.ui.UI; import com.vaadin.ui.themes.ValoTheme; /** - * + * Displays list of target filter queries * */ @SpringComponent @@ -75,6 +77,12 @@ public class TargetFilterTable extends Table { @Autowired private transient TargetFilterQueryManagement targetFilterQueryManagement; + @Autowired + private transient DistributionSetManagement distributionSetManagement; + + @Autowired + private DistributionSetSelectWindow dsSelectWindow; + private Container container; private static final int PROPERTY_DEPT = 3; @@ -141,15 +149,17 @@ public class TargetFilterTable extends Table { container.addContainerProperty(SPUILabelDefinitions.VAR_CREATED_DATE, Date.class, null); container.addContainerProperty(SPUILabelDefinitions.VAR_MODIFIED_DATE, Date.class, null); container.addContainerProperty(SPUILabelDefinitions.VAR_MODIFIED_BY, String.class, null); + container.addContainerProperty(SPUILabelDefinitions.AUTO_ASSIGN_DISTRIBUTION_SET, String.class, null); } private List getVisbleColumns() { final List columnList = new ArrayList<>(); columnList.add(new TableColumn(SPUILabelDefinitions.NAME, i18n.get("header.name"), 0.2F)); - columnList.add(new TableColumn(SPUILabelDefinitions.VAR_CREATED_USER, i18n.get("header.createdBy"), 0.15F)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_CREATED_USER, i18n.get("header.createdBy"), 0.1F)); columnList.add(new TableColumn(SPUILabelDefinitions.VAR_CREATED_DATE, i18n.get("header.createdDate"), 0.2F)); - columnList.add(new TableColumn(SPUILabelDefinitions.VAR_MODIFIED_BY, i18n.get("header.modifiedBy"), 0.15F)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_MODIFIED_BY, i18n.get("header.modifiedBy"), 0.1F)); columnList.add(new TableColumn(SPUILabelDefinitions.VAR_MODIFIED_DATE, i18n.get("header.modifiedDate"), 0.2F)); + columnList.add(new TableColumn(SPUILabelDefinitions.AUTO_ASSIGN_DISTRIBUTION_SET, i18n.get("header.auto.assignment.ds"), 0.1F)); columnList.add(new TableColumn(SPUIDefinitions.CUSTOM_FILTER_DELETE, i18n.get("header.delete"), 0.1F)); return columnList; @@ -171,7 +181,7 @@ public class TargetFilterTable extends Table { return deleteIcon; } - private String getDeleteIconId(final String targetFilterName) { + private static String getDeleteIconId(final String targetFilterName) { return new StringBuilder(UIComponentIdProvider.CUSTOM_FILTER_DELETE_ICON).append('.').append(targetFilterName) .toString(); } @@ -207,6 +217,9 @@ public class TargetFilterTable extends Table { addGeneratedColumn(SPUILabelDefinitions.NAME, (source, itemId, columnId) -> customFilterDetailButton((Long) itemId)); + addGeneratedColumn(SPUILabelDefinitions.AUTO_ASSIGN_DISTRIBUTION_SET, + (source, itemId, columnId) -> customFilterDistributionSetButton((Long) itemId)); + } private Button customFilterDetailButton(final Long itemId) { @@ -221,6 +234,34 @@ public class TargetFilterTable extends Table { return updateIcon; } + private Button customFilterDistributionSetButton(final Long itemId) { + final Item row1 = getItem(itemId); + final ProxyDistribution distSet = (ProxyDistribution) row1.getItemProperty(SPUILabelDefinitions.AUTO_ASSIGN_DISTRIBUTION_SET).getValue(); + final String buttonId = "distSetButton"; + Button updateIcon; + if(distSet == null) { + updateIcon = SPUIComponentProvider.getButton(buttonId, i18n.get("button.no.auto.assignment"), + i18n.get("button.auto.assignment.desc"), null, false, null, SPUIButtonStyleSmallNoBorder.class); + } else { + updateIcon = SPUIComponentProvider.getButton(buttonId, distSet.getNameVersion(), + i18n.get("button.auto.assignment.desc"), null, false, null, SPUIButtonStyleSmallNoBorder.class); + } + + updateIcon.addClickListener(this::onClickOfDistributionSetButton); + updateIcon.setData(row1); + updateIcon.addStyleName(ValoTheme.LINK_SMALL + " " + "on-focus-no-border link"); + + return updateIcon; + } + + private void onClickOfDistributionSetButton(final ClickEvent event) { + final Item item = (Item) ((Button) event.getComponent()).getData(); + final Long tfqId = (Long)item.getItemProperty(SPUILabelDefinitions.VAR_ID).getValue(); + + dsSelectWindow.showForTargetFilter(tfqId); + + } + private void onClickOfDetailButton(final ClickEvent event) { final String targetFilterName = (String) ((Button) event.getComponent()).getData(); final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java index aae1da513..601b111ac 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java @@ -481,6 +481,11 @@ public final class SPUIDefinitions { */ public static final String FILTER_BY_INVALID_QUERY = "FilterByInvalidFilterQueryText"; + /** + * Filter by distribution set complete. + */ + public static final String FILTER_BY_DS_COMPLETE = "FilterByDistributionSetComplete"; + /** * Sort order of column - created at in target table. */ 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 a7b8716cc..120c9fc58 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 @@ -203,6 +203,10 @@ public final class SPUILabelDefinitions { * ASSIGNED DISTRIBUTION ID. */ public static final String ASSIGNED_DISTRIBUTION_ID = "assignedDistributionSet.id"; + /** + * AUTO ASSIGN DISTRIBUTION SET ID + */ + public static final String AUTO_ASSIGN_DISTRIBUTION_SET = "autoAssignDistributionSet"; /** * ASSIGNED DISTRIBUTION Name & 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 e3475d20d..79ed125e1 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 @@ -877,6 +877,26 @@ public final class UIComponentIdProvider { */ public static final String DOWNLOAD_ANONYMOUS_CHECKBOX = "downloadanonymouscheckbox"; + /** + * Distribution set select table id + */ + public static final String DIST_SET_SELECT_TABLE_ID = "distribution.set.select.table"; + + /** + * Distribution set select window id + */ + public static final String DIST_SET_SELECT_WINDOW_ID = "distribution.set.select.window"; + + /** + * Distribution set select consequences window id + */ + public static final String DIST_SET_SELECT_CONS_WINDOW_ID = "distribution.set.select.consequences.window"; + + /** + * Distribution set select enable checkbox id + */ + public static final String DIST_SET_SELECT_ENABLE_ID = "distribution.set.select.enable"; + /** * /* Private Constructor. */ diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-common.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-common.scss index 2be45904a..ea5d5fbde 100644 --- a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-common.scss +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-common.scss @@ -168,6 +168,7 @@ .v-table-cell-content { height: 27px; + font-size: 12px; } } diff --git a/hawkbit-ui/src/main/resources/messages.properties b/hawkbit-ui/src/main/resources/messages.properties index 6a68eeff8..eef0c3140 100644 --- a/hawkbit-ui/src/main/resources/messages.properties +++ b/hawkbit-ui/src/main/resources/messages.properties @@ -22,6 +22,8 @@ button.no.actions = No actions button.ok = OK button.cancel = Cancel button.upload.file = Upload File +button.no.auto.assignment = none +button.auto.assignment.desc = Select auto assign distribution set bulk.targets.upload = Please upload csv file. bulkupload.ds.name = DS Name @@ -59,6 +61,7 @@ caption.filter.simple = Simple Filter caption.filter.custom = Custom Filter caption.metadata = Metadata +caption.select.auto.assign.dist = Select auto assignment distribution set caption.add.softwaremodule = Configure Software Module caption.add.new.dist = Configure New Distribution caption.update.dist = Configure Update Distribution @@ -92,26 +95,27 @@ caption.confirm.abort.action = Confirm Abort Action caption.filter.delete.confirmbox = Confirm Filter Delete Action caption.metadata.popup = Metadata of caption.metadata.delete.action.confirmbox = Confirm Metadata Delete Action - +caption.confirm.assign.consequences = Auto assign consequences +caption.auto.assignment.ds = Auto assignment # Labels prefix with - label -label.dist.details.type = Type : -label.dist.details.name = Name : -label.dist.details.version = Version : -label.dist.details.vendor = Vendor : -label.dist.details.jvm = Runtime : -label.dist.details.ah = Application : -label.dist.details.os = OS : -label.modified.date = Last modified at : -label.modified.by = Last modified by : -label.created.at = Created at : -label.created.by = Created by : -label.target.count = Targets : -label.description = Description : -label.ip = Address : -label.type = Type : -label.assigned.type = Assignment type : -label.assigned.count = {0} Assigned +label.dist.details.type = Type : +label.dist.details.name = Name : +label.dist.details.version = Version : +label.dist.details.vendor = Vendor : +label.dist.details.jvm = Runtime : +label.dist.details.ah = Application : +label.dist.details.os = OS : +label.modified.date = Last modified at : +label.modified.by = Last modified by : +label.created.at = Created at : +label.created.by = Created by : +label.target.count = Targets : +label.description = Description : +label.ip = Address : +label.type = Type : +label.assigned.type = Assignment type : +label.assigned.count = {0} Assigned label.installed.count = {0} Installed label.mandatory.field = * Mandatory Field label.components.drop.area = Drop here to delete @@ -133,10 +137,6 @@ label.target.filter.count = Total Targets: label.target.filtered.total = Total filtered targets : label.filter.selected = Selected: label.filter.shown = Shown: -label.filter = Filter : -label.target.filter.count = Total Targets : -label.filter.selected = Selected : -label.filter.shown = Shown : label.filter.targets = Filtered targets : label.filter.status = Status, label.filter.tags = Tags, @@ -155,9 +155,9 @@ label.cancelling = Canceling label.retrieved = Retrieved label.download = Downloading label.unknown = Unknown -label.target.id = Controller Id : -label.target.ip = Controller IP : -label.target.security.token = Security token : +label.target.id = Controller Id : +label.target.ip = Controller IP : +label.target.security.token = Security token : label.filter.by.status = Filter by Status label.target.controller.attrs = Controller attributes label.target.lastpolldate = Last poll : @@ -168,9 +168,11 @@ label.configuration.auth.gatewaytoken = Allow a gateway to authenticate and mana label.configuration.auth.targettoken = Allow targets to authenticate directly with their target security token label.configuration.anonymous.download = Allow targets to download artifacts without security credentials label.unsupported.browser.ie=Sorry! current browser is not supported. Please use Internet Explorer 11 and above +label.auto.assign.description=When an auto assign distribution set is selected, it will be automatically assigned to all targets that match the target filter. +label.auto.assign.enable=Enable auto assignment # Checkbox label prefix with - checkbox -checkbox.dist.migration.required = Required Migration Step : +checkbox.dist.migration.required = Required Migration Step : checkbox.dist.required.migration.step = Required Migration Step # TextFields prefix with - textfield @@ -186,7 +188,7 @@ prompt.target.id = Controller ID #Tooltips prefix with - tooltip -tooltip.add.module = Add Software Module +tooltip.add.module = Add Software Module tooltip.status.unknown = Unknown tooltip.status.registered = Registered tooltip.status.pending = Pending @@ -240,7 +242,7 @@ message.software.type.discard.success = All software moduleTypes selected for de message.assign.software.discard.success = All software moduleTypes selected for assign are discarded successfully ! message.software.delete.success = All software modules selected for delete are deleted successfully ! message.software.type.delete.success = All software modules types selected for delete are deleted successfully ! -message.dist.set.type.deleted.success = {0} DistributionSetType deleted successfully ! +message.dist.set.type.deleted.success = {0} DistributionSetType deleted successfully ! message.new.dist.save.success = {0} - {1} saved successfully message.dist.update.success = {0} - {1} updated successfully message.duplicate.dist = Distribution set [{0}] or version [{1}] must be unique, entered value already exists. @@ -248,7 +250,7 @@ message.error.view = No such view: {0} message.accessdenied.view = No access to view: {0} message.no.data = No data available message.target.assignment = {0} Assignment(s) done -message.target.deleted = {0} Target(s) deleted +message.target.deleted = {0} Target(s) deleted message.dist.deleted = {0} Distribution set(s) deleted message.tag.update.mandatory = Please select the Tag to update message.tag.duplicate.check = {0} already exists, please enter another value @@ -274,7 +276,7 @@ message.error.ah.softmodule = Please select the Application to delete message.error.softmodule.deleted = The selected Software Module is already deleted message.cancel.action = Cancel.. message.cancel.action.success = Action cancelled successfully ! -message.cancel.action.failed = Unable to cancel the action ! +message.cancel.action.failed = Unable to cancel the action ! message.cancel.action.confirm = Are you sure that you want to cancel this action? message.target.alreadyAssigned = {0} Target(s) were already assigned message.dist.alreadyAssigned = {0} Distribution Set(s) were already assigned @@ -283,8 +285,8 @@ message.force.action.confirm = Are you sure that you want to force this action? message.force.action.success = Action forced successfully ! message.forcequit.action = Force Quit.. message.forcequit.action.success = Action has been force quit successfully ! -message.forcequit.action.failed = Force Quitting the action is not possible ! -message.forcequit.action.confirm = Attention!\nForce quit should only be used when the assignment action is not working properly.\nForce quitting an action has no effect on the connected target. It is just resetting \nthe data stored on the SP update server. \nAre you absolutely sure that you want to force quit this action? +message.forcequit.action.failed = Force Quitting the action is not possible ! +message.forcequit.action.confirm = Attention!\nForce quit should only be used when the assignment action is not working properly.\nForce quitting an action has no effect on the connected target. It is just resetting \nthe data stored on the SP update server. \nAre you absolutely sure that you want to force quit this action? message.distribution.no.update = distribution {0} set is already assigned to targets and cannot be changed message.action.not.allowed = Action not allowed message.action.did.not.work = Action did not work. Please try again. @@ -306,12 +308,14 @@ message.update.filter.success = Custom filter updated Successfully! message.target.filter.validation = Please enter name and query message.target.filter.duplicate = {0} already exists, please enter another value message.tag.use.bulk.upload = {0} cannot be deleted .It is in use in targets bulk upload -message.bulk.upload.tag.assignment.failed = Tag {0} assignment failed as tag no longer exists -message.bulk.upload.tag.assignments.failed= Few tag assignments failed as tags no longer exists +message.bulk.upload.tag.assignment.failed = Tag {0} assignment failed as tag no longer exists +message.bulk.upload.tag.assignments.failed= Few tag assignments failed as tags no longer exists +message.confirm.assign.consequences.none = This auto assignment will not have any effect on the currently available targets. In future added targets might match the filter and will receive the selected distribution set automatically. +message.confirm.assign.consequences.text = When you confirm this auto assignment, {0} targets which match the filter will immediately get assigned with the selected distribution set. # action info action.target.table.selectall = Select all (Ctrl+A) -action.target.table.clear = Clear selections +action.target.table.clear = Clear selections #reused messages soft.module.jvm =Runtime @@ -322,8 +326,8 @@ soft.module.os =OS message.error.noFileSelected = No file selected for upload message.error.noProvidedName = Please provide custom file name message.error.noSwModuleSelected = Please select a Software Module -message.no.duplicateFiles = Duplicate files selected -message.no.duplicateFile = Duplicate file selected : +message.no.duplicateFiles = Duplicate files selected +message.no.duplicateFile = Duplicate file selected : message.delete.artifact = Are you sure that you want to delete artifact {0} ? message.duplicate.filename = Duplicate file name message.swModule.deleted = {0} Software Module(s) deleted @@ -337,7 +341,7 @@ message.abort.upload = Are you sure that you want to abort the upload? upload.swModuleTable.header = Software Module -upload.selectedfile.name = file selected for upload +upload.selectedfile.name = file selected for upload upload.file.name = File name upload.sha1 = SHA1 checksum upload.md5 = MD5 checksum @@ -366,7 +370,6 @@ custom.created.date = Created Date #Manage distributions view label.drop.dist.delete.area = Drop here
to delete -label.no.tag.assigned = NO TAG caption.assign.software.dist.accordion.tab = Assign Software Modules message.software.assignment = {0} Software Module(s) Assignment(s) done message.dist.inuse = {0} Distribution is already assigned to target @@ -376,9 +379,7 @@ message.target.assigned = {0} is assigned to {1} message.dist.type.delete = {0} DistributionType(s) Deleted successfully. message.sw.module.type.delete = {0} Software Module Type(s) deleted successfully. message.dist.type.discard.success = All Distribution Types are discarded successfully ! -message.dist.discard.success = All Distributions are discarded successfully ! message.assign.discard.success = All assignments are discarded successfully ! -message.target.ds.assign.success = Assignments saved successfully ! message.bulk.upload.assignment.failed = Distribution set assignment failed as distribution set no longer exists! message.key.missing = Key is missing ! message.value.missing = Value is missing ! @@ -449,12 +450,12 @@ header.assigned.ds = Assigned DS header.installed.ds = Installed DS header.target.status = Status header.target.tags = Tags -header.distributionset = Distribution set -header.numberofgroups = No. of groups -header.detail.status = Detail status -header.total.targets = Total targets +header.total.targets = Total targets header.key = Key header.value = Value +header.auto.assignment.ds = Auto assignment +header.target.filter.name = Target filter name +header.target.filter.query = Target filter query distribution.details.header = Distribution set target.details.header = Target diff --git a/hawkbit-ui/src/main/resources/messages_de.properties b/hawkbit-ui/src/main/resources/messages_de.properties deleted file mode 100644 index e46be6b6e..000000000 --- a/hawkbit-ui/src/main/resources/messages_de.properties +++ /dev/null @@ -1,505 +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 -# - -######################################################################################### -# This is the messages_en.properties file -######################################################################################### - -# Button names prefix with - button -button.save = Save -button.delete = Delete -button.discard = Discard -button.discard.all = Discard All -button.delete.all = Delete All -button.assign.all = Save Assign -button.actions = You have actions -button.no.actions = No actions -button.ok = OK -button.cancel = Cancel -button.upload.file = Upload File -bulk.targets.upload = Please upload csv file. -bulkupload.ds.name = DS Name - -# Headers prefix with - header -header.target.table=Targets -header.dist.table=Distributions -header.filter.tag=Filter by Tag -header.target.filter.tag=Filters -header.first.assignment.table = Targets -header.second.assignment.table = Distributions -header.dist.first.assignment.table = Distributions -header.dist.second.assignment.table = Software Modules -header.third.assignment.table = Discard -header.one.deletedist.table = Distribution Name -header.second.deletedist.table = Discard Changes -header.first.deletetarget.table = Target Name -header.second.deletetarget.table = Discard Changes -header.first.deleteswmodule.table = Delete software -header.first.delete.dist.type.table = DistributionSetType -header.second.delete.dist.type.table = Discard -header.first.delete.swmodule.type.table = Software Module Type -header.second.delete.swmodule.type.table = Discard -header.dist.twintable.selected=Selected -header.dist.twintable.available=Available -header.target.installed = Installed -header.target.assigned = Assigned - -# Captions prefix with - caption -caption.action.history = Action history for {0} -caption.error = Error -caption.new.softwaremodule.application = Configure New Application -caption.new.softwaremodule.jvm = Configure New Runtime -caption.new.softwaremodule.os = Configure New OS -caption.metadata = Metadata - - -caption.add.softwaremodule = Configure Software Module -caption.add.new.dist = Configure New Distribution -caption.update.dist = Configure Update Distribution -caption.add.tag = Configure Tag -caption.add.type = Configure Type -caption.add.new.target = Configure New Target -caption.update.target = Configure Update Target -caption.bulk.upload.targets = Bulk Upload -caption.softwares.distdetail.tab = Modules -caption.tags.tab = Tags -caption.logs.tab = Logs -caption.attributes.tab = Attributes -caption.types.tab = Types -caption.save.window = Action Details -caption.assign.dist.accordion.tab = Assign Distribution Set -caption.delete.dist.accordion.tab = Delete Distributions -caption.delete.target.accordion.tab = Delete Targets -caption.delete.swmodule.accordion.tab = Delete SW Modules -caption.delete.dist.set.type.accordion.tab = Delete Distribution Set Type -caption.delete.sw.module.type.accordion.tab = Delete Software Module Type -caption.attributes = Attributes -caption.panel.dist.installed = Installed Distribution Set -caption.panel.dist.assigned = Assigned Distribution Set -caption.soft.delete.confirmbox = Confirm Software Module Delete Action -caption.cancel.action.confirmbox = Confirm Action Cancellation -caption.forced.datefield = Force update at time -caption.force.action.confirmbox = Confirm Force Active Action -caption.filter.simple = Simple Filter -caption.filter.custom = Custom Filter -caption.filter.delete.confirmbox = Confirm Filter Delete Action -caption.confirm.abort.action = Confirm Abort Action - -caption.metadata.popup = Metadata of -caption.metadata.delete.action.confirmbox = Confirm Metadata Delete Action - -# Labels prefix with - label -label.dist.details.type = Type : -label.dist.details.name = Name : -label.dist.details.version = Version : -label.dist.details.vendor = Vendor : -label.dist.details.jvm = Runtime : -label.dist.details.ah = Application : -label.dist.details.os = OS : -label.modified.date = Last modified at : -label.modified.by = Last modified by : -label.created.at = Created at : -label.created.by = Created by : -label.target.count = Targets : -label.description = Description : -label.ip = Address : -label.type = Type : -label.assigned.type = Assignment type : -label.assigned.count = {0} Assigned -label.installed.count = {0} Installed -label.mandatory.field = * Mandatory Field -label.components.drop.area = Drop here to delete -label.software.module.drop.area = Delete Software -label.create.tag = Create Tag -label.update.tag = Update Tag -label.create.type = Create Type -label.update.type = Update Type -label.singleAssign.type = Firmware (FW) -label.multiAssign.type = Software (SW) -label.choose.type = Choose Type -label.choose.type.color = Choose Type Color -label.combobox.type = Select Type -label.combobox.tag = Select Tag -label.choose.tag = Choose Tag to update -label.choose.tag.color = Choose Tag Color -label.filter = Filter: -label.target.filter.count = Total Targets: -label.target.filtered.total = Total filtered targets : -label.filter.selected = Selected: -label.filter.shown = Shown: -label.filter = Filter : -label.target.filter.count = Total Targets : -label.filter.selected = Selected : -label.filter.shown = Shown : -label.filter.status = Status, -label.filter.tags = Tags, -label.filter.text = Search Text -label.filter.dist = Distribution, -label.filter.custom = Custom -label.target.filter.truncated={0} targets has been truncated in the list due the target size limit of {1}, use filters to reduce the targets to be shown -label.active =Active -label.inactive = In-active -label.finished = Finished -label.error = Error -label.warning = Warning -label.running = Running -label.cancelled = Cancelled -label.cancelling = Canceling -label.retrieved = Retrieved -label.download = Downloading -label.scheduled = Scheduled -label.target.id = Controller Id : -label.target.ip = Controller IP : -label.target.security.token = Security token : -label.filter.by.status = Filter by Status -label.target.controller.attrs = Controller attributes -label.target.lastpolldate = Last poll : -label.no.tag.assigned = NO TAG -label.tag.name = Tag name -label.configuration.auth.header = Allow targets to authenticate via a certificate authenticated by an 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.anonymous.download = Allow targets to download artifacts without security credentials -label.unsupported.browser.ie=Sorry! current browser is not supported. Please use Internet Explorer 11 and above - -# Checkbox label prefix with - checkbox -checkbox.dist.migration.required = Required Migration Step : -checkbox.dist.required.migration.step = Required Migration Step - -# TextFields prefix with - textfield -textfield.name = Name -textfield.key = Key -textfield.version = Version -textfield.vendor = Vendor -textfield.description = Description -textfield.customfiltername = Filter name -textfield.value = Value -ui.version = Powered by Bosch IoT Software Provisioning -prompt.target.id = Controller ID - - -#Tooltips prefix with - tooltip -tooltip.add.module = Add Software Module -tooltip.status.unknown = Unknown -tooltip.status.registered = Registered -tooltip.status.pending = Pending -tooltip.status.error = Error -tooltip.status.insync = In-sync -tooltip.delete.module = Select and delete Software Module -tooltip.forced.item=Forced update action -tooltip.soft.item=Soft update action which interacts with an user as a attempt update action for example -tooltip.timeforced.item=Soft update until a specific time and then the action will be forced -tooltip.check.for.mandatory=Check to make Mandatory -tooltip.artifact.icon=Show Artifact Details -tooltip.click.to.edit = Click to edit -tooltip.metadata.icon = Manage Metadata - - -# Notification messages prefix with - message -message.save.success = {0} saved successfully -message.update.success = {0} updated successfully -message.delete.success = {0} deleted successfully -message.dist.installedorassigned = Target {targId} is already assigned/installed with distribution -message.dist.pending.action = Target {0} is already assigned with distribution {1} . Pending for action -message.empty.target.tags= No Tags Created -message.empty.disttype.tags = No Distribution type tags created -message.select.row = Please select a row to drag -message.error = Unknown error occured during the operation. Please contact administrator -message.dist.assigned.one = {0} is assigned to {1} -message.dist.assigned.many = {0} DistributionSets are assigned to {1} -message.dist.unassigned.one = {0} is unassigned from {1} -message.dist.unassigned.many = {0} DistributionSets are unassigned from {1} -message.target.assigned.one = {0} is assigned to {1} -message.target.assigned.many = {0} Targets are assigned to {1} -message.target.unassigned.one = {0} is unassigned from {1} -message.target.unassigned.many = {0} Targets are unassigned from {1} -message.target.assigned.pending = Some target(s) are already assigned.Pending for action -message.cannot.delete = Cannot be deleted -message.check.softwaremodule = Please provide both name and version! -message.cannot.delete.default.dstype = Default distribution set type cannot be deleted -message.duplicate.softwaremodule = {0} : {1} already exists! -message.cannot.delete.default.dstype = Default distribution set type cannot be deleted -message.tag.delete = Please unclick the tag {0}, then try to delete -message.dist.type.check.delete = Please unclick the distribution type {0}, then try to delete -message.swmodule.type.check.delete = Please unclick the Software Module type {0}, then try to delete -message.targets.already.deleted = Few Target(s) are already deleted.Pending for action -message.dists.already.deleted = Few distribution(s) are already deleted.Pending for action -message.target.deleted.pending = Target(s) already deleted.Pending for action -message.dist.deleted.pending = Distribution(s) already deleted.Pending for action -message.dist.delete.success = All selected distribution sets are deleted successfully ! -message.dist.discard.success = All distributions selected for delete are discarded successfully ! -message.target.delete.success = All selected targets are deleted successfully ! -message.target.discard.success = All targets selected for delete are discarded successfully ! -message.software.discard.success = All software modules selected for delete are discarded successfully ! -message.software.type.discard.success = All software moduleTypes selected for delete are discarded successfully ! -message.assign.software.discard.success = All software moduleTypes selected for assign are discarded successfully ! -message.software.delete.success = All software modules selected for delete are deleted successfully ! -message.software.type.delete.success = All software modules types selected for delete are deleted successfully ! -message.dist.set.type.deleted.success = {0} DistributionSetType deleted successfully ! -message.new.dist.save.success = {0} - {1} saved successfully -message.dist.update.success = {0} - {1} updated successfully -message.duplicate.dist = Distribution set [{0}] or version [{1}] must be unique, entered value already exists. -message.error.view = No such view: {0} -message.accessdenied.view = No access to view: {0} -message.no.data = No data available -message.target.assignment = {0} Assignment(s) done -message.target.deleted = {0} Target(s) deleted -message.dist.deleted = {0} Distribution Set(s) deleted -message.tag.update.mandatory = Please select the Tag to update -message.tag.duplicate.check = {0} already exists, please enter another value -message.type.key.duplicate.check = Distribution type with key {0} already exists, please give another value -message.type.key.swmodule.duplicate.check = Software Module type with key {0} already exists, please give another value -message.no.action.history = No action history is available for the target : {0} -message.no.available = --No messages available-- -message.no.actionupdateds.available = No other updates available for this action -message.mandatory.check = Mandatory details are missing -message.target.duplicate.check = Target [ {0} ] must be unique, entered value already exists. -message.permission.insufficient = Insufficient permissions to perform this action. -message.error.temp = The operation cannot be fulfilled due to {0}. Please contact administrator -message.dist.alreadyassigned = {0} : {1} is already assigned/installed, cannot be updated -message.dist.tag.alreadyassigned = {0} : {1} is already assigned/installed, cannot assign/un-assign to tag -message.dists.unassign.tag.alreadyassigned = Few of the DistributionSet's are already assigned to Target, those cannot be unassigned from Tag" -message.dists.assign.tag.alreadyassigned = Few of the DistributionSet's are already assigned to Target, those cannot be assigned to Tag" -message.dists.tag.assigned = {0} DistributionSet's assigned to Tag {1} -message.dists.tag.unassigned = {0} DistributionSet's un-assigned from Tag {1} -message.dist.no.operation = {0} - already assigned/installed, No operation -message.sm.delete.confirm = Are you sure that you want to delete the selected {0} Software Module? -message.error.os.softmodule = Please select the OS to delete -message.error.ah.softmodule = Please select the Application to delete -message.error.softmodule.deleted = The selected Software Module is already deleted -message.cancel.action = Cancel -message.cancel.action.success = Action cancelled successfully ! -message.cancel.action.failed = Unable to cancel the action ! -message.cancel.action.confirm = Are you sure that you want to cancel this action? -message.target.alreadyAssigned = {0} Target(s) were already assigned -message.dist.alreadyAssigned = {0} Distribution Set(s) were already assigned -message.force.action = Force -message.force.action.confirm = Are you sure that you want to force this action? -message.force.action.success = Action forced successfully ! -message.distribution.no.update = distribution {0} set is already assigned to targets and cannot be changed -message.action.not.allowed = Action not allowed -message.action.did.not.work = Action did not work. Please try again. -message.onlyone.distribution.assigned = Only one distribution set can be assigned -message.onlyone.distribution.dropallowed = Only one distribution set can be dropped -message.error.missing.typename = Missing Type Name -message.error.missing.typenameorkey = Missing Type Name or Key -message.tag.cannot.be.assigned = Target/DS cannot be assigned to {0} -message.no.targets.assiged.fortag = No targets are assigned to tag {0} -message.error.missing.tagname = Please select tag name -message.type.delete = Please unclick the distribution type {0}, then try to delete -message.error.dist.set.type.update= Distribution Set Type is already assigned to targets and cannot be changed -message.target.ds.assign.success = Assignments saved successfully ! -message.no.directory.upload = Directory upload is not supported - -message.delete.filter.confirm = Are you sure that you want to delete custom filter? -message.delete.filter.success = Custom filter {0} deleted Successfully! -message.create.filter.success = Custom filter {0} created Successfully! -message.update.filter.success = Custom filter updated Successfully! -message.target.filter.validation = Please enter name and query -message.target.filter.duplicate = {0} already exists, please enter another value -message.tag.use.bulk.upload = {0} cannot be deleted .It is in use in targets bulk upload -message.bulk.upload.tag.assignment.failed = Tag {0} assignment failed as tag no longer exists -message.bulk.upload.tag.assignments.failed= Few tag assignments failed as tags no longer exists - -#reused messages -soft.module.jvm =Runtime -soft.module.application =Application -soft.module.os =OS - -#Artifact upload -message.error.noFileSelected = No file selected for upload -message.error.noProvidedName = Please provide custom file name -message.error.noSwModuleSelected = Please select a Software Module -message.no.duplicateFiles = Duplicate files selected -message.no.duplicateFile = Duplicate file selected : -message.delete.artifact = Are you sure that you want to delete artifact {0} ? -message.duplicate.filename = Duplicate file name -message.swModule.deleted = {0} Software Module(s) deleted -message.error.missing.tagname = Please select tag name -message.upload.failed = Streaming Failed -message.uploadedfile.size.exceeded = File size exceeded .Allowed size {0} bytes -message.uploadedfile.aborted = File upload aborted -message.file.not.found = File not found -message.artifact.deleted =Artifact with file {0} deleted successfully -message.abort.upload = Are you sure that you want to abort the upload? - - - -upload.swModuleTable.header = Software Module -upload.selectedfile.name = file selected for upload -upload.file.name = File name -upload.sha1 = SHA1 checksum -upload.md5 = MD5 checksum -upload.last.modified.date=Last modified date -upload.failed = Failed -upload.success = Success -upload.caption.add.new.swmodule = Configure New Software Module -upload.caption.delete.swmodule = Configure Delete Software Module -upload.swmodule.type = Type -upload.artifact.alreadyExists = Artifact will be overridden as the given name already exists -upload.size = Size(B) -upload.validation = Validation -upload.reason = Reason -upload.action = Action -upload.result.status = Upload status -upload.file = Upload File -upload.caption.update.swmodule = Update Software Module -caption.tab.details = Details -caption.tab.description = Description - -caption.delete.artifact.confirmbox = Confirm Artifact Delete Action - -#Manage distributions view -label.drop.dist.delete.area = Drop here
to delete -caption.assign.software.dist.accordion.tab = Assign Software Modules -message.software.assignment = {0} Assignment(s) done -message.dist.inuse = {0} Distribution is already assigned to target -message.software.dist.already.assigned = {0} Distribution already has Software Module {1} -message.software.dist.type.notallowed = {0} Software Module type can not assign to Distribution {1} -message.target.assigned = {0} is assigned to {1} -message.dist.type.delete = {0} DistributionType(s) Deleted successfully. -message.sw.module.type.delete = {0} Software Module Type(s) deleted successfully. -message.dist.type.discard.success = All Distribution Types are discarded successfully ! -message.dist.discard.success = All Distributions are discarded successfully ! -message.assign.discard.success = All assignments are discarded successfully ! -message.bulk.upload.assignment.failed = Distribution set assignment failed as distribution set no longer exists! -message.key.missing = Key is missing ! -message.value.missing = Value is missing ! -message.metadata.saved = Metadata with key {0} successfully saved ! -message.metadata.updated = Metadata with key {0} successfully updated ! -message.metadata.duplicate.check = Metadata with key {0} already exists, please enter another value -message.metadata.deleted.successfully = Metadata with key {0} successfully deleted ! -message.confirm.delete.metadata = Are you sure that you want to delete metadata with key {0} ? -message.error.notification.ds.target.assigned = Distribution set {0}:{1} is already assigned to targets and cannot be changed - - -# Login view -notification.login.title=Welcome to Bosch IoT Software Provisioning. -notification.login.description=Please login with your Bosch Identity Management credentials. -notification.login.failed.title=Login failed! -notification.login.failed.description=Login with the given credentials failed. -notification.login.failed.credentialsexpired.title=Passwort Abgelaufen! -notification.login.failed.credentialsexpired.description=Passwort ist entweder abgelaufen or muss initial gesetzt werden, bitte im Benutzer Verwaltungssystem �ndern. -label.login.tenant=Tenant -label.login.username=Username -label.login.password=Password -button.login.signin=Sign in -checkbox.login.rememberme=Remember me - -# Links -link.documentation.name=Dokumentation -link.demo.name=Demo -link.requestaccount.name=Beantragung eines Accounts -link.support.name=Support -link.usermanagement.name=Benutzerverwaltung - - -# System Configuration View -notification.configuration.save=Ver�nderungen gespeichert -configuration.defaultdistributionset.title=Distribution Set Typ -configuration.defaultdistributionset.select.label=Wahl des default Distribution Set typs: -configuration.savebutton.tooltip=Konfigurationen speichern -configuration.cancellbutton.tooltip=Konfigurationen zur�cksetzen -configuration.authentication.title=Authentifikationseinstellungen - -#Calendar -calendar.year=Jahr -calendar.years=Jahre -calendar.month=Monat -calendar.months=Monate -calendar.day=Tag -calendar.days=Tage -calendar.hour=Stunde -calendar.hours=Stunden -calendar.minute=Minute -calendar.minutes=Minuten -calendar.second=Sekunde -calendar.seconds=Sekunden - -header.name = Name -header.vendor = Vendor -header.version = Version -header.description = Description -header.createdBy = Created By -header.createdDate = Created Date -header.modifiedBy = Modified By -header.modifiedDate = Modified Date -header.delete = Delete -header.assigned.ds = Assigned DS -header.installed.ds = Installed DS -header.status = Status -header.target.tags = Tags -header.distributionset = Distribution set -header.numberofgroups = No. of groups -header.detail.status = Detail status -header.total.targets = Total targets -header.type = Type -header.swmodules = SwModules -header.migrations.step=IsRequiredMigrationStep - -header.action=Actions -header.action.run=Run -header.action.pause=Pause -header.action.update=Edit - -header.rolloutgroup.installed.percentage = % Finished -header.rolloutgroup.threshold.error = Error threshold -header.rolloutgroup.threshold = Trigger threshold - -header.rolloutgroup.target.date = Date and time -header.rolloutgroup.target.message = Messages - -rollout.group.label.target.truncated = {0} targets has been truncated in the list due the target size limit of {1} - -distribution.details.header = Distribution Set -target.details.header = Target -header.caption.mandatory = Mandatory -header.caption.typename = SoftwareModuleType -header.caption.softwaremodule = SoftwareModule -header.caption.unassign = Unassign -message.sw.unassigned = Software Module {0} successfully unassigned -header.caption.upload.details = Upload details -header.key = Key -header.value = Value -combo.type.tag.name = Type tag name - -label.yes = Yes -label.no =No - - -#Menu -menu.title = Software Provisioning - - -#Rollout management -prompt.number.of.groups = Number of groups -prompt.tigger.threshold = Trigger threshold -prompt.error.threshold = Error threshold -prompt.distribution.set = Distribution Set -caption.configure.rollout = Configure Rollout -caption.update.rollout = Update Rollout -prompt.target.filter = Custom Target Filter -message.rollout.nonzero.group.number = Number of groups must be greater than zero -message.rollout.max.group.number = Number of groups must not be greater than 500 -message.rollout.duplicate.check = Rollout [ {0} ] must be unique, entered value already exists. -message.rollout.field.value.range = Value should be in range {0} to {1} -message.correct.invalid.value = Please correct invalid values -message.enter.number = Please enter number -message.rollout.started = Rollout {0} started successfully -message.rollout.paused = Rollout {0} paused successfully -message.rollout.resumed = Rollout {0} resumed successfully -message.rollout.noofgroups.or.targetfilter.missing = Please enter number of groups and select target filter -message.rollouts = Rollouts -label.target.per.group = Targets per group : -message.dist.already.assigned = Distribution {0} is already assigned to target -message.error.creating.rollout = Server error. Error creating Rollout. Please contact the administrator -message.error.starting.rollout = Server error. Error starting Rollout. Please contact the administrator - -#Target Filter Management -breadcrumb.target.filter.custom.filters = Custom Filters diff --git a/hawkbit-ui/src/main/resources/messages_en.properties b/hawkbit-ui/src/main/resources/messages_en.properties deleted file mode 100644 index 4328b83f8..000000000 --- a/hawkbit-ui/src/main/resources/messages_en.properties +++ /dev/null @@ -1,501 +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 -# - -######################################################################################### -# This is the messages_en.properties file -######################################################################################### - -# Button names prefix with - button -button.save = Save -button.delete = Delete -button.discard = Discard -button.discard.all = Discard All -button.delete.all = Delete All -button.assign.all = Save Assign -button.actions = You have actions -button.no.actions = No actions -button.ok = OK -button.cancel = Cancel -button.upload.file = Upload File -bulk.targets.upload = Please upload csv file. -bulkupload.ds.name = DS Name - -# Headers prefix with - header -header.target.table=Targets -header.dist.table=Distributions -header.filter.tag=Filter by Tag -header.target.filter.tag=Filters -header.first.assignment.table = Targets -header.second.assignment.table = Distributions -header.dist.first.assignment.table = Distributions -header.dist.second.assignment.table = Software Modules -header.third.assignment.table = Discard -header.one.deletedist.table = Distribution Name -header.second.deletedist.table = Discard Changes -header.first.deletetarget.table = Target Name -header.second.deletetarget.table = Discard Changes -header.first.deleteswmodule.table = Delete software -header.first.delete.dist.type.table = DistributionSetType -header.second.delete.dist.type.table = Discard -header.first.delete.swmodule.type.table = Software Module Type -header.second.delete.swmodule.type.table = Discard -header.dist.twintable.selected=Selected -header.dist.twintable.available=Available -header.target.installed = Installed -header.target.assigned = Assigned - -# Captions prefix with - caption -caption.action.history = Action history for {0} -caption.error = Error -caption.new.softwaremodule.application = Configure New Application -caption.new.softwaremodule.jvm = Configure New Runtime -caption.new.softwaremodule.os = Configure New OS -caption.filter.simple = Simple Filter -caption.filter.custom = Custom Filter -caption.metadata = Metadata - -caption.add.softwaremodule = Configure Software Module -caption.add.new.dist = Configure New Distribution -caption.update.dist = Configure Update Distribution -caption.add.tag = Configure Tag -caption.add.type = Configure Type -caption.add.new.target = Configure New Target -caption.update.target = Configure Update Target -caption.bulk.upload.targets = Bulk Upload -caption.softwares.distdetail.tab = Modules -caption.tags.tab = Tags -caption.logs.tab = Logs -caption.attributes.tab = Attributes -caption.types.tab = Types -caption.save.window = Action Details -caption.assign.dist.accordion.tab = Assign Software Module -caption.delete.dist.accordion.tab = Delete Distributions -caption.delete.target.accordion.tab = Delete Targets -caption.delete.swmodule.accordion.tab = Delete SW Modules -caption.delete.dist.set.type.accordion.tab = Delete Distribution Set Type -caption.delete.sw.module.type.accordion.tab = Delete Software Module Type -caption.attributes = Attributes -caption.panel.dist.installed = Installed Distribution Set -caption.panel.dist.assigned = Assigned Distribution Set -caption.soft.delete.confirmbox = Confirm Software Module Delete Action -caption.cancel.action.confirmbox = Confirm Action Cancellation -caption.forced.datefield = Force update at time -caption.force.action.confirmbox = Confirm Force Active Action -caption.filter.delete.confirmbox = Confirm Filter Delete Action -caption.metadata.popup = Metadata of -caption.metadata.delete.action.confirmbox = Confirm Metadata Delete Action - -caption.confirm.abort.action = Confirm Abort Action - -# Labels prefix with - label -label.dist.details.type = Type : -label.dist.details.name = Name : -label.dist.details.version = Version : -label.dist.details.vendor = Vendor : -label.dist.details.jvm = Runtime : -label.dist.details.ah = Application : -label.dist.details.os = OS : -label.modified.date = Last modified at : -label.modified.by = Last modified by : -label.created.at = Created at : -label.created.by = Created by : -label.target.count = Targets : -label.description = Description : -label.ip = Address : -label.type = Type : -label.assigned.type = Assignment type : -label.assigned.count = {0} Assigned -label.installed.count = {0} Installed -label.mandatory.field = * Mandatory Field -label.components.drop.area = Drop here to delete -label.software.module.drop.area = Delete Software -label.create.tag = Create Tag -label.update.tag = Update Tag -label.create.type = Create Type -label.update.type = Update Type -label.singleAssign.type = Firmware (FW) -label.multiAssign.type = Software (SW) -label.choose.type = Choose Type to Update -label.choose.type.color = Choose Type Color -label.combobox.type = Select Type -label.combobox.tag = Select Tag -label.choose.tag = Choose Tag to update -label.choose.tag.color = Choose Tag Color -label.filter = Filter: -label.target.filter.count = Total Targets: -label.target.filtered.total = Total filtered targets : -label.filter.selected = Selected: -label.filter.shown = Shown: -label.filter.targets = Filtered targets : -label.filter = Filter : -label.target.filter.count = Total Targets : -label.filter.selected = Selected : -label.filter.shown = Shown : -label.filter.status = Status, -label.filter.tags = Tags, -label.filter.text = Search Text -label.filter.dist = Distribution, -label.filter.custom = Custom -label.target.filter.truncated={0} targets has been truncated in the list due the target size limit of {1}, use filters to reduce the targets to be shown -label.active =Active -label.inactive = In-active -label.finished = Finished -label.error = Error -label.warning = Warning -label.running = Running -label.cancelled = Cancelled -label.cancelling = Canceling -label.retrieved = Retrieved -label.download = Downloading -label.scheduled = Scheduled -label.target.id = Controller Id : -label.target.ip = Controller IP : -label.target.security.token = Security token : -label.filter.by.status = Filter by Status -label.target.controller.attrs = Controller attributes -label.target.lastpolldate = Last poll : -label.no.tag.assigned = NO TAG -label.tag.name = Tag name -label.configuration.auth.header = Allow targets to authenticate via a certificate authenticated by an 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.anonymous.download = Allow targets to download artifacts without security credentials -label.unsupported.browser.ie=Sorry! current browser is not supported. Please use Internet Explorer 11 and above - -# Checkbox label prefix with - checkbox -checkbox.dist.migration.required = Required Migration Step : -checkbox.dist.required.migration.step = Required Migration Step - -# TextFields prefix with - textfield -textfield.name = Name -textfield.key = Key -textfield.version = Version -textfield.vendor = Vendor -textfield.description = Description -textfield.customfiltername = Filter name -textfield.value = Value -ui.version = Powered by Bosch IoT Software Provisioning -prompt.target.id = Controller ID - - -#Tooltips prefix with - tooltip -tooltip.add.module = Add Software Module -tooltip.status.unknown = Unknown -tooltip.status.registered = Registered -tooltip.status.pending = Pending -tooltip.status.error = Error -tooltip.status.insync = In-sync -tooltip.delete.module = Select and delete Software Module -tooltip.forced.item=Forced update action -tooltip.soft.item=Soft update action which interacts with an user as a attempt update action for example -tooltip.timeforced.item=Soft update until a specific time and then the action will be forced -tooltip.check.for.mandatory=Check to make Mandatory -tooltip.artifact.icon=Show Artifact Details -tooltip.click.to.edit = Click to edit -tooltip.metadata.icon = Manage Metadata - - -# Notification messages prefix with - message -message.save.success = {0} saved successfully -message.update.success = {0} updated successfully -message.delete.success = {0} deleted successfully -message.dist.installedorassigned = Target {targId} is already assigned/installed with distribution -message.dist.pending.action = Target {0} is already assigned with distribution {1} . Pending for action -message.empty.target.tags= No Tags Created -message.empty.disttype.tags = No Distribution type tags created -message.select.row = Please select a row to drag -message.error = Unknown error occured during the operation. Please contact administrator -message.dist.assigned.one = {0} is assigned to {1} -message.dist.assigned.many = {0} DistributionSets are assigned to {1} -message.dist.unassigned.one = {0} is unassigned from {1} -message.dist.unassigned.many = {0} DistributionSets are unassigned from {1} -message.target.assigned.one = {0} is assigned to {1} -message.target.assigned.many = {0} Targets are assigned to {1} -message.target.unassigned.one = {0} is unassigned from {1} -message.target.unassigned.many = {0} Targets are unassigned from {1} -message.target.assigned.pending = Some target(s) are already assigned.Pending for action -message.cannot.delete = Cannot be deleted -message.check.softwaremodule = Please provide both name and version! -message.duplicate.softwaremodule = {0} : {1} already exists! -message.tag.delete = Please unclick the tag {0}, then try to delete -message.dist.type.check.delete = Please unclick the distribution type {0}, then try to delete -message.swmodule.type.check.delete = Please unclick the Software Module type {0}, then try to delete -message.targets.already.deleted = Few Target(s) are already deleted.Pending for action -message.dists.already.deleted = Few distribution(s) are already deleted.Pending for action -message.target.deleted.pending = Target(s) already deleted.Pending for action -message.dist.deleted.pending = Distribution(s) already deleted.Pending for action -message.dist.delete.success = All selected distribution sets are deleted successfully ! -message.dist.discard.success = All distributions selected for delete are discarded successfully ! -message.target.delete.success = All selected targets are deleted successfully ! -message.target.discard.success = All targets selected for delete are discarded successfully ! -message.software.discard.success = All software modules selected for delete are discarded successfully ! -message.software.type.discard.success = All software moduleTypes selected for delete are discarded successfully ! -message.assign.software.discard.success = All software moduleTypes selected for assign are discarded successfully ! -message.software.delete.success = All software modules selected for delete are deleted successfully ! -message.software.type.delete.success = All software modules types selected for delete are deleted successfully ! -message.dist.set.type.deleted.success = {0} DistributionSetType deleted successfully ! -message.new.dist.save.success = {0} - {1} saved successfully -message.dist.update.success = {0} - {1} updated successfully -message.duplicate.dist = Distribution set [{0}] or version [{1}] must be unique, entered value already exists. -message.error.view = No such view: {0} -message.accessdenied.view = No access to view: {0} -message.no.data = No data available -message.target.assignment = {0} Assignment(s) done -message.target.deleted = {0} Target(s) deleted -message.dist.deleted = {0} Distribution set(s) deleted -message.tag.update.mandatory = Please select the Tag to update -message.tag.duplicate.check = {0} already exists, please enter another value -message.type.key.duplicate.check = Distribution type with key {0} already exists, please give another value -message.type.key.swmodule.duplicate.check = Software Module type with key {0} already exists, please give another value -message.no.action.history = No action history is available for the target : {0} -message.no.available = --No messages available-- -message.no.actionupdateds.available = No other updates available for this action -message.mandatory.check = Mandatory details are missing -message.target.duplicate.check = Target [ {0} ] must be unique, entered value already exists. -message.permission.insufficient = Insufficient permissions to perform this action. -message.error.temp = The operation cannot be fulfilled due to {0}. Please contact administrator -message.dist.alreadyassigned = {0} : {1} is already assigned/installed, cannot be updated -message.dist.tag.alreadyassigned = {0} : {1} is already assigned/installed, cannot assign/un-assign to tag -message.dists.unassign.tag.alreadyassigned = Few of the DistributionSet's are already assigned to Target, those cannot be unassigned from Tag" -message.dists.assign.tag.alreadyassigned = Few of the DistributionSet's are already assigned to Target, those cannot be assigned to Tag" -message.dists.tag.assigned = {0} DistributionSet's assigned to Tag {1} -message.dists.tag.unassigned = {0} DistributionSet's un-assigned from Tag {1} -message.dist.no.operation = {0} - already assigned/installed, No operation -message.sm.delete.confirm = Are you sure that you want to delete the selected {0} Software Module? -message.error.os.softmodule = Please select the OS to delete -message.error.ah.softmodule = Please select the Application to delete -message.error.softmodule.deleted = The selected Software Module is already deleted -message.cancel.action = Cancel -message.cancel.action.success = Action cancelled successfully ! -message.cancel.action.failed = Unable to cancel the action ! -message.cancel.action.confirm = Are you sure that you want to cancel this action? -message.target.alreadyAssigned = {0} Target(s) were already assigned -message.dist.alreadyAssigned = {0} Distribution Set(s) were already assigned -message.force.action = Force -message.force.action.confirm = Are you sure that you want to force this action? -message.force.action.success = Action forced successfully ! -message.distribution.no.update = distribution {0} set is already assigned to targets and cannot be changed -message.action.not.allowed = Action not allowed -message.action.did.not.work = Action did not work. Please try again. -message.onlyone.distribution.assigned = Only one distribution set can be assigned -message.onlyone.distribution.dropallowed = Only one distribution set can be dropped -message.error.missing.typename = Missing Type Name -message.error.missing.typenameorkey = Missing Type Name or Key or Software Module type -message.tag.cannot.be.assigned = Target/DS cannot be assigned to {0} -message.no.targets.assiged.fortag = No targets are assigned to tag {0} -message.error.missing.tagname = Please select tag name -message.type.delete = Please unclick the distribution type {0}, then try to delete -message.error.dist.set.type.update= Distribution Set Type is already assigned to targets and cannot be changed -message.target.ds.assign.success = Assignments saved successfully ! -message.no.directory.upload = Directory upload is not supported - -message.delete.filter.confirm = Are you sure that you want to delete custom filter? -message.delete.filter.success = Custom filter {0} deleted Successfully! -message.create.filter.success = Custom filter {0} created Successfully! -message.update.filter.success = Custom filter updated Successfully! -message.target.filter.validation = Please enter name and query -message.target.filter.duplicate = {0} already exists, please enter another value - -# action info -action.target.table.selectall = Select all (Ctrl+A) -action.target.table.clear = Clear selections - -#reused messages -soft.module.jvm =Runtime -soft.module.application =Application -soft.module.os =OS - -#Artifact upload -message.error.noFileSelected = No file selected for upload -message.error.noProvidedName = Please provide custom file name -message.error.noSwModuleSelected = Please select a Software Module -message.no.duplicateFiles = Duplicate files selected -message.no.duplicateFile = Duplicate file selected : -message.delete.artifact = Are you sure that you want to delete artifact {0} ? -message.duplicate.filename = Duplicate file name -message.swModule.deleted = {0} Software Module(s) deleted -message.upload.failed = Streaming Failed -message.uploadedfile.size.exceeded = File size exceeded .Allowed size {0} bytes -message.uploadedfile.aborted = File upload aborted -message.file.not.found = File not found -message.abort.upload = Are you sure that you want to abort the upload? - - -upload.swModuleTable.header = Software Module -upload.selectedfile.name = file selected for upload -upload.file.name = File name -upload.sha1 = SHA1 checksum -upload.md5 = MD5 checksum -upload.last.modified.date=Last modified date -upload.failed = Failed -upload.success = Success -upload.caption.add.new.swmodule = Configure New Software Module -upload.caption.delete.swmodule = Configure Delete Software Module -upload.swmodule.type = Type -upload.artifact.alreadyExists = Artifact will be overridden as the given name already exists -upload.size = Size(B) -upload.validation = Validation -upload.reason = Reason -upload.action = Action -upload.result.status = Upload status -upload.file = Upload File -upload.caption.update.swmodule = Update Software Module -caption.tab.details = Details -caption.tab.description = Description - -caption.delete.artifact.confirmbox = Confirm Artifact Delete Action - - -#Manage distributions view -label.drop.dist.delete.area = Drop here
to delete -caption.assign.software.dist.accordion.tab = Assign Software Modules -message.software.assignment = {0} Software Module(s) Assignment(s) done -message.dist.inuse = {0} Distribution is already assigned to target -message.software.dist.already.assigned = {0} Distribution already has Software Module {1} -message.software.dist.type.notallowed = {0} Software Module type can not assign to Distribution {1} -message.target.assigned = {0} is assigned to {1} -message.dist.type.delete = {0} DistributionType(s) Deleted successfully. -message.sw.module.type.delete = {0} Software Module Type(s) deleted successfully. -message.dist.type.discard.success = All Distribution Types are discarded successfully ! -message.dist.discard.success = All Distributions are discarded successfully ! -message.assign.discard.success = All assignments are discarded successfully ! -message.key.missing = Key is missing ! -message.value.missing = Value is missing ! -message.metadata.saved = Metadata with key {0} successfully saved ! -message.metadata.updated = Metadata with key {0} successfully updated ! -message.metadata.duplicate.check = Metadata with key {0} already exists, please enter another value -message.metadata.deleted.successfully = Metadata with key {0} successfully deleted ! -message.confirm.delete.metadata = Are you sure that you want to delete metadata with key {0} ? -message.error.notification.ds.target.assigned = Distribution set {0}:{1} is already assigned to targets and cannot be changed - -# Login view -notification.login.title=Welcome to Bosch IoT Software Provisioning. -notification.login.description=Please login with your Bosch Identity Management credentials. -notification.login.failed.title=Login failed! -notification.login.failed.description=Login with the given credentials failed. -notification.login.failed.credentialsexpired.title=Password Expired! -notification.login.failed.credentialsexpired.description=Password has been expired or needs to be set initially, please visit the User Management to change or set your password. -label.login.tenant=Tenant -label.login.username=Username -label.login.password=Password -button.login.signin=Sign in -checkbox.login.rememberme=Remember me - -# Links -link.documentation.name=Documentation -link.demo.name=Demo -link.requestaccount.name=Request Account -link.support.name=Support -link.usermanagement.name=User Management - -# System Configuration View -notification.configuration.save=Saved changes -configuration.defaultdistributionset.title=Distribution Set Type -configuration.defaultdistributionset.select.label=Select the default Distribution Set type: -configuration.savebutton.tooltip=Save Configurations -configuration.cancellbutton.tooltip=Cancel Configurations -configuration.authentication.title=Authentication Configuration -controller.polling.title=Polling Configuration -controller.polling.time=Polling Time -controller.polling.overduetime=Polling Overdue Time - -#Calendar -calendar.year=year -calendar.years=years -calendar.month=month -calendar.months=months -calendar.day=day -calendar.days=days -calendar.hour=hour -calendar.hours=hours -calendar.minute=minute -calendar.minutes=minutes -calendar.second=second -calendar.seconds=seconds - -header.name = Name -header.vendor = Vendor -header.version = Version -header.description = Description -header.createdBy = Created By -header.createdDate = Created Date -header.modifiedBy = Modified By -header.modifiedDate = Modified Date -header.delete = Delete -header.assigned.ds = Assigned DS -header.installed.ds = Installed DS -header.status = Status -header.target.tags = Tags -header.distributionset = Distribution Set -header.numberofgroups = No. of groups -header.detail.status = Detail status -header.total.targets = Total targets -header.type = Type -header.swmodules = SwModules -header.migrations.step=IsRequiredMigrationStep - -header.action=Actions -header.action.run=Run -header.action.pause=Pause -header.action.update=Edit - -header.rolloutgroup.installed.percentage = % Finished -header.rolloutgroup.threshold.error = Error threshold -header.rolloutgroup.threshold = Trigger threshold - - - -header.rolloutgroup.target.date = Date and time -header.rolloutgroup.target.message = Messages - -rollout.group.label.target.truncated = {0} targets has been truncated in the list due the target size limit of {1} - -distribution.details.header = Distribution Set -target.details.header = Target -header.caption.mandatory = Mandatory -header.caption.typename = SoftwareModuleType -header.caption.softwaremodule = SoftwareModule -header.caption.upload.details = Upload details -header.key = Key -header.value = Value -combo.type.tag.name = Type tag name - -label.yes = Yes -label.no =No - -#Menu -menu.title = Software Provisioning - - -#Rollout management -prompt.number.of.groups = Number of groups -prompt.tigger.threshold = Trigger threshold -prompt.error.threshold = Error threshold -prompt.distribution.set = Distribution Set -caption.configure.rollout = Configure Rollout -caption.update.rollout = Update Rollout -prompt.target.filter = Custom Target Filter -message.rollout.nonzero.group.number = Number of groups must be greater than zero -message.rollout.max.group.number = Number of groups must not be greater than 500 -message.rollout.duplicate.check = Rollout [ {0} ] must be unique, entered value already exists. -message.correct.invalid.value = Please correct invalid values -message.enter.number = Please enter number -message.rollout.field.value.range = Value should be in range {0} to {1} -message.rollout.started = Rollout {0} started successfully -message.rollout.paused = Rollout {0} paused successfully -message.rollout.resumed = Rollout {0} resumed successfully -message.rollout.noofgroups.or.targetfilter.missing = Please enter number of groups and select target filter -message.rollouts = Rollouts -label.target.per.group = Targets per group : -message.dist.already.assigned = Distribution {0} is already assigned to target -message.error.creating.rollout = Server error. Error creating Rollout. Please contact the administrator -message.error.starting.rollout = Server error. Error starting Rollout. Please contact the administrator - -#Target Filter Management -breadcrumb.target.filter.custom.filters = Custom Filters