Skip to content

Commit

Permalink
Added support to search projects based on ancestor project id (#1298)
Browse files Browse the repository at this point in the history
* Added support to search projects based on ancestor ids

* changed ancestor ids to a boolean isAncestorProjectId

* minor fix

* Fixed java heap problem due to no filter in include descendants, include ancestors etc queries

* Added support to find root level project

* Added missing parameter

* Added code comments

* incremented version of project service

* added change log
  • Loading branch information
holashchand authored Jan 27, 2025
1 parent 235cb3e commit cfbfc8a
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 58 deletions.
3 changes: 3 additions & 0 deletions health-services/project/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
All notable changes to this module will be documented in this file.

## 1.1.6 - 2025-01-27
- Added isAncestorProjectId param for search projects API to support search projects with ancestor project id as well

## 1.1.5 - 2024-08-07
- Added UserAction functionality with support for Location capture.

Expand Down
2 changes: 1 addition & 1 deletion health-services/project/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<artifactId>project</artifactId>
<packaging>jar</packaging>
<name>project</name>
<version>1.1.5</version>
<version>1.1.6</version>
<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,37 +69,44 @@ public ProjectRepository(Producer producer, NamedParameterJdbcTemplate namedPara
}


public List<Project> getProjects(ProjectRequest project, Integer limit, Integer offset, String tenantId, Long lastChangedSince, Boolean includeDeleted, Boolean includeAncestors, Boolean includeDescendants, Long createdFrom, Long createdTo) {
/**
* @param isAncestorProjectId When true, treats the project IDs in the ProjectRequest as ancestor project IDs
*/
public List<Project> getProjects(ProjectRequest project, Integer limit, Integer offset, String tenantId, Long lastChangedSince, Boolean includeDeleted, Boolean includeAncestors, Boolean includeDescendants, Long createdFrom, Long createdTo, boolean isAncestorProjectId) {

//Fetch Projects based on search criteria
List<Project> projects = getProjectsBasedOnSearchCriteria(project.getProjects(), limit, offset, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo);
List<Project> projects = getProjectsBasedOnSearchCriteria(project.getProjects(), limit, offset, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, isAncestorProjectId);

Set<String> projectIds = projects.stream().map(Project :: getId).collect(Collectors.toSet());

List<Project> ancestors = null;
List<Project> descendants = null;
//Get Project ancestors if includeAncestors flag is true
if (includeAncestors) {
ancestors = getProjectAncestors(projects);
if (ancestors != null && !ancestors.isEmpty()) {
List<String> ancestorProjectIds = ancestors.stream().map(Project :: getId).collect(Collectors.toList());
projectIds.addAll(ancestorProjectIds);
List<Target> targets = new ArrayList<>();
List<Document> documents = new ArrayList<>();
if(!projectIds.isEmpty()) {
//Get Project ancestors if includeAncestors flag is true
if (includeAncestors) {
ancestors = getProjectAncestors(projects);
if (ancestors != null && !ancestors.isEmpty()) {
List<String> ancestorProjectIds = ancestors.stream().map(Project :: getId).collect(Collectors.toList());
projectIds.addAll(ancestorProjectIds);
}
}
}
//Get Project descendants if includeDescendants flag is true
if (includeDescendants) {
descendants = getProjectDescendants(projects);
if (descendants != null && !descendants.isEmpty()) {
List<String> descendantsProjectIds = descendants.stream().map(Project :: getId).collect(Collectors.toList());
projectIds.addAll(descendantsProjectIds);
//Get Project descendants if includeDescendants flag is true
if (includeDescendants) {
descendants = getProjectDescendants(projects);
if (descendants != null && !descendants.isEmpty()) {
List<String> descendantsProjectIds = descendants.stream().map(Project :: getId).collect(Collectors.toList());
projectIds.addAll(descendantsProjectIds);
}
}
}

//Fetch targets based on Project Ids
List<Target> targets = getTargetsBasedOnProjectIds(projectIds);
//Fetch targets based on Project Ids
targets = getTargetsBasedOnProjectIds(projectIds);

//Fetch documents based on Project Ids
List<Document> documents = getDocumentsBasedOnProjectIds(projectIds);
//Fetch documents based on Project Ids
documents = getDocumentsBasedOnProjectIds(projectIds);
}

//Construct Project Objects with fetched projects, targets and documents using Project id
return buildProjectSearchResult(projects, targets, documents, ancestors, descendants);
Expand All @@ -114,28 +121,32 @@ public List<Project> getProjects(@NotNull @Valid ProjectSearch projectSearch, @V

List<Project> ancestors = null;
List<Project> descendants = null;
//Get Project ancestors if includeAncestors flag is true
if (urlParams.getIncludeAncestors()) {
ancestors = getProjectAncestors(projects);
if (ancestors != null && !ancestors.isEmpty()) {
List<String> ancestorProjectIds = ancestors.stream().map(Project :: getId).collect(Collectors.toList());
projectIds.addAll(ancestorProjectIds);
List<Target> targets = new ArrayList<>();
List<Document> documents = new ArrayList<>();
if(!projectIds.isEmpty()) {
//Get Project ancestors if includeAncestors flag is true
if (urlParams.getIncludeAncestors()) {
ancestors = getProjectAncestors(projects);
if (ancestors != null && !ancestors.isEmpty()) {
List<String> ancestorProjectIds = ancestors.stream().map(Project :: getId).toList();
projectIds.addAll(ancestorProjectIds);
}
}
}
//Get Project descendants if includeDescendants flag is true
if (urlParams.getIncludeDescendants()) {
descendants = getProjectDescendants(projects);
if (descendants != null && !descendants.isEmpty()) {
List<String> descendantsProjectIds = descendants.stream().map(Project :: getId).collect(Collectors.toList());
projectIds.addAll(descendantsProjectIds);
//Get Project descendants if includeDescendants flag is true
if (urlParams.getIncludeDescendants()) {
descendants = getProjectDescendants(projects);
if (descendants != null && !descendants.isEmpty()) {
List<String> descendantsProjectIds = descendants.stream().map(Project :: getId).toList();
projectIds.addAll(descendantsProjectIds);
}
}
}

//Fetch targets based on Project Ids
List<Target> targets = getTargetsBasedOnProjectIds(projectIds);
//Fetch targets based on Project Ids
targets = getTargetsBasedOnProjectIds(projectIds);

//Fetch documents based on Project Ids
List<Document> documents = getDocumentsBasedOnProjectIds(projectIds);
//Fetch documents based on Project Ids
documents = getDocumentsBasedOnProjectIds(projectIds);
}

//Construct Project Objects with fetched projects, targets and documents using Project id
return buildProjectSearchResult(projects, targets, documents, ancestors, descendants);
Expand All @@ -151,9 +162,9 @@ private List<Project> getProjectsBasedOnV2SearchCriteria(@NotNull @Valid Project
}

/* Fetch Projects based on search criteria */
private List<Project> getProjectsBasedOnSearchCriteria(List<Project> projectsRequest, Integer limit, Integer offset, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo) {
private List<Project> getProjectsBasedOnSearchCriteria(List<Project> projectsRequest, Integer limit, Integer offset, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo, boolean isAncestorProjectId) {
List<Object> preparedStmtList = new ArrayList<>();
String query = queryBuilder.getProjectSearchQuery(projectsRequest, limit, offset, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, preparedStmtList, false);
String query = queryBuilder.getProjectSearchQuery(projectsRequest, limit, offset, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, isAncestorProjectId, preparedStmtList, false);
List<Project> projects = jdbcTemplate.query(query, addressRowMapper, preparedStmtList.toArray());

log.info("Fetched project list based on given search criteria");
Expand Down Expand Up @@ -339,9 +350,9 @@ private void addDescendantsToProjectSearchResult(Project project, List<Project>
* query build at the run time)
* @return
*/
public Integer getProjectCount(ProjectRequest project, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo) {
public Integer getProjectCount(ProjectRequest project, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo, boolean isAncestorProjectId) {
List<Object> preparedStatement = new ArrayList<>();
String query = queryBuilder.getSearchCountQueryString(project.getProjects(), tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, preparedStatement);
String query = queryBuilder.getSearchCountQueryString(project.getProjects(), tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, isAncestorProjectId, preparedStatement);

if (query == null)
return 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ public class ProjectAddressQueryBuilder {
"left join project_address addr " +
"on prj.id = addr.projectId ";;

/* Constructs project search query based on conditions */
public String getProjectSearchQuery(List<Project> projects, Integer limit, Integer offset, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo, List<Object> preparedStmtList, boolean isCountQuery) {
/**
* Constructs project search query based on conditions
* @param isAncestorProjectId if set to true, project id in the projects would be considered as ancestor project id.
*/
public String getProjectSearchQuery(List<Project> projects, Integer limit, Integer offset, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo, boolean isAncestorProjectId, List<Object> preparedStmtList, boolean isCountQuery) {
//This uses a ternary operator to choose between PROJECTS_COUNT_QUERY or FETCH_PROJECT_ADDRESS_QUERY based on the value of isCountQuery.
String query = isCountQuery ? PROJECTS_COUNT_QUERY : FETCH_PROJECT_ADDRESS_QUERY;
StringBuilder queryBuilder = new StringBuilder(query);
Expand All @@ -69,7 +72,16 @@ public String getProjectSearchQuery(List<Project> projects, Integer limit, Integ
}
}

if (StringUtils.isNotBlank(project.getId())) {
/*
* If isAncestorProjectId is set to true, Then either id equals to project id or projectHierarchy
* should have id of the project
*/
if (isAncestorProjectId && StringUtils.isNotBlank(project.getId())) {
addClauseIfRequired(preparedStmtList, queryBuilder);
queryBuilder.append(" ( prj.projectHierarchy LIKE ? OR prj.id =? ) ");
preparedStmtList.add('%' + project.getId() + '%');
preparedStmtList.add(project.getId());
} else if (StringUtils.isNotBlank(project.getId())) {
addClauseIfRequired(preparedStmtList, queryBuilder);
queryBuilder.append(" prj.id =? ");
preparedStmtList.add(project.getId());
Expand Down Expand Up @@ -379,8 +391,8 @@ public String getProjectDescendantsSearchQueryBasedOnIds(List<String> projectIds
}

/* Returns query to get total projects count based on project search params */
public String getSearchCountQueryString(List<Project> projects, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo, List<Object> preparedStatement) {
String query = getProjectSearchQuery(projects, config.getMaxLimit(), config.getDefaultOffset(), tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, preparedStatement, true);
public String getSearchCountQueryString(List<Project> projects, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo, boolean isAncestorProjectId, List<Object> preparedStatement) {
String query = getProjectSearchQuery(projects, config.getMaxLimit(), config.getDefaultOffset(), tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, isAncestorProjectId, preparedStatement, true);
return query;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ public ProjectRequest createProject(ProjectRequest projectRequest) {
return projectRequest;
}

/**
* Search for projects based on various criteria
* @param isAncestorProjectId When true, treats the project IDs in the search criteria as ancestor project IDs
* and returns all projects (including children) under these ancestors
*/
public List<Project> searchProject(
ProjectRequest project,
Integer limit,
Expand All @@ -89,7 +94,8 @@ public List<Project> searchProject(
Boolean includeAncestors,
Boolean includeDescendants,
Long createdFrom,
Long createdTo
Long createdTo,
boolean isAncestorProjectId
) {
projectValidator.validateSearchProjectRequest(project, limit, offset, tenantId, createdFrom, createdTo);
List<Project> projects = projectRepository.getProjects(
Expand All @@ -102,7 +108,8 @@ public List<Project> searchProject(
includeAncestors,
includeDescendants,
createdFrom,
createdTo
createdTo,
isAncestorProjectId
);
return projects;
}
Expand All @@ -125,7 +132,7 @@ public ProjectRequest updateProject(ProjectRequest request) {
List<Project> projectsFromDB = searchProject(
getSearchProjectRequest(request.getProjects(), request.getRequestInfo(), false),
projectConfiguration.getMaxLimit(), projectConfiguration.getDefaultOffset(),
request.getProjects().get(0).getTenantId(), null, false, false, false, null, null
request.getProjects().get(0).getTenantId(), null, false, false, false, null, null, false
);
log.info("Fetched projects for update request");

Expand Down Expand Up @@ -280,7 +287,8 @@ private void checkAndEnrichCascadingProjectDates(ProjectRequest request, Project
true,
true,
null,
null
null,
false
);

/*
Expand All @@ -301,7 +309,7 @@ private List<Project> getParentProjects(ProjectRequest projectRequest) {
List<Project> parentProjects = null;
List<Project> projectsForSearchRequest = projectRequest.getProjects().stream().filter(p -> StringUtils.isNotBlank(p.getParent())).collect(Collectors.toList());
if (projectsForSearchRequest.size() > 0) {
parentProjects = searchProject(getSearchProjectRequest(projectsForSearchRequest, projectRequest.getRequestInfo(), true), projectConfiguration.getMaxLimit(), projectConfiguration.getDefaultOffset(), projectRequest.getProjects().get(0).getTenantId(), null, false, false, false, null, null);
parentProjects = searchProject(getSearchProjectRequest(projectsForSearchRequest, projectRequest.getRequestInfo(), true), projectConfiguration.getMaxLimit(), projectConfiguration.getDefaultOffset(), projectRequest.getProjects().get(0).getTenantId(), null, false, false, false, null, null, false);
}
log.info("Fetched parent projects from DB");
return parentProjects;
Expand Down Expand Up @@ -329,8 +337,8 @@ private ProjectRequest getSearchProjectRequest(List<Project> projects, RequestIn
/**
* @return Count of List of matching projects
*/
public Integer countAllProjects(ProjectRequest project, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo) {
return projectRepository.getProjectCount(project, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo);
public Integer countAllProjects(ProjectRequest project, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo, boolean isAncestorProjectId) {
return projectRepository.getProjectCount(project, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, isAncestorProjectId);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,8 @@ public ResponseEntity<ProjectResponse> searchProject(
@ApiParam(value = "Used in project search API to specify if response should include project elements that are in the preceding hierarchy of matched projects.", defaultValue = "false") @Valid @RequestParam(value = "includeAncestors", required = false, defaultValue = "false") Boolean includeAncestors,
@ApiParam(value = "Used in project search API to specify if response should include project elements that are in the following hierarchy of matched projects.", defaultValue = "false") @Valid @RequestParam(value = "includeDescendants", required = false, defaultValue = "false") Boolean includeDescendants,
@ApiParam(value = "Used in project search API to limit the search results to only those projects whose creation date is after the specified 'createdFrom' date", defaultValue = "false") @Valid @RequestParam(value = "createdFrom", required = false) Long createdFrom,
@ApiParam(value = "Used in project search API to limit the search results to only those projects whose creation date is before the specified 'createdTo' date", defaultValue = "false") @Valid @RequestParam(value = "createdTo", required = false) Long createdTo
@ApiParam(value = "Used in project search API to limit the search results to only those projects whose creation date is before the specified 'createdTo' date", defaultValue = "false") @Valid @RequestParam(value = "createdTo", required = false) Long createdTo,
@ApiParam(value = "Used in project search API to specify if response should be one which is in the preceding hierarchy of matched projects.") @Valid @RequestParam(value = "isAncestorProjectId", required = false, defaultValue = "false") boolean isAncestorProjectId
) {
List<Project> projects = projectService.searchProject(
project,
Expand All @@ -498,10 +499,11 @@ public ResponseEntity<ProjectResponse> searchProject(
includeAncestors,
includeDescendants,
createdFrom,
createdTo
createdTo,
isAncestorProjectId
);
ResponseInfo responseInfo = ResponseInfoFactory.createResponseInfo(project.getRequestInfo(), true);
Integer count = projectService.countAllProjects(project, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo);
Integer count = projectService.countAllProjects(project, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, isAncestorProjectId);
ProjectResponse projectResponse = ProjectResponse.builder().responseInfo(responseInfo).project(projects).totalCount(count).build();
return new ResponseEntity<ProjectResponse>(projectResponse, HttpStatus.OK);
}
Expand Down

0 comments on commit cfbfc8a

Please sign in to comment.