Introduce target grouping (#2538)
* Introduce target grouping Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * minor refactor Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * throw validation exception instead direct returning bad request response Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * fix group query parameter Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * remove wrongly added import Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * add review fixes Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * apply latest review changes Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * apply latest changes after sybnc/review Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * fix after review Signed-off-by: strailov <Stanislav.Trailov@bosch.io> --------- Signed-off-by: strailov <Stanislav.Trailov@bosch.io>
This commit is contained in:
committed by
GitHub
parent
e7373275bf
commit
b4793fcce1
@@ -29,6 +29,7 @@ public enum TargetFields implements RsqlQueryField {
|
|||||||
UPDATESTATUS("updateStatus"),
|
UPDATESTATUS("updateStatus"),
|
||||||
IPADDRESS("address"),
|
IPADDRESS("address"),
|
||||||
ATTRIBUTE("controllerAttributes"),
|
ATTRIBUTE("controllerAttributes"),
|
||||||
|
GROUP("group"),
|
||||||
ASSIGNEDDS(
|
ASSIGNEDDS(
|
||||||
"assignedDistributionSet",
|
"assignedDistributionSet",
|
||||||
DistributionSetFields.NAME.getJpaEntityFieldName(), DistributionSetFields.VERSION.getJpaEntityFieldName()),
|
DistributionSetFields.NAME.getJpaEntityFieldName(), DistributionSetFields.VERSION.getJpaEntityFieldName()),
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ public class MgmtTarget extends MgmtNamedEntity {
|
|||||||
@Schema(description = "Controller ID", example = "123")
|
@Schema(description = "Controller ID", example = "123")
|
||||||
private String controllerId;
|
private String controllerId;
|
||||||
|
|
||||||
|
@Schema(description = "Target group", example = "Europe/East")
|
||||||
|
private String group;
|
||||||
|
|
||||||
@Schema(description = "If the target is in sync", example = "in_sync")
|
@Schema(description = "If the target is in sync", example = "in_sync")
|
||||||
private String updateStatus;
|
private String updateStatus;
|
||||||
|
|
||||||
|
|||||||
@@ -42,4 +42,7 @@ public class MgmtTargetRequestBody {
|
|||||||
|
|
||||||
@Schema(description = "ID of the target type", example = "10")
|
@Schema(description = "ID of the target type", example = "10")
|
||||||
private Long targetType;
|
private Long targetType;
|
||||||
|
|
||||||
|
@Schema(description = "Target group", example = "Asia")
|
||||||
|
private String group;
|
||||||
}
|
}
|
||||||
@@ -46,6 +46,10 @@ public final class MgmtRestConstants {
|
|||||||
* The target type URL mapping rest resource.
|
* The target type URL mapping rest resource.
|
||||||
*/
|
*/
|
||||||
public static final String TARGETTYPE_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/targettypes";
|
public static final String TARGETTYPE_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/targettypes";
|
||||||
|
/**
|
||||||
|
* The target group URL mapping rest resource.
|
||||||
|
*/
|
||||||
|
public static final String TARGET_GROUP_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/targetgroups";
|
||||||
/**
|
/**
|
||||||
* The tag URL mapping rest resource.
|
* The tag URL mapping rest resource.
|
||||||
*/
|
*/
|
||||||
@@ -162,6 +166,10 @@ public final class MgmtRestConstants {
|
|||||||
* The tag URL mapping rest resource.
|
* The tag URL mapping rest resource.
|
||||||
*/
|
*/
|
||||||
public static final String DISTRIBUTIONSET_TAG_DISTRIBUTIONSETS_REQUEST_MAPPING = "/{distributionsetTagId}/assigned";
|
public static final String DISTRIBUTIONSET_TAG_DISTRIBUTIONSETS_REQUEST_MAPPING = "/{distributionsetTagId}/assigned";
|
||||||
|
/**
|
||||||
|
* Target group URL mapping rest resource
|
||||||
|
*/
|
||||||
|
public static final String TARGET_GROUP_TARGETS_REQUEST_MAPPING = "/{group}/assigned";
|
||||||
/**
|
/**
|
||||||
* The default offset parameter in case the offset parameter is not present in the request.
|
* The default offset parameter in case the offset parameter is not present in the request.
|
||||||
*
|
*
|
||||||
@@ -233,6 +241,7 @@ public final class MgmtRestConstants {
|
|||||||
public static final String TARGET_TAG_ORDER = "2000";
|
public static final String TARGET_TAG_ORDER = "2000";
|
||||||
public static final String TARGET_TYPE_ORDER = "3000";
|
public static final String TARGET_TYPE_ORDER = "3000";
|
||||||
public static final String TARGET_FILTER_ORDER = "4000";
|
public static final String TARGET_FILTER_ORDER = "4000";
|
||||||
|
public static final String TARGET_GROUP_ORDER = "4500";
|
||||||
public static final String ACTION_ORDER = "5000";
|
public static final String ACTION_ORDER = "5000";
|
||||||
public static final String ROLLOUT_ORDER = "6000";
|
public static final String ROLLOUT_ORDER = "6000";
|
||||||
public static final String DISTRIBUTION_SET_ORDER = "7000";
|
public static final String DISTRIBUTION_SET_ORDER = "7000";
|
||||||
|
|||||||
@@ -0,0 +1,370 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.mgmt.rest.api;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.extensions.Extension;
|
||||||
|
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
|
||||||
|
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
|
||||||
|
import org.eclipse.hawkbit.rest.OpenApi;
|
||||||
|
import org.eclipse.hawkbit.rest.json.model.ExceptionInfo;
|
||||||
|
import org.springframework.hateoas.MediaTypes;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.TARGET_GROUP_ORDER;
|
||||||
|
|
||||||
|
@Tag(
|
||||||
|
name = "Target Groups", description = "REST API for Target Groups operations.",
|
||||||
|
extensions = @Extension(name = OpenApi.X_HAWKBIT, properties = @ExtensionProperty(name = "order", value = TARGET_GROUP_ORDER)))
|
||||||
|
public interface MgmtTargetGroupRestApi {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the GET request of retrieving a list of assigned targets for a specific group. Complex grouping (subgroups) not supported here.
|
||||||
|
* For complex grouping use the analogical resource with query parameter for target group.
|
||||||
|
*
|
||||||
|
* @param group - target group
|
||||||
|
* @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}
|
||||||
|
* @return a list of targets matching the provided group for a defined or default page request with status OK. The response is always paged. In any failure the
|
||||||
|
* JsonResponseExceptionHandler is handling the response.
|
||||||
|
*/
|
||||||
|
@Operation(summary = "Return assigned targets for group",
|
||||||
|
description = "Handles the GET request of retrieving a list of assigned targets for a specific group. Complex grouping (subgroups) not supported here." +
|
||||||
|
"For complex grouping use the analogical resource with query parameter for target group.")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
|
||||||
|
@ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionInfo.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "The request requires user authentication."),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Insufficient permissions, entity is not allowed to be " +
|
||||||
|
"changed (i.e. read-only) or data volume restriction applies."),
|
||||||
|
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource."),
|
||||||
|
@ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json."),
|
||||||
|
@ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " +
|
||||||
|
"and the client has to wait another second.")
|
||||||
|
})
|
||||||
|
@GetMapping(value = MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + MgmtRestConstants.TARGET_GROUP_TARGETS_REQUEST_MAPPING,
|
||||||
|
produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
|
||||||
|
ResponseEntity<PagedList<MgmtTarget>> getAssignedTargets(
|
||||||
|
@PathVariable
|
||||||
|
@Schema(description = "The target group of the targets")
|
||||||
|
String group,
|
||||||
|
@RequestParam(
|
||||||
|
value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET,
|
||||||
|
defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET)
|
||||||
|
@Schema(description = "The paging offset (default is 0)")
|
||||||
|
int pagingOffsetParam,
|
||||||
|
@RequestParam(
|
||||||
|
value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT,
|
||||||
|
defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT)
|
||||||
|
@Schema(description = "The maximum number of entries in a page (default is 50)")
|
||||||
|
int pagingLimitParam,
|
||||||
|
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false)
|
||||||
|
@Schema(description = """
|
||||||
|
The query parameter sort allows to define the sort order for the result of a query. A sort criteria
|
||||||
|
consists of the name of a field and the sort direction (ASC for ascending and DESC descending).
|
||||||
|
The sequence of the sort criteria (multiple can be used) defines the sort order of the entities
|
||||||
|
in the result.""")
|
||||||
|
String sortParam
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the GET request of retrieving a list of assigned targets for a specific group. Complex grouping (subgroups) is supported here.
|
||||||
|
* Search could be for specific group, complex group e.g Parent/Child or also for groups including its subgroups
|
||||||
|
*
|
||||||
|
* @param groupFilter - An Actual group - Parent/Child or Parent
|
||||||
|
* @param subgroups - If set to {@code true} enables the search in subgroups
|
||||||
|
* @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}
|
||||||
|
* @return a list of targets matching the provided group for a defined or default page request with status OK. The response is always paged. In any failure the
|
||||||
|
* JsonResponseExceptionHandler is handling the response.
|
||||||
|
*/
|
||||||
|
@Operation(summary = "Return assigned targets for group",
|
||||||
|
description = "Handles the GET request of retrieving a list of assigned targets for a specific group. Complex grouping (subgroups) is supported here." +
|
||||||
|
"Search could be for specific group, complex group e.g Parent/Child or also for groups including its subgroups")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
|
||||||
|
@ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionInfo.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "The request requires user authentication."),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Insufficient permissions, entity is not allowed to be " +
|
||||||
|
"changed (i.e. read-only) or data volume restriction applies."),
|
||||||
|
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource."),
|
||||||
|
@ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json."),
|
||||||
|
@ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " +
|
||||||
|
"and the client has to wait another second.")
|
||||||
|
})
|
||||||
|
@GetMapping(value = MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/assigned")
|
||||||
|
ResponseEntity<PagedList<MgmtTarget>> getAssignedTargetsWithSubgroups(
|
||||||
|
@RequestParam(value = "group")
|
||||||
|
@Schema(description = "Target Group or Filter based on target groups. ")
|
||||||
|
String groupFilter,
|
||||||
|
@RequestParam(value = "subgroups", defaultValue = "false")
|
||||||
|
@Schema(description = " Possibility to search for subgroups with wildcard or not - e.g. ParentGroup/*")
|
||||||
|
boolean subgroups,
|
||||||
|
@RequestParam(
|
||||||
|
value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET,
|
||||||
|
defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET)
|
||||||
|
@Schema(description = "The paging offset (default is 0)")
|
||||||
|
int pagingOffsetParam,
|
||||||
|
@RequestParam(
|
||||||
|
value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT,
|
||||||
|
defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT)
|
||||||
|
@Schema(description = "The maximum number of entries in a page (default is 50)")
|
||||||
|
int pagingLimitParam,
|
||||||
|
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false)
|
||||||
|
@Schema(description = """
|
||||||
|
The query parameter sort allows to define the sort order for the result of a query. A sort criteria
|
||||||
|
consists of the name of a field and the sort direction (ASC for ascending and DESC descending).
|
||||||
|
The sequence of the sort criteria (multiple can be used) defines the sort order of the entities
|
||||||
|
in the result.""")
|
||||||
|
String sortParam
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns targets to a given group.
|
||||||
|
* For complex groups use analogical method with query parameters.
|
||||||
|
*
|
||||||
|
* @param group - target group to be assigned
|
||||||
|
* @param controllerIds - list of controllerIds for targets to be assigned
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Operation(summary = "Assign target(s) to given group",
|
||||||
|
description = "Handles the POST request of target assignment. Already assigned target will be ignored. " +
|
||||||
|
"For complex groups use analogical method with query parameters.")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Successfully assigned"),
|
||||||
|
@ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionInfo.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "The request requires user authentication."),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Insufficient permissions, entity is not allowed to be " +
|
||||||
|
"changed (i.e. read-only) or data volume restriction applies."),
|
||||||
|
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource."),
|
||||||
|
@ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json."),
|
||||||
|
@ApiResponse(responseCode = "409", description = "E.g. in case an entity is created or modified by another " +
|
||||||
|
"user in another request at the same time. You may retry your modification request."),
|
||||||
|
@ApiResponse(responseCode = "415", description = "The request was attempt with a media-type which is not " +
|
||||||
|
"supported by the server for this resource."),
|
||||||
|
@ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " +
|
||||||
|
"and the client has to wait another second.")
|
||||||
|
})
|
||||||
|
@PutMapping(value = MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + MgmtRestConstants.TARGET_GROUP_TARGETS_REQUEST_MAPPING)
|
||||||
|
ResponseEntity<Void> assignTargetsToGroup(
|
||||||
|
@PathVariable(value = "group")
|
||||||
|
@Schema(description = "The target group to be set. Sub-grouping not allowed here, for sub-grouping use the analogical method with query parameter.")
|
||||||
|
String group,
|
||||||
|
@Schema(description = "List of controller ids to be assigned", example = "[\"controllerId1\", \"controllerId2\"]")
|
||||||
|
@RequestBody List<String> controllerIds
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns targets to a given group.
|
||||||
|
* Complex (subgroups) are allowed - e.g. Parent/Child
|
||||||
|
*
|
||||||
|
* @param group - target group to be assigned
|
||||||
|
* @param controllerIds - list of controllerIds for targets to be assigned
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Operation(summary = "Assign target(s) to given group",
|
||||||
|
description = "Handles the PUT request of assign target group." +
|
||||||
|
"Subgroups are allowed - e.g. Parent/Child")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Successfully assigned"),
|
||||||
|
@ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionInfo.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "The request requires user authentication."),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Insufficient permissions, entity is not allowed to be " +
|
||||||
|
"changed (i.e. read-only) or data volume restriction applies."),
|
||||||
|
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource."),
|
||||||
|
@ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json."),
|
||||||
|
@ApiResponse(responseCode = "409", description = "E.g. in case an entity is created or modified by another " +
|
||||||
|
"user in another request at the same time. You may retry your modification request."),
|
||||||
|
@ApiResponse(responseCode = "415", description = "The request was attempt with a media-type which is not " +
|
||||||
|
"supported by the server for this resource."),
|
||||||
|
@ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " +
|
||||||
|
"and the client has to wait another second.")
|
||||||
|
})
|
||||||
|
@PutMapping(value = MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/assigned")
|
||||||
|
ResponseEntity<Void> assignTargetsToGroupWithSubgroups(
|
||||||
|
@RequestParam("group")
|
||||||
|
@Schema(description = "The target group to be set. Sub-grouping is allowed here - '/' could be used for subgroups")
|
||||||
|
final String group,
|
||||||
|
@Schema(description = "List of controller ids to be assigned", example = "[\"controllerId1\", \"controllerId2\"]")
|
||||||
|
@RequestBody List<String> controllerIds
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns targets to a given group.
|
||||||
|
* Complex (subgroups) are allowed - e.g. Parent/Child
|
||||||
|
*
|
||||||
|
* @param group - target group to be assigned
|
||||||
|
* @param rsql - filter to match desired targets
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Operation(summary = "Assign target(s) to given group by rsql",
|
||||||
|
description = "Handles the PUT request of target group assignment." +
|
||||||
|
"Subgroups are NOT allowed here - e.g. Parent/Child")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Successfully assigned"),
|
||||||
|
@ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionInfo.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "The request requires user authentication."),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Insufficient permissions, entity is not allowed to be " +
|
||||||
|
"changed (i.e. read-only) or data volume restriction applies."),
|
||||||
|
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource."),
|
||||||
|
@ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json."),
|
||||||
|
@ApiResponse(responseCode = "409", description = "E.g. in case an entity is created or modified by another " +
|
||||||
|
"user in another request at the same time. You may retry your modification request."),
|
||||||
|
@ApiResponse(responseCode = "415", description = "The request was attempt with a media-type which is not " +
|
||||||
|
"supported by the server for this resource."),
|
||||||
|
@ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " +
|
||||||
|
"and the client has to wait another second.")
|
||||||
|
})
|
||||||
|
@PutMapping(value = MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/{group}")
|
||||||
|
ResponseEntity<Void> assignTargetsToGroupWithRsql(
|
||||||
|
@PathVariable final String group,
|
||||||
|
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH)
|
||||||
|
@Schema(description = """
|
||||||
|
Query fields based on the Feed Item Query Language (FIQL). See Entity Definitions for
|
||||||
|
available fields.""")
|
||||||
|
final String rsql
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unassigns targets from their groups
|
||||||
|
*
|
||||||
|
* @param controllerIds - list of targets to be unassigned.
|
||||||
|
*/
|
||||||
|
@Operation(summary = "Unassign targets from their target groups",
|
||||||
|
description = "Handles the DELETE request to unassign the given target(s).")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
|
||||||
|
@ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionInfo.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "The request requires user authentication."),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Insufficient permissions, entity is not allowed to be " +
|
||||||
|
"changed (i.e. read-only) or data volume restriction applies."),
|
||||||
|
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource."),
|
||||||
|
@ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json."),
|
||||||
|
@ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " +
|
||||||
|
"and the client has to wait another second.")
|
||||||
|
})
|
||||||
|
@DeleteMapping(value = MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/assigned")
|
||||||
|
ResponseEntity<Void> unassignTargetsFromGroup(
|
||||||
|
@RequestBody(required = false)
|
||||||
|
@Schema(description = "List of controller ids to be unassigned from their groups", example = "[\"controllerId1\", \"controllerId2\"]")
|
||||||
|
List<String> controllerIds
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unassigns targets from their groups
|
||||||
|
*
|
||||||
|
* @param rsql - filter for the matching targets to be unassigned
|
||||||
|
*/
|
||||||
|
@Operation(summary = "Unassign targets from their target groups",
|
||||||
|
description = "Handles the DELETE request to unassign the given target(s).")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
|
||||||
|
@ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionInfo.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "The request requires user authentication."),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Insufficient permissions, entity is not allowed to be " +
|
||||||
|
"changed (i.e. read-only) or data volume restriction applies."),
|
||||||
|
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource."),
|
||||||
|
@ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json."),
|
||||||
|
@ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " +
|
||||||
|
"and the client has to wait another second.")
|
||||||
|
})
|
||||||
|
@DeleteMapping(value = MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING)
|
||||||
|
ResponseEntity<Void> unassignTargetsFromGroupByRsql(
|
||||||
|
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false)
|
||||||
|
@Schema(description = """
|
||||||
|
Query fields based on the Feed Item Query Language (FIQL). See Entity Definitions for
|
||||||
|
available fields.""")
|
||||||
|
final String rsql
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return list of all assigned target groups
|
||||||
|
*/
|
||||||
|
@Operation(summary = "Return all assigned target groups",
|
||||||
|
description = "Handles the GET request of retrieving a list of all target groups.")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
|
||||||
|
@ApiResponse(responseCode = "401", description = "The request requires user authentication."),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Insufficient permissions, entity is not allowed to be " +
|
||||||
|
"changed (i.e. read-only) or data volume restriction applies."),
|
||||||
|
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource."),
|
||||||
|
@ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json."),
|
||||||
|
@ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " +
|
||||||
|
"and the client has to wait another second.")
|
||||||
|
})
|
||||||
|
@GetMapping(value = MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING,
|
||||||
|
produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
|
||||||
|
ResponseEntity<List<String>> getTargetGroups();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign targets matching a rsql filter to a provided target group
|
||||||
|
* @param group - target group to be assigned
|
||||||
|
* @param rsqlParam - rsql filter based on Target fields
|
||||||
|
*/
|
||||||
|
@Operation(summary = "Assign targets matching a rsql filter to provided target group",
|
||||||
|
description = "Handles the GET request of assigning targets matching a rsql filter to a provided target group" +
|
||||||
|
"Subgroups are allowed - e.g. Parent/Child")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Successfully assigned"),
|
||||||
|
@ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionInfo.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "The request requires user authentication."),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Insufficient permissions, entity is not allowed to be " +
|
||||||
|
"changed (i.e. read-only) or data volume restriction applies."),
|
||||||
|
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource."),
|
||||||
|
@ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json."),
|
||||||
|
@ApiResponse(responseCode = "409", description = "E.g. in case an entity is created or modified by another " +
|
||||||
|
"user in another request at the same time. You may retry your modification request."),
|
||||||
|
@ApiResponse(responseCode = "415", description = "The request was attempt with a media-type which is not " +
|
||||||
|
"supported by the server for this resource."),
|
||||||
|
@ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " +
|
||||||
|
"and the client has to wait another second.")
|
||||||
|
})
|
||||||
|
@PutMapping(value = MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING)
|
||||||
|
ResponseEntity<Void> assignTargetsToGroup(
|
||||||
|
@RequestParam(name = "group")
|
||||||
|
@Schema(description = "The target group to be set. Sub-grouping is allowed here - '/' could be used for subgroups")
|
||||||
|
final String group,
|
||||||
|
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH)
|
||||||
|
@Schema(description = """
|
||||||
|
Query fields based on the Feed Item Query Language (FIQL). See Entity Definitions for
|
||||||
|
available fields.""")
|
||||||
|
String rsqlParam);
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.mgmt.rest.resource;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
|
||||||
|
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
|
||||||
|
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetGroupRestApi;
|
||||||
|
import org.eclipse.hawkbit.mgmt.rest.resource.mapper.MgmtTargetMapper;
|
||||||
|
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
|
||||||
|
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
|
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||||
|
import org.eclipse.hawkbit.utils.TenantConfigHelper;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility.sanitizeTargetSortParam;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
public class MgmtTargetGroupResource implements MgmtTargetGroupRestApi {
|
||||||
|
|
||||||
|
private final TargetManagement targetManagement;
|
||||||
|
private final TenantConfigHelper tenantConfigHelper;
|
||||||
|
|
||||||
|
public MgmtTargetGroupResource(final TargetManagement targetManagement, final SystemSecurityContext systemSecurityContext,
|
||||||
|
final TenantConfigurationManagement tenantConfigurationManagement) {
|
||||||
|
this.targetManagement = targetManagement;
|
||||||
|
this.tenantConfigHelper = TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<PagedList<MgmtTarget>> getAssignedTargets(String group, int pagingOffsetParam, int pagingLimitParam, String sortParam) {
|
||||||
|
final Pageable pageable = PagingUtility.toPageable(pagingOffsetParam, pagingLimitParam, sanitizeTargetSortParam(sortParam));
|
||||||
|
|
||||||
|
final Page<Target> targets = targetManagement.findTargetsByGroup(group, false, pageable);
|
||||||
|
|
||||||
|
final List<MgmtTarget> rest = MgmtTargetMapper.toResponse(targets.getContent(), tenantConfigHelper);
|
||||||
|
return ResponseEntity.ok(new PagedList<>(rest, targets.getTotalElements()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<PagedList<MgmtTarget>> getAssignedTargetsWithSubgroups(String groupFilter, boolean subgroups, int pagingOffsetParam, int pagingLimitParam, String sortParam) {
|
||||||
|
final Pageable pageable = PagingUtility.toPageable(pagingOffsetParam, pagingLimitParam, sanitizeTargetSortParam(sortParam));
|
||||||
|
|
||||||
|
final Page<Target> targets = targetManagement.findTargetsByGroup(groupFilter, subgroups, pageable);
|
||||||
|
|
||||||
|
final List<MgmtTarget> rest = MgmtTargetMapper.toResponse(targets.getContent(), tenantConfigHelper);
|
||||||
|
return ResponseEntity.ok(new PagedList<>(rest, targets.getTotalElements()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<Void> assignTargetsToGroup(String group, List<String> controllerIds) {
|
||||||
|
return assignTargets(group, controllerIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<Void> assignTargetsToGroupWithSubgroups(String group, List<String> controllerIds) {
|
||||||
|
return assignTargets(group, controllerIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<Void> assignTargetsToGroupWithRsql(String group, String rsql) {
|
||||||
|
targetManagement.assignTargetGroupWithRsql(group, rsql);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<Void> unassignTargetsFromGroup(List<String> controllerIds) {
|
||||||
|
targetManagement.assignTargetsWithGroup(null, controllerIds);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<Void> unassignTargetsFromGroupByRsql(String rsql) {
|
||||||
|
targetManagement.assignTargetGroupWithRsql(null, rsql);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<List<String>> getTargetGroups() {
|
||||||
|
final List<String> groups = targetManagement.findGroups();
|
||||||
|
return ResponseEntity.ok(groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<Void> assignTargetsToGroup(final String group, final String rsql) {
|
||||||
|
targetManagement.assignTargetGroupWithRsql(group, rsql);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<Void> assignTargets(final String group, final List<String> controllerIds) {
|
||||||
|
targetManagement.assignTargetsWithGroup(group, controllerIds);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -140,6 +140,7 @@ public final class MgmtTargetMapper {
|
|||||||
targetRest.setDescription(target.getDescription());
|
targetRest.setDescription(target.getDescription());
|
||||||
targetRest.setName(target.getName());
|
targetRest.setName(target.getName());
|
||||||
targetRest.setUpdateStatus(target.getUpdateStatus().name().toLowerCase());
|
targetRest.setUpdateStatus(target.getUpdateStatus().name().toLowerCase());
|
||||||
|
targetRest.setGroup(target.getGroup());
|
||||||
|
|
||||||
final URI address = target.getAddress();
|
final URI address = target.getAddress();
|
||||||
if (address != null) {
|
if (address != null) {
|
||||||
@@ -325,7 +326,7 @@ public final class MgmtTargetMapper {
|
|||||||
private static TargetCreate fromRequest(final EntityFactory entityFactory, final MgmtTargetRequestBody targetRest) {
|
private static TargetCreate fromRequest(final EntityFactory entityFactory, final MgmtTargetRequestBody targetRest) {
|
||||||
return entityFactory.target().create().controllerId(targetRest.getControllerId()).name(targetRest.getName())
|
return entityFactory.target().create().controllerId(targetRest.getControllerId()).name(targetRest.getName())
|
||||||
.description(targetRest.getDescription()).securityToken(targetRest.getSecurityToken())
|
.description(targetRest.getDescription()).securityToken(targetRest.getSecurityToken())
|
||||||
.address(targetRest.getAddress()).targetType(targetRest.getTargetType());
|
.address(targetRest.getAddress()).targetType(targetRest.getTargetType()).group(targetRest.getGroup());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getType(final Action action) {
|
private static String getType(final Action action) {
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.mgmt.rest.resource;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||||
|
import org.eclipse.hawkbit.rest.util.JsonBuilder;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
|
||||||
|
public class MgmtTargetGroupResourceTest extends AbstractManagementApiIntegrationTest {
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRetrieveDistinctTargetGroups() throws Exception {
|
||||||
|
|
||||||
|
final List<String> expectedGroups = List.of("Europe", "Asia");
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target1").group("Europe"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target2").group("Asia"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target3").group("Europe"));
|
||||||
|
|
||||||
|
mvc.perform(get(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.[0]", Matchers.in(expectedGroups)))
|
||||||
|
.andExpect(jsonPath("$.[1]", Matchers.in(expectedGroups)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRetrieveTargetsFilteredByGroupAndParentGroupCorrectly() throws Exception {
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target1").group("Europe/West"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target2").group("Europe/East"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target3").group("Europe"));
|
||||||
|
|
||||||
|
mvc.perform(get(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/assigned")
|
||||||
|
.param("group", "Europe/East")
|
||||||
|
.param("subgroups", "false"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("content", Matchers.hasSize(1)))
|
||||||
|
.andExpect(jsonPath("content.[0].controllerId", Matchers.equalTo("target2")));
|
||||||
|
|
||||||
|
mvc.perform(get(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/assigned")
|
||||||
|
.param("group", "Europe")
|
||||||
|
.param("subgroups", "true")
|
||||||
|
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "ID:ASC"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("content", Matchers.hasSize(3)))
|
||||||
|
.andExpect(jsonPath("content.[0].controllerId", Matchers.equalTo("target1")))
|
||||||
|
.andExpect(jsonPath("content.[1].controllerId", Matchers.equalTo("target2")))
|
||||||
|
.andExpect(jsonPath("content.[2].controllerId", Matchers.equalTo("target3")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetAssignedTargetsToSpecificGroup() throws Exception {
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target1").group("Europe"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target2").group("US"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target3").group("Europe"));
|
||||||
|
|
||||||
|
mvc.perform(get(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/Europe/assigned")
|
||||||
|
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "ID:ASC"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("content", Matchers.hasSize(2)))
|
||||||
|
.andExpect(jsonPath("content.[0].controllerId", Matchers.equalTo("target1")))
|
||||||
|
.andExpect(jsonPath("content.[1].controllerId", Matchers.equalTo("target3")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAssignListOfTargetsToASpecificGroup() throws Exception {
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target1").group("Europe"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target2"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target3").group("Europe"));
|
||||||
|
|
||||||
|
mvc.perform(put(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/newGroup/assigned")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(JsonBuilder.toArray(Arrays.asList("target1", "target2", "target3"))))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
|
|
||||||
|
mvc.perform(get(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/newGroup/assigned")
|
||||||
|
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "ID:ASC")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("content", Matchers.hasSize(3)))
|
||||||
|
.andExpect(jsonPath("content.[0].controllerId", Matchers.equalTo("target1")))
|
||||||
|
.andExpect(jsonPath("content.[1].controllerId", Matchers.equalTo("target2")))
|
||||||
|
.andExpect(jsonPath("content.[2].controllerId", Matchers.equalTo("target3")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnBadRequestWhenProvidingAnEmptyListOfControllerIds() throws Exception {
|
||||||
|
mvc.perform(put(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/someGroup/assigned")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(JsonBuilder.toArray(Collections.emptyList())))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAssignListOfTargetsToProvidedGroupWithSubgroup() throws Exception {
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target1").group("Europe"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target2"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target3").group("US"));
|
||||||
|
|
||||||
|
mvc.perform(put(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/assigned")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(JsonBuilder.toArray(Arrays.asList("target1", "target2", "target3")))
|
||||||
|
.param("group", "Europe/East"))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
|
mvc.perform(get(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/assigned")
|
||||||
|
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "ID:ASC")
|
||||||
|
.param("group", "Europe/East")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("content", Matchers.hasSize(3)))
|
||||||
|
.andExpect(jsonPath("content.[0].controllerId", Matchers.equalTo("target1")))
|
||||||
|
.andExpect(jsonPath("content.[1].controllerId", Matchers.equalTo("target2")))
|
||||||
|
.andExpect(jsonPath("content.[2].controllerId", Matchers.equalTo("target3")));
|
||||||
|
|
||||||
|
// expect bad request if empty controllerIds
|
||||||
|
mvc.perform(put(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/assigned")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(JsonBuilder.toArray(Collections.emptyList()))
|
||||||
|
.param("group", "doesNotMatter"))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAssignTargetsToProvidedGroupByRsql() throws Exception {
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target1").group("A"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target2"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("shouldNotAssign").group("B"));
|
||||||
|
|
||||||
|
mvc.perform(put(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/C")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.param("q", "controllerId==target*"))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
|
mvc.perform(get(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/assigned")
|
||||||
|
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "ID:ASC")
|
||||||
|
.param("group", "C")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("content", Matchers.hasSize(2)))
|
||||||
|
.andExpect(jsonPath("content.[0].controllerId", Matchers.equalTo("target1")))
|
||||||
|
.andExpect(jsonPath("content.[1].controllerId", Matchers.equalTo("target2")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUnassignTargetsFromGroup() throws Exception {
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target1").group("Europe"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target2").group("Europe"));
|
||||||
|
|
||||||
|
mvc.perform(delete(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/assigned")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(JsonBuilder.toArray(Arrays.asList("target1", "target2"))))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
|
mvc.perform(get(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/assigned")
|
||||||
|
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "ID:ASC")
|
||||||
|
.param("group", "Europe")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("content", Matchers.hasSize(0)));
|
||||||
|
|
||||||
|
// expect bad request if empty
|
||||||
|
mvc.perform(delete(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/assigned")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(JsonBuilder.toArray(Collections.emptyList())))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUnassignTargetsFromGroupByRsqlFilter() throws Exception {
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target1").group("Europe"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target2").group("Europe"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("nonMatchingTarget").group("Europe"));
|
||||||
|
|
||||||
|
mvc.perform(delete(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.param("q", "controllerId==target*"))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
|
mvc.perform(get(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/assigned")
|
||||||
|
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "ID:ASC")
|
||||||
|
.param("group", "Europe")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("content", Matchers.hasSize(1)))
|
||||||
|
.andExpect(jsonPath("content.[0].controllerId", Matchers.equalTo("nonMatchingTarget")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUpdateTargetGroupsOfTargetsMatchingTheRsqlFilter() throws Exception {
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target1").group("Europe"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target2").group("Europe"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("target3").group("Europe"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("shouldNotBeUpdated1").group("Europe"));
|
||||||
|
targetManagement.create(entityFactory.target().create().controllerId("shouldNotBeUpdated2").group("Europe"));
|
||||||
|
|
||||||
|
mvc.perform(put(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.param("group", "Europe/East")
|
||||||
|
.param("q", "controllerId==target*"))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
|
mvc.perform(get(MgmtRestConstants.TARGET_GROUP_V1_REQUEST_MAPPING + "/assigned")
|
||||||
|
.param("group", "Europe/East")
|
||||||
|
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "ID:ASC")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("content", Matchers.hasSize(3)))
|
||||||
|
.andExpect(jsonPath("content.[0].controllerId", Matchers.equalTo("target1")))
|
||||||
|
.andExpect(jsonPath("content.[1].controllerId", Matchers.equalTo("target2")))
|
||||||
|
.andExpect(jsonPath("content.[2].controllerId", Matchers.equalTo("target3")));
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -620,6 +620,41 @@ public interface TargetManagement {
|
|||||||
@PreAuthorize(HAS_AUTH_UPDATE_TARGET)
|
@PreAuthorize(HAS_AUTH_UPDATE_TARGET)
|
||||||
Target assignType(@NotEmpty String controllerId, @NotNull Long targetTypeId);
|
Target assignType(@NotEmpty String controllerId, @NotNull Long targetTypeId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds targets by group or subgroup.
|
||||||
|
* @param group - provided group/subgroup to filter for
|
||||||
|
* @param withSubgroups - whether is a subgroup or not e.g. x/y/z
|
||||||
|
* @param pageable - page parameter
|
||||||
|
* @return all matching targets to provided group/subgroup
|
||||||
|
*/
|
||||||
|
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
|
||||||
|
Page<Target> findTargetsByGroup(@NotEmpty String group, boolean withSubgroups, @NotNull Pageable pageable);
|
||||||
|
/**
|
||||||
|
* Finds all the distinct target groups in the scope of a tenant
|
||||||
|
*
|
||||||
|
* @return list of all distinct target groups
|
||||||
|
*/
|
||||||
|
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
|
||||||
|
List<String> findGroups();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns the target group of the targets matching the provided rsql filter.
|
||||||
|
*
|
||||||
|
* @param group target group parameter
|
||||||
|
* @param rsql rsql filter for {@link Target}
|
||||||
|
*/
|
||||||
|
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
|
||||||
|
void assignTargetGroupWithRsql(String group, @NotNull String rsql);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns the provided group to the targets which are in the provided list of controllerIds.
|
||||||
|
*
|
||||||
|
* @param group target group parameter
|
||||||
|
* @param controllerIds list of targets
|
||||||
|
*/
|
||||||
|
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
|
||||||
|
void assignTargetsWithGroup(String group, @NotEmpty List<String> controllerIds);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* updates the {@link Target}.
|
* updates the {@link Target}.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -73,6 +73,12 @@ public interface TargetCreate {
|
|||||||
*/
|
*/
|
||||||
TargetCreate status(@NotNull TargetUpdateStatus status);
|
TargetCreate status(@NotNull TargetUpdateStatus status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param group for setting the group of the target
|
||||||
|
* @return updated builder instance
|
||||||
|
*/
|
||||||
|
TargetCreate group(String group);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return peek on current state of {@link Target} in the builder
|
* @return peek on current state of {@link Target} in the builder
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -70,4 +70,11 @@ public interface TargetUpdate {
|
|||||||
* @return updated builder instance
|
* @return updated builder instance
|
||||||
*/
|
*/
|
||||||
TargetUpdate requestAttributes(Boolean requestAttributes);
|
TargetUpdate requestAttributes(Boolean requestAttributes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param group for {@link Target#getGroup()}
|
||||||
|
* @return updated builder instance
|
||||||
|
*/
|
||||||
|
TargetUpdate group(String group);
|
||||||
}
|
}
|
||||||
@@ -124,4 +124,10 @@ public interface Target extends NamedEntity {
|
|||||||
* {@link #getControllerAttributes()}.
|
* {@link #getControllerAttributes()}.
|
||||||
*/
|
*/
|
||||||
boolean isRequestControllerAttributes();
|
boolean isRequestControllerAttributes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return the group of the target
|
||||||
|
*/
|
||||||
|
String getGroup();
|
||||||
}
|
}
|
||||||
@@ -34,6 +34,7 @@ public class AbstractTargetUpdateCreate<T> extends AbstractNamedEntityBuilder<T>
|
|||||||
protected TargetUpdateStatus status;
|
protected TargetUpdateStatus status;
|
||||||
protected Boolean requestAttributes;
|
protected Boolean requestAttributes;
|
||||||
protected Long targetTypeId;
|
protected Long targetTypeId;
|
||||||
|
protected String group;
|
||||||
|
|
||||||
protected AbstractTargetUpdateCreate(final String controllerId) {
|
protected AbstractTargetUpdateCreate(final String controllerId) {
|
||||||
this.controllerId = AbstractBaseEntityBuilder.strip(controllerId);
|
this.controllerId = AbstractBaseEntityBuilder.strip(controllerId);
|
||||||
@@ -83,6 +84,11 @@ public class AbstractTargetUpdateCreate<T> extends AbstractNamedEntityBuilder<T>
|
|||||||
return (T) this;
|
return (T) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public T group(final String group) {
|
||||||
|
this.group = group;
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
public String getControllerId() {
|
public String getControllerId() {
|
||||||
return controllerId;
|
return controllerId;
|
||||||
}
|
}
|
||||||
@@ -107,4 +113,6 @@ public class AbstractTargetUpdateCreate<T> extends AbstractNamedEntityBuilder<T>
|
|||||||
return targetTypeId;
|
return targetTypeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<String> getGroup() { return Optional.ofNullable(group); }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE sp_target ADD COLUMN target_group VARCHAR(256);
|
||||||
|
CREATE INDEX sp_idx_target_group ON sp_target (tenant, target_group);
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE sp_target ADD COLUMN target_group VARCHAR(256);
|
||||||
|
CREATE INDEX sp_idx_target_group ON sp_target (tenant, target_group);
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE sp_target ADD COLUMN target_group VARCHAR(256);
|
||||||
|
CREATE INDEX sp_idx_target_group ON sp_target (tenant, target_group);
|
||||||
@@ -53,6 +53,7 @@ public class JpaTargetCreate extends AbstractTargetUpdateCreate<TargetCreate> im
|
|||||||
target.setAddress(address);
|
target.setAddress(address);
|
||||||
target.setUpdateStatus(getStatus().orElse(TargetUpdateStatus.UNKNOWN));
|
target.setUpdateStatus(getStatus().orElse(TargetUpdateStatus.UNKNOWN));
|
||||||
getLastTargetQuery().ifPresent(target::setLastTargetQuery);
|
getLastTargetQuery().ifPresent(target::setLastTargetQuery);
|
||||||
|
target.setGroup(getGroup().orElse(null));
|
||||||
|
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ import java.util.stream.Collectors;
|
|||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||||
import jakarta.persistence.criteria.CriteriaQuery;
|
import jakarta.persistence.criteria.CriteriaQuery;
|
||||||
|
import jakarta.persistence.criteria.CriteriaUpdate;
|
||||||
import jakarta.persistence.criteria.MapJoin;
|
import jakarta.persistence.criteria.MapJoin;
|
||||||
|
import jakarta.persistence.criteria.Predicate;
|
||||||
import jakarta.persistence.criteria.Root;
|
import jakarta.persistence.criteria.Root;
|
||||||
import jakarta.persistence.metamodel.MapAttribute;
|
import jakarta.persistence.metamodel.MapAttribute;
|
||||||
import jakarta.validation.constraints.NotEmpty;
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
@@ -562,6 +564,57 @@ public class JpaTargetManagement implements TargetManagement {
|
|||||||
return targetRepository.save(target);
|
return targetRepository.save(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<Target> findTargetsByGroup(String group, final boolean withSubgroups, final Pageable pageable) {
|
||||||
|
if (withSubgroups) {
|
||||||
|
// search for eq(group) and like(group%)
|
||||||
|
return JpaManagementHelper
|
||||||
|
.findAllWithCountBySpec(targetRepository, List.of(TargetSpecifications.eqOrSubTargetGroup(group)), pageable);
|
||||||
|
} else {
|
||||||
|
return JpaManagementHelper
|
||||||
|
.findAllWithCountBySpec(targetRepository, List.of(TargetSpecifications.eqTargetGroup(group)), pageable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> findGroups() {
|
||||||
|
return targetRepository.findDistinctGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
|
||||||
|
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||||
|
public void assignTargetGroupWithRsql(String group, String rsql) {
|
||||||
|
final Specification<JpaTarget> rsqlSpecification = RsqlUtility.getInstance().buildRsqlSpecification(rsql, TargetFields.class);
|
||||||
|
|
||||||
|
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||||
|
final CriteriaUpdate<JpaTarget> criteriaUpdateQuery = cb.createCriteriaUpdate(JpaTarget.class);
|
||||||
|
final Root<JpaTarget> root = criteriaUpdateQuery.getRoot();
|
||||||
|
criteriaUpdateQuery.set("group", group);
|
||||||
|
// get predicate from rsql specification using a dummy query in order to execute batch update
|
||||||
|
final Predicate predicate = rsqlSpecification.toPredicate(root, entityManager.getCriteriaBuilder().createQuery(JpaTarget.class), cb);
|
||||||
|
criteriaUpdateQuery.where(predicate);
|
||||||
|
|
||||||
|
entityManager.createQuery(criteriaUpdateQuery).executeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
|
||||||
|
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||||
|
public void assignTargetsWithGroup(String group, List<String> controllerIds) {
|
||||||
|
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||||
|
CriteriaUpdate<JpaTarget> criteriaQuery = cb.createCriteriaUpdate(JpaTarget.class);
|
||||||
|
Root<JpaTarget> root = criteriaQuery.from(JpaTarget.class);
|
||||||
|
CriteriaBuilder.In<String> in = cb.in(root.get("controllerId"));
|
||||||
|
controllerIds.forEach(in::value);
|
||||||
|
|
||||||
|
entityManager.createQuery(criteriaQuery.set("group", group).where(in)).executeUpdate();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
|
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
|
||||||
|
|||||||
@@ -154,6 +154,11 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
|
|||||||
// set default request controller attributes to true, because we want to request them the first time
|
// set default request controller attributes to true, because we want to request them the first time
|
||||||
private boolean requestControllerAttributes = true;
|
private boolean requestControllerAttributes = true;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
@Column(name = "target_group")
|
||||||
|
private String group;
|
||||||
|
|
||||||
// actually it is OneToOne - but lazy loading is not supported for OneToOne (at least for hibernate 6.6.2)
|
// actually it is OneToOne - but lazy loading is not supported for OneToOne (at least for hibernate 6.6.2)
|
||||||
@OneToMany(fetch = FetchType.LAZY, mappedBy = "target", cascade = { CascadeType.ALL }, orphanRemoval = true)
|
@OneToMany(fetch = FetchType.LAZY, mappedBy = "target", cascade = { CascadeType.ALL }, orphanRemoval = true)
|
||||||
@PrimaryKeyJoinColumn
|
@PrimaryKeyJoinColumn
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
package org.eclipse.hawkbit.repository.jpa.repository;
|
package org.eclipse.hawkbit.repository.jpa.repository;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
@@ -96,4 +97,11 @@ public interface TargetRepository extends BaseEntityRepository<JpaTarget> {
|
|||||||
@Transactional
|
@Transactional
|
||||||
@Query("DELETE FROM JpaTarget t WHERE t.tenant = :tenant")
|
@Query("DELETE FROM JpaTarget t WHERE t.tenant = :tenant")
|
||||||
void deleteByTenant(@Param("tenant") String tenant);
|
void deleteByTenant(@Param("tenant") String tenant);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds all available distinct target groups
|
||||||
|
* @return all target groups
|
||||||
|
*/
|
||||||
|
@Query(value = "SELECT DISTINCT target_group FROM sp_target WHERE target_group IS NOT NULL", nativeQuery = true)
|
||||||
|
List<String> findDistinctGroups();
|
||||||
}
|
}
|
||||||
@@ -192,6 +192,22 @@ public final class TargetSpecifications {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Specification<JpaTarget> eqTargetGroup(final String targetGroup) {
|
||||||
|
return (targetRoot, query, criteriaBuilder) -> {
|
||||||
|
final String groupTextToLower = targetGroup.toLowerCase();
|
||||||
|
return criteriaBuilder.equal(criteriaBuilder.lower(targetRoot.get(JpaTarget_.group)), groupTextToLower);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Specification<JpaTarget> eqOrSubTargetGroup(final String targetGroupSearch) {
|
||||||
|
return (targetRoot, query, criteriaBuilder) -> {
|
||||||
|
final String searchTextToLower = targetGroupSearch.toLowerCase();
|
||||||
|
return criteriaBuilder.or(
|
||||||
|
criteriaBuilder.equal(criteriaBuilder.lower(targetRoot.get(JpaTarget_.group)), searchTextToLower),
|
||||||
|
criteriaBuilder.like(criteriaBuilder.lower(targetRoot.get(JpaTarget_.group)), searchTextToLower.concat("/%")));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link Specification} for retrieving {@link Target}s by "like controllerId".
|
* {@link Specification} for retrieving {@link Target}s by "like controllerId".
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public interface Constants {
|
|||||||
String VERSION = "Version";
|
String VERSION = "Version";
|
||||||
String VENDOR = "Vendor";
|
String VENDOR = "Vendor";
|
||||||
String TYPE = "Type";
|
String TYPE = "Type";
|
||||||
|
String GROUP = "Group";
|
||||||
String CREATED_BY = "Created by";
|
String CREATED_BY = "Created by";
|
||||||
String CREATED_AT = "Created at";
|
String CREATED_AT = "Created at";
|
||||||
String LAST_MODIFIED_BY = "Last modified by";
|
String LAST_MODIFIED_BY = "Last modified by";
|
||||||
|
|||||||
@@ -345,6 +345,7 @@ public class TargetView extends TableView<MgmtTarget, String> {
|
|||||||
private final TextField lastModifiedAt = Utils.textField(Constants.LAST_MODIFIED_AT);
|
private final TextField lastModifiedAt = Utils.textField(Constants.LAST_MODIFIED_AT);
|
||||||
private final TextField securityToken = Utils.textField(Constants.SECURITY_TOKEN);
|
private final TextField securityToken = Utils.textField(Constants.SECURITY_TOKEN);
|
||||||
private final TextField lastPoll = Utils.textField(Constants.LAST_POLL);
|
private final TextField lastPoll = Utils.textField(Constants.LAST_POLL);
|
||||||
|
private final TextField group = Utils.textField(Constants.GROUP);
|
||||||
private final TextArea targetAttributes = new TextArea(Constants.ATTRIBUTES);
|
private final TextArea targetAttributes = new TextArea(Constants.ATTRIBUTES);
|
||||||
private transient MgmtTarget target;
|
private transient MgmtTarget target;
|
||||||
|
|
||||||
@@ -355,7 +356,7 @@ public class TargetView extends TableView<MgmtTarget, String> {
|
|||||||
description,
|
description,
|
||||||
createdBy, createdAt,
|
createdBy, createdAt,
|
||||||
lastModifiedBy, lastModifiedAt,
|
lastModifiedBy, lastModifiedAt,
|
||||||
securityToken, lastPoll, targetAttributes
|
securityToken, lastPoll, targetAttributes, group
|
||||||
)
|
)
|
||||||
.forEach(field -> {
|
.forEach(field -> {
|
||||||
field.setReadOnly(true);
|
field.setReadOnly(true);
|
||||||
@@ -378,6 +379,7 @@ public class TargetView extends TableView<MgmtTarget, String> {
|
|||||||
lastModifiedBy.setValue(target.getLastModifiedBy());
|
lastModifiedBy.setValue(target.getLastModifiedBy());
|
||||||
lastModifiedAt.setValue(new Date(target.getLastModifiedAt()).toString());
|
lastModifiedAt.setValue(new Date(target.getLastModifiedAt()).toString());
|
||||||
securityToken.setValue(target.getSecurityToken());
|
securityToken.setValue(target.getSecurityToken());
|
||||||
|
group.setValue(target.getGroup() != null ? target.getGroup() : "");
|
||||||
|
|
||||||
final MgmtPollStatus pollStatus = target.getPollStatus();
|
final MgmtPollStatus pollStatus = target.getPollStatus();
|
||||||
lastPoll.setValue(pollStatus == null ? NOT_AVAILABLE_NULL : new Date(pollStatus.getLastRequestAt()).toString());
|
lastPoll.setValue(pollStatus == null ? NOT_AVAILABLE_NULL : new Date(pollStatus.getLastRequestAt()).toString());
|
||||||
|
|||||||
Reference in New Issue
Block a user