Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rest): Add endpoint to handle updation of clearing requests. #2480

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,18 @@ public static boolean isValidDate(String date, DateTimeFormatter format, Long gr
}
}


public static boolean isValidDate(String currRequestedClearingDate, String newRequestedClearingDate, DateTimeFormatter format) {
try {
LocalDate currLocalDate = LocalDate.parse(currRequestedClearingDate, format);
LocalDate requestedLocalDate = LocalDate.parse(newRequestedClearingDate, format);

return requestedLocalDate.isAfter(currLocalDate);
} catch (DateTimeParseException e) {
return false;
}
}

public static String printFullname(Release release) {
if (release == null || isNullOrEmpty(release.getName())) {
return "New Release";
Expand Down
16 changes: 16 additions & 0 deletions rest/resource-server/src/docs/asciidoc/clearingRequests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,19 @@ include::{snippets}/should_document_get_comments_by_clearing_request_id/curl-req

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


[[resources-clearingRequest-update]]
==== Update a clearingRequest

A `PATCH` request is used to update an existing clearingRequest

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

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

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

Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,35 @@
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.Map;
import java.time.format.DateTimeFormatter;

import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.servlet.http.HttpServletRequest;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import org.apache.thrift.TException;
import org.eclipse.sw360.datahandler.common.SW360Constants;
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.ClearingRequestState;
import org.eclipse.sw360.datahandler.thrift.Comment;
import org.eclipse.sw360.datahandler.thrift.components.Release;
import org.eclipse.sw360.datahandler.common.CommonUtils;
import org.eclipse.sw360.datahandler.common.SW360Utils;
import org.eclipse.sw360.datahandler.permissions.PermissionUtils;
import org.eclipse.sw360.datahandler.thrift.RequestStatus;
import org.eclipse.sw360.datahandler.thrift.projects.ClearingRequest;
import org.eclipse.sw360.datahandler.thrift.projects.Project;
import org.eclipse.sw360.datahandler.thrift.users.User;
import org.eclipse.sw360.rest.resourceserver.core.HalResource;
import org.eclipse.sw360.rest.resourceserver.core.RestControllerHelper;
import org.eclipse.sw360.rest.resourceserver.moderationrequest.Sw360ModerationRequestService;
import org.eclipse.sw360.rest.resourceserver.project.Sw360ProjectService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
Expand Down Expand Up @@ -84,6 +90,9 @@ public class ClearingRequestController implements RepresentationModelProcessor<R
@NonNull
private final com.fasterxml.jackson.databind.Module sw360Module;

@NonNull
private final Sw360ModerationRequestService moderationRequestService;


@Operation(
summary = "Get clearing request by id.",
Expand Down Expand Up @@ -237,4 +246,98 @@ public RepositoryLinksResource process(RepositoryLinksResource resource) {
resource.add(linkTo(ClearingRequestController.class).slash("api" + CLEARING_REQUEST_URL).withRel("clearingRequests"));
return resource;
}

@PreAuthorize("hasAuthority('WRITE')")
@Operation(
summary = "Update clearing request",
description = "Update a clearing request by id.",
tags = {"ClearingRequest"}
)
@RequestMapping(value = CLEARING_REQUEST_URL + "/{id}", method = RequestMethod.PATCH)
public ResponseEntity<?> patchClearingRequest(
@Parameter(description = "id of the clearing request")
@PathVariable("id") String id,
@Parameter(description = "The updated fields of clearing request.",
schema = @Schema(implementation = ClearingRequest.class))
@RequestBody Map<String, Object> reqBodyMap,
HttpServletRequest request
) throws TException {

try{
User sw360User = restControllerHelper.getSw360UserFromAuthentication();

ClearingRequest clearingRequest = sw360ClearingRequestService.getClearingRequestById(id, sw360User);
String projectId = clearingRequest.getProjectId();

ClearingRequest updatedClearingRequest = convertToClearingRequest(reqBodyMap);
updatedClearingRequest.setId(clearingRequest.getId());
updatedClearingRequest.setProjectId(clearingRequest.getProjectId());
updatedClearingRequest.setTimestamp(clearingRequest.getTimestamp());
updatedClearingRequest.setProjectBU(clearingRequest.getProjectBU());

if(CommonUtils.isNotNullEmptyOrWhitespace(updatedClearingRequest.getRequestingUser()) && PermissionUtils.isAdmin(sw360User)){
User updatedRequestingUser = restControllerHelper.getUserByEmailOrNull(updatedClearingRequest.getRequestingUser());
if (updatedRequestingUser == null) {
return new ResponseEntity<String>("Requesting user is not a valid", HttpStatus.BAD_REQUEST);
}else{
updatedClearingRequest.setRequestingUser(updatedRequestingUser.getEmail());
}
}

if (CommonUtils.isNotNullEmptyOrWhitespace(updatedClearingRequest.getRequestedClearingDate())) {
if (!clearingRequest.getRequestingUser().equals(sw360User.getEmail())) {
return new ResponseEntity<String>("Requested Clearing Date can only be updated by the requesting user", HttpStatus.FORBIDDEN);
}
if (!SW360Utils.isValidDate(clearingRequest.getRequestedClearingDate(), updatedClearingRequest.getRequestedClearingDate(), DateTimeFormatter.ISO_LOCAL_DATE)) {
return new ResponseEntity<String>("Invalid clearing date requested", HttpStatus.BAD_REQUEST);
}
}

if ((updatedClearingRequest.getClearingType() != null || updatedClearingRequest.getPriority() != null ) &&
!(PermissionUtils.isClearingAdmin(sw360User) || PermissionUtils.isAdmin(sw360User))) {
return new ResponseEntity<String>("Update not allowed for field ClearingType, Priority with user role", HttpStatus.FORBIDDEN);
}

if (updatedClearingRequest.getClearingTeam() != null) {
User updatedClearingTeam = restControllerHelper.getUserByEmailOrNull(updatedClearingRequest.getClearingTeam());
if (updatedClearingTeam == null) {
return new ResponseEntity<String>("ClearingTeam is not a valid user", HttpStatus.BAD_REQUEST);
}
}


if (updatedClearingRequest.getAgreedClearingDate() != null) {
if (PermissionUtils.isClearingAdmin(sw360User) || PermissionUtils.isAdmin(sw360User)) {
String currentAgreedClearingDate = CommonUtils.isNotNullEmptyOrWhitespace(clearingRequest.getAgreedClearingDate()) ? clearingRequest.getAgreedClearingDate() : "1980-01-01";
if (!SW360Utils.isValidDate(currentAgreedClearingDate, updatedClearingRequest.getAgreedClearingDate(), DateTimeFormatter.ISO_LOCAL_DATE)) {
return new ResponseEntity<String>("Invalid agreed clearing date requested", HttpStatus.BAD_REQUEST);
}
} else {
return new ResponseEntity<String>("Update not allowed for field Agreed Clearing Date with user role", HttpStatus.FORBIDDEN);
}
}

clearingRequest = this.restControllerHelper.updateClearingRequest(clearingRequest, updatedClearingRequest);

String baseURL = restControllerHelper.getBaseUrl(request);
RequestStatus updateCRStatus = sw360ClearingRequestService.updateClearingRequest(clearingRequest, sw360User, baseURL, projectId);
HalResource<ClearingRequest> halClearingRequest = createHalClearingRequestWithAllDetails(clearingRequest, sw360User);

if (updateCRStatus == RequestStatus.ACCESS_DENIED) {
return new ResponseEntity<String>("Edit action is not allowed for this user role", HttpStatus.FORBIDDEN);
}

return new ResponseEntity<>(halClearingRequest, HttpStatus.OK);
}catch (Exception e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
}

private ClearingRequest convertToClearingRequest(Map<String, Object> requestBody){
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(sw360Module);

return mapper.convertValue(requestBody, ClearingRequest.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransportException;
import org.eclipse.sw360.datahandler.thrift.ClearingRequestState;
import org.eclipse.sw360.datahandler.thrift.RequestStatus;
import org.eclipse.sw360.datahandler.thrift.SW360Exception;
import org.eclipse.sw360.datahandler.thrift.moderation.ModerationService;
import org.eclipse.sw360.datahandler.thrift.projects.ClearingRequest;
Expand Down Expand Up @@ -88,4 +89,19 @@ public Set<ClearingRequest> getMyClearingRequests(User sw360User, ClearingReques
return clearingrequests;
}

public RequestStatus updateClearingRequest(ClearingRequest clearingRequest, User sw360User, String baseUrl, String projectId) throws TException {
ModerationService.Iface sw360ModerationClient = getThriftModerationClient();

String projectUrl = baseUrl + "/projects/-/project/detail/" + projectId;

RequestStatus requestStatus;

requestStatus = sw360ModerationClient.updateClearingRequest(clearingRequest, sw360User, projectUrl);

if (requestStatus == RequestStatus.FAILURE) {
throw new RuntimeException("Clearing Request with id '" + clearingRequest.getId() + " cannot be updated.");
}
return requestStatus;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,16 @@ public Package updatePackage(Package packageToUpdate, Package requestBodyPackage
return packageToUpdate;
}

public ClearingRequest updateClearingRequest(ClearingRequest crToUpdate, ClearingRequest requestBodyCR) {
for(ClearingRequest._Fields field: ClearingRequest._Fields.values()) {
Object fieldValue = requestBodyCR.getFieldValue(field);
if (fieldValue != null) {
crToUpdate.setFieldValue(field, fieldValue);
}
}
return crToUpdate;
}

public User updateUserProfile(User userToUpdate, Map<String, Object> requestBodyUser, ImmutableSet<User._Fields> setOfUserProfileFields) {
for (User._Fields field : setOfUserProfileFields) {
Object fieldValue = requestBodyUser.get(field.getFieldName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.io.IOException;
Expand Down Expand Up @@ -61,12 +63,14 @@ public class ClearingRequestSpecTest extends TestRestDocsSpecBase {

@MockBean
private Sw360ClearingRequestService clearingRequestServiceMock;

ClearingRequest clearingRequest = new ClearingRequest();
ClearingRequest cr1 = new ClearingRequest();
ClearingRequest cr2 = new ClearingRequest();
List<Comment> comments = new ArrayList<Comment>();
@Before
public void before() throws TException, IOException {

clearingRequest.setId("CR-101");
clearingRequest.setAgreedClearingDate("12-07-2020");
clearingRequest.setClearingState(ClearingRequestState.ACCEPTED);
Expand Down Expand Up @@ -276,4 +280,43 @@ public void should_document_get_comments_by_clearing_request_id() throws Excepti
)));
}

@Test
public void should_document_patch_clearingrequest() throws Exception {
ClearingRequest updateClearingRequest = new ClearingRequest()
.setClearingTeam("clearing.team@sw60.org")
sameed20 marked this conversation as resolved.
Show resolved Hide resolved
.setClearingState(ClearingRequestState.SANITY_CHECK);

String accessToken = TestHelper.generateAuthHeader(testUserId, testUserPassword);

mockMvc.perform(patch("/api/clearingrequest/" + clearingRequest.getId())
.contentType(MediaTypes.HAL_JSON)
.content(this.objectMapper.writeValueAsString(updateClearingRequest))
.header("Authorization", accessToken)
.accept(MediaTypes.HAL_JSON))
.andExpect(status().isOk())
.andDo(this.documentationHandler.document(
requestFields(
fieldWithPath("clearingTeam").description("The clearing team email id."),
fieldWithPath("clearingState").description("The clearing state of request")
),
responseFields(
fieldWithPath("id").description("The id of the clearing request"),
fieldWithPath("agreedClearingDate").description("The agreed clearing date of the request, on / before which CR should be cleared"),
fieldWithPath("clearingState").description("The clearing state of the request. Possible values are: " + Arrays.asList(ClearingRequestState.values())),
fieldWithPath("clearingTeam").description("The clearing team email id."),
fieldWithPath("projectBU").description("The Business Unit / Group of the Project, for which the clearing request is created"),
fieldWithPath("projectId").description("The id of the Project, for which the clearing request is created"),
fieldWithPath("requestedClearingDate").description("The requested clearing date of releases"),
fieldWithPath("requestingUser").description("The user who created the clearing request"),
fieldWithPath("requestingUserComment").description("The comment from the requesting user"),
fieldWithPath("priority").description("The priority of the clearing request. Possible values are: " + Arrays.asList(ClearingRequestPriority.values())),
subsectionWithPath("comments").description("The clearing request comments"),
subsectionWithPath("comments[].text").description("The clearing request comment text"),
subsectionWithPath("comments[].commentedBy").description("The user who added the comment on the clearing request"),
subsectionWithPath("_embedded.sw360:project").description("The Project associated with the ClearingRequest"),
subsectionWithPath("_embedded.clearingTeam").description("Clearing team user detail"),
subsectionWithPath("_embedded.requestingUser").description("Requesting user detail"),
subsectionWithPath("_links").description("Links to other resources")
)));
}
}