Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3372 - adds database changes to support managing IAA subrecipients #3522

Merged
merged 27 commits into from
Sep 20, 2024

Conversation

as1729
Copy link
Contributor

@as1729 as1729 commented Sep 18, 2024

Ticket #3372

Description

This PR adds support for the new IAA subrecipient field. The goal here is to ensure that we can add subrecipients that do not have either a UEI or TIN value. In order to do this the PR does the following:

  1. Adds a new name field to arpa_subrecipients table and a corresponding unique index that ensures that when both uei and tin are missing, that we do not create duplicate subrecipients with the same name.
  2. Adds application-layer validation when createSubrecipient function is called.
  3. Removes unique constraint on uei and tin fields and replaces with a unique index that only applies when these fields are NOT NULL.

Screenshots / Demo Video

State Before migrations

usdr_grants=# \d arpa_subrecipients
                                          Table "public.arpa_subrecipients"
      Column       |           Type           | Collation | Nullable |                    Default                     
-------------------+--------------------------+-----------+----------+------------------------------------------------
 id                | integer                  |           | not null | nextval('arpa_subrecipients_id_seq'::regclass)
 tenant_id         | integer                  |           | not null | 
 created_at        | timestamp with time zone |           | not null | CURRENT_TIMESTAMP
 updated_at        | timestamp with time zone |           |          | 
 updated_by        | integer                  |           |          | 
 uei               | text                     |           |          | 
 tin               | text                     |           |          | 
 record            | text                     |           |          | 
 upload_id         | uuid                     |           |          | 
 treasury_id       | text                     |           |          | 
 has_been_reported | boolean                  |           | not null | false
 archived_at       | timestamp with time zone |           |          | 
Indexes:
    "arpa_subrecipients_pkey" PRIMARY KEY, btree (id)
    "arpa_subrecipients_tenant_id_tin_unique" UNIQUE CONSTRAINT, btree (tenant_id, tin)
    "arpa_subrecipients_tenant_id_treasury_id_unique" UNIQUE CONSTRAINT, btree (tenant_id, treasury_id)
    "arpa_subrecipients_tenant_id_uei_unique" UNIQUE CONSTRAINT, btree (tenant_id, uei)
Foreign-key constraints:
    "arpa_subrecipients_tenant_id_foreign" FOREIGN KEY (tenant_id) REFERENCES tenants(id)
    "arpa_subrecipients_updated_by_foreign" FOREIGN KEY (updated_by) REFERENCES users(id)
    "arpa_subrecipients_upload_id_foreign" FOREIGN KEY (upload_id) REFERENCES uploads(id) ON DELETE CASCADE

State After Migrations

fdcc06a80a69:/app/packages/server# yarn db:migrate
yarn run v1.22.19
$ knex migrate:latest
Using environment: development
Batch 2 run: 3 migrations
Done in 0.92s.
fdcc06a80a69:/app/packages/server# 

usdr_grants=# \d arpa_subrecipients
                                          Table "public.arpa_subrecipients"
      Column       |           Type           | Collation | Nullable |                    Default                     
-------------------+--------------------------+-----------+----------+------------------------------------------------
 id                | integer                  |           | not null | nextval('arpa_subrecipients_id_seq'::regclass)
 tenant_id         | integer                  |           | not null | 
 created_at        | timestamp with time zone |           | not null | CURRENT_TIMESTAMP
 updated_at        | timestamp with time zone |           |          | 
 updated_by        | integer                  |           |          | 
 uei               | text                     |           |          | 
 tin               | text                     |           |          | 
 record            | text                     |           |          | 
 upload_id         | uuid                     |           |          | 
 treasury_id       | text                     |           |          | 
 has_been_reported | boolean                  |           | not null | false
 archived_at       | timestamp with time zone |           |          | 
 name              | text                     |           |          | 
