From 5a144eb35d0b2ac55bf33df2ff78775831e026f8 Mon Sep 17 00:00:00 2001 From: Jakob Frank Date: Tue, 30 Apr 2024 16:44:10 +0200 Subject: [PATCH 1/2] #224 Initial Outline of the Study-Preview --- .../more/studymanager/model/Study.java | 16 +++- .../repository/ParticipantRepository.java | 11 ++- .../repository/StudyRepository.java | 46 +++++++---- .../service/InterventionService.java | 3 +- .../service/ObservationService.java | 3 +- .../service/ParticipantService.java | 11 ++- .../studymanager/service/StudyService.java | 78 ++++++++++--------- .../db/migration/V1_15_0__study_preview.sql | 2 + .../resources/openapi/StudyManagerAPI.yaml | 2 + .../repository/StudyRepositoryTest.java | 17 +++- 10 files changed, 131 insertions(+), 58 deletions(-) create mode 100644 studymanager/src/main/resources/db/migration/V1_15_0__study_preview.sql diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/Study.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/Study.java index 870f9a5b..a1fe0447 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/model/Study.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/Study.java @@ -36,7 +36,10 @@ public enum Status { DRAFT("draft"), ACTIVE("active"), PAUSED("paused"), - CLOSED("closed"); + CLOSED("closed"), + PREVIEW("preview"), + PAUSED_PREVIEW("paused-preview"), + ; private final String value; @@ -47,6 +50,17 @@ public enum Status { public String getValue() { return value; } + + public static Status fromValue(String value) { + for (Status c : Status.values()) { + if (c.value.equalsIgnoreCase(value)) { + return c; + } + } + throw new IllegalArgumentException( + "No enum constant " + Status.class.getCanonicalName() + " with value " + value + ); + } } public Long getStudyId() { diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/repository/ParticipantRepository.java b/studymanager/src/main/java/io/redlink/more/studymanager/repository/ParticipantRepository.java index 7abedf7d..046ec8a0 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/repository/ParticipantRepository.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/repository/ParticipantRepository.java @@ -24,13 +24,17 @@ import org.springframework.transaction.annotation.Transactional; import static io.redlink.more.studymanager.repository.RepositoryUtils.getValidNullableIntegerValue; -import static io.redlink.more.studymanager.repository.RepositoryUtils.toParam; @Component public class ParticipantRepository { private static final String INSERT_PARTICIPANT_AND_TOKEN = "WITH p AS (INSERT INTO participants(study_id,participant_id,alias,study_group_id) VALUES (:study_id,(SELECT COALESCE(MAX(participant_id),0)+1 FROM participants WHERE study_id = :study_id),:alias,:study_group_id) RETURNING participant_id, study_id) INSERT INTO registration_tokens(participant_id,study_id,token) SELECT participant_id, study_id, :token FROM p"; + private static final String UPDATE_REGISTRATION_TOKEN = """ + INSERT INTO registration_tokens(study_id, participant_id, token) + VALUES (:study_id, :participant_id, :token) + ON CONFLICT (study_id, participant_id) DO UPDATE SET token = excluded.token + """; private static final String GET_PARTICIPANT_BY_IDS = "SELECT p.participant_id, p.study_id, p.alias, p.study_group_id, r.token as token, p.status, p.created, p.modified, p.start FROM participants p LEFT JOIN registration_tokens r ON p.study_id = r.study_id AND p.participant_id = r.participant_id WHERE p.study_id = ? AND p.participant_id = ?"; private static final String LIST_PARTICIPANTS_BY_STUDY = "SELECT p.participant_id, p.study_id, p.alias, p.study_group_id, r.token as token, p.status, p.created, p.modified, p.start FROM participants p LEFT JOIN registration_tokens r ON p.study_id = r.study_id AND p.participant_id = r.participant_id WHERE p.study_id = ?"; private static final String DELETE_PARTICIPANT = @@ -132,6 +136,11 @@ public void cleanupParticipants(Long studyId) { namedTemplate.update("DELETE FROM push_notifications_token WHERE study_id = :study_id", params); } + public void updateRegistrationToken(Long studyId, Integer participantId, String registrationToken) { + namedTemplate.update(UPDATE_REGISTRATION_TOKEN, + toParams(studyId, participantId).addValue("token", registrationToken)); + } + public void clear() { template.update(DELETE_ALL); } diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java b/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java index 42c8ff41..f448f79a 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyRepository.java @@ -57,10 +57,14 @@ public class StudyRepository { private static final String DELETE_BY_ID = "DELETE FROM studies WHERE study_id = ?"; private static final String CLEAR_STUDIES = "DELETE FROM studies"; - private static final String SET_DRAFT_STATE_BY_ID = "UPDATE studies SET status = 'draft', start_date = NULL, end_date = NULL, modified = now() WHERE study_id = ?"; - private static final String SET_ACTIVE_STATE_BY_ID = "UPDATE studies SET status = 'active', start_date = now(), modified = now() WHERE study_id = ?"; - private static final String SET_PAUSED_STATE_BY_ID = "UPDATE studies SET status = 'paused', modified = now() WHERE study_id = ?"; - private static final String SET_CLOSED_STATE_BY_ID = "UPDATE studies SET status = 'closed', end_date = now(), modified = now() WHERE study_id = ?"; + private static final String SET_STUDY_STATE = """ + UPDATE studies + SET status = :newState::study_state, + modified = now(), + start_date = CASE WHEN :setStart = 0 THEN NULL WHEN :setStart = 1 THEN now() ELSE start_date END, + end_date = CASE WHEN :setEnd = 0 THEN NULL WHEN :setEnd = 1 THEN now() ELSE end_date END + WHERE study_id = :studyId + RETURNING *"""; private static final String STUDY_HAS_STATE = "SELECT study_id FROM studies WHERE study_id = :study_id AND status::varchar IN (:study_status)"; private final JdbcTemplate template; @@ -116,17 +120,29 @@ public void deleteById(long id) { template.update(DELETE_BY_ID, id); } - public void setStateById(long id, Study.Status status) { - template.update(getStatusQuery(status), id); - } + public Optional setStateById(long id, Study.Status status) { + final int toNull = 0, toNow = 1, keepCurrentValue = -1; + int setStart = keepCurrentValue, setEnd = keepCurrentValue; + switch (status) { + case DRAFT -> { + setStart = toNull; + setEnd = toNull; + } + case ACTIVE, PREVIEW -> setStart = toNow; + case CLOSED -> setEnd = toNow; + } - private String getStatusQuery(Study.Status status) { - return switch (status) { - case DRAFT -> SET_DRAFT_STATE_BY_ID; - case ACTIVE -> SET_ACTIVE_STATE_BY_ID; - case PAUSED -> SET_PAUSED_STATE_BY_ID; - case CLOSED -> SET_CLOSED_STATE_BY_ID; - }; + try (var stream = namedTemplate.queryForStream(SET_STUDY_STATE, + new MapSqlParameterSource() + .addValue("studyId", id) + .addValue("newState", status.getValue()) + .addValue("setStart", setStart) + .addValue("setEnd", setEnd), + getStudyRowMapper() + )) { + return stream + .findFirst(); + } } private static MapSqlParameterSource studyToParams(Study study) { @@ -161,7 +177,7 @@ private static RowMapper getStudyRowMapper() { .setDuration(MapperUtils.readValue(rs.getString("duration"), Duration.class)) .setCreated(RepositoryUtils.readInstant(rs, "created")) .setModified(RepositoryUtils.readInstant(rs, "modified")) - .setStudyState(Study.Status.valueOf(rs.getString("status").toUpperCase())) + .setStudyState(Study.Status.fromValue(rs.getString("status").toUpperCase())) .setContact(new Contact() .setInstitute(rs.getString("institute")) .setPerson(rs.getString("contact_person")) diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java index 1f20bec0..77cf7406 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java @@ -26,6 +26,7 @@ import io.redlink.more.studymanager.sdk.MoreSDK; import io.redlink.more.studymanager.utils.LoggingUtils; import java.text.ParseException; +import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -152,7 +153,7 @@ public void onStartUp() { } public void alignInterventionsWithStudyState(Study study) { - if (study.getStudyState() == Study.Status.ACTIVE) { + if (EnumSet.of(Study.Status.ACTIVE, Study.Status.PREVIEW).contains(study.getStudyState())) { activateInterventionsFor(study); } else { deactivateInterventionsFor(study); diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java index 85510543..38ae474f 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java @@ -18,6 +18,7 @@ import io.redlink.more.studymanager.model.Study; import io.redlink.more.studymanager.repository.ObservationRepository; import io.redlink.more.studymanager.sdk.MoreSDK; +import java.util.EnumSet; import org.springframework.stereotype.Service; import java.util.List; @@ -79,7 +80,7 @@ public Observation updateObservation(Observation observation) { } public void alignObservationsWithStudyState(Study study){ - if(study.getStudyState() == Study.Status.ACTIVE) + if (EnumSet.of(Study.Status.ACTIVE, Study.Status.PREVIEW).contains(study.getStudyState())) activateObservationsFor(study); else deactivateObservationsFor(study); } diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/ParticipantService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/ParticipantService.java index 9fecaaf0..19df9a37 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/service/ParticipantService.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/ParticipantService.java @@ -15,9 +15,9 @@ import java.util.EnumSet; import java.util.List; -import java.util.Optional; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import static io.redlink.more.studymanager.model.Participant.Status.*; @@ -66,10 +66,17 @@ public Participant updateParticipant(Participant participant) { return participantRepository.update(participant); } + @Transactional public void alignParticipantsWithStudyState(Study study) { - if (study.getStudyState() == Study.Status.CLOSED) { + if (EnumSet.of(Study.Status.CLOSED, Study.Status.DRAFT).contains(study.getStudyState())) { participantRepository.cleanupParticipants(study.getStudyId()); } + if (EnumSet.of(Study.Status.PREVIEW).contains(study.getStudyState())) { + participantRepository.listParticipants(study.getStudyId()) + .forEach(p -> participantRepository.updateRegistrationToken( + p.getStudyId(), p.getParticipantId(), RandomTokenGenerator.generate() + )); + } } public void setStatus(Long studyId, Integer participantId, Participant.Status status) { diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyService.java index 81247031..2ba89bb0 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyService.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyService.java @@ -22,11 +22,21 @@ import org.springframework.stereotype.Service; import java.util.*; +import org.springframework.transaction.annotation.Transactional; @Service public class StudyService { private static final Logger log = LoggerFactory.getLogger(StudyService.class); + + private static final Map> VALID_STUDY_TRANSITIONS = Map.of( + Study.Status.DRAFT, EnumSet.of(Study.Status.PREVIEW, Study.Status.ACTIVE), + Study.Status.PREVIEW, EnumSet.of(Study.Status.PAUSED_PREVIEW, Study.Status.DRAFT), + Study.Status.PAUSED_PREVIEW, EnumSet.of(Study.Status.DRAFT), + Study.Status.ACTIVE, EnumSet.of(Study.Status.PAUSED, Study.Status.CLOSED), + Study.Status.PAUSED, EnumSet.of(Study.Status.ACTIVE) + ); + private final StudyRepository studyRepository; private final StudyAclRepository aclRepository; private final UserRepository userRepo; @@ -91,52 +101,48 @@ public void deleteStudy(Long studyId) { elasticService.deleteIndex(studyId); } - public void setStatus(Long studyId, Study.Status status, User user) { - Study study = getStudy(studyId, user) + @Transactional + public void setStatus(Long studyId, Study.Status newState, User user) { + final Study study = getStudy(studyId, user) .orElseThrow(() -> NotFoundException.Study(studyId)); - if (status.equals(Study.Status.DRAFT)) { - throw BadRequestException.StateChange(study.getStudyState(), Study.Status.DRAFT); - } - if (study.getStudyState().equals(Study.Status.CLOSED)) { - throw BadRequestException.StateChange(Study.Status.CLOSED, status); - } - if (study.getStudyState().equals(status)) { - throw BadRequestException.StateChange(study.getStudyState(), status); + final Study.Status oldState = study.getStudyState(); + + /* Validate the transition */ + if (!VALID_STUDY_TRANSITIONS.getOrDefault(oldState, EnumSet.noneOf(Study.Status.class)).contains(newState)) { + throw BadRequestException.StateChange(oldState, newState); } - Study.Status oldState = study.getStudyState(); - - studyRepository.setStateById(studyId, status); - studyRepository.getById(studyId).ifPresent(s -> { - try { - alignWithStudyState(s); - participantService.listParticipants(studyId).forEach(participant -> { - pushNotificationService.sendPushNotification( - studyId, - participant.getParticipantId(), - "Your Study has a new update", - "Your study was updated. For more information, please launch the app!", - Map.of("key", "STUDY_STATE_CHANGED", - "oldState", oldState.getValue(), - "newState", s.getStudyState().getValue()) - ); + studyRepository.setStateById(studyId, newState) + .ifPresent(s -> { + try { + alignWithStudyState(s); + participantService.listParticipants(studyId).forEach(participant -> + pushNotificationService.sendPushNotification( + studyId, + participant.getParticipantId(), + "Your Study has a new update", + "Your study was updated. For more information, please launch the app!", + Map.of("key", "STUDY_STATE_CHANGED", + "oldState", oldState.getValue(), + "newState", s.getStudyState().getValue()) + ) + ); + participantService.alignParticipantsWithStudyState(s); + } catch (Exception e) { + log.warn("Could not set new state for study id {}; old state: {}; new state: {}", studyId, oldState.getValue(), s.getStudyState().getValue()); + //ROLLBACK + studyRepository.setStateById(studyId, oldState); + studyRepository.getById(studyId).ifPresent(this::alignWithStudyState); + throw new BadRequestException("Study cannot be initialized", e); + } }); - participantService.alignParticipantsWithStudyState(s); - } catch (Exception e) { - log.warn("Could not set new state for study id {}; old state: {}; new state: {}", studyId, oldState.getValue(), s.getStudyState().getValue()); - //ROLLBACK - studyRepository.setStateById(studyId, oldState); - studyRepository.getById(studyId).ifPresent(this::alignWithStudyState); - throw new BadRequestException("Study cannot be initialized",e); - } - }); } // every minute @Scheduled(cron = "0 * * * * ?") public void closeParticipationsForStudiesWithDurations() { List participantsToClose = participantService.listParticipantsForClosing(); - log.debug("Selected {} paticipants to close", participantsToClose.size()); + log.debug("Selected {} participants to close", participantsToClose.size()); participantsToClose.forEach(participant -> { pushNotificationService.sendPushNotification( participant.getStudyId(), diff --git a/studymanager/src/main/resources/db/migration/V1_15_0__study_preview.sql b/studymanager/src/main/resources/db/migration/V1_15_0__study_preview.sql new file mode 100644 index 00000000..8957ac83 --- /dev/null +++ b/studymanager/src/main/resources/db/migration/V1_15_0__study_preview.sql @@ -0,0 +1,2 @@ +ALTER TYPE study_state ADD VALUE 'preview'; +ALTER TYPE study_state ADD VALUE 'paused-preview'; diff --git a/studymanager/src/main/resources/openapi/StudyManagerAPI.yaml b/studymanager/src/main/resources/openapi/StudyManagerAPI.yaml index 16d8a6dd..7855db6a 100644 --- a/studymanager/src/main/resources/openapi/StudyManagerAPI.yaml +++ b/studymanager/src/main/resources/openapi/StudyManagerAPI.yaml @@ -1356,6 +1356,8 @@ components: type: string enum: - draft + - preview + - paused-preview - active - paused - closed diff --git a/studymanager/src/test/java/io/redlink/more/studymanager/repository/StudyRepositoryTest.java b/studymanager/src/test/java/io/redlink/more/studymanager/repository/StudyRepositoryTest.java index ade58668..c76d7122 100644 --- a/studymanager/src/test/java/io/redlink/more/studymanager/repository/StudyRepositoryTest.java +++ b/studymanager/src/test/java/io/redlink/more/studymanager/repository/StudyRepositoryTest.java @@ -131,9 +131,24 @@ void testSetState() { Study study = studyRepository.insert(new Study().setContact(new Contact().setPerson("test").setEmail("test"))); assertThat(study.getStudyState()).isEqualTo(Study.Status.DRAFT); assertThat(study.getStartDate()).isNull(); + assertThat(study.getEndDate()).isNull(); - studyRepository.setStateById(study.getStudyId(), Study.Status.ACTIVE); + study = assertPresent(studyRepository.setStateById(study.getStudyId(), Study.Status.PREVIEW)); + assertThat(study.getStudyState()).isEqualTo(Study.Status.PREVIEW); + assertThat(study.getStartDate()).isNotNull(); + assertThat(study.getEndDate()).isNull(); + + study = assertPresent(studyRepository.setStateById(study.getStudyId(), Study.Status.PAUSED_PREVIEW)); + assertThat(study.getStudyState()).isEqualTo(Study.Status.PAUSED_PREVIEW); + assertThat(study.getStartDate()).isNotNull(); + assertThat(study.getEndDate()).isNull(); + + study = assertPresent(studyRepository.setStateById(study.getStudyId(), Study.Status.DRAFT)); + assertThat(study.getStudyState()).isEqualTo(Study.Status.DRAFT); + assertThat(study.getStartDate()).isNull(); + assertThat(study.getEndDate()).isNull(); + studyRepository.setStateById(study.getStudyId(), Study.Status.ACTIVE); study = assertPresent(studyRepository.getById(study.getStudyId())); assertThat(study.getStudyState()).isEqualTo(Study.Status.ACTIVE); assertThat(study.getStartDate()).isNotNull(); From 6c54dd7c8a779a578fc0370f4d0e683a6bcd352e Mon Sep 17 00:00:00 2001 From: Jakob Frank Date: Sun, 12 May 2024 17:20:59 +0200 Subject: [PATCH 2/2] #224 Study-Preview Mode: Cleanup after Preview - Reset Participant-Status - Recreate Registration-Token - Clear Study-Data -> Tell the Gateway to collect data in Preview-Mode --- .../model/transformer/StudyTransformer.java | 9 ++++---- .../repository/ParticipantRepository.java | 22 ++++++++++++++++--- .../repository/RepositoryUtils.java | 9 ++++++++ .../studymanager/service/ElasticService.java | 8 ++++++- .../service/ParticipantService.java | 9 +++----- .../studymanager/service/StudyService.java | 5 +++-- .../V1_15_1__extend_gateway_for_preview.sql | 9 ++++++++ 7 files changed, 55 insertions(+), 16 deletions(-) create mode 100644 studymanager/src/main/resources/db/migration/V1_15_1__extend_gateway_for_preview.sql diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyTransformer.java index 5253deb2..24ac7edd 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyTransformer.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyTransformer.java @@ -8,13 +8,14 @@ */ package io.redlink.more.studymanager.model.transformer; -import io.redlink.more.studymanager.api.v1.model.*; +import io.redlink.more.studymanager.api.v1.model.ContactDTO; +import io.redlink.more.studymanager.api.v1.model.StatusChangeDTO; +import io.redlink.more.studymanager.api.v1.model.StudyDTO; +import io.redlink.more.studymanager.api.v1.model.StudyStatusDTO; import io.redlink.more.studymanager.model.Contact; import io.redlink.more.studymanager.model.Study; import io.redlink.more.studymanager.model.scheduler.Duration; -import java.util.Optional; - public class StudyTransformer { private StudyTransformer() {} @@ -60,6 +61,6 @@ public static StudyDTO toStudyDTO_V1(Study study) { } public static Study.Status fromStatusChangeDTO_V1(StatusChangeDTO statusChangeDTO) { - return Study.Status.valueOf(statusChangeDTO.getStatus().getValue().toUpperCase()); + return Study.Status.fromValue(statusChangeDTO.getStatus().getValue()); } } diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/repository/ParticipantRepository.java b/studymanager/src/main/java/io/redlink/more/studymanager/repository/ParticipantRepository.java index 046ec8a0..de03f205 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/repository/ParticipantRepository.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/repository/ParticipantRepository.java @@ -8,6 +8,7 @@ */ package io.redlink.more.studymanager.repository; +import com.google.common.base.Supplier; import io.redlink.more.studymanager.exception.BadRequestException; import io.redlink.more.studymanager.model.Participant; import java.util.List; @@ -24,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional; import static io.redlink.more.studymanager.repository.RepositoryUtils.getValidNullableIntegerValue; +import static io.redlink.more.studymanager.repository.RepositoryUtils.intReader; @Component public class ParticipantRepository { @@ -136,9 +138,23 @@ public void cleanupParticipants(Long studyId) { namedTemplate.update("DELETE FROM push_notifications_token WHERE study_id = :study_id", params); } - public void updateRegistrationToken(Long studyId, Integer participantId, String registrationToken) { - namedTemplate.update(UPDATE_REGISTRATION_TOKEN, - toParams(studyId, participantId).addValue("token", registrationToken)); + @Transactional + public void resetParticipants(final Long studyId, final Supplier tokenSource) { + // First clear credentials and tokens... + cleanupParticipants(studyId); + // ... then reset participant-status and start-date ... + final var pIDs = namedTemplate.query( + "UPDATE participants SET status = DEFAULT, start = NULL WHERE study_id = :study_id RETURNING *", + toParams(studyId), + intReader("participant_id") + ); + // ... and finally create new token for the participants + namedTemplate.batchUpdate( + UPDATE_REGISTRATION_TOKEN, + pIDs.stream() + .map(pid -> toParams(studyId, pid).addValue("token", tokenSource.get())) + .toArray(MapSqlParameterSource[]::new) + ); } public void clear() { diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/repository/RepositoryUtils.java b/studymanager/src/main/java/io/redlink/more/studymanager/repository/RepositoryUtils.java index 384e6f74..5c6154d3 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/repository/RepositoryUtils.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/repository/RepositoryUtils.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import java.time.Instant; import java.time.LocalDate; +import org.springframework.jdbc.core.RowMapper; public final class RepositoryUtils { @@ -69,4 +70,12 @@ public static String toParam(Participant.Status status) { case LOCKED -> "locked"; }; } + + public static RowMapper intReader(String columnLabel) { + return (rs, rowNum) -> { + int anInt = rs.getInt(columnLabel); + if (rs.wasNull()) return null; + return anInt; + }; + } } diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/ElasticService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/ElasticService.java index 72ddb09d..376bd9d9 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/service/ElasticService.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/ElasticService.java @@ -29,6 +29,7 @@ import io.redlink.more.studymanager.model.data.SimpleDataPoint; import io.redlink.more.studymanager.properties.ElasticProperties; import io.redlink.more.studymanager.utils.MapperUtils; +import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -265,7 +266,12 @@ public List getParticipationData(Long studyId){ } return participationDataList; }catch (IOException | ElasticsearchException e) { - LOG.error("Elastic Query failed", e); + if (e instanceof ElasticsearchException ee) { + if (Objects.equals(ee.error().type(), "index_not_found_exception")) { + return List.of(); + } + } + LOG.warn("Elastic Query failed", e); return new ArrayList<>(); } } diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/ParticipantService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/ParticipantService.java index 19df9a37..af812635 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/service/ParticipantService.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/ParticipantService.java @@ -68,14 +68,11 @@ public Participant updateParticipant(Participant participant) { @Transactional public void alignParticipantsWithStudyState(Study study) { - if (EnumSet.of(Study.Status.CLOSED, Study.Status.DRAFT).contains(study.getStudyState())) { + if (EnumSet.of(Study.Status.CLOSED).contains(study.getStudyState())) { participantRepository.cleanupParticipants(study.getStudyId()); } - if (EnumSet.of(Study.Status.PREVIEW).contains(study.getStudyState())) { - participantRepository.listParticipants(study.getStudyId()) - .forEach(p -> participantRepository.updateRegistrationToken( - p.getStudyId(), p.getParticipantId(), RandomTokenGenerator.generate() - )); + if (EnumSet.of(Study.Status.DRAFT).contains(study.getStudyState())) { + participantRepository.resetParticipants(study.getStudyId(), RandomTokenGenerator::generate); } } diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyService.java index 2ba89bb0..85e1a848 100644 --- a/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyService.java +++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyService.java @@ -32,9 +32,9 @@ public class StudyService { private static final Map> VALID_STUDY_TRANSITIONS = Map.of( Study.Status.DRAFT, EnumSet.of(Study.Status.PREVIEW, Study.Status.ACTIVE), Study.Status.PREVIEW, EnumSet.of(Study.Status.PAUSED_PREVIEW, Study.Status.DRAFT), - Study.Status.PAUSED_PREVIEW, EnumSet.of(Study.Status.DRAFT), + Study.Status.PAUSED_PREVIEW, EnumSet.of(Study.Status.PREVIEW, Study.Status.DRAFT), Study.Status.ACTIVE, EnumSet.of(Study.Status.PAUSED, Study.Status.CLOSED), - Study.Status.PAUSED, EnumSet.of(Study.Status.ACTIVE) + Study.Status.PAUSED, EnumSet.of(Study.Status.ACTIVE, Study.Status.CLOSED) ); private final StudyRepository studyRepository; @@ -128,6 +128,7 @@ public void setStatus(Long studyId, Study.Status newState, User user) { ) ); participantService.alignParticipantsWithStudyState(s); + elasticService.deleteIndex(s.getStudyId()); } catch (Exception e) { log.warn("Could not set new state for study id {}; old state: {}; new state: {}", studyId, oldState.getValue(), s.getStudyState().getValue()); //ROLLBACK diff --git a/studymanager/src/main/resources/db/migration/V1_15_1__extend_gateway_for_preview.sql b/studymanager/src/main/resources/db/migration/V1_15_1__extend_gateway_for_preview.sql new file mode 100644 index 00000000..2cc80424 --- /dev/null +++ b/studymanager/src/main/resources/db/migration/V1_15_1__extend_gateway_for_preview.sql @@ -0,0 +1,9 @@ +-- Update gateway-view to consider preview +CREATE OR REPLACE VIEW auth_routing_info (api_id, api_secret, study_id, participant_id, study_group_id, study_is_active) AS +SELECT api_credentials.*, pt.study_group_id, s.status IN ('active', 'preview') +FROM api_credentials + INNER JOIN participants pt + ON (api_credentials.study_id = pt.study_id and api_credentials.participant_id = pt.participant_id) + INNER JOIN studies s + ON (api_credentials.study_id = s.study_id) +;