From 558a347c0d6a378184d5e9095ac7c3c89c49645c Mon Sep 17 00:00:00 2001 From: afsahsyeda Date: Tue, 30 Jan 2024 15:44:21 +0530 Subject: [PATCH] feat(REST):Create clearing request for a project Signed-off-by: afsahsyeda --- .../db/ProjectDatabaseHandler.java | 2 + .../src/docs/asciidoc/projects.adoc | 14 +++ .../core/RestControllerHelper.java | 12 +++ .../project/ProjectController.java | 102 +++++++++++++++++- .../project/Sw360ProjectService.java | 9 +- .../restdocs/ProjectSpecTest.java | 48 +++++++++ 6 files changed, 184 insertions(+), 3 deletions(-) diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java index 0a4f60d7af..8ec7310d05 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java @@ -251,6 +251,8 @@ public AddDocumentRequestSummary createClearingRequest(ClearingRequest clearingR clearingRequest.setId(crId); updateProject(project, user); sendMailForNewClearing(project, projectUrl, clearingRequest, user); + //set requestSummary message that creation of CR was successful + requestSummary.setMessage("Clearing request created successfully"); return requestSummary.setRequestStatus(AddDocumentRequestStatus.SUCCESS).setId(project.getClearingRequestId()); } else { log.error("Failed to create clearing request for project: " + project.getId()); diff --git a/rest/resource-server/src/docs/asciidoc/projects.adoc b/rest/resource-server/src/docs/asciidoc/projects.adoc index fbb0356100..0797980b59 100644 --- a/rest/resource-server/src/docs/asciidoc/projects.adoc +++ b/rest/resource-server/src/docs/asciidoc/projects.adoc @@ -862,3 +862,17 @@ include::{snippets}/should_document_update_project_with_network/curl-request.ado ===== Example response include::{snippets}/should_document_update_project_with_network/http-response.adoc[] + +[[resources-clearing-request-create]] +==== Creating a clearing request + +A `POST` request is used to create the clearing request for a project. + +===== Request structure +include::{snippets}/should_document_create_clearing_request/request-fields.adoc[] + +===== Example request +include::{snippets}/should_document_create_clearing_request/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_create_clearing_request/http-response.adoc[] diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java index addae54551..5078576b88 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java @@ -315,6 +315,18 @@ public User getUserByEmail(String emailId) { return sw360User; } + public User getUserByEmailOrNull(String emailId) { + User sw360User; + try { + sw360User = userService.getUserByEmail(emailId); + } catch (RuntimeException e) { + LOGGER.debug("Could not get user object from backend with email: " + emailId); + return null; + } + return sw360User; + } + + public void addEmbeddedContributors(HalResource halResource, Set contributors) { for (String contributorEmail : contributors) { User sw360User = getUserByEmail(contributorEmail); diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java index 427e2eda56..1bbb4d9c59 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java @@ -47,6 +47,10 @@ 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.AddDocumentRequestStatus; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; +import org.eclipse.sw360.datahandler.thrift.ClearingRequestState; +import org.eclipse.sw360.datahandler.thrift.ClearingRequestType; import org.eclipse.sw360.datahandler.thrift.MainlineState; import org.eclipse.sw360.datahandler.thrift.ProjectReleaseRelationship; import org.eclipse.sw360.datahandler.thrift.ReleaseRelationship; @@ -71,6 +75,7 @@ import org.eclipse.sw360.datahandler.thrift.licenseinfo.OutputFormatInfo; import org.eclipse.sw360.datahandler.thrift.licenseinfo.OutputFormatVariant; import org.eclipse.sw360.datahandler.thrift.licenses.License; +import org.eclipse.sw360.datahandler.thrift.projects.ClearingRequest; import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.projects.ProjectClearingState; import org.eclipse.sw360.datahandler.thrift.projects.ProjectLink; @@ -126,6 +131,9 @@ import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -1458,8 +1466,8 @@ private HalResource attachmentUsageReleases(Project sw360Project, User sw360User Map releaseIdToUsages = sw360Project.getReleaseIdToUsage(); Map projectMap = oMapper.convertValue(sw360Project, Map.class); Map resultMap = new HashMap<>(); - resultMap.put("linkedProjects", (Map) projectMap.get("linkedProjects")); - resultMap.put("releaseIdToUsage", (Map) projectMap.get("releaseIdToUsage")); + resultMap.put("linkedProjects", projectMap.get("linkedProjects")); + resultMap.put("releaseIdToUsage", projectMap.get("releaseIdToUsage")); Map releaseIdToUsage = (Map) resultMap.get("releaseIdToUsage"); final ImmutableSet fieldsToRemove = ImmutableSet.of("setCreatedBy", "setCreatedOn", "setComment", "setReleaseRelation", "setMainlineState"); @@ -1914,6 +1922,13 @@ private Project convertToProject(Map requestBody) { return mapper.convertValue(requestBody, Project.class); } + private ClearingRequest convertToClearingRequest(Map requestBody) { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.registerModule(sw360Module); + return mapper.convertValue(requestBody, ClearingRequest.class); + } + public static TSerializer getJsonSerializer() { try { return new TSerializer(new TSimpleJSONProtocol.Factory()); @@ -2176,4 +2191,87 @@ private void updateReleaseNodeData(ReleaseNode releaseNode, ProjectOperation ope } } } + + private ClearingRequest convertToClearingRequest(Map requestBody, String projectId) { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + ClearingRequest clearingRequest = mapper.convertValue(requestBody, ClearingRequest.class); + return clearingRequest; + } + + /** + * Creates a clearing request for a project. + * + * @param id The project ID. + * @param reqBodyMap The clearing request + * @return The response entity containing the result of the operation. + * @throws TException If an error occurs during the operation. + */ + @PreAuthorize("hasAuthority('WRITE')") + @Operation( + summary = "Create a clearing request for a project.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/clearingRequest", method = RequestMethod.POST) + public ResponseEntity createClearingRequest( + @Parameter(description = "Project ID", example = "376576") + @PathVariable("id") String id, + @Parameter(description = "Clearing request", + schema = @Schema(implementation = ClearingRequest.class)) + @RequestBody Map reqBodyMap + ) throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Project sw360Project = projectService.getProjectForUserById(id, sw360User); + ClearingRequest clearingRequest = convertToClearingRequest(reqBodyMap, id); + clearingRequest.setProjectId(id); + clearingRequest.setRequestingUser(sw360User.getEmail()); + clearingRequest.setClearingState(ClearingRequestState.NEW); + + if (clearingRequest.getRequestingUserComment() == null) { + clearingRequest.setRequestingUserComment(""); + } + if (clearingRequest.getClearingType() == null) { + return new ResponseEntity("clearingType is a mandatory field. Possible values are:" + + Arrays.asList(ClearingRequestType.values()), HttpStatus.BAD_REQUEST); + } + //Generated by AI(GithubCopilot) + if (clearingRequest.getRequestedClearingDate() != null) { + try { + LocalDate.parse(clearingRequest.getRequestedClearingDate(), DateTimeFormatter.ofPattern("yyyy-MM-dd")); + LocalDate today = LocalDate.now(); + LocalDate requestedClearingDate = LocalDate.parse(clearingRequest.getRequestedClearingDate(), + DateTimeFormatter.ofPattern("yyyy-MM-dd")); + if (requestedClearingDate.isBefore(today)) { + return new ResponseEntity("requestedClearingDate must be a current or future date", + HttpStatus.BAD_REQUEST); + } + } catch (DateTimeParseException e) { + return new ResponseEntity("requestedClearingDate must be in yyyy-MM-dd format", + HttpStatus.BAD_REQUEST); + } + } + if (clearingRequest.getClearingTeam() != null) { + User user = restControllerHelper.getUserByEmailOrNull(clearingRequest.getClearingTeam()); + if (user == null) { + return new ResponseEntity("clearingTeam is not a valid user", HttpStatus.BAD_REQUEST); + } + } + + AddDocumentRequestSummary addDocumentRequestSummary = projectService.createClearingRequest(clearingRequest, sw360User); + + if (addDocumentRequestSummary.getRequestStatus() == AddDocumentRequestStatus.DUPLICATE) { + return new ResponseEntity(addDocumentRequestSummary.getMessage(), HttpStatus.CONFLICT); + } else if (addDocumentRequestSummary.getRequestStatus() == AddDocumentRequestStatus.FAILURE) { + return new ResponseEntity(addDocumentRequestSummary.getMessage(), HttpStatus.BAD_REQUEST); + } + clearingRequest.setId(addDocumentRequestSummary.getId()); + + URI location = ServletUriComponentsBuilder + .fromCurrentRequest().path("/{id}") + .buildAndExpand(clearingRequest.getId()).toUri(); + + HalResource halResource = new HalResource<>(clearingRequest); + + return ResponseEntity.created(location).body(halResource); + } } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/Sw360ProjectService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/Sw360ProjectService.java index 78ad7207a7..4fddd8e0de 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/Sw360ProjectService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/Sw360ProjectService.java @@ -22,6 +22,7 @@ import org.eclipse.sw360.datahandler.common.SW360Utils; import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestStatus; import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; +import org.eclipse.sw360.datahandler.thrift.projects.ClearingRequest; import org.eclipse.sw360.datahandler.thrift.PaginationData; import org.eclipse.sw360.datahandler.thrift.ProjectReleaseRelationship; import org.eclipse.sw360.datahandler.thrift.ReleaseRelationship; @@ -223,7 +224,7 @@ public void deleteAllProjects(User sw360User) throws TException { } } } - + public Project getClearingInfo(Project sw360Project, User sw360User) throws TException { ProjectService.Iface sw360ProjectClient = getThriftProjectClient(); return sw360ProjectClient.fillClearingStateSummaryIncludingSubprojectsForSingleProject(sw360Project, sw360User); @@ -566,4 +567,10 @@ public int countProjectsByReleaseIds(Set releaseIds) { return 0; } } + + public AddDocumentRequestSummary createClearingRequest(ClearingRequest clearingRequest, User sw360User) throws TException { + ProjectService.Iface sw360ProjectClient = getThriftProjectClient(); + return sw360ProjectClient.createClearingRequest(clearingRequest, sw360User, " "); + } + } diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java index dc22f69662..07dbeb55bb 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java @@ -15,12 +15,16 @@ import org.apache.thrift.TException; import org.eclipse.sw360.datahandler.common.SW360Constants; import org.eclipse.sw360.datahandler.common.SW360Utils; +import org.eclipse.sw360.datahandler.thrift.ClearingRequestState; +import org.eclipse.sw360.datahandler.thrift.ClearingRequestType; import org.eclipse.sw360.datahandler.thrift.CycloneDxComponentType; import org.eclipse.sw360.datahandler.thrift.MainlineState; import org.eclipse.sw360.datahandler.thrift.ProjectReleaseRelationship; import org.eclipse.sw360.datahandler.thrift.ReleaseRelationship; import org.eclipse.sw360.datahandler.thrift.RequestStatus; import org.eclipse.sw360.datahandler.thrift.RequestSummary; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestStatus; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; import org.eclipse.sw360.datahandler.thrift.Source; import org.eclipse.sw360.datahandler.thrift.Visibility; import org.eclipse.sw360.datahandler.thrift.attachments.Attachment; @@ -41,6 +45,7 @@ import org.eclipse.sw360.datahandler.thrift.licenseinfo.OutputFormatVariant; import org.eclipse.sw360.datahandler.thrift.packages.Package; import org.eclipse.sw360.datahandler.thrift.packages.PackageManager; +import org.eclipse.sw360.datahandler.thrift.projects.ClearingRequest; import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.projects.ProjectClearingState; import org.eclipse.sw360.datahandler.thrift.projects.ProjectProjectRelationship; @@ -446,6 +451,12 @@ public void before() throws TException, IOException { RequestSummary requestSummaryForCycloneDX = new RequestSummary(); requestSummaryForCycloneDX.setMessage("{\"projectId\":\"" + cycloneDXProject.getId() + "\"}"); + AddDocumentRequestSummary requestSummaryForCR = new AddDocumentRequestSummary(); + requestSummaryForCR.setMessage("Clearing request created successfully"); + requestSummaryForCR.setRequestStatus(AddDocumentRequestStatus.SUCCESS); + requestSummaryForCR.setId("CR-1"); + + given(this.projectServiceMock.createClearingRequest(any(),any())).willReturn(requestSummaryForCR); given(this.projectServiceMock.importSPDX(any(),any())).willReturn(requestSummaryForSPDX); given(this.projectServiceMock.importCycloneDX(any(),any(),any())).willReturn(requestSummaryForCycloneDX); given(this.sw360ReportServiceMock.getProjectBuffer(any(),anyBoolean())).willReturn(ByteBuffer.allocate(10000)); @@ -583,6 +594,8 @@ public void before() throws TException, IOException { new User("admin@sw360.org", "sw360").setId("123456789")); given(this.userServiceMock.getUserByEmail("jane@sw360.org")).willReturn( new User("jane@sw360.org", "sw360").setId("209582812")); + given(this.userServiceMock.getUserByEmail("clearingTeam@sw360.org")).willReturn( + new User("clearingTeam@sw360.org", "sw360").setId("2012312")); OutputFormatInfo outputFormatInfo = new OutputFormatInfo(); outputFormatInfo.setFileExtension("html"); given(this.licenseInfoMockService.getOutputFormatInfoForGeneratorClass(any())) @@ -1467,6 +1480,41 @@ public void should_document_get_project_attachment() throws Exception { .andDo(this.documentationHandler.document()); } + @Test + public void should_document_create_clearing_request() throws Exception { + Map cr = new HashMap<>(); + cr.put("clearingType", ClearingRequestType.HIGH.toString()); + cr.put("clearingTeam", "clearingTeam@sw360.org"); + cr.put("requestedClearingDate", "2024-02-24"); + cr.put("requestingUserComment", "New clearing"); + + String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword); + this.mockMvc.perform(post("/api/projects/" + project.getId() + "/clearingRequest") + .contentType(MediaTypes.HAL_JSON) + .content(this.objectMapper.writeValueAsString(cr)) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isCreated()) + .andDo(this.documentationHandler.document( + links(linkWithRel("self").description("The <>")), + requestFields(fieldWithPath("clearingTeam").description("Email of the clearing team."), + fieldWithPath("requestedClearingDate").description( + "Requested clearing date of the project. It should be a current or future date in the format yyyy-MM-dd."), + fieldWithPath("clearingType").description("Clearing type of the project. Possible values are: " + Arrays.asList(ClearingRequestType.values()).toString()), + fieldWithPath("requestingUserComment").description("Requesting user comment on the clearing of the project.") + ), + responseFields(fieldWithPath("id").description("Clearing request id."), + fieldWithPath("projectId").description("Project id associated with clearing request."), + fieldWithPath("clearingState").description("Clearing state of the project."), + fieldWithPath("clearingTeam").description("Clearing team of the project."), + fieldWithPath("requestedClearingDate") + .description("Requested clearing date of the project."), + fieldWithPath("clearingType").description("Clearing type of the project."), + fieldWithPath("requestingUser").description("User requesting the clearing of the project."), + fieldWithPath("requestingUserComment").description("Requesting user comment on the clearing of the project."), + subsectionWithPath("_links").description("<> to other resources") + ))); + } + @Test public void should_document_create_project() throws Exception { Map project = new HashMap<>();