Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public FeaturesController(FeaturesService featuresService) {
@Parameter(name = HeaderConstants.IF_MATCH_HEADER, description = HeaderConstants.IF_MATCH_DESCRIPTION,
required = true, schema = @Schema(implementation = String.class), in = ParameterIn.HEADER)
public ResponseEntity<Map<String, Object>> getAllFeatures(
@RequestParam(required = false) UUID projectGuid,
@RequestParam(required = false) List<UUID> programAreaGuid,
@RequestParam(required = false) List<String> fiscalYear,
@RequestParam(required = false) List<String> activityCategoryCode,
Expand All @@ -69,10 +70,13 @@ public ResponseEntity<Map<String, Object>> getAllFeatures(
@RequestParam(required = false) List<String> forestDistrictOrgUnitId,
@RequestParam(required = false) List<String> fireCentreOrgUnitId,
@RequestParam(required = false) List<String> projectTypeCode,
@RequestParam(required = false) String searchText
@RequestParam(required = false) String searchText,
@RequestParam(defaultValue = "1") int pageNumber,
@RequestParam(defaultValue = "20") int pageRowCount
) {
try {
FeatureQueryParams queryParams = new FeatureQueryParams();
queryParams.setProjectGuid(projectGuid);
queryParams.setProgramAreaGuids(programAreaGuid);
queryParams.setFiscalYears(fiscalYear);
queryParams.setActivityCategoryCodes(activityCategoryCode);
Expand All @@ -83,7 +87,7 @@ public ResponseEntity<Map<String, Object>> getAllFeatures(
queryParams.setProjectTypeCodes(projectTypeCode);
queryParams.setSearchText(searchText);

Map<String, Object> result = featuresService.getAllFeatures(queryParams);
Map<String, Object> result = featuresService.getAllFeatures(queryParams, pageNumber, pageRowCount);
return ResponseEntity.ok(result);

} catch(ServiceException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

@Data
public class FeatureQueryParams {
private UUID projectGuid;
private List<UUID> programAreaGuids;
private List<String> fiscalYears;
private List<String> forestRegionOrgUnitIds;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,24 @@ public class FeaturesService implements CommonService {
private static final String REVISION_COUNT = "revisionCount";


public Map<String, Object> getAllFeatures(FeatureQueryParams params) throws ServiceException {
public Map<String, Object> getAllFeatures(FeatureQueryParams params, int pageNumber, int pageRowCount) throws ServiceException {
try {
List<ProjectEntity> filteredProjects = findFilteredProjects(params);
if (params.getProjectGuid() != null) {
ProjectEntity project = entityManager.find(ProjectEntity.class, params.getProjectGuid());
if (project == null) {
return Collections.emptyMap();
}

Map<String, Object> projectMap = createProjectProperties(project);
addProjectBoundaries(project, projectMap);
addProjectFiscals(project, params, projectMap);

Map<String, Object> response = new HashMap<>();
response.put("project", projectMap);
return response;
}
long totalItems = countFilteredProjects(params);
List<ProjectEntity> filteredProjects = findFilteredProjects(params, pageNumber, pageRowCount);
List<Map<String, Object>> projects = new ArrayList<>();

for (ProjectEntity project : filteredProjects) {
Expand All @@ -65,7 +80,14 @@ public Map<String, Object> getAllFeatures(FeatureQueryParams params) throws Serv
projects.add(projectMap);
}

return Map.of("projects", projects);
int totalPages = (int) Math.ceil((double) totalItems / pageRowCount);
Map<String, Object> response = new HashMap<>();
response.put("projects", projects);
response.put("currentPage", pageNumber);
response.put("pageSize", pageRowCount);
response.put("totalItems", totalItems);
response.put("totalPages", totalPages);
return response;
}catch (Exception e){
log.error("Error encountered while fetching features:", e);
throw new ServiceException(e.getLocalizedMessage(), e);
Expand Down Expand Up @@ -166,13 +188,12 @@ private void processLocationGeometry(ProjectBoundaryEntity latestBoundary, List<
}


List<ProjectEntity> findFilteredProjects(FeatureQueryParams params) {
List<ProjectEntity> findFilteredProjects(FeatureQueryParams params, int pageNumber, int pageRowCount) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ProjectEntity> query = cb.createQuery(ProjectEntity.class);
Root<ProjectEntity> project = query.from(ProjectEntity.class);

List<Predicate> predicates = new ArrayList<>();

addProjectLevelFilters(project, predicates, params);
addFiscalAttributeFilters(cb, project, predicates, params);
addSearchTextFilters(cb, project, predicates, params);
Expand All @@ -183,11 +204,18 @@ List<ProjectEntity> findFilteredProjects(FeatureQueryParams params) {

query.distinct(true);

return entityManager.createQuery(query).getResultList();
return entityManager.createQuery(query)
.setFirstResult((pageNumber - 1) * pageRowCount)
.setMaxResults(pageRowCount)
.getResultList();
}

void addProjectLevelFilters(Root<ProjectEntity> project, List<Predicate> predicates, FeatureQueryParams params) {
log.info("Filtering by projectTypeCodes: {}", params.getProjectTypeCodes());
if (params.getProjectGuid() != null) {
predicates.add(project.get("projectGuid").in(params.getProjectGuid()));
return;
}
if (params.getProgramAreaGuids() != null && !params.getProgramAreaGuids().isEmpty()) {
predicates.add(project.get("programAreaGuid").in(params.getProgramAreaGuids()));
}
Expand Down Expand Up @@ -355,7 +383,7 @@ ProjectBoundaryEntity findLatestProjectBoundary(UUID projectGuid) {
.setMaxResults(1) // Only get the first result (most recent)
.getResultList();

return results.isEmpty() ? null : results.getFirst();
return results.isEmpty() ? null : results.get(0);
}

private Map<String, Object> createProjectProperties(ProjectEntity project) {
Expand Down Expand Up @@ -566,4 +594,22 @@ static List<double[]> extractCoordinates(LinearRing linearRing) {

return coordinates;
}
}

long countFilteredProjects(FeatureQueryParams params) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
Root<ProjectEntity> project = countQuery.from(ProjectEntity.class);

List<Predicate> predicates = new ArrayList<>();
addProjectLevelFilters(project, predicates, params);
addFiscalAttributeFilters(cb, project, predicates, params);
addSearchTextFilters(cb, project, predicates, params);

if (!predicates.isEmpty()) {
countQuery.where(cb.and(predicates.toArray(new Predicate[0])));
}

countQuery.select(cb.countDistinct(project));
return entityManager.createQuery(countQuery).getSingleResult();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import ca.bc.gov.nrs.wfprev.services.FeaturesService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

Expand All @@ -16,7 +17,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
Expand Down Expand Up @@ -50,10 +51,11 @@ void testGetAllFeatures_WithQueryParams_ReturnsExpectedMap() throws ServiceExcep
Map<String, Object> expectedResponse = Map.of("type", "FeatureCollection");

// Mock service call
when(featuresService.getAllFeatures(any(FeatureQueryParams.class))).thenReturn(expectedResponse);
when(featuresService.getAllFeatures(any(FeatureQueryParams.class), anyInt(), anyInt())).thenReturn(expectedResponse);

// Call controller method
ResponseEntity<Map<String, Object>> result = featuresController.getAllFeatures(
null,
programAreaGuids,
fiscalYears,
activityCategoryCodes,
Expand All @@ -62,41 +64,95 @@ void testGetAllFeatures_WithQueryParams_ReturnsExpectedMap() throws ServiceExcep
forestDistrictOrgUnitIds,
fireCentreOrgUnitIds,
projectTypeCodes,
searchText
searchText,
1,
20
);

// Assert status and body
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals(expectedResponse, result.getBody());
verify(featuresService, times(1)).getAllFeatures(any(FeatureQueryParams.class));
verify(featuresService, times(1)).getAllFeatures(any(FeatureQueryParams.class), anyInt(), anyInt());
}

@Test
void testGetAllFeatures_WithNoParams_CallsServiceWithEmptyParams() throws ServiceException {
// Expected result
Map<String, Object> expected = Map.of("type", "FeatureCollection");

when(featuresService.getAllFeatures(any(FeatureQueryParams.class))).thenReturn(expected);
when(featuresService.getAllFeatures(any(FeatureQueryParams.class), anyInt(), anyInt())).thenReturn(expected);

ResponseEntity<Map<String, Object>> result = featuresController.getAllFeatures(
null, null, null, null, null, null, null, null, null
null,null, null, null, null, null, null, null, null, null, 1, 20
);

assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals(expected, result.getBody());
verify(featuresService, times(1)).getAllFeatures(any(FeatureQueryParams.class));
verify(featuresService, times(1)).getAllFeatures(any(FeatureQueryParams.class), anyInt(), anyInt());
}

@Test
void testGetAllFeatures_ServiceThrowsException_ReturnsInternalServerError() throws ServiceException {
// Mock exception
when(featuresService.getAllFeatures(any(FeatureQueryParams.class))).thenThrow(new ServiceException("Service failed"));
when(featuresService.getAllFeatures(any(FeatureQueryParams.class), anyInt(), anyInt())).thenThrow(new ServiceException("Service failed"));

ResponseEntity<Map<String, Object>> result = featuresController.getAllFeatures(
null, null, null, null, null, null, null, null, null
null,null, null, null, null, null, null, null, null, null, 1, 20
);

assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode());
assertNull(result.getBody());
}

@Test
void testGetAllFeatures_WithProjectGuid_ReturnsSingleProject() throws ServiceException {
UUID projectGuid = UUID.randomUUID();
Map<String, Object> expected = Map.of("project", Map.of("projectGuid", projectGuid));

when(featuresService.getAllFeatures(any(FeatureQueryParams.class), anyInt(), anyInt()))
.thenReturn(expected);

ResponseEntity<Map<String, Object>> result = featuresController.getAllFeatures(
projectGuid,
null, null, null, null, null, null, null, null, null,
1, 20
);

assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals(expected, result.getBody());
verify(featuresService, times(1)).getAllFeatures(any(FeatureQueryParams.class), anyInt(), anyInt());
}

@Test
void testGetAllFeatures_PassesCorrectParamsToService() throws ServiceException {
UUID projectGuid = UUID.randomUUID();
List<String> fiscalYears = List.of("2024");

when(featuresService.getAllFeatures(any(FeatureQueryParams.class), anyInt(), anyInt()))
.thenReturn(Map.of("ok", true));

featuresController.getAllFeatures(
projectGuid,
null,
fiscalYears,
null,
null,
null,
null,
null,
null,
"search text",
2,
50
);

ArgumentCaptor<FeatureQueryParams> captor = ArgumentCaptor.forClass(FeatureQueryParams.class);
verify(featuresService).getAllFeatures(captor.capture(), anyInt(), anyInt());

FeatureQueryParams params = captor.getValue();
assertEquals(projectGuid, params.getProjectGuid());
assertEquals(fiscalYears, params.getFiscalYears());
assertEquals("search text", params.getSearchText());
}

}
Loading
Loading