From 15372052828e1f16375c9ea1d8a75fc92e76ba0b Mon Sep 17 00:00:00 2001 From: Keerthi B L Date: Mon, 23 Dec 2024 15:04:14 +0530 Subject: [PATCH] feat(rest) : Advanced Search for project page Signed-off-by: Keerthi B L --- .../src/docs/asciidoc/projects.adoc | 19 +++ .../project/ProjectController.java | 130 +++++++++++++----- .../restdocs/ProjectSpecTest.java | 41 ++++++ 3 files changed, 157 insertions(+), 33 deletions(-) diff --git a/rest/resource-server/src/docs/asciidoc/projects.adoc b/rest/resource-server/src/docs/asciidoc/projects.adoc index b7212f92cf..e6b7a332c4 100644 --- a/rest/resource-server/src/docs/asciidoc/projects.adoc +++ b/rest/resource-server/src/docs/asciidoc/projects.adoc @@ -1233,3 +1233,22 @@ include::{snippets}/should_document_get_export_project_create_clearing_request/c ===== Example response include::{snippets}/should_document_get_export_project_create_clearing_request/http-response.adoc[] + +[[resources-projects-list-by-search]] +==== Filtering with more fields + +A `GET` request to fetch filtered list of projects. + +Note : send query parameter's value in encoded format. (Reference: `https://datatracker.ietf.org/doc/html/rfc3986`) + +===== Response structure +include::{snippets}/should_document_get_projects_by_advance_search/response-fields.adoc[] + +===== Example request +include::{snippets}/should_document_get_projects_by_advance_search/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_get_projects_by_advance_search/http-response.adoc[] + +===== Links +include::{snippets}/should_document_get_projects_by_advance_search/links.adoc[] \ No newline at end of file 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 a0648184e4..08d4dfeaba 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 @@ -69,11 +69,7 @@ import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentUsage; import org.eclipse.sw360.datahandler.thrift.attachments.CheckStatus; import org.eclipse.sw360.datahandler.thrift.attachments.UsageData; -import org.eclipse.sw360.datahandler.thrift.components.ClearingState; -import org.eclipse.sw360.datahandler.thrift.components.Release; -import org.eclipse.sw360.datahandler.thrift.components.ReleaseClearingStateSummary; -import org.eclipse.sw360.datahandler.thrift.components.ReleaseLink; -import org.eclipse.sw360.datahandler.thrift.components.ReleaseNode; +import org.eclipse.sw360.datahandler.thrift.components.*; import org.eclipse.sw360.datahandler.thrift.licenseinfo.LicenseInfo; import org.eclipse.sw360.datahandler.thrift.licenseinfo.LicenseInfoFile; import org.eclipse.sw360.datahandler.thrift.licenseinfo.LicenseInfoParsingResult; @@ -81,15 +77,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.ObligationList; -import org.eclipse.sw360.datahandler.thrift.projects.ObligationStatusInfo; -import org.eclipse.sw360.datahandler.thrift.projects.Project; -import org.eclipse.sw360.datahandler.thrift.projects.ProjectClearingState; -import org.eclipse.sw360.datahandler.thrift.projects.ProjectLink; -import org.eclipse.sw360.datahandler.thrift.projects.ProjectProjectRelationship; -import org.eclipse.sw360.datahandler.thrift.projects.ProjectRelationship; -import org.eclipse.sw360.datahandler.thrift.projects.ProjectDTO; -import org.eclipse.sw360.datahandler.thrift.projects.ClearingRequest; +import org.eclipse.sw360.datahandler.thrift.projects.*; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.datahandler.thrift.users.UserGroup; import org.eclipse.sw360.datahandler.thrift.vendors.Vendor; @@ -259,6 +247,16 @@ public ResponseEntity>> getProjectsForUser( @RequestParam(value = "tag", required = false) String tag, @Parameter(description = "Flag to get projects with all details.") @RequestParam(value = "allDetails", required = false) boolean allDetails, + @Parameter(description = "The version of the project") + @RequestParam(value = "version", required = false) String version, + @Parameter(description = "The projectResponsible of the project") + @RequestParam(value = "projectResponsible", required = false) String projectResponsible, + @Parameter(description = "The state of the project") + @RequestParam(value = "state", required = false) ProjectState projectState, + @Parameter(description = "The clearingStatus of the project") + @RequestParam(value = "clearingStatus", required = false) ProjectClearingState projectClearingState, + @Parameter(description = "The additionalData of the project") + @RequestParam(value = "additionalData", required = false) String additionalData, @Parameter(description = "List project by lucene search") @RequestParam(value = "luceneSearch", required = false) boolean luceneSearch, HttpServletRequest request) throws TException, URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { @@ -269,23 +267,13 @@ public ResponseEntity>> getProjectsForUser( boolean isSearchByType = CommonUtils.isNotNullEmptyOrWhitespace(projectType); boolean isSearchByGroup = CommonUtils.isNotNullEmptyOrWhitespace(group); boolean isNoFilter = false; + boolean isAllProjectAdded=false; String queryString = request.getQueryString(); Map params = restControllerHelper.parseQueryString(queryString); List sw360Projects = new ArrayList<>(); - Map> filterMap = new HashMap<>(); if (luceneSearch) { - if (CommonUtils.isNotNullEmptyOrWhitespace(projectType)) { - Set values = CommonUtils.splitToSet(projectType); - filterMap.put(Project._Fields.PROJECT_TYPE.getFieldName(), values); - } - if (CommonUtils.isNotNullEmptyOrWhitespace(group)) { - Set values = CommonUtils.splitToSet(group); - filterMap.put(Project._Fields.BUSINESS_UNIT.getFieldName(), values); - } - if (CommonUtils.isNotNullEmptyOrWhitespace(tag)) { - Set values = CommonUtils.splitToSet(tag); - filterMap.put(Project._Fields.TAG.getFieldName(), values); - } + Map> filterMap = getFilterMap(tag, projectType, group, version, projectResponsible, projectState, projectClearingState, + additionalData); if (CommonUtils.isNotNullEmptyOrWhitespace(name)) { Set values = CommonUtils.splitToSet(name); @@ -298,14 +286,16 @@ public ResponseEntity>> getProjectsForUser( } else { if (isSearchByName) { sw360Projects.addAll(projectService.searchProjectByName(params.get("name"), sw360User)); - } else if (isSearchByGroup) { - sw360Projects.addAll(projectService.searchProjectByGroup(group, sw360User)); - } else if (isSearchByTag) { - sw360Projects.addAll(projectService.searchProjectByTag(params.get("tag"), sw360User)); - } else if (isSearchByType) { - sw360Projects.addAll(projectService.searchProjectByType(projectType, sw360User)); } else { + isAllProjectAdded=true; sw360Projects.addAll(projectService.getProjectsForUser(sw360User, pageable)); + } + Map> restrictions = getFilterMap(tag, projectType, group, version, projectResponsible, projectState, projectClearingState, + additionalData); + if (!restrictions.isEmpty()) { + sw360Projects = new ArrayList<>(sw360Projects.stream() + .filter(filterProjectMap(restrictions)).toList()); + }else if(isAllProjectAdded){ isNoFilter = true; } } @@ -313,6 +303,36 @@ public ResponseEntity>> getProjectsForUser( mapOfProjects, isSearchByName, sw360Projects, isNoFilter); } + private Map> getFilterMap(String tag, String projectType, String group, String version, String projectResponsible, + ProjectState projectState, ProjectClearingState projectClearingState, String additionalData) { + Map> filterMap = new HashMap<>(); + if (CommonUtils.isNotNullEmptyOrWhitespace(tag)) { + filterMap.put(Project._Fields.TAG.getFieldName(), CommonUtils.splitToSet(tag)); + } + if (CommonUtils.isNotNullEmptyOrWhitespace(projectType)) { + filterMap.put(Project._Fields.PROJECT_TYPE.getFieldName(), CommonUtils.splitToSet(projectType)); + } + if (CommonUtils.isNotNullEmptyOrWhitespace(group)) { + filterMap.put(Project._Fields.BUSINESS_UNIT.getFieldName(), CommonUtils.splitToSet(group)); + } + if (CommonUtils.isNotNullEmptyOrWhitespace(version)) { + filterMap.put(Project._Fields.VERSION.getFieldName(), CommonUtils.splitToSet(version)); + } + if (CommonUtils.isNotNullEmptyOrWhitespace(projectResponsible)) { + filterMap.put(Project._Fields.PROJECT_RESPONSIBLE.getFieldName(), CommonUtils.splitToSet(projectResponsible)); + } + if (projectState!=null && CommonUtils.isNotNullEmptyOrWhitespace(projectState.name())) { + filterMap.put(Project._Fields.STATE.getFieldName(), CommonUtils.splitToSet(projectState.name())); + } + if (projectClearingState!=null && CommonUtils.isNotNullEmptyOrWhitespace(projectClearingState.name())) { + filterMap.put(Project._Fields.CLEARING_STATE.getFieldName(), CommonUtils.splitToSet(projectClearingState.name())); + } + if (CommonUtils.isNotNullEmptyOrWhitespace(additionalData)) { + filterMap.put(Project._Fields.ADDITIONAL_DATA.getFieldName(), CommonUtils.splitToSet(additionalData)); + } + return filterMap; + } + @NotNull private ResponseEntity>> getProjectResponse(Pageable pageable, String projectType, String group, String tag, boolean allDetails, boolean luceneSearch, @@ -3396,4 +3416,48 @@ public ResponseEntity createDuplicateProjectWithDependencyNetwork( return ResponseEntity.created(location).body(projectDTOHalResource); } + + /** + * Create a filter predicate to remove all projects which do not satisfy the restriction set. + * @param restrictions Restrictions set to filter projects on + * @return Filter predicate for stream. + */ + private static @NonNull Predicate filterProjectMap(Map> restrictions) { + return project -> { + for (Map.Entry> restriction : restrictions.entrySet()) { + final Set filterSet = restriction.getValue(); + Project._Fields field = Project._Fields.findByName(restriction.getKey()); + Object fieldValue = project.getFieldValue(field); + if (fieldValue == null) { + return false; + } + if (field == Project._Fields.PROJECT_TYPE && !filterSet.contains(project.projectType.name())) { + return false; + } else if (field == Project._Fields.VERSION && !filterSet.contains(project.version)) { + return false; + } else if (field == Project._Fields.PROJECT_RESPONSIBLE && !filterSet.contains(project.projectResponsible)) { + return false; + } else if (field == Project._Fields.STATE && !filterSet.contains(project.state.name())) { + return false; + } else if (field == Project._Fields.CLEARING_STATE && !filterSet.contains(project.clearingState.name())) { + return false; + } else if ((field == Project._Fields.CREATED_BY || field == Project._Fields.CREATED_ON) + && !fieldValue.toString().equalsIgnoreCase(filterSet.iterator().next())) { + return false; + } else if (fieldValue instanceof Set) { + if (Sets.intersection(filterSet, (Set) fieldValue).isEmpty()) { + return false; + } + } else if (fieldValue instanceof Map) { + Map fieldValueMap = (Map) fieldValue; + boolean hasIntersection = fieldValueMap.keySet().stream() + .anyMatch(filterSet::contains); + if (!hasIntersection) { + return false; + } + } + } + return true; + }; + } } 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 c7910f8b3a..aae218f274 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 @@ -3200,4 +3200,45 @@ public void should_document_get_project_release_with_ecc_spreadsheet() throws Ex parameterWithName("projectId").description("Id of a project")) )); } + + @Test + public void should_document_get_projects_by_advance_search() throws Exception { + mockMvc.perform(get("/api/projects") + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) + .queryParam("projectType", project.getProjectType().toString()) + .queryParam("createdOn", project.getCreatedOn()) + .queryParam("version", project.getVersion()) + .queryParam("luceneSearch", "false") + .queryParam("page", "0") + .queryParam("page_entries", "5") + .queryParam("sort", "name,desc") + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( + queryParameters( + parameterWithName("projectType").description("Filter for type"), + parameterWithName("createdOn").description("Filter for project creation date"), + parameterWithName("version").description("Filter for version"), + parameterWithName("luceneSearch").description("Filter with exact match or lucene match."), + parameterWithName("page").description("Page of projects"), + parameterWithName("page_entries").description("Amount of projects per page"), + parameterWithName("sort").description("Defines order of the projects") + ), + 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:projects.[]name").description("The name of the component"), + subsectionWithPath("_embedded.sw360:projects.[]projectType").description("The component type, possible values are: " + Arrays.asList(ComponentType.values())), + subsectionWithPath("_embedded.sw360:projects").description("An array of <>"), + subsectionWithPath("_links").description("<> to other resources"), + fieldWithPath("page").description("Additional paging information"), + fieldWithPath("page.size").description("Number of projects per page"), + fieldWithPath("page.totalElements").description("Total number of all existing projects"), + fieldWithPath("page.totalPages").description("Total number of pages"), + fieldWithPath("page.number").description("Number of the current page") + ))); + } }