From de1959d1d53810a5156a755315e654a5c5e4d572 Mon Sep 17 00:00:00 2001 From: bhoff Date: Wed, 15 Jul 2026 16:46:14 -0700 Subject: [PATCH] PLFM-9656 --- .../model/dbo/dao/dataaccess/DBORequest.java | 42 ++++- .../dbo/dao/dataaccess/DBORequestDAOImpl.java | 146 +++++++++++++++--- .../dbo/dao/dataaccess/DBORequestUser.java | 126 +++++++++++++++ .../model/dbo/dao/dataaccess/RequestDAO.java | 8 + .../dbo/dao/dataaccess/RequestUserInfo.java | 100 ++++++++++++ .../dbo/dao/dataaccess/RequestUtils.java | 1 + .../repo/model/query/jdo/SqlConstants.java | 7 + .../schema/DataAccessRequest-ddl.sql | 1 + .../schema/DataAccessRequestUser-ddl.sql | 7 + .../model/dataaccess/AccessRequestList.json | 15 ++ .../dataaccess/AccessRequestListRequest.json | 9 ++ .../dataaccess/AccessRequestStatusEnum.json | 51 ++++++ .../dataaccess/AccessRequestSummary.json | 39 +++++ .../repo/model/migration/MigrationType.json | 4 + .../docusign/DocuSignClient.java | 7 + .../docusign/DocuSignEnvelopesApi.java | 4 + .../docusign/DocuSignEnvelopesApiImpl.java | 18 +++ .../manager/dataaccess/RequestManager.java | 4 + .../dataaccess/RequestManagerImpl.java | 114 ++++++++++++++ .../service/dataaccess/DataAccessService.java | 4 + .../dataaccess/DataAccessServiceImpl.java | 8 + .../sagebionetworks/repo/web/UrlHelpers.java | 1 + .../web/controller/DataAccessController.java | 18 +++ 23 files changed, 709 insertions(+), 25 deletions(-) create mode 100644 lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/DBORequestUser.java create mode 100644 lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/RequestUserInfo.java create mode 100644 lib/jdomodels/src/main/resources/schema/DataAccessRequestUser-ddl.sql create mode 100644 lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestList.json create mode 100644 lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestListRequest.json create mode 100644 lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestStatusEnum.json create mode 100644 lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestSummary.json diff --git a/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/DBORequest.java b/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/DBORequest.java index 98e3adf87f4..d99f58ed97c 100644 --- a/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/DBORequest.java +++ b/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/DBORequest.java @@ -3,6 +3,7 @@ import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_REQUEST_ACCESS_REQUIREMENT_ID; import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_REQUEST_CREATED_BY; import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_REQUEST_CREATED_ON; +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_REQUEST_EDUC_ENVELOPE_ID; import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_REQUEST_ETAG; import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_REQUEST_ID; import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_REQUEST_MODIFIED_BY; @@ -16,12 +17,12 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; +import java.util.LinkedList; import java.util.List; import org.sagebionetworks.repo.model.dbo.FieldColumn; import org.sagebionetworks.repo.model.dbo.MigratableDatabaseObject; import org.sagebionetworks.repo.model.dbo.TableMapping; -import org.sagebionetworks.repo.model.dbo.migration.BasicMigratableTableTranslation; import org.sagebionetworks.repo.model.dbo.migration.MigratableTableTranslation; import org.sagebionetworks.repo.model.migration.MigrationType; @@ -36,7 +37,8 @@ public class DBORequest implements MigratableDatabaseObject getTableMapping() { return new TableMapping(){ @@ -216,6 +227,7 @@ public DBORequest mapRow(ResultSet rs, int rowNum) throws SQLException { dbo.setEtag(rs.getString(COL_DATA_ACCESS_REQUEST_ETAG)); Blob blob = rs.getBlob(COL_DATA_ACCESS_REQUEST_REQUEST_SERIALIZED); dbo.setRequestSerialized(blob.getBytes(1, (int) blob.length())); + dbo.setEDucEnvelopeId(rs.getString(COL_DATA_ACCESS_REQUEST_EDUC_ENVELOPE_ID)); return dbo; } @@ -249,7 +261,27 @@ public MigrationType getMigratableTableType() { @Override public MigratableTableTranslation getTranslator() { - return new BasicMigratableTableTranslation(); + return new MigratableTableTranslation() { + @Override + public DBORequest createDatabaseObjectFromBackup(DBORequest backup) { + if (backup.getEDucEnvelopeId() == null && backup.getRequestSerialized() != null) { + try { + var dto = RequestUtils.readSerializedField(backup.getRequestSerialized()); + if (dto.getEDucSignatureEnvelopeId() != null) { + backup.setEDucEnvelopeId(dto.getEDucSignatureEnvelopeId()); + } + } catch (Exception e) { + // If deserialization fails during migration, skip populating the column + } + } + return backup; + } + + @Override + public DBORequest createBackupFromDatabaseObject(DBORequest dbo) { + return dbo; + } + }; } @Override @@ -264,7 +296,9 @@ public Class getDatabaseObjectClass() { @Override public List> getSecondaryTypes() { - return null; + List> list = new LinkedList<>(); + list.add(new DBORequestUser()); + return list; } } diff --git a/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/DBORequestDAOImpl.java b/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/DBORequestDAOImpl.java index ef8ae52b24d..bc11d1804d2 100644 --- a/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/DBORequestDAOImpl.java +++ b/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/DBORequestDAOImpl.java @@ -1,14 +1,38 @@ package org.sagebionetworks.repo.model.dbo.dao.dataaccess; +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_ACCESS_REQUIREMENT_NAME; import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_REQUEST_ACCESS_REQUIREMENT_ID; import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_REQUEST_CREATED_BY; +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_REQUEST_EDUC_ENVELOPE_ID; import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_REQUEST_ID; +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_REQUEST_MODIFIED_ON; +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_REQUEST_USER_REQUEST_ID; +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_REQUEST_USER_USER_ID; +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_SUBMISSION_DATA_ACCESS_REQUEST_ID; +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_SUBMISSION_ID; +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_SUBMISSION_STATUS_CREATED_ON; +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_SUBMISSION_STATUS_MODIFIED_ON; +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_SUBMISSION_STATUS_STATE; +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.COL_DATA_ACCESS_SUBMISSION_STATUS_SUBMISSION_ID; +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.TABLE_ACCESS_REQUIREMENT; import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.TABLE_DATA_ACCESS_REQUEST; - +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.TABLE_DATA_ACCESS_REQUEST_USER; +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.TABLE_DATA_ACCESS_SUBMISSION; +import static org.sagebionetworks.repo.model.query.jdo.SqlConstants.TABLE_DATA_ACCESS_SUBMISSION_STATUS; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashSet; +import java.util.List; import java.util.UUID; import org.sagebionetworks.ids.IdGenerator; import org.sagebionetworks.ids.IdType; +import org.sagebionetworks.repo.model.dataaccess.AccessType; +import org.sagebionetworks.repo.model.dataaccess.AccessorChange; +import org.sagebionetworks.repo.model.dataaccess.PrincipalInvestigator; import org.sagebionetworks.repo.model.dataaccess.Request; import org.sagebionetworks.repo.model.dataaccess.RequestInterface; import org.sagebionetworks.repo.model.dbo.DBOBasicDao; @@ -22,7 +46,7 @@ import org.springframework.stereotype.Repository; @Repository -public class DBORequestDAOImpl implements RequestDAO{ +public class DBORequestDAOImpl implements RequestDAO { public static final String DATA_ACCESS_REQUEST_DOES_NOT_EXIST = "Data access request: '%s' does not exist"; @@ -35,26 +59,62 @@ public class DBORequestDAOImpl implements RequestDAO{ @Autowired private IdGenerator idGenerator; - public static final String SQL_DELETE = "DELETE FROM "+TABLE_DATA_ACCESS_REQUEST - + " WHERE "+COL_DATA_ACCESS_REQUEST_ID+" = ?"; + public static final String SQL_DELETE = "DELETE FROM " + TABLE_DATA_ACCESS_REQUEST + + " WHERE " + COL_DATA_ACCESS_REQUEST_ID + " = ?"; public static final String SQL_GET = "SELECT *" - + " FROM "+TABLE_DATA_ACCESS_REQUEST - + " WHERE "+COL_DATA_ACCESS_REQUEST_ACCESS_REQUIREMENT_ID+" = ?" - + " AND "+COL_DATA_ACCESS_REQUEST_CREATED_BY+" = ?"; + + " FROM " + TABLE_DATA_ACCESS_REQUEST + + " WHERE " + COL_DATA_ACCESS_REQUEST_ACCESS_REQUIREMENT_ID + " = ?" + + " AND " + COL_DATA_ACCESS_REQUEST_CREATED_BY + " = ?"; public static final String SQL_GET_BY_ID = "SELECT *" - + " FROM "+TABLE_DATA_ACCESS_REQUEST - + " WHERE "+COL_DATA_ACCESS_REQUEST_ID+" = ?"; - - public static final String SQL_GET_AR_ID_BY_ID = "SELECT " + COL_DATA_ACCESS_REQUEST_ACCESS_REQUIREMENT_ID - + " FROM "+TABLE_DATA_ACCESS_REQUEST - + " WHERE "+COL_DATA_ACCESS_REQUEST_ID+" = ?"; + + " FROM " + TABLE_DATA_ACCESS_REQUEST + + " WHERE " + COL_DATA_ACCESS_REQUEST_ID + " = ?"; - public static final String SQL_GET_FOR_UPDATE = SQL_GET_BY_ID+" FOR UPDATE"; + public static final String SQL_GET_AR_ID_BY_ID = "SELECT " + COL_DATA_ACCESS_REQUEST_ACCESS_REQUIREMENT_ID + + " FROM " + TABLE_DATA_ACCESS_REQUEST + + " WHERE " + COL_DATA_ACCESS_REQUEST_ID + " = ?"; + + public static final String SQL_GET_FOR_UPDATE = SQL_GET_BY_ID + " FOR UPDATE"; + + private static final String SQL_DELETE_REQUEST_USERS = "DELETE FROM " + TABLE_DATA_ACCESS_REQUEST_USER + + " WHERE " + COL_DATA_ACCESS_REQUEST_USER_REQUEST_ID + " = ?"; + + private static final String SQL_GET_USER_REQUESTS = + "SELECT r." + COL_DATA_ACCESS_REQUEST_ID + " AS REQUEST_ID" + + ", r." + COL_DATA_ACCESS_REQUEST_ACCESS_REQUIREMENT_ID + " AS ACCESS_REQUIREMENT_ID" + + ", ar." + COL_ACCESS_REQUIREMENT_NAME + " AS ACCESS_REQUIREMENT_NAME" + + ", ss." + COL_DATA_ACCESS_SUBMISSION_STATUS_STATE + " AS SUBMISSION_STATUS" + + ", r." + COL_DATA_ACCESS_REQUEST_EDUC_ENVELOPE_ID + " IS NOT NULL AS IS_EDUC" + + ", r." + COL_DATA_ACCESS_REQUEST_EDUC_ENVELOPE_ID + " AS ENVELOPE_ID" + + ", ss." + COL_DATA_ACCESS_SUBMISSION_STATUS_CREATED_ON + " AS SUBMITTED_ON" + + ", ss." + COL_DATA_ACCESS_SUBMISSION_STATUS_MODIFIED_ON + " AS MODIFIED_ON" + + " FROM " + TABLE_DATA_ACCESS_REQUEST + " r" + + " JOIN " + TABLE_DATA_ACCESS_REQUEST_USER + " ru ON r." + COL_DATA_ACCESS_REQUEST_ID + " = ru." + COL_DATA_ACCESS_REQUEST_USER_REQUEST_ID + + " JOIN " + TABLE_ACCESS_REQUIREMENT + " ar ON r." + COL_DATA_ACCESS_REQUEST_ACCESS_REQUIREMENT_ID + " = ar.ID" + + " LEFT JOIN " + TABLE_DATA_ACCESS_SUBMISSION + " s ON s." + COL_DATA_ACCESS_SUBMISSION_DATA_ACCESS_REQUEST_ID + " = r." + COL_DATA_ACCESS_REQUEST_ID + + " LEFT JOIN " + TABLE_DATA_ACCESS_SUBMISSION_STATUS + " ss ON ss." + COL_DATA_ACCESS_SUBMISSION_STATUS_SUBMISSION_ID + " = s." + COL_DATA_ACCESS_SUBMISSION_ID + + " WHERE ru." + COL_DATA_ACCESS_REQUEST_USER_USER_ID + " = ?" + + " ORDER BY r." + COL_DATA_ACCESS_REQUEST_MODIFIED_ON + " DESC" + + " LIMIT ? OFFSET ?"; private static final RowMapper MAPPER = new DBORequest().getTableMapping(); + private static final RowMapper USER_REQUEST_MAPPER = (ResultSet rs, int rowNum) -> { + RequestUserInfo info = new RequestUserInfo(); + info.setRequestId(String.valueOf(rs.getLong("REQUEST_ID"))); + info.setAccessRequirementId(String.valueOf(rs.getLong("ACCESS_REQUIREMENT_ID"))); + info.setAccessRequirementName(rs.getString("ACCESS_REQUIREMENT_NAME")); + info.setSubmissionStatus(rs.getString("SUBMISSION_STATUS")); + info.setIsEDuc(rs.getBoolean("IS_EDUC")); + info.setEnvelopeId(rs.getString("ENVELOPE_ID")); + long submittedOn = rs.getLong("SUBMITTED_ON"); + info.setSubmittedOn(rs.wasNull() ? null : new Date(submittedOn)); + long modifiedOn = rs.getLong("MODIFIED_ON"); + info.setModifiedOn(rs.wasNull() ? null : new Date(modifiedOn)); + return info; + }; + @WriteTransaction @Override public Request create(Request toCreate) { @@ -63,6 +123,7 @@ public Request create(Request toCreate) { DBORequest dbo = new DBORequest(); RequestUtils.copyDtoToDbo(toCreate, dbo); basicDao.createNew(dbo); + populateRequestUsers(toCreate); return (Request) getUserOwnCurrentRequest(toCreate.getAccessRequirementId(), toCreate.getCreatedBy()); } @@ -71,8 +132,7 @@ public RequestInterface getUserOwnCurrentRequest(String accessRequirementId, Str throws NotFoundException { try { DBORequest dbo = jdbcTemplate.queryForObject(SQL_GET, MAPPER, accessRequirementId, userId); - RequestInterface dto = RequestUtils.copyDboToDto(dbo); - return dto; + return RequestUtils.copyDboToDto(dbo); } catch (EmptyResultDataAccessException e) { throw new NotFoundException(String.format("Data access request does not exist for access requirement: '%s' and user id: '%s'", accessRequirementId, userId)); } @@ -85,6 +145,7 @@ public RequestInterface update(RequestInterface toUpdate) throws NotFoundExcepti RequestUtils.copyDtoToDbo(toUpdate, dbo); dbo.setEtag(UUID.randomUUID().toString()); basicDao.update(dbo); + populateRequestUsers(toUpdate); return getUserOwnCurrentRequest(toUpdate.getAccessRequirementId(), toUpdate.getCreatedBy()); } @@ -98,8 +159,7 @@ public void delete(String id) { public RequestInterface getForUpdate(String id) { try { DBORequest dbo = jdbcTemplate.queryForObject(SQL_GET_FOR_UPDATE, MAPPER, id); - RequestInterface dto = RequestUtils.copyDboToDto(dbo); - return dto; + return RequestUtils.copyDboToDto(dbo); } catch (EmptyResultDataAccessException e) { throw new NotFoundException(String.format(DATA_ACCESS_REQUEST_DOES_NOT_EXIST, id)); } @@ -109,13 +169,12 @@ public RequestInterface getForUpdate(String id) { public RequestInterface get(String id) { try { DBORequest dbo = jdbcTemplate.queryForObject(SQL_GET_BY_ID, MAPPER, id); - RequestInterface dto = RequestUtils.copyDboToDto(dbo); - return dto; + return RequestUtils.copyDboToDto(dbo); } catch (EmptyResultDataAccessException e) { throw new NotFoundException(String.format(DATA_ACCESS_REQUEST_DOES_NOT_EXIST, id)); } } - + @Override public String getAccessRequirementId(String requestId) { try { @@ -125,9 +184,54 @@ public String getAccessRequirementId(String requestId) { } } + @Override + public List getUserRequests(Long userId, long limit, long offset) { + return jdbcTemplate.query(SQL_GET_USER_REQUESTS, USER_REQUEST_MAPPER, userId, limit, offset); + } + @Override public void truncateAll() { jdbcTemplate.update("DELETE FROM " + TABLE_DATA_ACCESS_REQUEST); } + private void populateRequestUsers(RequestInterface request) { + Long requestId = Long.parseLong(request.getId()); + jdbcTemplate.update(SQL_DELETE_REQUEST_USERS, requestId); + + List users = buildRequestUsers(request); + if (!users.isEmpty()) { + basicDao.createBatch(users); + } + } + + List buildRequestUsers(RequestInterface request) { + Long requestId = Long.parseLong(request.getId()); + LinkedHashSet userIds = new LinkedHashSet<>(); + + userIds.add(Long.parseLong(request.getCreatedBy())); + + PrincipalInvestigator pi = request.getPrincipalInvestigator(); + if (pi != null && pi.getUserId() != null) { + userIds.add(Long.parseLong(pi.getUserId())); + } + + List accessorChanges = request.getAccessorChanges(); + if (accessorChanges != null) { + for (AccessorChange change : accessorChanges) { + if (AccessType.GAIN_ACCESS.equals(change.getType()) + || AccessType.RENEW_ACCESS.equals(change.getType())) { + userIds.add(Long.parseLong(change.getUserId())); + } + } + } + + List result = new ArrayList<>(userIds.size()); + for (Long userId : userIds) { + DBORequestUser dbo = new DBORequestUser(); + dbo.setRequestId(requestId); + dbo.setUserId(userId); + result.add(dbo); + } + return result; + } } diff --git a/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/DBORequestUser.java b/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/DBORequestUser.java new file mode 100644 index 00000000000..1559ec0205a --- /dev/null +++ b/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/DBORequestUser.java @@ -0,0 +1,126 @@ +package org.sagebionetworks.repo.model.dbo.dao.dataaccess; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; + +import org.sagebionetworks.repo.model.dbo.FieldColumn; +import org.sagebionetworks.repo.model.dbo.MigratableDatabaseObject; +import org.sagebionetworks.repo.model.dbo.TableMapping; +import org.sagebionetworks.repo.model.dbo.migration.BasicMigratableTableTranslation; +import org.sagebionetworks.repo.model.dbo.migration.MigratableTableTranslation; +import org.sagebionetworks.repo.model.migration.MigrationType; +import org.sagebionetworks.repo.model.query.jdo.SqlConstants; + +public class DBORequestUser implements MigratableDatabaseObject { + + private static final FieldColumn[] FIELDS = new FieldColumn[] { + new FieldColumn("requestId", SqlConstants.COL_DATA_ACCESS_REQUEST_USER_REQUEST_ID, true).withIsBackupId(true), + new FieldColumn("userId", SqlConstants.COL_DATA_ACCESS_REQUEST_USER_USER_ID, true) + }; + + private static final TableMapping TABLE_MAPPING = new TableMapping() { + + @Override + public DBORequestUser mapRow(ResultSet rs, int rowNum) throws SQLException { + DBORequestUser dbo = new DBORequestUser(); + dbo.setRequestId(rs.getLong(SqlConstants.COL_DATA_ACCESS_REQUEST_USER_REQUEST_ID)); + dbo.setUserId(rs.getLong(SqlConstants.COL_DATA_ACCESS_REQUEST_USER_USER_ID)); + return dbo; + } + + @Override + public String getTableName() { + return SqlConstants.TABLE_DATA_ACCESS_REQUEST_USER; + } + + @Override + public String getDDLFileName() { + return SqlConstants.DDL_DATA_ACCESS_REQUEST_USER; + } + + @Override + public FieldColumn[] getFieldColumns() { + return FIELDS; + } + + @Override + public Class getDBOClass() { + return DBORequestUser.class; + } + }; + + private static final MigratableTableTranslation MIGRATION_TRANSLATOR = new BasicMigratableTableTranslation<>(); + + private Long requestId; + private Long userId; + + public Long getRequestId() { + return requestId; + } + + public void setRequestId(Long requestId) { + this.requestId = requestId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + @Override + public TableMapping getTableMapping() { + return TABLE_MAPPING; + } + + @Override + public MigrationType getMigratableTableType() { + return MigrationType.DATA_ACCESS_REQUEST_USER; + } + + @Override + public MigratableTableTranslation getTranslator() { + return MIGRATION_TRANSLATOR; + } + + @Override + public Class getBackupClass() { + return DBORequestUser.class; + } + + @Override + public Class getDatabaseObjectClass() { + return DBORequestUser.class; + } + + @Override + public List> getSecondaryTypes() { + return null; + } + + @Override + public int hashCode() { + return Objects.hash(requestId, userId); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + DBORequestUser other = (DBORequestUser) obj; + return Objects.equals(requestId, other.requestId) && Objects.equals(userId, other.userId); + } + + @Override + public String toString() { + return "DBORequestUser [requestId=" + requestId + ", userId=" + userId + "]"; + } +} diff --git a/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/RequestDAO.java b/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/RequestDAO.java index 30ac2644ad1..536a30598a6 100644 --- a/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/RequestDAO.java +++ b/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/RequestDAO.java @@ -1,5 +1,7 @@ package org.sagebionetworks.repo.model.dbo.dao.dataaccess; +import java.util.List; + import org.sagebionetworks.repo.model.dataaccess.Request; import org.sagebionetworks.repo.model.dataaccess.RequestInterface; import org.sagebionetworks.repo.web.NotFoundException; @@ -61,6 +63,12 @@ public interface RequestDAO { */ String getAccessRequirementId(String requestId); + /** + * Returns requests associated with the given user (as creator, PI, or collaborator), + * ordered by most recently modified first. + */ + List getUserRequests(Long userId, long limit, long offset); + // For testing void truncateAll(); diff --git a/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/RequestUserInfo.java b/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/RequestUserInfo.java new file mode 100644 index 00000000000..fb9c84e2cbc --- /dev/null +++ b/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/RequestUserInfo.java @@ -0,0 +1,100 @@ +package org.sagebionetworks.repo.model.dbo.dao.dataaccess; + +import java.util.Date; +import java.util.Objects; + +public class RequestUserInfo { + + private String requestId; + private String accessRequirementId; + private String accessRequirementName; + private String submissionStatus; + private Boolean isEDuc; + private String envelopeId; + private Date submittedOn; + private Date modifiedOn; + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public String getAccessRequirementId() { + return accessRequirementId; + } + + public void setAccessRequirementId(String accessRequirementId) { + this.accessRequirementId = accessRequirementId; + } + + public String getAccessRequirementName() { + return accessRequirementName; + } + + public void setAccessRequirementName(String accessRequirementName) { + this.accessRequirementName = accessRequirementName; + } + + public String getSubmissionStatus() { + return submissionStatus; + } + + public void setSubmissionStatus(String submissionStatus) { + this.submissionStatus = submissionStatus; + } + + public Boolean getIsEDuc() { + return isEDuc; + } + + public void setIsEDuc(Boolean isEDuc) { + this.isEDuc = isEDuc; + } + + public String getEnvelopeId() { + return envelopeId; + } + + public void setEnvelopeId(String envelopeId) { + this.envelopeId = envelopeId; + } + + public Date getSubmittedOn() { + return submittedOn; + } + + public void setSubmittedOn(Date submittedOn) { + this.submittedOn = submittedOn; + } + + public Date getModifiedOn() { + return modifiedOn; + } + + public void setModifiedOn(Date modifiedOn) { + this.modifiedOn = modifiedOn; + } + + @Override + public int hashCode() { + return Objects.hash(requestId, accessRequirementId, accessRequirementName, + submissionStatus, isEDuc, submittedOn, modifiedOn); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + RequestUserInfo other = (RequestUserInfo) obj; + return Objects.equals(requestId, other.requestId) + && Objects.equals(accessRequirementId, other.accessRequirementId) + && Objects.equals(accessRequirementName, other.accessRequirementName) + && Objects.equals(submissionStatus, other.submissionStatus) + && Objects.equals(isEDuc, other.isEDuc) + && Objects.equals(submittedOn, other.submittedOn) + && Objects.equals(modifiedOn, other.modifiedOn); + } +} diff --git a/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/RequestUtils.java b/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/RequestUtils.java index 1c0c54e24ab..d432a413f4c 100644 --- a/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/RequestUtils.java +++ b/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/dbo/dao/dataaccess/RequestUtils.java @@ -27,6 +27,7 @@ public static void copyDtoToDbo(RequestInterface dto, DBORequest dbo) throws Dat dbo.setModifiedBy(Long.parseLong(dto.getModifiedBy())); dbo.setModifiedOn(dto.getModifiedOn().getTime()); dbo.setEtag(dto.getEtag()); + dbo.setEDucEnvelopeId(dto.getEDucSignatureEnvelopeId()); copyToSerializedField(dto, dbo); } diff --git a/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/query/jdo/SqlConstants.java b/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/query/jdo/SqlConstants.java index cccdd275183..ed30d9a0d04 100644 --- a/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/query/jdo/SqlConstants.java +++ b/lib/jdomodels/src/main/java/org/sagebionetworks/repo/model/query/jdo/SqlConstants.java @@ -1067,6 +1067,13 @@ public class SqlConstants { public static final String COL_DATA_ACCESS_REQUEST_MODIFIED_ON = "MODIFIED_ON"; public static final String COL_DATA_ACCESS_REQUEST_ETAG = "ETAG"; public static final String COL_DATA_ACCESS_REQUEST_REQUEST_SERIALIZED = "REQUEST_SERIALIZED"; + public static final String COL_DATA_ACCESS_REQUEST_EDUC_ENVELOPE_ID = "EDUC_ENVELOPE_ID"; + + // DataAccessRequestUser + public static final String DDL_DATA_ACCESS_REQUEST_USER = "schema/DataAccessRequestUser-ddl.sql"; + public static final String TABLE_DATA_ACCESS_REQUEST_USER = "DATA_ACCESS_REQUEST_USER"; + public static final String COL_DATA_ACCESS_REQUEST_USER_REQUEST_ID = "REQUEST_ID"; + public static final String COL_DATA_ACCESS_REQUEST_USER_USER_ID = "USER_ID"; // DataAccessSubmission public static final String DDL_DATA_ACCESS_SUBMISSION = "schema/DataAccessSubmission-ddl.sql"; diff --git a/lib/jdomodels/src/main/resources/schema/DataAccessRequest-ddl.sql b/lib/jdomodels/src/main/resources/schema/DataAccessRequest-ddl.sql index c379085c813..e13c8506be8 100644 --- a/lib/jdomodels/src/main/resources/schema/DataAccessRequest-ddl.sql +++ b/lib/jdomodels/src/main/resources/schema/DataAccessRequest-ddl.sql @@ -8,6 +8,7 @@ CREATE TABLE IF NOT EXISTS `DATA_ACCESS_REQUEST` ( `MODIFIED_ON` BIGINT NOT NULL, `ETAG` char(36) NOT NULL, `REQUEST_SERIALIZED` mediumblob NOT NULL, + `EDUC_ENVELOPE_ID` VARCHAR(255) DEFAULT NULL, PRIMARY KEY (`ID`), UNIQUE KEY `ACCESS_REQUIREMENT_ID_AND_CREATED_BY` (`ACCESS_REQUIREMENT_ID`, `CREATED_BY`), CONSTRAINT `DATA_ACCESS_REQUEST_ACCESS_REQUIREMENT_ID_FK` FOREIGN KEY (`ACCESS_REQUIREMENT_ID`) REFERENCES `ACCESS_REQUIREMENT` (`ID`) ON DELETE RESTRICT, diff --git a/lib/jdomodels/src/main/resources/schema/DataAccessRequestUser-ddl.sql b/lib/jdomodels/src/main/resources/schema/DataAccessRequestUser-ddl.sql new file mode 100644 index 00000000000..0443b0704e8 --- /dev/null +++ b/lib/jdomodels/src/main/resources/schema/DataAccessRequestUser-ddl.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `DATA_ACCESS_REQUEST_USER` ( + `REQUEST_ID` BIGINT NOT NULL, + `USER_ID` BIGINT NOT NULL, + PRIMARY KEY (`REQUEST_ID`, `USER_ID`), + CONSTRAINT `DATA_ACCESS_REQUEST_USER_REQUEST_ID_FK` FOREIGN KEY (`REQUEST_ID`) REFERENCES `DATA_ACCESS_REQUEST` (`ID`) ON DELETE CASCADE, + INDEX `DATA_ACCESS_REQUEST_USER_USER_ID_IDX` (`USER_ID`) +) diff --git a/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestList.json b/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestList.json new file mode 100644 index 00000000000..3174b3a2e15 --- /dev/null +++ b/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestList.json @@ -0,0 +1,15 @@ +{ + "description": "List of data access requests", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "org.sagebionetworks.repo.model.dataaccess.AccessRequestSummary" + } + }, + "nextPageToken": { + "type": "string", + "description": "The token to get the next page of results." + } + } +} diff --git a/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestListRequest.json b/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestListRequest.json new file mode 100644 index 00000000000..8dae0ccc6b2 --- /dev/null +++ b/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestListRequest.json @@ -0,0 +1,9 @@ +{ + "description": "Request to list data access requests for the current user", + "properties": { + "nextPageToken": { + "type": "string", + "description": "The token to get the next page of results." + } + } +} diff --git a/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestStatusEnum.json b/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestStatusEnum.json new file mode 100644 index 00000000000..aa36e6ae499 --- /dev/null +++ b/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestStatusEnum.json @@ -0,0 +1,51 @@ +{ + "description": "The status of a data access request", + "name": "AccessRequestStatusEnum", + "type": "string", + "enum": [ + { + "name": "created", + "description": "The request has been created but not yet submitted or routed for eDUC signature" + }, + { + "name": "sent", + "description": "The eDUC has been sent to recipients for signature" + }, + { + "name": "delivered", + "description": "All eDUC recipients have viewed the document" + }, + { + "name": "completed", + "description": "All eDUC recipients have signed the document" + }, + { + "name": "declined", + "description": "An eDUC recipient has declined to sign" + }, + { + "name": "voided", + "description": "The eDUC envelope has been voided or expired" + }, + { + "name": "correct", + "description": "The eDUC envelope has been opened for correction" + }, + { + "name": "submitted", + "description": "The request has been submitted for review" + }, + { + "name": "approved", + "description": "The submission has been approved" + }, + { + "name": "rejected", + "description": "The submission has been rejected" + }, + { + "name": "cancelled", + "description": "The submission has been cancelled" + } + ] +} diff --git a/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestSummary.json b/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestSummary.json new file mode 100644 index 00000000000..2481a509e77 --- /dev/null +++ b/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/dataaccess/AccessRequestSummary.json @@ -0,0 +1,39 @@ +{ + "description": "Summary of a data access request", + "properties": { + "requestId": { + "type": "string", + "description": "Id of the data access request" + }, + "accessRequirementName": { + "type": "string", + "description": "Name of the related access requirement" + }, + "status": { + "$ref": "org.sagebionetworks.repo.model.dataaccess.AccessRequestStatusEnum", + "description": "The status of the request" + }, + "isEDuc": { + "type": "boolean", + "description": "True if the request uses the eDUC flow" + }, + "signaturesRequested": { + "type": "integer", + "description": "The number of signers requested for the eDUC, or null if using non-eDUC flow" + }, + "signaturesAcquired": { + "type": "integer", + "description": "The number of signers who have signed the eDUC, or null if using non-eDUC flow" + }, + "submittedOn": { + "type": "string", + "format": "date-time", + "description": "Date/time the request was submitted, or null if not yet submitted" + }, + "modifiedOn": { + "type": "string", + "format": "date-time", + "description": "Date/time the request was last modified" + } + } +} diff --git a/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/migration/MigrationType.json b/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/migration/MigrationType.json index a0e6ad1abfd..2938f0ed138 100644 --- a/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/migration/MigrationType.json +++ b/lib/lib-auto-generated/src/main/resources/schema/org/sagebionetworks/repo/model/migration/MigrationType.json @@ -554,6 +554,10 @@ "name": "EDUC_QUOTA", "description": "Table that tracks eDUC envelope routing quotas" }, + { + "name": "DATA_ACCESS_REQUEST_USER", + "description": "Users associated with a data access request (creator, PI, collaborators)" + }, { "name": "CHANGE", "description": "A record of a change event in the system, used for migration tracking." diff --git a/lib/lib-docusign/src/main/java/org/sagebionetworks/docusign/DocuSignClient.java b/lib/lib-docusign/src/main/java/org/sagebionetworks/docusign/DocuSignClient.java index fe5eae7e72f..785dfb700a5 100644 --- a/lib/lib-docusign/src/main/java/org/sagebionetworks/docusign/DocuSignClient.java +++ b/lib/lib-docusign/src/main/java/org/sagebionetworks/docusign/DocuSignClient.java @@ -161,6 +161,13 @@ public EnvelopeStatusResult getEnvelopeStatus(String envelopeId) { return new EnvelopeStatusResult(status, signerEmails); } + public List listEnvelopeStatuses(List envelopeIds) { + if (envelopeIds == null || envelopeIds.isEmpty()) { + return List.of(); + } + return envelopesApi.listStatus(envelopeIds); + } + public byte[] getSignedDocument(String envelopeId) { ValidateArgument.required(envelopeId, "envelopeId"); Envelope envelope = envelopesApi.getEnvelope(envelopeId); diff --git a/lib/lib-docusign/src/main/java/org/sagebionetworks/docusign/DocuSignEnvelopesApi.java b/lib/lib-docusign/src/main/java/org/sagebionetworks/docusign/DocuSignEnvelopesApi.java index 92097b34575..7ed1f01eca2 100644 --- a/lib/lib-docusign/src/main/java/org/sagebionetworks/docusign/DocuSignEnvelopesApi.java +++ b/lib/lib-docusign/src/main/java/org/sagebionetworks/docusign/DocuSignEnvelopesApi.java @@ -1,5 +1,7 @@ package org.sagebionetworks.docusign; +import java.util.List; + import com.docusign.esign.model.Envelope; import com.docusign.esign.model.EnvelopeDefinition; import com.docusign.esign.model.EnvelopeSummary; @@ -12,5 +14,7 @@ interface DocuSignEnvelopesApi { Envelope getEnvelope(String envelopeId); + List listStatus(List envelopeIds); + byte[] getDocument(String envelopeId, String documentId); } diff --git a/lib/lib-docusign/src/main/java/org/sagebionetworks/docusign/DocuSignEnvelopesApiImpl.java b/lib/lib-docusign/src/main/java/org/sagebionetworks/docusign/DocuSignEnvelopesApiImpl.java index dfe818aca2c..964df9111f6 100644 --- a/lib/lib-docusign/src/main/java/org/sagebionetworks/docusign/DocuSignEnvelopesApiImpl.java +++ b/lib/lib-docusign/src/main/java/org/sagebionetworks/docusign/DocuSignEnvelopesApiImpl.java @@ -1,12 +1,17 @@ package org.sagebionetworks.docusign; +import java.util.Collections; +import java.util.List; + import org.springframework.stereotype.Service; import com.docusign.esign.api.EnvelopesApi; import com.docusign.esign.client.ApiClient; import com.docusign.esign.model.Envelope; import com.docusign.esign.model.EnvelopeDefinition; +import com.docusign.esign.model.EnvelopeIdsRequest; import com.docusign.esign.model.EnvelopeSummary; +import com.docusign.esign.model.EnvelopesInformation; @Service class DocuSignEnvelopesApiImpl implements DocuSignEnvelopesApi { @@ -54,6 +59,19 @@ public Envelope getEnvelope(String envelopeId) { }); } + @Override + public List listStatus(List envelopeIds) { + return retryHelper.executeWithRetry(accessToken -> { + ApiClient apiClient = new ApiClient(config.getBasePath()); + apiClient.addDefaultHeader("Authorization", "Bearer " + accessToken); + EnvelopesApi envelopesApi = new EnvelopesApi(apiClient); + EnvelopeIdsRequest request = new EnvelopeIdsRequest(); + request.setEnvelopeIds(envelopeIds); + EnvelopesInformation info = envelopesApi.listStatus(config.getAccountId(), request); + return info.getEnvelopes() != null ? info.getEnvelopes() : Collections.emptyList(); + }); + } + @Override public byte[] getDocument(String envelopeId, String documentId) { return retryHelper.executeWithRetry(accessToken -> { diff --git a/services/repository-managers/src/main/java/org/sagebionetworks/repo/manager/dataaccess/RequestManager.java b/services/repository-managers/src/main/java/org/sagebionetworks/repo/manager/dataaccess/RequestManager.java index 1e4021ea370..652aafefd92 100644 --- a/services/repository-managers/src/main/java/org/sagebionetworks/repo/manager/dataaccess/RequestManager.java +++ b/services/repository-managers/src/main/java/org/sagebionetworks/repo/manager/dataaccess/RequestManager.java @@ -1,6 +1,8 @@ package org.sagebionetworks.repo.manager.dataaccess; import org.sagebionetworks.repo.model.UserInfo; +import org.sagebionetworks.repo.model.dataaccess.AccessRequestList; +import org.sagebionetworks.repo.model.dataaccess.AccessRequestListRequest; import org.sagebionetworks.repo.model.dataaccess.RequestInterface; import org.sagebionetworks.repo.web.NotFoundException; @@ -45,5 +47,7 @@ public interface RequestManager { */ public RequestInterface getRequestForSubmission(String requestId); + public AccessRequestList listUserRequests(UserInfo userInfo, AccessRequestListRequest request); + public void truncateAll(); } diff --git a/services/repository-managers/src/main/java/org/sagebionetworks/repo/manager/dataaccess/RequestManagerImpl.java b/services/repository-managers/src/main/java/org/sagebionetworks/repo/manager/dataaccess/RequestManagerImpl.java index dc6e0217897..1735a493600 100644 --- a/services/repository-managers/src/main/java/org/sagebionetworks/repo/manager/dataaccess/RequestManagerImpl.java +++ b/services/repository-managers/src/main/java/org/sagebionetworks/repo/manager/dataaccess/RequestManagerImpl.java @@ -1,14 +1,23 @@ package org.sagebionetworks.repo.manager.dataaccess; +import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.sagebionetworks.docusign.DocuSignClient; import org.sagebionetworks.docusign.EnvelopeStatusResult; import org.sagebionetworks.repo.manager.file.FileHandleAuthorizationManager; +import org.sagebionetworks.repo.model.NextPageToken; import org.sagebionetworks.repo.model.educ.EDucStatusEnum; import org.sagebionetworks.repo.model.AccessRequirement; +import org.sagebionetworks.repo.model.dataaccess.AccessRequestList; +import org.sagebionetworks.repo.model.dataaccess.AccessRequestListRequest; +import org.sagebionetworks.repo.model.dataaccess.AccessRequestStatusEnum; +import org.sagebionetworks.repo.model.dataaccess.AccessRequestSummary; import org.sagebionetworks.repo.model.AccessRequirementDAO; import org.sagebionetworks.repo.model.ConflictingUpdateException; import org.sagebionetworks.repo.model.ManagedACTAccessRequirement; @@ -23,7 +32,11 @@ import org.sagebionetworks.repo.model.dataaccess.SigningOfficial; import org.sagebionetworks.repo.model.dataaccess.SubmissionState; import org.sagebionetworks.repo.model.dbo.dao.dataaccess.RequestDAO; +import org.sagebionetworks.repo.model.dbo.dao.dataaccess.RequestUserInfo; import org.sagebionetworks.repo.model.dbo.dao.dataaccess.SubmissionDAO; + +import com.docusign.esign.model.Envelope; +import com.docusign.esign.model.Signer; import org.sagebionetworks.repo.model.principal.AliasEnum; import org.sagebionetworks.repo.transactions.WriteTransaction; import org.sagebionetworks.repo.web.NotFoundException; @@ -258,6 +271,107 @@ public RequestInterface getRequestForSubmission(String requestId) { return requestDao.get(requestId); } + @Override + public AccessRequestList listUserRequests(UserInfo userInfo, AccessRequestListRequest request) { + ValidateArgument.required(userInfo, "userInfo"); + ValidateArgument.required(request, "request"); + + NextPageToken token = new NextPageToken(request.getNextPageToken()); + List page = requestDao.getUserRequests( + userInfo.getId(), token.getLimitForQuery(), token.getOffset()); + + List envelopeIds = page.stream() + .filter(RequestUserInfo::getIsEDuc) + .map(r -> r.getEnvelopeId()) + .filter(id -> id != null) + .collect(Collectors.toList()); + + Map envelopeMap = new HashMap<>(); + if (!envelopeIds.isEmpty()) { + List envelopes = docuSignClient.listEnvelopeStatuses(envelopeIds); + for (Envelope env : envelopes) { + envelopeMap.put(env.getEnvelopeId(), env); + } + } + + List results = new ArrayList<>(); + for (RequestUserInfo info : page) { + AccessRequestSummary summary = new AccessRequestSummary(); + summary.setRequestId(info.getRequestId()); + summary.setAccessRequirementName(info.getAccessRequirementName()); + summary.setIsEDuc(info.getIsEDuc()); + summary.setSubmittedOn(info.getSubmittedOn()); + summary.setModifiedOn(info.getModifiedOn()); + + if (info.getSubmissionStatus() != null) { + summary.setStatus(toAccessRequestStatus(info.getSubmissionStatus())); + } else if (info.getIsEDuc() && info.getEnvelopeId() != null) { + Envelope env = envelopeMap.get(info.getEnvelopeId()); + if (env != null) { + summary.setStatus(toAccessRequestStatusFromEnvelope(env.getStatus())); + if (env.getRecipients() != null && env.getRecipients().getSigners() != null) { + List signers = env.getRecipients().getSigners(); + summary.setSignaturesRequested((long) signers.size()); + long completed = signers.stream() + .filter(s -> "completed".equalsIgnoreCase(s.getStatus()) + || "signed".equalsIgnoreCase(s.getStatus())) + .count(); + summary.setSignaturesAcquired(completed); + } + } else { + summary.setStatus(AccessRequestStatusEnum.created); + } + } else { + summary.setStatus(AccessRequestStatusEnum.created); + } + + results.add(summary); + } + + AccessRequestList result = new AccessRequestList(); + result.setResults(results); + result.setNextPageToken(token.getNextPageTokenForCurrentResults(results)); + return result; + } + + static AccessRequestStatusEnum toAccessRequestStatus(String submissionState) { + switch (submissionState.toUpperCase()) { + case "SUBMITTED": + return AccessRequestStatusEnum.submitted; + case "APPROVED": + return AccessRequestStatusEnum.approved; + case "REJECTED": + return AccessRequestStatusEnum.rejected; + case "CANCELLED": + return AccessRequestStatusEnum.cancelled; + default: + return AccessRequestStatusEnum.created; + } + } + + static AccessRequestStatusEnum toAccessRequestStatusFromEnvelope(String envelopeStatus) { + if (envelopeStatus == null) { + return AccessRequestStatusEnum.created; + } + switch (envelopeStatus.toLowerCase()) { + case "sent": + return AccessRequestStatusEnum.sent; + case "delivered": + return AccessRequestStatusEnum.delivered; + case "completed": + case "signed": + return AccessRequestStatusEnum.completed; + case "declined": + return AccessRequestStatusEnum.declined; + case "voided": + return AccessRequestStatusEnum.voided; + case "correct": + return AccessRequestStatusEnum.correct; + default: + return AccessRequestStatusEnum.created; + } + } + @Override public void truncateAll() { requestDao.truncateAll(); diff --git a/services/repository-managers/src/main/java/org/sagebionetworks/repo/service/dataaccess/DataAccessService.java b/services/repository-managers/src/main/java/org/sagebionetworks/repo/service/dataaccess/DataAccessService.java index a17c109bda7..60887dfb568 100644 --- a/services/repository-managers/src/main/java/org/sagebionetworks/repo/service/dataaccess/DataAccessService.java +++ b/services/repository-managers/src/main/java/org/sagebionetworks/repo/service/dataaccess/DataAccessService.java @@ -1,6 +1,8 @@ package org.sagebionetworks.repo.service.dataaccess; import org.sagebionetworks.repo.model.AccessApproval; +import org.sagebionetworks.repo.model.dataaccess.AccessRequestList; +import org.sagebionetworks.repo.model.dataaccess.AccessRequestListRequest; import org.sagebionetworks.repo.model.RestrictionInformationBatchRequest; import org.sagebionetworks.repo.model.RestrictionInformationBatchResponse; import org.sagebionetworks.repo.model.RestrictionInformationRequest; @@ -63,4 +65,6 @@ public interface DataAccessService { UserSubmissionSearchResponse searchUserSubmissions(Long userId, UserSubmissionSearchRequest request); + AccessRequestList listUserRequests(Long userId, AccessRequestListRequest request); + } diff --git a/services/repository-managers/src/main/java/org/sagebionetworks/repo/service/dataaccess/DataAccessServiceImpl.java b/services/repository-managers/src/main/java/org/sagebionetworks/repo/service/dataaccess/DataAccessServiceImpl.java index b7278f32540..2721a21e06d 100644 --- a/services/repository-managers/src/main/java/org/sagebionetworks/repo/service/dataaccess/DataAccessServiceImpl.java +++ b/services/repository-managers/src/main/java/org/sagebionetworks/repo/service/dataaccess/DataAccessServiceImpl.java @@ -11,6 +11,8 @@ import org.sagebionetworks.repo.model.RestrictionInformationRequest; import org.sagebionetworks.repo.model.RestrictionInformationResponse; import org.sagebionetworks.repo.model.UserInfo; +import org.sagebionetworks.repo.model.dataaccess.AccessRequestList; +import org.sagebionetworks.repo.model.dataaccess.AccessRequestListRequest; import org.sagebionetworks.repo.model.dataaccess.AccessRequirementStatus; import org.sagebionetworks.repo.model.dataaccess.CreateSubmissionRequest; import org.sagebionetworks.repo.model.dataaccess.OpenSubmissionPage; @@ -160,4 +162,10 @@ public UserSubmissionSearchResponse searchUserSubmissions(Long userId, UserSubmi return dataAccessSubmissionManager.searchUserSubmissions(user, request); } + @Override + public AccessRequestList listUserRequests(Long userId, AccessRequestListRequest request) { + UserInfo user = userManager.getUserInfo(userId); + return dataAccessRequestManager.listUserRequests(user, request); + } + } diff --git a/services/repository/src/main/java/org/sagebionetworks/repo/web/UrlHelpers.java b/services/repository/src/main/java/org/sagebionetworks/repo/web/UrlHelpers.java index f9ac712b793..c05619fc229 100644 --- a/services/repository/src/main/java/org/sagebionetworks/repo/web/UrlHelpers.java +++ b/services/repository/src/main/java/org/sagebionetworks/repo/web/UrlHelpers.java @@ -1180,6 +1180,7 @@ public class UrlHelpers { ACCESS_REQUIREMENT_WITH_REQUIREMENT_ID +"/dataAccessRequestForUpdate"; public static final String DATA_ACCESS_REQUEST_ID_SUBMISSION = DATA_ACCESS_REQUEST+"/{requestId}/submission"; + public static final String DATA_ACCESS_REQUEST_LIST = DATA_ACCESS_REQUEST + "/list"; public static final String DATA_ACCESS_REQUEST_ID_SIGNATURE = DATA_ACCESS_REQUEST + "/{requestId}/signature"; public static final String DATA_ACCESS_REQUEST_ID_SIGNATURE_STATUS = DATA_ACCESS_REQUEST_ID_SIGNATURE + "/status"; public static final String DATA_ACCESS_REQUEST_ID_SIGNATURE_FILE_HANDLE = DATA_ACCESS_REQUEST_ID_SIGNATURE + "/filehandleId"; diff --git a/services/repository/src/main/java/org/sagebionetworks/repo/web/controller/DataAccessController.java b/services/repository/src/main/java/org/sagebionetworks/repo/web/controller/DataAccessController.java index 472ba3741f9..db5205fdf51 100644 --- a/services/repository/src/main/java/org/sagebionetworks/repo/web/controller/DataAccessController.java +++ b/services/repository/src/main/java/org/sagebionetworks/repo/web/controller/DataAccessController.java @@ -10,6 +10,8 @@ import org.sagebionetworks.repo.model.RestrictionInformationBatchResponse; import org.sagebionetworks.repo.model.RestrictionInformationRequest; import org.sagebionetworks.repo.model.RestrictionInformationResponse; +import org.sagebionetworks.repo.model.dataaccess.AccessRequestList; +import org.sagebionetworks.repo.model.dataaccess.AccessRequestListRequest; import org.sagebionetworks.repo.model.dataaccess.AccessRequirementStatus; import org.sagebionetworks.repo.model.dataaccess.CreateSubmissionRequest; import org.sagebionetworks.repo.model.dataaccess.OpenSubmissionPage; @@ -116,6 +118,22 @@ public class DataAccessController { return serviceProvider.getDataAccessService().createOrUpdate(userId, toCreate); } + /** + * List data access requests associated with the current user. + * + * @param userId - The ID of the user who is making the request. + * @param request - Pagination parameters. + * @return A paginated list of access request summaries. + */ + @RequiredScope({view}) + @ResponseStatus(HttpStatus.OK) + @RequestMapping(value = UrlHelpers.DATA_ACCESS_REQUEST_LIST, method = RequestMethod.POST) + public @ResponseBody AccessRequestList listUserRequests( + @RequestParam(value = AuthorizationConstants.USER_ID_PARAM) Long userId, + @RequestBody AccessRequestListRequest request) { + return serviceProvider.getDataAccessService().listUserRequests(userId, request); + } + /** * Retrieve the Request for update. * If one does not exist, an Request with some re-filled information is returned.