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) : Advanced Search for project page #2851

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
19 changes: 19 additions & 0 deletions rest/resource-server/src/docs/asciidoc/projects.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Original file line number Diff line number Diff line change
Expand Up @@ -69,27 +69,15 @@
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;
import org.eclipse.sw360.datahandler.thrift.licenseinfo.LicenseNameWithText;
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;
Expand Down Expand Up @@ -259,6 +247,16 @@ public ResponseEntity<CollectionModel<EntityModel<Project>>> 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 {
Expand All @@ -269,23 +267,13 @@ public ResponseEntity<CollectionModel<EntityModel<Project>>> getProjectsForUser(
boolean isSearchByType = CommonUtils.isNotNullEmptyOrWhitespace(projectType);
boolean isSearchByGroup = CommonUtils.isNotNullEmptyOrWhitespace(group);
boolean isNoFilter = false;
boolean isAllProjectAdded=false;
String queryString = request.getQueryString();
Map<String, String> params = restControllerHelper.parseQueryString(queryString);
List<Project> sw360Projects = new ArrayList<>();
Map<String, Set<String>> filterMap = new HashMap<>();
if (luceneSearch) {
if (CommonUtils.isNotNullEmptyOrWhitespace(projectType)) {
Set<String> values = CommonUtils.splitToSet(projectType);
filterMap.put(Project._Fields.PROJECT_TYPE.getFieldName(), values);
}
if (CommonUtils.isNotNullEmptyOrWhitespace(group)) {
Set<String> values = CommonUtils.splitToSet(group);
filterMap.put(Project._Fields.BUSINESS_UNIT.getFieldName(), values);
}
if (CommonUtils.isNotNullEmptyOrWhitespace(tag)) {
Set<String> values = CommonUtils.splitToSet(tag);
filterMap.put(Project._Fields.TAG.getFieldName(), values);
}
Map<String, Set<String>> filterMap = getFilterMap(tag, projectType, group, version, projectResponsible, projectState, projectClearingState,
additionalData);

if (CommonUtils.isNotNullEmptyOrWhitespace(name)) {
Set<String> values = CommonUtils.splitToSet(name);
Expand All @@ -298,21 +286,53 @@ public ResponseEntity<CollectionModel<EntityModel<Project>>> 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<String, Set<String>> 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;
}
}
return getProjectResponse(pageable, projectType, group, tag, allDetails, luceneSearch, request, sw360User,
mapOfProjects, isSearchByName, sw360Projects, isNoFilter);
Comment on lines 302 to 303
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getProjectResponse() no longer requires filter of projectType, group, etc. as it is already done by filterProjectMap() and refineSearch().

}

private Map<String, Set<String>> getFilterMap(String tag, String projectType, String group, String version, String projectResponsible,
ProjectState projectState, ProjectClearingState projectClearingState, String additionalData) {
Map<String, Set<String>> 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<CollectionModel<EntityModel<Project>>> getProjectResponse(Pageable pageable,
String projectType, String group, String tag, boolean allDetails, boolean luceneSearch,
Expand Down Expand Up @@ -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<Project> filterProjectMap(Map<String, Set<String>> restrictions) {
return project -> {
for (Map.Entry<String, Set<String>> restriction : restrictions.entrySet()) {
final Set<String> 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<String>) 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;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 <<resources-projects, Projects resources>>"),
subsectionWithPath("_links").description("<<resources-index-links,Links>> 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")
)));
}
}
Loading