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

[STORY]: Create new email notifications for 'Follow + Note' #2962

Open
4 of 12 tasks
ekhoffman opened this issue Apr 22, 2024 · 6 comments
Open
4 of 12 tasks

[STORY]: Create new email notifications for 'Follow + Note' #2962

ekhoffman opened this issue Apr 22, 2024 · 6 comments
Labels
collaboration Grant Finder Issues related to the Grant Finder story

Comments

@ekhoffman
Copy link

ekhoffman commented Apr 22, 2024

Full User Story

As an organization, we would like to foster more collaboration between our users to allow for the sharing of knowledge and resources to empower them to more efficiently and effectively apply for the grants discovered on our platform.

As a grantseeker, I want to be able to easily share grants I’ve discovered with my team.

As a grantseeker, I want to be able to easily find collaborators who may be able to help me when preparing and applying for grants.

As a grantseeker, I want to be able to quickly see what grants my team members are interested in.

As an admin, I want to be able to easily surface and resolve issues and blockers my team is running into.

Why is this issue important?

In order to help build the collaborative spirit we're going for, we need to have a prompt to pull people back to the platform when people they may be interested in supporting or receiving support from mark interest in the same grant. This will help bring users back to the platform in a timely manner to collaborate on previously marked grants.

Action Plan

  • Initial design drafted
  • Initial acceptance criteria drafted
  • High level technical approach & feature flag use determined
  • Designs & Acceptance criteria finalized
  • Eng task breakout complete
  • Assessment of whether partner training & communication actions are needed
  • Acceptance criteria testing completed & passed in staging
  • Feature flags enabled in production
  • Feature flags are removed once the feature is deemed stable

Engineering Tasks

Acceptance Criteria

Designs

