diff --git a/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/ModerationHandler.java b/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/ModerationHandler.java index 775ae5a7f8..735b4c3908 100644 --- a/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/ModerationHandler.java +++ b/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/ModerationHandler.java @@ -269,6 +269,13 @@ public List getRequestsByRequestingUser(User user) throws TEx return handler.getRequestsByRequestingUser(user.getEmail()); } + @Override + public List getRequestsByRequestingUserWithPagination(User user, PaginationData pageData) throws TException { + assertUser(user); + + return handler.getRequestsByRequestingUserWithPagination(user.getEmail(), pageData); + } + @Override public ClearingRequest getClearingRequestByProjectId(String projectId, User user) throws TException { assertId(projectId); @@ -372,6 +379,12 @@ public Map getCountByModerationState(User user) throws TException return handler.getCountByModerationState(user.getEmail()); } + @Override + public Map getCountByRequester(User user) throws TException { + assertUser(user); + return handler.getCountByRequester(user.getEmail()); + } + @Override public Map> getRequestsByModeratorWithPagination(User user, PaginationData pageData, boolean open) throws TException { diff --git a/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/db/ModerationDatabaseHandler.java b/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/db/ModerationDatabaseHandler.java index db4e0f5f8f..1e9297988e 100644 --- a/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/db/ModerationDatabaseHandler.java +++ b/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/db/ModerationDatabaseHandler.java @@ -148,6 +148,10 @@ public List getRequestsByRequestingUser(String user) { return repository.getRequestsByRequestingUser(user); } + public List getRequestsByRequestingUserWithPagination(String user, PaginationData pageData) { + return repository.getRequestsByRequestingUserWithPagination(user, pageData); + } + public ClearingRequest getClearingRequestByProjectId(String projectId, User user) throws SW360Exception { projectDatabaseHandler.getProjectById(projectId, user); // check if user have READ access to project. return clearingRequestRepository.getClearingRequestByProjectId(projectId); @@ -890,6 +894,10 @@ public Map getCountByModerationState(String moderator) { return repository.getCountByModerationState(moderator); } + public Map getCountByRequester(String moderator) { + return repository.getCountByRequester(moderator); + } + public Set getRequestingUserDepts() { return repository.getRequestingUserDepts(); } diff --git a/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/db/ModerationRequestRepository.java b/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/db/ModerationRequestRepository.java index 59f6e9408f..f82135ce2a 100644 --- a/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/db/ModerationRequestRepository.java +++ b/backend/src/src-moderation/src/main/java/org/eclipse/sw360/moderation/db/ModerationRequestRepository.java @@ -75,12 +75,19 @@ public class ModerationRequestRepository extends SummaryAwareRepository views = new HashMap(); views.put("all", createMapReduce(ALL, null)); views.put("byRequestingUsersDeptView", createMapReduce(REQUESTING_USERS_VIEW, null)); views.put("countByModerationState", createMapReduce(COUNTBYMODERATIONSTATE, "_count")); + views.put("countByRequester", createMapReduce(COUNTBYREQUESTER, "_count")); initStandardDesignDocument(views, db); createIndex("byModerators", new String[] {"moderators"}, db); createIndex("byDate", new String[] {"timestamp"}, db); @@ -260,6 +267,29 @@ public List getRequestsByRequestingUser(String user) { return makeSummaryFromFullDocs(SummaryType.SHORT, mrs); } + public List getRequestsByRequestingUserWithPagination(String user, PaginationData pageData) { + final int rowsPerPage = pageData.getRowsPerPage(); + final boolean ascending = pageData.isAscending(); + final int skip = pageData.getDisplayStart(); + final Selector typeSelector = eq("type", "moderation"); + final Selector filterByModeratorSelector = eq("requestingUser", user); + final Selector finalSelector = and(typeSelector, filterByModeratorSelector); + QueryBuilder qb = new QueryBuilder(finalSelector); + qb.limit(rowsPerPage); + qb.skip(skip); + qb.useIndex("byUsers"); + qb = ascending ? qb.sort(Sort.asc("timestamp")) : qb.sort(Sort.desc("timestamp")); + + List modReqs = Lists.newArrayList(); + try { + QueryResult queryResult = getConnector().getQueryResult(qb.build(), ModerationRequest.class); + modReqs = queryResult.getDocs(); + } catch (Exception e) { + log.error("Error getting moderation requests", e); + } + return modReqs; + } + public Map getCountByModerationState(String moderator) { Map countByModerationState = Maps.newHashMap(); List keys = prepareKeys(moderator, true); @@ -282,6 +312,27 @@ public Map getCountByModerationState(String moderator) { return countByModerationState; } + public Map getCountByRequester(String user) { + Map countByModerationState = Maps.newHashMap(); + + List keys = prepareKeys(user, true); + ViewRequest countReq = getConnector() + .createQuery(ModerationRequest.class, "countByRequester").newRequest(Key.Type.COMPLEX, Long.class) + .startKey(keys.get(0)).endKey(keys.get(1)).group(true).groupLevel(2).reduce(true).build(); + try { + ViewResponse response = countReq.getResponse(); + if (null != response) { + countByModerationState = response.getRows().stream().collect(Collectors.toMap(key -> { + String json = key.getKey().toJson(); + return json.replaceAll("[\\[\\]\"]", ""); + }, ViewResponse.Row::getValue)); + } + } catch (IOException e) { + log.error("Error getting count of moderation requests based on moderation state", e); + } + return countByModerationState; + } + public Set getRequestingUserDepts() { Set requestingUserDepts = Sets.newHashSet(); ViewRequest query = getConnector() diff --git a/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/resourcelists/ResourceComparatorGenerator.java b/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/resourcelists/ResourceComparatorGenerator.java index e4adefa678..9387d81779 100644 --- a/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/resourcelists/ResourceComparatorGenerator.java +++ b/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/resourcelists/ResourceComparatorGenerator.java @@ -28,11 +28,11 @@ import org.eclipse.sw360.datahandler.thrift.moderation.ModerationRequest; import org.eclipse.sw360.datahandler.thrift.packages.Package; import org.eclipse.sw360.datahandler.thrift.projects.Project; +import org.eclipse.sw360.datahandler.thrift.search.SearchResult; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.datahandler.thrift.vendors.Vendor; -import org.eclipse.sw360.datahandler.thrift.search.SearchResult; -import org.eclipse.sw360.datahandler.thrift.vulnerabilities.VulnerabilityDTO; import org.eclipse.sw360.datahandler.thrift.vulnerabilities.Vulnerability; +import org.eclipse.sw360.datahandler.thrift.vulnerabilities.VulnerabilityDTO; public class ResourceComparatorGenerator { @@ -166,6 +166,10 @@ private static Map> gen Comparator.comparing(ModerationRequest::getRequestingUser, Comparator.nullsFirst(String.CASE_INSENSITIVE_ORDER))); moderationRequestMap.put(ModerationRequest._Fields.DOCUMENT_TYPE, Comparator.comparing(c -> Optional.ofNullable(c.getDocumentType()).map(Object::toString).orElse(null), Comparator.nullsFirst(String.CASE_INSENSITIVE_ORDER))); + moderationRequestMap.put(ModerationRequest._Fields.DOCUMENT_NAME, + Comparator.comparing(ModerationRequest::getDocumentName, Comparator.nullsFirst(String.CASE_INSENSITIVE_ORDER))); + moderationRequestMap.put(ModerationRequest._Fields.MODERATION_STATE, + Comparator.comparing(c -> Optional.ofNullable(c.getModerationState()).map(Object::toString).orElse(null), Comparator.nullsFirst(String.CASE_INSENSITIVE_ORDER))); return Collections.unmodifiableMap(moderationRequestMap); } @@ -309,6 +313,15 @@ public Comparator generateComparator(String type, List properties) th } } return generateReleaseEccComparatorWithFields(type, releaseFields, eccInfoFields); + case SW360Constants.TYPE_MODERATION: + List modFields = new ArrayList<>(); + for (String property : properties) { + ModerationRequest._Fields field = ModerationRequest._Fields.findByName(property); + if (field != null) { + modFields.add(field); + } + } + return generateModerationRequestComparatorWithFields(type, modFields); default: throw new ResourceClassNotFoundException("No comparator for resource class with name " + type); } @@ -414,6 +427,16 @@ public Comparator generateVulComparatorWithFields(String type, List generateModerationRequestComparatorWithFields( + String type, List fields) throws ResourceClassNotFoundException { + switch (type) { + case SW360Constants.TYPE_MODERATION: + return (Comparator) moderationComparator(fields); + default: + throw new ResourceClassNotFoundException("No comparator for resource class with name " + type); + } + } + private Comparator componentComparator(List fields) { Comparator comparator = Comparator.comparing(x -> true); @@ -560,6 +583,18 @@ private Comparator vulnComparator(List fie return comparator; } + private Comparator moderationComparator(List fields) { + Comparator comparator = Comparator.comparing(x -> true); + for (ModerationRequest._Fields field : fields) { + Comparator fieldComparator = moderationRequestMap.get(field); + if (fieldComparator != null) { + comparator = comparator.thenComparing(fieldComparator); + } + } + comparator = comparator.thenComparing(defaultModerationRequestComparator()); + return comparator; + } + private Comparator defaultComponentComparator() { return componentMap.get(Component._Fields.NAME); } diff --git a/libraries/datahandler/src/main/thrift/moderation.thrift b/libraries/datahandler/src/main/thrift/moderation.thrift index 36c6352088..e6c6c02094 100644 --- a/libraries/datahandler/src/main/thrift/moderation.thrift +++ b/libraries/datahandler/src/main/thrift/moderation.thrift @@ -269,6 +269,11 @@ service ModerationService { **/ list getRequestsByRequestingUser(1: User user); + /** + * get list of moderation requests where user is requesting user, paginated + **/ + list getRequestsByRequestingUserWithPagination(1: User user, 2: PaginationData pageData); + /** * delete moderation request specified by id if user is requesting user of moderation request **/ @@ -334,6 +339,11 @@ service ModerationService { **/ map getCountByModerationState(1: User user); + /** + * get count of moderation requests by a requester + **/ + map getCountByRequester(1: User user); + /** * get requesting users departments **/ diff --git a/rest/resource-server/src/docs/asciidoc/moderationRequests.adoc b/rest/resource-server/src/docs/asciidoc/moderationRequests.adoc index 7e31c39a74..c08d7123a0 100644 --- a/rest/resource-server/src/docs/asciidoc/moderationRequests.adoc +++ b/rest/resource-server/src/docs/asciidoc/moderationRequests.adoc @@ -129,3 +129,20 @@ include::{snippets}/should_document_get_moderationrequests_assign/curl-request.a ===== Example response include::{snippets}/should_document_get_moderationrequests_assign/http-response.adoc[] + +[[resources-moderationRequest-submission]] +==== Get Submitted Moderation Requests + +A `GET` will pull <> which are created by the requesting user. + +===== Request parameter +include::{snippets}/should_document_get_moderationrequests_submission/request-parameters.adoc[] + +===== Response structure +include::{snippets}/should_document_get_moderationrequests_submission/response-fields.adoc[] + +===== Example request +include::{snippets}/should_document_get_moderationrequests_submission/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_get_moderationrequests_submission/http-response.adoc[] diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/ModerationRequestController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/ModerationRequestController.java index 87621c8420..090b4884d5 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/ModerationRequestController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/ModerationRequestController.java @@ -101,22 +101,14 @@ public ResponseEntity> getModerationRequests( ) throws TException, ResourceClassNotFoundException, URISyntaxException { User sw360User = restControllerHelper.getSw360UserFromAuthentication(); List moderationRequests = sw360ModerationRequestService.getRequestsByModerator(sw360User, pageable); - int totalCount = (int) sw360ModerationRequestService.getTotalCountOfRequests(sw360User); - PaginationResult paginationResult = restControllerHelper.paginationResultFromPaginatedList(request, - pageable, moderationRequests, SW360Constants.TYPE_MODERATION, totalCount); - List> moderationRequestResources = new ArrayList<>(); - paginationResult.getResources().forEach(m -> addModerationRequest(m, allDetails, moderationRequestResources)); - - CollectionModel resources; - if (moderationRequestResources.isEmpty()) { - resources = restControllerHelper.emptyPageResource(ModerationRequest.class, paginationResult); - } else { - resources = restControllerHelper.generatePagesResource(paginationResult, moderationRequestResources); - } + Map> modRequestsWithPageData = + new HashMap<>(); + PaginationData paginationData = new PaginationData(); + paginationData.setTotalRowCount(sw360ModerationRequestService.getTotalCountOfRequests(sw360User)); + modRequestsWithPageData.put(paginationData, moderationRequests); - HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; - return new ResponseEntity<>(resources, status); + return getModerationResponseEntity(pageable, request, allDetails, modRequestsWithPageData); } @Operation( @@ -166,27 +158,7 @@ public ResponseEntity> getModerationRequestsB boolean stateOpen = stateOptions.get(0).equalsIgnoreCase(state); Map> modRequestsWithPageData = sw360ModerationRequestService.getRequestsByState(sw360User, pageable, stateOpen, allDetails); - List moderationRequests = new ArrayList<>(); - int totalCount = 0; - if (!CommonUtils.isNullOrEmptyMap(modRequestsWithPageData)) { - PaginationData paginationData = modRequestsWithPageData.keySet().iterator().next(); - moderationRequests = modRequestsWithPageData.get(paginationData); - totalCount = (int) paginationData.getTotalRowCount(); - } - - PaginationResult paginationResult = restControllerHelper.paginationResultFromPaginatedList(request, - pageable, moderationRequests, SW360Constants.TYPE_MODERATION, totalCount); - - List> moderationRequestResources = new ArrayList<>(); - paginationResult.getResources().forEach(m -> addModerationRequest(m, allDetails, moderationRequestResources)); - - CollectionModel resources; - if (moderationRequestResources.isEmpty()) { - resources = restControllerHelper.emptyPageResource(ModerationRequest.class, paginationResult); - } else { - resources = restControllerHelper.generatePagesResource(paginationResult, moderationRequestResources); - } - return new ResponseEntity<>(resources, HttpStatus.OK); + return getModerationResponseEntity(pageable, request, allDetails, modRequestsWithPageData); } private @NotNull HalResource createHalModerationRequestWithAllDetails( @@ -317,4 +289,57 @@ private void addModerationRequest(ModerationRequest moderationRequest, boolean a } moderationRequestResources.add(embeddedModerationRequestResource); } + + @Operation( + summary = "Get my submissions.", + description = "Get moderation requests submitted by the user. The responses are sortable by fields " + + "\"timestamp\", \"documentName\" and \"moderationState\".", + tags = {"Moderation Requests"} + ) + @GetMapping(value = MODERATION_REQUEST_URL + "/mySubmissions") + public ResponseEntity> getSubmissions( + Pageable pageable, HttpServletRequest request + ) throws TException, URISyntaxException, ResourceClassNotFoundException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Map> modRequestsWithPageData = + sw360ModerationRequestService.getRequestsByRequestingUser(sw360User, pageable); + return getModerationResponseEntity(pageable, request, false, modRequestsWithPageData); + } + + /** + * Generate a Response Entity for paginated moderation request list. + * @param pageable Pageable request + * @param request HTTP Request + * @param allDetails Request with allDetails? + * @param modRequestsWithPageData Map of pagination data and moderation request list + * @return Returns the Response Entity with pagination data. + */ + @NotNull + private ResponseEntity> getModerationResponseEntity( + Pageable pageable, HttpServletRequest request, boolean allDetails, + Map> modRequestsWithPageData + ) throws ResourceClassNotFoundException, URISyntaxException { + List moderationRequests = new ArrayList<>(); + int totalCount = 0; + if (!CommonUtils.isNullOrEmptyMap(modRequestsWithPageData)) { + PaginationData paginationData = modRequestsWithPageData.keySet().iterator().next(); + moderationRequests = modRequestsWithPageData.get(paginationData); + totalCount = (int) paginationData.getTotalRowCount(); + } + + PaginationResult paginationResult = restControllerHelper.paginationResultFromPaginatedList( + request, pageable, moderationRequests, SW360Constants.TYPE_MODERATION, totalCount); + + List> moderationRequestResources = new ArrayList<>(); + paginationResult.getResources().forEach(m -> addModerationRequest(m, allDetails, moderationRequestResources)); + + CollectionModel resources; + if (moderationRequestResources.isEmpty()) { + resources = restControllerHelper.emptyPageResource(ModerationRequest.class, paginationResult); + } else { + resources = restControllerHelper.generatePagesResource(paginationResult, moderationRequestResources); + } + HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; + return new ResponseEntity<>(resources, status); + } } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/Sw360ModerationRequestService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/Sw360ModerationRequestService.java index 3e0b23caf2..34b5b2a9d2 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/Sw360ModerationRequestService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/Sw360ModerationRequestService.java @@ -36,6 +36,7 @@ import org.springframework.stereotype.Service; import java.security.InvalidParameterException; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -94,6 +95,29 @@ public List getRequestsByModerator(User sw360User, Pageable p return getThriftModerationClient().getRequestsByModeratorWithPaginationNoFilter(sw360User, pageData); } + /** + * Get paginated list of moderation requests where user is the requester. + * @param sw360User Requester + * @param pageable Pageable information from request + * @return Paginated list of moderation requests. + * @throws TException Exception in case of error. + */ + public Map> getRequestsByRequestingUser( + User sw360User, Pageable pageable + ) throws TException { + PaginationData pageData = pageableToPaginationData(pageable); + ModerationService.Iface client = getThriftModerationClient(); + + List moderationList = client. + getRequestsByRequestingUserWithPagination(sw360User, pageData); + Map countInfo = client.getCountByRequester(sw360User); + pageData.setTotalRowCount(countInfo.getOrDefault(sw360User.getEmail(), 0L)); + + Map> result = new HashMap<>(); + result.put(pageData, moderationList); + return result; + } + /** * Get total count of moderation requests with user as a moderator. * diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ModerationRequestSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ModerationRequestSpecTest.java index a80111877d..f4b4add45c 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ModerationRequestSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ModerationRequestSpecTest.java @@ -203,6 +203,7 @@ public void before() throws TException, IOException { given(this.moderationRequestServiceMock.getRequestsByState(any(), any(), eq(false), anyBoolean())).willReturn(requestsByState); given(this.moderationRequestServiceMock.acceptRequest(eq(moderationRequest), eq("Changes looks good."), any())).willReturn(ModerationState.APPROVED); given(this.moderationRequestServiceMock.assignRequest(eq(moderationRequest), any())).willReturn(ModerationState.INPROGRESS); + given(this.moderationRequestServiceMock.getRequestsByRequestingUser(any(), any())).willReturn(requestsByState); } @Test @@ -448,4 +449,49 @@ public void should_document_get_moderationrequests_assign() throws Exception { subsectionWithPath("_links").description("Link to current <>") ))); } + + @Test + public void should_document_get_moderationrequests_submission() throws Exception { + String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword); + mockMvc.perform(get("/api/moderationrequest/mySubmissions") + .header("Authorization", "Bearer " + accessToken) + .param("page", "0") + .param("page_entries", "5") + .param("sort", "documentName,asc") + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( + requestParameters( + parameterWithName("page").description("Page of moderation requests"), + parameterWithName("page_entries").description("Amount of requests per page"), + parameterWithName("sort").description("Sort the result by the given field and order. " + + "Possible values are: `documentName`, `timestamp` and `moderationState`.") + ), + links( + linkWithRel("curies").description("Curies are used for online documentation"), + linkWithRel("first").description("Link to first page"), + linkWithRel("last").description("Link to last page") + ), + responseFields( + subsectionWithPath("_embedded.sw360:moderationRequests").description("An array of <>."), + fieldWithPath("_embedded.sw360:moderationRequests.[]id").description("The id of the moderation request."), + fieldWithPath("_embedded.sw360:moderationRequests.[]timestamp").description("Timestamp (in unix epoch) when the request was created."), + fieldWithPath("_embedded.sw360:moderationRequests.[]timestampOfDecision").description("Timestamp (in unix epoch) when the decision on the request was made."), + fieldWithPath("_embedded.sw360:moderationRequests.[]documentId").description("The ID of the document for which the moderation request was made."), + fieldWithPath("_embedded.sw360:moderationRequests.[]documentType").description("Type of the document. Possible values are: " + Arrays.asList(DocumentType.values())), + fieldWithPath("_embedded.sw360:moderationRequests.[]requestingUser").description("The user who created the moderation request."), + subsectionWithPath("_embedded.sw360:moderationRequests.[]moderators.[]").description("List of users who are marked as moderators for the request."), + fieldWithPath("_embedded.sw360:moderationRequests.[]documentName").description("Name of the document for which the request was created."), + fieldWithPath("_embedded.sw360:moderationRequests.[]moderationState").description("The state of the moderation request. Possible values are: " + Arrays.asList(ModerationState.values())), + fieldWithPath("_embedded.sw360:moderationRequests.[]requestingUserDepartment").description("The Business Unit / Group of the Project, for which clearing request is created."), + fieldWithPath("_embedded.sw360:moderationRequests.[]componentType").description("Type of the component for which the moderation request is created. Possible values are: " + Arrays.asList(ComponentType.values())), + subsectionWithPath("_embedded.sw360:moderationRequests.[]moderatorsSize").description("Number of users in moderators list."), + fieldWithPath("page").description("Additional paging information"), + fieldWithPath("page.size").description("Number of projects per page"), + fieldWithPath("page.totalElements").description("Total number of all existing moderation requests"), + fieldWithPath("page.totalPages").description("Total number of pages"), + fieldWithPath("page.number").description("Number of the current page"), + subsectionWithPath("_links").description("<> to other resources") + ))); + } }