Skip to content

Commit

Permalink
[JN-1419] tracking login on PortalParticipantUser (#1137)
Browse files Browse the repository at this point in the history
  • Loading branch information
devonbush authored Oct 11, 2024
1 parent c828dd6 commit 0071108
Show file tree
Hide file tree
Showing 23 changed files with 324 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ public UserLoginDto tokenLogin(
UserLoginDto user = loadByToken(token, portalShortcode, environmentName);
user.user.setLastLogin(Instant.now());
participantUserDao.update(user.user);
user.ppUsers.forEach(
ppUser -> {
ppUser.setLastLogin(Instant.now());
portalParticipantUserService.update(ppUser);
});
return user;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package bio.terra.pearl.core.dao.participant;

import bio.terra.pearl.core.dao.BaseJdbiDao;
import bio.terra.pearl.core.dao.BaseMutableJdbiDao;
import bio.terra.pearl.core.model.EnvironmentName;
import bio.terra.pearl.core.model.participant.PortalParticipantUser;
import java.util.List;
Expand All @@ -10,7 +11,7 @@
import org.springframework.stereotype.Component;

@Component
public class PortalParticipantUserDao extends BaseJdbiDao<PortalParticipantUser> {
public class PortalParticipantUserDao extends BaseMutableJdbiDao<PortalParticipantUser> {
private ProfileDao profileDao;
public PortalParticipantUserDao(Jdbi jdbi, ProfileDao profileDao) {
super(jdbi);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

import bio.terra.pearl.core.dao.participant.EnrolleeDao;
import bio.terra.pearl.core.dao.participant.ProfileDao;
import bio.terra.pearl.core.model.BaseEntity;
import bio.terra.pearl.core.model.address.MailingAddress;
import bio.terra.pearl.core.model.kit.KitRequest;
import bio.terra.pearl.core.model.participant.Enrollee;
import bio.terra.pearl.core.model.participant.Family;
import bio.terra.pearl.core.model.participant.ParticipantUser;
import bio.terra.pearl.core.model.participant.Profile;
import bio.terra.pearl.core.model.participant.*;
import bio.terra.pearl.core.model.search.EnrolleeSearchExpressionResult;
import bio.terra.pearl.core.model.survey.Answer;
import bio.terra.pearl.core.model.workflow.ParticipantTask;
import bio.terra.pearl.core.service.search.EnrolleeSearchExpression;
import bio.terra.pearl.core.service.search.EnrolleeSearchOptions;
import bio.terra.pearl.core.service.search.expressions.DefaultSearchExpression;
import bio.terra.pearl.core.service.search.sql.EnrolleeSearchQueryBuilder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.jdbi.v3.core.Handle;
import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.core.mapper.RowMapper;
Expand All @@ -26,20 +26,34 @@
import org.jooq.SQLDialect;
import org.jooq.impl.DSL;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.BiConsumer;

@Component
@Slf4j
public class EnrolleeSearchExpressionDao {
private final Jdbi jdbi;
private final EnrolleeDao enrolleeDao;
private final ProfileDao profileDao;

/** list of mappers for the various modules that can be included in a search result */
protected static final List<SearchModuleMapper<? extends BaseEntity>> moduleMappers = List.of(
new SearchModuleMapper<>("enrollee", Enrollee.class, EnrolleeSearchExpressionResult::setEnrollee),
new SearchModuleMapper<>("profile", Profile.class, EnrolleeSearchExpressionResult::setProfile),
new SearchModuleMapper<>("portalUser", PortalParticipantUser.class, EnrolleeSearchExpressionResult::setPortalParticipantUser),
new SearchModuleMapper<>("participant_user", ParticipantUser.class, EnrolleeSearchExpressionResult::setParticipantUser),
new SearchModuleMapper<>("mailing_address", MailingAddress.class, EnrolleeSearchExpressionResult::setMailingAddress),
new SearchModuleMapper<>("latest_kit", KitRequest.class, EnrolleeSearchExpressionResult::setLatestKit),
new SearchModuleCollectionMapper<>("answer", Answer.class, (result, answer) -> result.getAnswers().add(answer)),
new SearchModuleCollectionMapper<>("task", ParticipantTask.class, (result, task) -> result.getTasks().add(task)));


public EnrolleeSearchExpressionDao(Jdbi jdbi, EnrolleeDao enrolleeDao, ProfileDao profileDao) {
this.jdbi = jdbi;
this.enrolleeDao = enrolleeDao;
Expand All @@ -63,13 +77,13 @@ public List<EnrolleeSearchExpressionResult> executeSearch(EnrolleeSearchExpressi
public List<EnrolleeSearchExpressionResult> executeSearch(EnrolleeSearchQueryBuilder search, EnrolleeSearchOptions opts) {
return jdbi.withHandle(handle -> {
org.jooq.Query jooqQuery = search.toQuery(DSL.using(SQLDialect.POSTGRES), opts);

Query query = jdbiFromJooq(jooqQuery, handle);
return query
var result = query
.registerRowMapper(Family.class, BeanMapper.of(Family.class, "family"))
.registerRowMapper(EnrolleeSearchExpressionResult.class, new EnrolleeSearchResultMapper())
.reduceRows(new EnrolleeSearchResultReducer())
.toList();
return result;
});
}

Expand All @@ -89,96 +103,13 @@ private static Query jdbiFromJooq(org.jooq.Query jooqQuery, Handle handle) {
public static class EnrolleeSearchResultMapper implements RowMapper<EnrolleeSearchExpressionResult> {
@Override
public EnrolleeSearchExpressionResult map(ResultSet rs, StatementContext ctx) throws SQLException {
EnrolleeSearchExpressionResult enrolleeSearchExpressionResult = new EnrolleeSearchExpressionResult();

enrolleeSearchExpressionResult.setEnrollee(
BeanMapper.of(Enrollee.class, "enrollee")
.map(rs, ctx)
);

enrolleeSearchExpressionResult.setProfile(
BeanMapper.of(Profile.class, "profile")
.map(rs, ctx)
);

// anything that starts with "task" will be added to the tasks list
mapAllBeans(
rs,
ctx,
ParticipantTask.class,
"task",
enrolleeSearchExpressionResult.getTasks()::add
);

mapAllBeans(
rs,
ctx,
Answer.class,
"answer",
enrolleeSearchExpressionResult.getAnswers()::add
);

mapBean(rs,
ctx,
MailingAddress.class,
"mailing_address",
enrolleeSearchExpressionResult::setMailingAddress);

mapBean(rs,
ctx,
KitRequest.class,
"latest_kit",
enrolleeSearchExpressionResult::setLatestKit);

mapBean(rs,
ctx,
ParticipantUser.class,
"participant_user",
enrolleeSearchExpressionResult::setParticipantUser);

return enrolleeSearchExpressionResult;
}

private boolean isColumnPresent(ResultSet rs, String columnName) {
try {
rs.findColumn(columnName);
return true;
} catch (SQLException e) {
return false;
EnrolleeSearchExpressionResult result = new EnrolleeSearchExpressionResult();
for (SearchModuleMapper processor : moduleMappers) {
processor.map(rs, ctx, result);
}
return result;
}


private <T> void mapBean(
ResultSet rs,
StatementContext ctx,
Class<T> clazz,
String prefix,
Consumer<T> callback) throws SQLException {
if (isColumnPresent(rs, prefix + "_id")) {
callback.accept(BeanMapper.of(clazz, prefix).map(rs, ctx));
}
}

private <T> void mapAllBeans(
ResultSet rs,
StatementContext ctx,
Class<T> clazz,
String prefix,
Consumer<T> callback) throws SQLException {
// Loop through all the columns to see if any of the possible extra objects
// are present.
// (the column count starts from 1)
for (int i = 1; i < rs.getMetaData().getColumnCount(); i++) {
String columnName = rs.getMetaData().getColumnName(i);
if (columnName.startsWith(prefix) && columnName.endsWith("_created_at")) {
String modelName = columnName.substring(
0,
columnName.length() - "_created_at".length());
callback.accept(BeanMapper.of(clazz, modelName).map(rs, ctx));
}
}
}
}

/**
Expand All @@ -204,4 +135,56 @@ private <T> boolean isColumnPresent(RowView rv, String columnName, Class<T> c) {
}
}
}

/** for simple beans that need to be mapped to a property in the EnrolleeSearchExpressionResult, like Profile */
@Getter
private static class SearchModuleMapper<T> {
protected final String prefix;
protected final RowMapper<T> mapper;
protected final BiConsumer<EnrolleeSearchExpressionResult,T> consumer;
protected final Class<T> clazz;

public SearchModuleMapper(String prefix, Class<T> clazz, BiConsumer<EnrolleeSearchExpressionResult, T> consumer) {
this.prefix = prefix;
this.clazz = clazz;
this.mapper = BeanMapper.of(clazz, prefix);
this.consumer = consumer;
}

public void map(ResultSet rs, StatementContext ctx, EnrolleeSearchExpressionResult result) throws SQLException {
if (isColumnPresent(rs, prefix + "_id")) {
consumer.accept(result, mapper.map(rs, ctx));
}
}

private boolean isColumnPresent(ResultSet rs, String columnName) {
try {
rs.findColumn(columnName);
return true;
} catch (SQLException e) {
return false;
}
}
}

/** for modules that are mapped to a collection in the EnrolleeSearchExpressionResult, such as tasks */
private static class SearchModuleCollectionMapper<T> extends SearchModuleMapper<T> {
public SearchModuleCollectionMapper(String prefix, Class<T> clazz, BiConsumer<EnrolleeSearchExpressionResult, T> consumer) {
super(prefix, clazz, consumer);
}

@Override
public void map(ResultSet rs, StatementContext ctx, EnrolleeSearchExpressionResult result) throws SQLException {
for (int i = 1; i < rs.getMetaData().getColumnCount(); i++) {
String columnName = rs.getMetaData().getColumnName(i);
if (columnName.startsWith(prefix) && columnName.endsWith("_created_at")) {
String itemPrefix = columnName.substring(
0,
columnName.length() - "_created_at".length());
RowMapper<T> itemMapper = BeanMapper.of(clazz, itemPrefix);
consumer.accept(result, itemMapper.map(rs, ctx));
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import lombok.Setter;
import lombok.experimental.SuperBuilder;

import java.time.Instant;
import java.util.UUID;

@Getter
Expand All @@ -20,4 +21,5 @@ public class PortalParticipantUser extends BaseEntity {
private UUID portalEnvironmentId;
private Profile profile;
private UUID profileId;
private Instant lastLogin;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

import bio.terra.pearl.core.model.address.MailingAddress;
import bio.terra.pearl.core.model.kit.KitRequest;
import bio.terra.pearl.core.model.participant.Enrollee;
import bio.terra.pearl.core.model.participant.Family;
import bio.terra.pearl.core.model.participant.ParticipantUser;
import bio.terra.pearl.core.model.participant.Profile;
import bio.terra.pearl.core.model.participant.*;
import bio.terra.pearl.core.model.survey.Answer;
import bio.terra.pearl.core.model.workflow.ParticipantTask;
import lombok.Getter;
Expand All @@ -24,6 +21,7 @@ public class EnrolleeSearchExpressionResult {
private Enrollee enrollee;
private Profile profile;
private ParticipantUser participantUser;
private PortalParticipantUser portalParticipantUser;
private MailingAddress mailingAddress;
private final List<Answer> answers = new ArrayList<>();
private final List<ParticipantTask> tasks = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import bio.terra.pearl.core.model.participant.PortalParticipantUser;
import bio.terra.pearl.core.model.participant.Profile;
import bio.terra.pearl.core.service.CascadeProperty;
import bio.terra.pearl.core.service.CrudService;
import bio.terra.pearl.core.service.ImmutableEntityService;
import bio.terra.pearl.core.service.workflow.ParticipantDataChangeService;
import org.springframework.context.annotation.Lazy;
Expand All @@ -20,7 +21,7 @@
import java.util.UUID;

@Service
public class PortalParticipantUserService extends ImmutableEntityService<PortalParticipantUser, PortalParticipantUserDao> {
public class PortalParticipantUserService extends CrudService<PortalParticipantUser, PortalParticipantUserDao> {
private final ProfileService profileService;
private final PreregistrationResponseDao preregistrationResponseDao;
private final ParticipantDataChangeService participantDataChangeService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ public class EnrolleeSearchExpressionParser {
private final KitRequestDao kitRequestDao;
private final FamilyDao familyDao;
private final ParticipantUserDao participantUserDao;
private final PortalParticipantUserDao portalParticipantUserDao;

public EnrolleeSearchExpressionParser(EnrolleeDao enrolleeDao, AnswerDao answerDao, ProfileDao profileDao, MailingAddressDao mailingAddressDao, ParticipantTaskDao participantTaskDao, KitRequestDao kitRequestDao, FamilyDao familyDao, ParticipantUserDao participantUserDao) {
public EnrolleeSearchExpressionParser(EnrolleeDao enrolleeDao, AnswerDao answerDao, ProfileDao profileDao, MailingAddressDao mailingAddressDao, ParticipantTaskDao participantTaskDao, KitRequestDao kitRequestDao, FamilyDao familyDao, ParticipantUserDao participantUserDao, PortalParticipantUserDao portalParticipantUserDao) {
this.enrolleeDao = enrolleeDao;
this.answerDao = answerDao;
this.profileDao = profileDao;
Expand All @@ -50,6 +51,7 @@ public EnrolleeSearchExpressionParser(EnrolleeDao enrolleeDao, AnswerDao answerD
this.kitRequestDao = kitRequestDao;
this.familyDao = familyDao;
this.participantUserDao = participantUserDao;
this.portalParticipantUserDao = portalParticipantUserDao;
}


Expand Down Expand Up @@ -191,44 +193,39 @@ private SearchTerm parseVariableTerm(String variable) {
switch (model) {
case "profile":
String profileField = parseField(trimmedVar);

return new ProfileTerm(profileDao, mailingAddressDao, profileField);
case "answer":
String[] answerFields = parseFields(trimmedVar);
if (answerFields.length != 2) {
throw new IllegalArgumentException("Invalid answer variable");
}

return new AnswerTerm(answerDao, answerFields[0], answerFields[1]);
case "age":
if (!trimmedVar.equals(model)) {
throw new IllegalArgumentException("Invalid age variable");
}

return new AgeTerm(profileDao);
case "enrollee":
String enrolleeField = parseField(trimmedVar);

return new EnrolleeTerm(enrolleeField);
case "task":
String[] taskFields = parseFields(trimmedVar);
if (taskFields.length != 2) {
throw new IllegalArgumentException("Invalid answer variable");
}

return new TaskTerm(participantTaskDao, taskFields[0], taskFields[1]);
case "latestKit":
String latestKitField = parseField(trimmedVar);

return new LatestKitTerm(kitRequestDao, latestKitField);
case "family":
String familyField = parseField(trimmedVar);

return new FamilyTerm(familyDao, familyField);
case "user":
String userField = parseField(trimmedVar);

return new UserTerm(participantUserDao, userField);
case "portalUser":
String portalUserField = parseField(trimmedVar);
return new PortalUserTerm(portalParticipantUserDao, portalUserField);
default:
throw new IllegalArgumentException("Unknown model " + model);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public Map<String, SearchValueTypeDefinition> getExpressionSearchFacetsForStudyE
EnrolleeTerm.FIELDS.forEach((term, type) -> fields.put("enrollee." + term, type));
// latest kit fields
LatestKitTerm.FIELDS.forEach((term, type) -> fields.put("latestKit." + term, type));
PortalUserTerm.FIELDS.forEach((term, type) -> fields.put("portalUser." + term, type));
// age
fields.put("age", SearchValueTypeDefinition.builder().type(NUMBER).build());
// answers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import bio.terra.pearl.core.dao.BaseJdbiDao;
import bio.terra.pearl.core.dao.participant.EnrolleeDao;
import bio.terra.pearl.core.dao.participant.PortalParticipantUserDao;
import bio.terra.pearl.core.dao.participant.ProfileDao;
import bio.terra.pearl.core.service.search.EnrolleeSearchOptions;
import lombok.Getter;
Expand Down
Loading

0 comments on commit 0071108

Please sign in to comment.