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

Feature: Grant followers modal #3505

Merged
merged 16 commits into from
Sep 19, 2024
Merged

Feature: Grant followers modal #3505

merged 16 commits into from
Sep 19, 2024

Conversation

greg-adams
Copy link
Contributor

Ticket #3407

Description

  • Adds modal for displaying grant followers
  • Updates pagination for grant followers to allow for sorting by specific fields (created_at vs user name).

Screenshots / Demo Video

Screenshot from 2024-09-13 14-14-05

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 enhancement New feature or request javascript Pull requests that update Javascript code labels Sep 13, 2024
Copy link

github-actions bot commented Sep 13, 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.4 52.34 54.85 59.5
🟢  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 38.58 32.92 44.44 40.16
🔴   arpa-subrecipients.js 13.15 4.34 15.38 14.28 23-92
🔴   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.18 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 57.21 37.03 44.2 ...48,367,389,407-684,699
🟢   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 25 0 0 25 12-62
🟢  src/lib 86.24 79.44 91.42 86.2 ...[Comment body truncated]

Copy link

github-actions bot commented Sep 13, 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/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-DRPQpa6U.css"]
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/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/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/main-51yKd0t5.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/main-51yKd0t5.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["assets/style-DF53RXBe.js.map"]
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/DashboardView-7j8begu3.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/DashboardView-7j8begu3.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantDetailsLegacy-CNVectiI.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantDetailsLegacy-CNVectiI.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantDetailsView-CYNdw3yj.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantDetailsView-CYNdw3yj.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantDetailsView-fBQcmnr8.css"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantsTable-q9SeQenv.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantsTable-q9SeQenv.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantsView-zrZ-w-DQ.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/GrantsView-zrZ-w-DQ.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/MyGrantsView-BlpqpJ0y.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/MyGrantsView-BlpqpJ0y.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/MyProfileView-Cd64CDLN.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/MyProfileView-Cd64CDLN.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/OrganizationsView-BGfz5sDb.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/OrganizationsView-BGfz5sDb.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/RecentActivityView-BilvVJNP.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/RecentActivityView-BilvVJNP.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/TeamsView-DHti4yIP.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/TeamsView-DHti4yIP.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/UsersView-C8_3WRv3.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/UsersView-C8_3WRv3.js.map"]
module.website.aws_s3_object.origin_dist_artifact["assets/main-DyHHwK_f.js"]
module.website.aws_s3_object.origin_dist_artifact["assets/main-DyHHwK_f.js.map"]

Pusher: @greg-adams, 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.

@greg-adams I have two visual formatting suggestions (see comments in GrantFollowers.vue) for you to consider.

A bit more broadly, I don't think we need the changes to pagination behavior that this PR introduces. For clarification:

  • The intended ordering behavior for the modal's list is most-recently-followed first (latest follower at the top, first follower at the bottom), not alphabetical.
  • I'd like to generally move the API towards cursor pagination rather than traditional offset-based pagination.

(As always, happy to chat about this further if you have questions or think I missed something.)


Additional context to elaborate on the preference for cursor-based pagination, in case you're interested – feel free to disregard if not :) ...

With cursor-based pagination, the database returns a cursor (or "checkpoint") value pointing to the final item in the result set, which is then used as an "exclusive start" filter on a subsequent query. This is in contrast to offset-based pagination, where the query specifies a certain number of results to skip, based on a running total result count. As long as clients treat the cursor value (pagination.from in our case) as an arbitrary string, it allows us to more easily implement a different checkpoint mechanism for a particular retrieval scenario (e.g. maybe we return from: grant_followers.created_at instead of from: grant_followers.id).

One advantage of cursor pagination is that it's more accurate – offset-based pagination has a tendency to either skip or return duplicate items when the overall result set is being modified between queries. For example, in a scenario where an array of Alice, Bob, Charlie, Elise, Frank, George is being paged 1 result at a time, if the query returns Frank and then Dan is inserted between Charlie and Elise, an offset-based paginated request would return Frank a second time. A cursor-based paginated request would ensure that George (or another value alphabetically greater than Frank) always comes next.

Another advantage is that they tend to be more compatible with non-SQL backends, so it offers a certain amount of future-proofing in case we decide to move to an ordered result cache or other kind of precomputed index.

Note: Although we don't currently have a use-case for ordering followers alphabetically, it is possible to implement cursor-based pagination in a way that supports multiple ordering schemes – the paginate.from value would just need to be set to the ORDER BY column value of the last item in the page. We would also need to support an afterName parameter in getFollowersForGrant(), or else refactor to a more generic startAfter parameter.

packages/client/src/components/Modals/GrantFollowers.vue Outdated Show resolved Hide resolved
packages/client/src/components/Modals/GrantFollowers.vue Outdated Show resolved Hide resolved
@greg-adams
Copy link
Contributor Author

greg-adams commented Sep 18, 2024

Ok gotcha, this makes sense, thanks for the background. The story AC requests sorting alphabetically by name, if we don't need this change I can return to ordering by creation. While making this change I added support on the response to identify a final result set (next) to avoid the user clicking "Show More" an extra time at the end, do you have a preference for supporting this in cursor-based approach? e.g. paginate.before

@TylerHendrickson
Copy link
Member

@greg-adams

The story AC requests sorting alphabetically by name, if we don't need this change I can return to ordering by creation.

Yeah, I just noticed that; we'd discussed in the comments of the parent issue (#2960) and decided to go with with most-recent-first instead, but it seems the DoD on #3407 was never updated to reflect that decision 🤦 .

While making this change I added support on the response to identify a final result set (next) to avoid the user clicking "Show More" an extra time at the end, do you have a preference for supporting this in cursor-based approach? e.g. paginate.before

I think the look-ahead behavior that you implemented in the query is exactly right! Since cursor pagination is typically based on an exclusive filter, I think the way we would incorporate that as a conditional instruction for cursor-based pagination would look something like this:

        pagination: {
            next: grantFollowersResult.length > limit ? grantFollowersResult[limit - 1].id : null,
        },

Essentially, we're instructing the client that

  1. If pagination.next is not null, then there are more results.
  2. To continue paginating (in the same direction) from where the final result left off, provide the value of the final result from this current page, which will then be used as an exclusive filter on the next request.

Note that the above is pretty implementation-specific, as it relies on the concept that a higher grant_followers.id value implies a higher grant_followers.created_at value as well. If we were paginating by user name if alphabetical order, this wouldn't work. In fact, it could be argued that the more "correct" way to provide a cursor for follower recency would be to provide pagination.next as a created_at timestamp (I chose to go with id in the original spec because it avoids an edge case stemming from more than one row with the same created_at value). But that shouldn't really matter as long as the client is treating the cursor as an arbitrary value.

A more agnostic way to implement cursors that support multiple ordering schemes could be to provide the value of grantFollowersResult[orderBy] as the pagination.next cursor, but I think that gets a little dicey/brittle without having a clearer sense of what scenarios we'd need to support.

@greg-adams
Copy link
Contributor Author

No worries - I've updated to maintain use of created_at, keeping the next property to indicate an additional set

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.

@greg-adams Looks great!

@TylerHendrickson TylerHendrickson added the Grant Finder Issues related to the Grant Finder label Sep 19, 2024
@greg-adams greg-adams merged commit 647dacf into main Sep 19, 2024
19 checks passed
@greg-adams greg-adams deleted the feat/grant-followers-modal branch September 19, 2024 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Grant Finder Issues related to the Grant Finder javascript Pull requests that update Javascript code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants