Skip to content

Commit

Permalink
feat(rest): modify moderation requests
Browse files Browse the repository at this point in the history
Modify ModerationRequest being
- Accepted
- Rejected
- Assigned (In progress)
- Unassigned (Pending)

Commit is related to issue eclipse-sw360#1849

Signed-off-by: Gaurav Mishra <[email protected]>
  • Loading branch information
GMishx committed Apr 7, 2023
1 parent 4debd5a commit 0e10fd3
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 10 deletions.
39 changes: 39 additions & 0 deletions rest/resource-server/src/docs/asciidoc/moderationRequests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,42 @@ include::{snippets}/should_document_get_moderationrequests_by_state/curl-request
===== Example response
include::{snippets}/should_document_get_moderationrequests_by_state/http-response.adoc[]

[[resources-moderationRequest-accept-reject]]
==== Accepting or rejecting a moderation request

A `PATCH` request will accept or reject the ModerationRequest, save the comment by the reviewer and send email notifications.

===== Request body
include::{snippets}/should_document_get_moderationrequests_accept/request-body.adoc[]

===== Request structure
include::{snippets}/should_document_get_moderationrequests_accept/request-fields.adoc[]

===== Response structure
include::{snippets}/should_document_get_moderationrequests_accept/response-fields.adoc[]

===== Example request
include::{snippets}/should_document_get_moderationrequests_accept/curl-request.adoc[]

===== Example response
include::{snippets}/should_document_get_moderationrequests_accept/http-response.adoc[]

[[resources-moderationRequest-assign-unassign]]
==== Assign or Unassign a moderation request

A `PATCH` request will assign a ModerationRequest to the user. The request can be used to unassign the user from the ModerationRequest as well.

===== Request body
include::{snippets}/should_document_get_moderationrequests_assign/request-body.adoc[]

===== Request structure
include::{snippets}/should_document_get_moderationrequests_assign/request-fields.adoc[]

===== Response structure
include::{snippets}/should_document_get_moderationrequests_assign/response-fields.adoc[]

===== Example request
include::{snippets}/should_document_get_moderationrequests_assign/curl-request.adoc[]

===== Example response
include::{snippets}/should_document_get_moderationrequests_assign/http-response.adoc[]
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import org.eclipse.sw360.datahandler.thrift.vulnerabilities.VulnerabilityDTO;
import org.eclipse.sw360.rest.resourceserver.core.serializer.JsonProjectRelationSerializer;
import org.eclipse.sw360.rest.resourceserver.core.serializer.JsonReleaseRelationSerializer;
import org.eclipse.sw360.rest.resourceserver.moderationrequest.ModerationPatch;
import org.eclipse.sw360.rest.resourceserver.project.EmbeddedProject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand Down Expand Up @@ -104,6 +105,7 @@ public Sw360Module() {
setMixInAnnotation(VerificationStateInfo.class, Sw360Module.VerificationStateInfoMixin.class);
setMixInAnnotation(ProjectProjectRelationship.class, Sw360Module.ProjectProjectRelationshipMixin.class);
setMixInAnnotation(ModerationRequest.class, Sw360Module.ModerationRequestMixin.class);
setMixInAnnotation(ModerationPatch.class, Sw360Module.ModerationPatchMixin.class);
}

@JsonInclude(JsonInclude.Include.NON_NULL)
Expand Down Expand Up @@ -1239,5 +1241,9 @@ public static abstract class ProjectProjectRelationshipMixin extends ProjectProj
@JsonRootName(value = "moderationRequest")
public static abstract class ModerationRequestMixin extends ModerationRequest {
}

