Skip to content

[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

Open
otchet-broad wants to merge 29 commits intodevelopfrom
otchet-dt-3063-find-daas-for-dataset-ids
Open

[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
otchet-broad wants to merge 29 commits intodevelopfrom
otchet-dt-3063-find-daas-for-dataset-ids

Conversation

@otchet-broad
Copy link
Copy Markdown
Contributor

@otchet-broad otchet-broad commented Apr 8, 2026

Addresses

https://broadworkbench.atlassian.net/browse/DT-3063
https://broadworkbench.atlassian.net/browse/DT-3065

Summary

  • Provides an API for obtaining the mapping of the DAAs to a list of datasets so that the client can display the relationship
  • Validates the DAAs submitted match the DAR or Progress Report (when not a closeout) expected DAAs and routes the DAR to either the SO or the DAC.
  • Stores the relationship between data_access_request table entries with DAAs on DAR or Progress Report (when not a closeout) submission

Have you read CONTRIBUTING.md lately? If not, do that first.

  • Label PR with a Jira ticket number and include a link to the ticket
  • Label PR with a security risk modifier [no, low, medium, high]
  • PR describes scope of changes
  • Get a minimum of one thumbs worth of review, preferably two if enough team members are available
  • Get PO sign-off for all non-trivial UI or workflow changes
  • Verify all tests go green
  • Test this change deployed correctly and works on dev environment after deployment

@otchet-broad otchet-broad marked this pull request as ready for review April 8, 2026 22:24
@otchet-broad otchet-broad requested a review from a team as a code owner April 8, 2026 22:24
@otchet-broad otchet-broad requested review from eweitz, fboulnois, kevinmarete and rushtong and removed request for a team April 8, 2026 22:24
@otchet-broad otchet-broad marked this pull request as draft April 8, 2026 22:41
@otchet-broad otchet-broad changed the title [DT-3063] Store relationship between DARs/Progress Reports and DAAs [DT-3063, DT-3065] Store relationship between DARs/Progress Reports and DAAs Apr 9, 2026
@otchet-broad otchet-broad changed the title [DT-3063, DT-3065] Store relationship between DARs/Progress Reports and DAAs [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 Apr 9, 2026
@otchet-broad otchet-broad marked this pull request as ready for review April 9, 2026 21:56
Copy link
Copy Markdown
Contributor

@rushtong rushtong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks super useful 👍🏽

Comment on lines +250 to +259
Integer darId =
dataAccessRequestDAO.insertDataAccessRequest(
collectionId,
referenceId,
user.getUserId(),
now,
now,
now,
darData,
user.getEraCommonsId());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might go with a slightly different approach to see how we like it, but it will be in one transaction.

Copy link
Copy Markdown
Contributor Author

@otchet-broad otchet-broad Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing - it would be nice to tighten this up separately

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just clarifying that I think we're planning on the FE to populate these values on submission. If not, these will be empty.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. It's the next ticket I'm grabbing. :-)

Comment thread src/main/java/org/broadinstitute/consent/http/db/DaaDAO.java
"""
INSERT INTO dar_daa (dar_id, daa_id) VALUES (:darId, :daaId)
""")
void insertDarDAARelationship(@Bind("darId") Integer darId, @Bind("daaId") Set<Integer> daaIds);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/main/resources/assets/paths/daaDatasets.yaml Outdated
Comment thread src/main/resources/assets/paths/daaDatasets.yaml Outdated
Comment thread src/main/java/org/broadinstitute/consent/http/db/DaaDAO.java Outdated
Copy link
Copy Markdown
Contributor

@rushtong rushtong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the updates 👍🏽

…lationships and record new ones based on the object submitted.
Copilot AI review requested due to automatic review settings April 21, 2026 18:34
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_daa persistence (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>
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
</createTable>
</createTable>
<addPrimaryKey tableName="dar_daa" columnNames="dar_id, daa_id" constraintName="pk_dar_daa"/>

Copilot uses AI. Check for mistakes.
Comment on lines +400 to +411
@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);
}
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
try {
Gson gson = new Gson();
Type setType = new TypeToken<Set<Integer>>() {}.getType();
Set<Integer> set = gson.fromJson(json, setType);
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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.");
}

Copilot uses AI. Check for mistakes.
return daaDAO.findByDarReferenceId(referenceId);
}

public Map<Integer, Set<Integer>> findDaaIdsByDatasetIds(Set<Integer> datasetIds) {
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
public Map<Integer, Set<Integer>> findDaaIdsByDatasetIds(Set<Integer> datasetIds) {
public Map<Integer, Set<Integer>> mapDaaIdsToDatasetIds(Set<Integer> datasetIds) {

Copilot uses AI. Check for mistakes.
darData,
user.getEraCommonsId());
}
daaDAO.insertDarDAARelationship(darId, dataAccessRequest.data.getDaaIds());
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
daaDAO.insertDarDAARelationship(darId, dataAccessRequest.data.getDaaIds());
daaDAO.insertDarDAARelationship(darId, darData.getDaaIds());

Copilot uses AI. Check for mistakes.
@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants