Skip to content

Commit ce685a4

Browse files
[JN-1664] option to exclude in progress surveys (#1587)
Co-authored-by: Devon <[email protected]>
1 parent b45b8a2 commit ce685a4

File tree

12 files changed

+174
-27
lines changed

12 files changed

+174
-27
lines changed

core/src/main/java/bio/terra/pearl/core/dao/survey/SurveyResponseDao.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package bio.terra.pearl.core.dao.survey;
22

33
import bio.terra.pearl.core.dao.BaseMutableJdbiDao;
4-
import bio.terra.pearl.core.model.file.ParticipantFile;
54
import bio.terra.pearl.core.model.file.ScannedParticipantFileDto;
65
import bio.terra.pearl.core.model.survey.Answer;
76
import bio.terra.pearl.core.model.survey.SurveyResponse;
@@ -34,7 +33,7 @@ public List<SurveyResponse> findByEnrolleeId(UUID enrolleeId) {
3433
}
3534

3635
/** excludes responses that are associated with removed tasks */
37-
public Map<UUID, List<SurveyResponse>> findByEnrolleeIdsNotRemoved(List<UUID> enrolleeIds) {
36+
public Map<UUID, List<SurveyResponse>> findByEnrolleeIdsNotRemoved(List<UUID> enrolleeIds, boolean onlyComplete) {
3837
if (enrolleeIds.isEmpty()) {
3938
return Collections.emptyMap();
4039
}
@@ -45,8 +44,9 @@ public Map<UUID, List<SurveyResponse>> findByEnrolleeIdsNotRemoved(List<UUID> en
4544
select sr.* from %s sr
4645
left join participant_task task on task.survey_response_id = sr.id
4746
where sr.enrollee_id in (<enrolleeIds>)
47+
%s
4848
and task.status is distinct from 'REMOVED'
49-
""".formatted(tableName))
49+
""".formatted(tableName, onlyComplete ? "and task.status = 'COMPLETE' and sr.complete = true" : ""))
5050
.bindList("enrolleeIds", enrolleeIds)
5151
.mapTo(clazz)
5252
.stream().collect(Collectors.groupingBy(SurveyResponse::getEnrolleeId, Collectors.toList()))

core/src/main/java/bio/terra/pearl/core/model/export/ExportOptions.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import bio.terra.pearl.core.model.BaseEntity;
44
import bio.terra.pearl.core.service.export.ExportFileFormat;
55
import com.fasterxml.jackson.annotation.JsonIgnore;
6-
import lombok.*;
6+
import lombok.Builder;
7+
import lombok.Getter;
8+
import lombok.NoArgsConstructor;
9+
import lombok.Setter;
710
import lombok.experimental.SuperBuilder;
811

912
import java.time.ZoneId;
@@ -20,6 +23,8 @@ public class ExportOptions extends BaseEntity {
2023
private boolean stableIdsForOptions = false;
2124
@Builder.Default
2225
private boolean onlyIncludeMostRecent = true;
26+
@Builder.Default
27+
private boolean onlyIncludeCompleted = false;
2328
private String filterString;
2429
@Builder.Default
2530
private ExportFileFormat fileFormat = ExportFileFormat.TSV;

core/src/main/java/bio/terra/pearl/core/service/export/EnrolleeExportService.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import bio.terra.pearl.core.model.participant.*;
88
import bio.terra.pearl.core.model.search.EnrolleeSearchExpressionResult;
99
import bio.terra.pearl.core.model.study.Study;
10-
import bio.terra.pearl.core.model.study.StudyEnvironment;
1110
import bio.terra.pearl.core.model.study.StudyEnvironmentConfig;
1211
import bio.terra.pearl.core.model.survey.*;
1312
import bio.terra.pearl.core.model.workflow.ParticipantTask;
@@ -92,7 +91,7 @@ public EnrolleeExportService(ProfileService profileService,
9291
/**
9392
* exports the specified number of enrollees from the given environment
9493
* The enrollees will be returned most-recently-created first
95-
* */
94+
*/
9695
public void export(ExportOptionsWithExpression exportOptions, UUID studyEnvironmentId, OutputStream os) {
9796

9897
List<EnrolleeExportData> enrolleeExportData = loadEnrolleeExportData(studyEnvironmentId, exportOptions);
@@ -217,7 +216,7 @@ public List<EnrolleeExportData> loadEnrolleeExportData(UUID studyEnvironmentId,
217216
Map<UUID, List<SurveyResponseWithTaskDto>> surveyResponses =
218217
attachTasksToSurveyResponses(
219218
tasks,
220-
surveyResponseService.findByEnrolleeIdsNotRemoved(enrolleeIds));
219+
surveyResponseService.findByEnrolleeIdsNotRemoved(enrolleeIds, exportOptions.isOnlyIncludeCompleted()));
221220
Map<UUID, List<KitRequestDto>> kitRequests = kitRequestService.findByEnrollees(enrollees);
222221

223222
return enrollees.stream()

core/src/main/java/bio/terra/pearl/core/service/survey/SurveyResponseService.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ public List<SurveyResponse> findByEnrolleeId(UUID enrolleeId) {
6464
return dao.findByEnrolleeId(enrolleeId);
6565
}
6666

67-
public Map<UUID, List<SurveyResponse>> findByEnrolleeIdsNotRemoved(List<UUID> enrolleeIds) {
68-
return dao.findByEnrolleeIdsNotRemoved(enrolleeIds);
67+
public Map<UUID, List<SurveyResponse>> findByEnrolleeIdsNotRemoved(List<UUID> enrolleeIds, boolean onlyComplete) {
68+
return dao.findByEnrolleeIdsNotRemoved(enrolleeIds, onlyComplete);
6969
}
7070

7171
public Optional<SurveyResponse> findOneWithAnswers(UUID responseId) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
databaseChangeLog:
2+
- changeSet:
3+
id: "export_options_only_complete"
4+
author: connorlbark
5+
changes:
6+
- addColumn:
7+
tableName: export_options
8+
columns:
9+
- column:
10+
name: only_include_completed
11+
type: boolean
12+
defaultValueBoolean: false

core/src/main/resources/db/changelog/db.changelog-master.yaml

+3-1
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,9 @@ databaseChangeLog:
401401
- include:
402402
file: changesets/2025_02_14_trigger_admin_email_filter.yaml
403403
relativeToChangelogFile: true
404-
404+
- include:
405+
file: changesets/2025_04_11_export_option_only_complete.yaml
406+
relativeToChangelogFile: true
405407

406408
# README: it is a best practice to put each DDL statement in its own change set. DDL statements
407409
# are atomic. When they are grouped in a changeset and one fails the changeset cannot be

core/src/test/java/bio/terra/pearl/core/service/export/EnrolleeExportServiceTests.java

+97
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@
1717
import bio.terra.pearl.core.model.study.StudyEnvironment;
1818
import bio.terra.pearl.core.model.study.StudyEnvironmentConfig;
1919
import bio.terra.pearl.core.model.survey.Survey;
20+
import bio.terra.pearl.core.model.survey.SurveyResponse;
2021
import bio.terra.pearl.core.model.survey.SurveyResponseWithTaskDto;
2122
import bio.terra.pearl.core.model.survey.SurveyType;
23+
import bio.terra.pearl.core.model.workflow.ParticipantTask;
24+
import bio.terra.pearl.core.model.workflow.TaskStatus;
25+
import bio.terra.pearl.core.model.workflow.TaskType;
2226
import bio.terra.pearl.core.service.export.formatters.ExportFormatUtils;
2327
import bio.terra.pearl.core.service.export.formatters.item.AnswerItemFormatter;
2428
import bio.terra.pearl.core.service.export.formatters.item.ItemFormatter;
@@ -745,4 +749,97 @@ public void testExportSubheadersOption(TestInfo testInfo) throws Exception {
745749
assertThat(export, containsString(",enrollee.createdAt"));
746750
assertThat(export, containsString(",Created At"));
747751
}
752+
753+
@Test
754+
@Transactional
755+
public void testOnlyCompleteSurveyResponses(TestInfo testInfo) throws Exception {
756+
757+
ParticipantTask.ParticipantTaskBuilder taskBuilder = ParticipantTask.builder()
758+
.status(TaskStatus.NEW)
759+
.taskType(TaskType.SURVEY)
760+
.targetName("test")
761+
.taskOrder(1);
762+
763+
String testName = getTestName(testInfo);
764+
StudyEnvironmentBundle studyEnvBundle = studyEnvironmentFactory.buildBundle(testName, EnvironmentName.sandbox);
765+
StudyEnvironment studyEnv = studyEnvBundle.getStudyEnv();
766+
PortalEnvironment portalEnv = studyEnvBundle.getPortalEnv();
767+
Survey survey = surveyService.create(
768+
surveyFactory
769+
.builderWithDependencies(getTestName(testInfo))
770+
.content(SOCIAL_HEALTH_EXCERPT)
771+
.name("Survey Test")
772+
.stableId("examplesurvey")
773+
.surveyType(SurveyType.RESEARCH)
774+
.version(1)
775+
.autoAssign(false)
776+
.build());
777+
778+
surveyFactory.attachToEnv(survey, studyEnv.getId(), true);
779+
780+
EnrolleeBundle enrollee1Bundle = enrolleeFactory.buildWithPortalUser(testName, portalEnv, studyEnv, new Profile());
781+
Enrollee enrollee1 = enrollee1Bundle.enrollee();
782+
783+
SurveyResponse e1r1 = surveyResponseFactory.buildWithAnswers(
784+
enrollee1,
785+
survey,
786+
Map.of(
787+
"hd_hd_socialHealth_neighborhoodIsWalkable", "agree"
788+
),
789+
true
790+
);
791+
792+
SurveyResponse e1r2 = surveyResponseFactory.buildWithAnswers(
793+
enrollee1,
794+
survey,
795+
Map.of(
796+
"hd_hd_socialHealth_neighborhoodIsWalkable", "disagree"
797+
)
798+
);
799+
800+
participantTaskFactory.buildPersisted(enrollee1Bundle,
801+
taskBuilder
802+
.surveyResponseId(e1r1.getId())
803+
.status(TaskStatus.COMPLETE));
804+
participantTaskFactory.buildPersisted(enrollee1Bundle,
805+
taskBuilder
806+
.surveyResponseId(e1r2.getId())
807+
.status(TaskStatus.IN_PROGRESS));
808+
809+
810+
Enrollee enrollee2 = enrolleeFactory.buildPersisted(testName, studyEnv, new Profile());
811+
812+
SurveyResponse e2r1 = surveyResponseFactory.buildWithAnswers(
813+
enrollee2,
814+
survey,
815+
Map.of(
816+
"hd_hd_socialHealth_neighborhoodIsWalkable", "agree"
817+
)
818+
);
819+
820+
participantTaskFactory.buildPersisted(enrollee1Bundle,
821+
taskBuilder
822+
.surveyResponseId(e2r1.getId())
823+
.status(TaskStatus.IN_PROGRESS));
824+
825+
826+
ExportOptionsWithExpression exportOptions = new ExportOptionsWithExpression();
827+
828+
exportOptions.setOnlyIncludeCompleted(true);
829+
830+
List<EnrolleeExportData> exportData = enrolleeExportService.loadEnrolleeExportData(studyEnv.getId(), exportOptions);
831+
List<ModuleFormatter> moduleFormatters = enrolleeExportService.generateModuleInfos(new ExportOptions(), studyEnv.getId(), exportData);
832+
List<Map<String, String>> exportMaps = enrolleeExportService.generateExportMaps(exportData, moduleFormatters);
833+
834+
835+
assertThat(exportMaps, hasSize(2));
836+
837+
Map<String, String> enrollee1Map = exportMaps.stream().filter(map -> map.get("enrollee.shortcode").equals(enrollee1.getShortcode())).findFirst().get();
838+
Map<String, String> enrollee2Map = exportMaps.stream().filter(map -> map.get("enrollee.shortcode").equals(enrollee2.getShortcode())).findFirst().get();
839+
840+
assertThat(enrollee1Map.get("examplesurvey.hd_hd_socialHealth_neighborhoodIsWalkable"), equalTo("Agree"));
841+
assertThat(enrollee1Map.containsKey("examplesurvey.hd_hd_socialHealth_neighborhoodIsWalkable[1]"), equalTo(false));
842+
843+
assertThat(enrollee2Map.containsKey("examplesurvey.hd_hd_socialHealth_neighborhoodIsWalkable"), equalTo(false));
844+
}
748845
}

core/src/test/java/bio/terra/pearl/core/service/workflow/ParticipantTaskServiceTests.java

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import bio.terra.pearl.core.model.EnvironmentName;
1212
import bio.terra.pearl.core.model.audit.ResponsibleEntity;
1313
import bio.terra.pearl.core.model.workflow.ParticipantTask;
14+
import bio.terra.pearl.core.model.workflow.TaskStatus;
15+
import bio.terra.pearl.core.model.workflow.TaskType;
1416
import bio.terra.pearl.core.service.survey.SurveyResponseService;
1517
import org.junit.jupiter.api.Test;
1618
import org.junit.jupiter.api.TestInfo;

core/src/testFixtures/java/bio/terra/pearl/core/factory/survey/SurveyResponseFactory.java

+14-4
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
import bio.terra.pearl.core.factory.participant.EnrolleeFactory;
55
import bio.terra.pearl.core.model.audit.ResponsibleEntity;
66
import bio.terra.pearl.core.model.participant.Enrollee;
7-
import bio.terra.pearl.core.model.portal.Portal;
8-
import bio.terra.pearl.core.model.survey.*;
7+
import bio.terra.pearl.core.model.survey.Answer;
8+
import bio.terra.pearl.core.model.survey.AnswerType;
9+
import bio.terra.pearl.core.model.survey.Survey;
10+
import bio.terra.pearl.core.model.survey.SurveyResponse;
911
import bio.terra.pearl.core.model.workflow.HubResponse;
1012
import bio.terra.pearl.core.model.workflow.ParticipantTask;
1113
import bio.terra.pearl.core.service.survey.SurveyResponseService;
@@ -15,7 +17,6 @@
1517

1618
import java.util.List;
1719
import java.util.Map;
18-
import java.util.UUID;
1920

2021
@Component
2122
public class SurveyResponseFactory {
@@ -47,12 +48,21 @@ public SurveyResponse buildPersisted(String testName) {
4748
return surveyResponseService.create(builderWithDependencies(testName).build());
4849
}
4950

50-
/** to create a response with an objectValued answer, use a JsonNode value in the answerMap */
51+
/**
52+
* to create a response with an objectValued answer, use a JsonNode value in the answerMap
53+
*/
5154
public SurveyResponse buildWithAnswers(Enrollee enrollee, Survey survey, Map<String, Object> answerMap) {
55+
return buildWithAnswers(enrollee, survey, answerMap, false);
56+
}
57+
58+
59+
/** to create a response with an objectValued answer, use a JsonNode value in the answerMap */
60+
public SurveyResponse buildWithAnswers(Enrollee enrollee, Survey survey, Map<String, Object> answerMap, boolean complete) {
5261
SurveyResponse response = surveyResponseService.create(SurveyResponse.builder()
5362
.enrolleeId(enrollee.getId())
5463
.creatingParticipantUserId(enrollee.getParticipantUserId())
5564
.surveyId(survey.getId())
65+
.complete(complete)
5666
.build());
5767
List<Answer> answers = answerFactory.createFromMap(answerMap, enrollee, survey, response);
5868
response.setAnswers(answers);

populate/src/main/resources/seed/portals/demo/studies/heartdemo/enrollees/jsalk.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
{"questionStableId": "neighborhoodMatrix", "objectValue": "{\"affordable\":5,\"friendlyPeople\":4,\"betterThanOthers\":5,\"easyTravelTo\":3}\n", "viewedLanguage": "en"}
5454
],
5555
"currentPageNo": 1,
56-
"complete": true,
56+
"complete": false,
5757
"submittedHoursAgo": 246
5858
},
5959
{

ui-admin/src/api/api.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ export type ExportOptions = {
289289
splitOptionsIntoColumns?: boolean,
290290
stableIdsForOptions?: boolean,
291291
onlyIncludeMostRecent?: boolean,
292+
onlyIncludeCompleted?: boolean,
292293
includeSubHeaders?: boolean,
293294
excludeModules?: string[],
294295
filterString?: string,

0 commit comments

Comments
 (0)