Skip to content

Commit

Permalink
feat(rest): Add endpoint to handle updation of clearing requests.
Browse files Browse the repository at this point in the history
Signed-off-by: sameed.ahmad <sameed.ahmad@siemens-healthineers.com>
  • Loading branch information
sameed20 committed Jul 31, 2024
1 parent 612bce6 commit 95a7408
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 3 deletions.
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
15 changes: 15 additions & 0 deletions rest/resource-server/src/docs/asciidoc/clearingRequests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,18 @@ include::{snippets}/should_document_get_clearingrequests_by_state/curl-request.a
===== Example response
include::{snippets}/should_document_get_clearingrequests_by_state/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 @@ -17,20 +17,29 @@
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.Map;
import java.time.format.DateTimeFormatter;

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.CommonUtils;
import org.eclipse.sw360.datahandler.common.SW360Utils;
import org.eclipse.sw360.datahandler.permissions.PermissionUtils;
import org.eclipse.sw360.datahandler.thrift.ClearingRequestState;
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 All @@ -41,6 +50,7 @@
import org.springframework.hateoas.server.RepresentationModelProcessor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
Expand Down Expand Up @@ -70,6 +80,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 @@ -170,4 +183,96 @@ 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)) {
if (!SW360Utils.isValidDate(clearingRequest.getAgreedClearingDate(), 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 @@ -653,6 +653,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,11 +13,13 @@
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.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.formParameters;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
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 @@ -66,9 +68,13 @@ public class ClearingRequestSpecTest extends TestRestDocsSpecBase {
@MockBean
private Sw360ClearingRequestService clearingRequestServiceMock;

ClearingRequest clearingRequest;
ClearingRequest cr1;
ClearingRequest cr2;

@Before
public void before() throws TException, IOException {
ClearingRequest clearingRequest = new ClearingRequest();
clearingRequest = new ClearingRequest();
clearingRequest.setId("CR-101");
clearingRequest.setAgreedClearingDate("12-07-2020");
clearingRequest.setClearingState(ClearingRequestState.ACCEPTED);
Expand All @@ -82,7 +88,7 @@ public void before() throws TException, IOException {

Set<ClearingRequest> clearingrequests = new HashSet<>();
Set<ClearingRequest> clearingrequestsbystate = new HashSet<>();
ClearingRequest cr1 = new ClearingRequest();
cr1 = new ClearingRequest();
cr1.setId("CR-2");
cr1.setAgreedClearingDate("12-10-2020");
cr1.setClearingState(ClearingRequestState.ACCEPTED);
Expand All @@ -93,7 +99,7 @@ public void before() throws TException, IOException {
cr1.setRequestedClearingDate("10-08-2020");
cr1.setRequestingUser("test.user@sw60.org");

ClearingRequest cr2 = new ClearingRequest();
cr2 = new ClearingRequest();
cr2.setId("CR-3");
cr2.setAgreedClearingDate("24-10-2020");
cr2.setClearingState(ClearingRequestState.NEW);
Expand Down Expand Up @@ -244,4 +250,43 @@ public void should_document_get_clearingrequests_by_state() throws Exception {
)));
}

@Test
public void should_document_patch_clearingrequest() throws Exception {
ClearingRequest updateClearingRequest = new ClearingRequest()
.setClearingTeam("clearing.team@sw60.org")
.setClearingState(ClearingRequestState.SANITY_CHECK);

String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword);

mockMvc.perform(patch("/api/clearingrequest/" + clearingRequest.getId())
.contentType(MediaTypes.HAL_JSON)
.content(this.objectMapper.writeValueAsString(updateClearingRequest))
.header("Authorization", "Bearer " + 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")
)));
}
}

0 comments on commit 95a7408

Please sign in to comment.