Indexes:
    "arpa_subrecipients_pkey" PRIMARY KEY, btree (id)
    "arpa_subrecipients_tenant_id_treasury_id_unique" UNIQUE CONSTRAINT, btree (tenant_id, treasury_id)
    "idx_arpa_subrecipients_tenant_id_name_unique" UNIQUE, btree (tenant_id, name) WHERE name IS NOT NULL AND uei IS NULL AND tin IS NULL
    "idx_arpa_subrecipients_tenant_id_tin_unique" UNIQUE, btree (tenant_id, tin) WHERE tin IS NOT NULL
    "idx_arpa_subrecipients_tenant_id_uei_unique" UNIQUE, btree (tenant_id, uei) WHERE uei IS NOT NULL
Check constraints:
    "chk_at_least_one_of_uei_tin_name_not_null" CHECK (num_nonnulls(uei, tin, name) > 0)
Foreign-key constraints:
    "arpa_subrecipients_tenant_id_foreign" FOREIGN KEY (tenant_id) REFERENCES tenants(id)
    "arpa_subrecipients_updated_by_foreign" FOREIGN KEY (updated_by) REFERENCES users(id)
    "arpa_subrecipients_upload_id_foreign" FOREIGN KEY (upload_id) REFERENCES uploads(id) ON DELETE CASCADE

Confirming that Rollback works

fdcc06a80a69:/app/packages/server# yarn db:rollback
yarn run v1.22.19
$ knex migrate:rollback
Using environment: development
Batch 2 rolled back: 3 migrations
Done in 0.87s.
fdcc06a80a69:/app/packages/server# 

Testing

Automated and Unit Tests

  • Added Unit tests

Manual tests for Reviewer

  • Added steps to test feature/functionality manually

Checklist

  • Provided ticket and description
  • Provided screenshots/demo
  • Provided testing information
  • Provided adequate test coverage for all new code
  • Added PR reviewers

@github-actions github-actions bot added database-changes Includes schema migrations or other critical changes javascript Pull requests that update Javascript code labels Sep 18, 2024
Copy link

github-actions bot commented Sep 18, 2024

QA Summary

QA Check Result
🌐 Client Tests
🔗 Server Tests
🤝 E2E Tests
📏 ESLint
🧹 TFLint

Test Coverage