@JsonInclude(JsonInclude.Include.NON_NULL)
public static abstract class ModerationPatchMixin extends ModerationPatch {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright Siemens AG, 2023. Part of the SW360 Portal Project.
*
* 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
*
* SPDX-FileCopyrightText: 2023, Siemens AG. Part of the SW360 Portal Project.
* SPDX-FileContributor: Gaurav Mishra <[email protected]>
*/

package org.eclipse.sw360.rest.resourceserver.moderationrequest;

/**
* Input for PATCH request on moderation request to accept/reject it.
*/
public class ModerationPatch {
private ModerationAction action;
private String comment;

public ModerationPatch() {
this.action = null;
this.comment = null;
}

public ModerationAction getAction() {
return action;
}

public void setAction(ModerationAction action) {
this.action = action;
}

public String getComment() {
return comment;
}

public void setComment(String comment) {
this.comment = comment;
}

/**
* Actions which can be perfromed on the moderation request
*/
public enum ModerationAction {
ACCEPT,
REJECT,
UNASSIGN,
ASSIGN
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.eclipse.sw360.datahandler.resourcelists.PaginationParameterException;
import org.eclipse.sw360.datahandler.resourcelists.PaginationResult;
import org.eclipse.sw360.datahandler.resourcelists.ResourceClassNotFoundException;
import org.eclipse.sw360.datahandler.thrift.ModerationState;
import org.eclipse.sw360.datahandler.thrift.PaginationData;
import org.eclipse.sw360.datahandler.thrift.components.Component;
import org.eclipse.sw360.datahandler.thrift.components.Release;
Expand All @@ -31,6 +32,7 @@
import org.eclipse.sw360.rest.resourceserver.core.HalResource;
import org.eclipse.sw360.rest.resourceserver.core.RestControllerHelper;
import org.eclipse.sw360.rest.resourceserver.project.Sw360ProjectService;
import org.eclipse.sw360.rest.resourceserver.release.ReleaseController;
import org.eclipse.sw360.rest.resourceserver.release.Sw360ReleaseService;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -39,48 +41,49 @@
import org.springframework.data.rest.webmvc.RepositoryLinksResource;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.server.RepresentationModelProcessor;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
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;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import javax.servlet.http.HttpServletRequest;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;

@RestController
@BasePathAwareController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ModerationRequestController implements RepresentationModelProcessor<RepositoryLinksResource> {

public static final String MODERATION_REQUEST_URL = "/moderationrequest";

@Autowired
private Sw360ModerationRequestService sw360ModerationRequestService;

@NonNull
private final RestControllerHelper restControllerHelper;

@NonNull
private final Sw360ProjectService projectService;

@NonNull
private final Sw360ReleaseService releaseService;

@NonNull
private final Sw360ComponentService componentService;

@NonNull
private final com.fasterxml.jackson.databind.Module sw360Module;

@Autowired
private Sw360ModerationRequestService sw360ModerationRequestService;

@RequestMapping(value = MODERATION_REQUEST_URL, method = RequestMethod.GET)
public ResponseEntity<CollectionModel> getModerationRequests(Pageable pageable, HttpServletRequest request)
Expand Down Expand Up @@ -162,6 +165,50 @@ public ResponseEntity<CollectionModel> getModerationRequestsByState(Pageable pag
return new ResponseEntity<>(resources, HttpStatus.OK);
}

@RequestMapping(value = MODERATION_REQUEST_URL + "/{id}", method = RequestMethod.PATCH)
public ResponseEntity<HalResource> updateModerationRequestById(HttpInputMessage request,
@PathVariable String id,
@RequestBody ModerationPatch patch)
throws TException {
User sw360User = restControllerHelper.getSw360UserFromAuthentication();

ModerationRequest moderationRequest = sw360ModerationRequestService.getModerationRequestById(id);
ModerationState moderationStatus;
String requestResponse;
switch (patch.getAction()) {
case ACCEPT:
moderationStatus = sw360ModerationRequestService.acceptRequest(moderationRequest, patch.getComment(),
sw360User);
requestResponse = moderationStatus.toString();
break;
case REJECT:
moderationStatus = sw360ModerationRequestService.rejectRequest(moderationRequest, patch.getComment(),
sw360User);
requestResponse = moderationStatus.toString();
break;
case UNASSIGN:
moderationStatus = sw360ModerationRequestService.removeMeFromModerators(moderationRequest, sw360User);
requestResponse = moderationStatus.toString();
break;
case ASSIGN:
moderationStatus = sw360ModerationRequestService.assignRequest(moderationRequest, sw360User);
requestResponse = moderationStatus.toString();
break;
default:
throw new HttpMessageNotReadableException("Action should be `" +
Arrays.asList(ModerationPatch.ModerationAction.values()) +
"`, '" + patch.getAction() + "' received.", request);
}
Map<String, String> responseMap = new HashMap<>();
responseMap.put("status", requestResponse);
HalResource responseResource = new HalResource(responseMap);
Link requestLink = linkTo(ReleaseController.class).slash("api" + MODERATION_REQUEST_URL).slash(id)
.withSelfRel();
responseResource.add(requestLink);

return new ResponseEntity<HalResource>(responseResource, HttpStatus.ACCEPTED);
}

private HalResource<ModerationRequest> createHalModerationRequestWithAllDetails(ModerationRequest moderationRequest,
User sw360User) throws TException {
HalResource<ModerationRequest> halModerationRequest = new HalResource<>(moderationRequest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,22 @@
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransportException;
import org.eclipse.sw360.datahandler.thrift.ModerationState;
import org.eclipse.sw360.datahandler.thrift.PaginationData;
import org.eclipse.sw360.datahandler.thrift.RemoveModeratorRequestStatus;
import org.eclipse.sw360.datahandler.thrift.SW360Exception;
import org.eclipse.sw360.datahandler.thrift.moderation.ModerationRequest;
import org.eclipse.sw360.datahandler.thrift.moderation.ModerationService;
import org.eclipse.sw360.datahandler.thrift.users.User;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;

import java.security.InvalidParameterException;
import java.util.List;
import java.util.Map;

Expand All @@ -43,12 +47,23 @@ public class Sw360ModerationRequestService {
@Value("${sw360.thrift-server-url:http://localhost:8080}")
private String thriftServerUrl;

public static boolean isOpenModerationRequest(@NotNull ModerationRequest moderationRequest) {
return moderationRequest.getModerationState() == ModerationState.PENDING || moderationRequest.getModerationState() == ModerationState.INPROGRESS;
}

private ModerationService.Iface getThriftModerationClient() throws TTransportException {
THttpClient thriftClient = new THttpClient(thriftServerUrl + "/moderation/thrift");
TProtocol protocol = new TCompactProtocol(thriftClient);
return new ModerationService.Client(protocol);
}

/**
* Get a moderation request by id if exists. Otherwise raise appropriate exception.
*
* @param requestId ID of moderation request to get
* @return Moderation Request
* @throws TException Appropriate exception if request does not exists or not accessible.
*/
public ModerationRequest getModerationRequestById(String requestId) throws TException {
try {
return getThriftModerationClient().getModerationRequestById(requestId);
Expand All @@ -65,10 +80,27 @@ public ModerationRequest getModerationRequestById(String requestId) throws TExce
}
}

/**
* Get list of moderation requests where user is one of the moderators.
*
* @param sw360User Moderator
* @return List of moderation requests.
* @throws TException Exception in case of error.
*/
public List<ModerationRequest> getRequestsByModerator(User sw360User) throws TException {
return getThriftModerationClient().getRequestsByModerator(sw360User);
}

/**
* Get list of moderation requests based on moderation state(open/closed)
* where user is one of the moderators.
*
* @param sw360user Moderator
* @param pageable Pagination information
* @param open State is open?
* @return Map of pagination information and list of moderation requests
* @throws TException Exception in case of error.
*/
public Map<PaginationData, List<ModerationRequest>> getRequestsByState(User sw360user, Pageable pageable,
boolean open) throws TException {
PaginationData pageData = new PaginationData();
Expand All @@ -77,4 +109,60 @@ public Map<PaginationData, List<ModerationRequest>> getRequestsByState(User sw36
pageData.setSortColumnNumber(-1);
return getThriftModerationClient().getRequestsByModeratorWithPagination(sw360user, pageData, open);
}

/**
* Set moderation state of moderation request to ACCEPTED
*
* @param request Request to accept
* @param moderatorComment Comments from moderator
* @param reviewer Reviewer
* @return Current status of request
* @throws TException Exception in case of error.
*/
public ModerationState acceptRequest(ModerationRequest request, String moderatorComment, @NotNull User reviewer)
throws TException {
getThriftModerationClient().acceptRequest(request, moderatorComment, reviewer.getEmail());
return ModerationState.APPROVED;
}

/**
* Set moderation state of the moderation request to REJECTED
*
* @param request Moderation request to reject
* @param moderatorComment Comment from moderator
* @param reviewer Reviewer
* @return Current status of the moderation request
* @throws TException Exception in case of error.
*/
public ModerationState rejectRequest(@NotNull ModerationRequest request, String moderatorComment, @NotNull User reviewer)
throws TException {
getThriftModerationClient().refuseRequest(request.getId(), moderatorComment, reviewer.getEmail());
return ModerationState.REJECTED;
}

public ModerationState removeMeFromModerators(@NotNull ModerationRequest request, @NotNull User reviewer) throws SW360Exception {
RemoveModeratorRequestStatus status = RemoveModeratorRequestStatus.FAILURE;
try {
status = getThriftModerationClient().removeUserFromAssignees(request.getId(), reviewer);
} catch (TException e) {
log.error("Error in Moderation ", e);
}
if (status == RemoveModeratorRequestStatus.LAST_MODERATOR) {
throw new InvalidParameterException("You are the last moderator for this request - you are not allowed to unsubscribe.");
} else if (status == RemoveModeratorRequestStatus.FAILURE) {
throw new SW360Exception("Failed to remove from moderator list.");
}
return ModerationState.PENDING;
}

public ModerationState assignRequest(@NotNull ModerationRequest request, @NotNull User reviewer)
throws TException {
if (!request.getModerators().contains(reviewer.getEmail())) {
throw new AccessDeniedException("User is not assigned as a moderator for the request.");
} else if (!isOpenModerationRequest(request)) {
throw new InvalidParameterException("Moderation request is not in open state.");
}
getThriftModerationClient().setInProgress(request.getId(), reviewer);
return ModerationState.INPROGRESS;
}
}
Loading

0 comments on commit 0e10fd3

Please sign in to comment.