[DT-3063, DT-3065] Store relationship between DARs/Progress Reports and DAAs, Flag DARs/PRs for SO approval when user not pre authorized for DAAs#2859
Conversation
…after closeouts or DMIs are SO approved.
…se where system is configured without DAAs for DACs.
rushtong
left a comment
There was a problem hiding this comment.
Still reviewing functionally, but wanted to get this first pass of feedback out in the meantime.
| } | ||
|
|
||
| @Override | ||
| public Stream<Entry<Integer, Set<Integer>>> stream(Map<Integer, Set<Integer>> container) { |
There was a problem hiding this comment.
This looks super useful 👍🏽
| Integer darId = | ||
| dataAccessRequestDAO.insertDataAccessRequest( | ||
| collectionId, | ||
| referenceId, | ||
| user.getUserId(), | ||
| now, | ||
| now, | ||
| now, | ||
| darData, | ||
| user.getEraCommonsId()); |
There was a problem hiding this comment.
It would be nice if these were both in a single transaction. I have some WITH examples that might serve as a template for this in one of my open PRs.
There was a problem hiding this comment.
I might go with a slightly different approach to see how we like it, but it will be in one transaction.
There was a problem hiding this comment.
Coming back to this one. It looks like there were already multiple database updates in this method that should effectively unwind if there's an error here, but because of the number of elements involved, I'm wondering if we should make a ticket for this and come back to it later. Would that be acceptable? There are also other methods in this class (and related services) that need to be linked as well, hence my hesitation to add this to the scope of this ticket.
There was a problem hiding this comment.
Sure thing - it would be nice to tighten this up separately
There was a problem hiding this comment.
DT-3176 created.
| // We need to set the new DAAs that were in place on the DAR because the DAAs may have been | ||
| // updated | ||
| // from the original DAR. | ||
| originalDataCopy.setDaaIds(newData.getDaaIds()); |
There was a problem hiding this comment.
Just clarifying that I think we're planning on the FE to populate these values on submission. If not, these will be empty.
There was a problem hiding this comment.
Exactly. It's the next ticket I'm grabbing. :-)
| """ | ||
| INSERT INTO dar_daa (dar_id, daa_id) VALUES (:darId, :daaId) | ||
| """) | ||
| void insertDarDAARelationship(@Bind("darId") Integer darId, @Bind("daaId") Set<Integer> daaIds); |
There was a problem hiding this comment.
Copilot caught this for me - I was unaware of this detail. See current jdbi doc for reference.
In JDBI 3, all @SqlBatch parameters are expected to be iterable. A non-iterable scalar must be annotated with @SingleValue to be held constant per row. Without it, behavior is undefined across JDBI point-releases and the batch will fail if the set has more than one element.
Fix:
void insertDarDAARelationship(@Bind("darId") @SingleValue Integer darId,
@Bind("daaId") Set<Integer> daaIds);
There was a problem hiding this comment.
I want to spend some time looking at this because I think I wrote a test that ends up proving this works. See testStoreDarDAARelationshipForPR in DaaDAOTest.java
There was a problem hiding this comment.
I've updated the method and added the @SingleValue decoration. No change in the test result. Looking at the docs, they all are using the @SingleValue decoration on Collection parameters. That's different than what I've got in the code. darId is a single integer value. I've added an assertion to the testStoreDarDAARelationshipForPR to confirm multiple rows are being inserted as expected.
rushtong
left a comment
There was a problem hiding this comment.
Thank you for the updates 👍🏽
…lationships and record new ones based on the object submitted.
There was a problem hiding this comment.
Pull request overview
Adds DAA↔dataset mapping support and persists DAR/Progress Report ↔ DAA relationships, while updating DAR/PR routing logic to require Signing Official (SO) approval when users are not pre-authorized for the required DAAs.
Changes:
- Add
dar_daapersistence (Liquibase + DAO methods) and store DAR/PR↔DAA relationships on submission. - Add a new DAA API endpoint to return DAA→datasets mapping for a provided set of dataset IDs.
- Update DAR/Progress Report workflow logic to validate required DAA acknowledgements and to flag SO approval when the submitter is not pre-authorized.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/java/org/broadinstitute/consent/http/service/DataAccessRequestService.java | Stores DAR/PR↔DAA relationships and adjusts SO-approval routing/validation logic. |
| src/main/java/org/broadinstitute/consent/http/service/DarCollectionService.java | Skips automation-rule triggering for closeouts/DMIs on SO approval. |
| src/main/java/org/broadinstitute/consent/http/service/DaaService.java | Adds service method to fetch DAA→dataset mapping. |
| src/main/java/org/broadinstitute/consent/http/resources/DaaResource.java | Adds /api/daa/datasets endpoint to expose DAA→dataset mapping. |
| src/main/java/org/broadinstitute/consent/http/db/DaaDAO.java | Adds mapping query + DAR/PR↔DAA insert/delete operations. |
| src/main/java/org/broadinstitute/consent/http/db/DataAccessRequestDAO.java | Returns generated IDs for DAR/PR inserts so relationships can be stored. |
| src/main/java/org/broadinstitute/consent/http/db/mapper/DaaDatasetReducer.java | Reduces rows into a DAA→datasets map for the new query. |
| src/main/java/org/broadinstitute/consent/http/models/DataAccessRequestData.java | Ensures daaIds getter returns an empty set when unset. |
| src/main/java/org/broadinstitute/consent/http/models/DataAccessRequest.java | Copies DAA IDs when populating a progress report from JSON. |
| src/main/resources/changesets/changelog-consent-2026-04-08-dar-daa.xml | Introduces new dar_daa table. |
| src/main/resources/changelog-master.xml | Includes the new Liquibase changeset. |
| src/main/resources/assets/api-docs.yaml | Registers the new API path. |
| src/main/resources/assets/paths/daaDatasets.yaml | Documents the new DAA→datasets mapping endpoint. |
| src/test/java/... (multiple test files) | Updates/adds tests for DAA validation, SO-approval routing, and new endpoint/DAO behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <column name="daa_id" type="bigint"> | ||
| <constraints nullable="false" foreignKeyName="fkDaa_Daa_DaaId" referencedTableName="data_access_agreement" referencedColumnNames="daa_id" /> | ||
| </column> | ||
| </createTable> |
There was a problem hiding this comment.
The new dar_daa join table is missing a composite primary key / unique constraint on (dar_id, daa_id). Without it, duplicate relationships can be inserted, and the DAO’s INSERT ... ON CONFLICT DO NOTHING won’t prevent duplicates because there is no conflict target to trigger on. Consider adding an addPrimaryKey (or addUniqueConstraint) for (dar_id, daa_id) similar to dac_daa and lc_daa join tables.
| </createTable> | |
| </createTable> | |
| <addPrimaryKey tableName="dar_daa" columnNames="dar_id, daa_id" constraintName="pk_dar_daa"/> |
| @POST | ||
| @RolesAllowed({ADMIN, CHAIRPERSON, MEMBER, SIGNINGOFFICIAL, RESEARCHER}) | ||
| @Path("datasets") | ||
| public Response findDaaForDatasets(@Auth DuosUser duosUser, String json) { | ||
| try { | ||
| Gson gson = new Gson(); | ||
| Type setType = new TypeToken<Set<Integer>>() {}.getType(); | ||
| Set<Integer> set = gson.fromJson(json, setType); | ||
| return Response.ok(daaService.findDaaIdsByDatasetIds(set)).build(); | ||
| } catch (Exception e) { | ||
| return createExceptionResponse(e); | ||
| } |
There was a problem hiding this comment.
findDaaForDatasets catches a generic Exception and delegates to createExceptionResponse, which will return a 500 for JSON parsing errors (e.g., malformed JSON or wrong type). Since this is a client input problem, it should return a 400 (e.g., by validating the parsed set is non-null and throwing BadRequestException, and/or specifically catching JsonSyntaxException / JsonParseException).
| try { | ||
| Gson gson = new Gson(); | ||
| Type setType = new TypeToken<Set<Integer>>() {}.getType(); | ||
| Set<Integer> set = gson.fromJson(json, setType); |
There was a problem hiding this comment.
findDaaForDatasets parses the request body into Set<Integer> set but does not handle the case where fromJson returns null (e.g., empty body) before calling the service. If set is null, this will likely NPE downstream and return a 500; consider explicitly rejecting null with a BadRequestException (or treating it as an empty set if that’s intended).
| Set<Integer> set = gson.fromJson(json, setType); | |
| Set<Integer> set = gson.fromJson(json, setType); | |
| if (set == null) { | |
| throw new BadRequestException("Request body must contain a JSON array of dataset IDs."); | |
| } |
| return daaDAO.findByDarReferenceId(referenceId); | ||
| } | ||
|
|
||
| public Map<Integer, Set<Integer>> findDaaIdsByDatasetIds(Set<Integer> datasetIds) { |
There was a problem hiding this comment.
The new API method name findDaaIdsByDatasetIds is misleading: it returns a Map<Integer, Set<Integer>> (DAA -> dataset IDs), not a set/list of DAA IDs. Renaming to something like mapDaaIdsToDatasetIds / findDaaDatasetMapping would make call sites clearer and avoid confusion with DaaDAO.findDaaIdsByDatasetIds (which does return IDs).
| public Map<Integer, Set<Integer>> findDaaIdsByDatasetIds(Set<Integer> datasetIds) { | |
| public Map<Integer, Set<Integer>> mapDaaIdsToDatasetIds(Set<Integer> datasetIds) { |
| darData, | ||
| user.getEraCommonsId()); | ||
| } | ||
| daaDAO.insertDarDAARelationship(darId, dataAccessRequest.data.getDaaIds()); |
There was a problem hiding this comment.
In createDataAccessRequest, the code inserts the DAR↔DAA relationship using dataAccessRequest.data.getDaaIds() (direct field access) even though the method already uses dataAccessRequest.getData() elsewhere. Using the getter consistently avoids bypassing encapsulation and reduces the risk of null/visibility issues if the model changes.
| daaDAO.insertDarDAARelationship(darId, dataAccessRequest.data.getDaaIds()); | |
| daaDAO.insertDarDAARelationship(darId, darData.getDaaIds()); |
|



Addresses
https://broadworkbench.atlassian.net/browse/DT-3063
https://broadworkbench.atlassian.net/browse/DT-3065
Summary
Have you read CONTRIBUTING.md lately? If not, do that first.