Coverage report for `packages/client`
St File % Stmts % Branch % Funcs % Lines Uncovered Line #s
🔴 All files 34.08 33.14 30.47 35.65
🔴  src 0 100 100 0
🔴   App.vue 0 100 100 0 2-9
🔴  src/arpa_reporter 0 100 100 0
🔴   App.vue 0 100 100 0 2-13
🟡  ...ter/components 58.58 48 46.8 59.78
🟡   AlertBox.vue 80 75 50 80 13
🔴   ...oadButton.vue 20 0 0 20 2-7,38-67
🟢   ...ileButton.vue 100 100 100 100
🟢   ...ttonSmall.vue 100 100 100 100
🟢   ...mplateBtn.vue 100 100 100 100
🟡   ...avigation.vue 67.74 63.63 52.63 67.74 ...13-219,228-235
🔴   StandardForm.vue 45 50 41.66 45.45 ...24-128,135-157
🟢  ...porter/helpers 84.61 79.48 87.5 84.61
🟢   form-helpers.js 84.21 79.48 85.71 84.21 7,16,25,81-83
🟢   short-uuid.js 100 100 100 100
🔴  ...eporter/router 0 0 0 0
🔴   index.js 0 0 0 0 20-131
🔴  ...reporter/store 3.92 0 2.17 4.12
🔴   index.js 3.92 0 2.17 4.12 11-14,32-261
🔴  ...reporter/views 40.98 25.13 41.37 42.91
🟢   AgenciesView.vue 100 0 100 100 16
🔴   AgencyView.vue 40.74 36.36 50 45.83 53-62,70-96
🔴   HomeView.vue 26.66 34.14 36.36 26.66 36-66,113,137-207
🔴   LoginView.vue 29.62 36.36 20 30.76 1,4,19-34,72-100
🔴   ...plateView.vue 27.02 35.71 45.45 25 ...2,30-37,69-113
🔴   ...ploadView.vue 24.24 16.66 33.33 26.66 1,30-31,116-144
🔴   ...eriodView.vue 48 18.18 75 52.17 64-90
🟡   ...riodsView.vue 57.57 28.57 60 59.37 101,132,149-171
🔴   ...pientView.vue 40 10.52 30.76 41.02 73-93,110-152
🔴   ...ientsView.vue 48.48 8.33 46.66 53.33 ...46,165-190,203
🟡   UploadView.vue 61.53 43.75 62.5 61.36 ...41-442,448-449
🔴   UploadsView.vue 16.66 0 0 18.18 59,110-287
🔴   UserView.vue 46.34 33.33 68.75 48.64 84,97-137
🟡   UsersView.vue 60 7.69 62.5 66.66 91-92,105-107,123
🔴   ...ationView.vue 37.03 18.18 26.66 41.66 ...19,238,246-270
🔴  src/components 46.4 39.66 47.32 47.12
🔴   ...vityTable.vue 18.75 0 28.57 19.35 115-185
🟡   BaseLayout.vue 69.56 53.84 60 69.56 172,219-231
🔴   CopyButton.vue 28.57 33.33 25 28.57 5,48-55
🟡   ...tActivity.vue 77.77 63.63 80 77.77 ...29-134,165,179
🔴   GrantsTable.vue 45.03 38.77 45 46.85 ...51-455,463-543
🔴   ...dUploader.vue 24.24 33.33 37.5 24.24 16,25,74-111
🔴   SearchFilter.vue 40.74 28 37.5 40 ...52,64,67,72-82
🔴   ShareGrant.vue 44.11 71.42 23.07 45.45 ...03-107,126-147
🟡   UserAvatar.vue 70 75 100 62.5 35-37
🔴  ...ponents/Modals 31.97 44.1 34 35.89
🔴   ...anization.vue 17.39 65.62 30 22.22 1-14,149-178
🔴   AddTeam.vue 45.45 55.55 61.53 57.69 ...04,210,222-245
🔴   AddUser.vue 36.66 66.66 60 37.5 ...40,145,148-176
🔴   ...anization.vue 15.38 64.28 14.28 16.66 1-15,58-78
🔴   EditTeam.vue 18.18 26.43 33.33 20.83 ...29,208,216-301
🔴   EditUser.vue 21.05 66.66 25 22.22 1,101-128
🔴   ...ilsLegacy.vue 22.03 0 0 24.07 131,177,205-369
🟢   ...Followers.vue 86.48 69.23 66.66 86.48 ...32,134,149-150
🟡   ImportTeams.vue 50 41.17 50 53.33 28,64-69,81-82
🔴   ImportUsers.vue 42.85 60 40 46.15 29,65-80
🔴   ...archPanel.vue 27.9 15.78 23.52 28.57 ...68-178,211-255
🔴   SearchPanel.vue 21.62 63.26 32 27.58 ...77-380,386-458
🔴  src/helpers 15.27 17.92 14.63 15.94
🟢   constants.js 100 100 100 100
🟢   currency.js 100 100 100 100
🟡   dates.js 66.66 100 33.33 100
🔴   fetchApi.js 6 13.79 5.26 6.12 10-12,20-132
🔴   filters.js 4 0 0 4.54 19-51
🔴   form-helpers.js 0 0 0 0 5-82
🟡   gtag.js 77.77 90 75 77.77 12,51
🔴   ...patWarning.js 0 0 0 0 39-61
🟢  ...s/featureFlags 83.33 100 66.66 83.33
🟡   index.js 50 100 50 50 8,16
🟢   utils.js 100 100 100 100
🔴  src/mixin 20 0 28.57 20
🔴   ...zableTable.js 20 0 28.57 20 16-31,36-37,42
🔴  src/router 18.91 14.28 11.11 18.91
🔴   index.js 18.91 14.28 11.11 18.91 ...76-177,181-200
🟢  src/store 100 100 100 100
🟢   index.js 100 100 100 100
🔴  src/store/modules 3.07 0 4.57 3.21
🔴   agencies.js 5.26 100 8.33 5.55 13-70
🔴   alerts.js 20 100 20 20 10-24
🔴   grants.js 1.12 0 1 1.16 61-419
🔴   organization.js 33.33 100 33.33 33.33 21-25
🔴   roles.js 20 100 20 25 13-22
🔴   tenants.js 11.11 100 14.28 12.5 13-32
🔴   users.js 2.43 0 4.76 2.5 17-100
🔴  src/views 42.76 35.95 32.45 44.48
🔴   ...orterView.vue 25.58 51.85 18.18 26.82 ...,62,84,109-151
🟡   ...boardView.vue 50 17.64 50 52 89-98,114-125
🔴   ...tailsView.vue 32.97 14.28 17.07 33.69 ...92-431,437-458
🟢   GrantsView.vue 100 100 100 100
🔴   LoginView.vue 34.61 33.33 22.22 36 1,22,57,109-136
🟡   MyGrantsView.vue 77.77 66.66 66.66 77.77 1,69
🟡   ...ofileView.vue 77.27 75 42.85 77.27 1,32,63,130-134
🟢   NotFoundView.vue 100 100 100 100
🔴   ...tionsView.vue 47.05 57.14 41.66 53.33 ...97-100,114-118
🔴   ...ivityView.vue 46.42 23.8 43.75 46.42 ...01,114,120-134
🔴   TeamsView.vue 44.44 88.88 41.66 53.33 1,58,142,156-163
🟡   UsersView.vue 50 66.66 36.36 53.84 ...16-121,133-139
Coverage report for `packages/server`
St File % Stmts % Branch % Funcs % Lines Uncovered Line #s
🟡 All files 59.77 53.09 55.28 59.85
🟢  src 81.63 33.33 60 81.63
🟢   configure.js 81.63 33.33 60 81.63 42,61-68,97-99
🟢  src/arpa_reporter 98.75 66.66 100 98.75
🟢   configure.js 97.36 40 100 97.36 36
🟢   environment.js 100 100 100 100
🟢   use-request.js 100 100 100 100
🟡  src/arpa_reporter/db 50.73 45.88 50 51.9
🟡   arpa-subrecipients.js 53.19 50 38.46 54.54 23-60,101,113-122
🔴   reporting-periods.js 37.2 46.87 40 38.09 46,77-156
🟢   settings.js 100 83.33 100 100 13
🟡   uploads.js 50 28.57 52.38 51.42 18-29,84,99-124,141-150
🔴  src/arpa_reporter/lib 29.57 33.08 34.56 28.46
🟢   arpa-ec-codes.js 100 100 100 100
🔴   audit-report.js 21.44 19.35 24.19 21.32 ...28-529,554-684,732-758
🟡   ensure-async-context.js 75 100 50 100
🟢   format.js 90.62 90 90 91.3 41-42
🟡   log.js 75 50 50 75 13,25
🟡   preconditions.js 66.66 33.33 100 66.66 3
🔴   spreadsheet.js 9.09 0 0 9.09 15-32
🟢   validation-error.js 85.71 100 50 85.71 16
🔴  src/arpa_reporter/routes 40 14.92 14.28 40.6
🔴   agencies.js 22.58 0 0 23.33 13-21,26-53
🟡   application_settings.js 75 100 0 75 10-11
🟡   audit-report.js 68.91 58.33 100 68.91 57-58,64-78,100-116
🟢   exports.js 81.42 83.33 100 81.42 61-75,98-99
🔴   reporting-periods.js 20 0 0 20.43 ...25-137,143-149,154-180
🔴   subrecipients.js 23.8 0 0 23.8 12-13,17-27,31-48,52-63
🔴   uploads.js 28.28 7.89 9.09 29.16 ...33-154,164-166,173-180
🔴   users.js 19.6 0 0 20 15-35,39-44,48-81
🔴  src/arpa_reporter/services 44.32 34.79 46.42 44.6
🔴   generate-arpa-report.js 36.86 2.77 50 37.24 ...-975,984-997,1071-1138
🔴   get-template.js 21.62 0 0 21.62 18-79
🟡   persist-upload.js 68.6 90 69.56 68.67 ...58-200,221-235,273-295
🔴   records.js 20.75 0 11.11 21.15 38-204,221-276
🔴   revalidate-uploads.js 37.5 100 0 37.5 5-14
🔴   validate-upload.js 43.56 58.51 37.03 44.2 ...52,371,393,411-688,703
🟢   validation-rules.js 98.18 90 90.9 100 157,173
🟡  src/db 74.26 71.42 68.42 74.29
🟢   connection.js 100 50 100 100 6
🟢   constants.js 100 100 100 100
🟡   helpers.js 75 83.33 50 75 5,21-22
🟢   index.js 82.4 78.54 82.08 82.35 ...48-1414,1596-1597,1604
🟢   saved_search_migration.js 92 88.23 71.42 93.61 5,69,134
🔴   tenant_creation.js 10.58 2.7 0 11.11 15-40,48-210,220
🔴  src/db/arpa_reporter_db_shims 23.68 0 0 23.68
🔴   agencies.js 22.22 100 0 22.22 11-51
🔴   [users.js](https://github.com/usdigitalresponse/usdr-gost/blob/d1fbf73255e3abfcb9e09fef39c43e964b50a27c/pack...*[Comment body truncated]*

Copy link

github-actions bot commented Sep 18, 2024

Terraform Summary

Step Result
🖌 Terraform Format & Style
⚙️ Terraform Initialization
🤖 Terraform Validation
📖 Terraform Plan

Hint: If "Terraform Format & Style" failed, run terraform fmt -recursive from the terraform/ directory and commit the results.

Output

Validation Output
stdout:
Success! The configuration is valid.


-------------------------------------
stderr:

Plan Summary
CHANGE RESOURCE
add module.website.aws_s3_object.origin_dist_artifact["assets/ActivityTable-CHTtikuK.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/ActivityTable-CHTtikuK.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/ArpaAnnualPerformanceReporterView-ByD2J9oL.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/ArpaAnnualPerformanceReporterView-ByD2J9oL.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/DashboardView-Bc9VE6iz.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/DashboardView-Bc9VE6iz.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantDetailsLegacy-DGKTE6en.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantDetailsLegacy-DGKTE6en.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantDetailsView-BjkfQ_yB.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantDetailsView-BjkfQ_yB.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantsTable-uNGPw_nO.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantsTable-uNGPw_nO.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantsView-CV3UiV-Y.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantsView-CV3UiV-Y.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/MyGrantsView-Bhg5a7oC.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/MyGrantsView-Bhg5a7oC.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/MyProfileView-C0W1TwYa.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/MyProfileView-C0W1TwYa.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/NotFoundView-3El2Q5Kn.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/NotFoundView-3El2Q5Kn.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/OrganizationsView-CwBKwmB2.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/OrganizationsView-CwBKwmB2.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/RecentActivityView-B2HL5VEH.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/RecentActivityView-B2HL5VEH.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/RecordUploader-8-i7eD-k.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/RecordUploader-8-i7eD-k.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/TeamsView-CloysElN.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/TeamsView-CloysElN.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/UsersView-EcO8qo38.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/UsersView-EcO8qo38.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/arpa_reporter-4f3JkiGe.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/arpa_reporter-4f3JkiGe.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/form-helpers-DIz_J9oO.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/form-helpers-DIz_J9oO.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/main-BLE5NEyI.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/main-BLE5NEyI.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/style-3MVopm_C.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/style-3MVopm_C.js.map"]
update module.api.aws_ecs_service.default[0]
module.api.module.grant_digest_scheduled_task.aws_iam_role_policy.default[0]
module.api.module.grant_digest_scheduled_task.aws_scheduler_schedule.default[0]
module.arpa_audit_report.aws_ecs_service.default
module.arpa_treasury_report.aws_ecs_service.default
module.consume_grants.aws_ecs_service.default
module.website.aws_s3_object.deploy-config[0]
module.website.aws_s3_object.origin_dist_artifact["arpa_reporter/index.html"]
module.website.aws_s3_object.origin_dist_artifact["index.html"]
recreate module.api.aws_ecs_task_definition.default[0]
module.arpa_audit_report.aws_ecs_task_definition.consumer
module.arpa_treasury_report.aws_ecs_task_definition.consumer
module.consume_grants.aws_ecs_task_definition.consume_grants
delete module.website.aws_s3_object.origin_dist_artifact["assets/ActivityTable-DfKIQ_wW.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/ActivityTable-DfKIQ_wW.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/ArpaAnnualPerformanceReporterView-DgxIsI5n.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/ArpaAnnualPerformanceReporterView-DgxIsI5n.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/DashboardView-PVgfy3U2.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/DashboardView-PVgfy3U2.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantDetailsLegacy-CydqlwxL.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantDetailsLegacy-CydqlwxL.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantDetailsView-D_kx4YtL.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantDetailsView-D_kx4YtL.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantsTable-CwYnTe6h.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantsTable-CwYnTe6h.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantsView-D-ma2MTv.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantsView-D-ma2MTv.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/MyGrantsView-DKQK-t23.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/MyGrantsView-DKQK-t23.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/MyProfileView-Czkd_bQG.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/MyProfileView-Czkd_bQG.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/NotFoundView-BHa6zy1b.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/NotFoundView-BHa6zy1b.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/OrganizationsView-Bf-ls2xi.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/OrganizationsView-Bf-ls2xi.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/RecentActivityView-DU7Tcx2z.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/RecentActivityView-DU7Tcx2z.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/RecordUploader-Cu0fIHyN.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/RecordUploader-Cu0fIHyN.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/TeamsView-B9xlCLQ-.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/TeamsView-B9xlCLQ-.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/UsersView-DkjTPwNA.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/UsersView-DkjTPwNA.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/arpa_reporter-Cyzx1SnQ.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/arpa_reporter-Cyzx1SnQ.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/form-helpers-Bjl_m51z.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/form-helpers-Bjl_m51z.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/main-51yKd0t5.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/main-51yKd0t5.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/style-DF53RXBe.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/style-DF53RXBe.js.map"]

Pusher: @as1729, Action: pull_request_target, Workflow: Continuous Integration

Copy link
Member

@TylerHendrickson TylerHendrickson left a comment

Choose a reason for hiding this comment

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

@as1729 A few initial comments for you to consider.

Additionally, would you mind filling out the description for this PR when you get a chance? I think that would help clarify the intended changes regarding the behavior of the new and existing fields (name, uei, and tin) for reviewers, especially since #3372 is showing up as closed by #3503.

Comment on lines 5 to 9
exports.up = function (knex) {
return knex.schema.table('arpa_subrecipients', (table) => {
table.text('name');
});
};
Copy link
Member

Choose a reason for hiding this comment

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

Is any kind of unique constraint/index warranted here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes good-call 👍
We only want to enforce it when both uei and tin are missing so as to not interfere with previous data in the database so I'll create an index with the where-clause as described above.

@as1729 as1729 marked this pull request as draft September 19, 2024 01:57
} else if (fieldType === 'tin') {
query.where('arpa_subrecipients.tin', value);
} else if (fieldType === 'name') {
query.where('arpa_subrecipients.name', value);
} else {
return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be worth logging or printing something here or failing if a fieldType is passed in that doesn't match uei, tin or name.

We should also document the expected fieldType values in a docstring.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good call- I updated docstrings as well as updated the method to throw an error instead of returning null as it can be confusing for the caller.

}
}
if (!recipient.tin && !recipient.uei && recipient.name) {
const existingRecipient = await findRecipient('name', recipient.name, trns);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can the same name exist across multiple tenants?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, findRecipient scopes by tenant by default, so we should only be looking for duplicates within a tenant.

@as1729 as1729 marked this pull request as ready for review September 19, 2024 21:27
@as1729 as1729 enabled auto-merge (squash) September 19, 2024 21:31
table.text('name');
});
await knex.raw(
'CREATE UNIQUE INDEX idx_arpa_subrecipients_tenant_id_name_unique ON arpa_subrecipients (tenant_id, name) WHERE name is not null and uei is null and tin is null',
Copy link
Member

Choose a reason for hiding this comment

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

In a situation like:

  • name is not null
  • uei is not null
  • tin is null

Should name still be unique to the tenant_id? Similarly, could this be simplified to (tenant_id, name) WHERE name is not null i.e. should duplicate non-null names for the same tenant allowed in any circumstance, regardless of uei or tin?

Also we may want an additional constraint to ensure that at least one of uei/tin/name is provided. If that sounds like a good idea to you, I think that could be implemented as a CHECK constraint like:

ALTER TABLE arpa_subrecipients
ADD CONSTRAINT
  chk_at_least_one_of_uei_tin_name_not_null 
  CHECK (num_nonnulls(uei, tin, name) > 0);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i.e. should duplicate non-null names for the same tenant allowed in any circumstance, regardless of uei or tin?

Right now we're working with insufficient data here. Working assumption is that there could be subrecipients in the system that share the same name but have slightly different UEI/TIN since this was never enforced in the past.

Also we may want an additional constraint to ensure that at least one of uei/tin/name is provided. If that sounds like a good idea to you, I think that could be implemented as a CHECK constraint like:

This makes sense to add since we enforce this on the application layer, can translate to a database check as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resolved

Comment on lines 95 to 97
if (!(recipient.uei || recipient.tin || recipient.name)) {
throw new Error('recipient row must include a `uei`, `tin`, or `name` field');
}
Copy link
Member

Choose a reason for hiding this comment

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

I like how the createRecipient() function's various if ... throw blocks make the business logic clear to developers and provide friendly error messages for users; I'm wondering if we could preserve that clarity while making better use of the db-level constraints by checking the cause of a failure after inserting rather than checking ahead of time (which can fail to identify the problem ahead of time in a double-click situation or similar race condition).

I think something like this might work:

async function createRecipient(recipient, trns = knex) {
    const tenantId = useTenantId();
    try {
        return await trns('arpa_subrecipients')
            .insert({ ...recipient, tenant_id: tenantId })
            .returning('*')
            .then((rows) => rows[0]);
    } catch (e) {
        switch (e.constraint) {
            case 'chk_at_least_one_of_uei_tin_name_not_null':
                throw new Error('recipient row must include a `uei`, `tin`, or `name` field');
            case 'idx_arpa_subrecipients_tenant_id_uei_unique':
                throw new Error('A recipient with this UEI already exists');
            case 'idx_arpa_subrecipients_tenant_id_tin_unique':
                throw new Error('A recipient with this TIN already exists');
            // Other `case 'name_of_constraint_or_index'` statements go here...
            default:
                // Something else
                throw e;
        }
    }
}

(See my previous comment regarding the chk_at_least_one_of_uei_tin_name_not_null constraint.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is much cleaner since we don't need to duplicate the validation logic in both layers. Will update the code 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added in the latest commit

Copy link
Member

@TylerHendrickson TylerHendrickson left a comment

Choose a reason for hiding this comment

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

Looks good!

@as1729 as1729 merged commit e974db6 into main Sep 20, 2024
37 checks passed
@as1729 as1729 deleted the 3372-add-support-for-iaa-fields branch September 20, 2024 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
database-changes Includes schema migrations or other critical changes javascript Pull requests that update Javascript code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants