Skip to content

Commit

Permalink
MAT-8002 Updating existing search Measure API to return paginated mea…
Browse files Browse the repository at this point in the history
…sures based on a search Criteria
  • Loading branch information
RohitKandimalla committed Jan 13, 2025
1 parent 03534e5 commit de2118d
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 80 deletions.
16 changes: 16 additions & 0 deletions src/main/java/cms/gov/madie/measure/dto/MeasureSearchCriteria.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package cms.gov.madie.measure.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder(toBuilder = true)
@AllArgsConstructor
@NoArgsConstructor
public class MeasureSearchCriteria {
private String query;
private String model;
private Boolean draft;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import cms.gov.madie.measure.dto.MeasureListDTO;

import cms.gov.madie.measure.dto.MeasureSearchCriteria;
import gov.cms.madie.models.dto.LibraryUsage;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand All @@ -17,7 +18,11 @@ public interface MeasureAclRepository {
* @param pageable- instance of Pageable
* @return Pageable List of measures
*/
Page<MeasureListDTO> findMyActiveMeasures(String userId, Pageable pageable, String searchTerm);
Page<MeasureListDTO> findActiveMeasures(
String userId,
Pageable pageable,
MeasureSearchCriteria searchCriteria,
boolean filterByCurrentUser);

/**
* Get all the measures(name, version and owner) if they include any version of given library name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import cms.gov.madie.measure.dto.FacetDTO;
import cms.gov.madie.measure.dto.MeasureListDTO;
import cms.gov.madie.measure.dto.MeasureSearchCriteria;
import gov.cms.madie.models.access.RoleEnum;
import gov.cms.madie.models.dto.LibraryUsage;
import gov.cms.madie.models.measure.Measure;
Expand Down Expand Up @@ -36,31 +37,56 @@ private LookupOperation getLookupOperation() {
.as("measureSet");
}

// First get All Active measures
// if query string is given then search for the query string in measureName and eCQM title
// If model is provided filter out those measures based on Model
// if draft status is provided then filter out them based on draft value
// if filterByCurrentUser = true then filter measures owned by user or shared with
@Override
public Page<MeasureListDTO> findMyActiveMeasures(
String userId, Pageable pageable, String searchTerm) {
public Page<MeasureListDTO> findActiveMeasures(
String userId,
Pageable pageable,
MeasureSearchCriteria measureSearchCriteria,
boolean filterByCurrentUser) {
// join measure and measure_set to lookup owner and ACL info
LookupOperation lookupOperation = getLookupOperation();

// prepare measure search criteria
Criteria measureCriteria = Criteria.where("active").is(true);
if (StringUtils.isNotBlank(searchTerm)) {
measureCriteria.andOperator(
new Criteria()
.orOperator(
Criteria.where("measureName").regex(searchTerm, "i"),
Criteria.where("ecqmTitle").regex(searchTerm, "i")));

if (measureSearchCriteria != null) {
// If query is given, search for the query string in measureName and ecqmTitle
if (StringUtils.isNotBlank(measureSearchCriteria.getQuery())) {
measureCriteria.andOperator(
new Criteria()
.orOperator(
Criteria.where("measureName").regex(measureSearchCriteria.getQuery(), "i"),
Criteria.where("ecqmTitle").regex(measureSearchCriteria.getQuery(), "i")));
}

// If model is provided, filter out those measures with that model
if (StringUtils.isNotBlank(measureSearchCriteria.getModel())) {
measureCriteria.and("model").is(measureSearchCriteria.getModel());
}

// If draft is provided, filter measures based on MeasureMetaData.draft
if (measureSearchCriteria.getDraft() != null) {
measureCriteria.and("measureMetaData.draft").is(measureSearchCriteria.getDraft());
}
}

// prepare measure set search criteria(user is either owner or shared with)
Criteria measureSetCriteria =
new Criteria()
.orOperator(
Criteria.where("measureSet.owner").regex("^\\Q" + userId + "\\E$", "i"),
Criteria.where("measureSet.acls.userId")
.regex("^\\Q" + userId + "\\E$", "i")
.and("measureSet.acls.roles")
.in(RoleEnum.SHARED_WITH));
Criteria measureSetCriteria = new Criteria();
if (filterByCurrentUser) {
measureSetCriteria =
new Criteria()
.orOperator(
Criteria.where("measureSet.owner").regex("^\\Q" + userId + "\\E$", "i"),
Criteria.where("measureSet.acls.userId")
.regex("^\\Q" + userId + "\\E$", "i")
.and("measureSet.acls.roles")
.in(RoleEnum.SHARED_WITH));
}

// combine measure and measure set criteria
MatchOperation matchOperation =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ public interface MeasureRepository
@Query(value = "{'groups._id': ?0}")
Optional<Measure> findGroupById(String groupId);

@Query(
" {$and: [{active : true} , "
+ "{$or: [{'measureName' : { $regex : /\\Q?0\\E/, $options: 'i' } },"
+ "{'ecqmTitle' : { $regex : /\\Q?0\\E/, $options: 'i' }}]} "
+ "]}")
Page<MeasureListDTO> findAllByMeasureNameOrEcqmTitle(String criteria, Pageable page);
// @Query(
// " {$and: [{active : true} , "
// + "{$or: [{'measureName' : { $regex : /\\Q?0\\E/, $options: 'i' } },"
// + "{'ecqmTitle' : { $regex : /\\Q?0\\E/, $options: 'i' }}]} "
// + "]}")
// Page<MeasureListDTO> findAllByMeasureNameOrEcqmTitle(String criteria, Pageable page);

boolean existsByMeasureSetIdAndActiveAndMeasureMetaDataDraft(
String setId, boolean active, boolean draft);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cms.gov.madie.measure.resources;

import cms.gov.madie.measure.dto.MeasureListDTO;
import cms.gov.madie.measure.dto.MeasureSearchCriteria;
import cms.gov.madie.measure.exceptions.*;
import cms.gov.madie.measure.repositories.MeasureRepository;
import cms.gov.madie.measure.repositories.MeasureSetRepository;
Expand Down Expand Up @@ -37,11 +38,7 @@
import org.springframework.web.bind.annotation.RestController;

import jakarta.servlet.http.HttpServletRequest;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand All @@ -60,9 +57,8 @@ public class MeasureController {

@GetMapping("/measures/draftstatus")
public ResponseEntity<Map<String, Boolean>> getDraftStatuses(
@RequestParam(required = true, name = "measureSetIds") List<String> measureSetIds) {
Map<String, Boolean> results = new HashMap<>();
results = measureService.getMeasureDrafts(measureSetIds);
@RequestParam(name = "measureSetIds") List<String> measureSetIds) {
Map<String, Boolean> results = measureService.getMeasureDrafts(measureSetIds);
return ResponseEntity.status(HttpStatus.CREATED).body(results);
}

Expand Down Expand Up @@ -291,22 +287,24 @@ public ResponseEntity<Measure> deleteStratification(
measureId, groupId, stratificationId, principal.getName()));
}

@GetMapping("/measures/search")
public ResponseEntity<Page<MeasureListDTO>> findAllByMeasureNameOrEcqmTitle(
@PutMapping("/measures/search")
public ResponseEntity<Page<MeasureListDTO>> measureSearchByCriteria(
Principal principal,
@RequestParam(required = false, defaultValue = "false", name = "currentUser")
boolean filterByCurrentUser,
@RequestParam(required = false, name = "query") String query,
@RequestBody(required = false) MeasureSearchCriteria searchCriteria,
@RequestParam(required = false, defaultValue = "10", name = "limit") int limit,
@RequestParam(required = false, defaultValue = "0", name = "page") int page) {

final String username = principal.getName();
final Pageable pageReq = PageRequest.of(page, limit, Sort.by("lastModifiedAt").descending());

// We need to decode the encoded strings we send over or we can't find stuff
String decodedQuery = URLDecoder.decode(query, StandardCharsets.UTF_8);
// searchCriteria is an optional body, so initializing the obj to avoid any potential NPE
if (searchCriteria == null) {
searchCriteria = MeasureSearchCriteria.builder().build();
}
Page<MeasureListDTO> measures =
measureService.getMeasuresByCriteria(filterByCurrentUser, pageReq, username, decodedQuery);
measureService.getMeasuresByCriteria(
searchCriteria, filterByCurrentUser, pageReq, username);
measures.map(
measure -> {
MeasureSet measureSet =
Expand Down
21 changes: 14 additions & 7 deletions src/main/java/cms/gov/madie/measure/services/MeasureService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cms.gov.madie.measure.services;

import cms.gov.madie.measure.dto.MeasureListDTO;
import cms.gov.madie.measure.dto.MeasureSearchCriteria;
import cms.gov.madie.measure.exceptions.*;
import cms.gov.madie.measure.repositories.MeasureRepository;
import cms.gov.madie.measure.repositories.MeasureSetRepository;
Expand All @@ -23,6 +24,8 @@
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalTime;
Expand Down Expand Up @@ -310,9 +313,7 @@ private void updateMeasurementPeriods(Measure measure) {

public Page<MeasureListDTO> getMeasures(
boolean filterByCurrentUser, Pageable pageReq, String username) {
return filterByCurrentUser
? measureRepository.findMyActiveMeasures(username, pageReq, null)
: measureRepository.findAllByActive(true, pageReq);
return measureRepository.findActiveMeasures(username, pageReq, null, filterByCurrentUser);
}

public void checkDuplicateCqlLibraryName(String cqlLibraryName) {
Expand Down Expand Up @@ -431,10 +432,16 @@ public List<String> getAllActiveMeasureIds(boolean draftOnly) {
}

public Page<MeasureListDTO> getMeasuresByCriteria(
boolean filterByCurrentUser, Pageable pageReq, String username, String criteria) {
return filterByCurrentUser
? measureRepository.findMyActiveMeasures(username, pageReq, criteria)
: measureRepository.findAllByMeasureNameOrEcqmTitle(criteria, pageReq);
MeasureSearchCriteria searchCriteria,
boolean filterByCurrentUser,
Pageable pageReq,
String username) {
// We need to decode the encoded strings we send over or we can't find stuff
if (StringUtils.isNotBlank(searchCriteria.getQuery())) {
searchCriteria.setQuery(URLDecoder.decode(searchCriteria.getQuery(), StandardCharsets.UTF_8));
}
return measureRepository.findActiveMeasures(
username, pageReq, searchCriteria, filterByCurrentUser);
}

protected void updateReferenceId(MeasureMetaData metaData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import cms.gov.madie.measure.dto.FacetDTO;
import cms.gov.madie.measure.dto.MeasureListDTO;
import cms.gov.madie.measure.dto.MeasureSearchCriteria;
import gov.cms.madie.models.dto.LibraryUsage;
import org.bson.Document;

Expand Down Expand Up @@ -69,7 +70,7 @@ public void testFindMyActiveMeasures() {
.thenReturn(pagedResults);

Page<MeasureListDTO> page =
measureAclRepository.findMyActiveMeasures("john", pageRequest, null);
measureAclRepository.findActiveMeasures("john", pageRequest, null, true);
assertEquals(page.getTotalElements(), 5);
assertEquals(page.getTotalPages(), 2);
assertEquals(page.getContent().size(), 3);
Expand All @@ -90,8 +91,10 @@ public void testFindMyActiveMeasuresWithSearchTerm() {
when(mongoTemplate.aggregate(any(Aggregation.class), (Class<?>) any(), any()))
.thenReturn(pagedResults);

MeasureSearchCriteria measureSearchCriteria =
MeasureSearchCriteria.builder().query("test measure").build();
Page<MeasureListDTO> page =
measureAclRepository.findMyActiveMeasures("john", pageRequest, "test measure");
measureAclRepository.findActiveMeasures("john", pageRequest, measureSearchCriteria, true);
assertEquals(page.getTotalElements(), 2);
assertEquals(page.getTotalPages(), 1);
assertEquals(page.getContent().size(), 2);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.Set;

import cms.gov.madie.measure.dto.MeasureListDTO;
import cms.gov.madie.measure.dto.MeasureSearchCriteria;
import cms.gov.madie.measure.services.MeasureSetService;
import gov.cms.madie.models.access.AclOperation;
import gov.cms.madie.models.access.AclSpecification;
Expand Down Expand Up @@ -104,6 +105,7 @@ public class MeasureControllerMvcTest {
@Captor private ArgumentCaptor<String> targetIdArgumentCaptor;
@Captor private ArgumentCaptor<String> performedByArgumentCaptor;

ObjectMapper objectMapper = new ObjectMapper();
private static final String MODEL = ModelType.QI_CORE.toString();

private static final String LIBRARY_NAME_VALIDATION_ERROR =
Expand Down Expand Up @@ -1533,13 +1535,18 @@ public void testSearchMeasuresByMeasureNameOrEcqmTitleNoQueryParams() throws Exc

doReturn(allMeasures)
.when(measureService)
.getMeasuresByCriteria(eq(false), any(Pageable.class), eq(TEST_USER_ID), eq("measure"));
.getMeasuresByCriteria(
any(MeasureSearchCriteria.class), eq(false), any(Pageable.class), eq(TEST_USER_ID));
MvcResult result =
mockMvc
.perform(
get("/measures/search")
put("/measures/search")
.with(user(TEST_USER_ID))
.queryParam("query", "measure")
.with(csrf())
.content(
objectMapper.writeValueAsString(
MeasureSearchCriteria.builder().query("measure").build()))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
Expand All @@ -1548,7 +1555,8 @@ public void testSearchMeasuresByMeasureNameOrEcqmTitleNoQueryParams() throws Exc
assertTrue(resultStr.length() > 0);

verify(measureService, times(1))
.getMeasuresByCriteria(eq(false), any(Pageable.class), eq(TEST_USER_ID), eq("measure"));
.getMeasuresByCriteria(
any(MeasureSearchCriteria.class), eq(false), any(Pageable.class), eq(TEST_USER_ID));
verifyNoMoreInteractions(measureRepository);
}

Expand All @@ -1565,24 +1573,30 @@ public void testSearchMeasuresByMeasureNameOrEcqmTitleWithCurrentUserFalse() thr

doReturn(allMeasures)
.when(measureService)
.getMeasuresByCriteria(eq(false), any(Pageable.class), eq(TEST_USER_ID), eq("ecqm"));
.getMeasuresByCriteria(
any(MeasureSearchCriteria.class), eq(false), any(Pageable.class), eq(TEST_USER_ID));
MvcResult result =
mockMvc
.perform(
get("/measures/search")
put("/measures/search")
.with(user(TEST_USER_ID))
.queryParam("query", "ecqm")
.with(csrf())
.queryParam("currentUser", "false")
.queryParam("limit", "8")
.queryParam("page", "1")
.content(
objectMapper.writeValueAsString(
MeasureSearchCriteria.builder().query("ecqm").build()))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
String resultStr = result.getResponse().getContentAsString();

assertTrue(resultStr.length() > 0);
verify(measureService, times(1))
.getMeasuresByCriteria(eq(false), any(Pageable.class), eq(TEST_USER_ID), eq("ecqm"));
.getMeasuresByCriteria(
any(MeasureSearchCriteria.class), eq(false), any(Pageable.class), eq(TEST_USER_ID));
verifyNoMoreInteractions(measureRepository);
}

Expand All @@ -1599,24 +1613,30 @@ public void testSearchMeasuresByMeasureNameOrEcqmTitleFilterByCurrentUser() thro

doReturn(measures)
.when(measureService)
.getMeasuresByCriteria(eq(true), any(Pageable.class), eq(TEST_USER_ID), eq("measure"));
.getMeasuresByCriteria(
any(MeasureSearchCriteria.class), eq(true), any(Pageable.class), eq(TEST_USER_ID));
MvcResult result =
mockMvc
.perform(
get("/measures/search")
put("/measures/search")
.with(user(TEST_USER_ID))
.queryParam("query", "measure")
.with(csrf())
.queryParam("currentUser", "true")
.queryParam("limit", "8")
.queryParam("page", "1")
.content(
objectMapper.writeValueAsString(
MeasureSearchCriteria.builder().query("measure").build()))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
String resultStr = result.getResponse().getContentAsString();

assertTrue(resultStr.length() > 0);
verify(measureService, times(1))
.getMeasuresByCriteria(eq(true), any(Pageable.class), eq(TEST_USER_ID), eq("measure"));
.getMeasuresByCriteria(
any(MeasureSearchCriteria.class), eq(true), any(Pageable.class), eq(TEST_USER_ID));

verifyNoMoreInteractions(measureRepository);
}
Expand Down
Loading

0 comments on commit de2118d

Please sign in to comment.