Trigger
A daily digest of follow & notes activity will send each morning to a user who:

  • has their Grant Activity notification set to "on" AND at the time of the check, if a new user in the organization follows a grant and/or a new note is added to a grant, and/or an existing note is edited for any grants that the user follows (i.e. since the previous day's check for activity).
  • if the user does not follow any grants or has no activity in the past 24 hours (since the last check) for any grant they follow, they will not receive an email
  • the user receiving the email must be following the grant at the time of the check (i.e. if I followed a grant at 10am today, but unfollowed it at 2pm today, then tomorrow morning I would not receive a grant activity notification email)
  • The person who followed the grant should not receive the email if the only activity is their own follow action and/or their own note creation/edit

General email specifications
Create new email notification for 'Grant Activity'
- From: USDR Federal Grant Finder ([email protected])
- Email Subject: New activity in your grants
- Preview Text: See recent activity from grants you follow

General Email Content
- Header should match Grants Digest header (i.e. USDR Logo with "Federal Grant Finder" underneath)
- Email Title: New activity in your grants
- Subtitle: Today's date will display under the title
- Footer: Should display the text "Change notification preferences" - this text should link to the /my-profile page of grant finder
- Body: - Each grant with activity should be displayed in its own box. There is no maximum of grants that should display. We will want to ensure that we are able to monitor frequency of the email size exceeding what can load (i.e. so we can understand how frequently users will not be able to see full content due to the email being too long)

Content within each grant box
- The grey header of the box should display the name of the grant - this should truncate after two lines with an ellipses at the end (see third grant in design as an example of truncation). The name of the grant should also link to the relevant grant details page in Grant Finder.
- Below the grant title, new note activity should display first if there is any. If there are no new or edited notes, then no note section should display. If there are no new followers, then no follow section should display. If neither new/edited notes or followers exist, the grant should not appear in the email digest.
- At the bottom of the grants box, the "View Grant" button should display and link to grant details page

Notes section:

  • The Notes section should begin with: "[#] NEW NOTES" with the total count of notes that are new or have been edited since the previous day's check. If a note was deleted in this time, it will not be included.
  • The list of notes should display as a bulleted list
  • Note line 1: [Name of user who left the note] • [User's team name]
  • Note line 2: [email address of user who left note]
  • Note line 3: Display full text of the note. This should wrap to additional lines as needed and should not truncate.
  • A maximum of two notes should display. If there are more than two new notes, then "+ See all notes" should display as text that links to the grant details page

Followers section

  • The Followers section should begin with "[#] NEW FOLLOWERS"
  • The list of followers should display as a bulleted list
  • Follower line 1: [Name of user who followed] • [User's team name]
  • Follower line 2: [email address of user who followed]
  • A maximum of one follower should display. If there are more than two new notes, then "+ See all notes" should display as text that links to the grant details page

My Profile

  1. Add new entry to Email Notifications.
    • Name: Grant Activity
    • Description: Send me a daily summary of new activity for grants that I follow.
    • Enabled/Disabled Toggle: By default, this will be Toggled ON for all users.
@samserif23
Copy link

New Collaborator Email -- Complex Layout
New Collaborator Email -- Simple Layout

@samserif23
Copy link

Two leading designs thus far, one exploring pulling in the user avatar + comment visual component into the email

@samserif23
Copy link

Additional comment: View My Grants button links to interested tab

@ekhoffman ekhoffman changed the title [STORY]: Create new email notification for 'Mark as Interested' [STORY]: Create new email notifications for 'Follow + Note' May 20, 2024
@ClaireValdivia ClaireValdivia added the Grant Finder Issues related to the Grant Finder label May 31, 2024
@ClaireValdivia ClaireValdivia moved this from 🆕 New to 📋 Scoping in Grants Team Agile Planning May 31, 2024
@TylerHendrickson
Copy link
Member

TylerHendrickson commented Sep 20, 2024

Drafting some scratch for implementing these notes as a digest...

Data Retrieval

Querying for grant activity

The following query returns both new follows and note revision content for grants since a given :digestPeriodStart timestamp and up to a given :digestPeriodEnd timestamp. In this example, the rows are ordered by grant ID (to keep rows pertaining to the same grant together), then new follower activity (oldest first), then new note revision content (oldest first).

SELECT
    g.grant_id AS grant_id,
    g.title AS grant_title,
    u.id AS user_id,
    u.name AS user_name,
    u.email AS user_email,
    a.name AS agency_name,
    activity.activity_at,
    activity.activity_type,
    activity.text_content AS note_text
FROM (
    SELECT
        gf.id,
        gf.grant_id,
        gf.user_id,
        gf.created_at AS activity_at,
        'follow' AS activity_type,
        null AS text_content
    FROM grant_followers gf
    UNION ALL
    SELECT
        rev.id,
        gn.grant_id,
        gn.user_id,
        rev.created_at AS activity_at,
        'note' AS activity_type,
        rev.text AS text_content
    FROM grant_notes gn
    LEFT JOIN LATERAL (
        SELECT
            r.id,
            r.grant_note_id,
            r.created_at,
            r.text
        FROM grant_notes_revisions r
        WHERE r.grant_note_id = gn.id
        ORDER BY r.created_at DESC
        LIMIT 1
    ) AS rev ON rev.grant_note_id = gn.id
) activity
JOIN users u ON u.id = activity.user_id
JOIN agencies a ON a.id = u.agency_id
JOIN grants g on g.grant_id = activity.grant_id
WHERE 
    activity.activity_at > :digestPeriodStart
    AND activity.activity_at <= :digestPeriodEnd
ORDER BY
    g.grant_id DESC,
    -- Followers first, then notes (remove for grant activity in chronological order, regardless of type):
    array_position(array['follow', 'note'], activity.activity_type) ASC,
    activity.activity_at ASC;

Example result set:

grant_id grant_title user_id user_name user_email agency_name activity_at activity_type note_text
987654 This grant has a lot of activity 5 Jennifer Coleman [email protected] Climate Office 2024-09-19 09:24:07.531172+00 follow null
987654 This grant has a lot of activity 2 Ray Welch [email protected] Dept of Education 2024-09-19 15:20:37.545297+00 follow null
987654 This grant has a lot of activity 5 Jennifer Coleman [email protected] Climate Office 2024-09-19 09:25:53.746174+00 note First note of the day.
987654 This grant has a lot of activity 4 Ken Ingram [email protected] Public Works 2024-09-19 10:01:26.897312+00 note This is the second note.
987654 This grant has a lot of activity 3 Traci Tran [email protected] Climate Office 2024-09-19 14:21:53.306255+00 note Last note for today!
246813 A grant with a single new follower 4 Ken Ingram [email protected] Public Works 2024-09-19 10:45:29.726974+00 follow null
123456 Two new followers and one new note 3 Traci Tran [email protected] Climate Office 2024-09-19 15:50:42.655014+00 follow null
123456 Two new followers and one new note 5 Jennifer Coleman [email protected] Climate Office 2024-09-19 16:22:08.934724+00 follow null
123456 Two new followers and one new note 5 Jennifer Coleman [email protected] Climate Office 2024-09-19 16:22:09.468254+00 note This is the second grant I've followed today.

This query retrieves all new grant collaboration activity (new follows and new note revisions), across all organizations (tenants). However, it only represents user data about users who performed some activity (i.e. became a new follower and/or created or revised a note on a grant); it does not provide all users who are following a grant. In other words, the query in the previous section provides results that can be used to build the content of a digest email, but not to determine all users who should receive that digest. Although this query may be useful for analytical purposes, it must be adjusted in order to be made suitable for our email digest use-case.

Querying for digest recipients

In order to determine every recipient for a 24-hour digest, we need to locate every grant with at least one new follower and/or at least one new or revised note. Although the query in the previous section satisfies that need, we also need to determine which users are following those grants – all followers of a grant (not just new followers) will receive a digest email as long as some of the follower and/or activity pertains to users within the same organization.

This can be achieved by modifying the SELECT statement and adding JOIN expressions that establishes and then filters on a new relationship, grant_followers AS recipient_followers:

SELECT
    DISTINCT recipient_followers.user_id AS recipient_user_id
FROM (
    SELECT
        gf.id,
        gf.grant_id,
        gf.user_id,
        gf.created_at AS activity_at,
        'follow' AS activity_type,
        null AS text_content
    FROM grant_followers gf
    UNION ALL
    SELECT
        rev.id,
        gn.grant_id,
        gn.user_id,
        rev.created_at AS activity_at,
        'note' AS activity_type,
        rev.text AS text_content
    FROM grant_notes gn
    LEFT JOIN LATERAL (
        SELECT
            r.id,
            r.grant_note_id,
            r.created_at,
            r.text
        FROM grant_notes_revisions r
        WHERE r.grant_note_id = gn.id
        ORDER BY r.created_at DESC
        LIMIT 1
    ) AS rev ON rev.grant_note_id = gn.id
) activity
-- Limit activity to grants with (current) followers:
JOIN grant_followers recipient_followers ON recipient_followers.grant_id = activity.grant_id
-- Incorporate `users` table data for users responsible for the activity
JOIN users activity_users ON activity_users.id = activity.user_id
-- Incorporate `users` table data for recipient followers
JOIN users recipient_users ON recipient_users.id = recipient_followers.user_id
-- (No need to JOIN on `agencies` or `grants` tables because their data is only used for email bodies)
WHERE 
    activity.activity_at > :digestPeriodStart
    AND activity.activity_at <= :digestPeriodEnd
    -- Only consider actions taken by users in the same organization as the recipient:
    AND recipient_users.tenant_id = activity_users.tenant_id
    -- Exclude rows where the recipient user is the one taking the action, 
    --   to ensure that users only receive a digest if OTHER users took action:
    AND recipient_followers.user_id != activity.user_id;

The results of this query are a single column of unique recipient user IDs, which can be iterated over in a fan-out scenario so that individual digest emails can be constructed and sent in parallel.

Querying for a single recipient's email data

In this scenario, a single recipient user ID is a known value, having been determined as described in "Querying for digest recipients". Now we need to build the contents of the digest email that will be sent to this single recipient.

Once again, our query is primarily concerned with grant activity that occurred within a start/end timestamp threshold. This time however, we need to select all the values that will be used in the body of the email, as well as filter results in the following ways:

  • Only include information about an activity where the grant associated with the activity is followed by the recipient user.
  • Only include information about an activity where the user associated with the activity belongs to the same organization (i.e. same users.tenant_id value) as the recipient user.
SELECT
    g.grant_id AS grant_id,
    g.title AS grant_title,
    activity_users.id AS user_id,
    activity_users.name AS user_name,
    activity_users.email AS user_email,
    activity_users_agencies.name AS agency_name,
    activity.activity_at,
    activity.activity_type,
    activity.text_content AS note_text
FROM (
    SELECT
        gf.id,
        gf.grant_id,
        gf.user_id,
        gf.created_at AS activity_at,
        'follow' AS activity_type,
        null AS text_content
    FROM grant_followers gf
    UNION ALL
    SELECT
        rev.id,
        gn.grant_id,
        gn.user_id,
        rev.created_at AS activity_at,
        'note' AS activity_type,
        rev.text AS text_content
    FROM grant_notes gn
    LEFT JOIN LATERAL (
        SELECT
            r.id,
            r.grant_note_id,
            r.created_at,
            r.text
        FROM grant_notes_revisions r
        WHERE r.grant_note_id = gn.id
        ORDER BY r.created_at DESC
        LIMIT 1
    ) AS rev ON rev.grant_note_id = gn.id
) activity
-- Limit activity to grants for which the recipient user is a follower (note the additional condition):
JOIN grant_followers recipient_followers ON recipient_followers.grant_id = activity.grant_id
    -- (This could alternatively go in the WHERE clause:)
    AND recipient_followers.user_id = :recipientUserId
-- Incorporate `users` table data for users responsible for the activity:
JOIN users activity_users ON activity_users.id = activity.user_id
-- Incorporate `users` table data for the recipient follower:
JOIN users recipient_users ON recipient_users.id = recipient_followers.user_id
-- Additional JOINs for data selected for use in the email's content:
JOIN grants g on g.grant_id = activity.grant_id
JOIN agencies activity_users_agencies ON activity_users_agencies.id = activity_users.agency_id
WHERE 
    activity.activity_at > :digestPeriodStart
    AND activity.activity_at <= :digestPeriodEnd
    -- Limit to activity where the user performing the activity belongs to the same organization:
    AND activity_users.tenant_id = recipient_users.tenant_id
ORDER BY
    -- Somewhat arbitrary, but ensures rows with the same `grant_id` occur consecutively:
    g.grant_id DESC,
    -- Followers first, then notes (remove for grant activity in chronological order, regardless of type):
    array_position(array['follow', 'note'], activity.activity_type) ASC,
    -- Activity of the same type is ordered oldest to most-recent:
    activity.activity_at ASC;

The ORDER BY criteria allows for the result set to be iterated over in a manner that can be used to progressively construct the contents of a digest email. Since rows with the same grant ID appear consecutively (and within those, rows representing the same activity type appear consecutively, ordered by oldest-first), the body of an iterating loop can watch for changing grant_id and activity_type values to determine when to construct a new digest section for a grant and a new sub-section pertaining to activity type within that grant.

@greg-adams
Copy link
Contributor

@ClaireValdivia Just want to confirm whether or not the email digest a user receives will contain their own follow/note activity (assuming it is not the only activity for that day)

@ClaireValdivia
Copy link
Contributor

@greg-adams no, the user should not see their own follow/note activity in the digest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
collaboration Grant Finder Issues related to the Grant Finder story
Projects
Status: 🔖 Ready
Development

No branches or pull requests

5 participants