Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3428929
create case contact types reminder task
harsohailB May 26, 2022
a5a4835
add table to capture reminder sent time so reminders are only sent on…
harsohailB May 26, 2022
36d9e6e
write task tests for sms contact types reminder (wip)
harsohailB May 29, 2022
4912e00
complete casa contact types reminder testing
harsohailB May 29, 2022
213bf60
add test for when case contact was made within last 60 days 9sms shou…
harsohailB May 29, 2022
d3079ba
Merge branch 'main' into feature/1793
harsohailB May 29, 2022
19fc6ac
add stubs to webmock helper for linux architecture
harsohailB May 29, 2022
e8b658d
improve cognitive complexity of send function
harsohailB May 29, 2022
7beb5dd
remove excess header attributes
harsohailB May 29, 2022
0c59ac1
fix linting issues
harsohailB May 30, 2022
515b566
add short link of production url in third message
harsohailB Jun 6, 2022
f05a3d0
fix linting issues
harsohailB Jun 6, 2022
1332a7d
fix webmock stub
harsohailB Jun 6, 2022
90028c5
fix linting issues
harsohailB Jun 6, 2022
6abded8
add test to UserCaseContactTypesReminder spec
harsohailB Jun 7, 2022
e189f56
run case contact types reminder logic every week
harsohailB Jun 11, 2022
a961098
use google home page as test url for short io service
harsohailB Jun 11, 2022
5c1e058
fix twilio service spec url
harsohailB Jun 11, 2022
b7de75f
store short io env variables
harsohailB Jun 11, 2022
2fa7745
clean up short io service spec
harsohailB Jun 11, 2022
41de22c
disable creation of short url service for each test
harsohailB Jun 11, 2022
fa328db
fix twilio service spec as short url service no longer uses arguments
harsohailB Jun 12, 2022
fa5fbfe
use dynamic base url through environment variables
harsohailB Jun 12, 2022
0aa6b5a
fix linting issues
harsohailB Jun 12, 2022
840b09b
Merge branch 'main' into feature/1793
compwron Jun 13, 2022
ea62122
remove unneeded include
harsohailB Jun 13, 2022
012146d
Merge branch 'feature/1793' of https://github.com/harsohailB/casa int…
harsohailB Jun 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/models/user_case_contact_types_reminder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class UserCaseContactTypesReminder < ApplicationRecord
belongs_to :user
end

# == Schema Information
#
# Table name: user_case_contact_types_reminders
#
# id :bigint not null, primary key
# reminder_sent :datetime
# created_at :datetime not null
# updated_at :datetime not null
# user_id :bigint not null
#
# Indexes
#
# index_user_case_contact_types_reminders_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (user_id => users.id)
#
18 changes: 15 additions & 3 deletions app/services/short_url_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ class ShortUrlService
headers RequestHeader::ACCEPT_JSON
headers RequestHeader::CONTENT_TYPE_JSON

def initialize(short_domain = nil, api_key = nil)
@short_domain = short_domain
@short_api_key = api_key
def initialize
validate_credentials
@short_domain = Rails.application.credentials[:SHORT_IO_DOMAIN]
@short_api_key = Rails.application.credentials[:SHORT_IO_API_KEY]
@short_url = nil
end

Expand All @@ -23,4 +24,15 @@ def create_short_url(original_url = nil)
@short_url = JSON.parse(response.body)["shortURL"]
response
end

private

def validate_credentials
variables = [Rails.application.credentials[:SHORT_IO_DOMAIN], Rails.application.credentials[:SHORT_IO_API_KEY]]
variables.each do |var|
if var.blank?
raise "#{var} environment variable missing for Short IO serivce"
end
end
end
end
2 changes: 1 addition & 1 deletion config/credentials/development.yml.enc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1Oa+HbXh12bAr8uQOAThcsDm9IZ+6QkW7XDQyKEehHicQ6r0Ujf6teoeEsoJeHqnbZCCuO1nTzlQxsFoGKTCm4pKzbMmuueq1lRAzADomODCzzJ5OPTGy0lZc9LD61PLp8YmKufNPIfoJTX1b6OhKgArVR320mH3D2HBKZ677cZvoFPcbPQujnbhlURXaZFtTi8gjOmFvgqNa+EMOz1voUUWDyFeImzwjC6fGe2/KEQQX/5e1P0wyqgjET4S--MdZnjuArDnv8aXVt--hrZS+Llnb4VJoqmuCS06og==
svCtLWmi6TUWfy4jhsNxZgGKdzBrjq5JjKkGUaDA5tlP2XFn6XY8lJDVhF+T82kGjwT4EgsBheMZqPMbytlJ6iSDBIq/bHfjl1E5Zx3DqCkd4gDYgVK0roJffesKQPuWUSQUzvJV9pZ9VQEKbh+YA/I/N6aWGbkYlKXTOPHMY7F+rfiKXb8vHodUGWxCTycsWLpe/ohBvF7zzSwxkG7sEmbnRnqYd2Tmn0ASf6vNKXOzPamQ21rrgUss427/zjCjzWHCk4iUaHnhQQYwC2zJ+m1/0Uu+sM5CkYJhddsPbeeQkd7vgPjHBylgkT6L86XTz8sBrQDZB51TbmNouygu96NzQwE472c0csFEWwjz7fepy7sZkHN5KqQ=--dx6D/QqFOeacGYGg--+r3ffqcg8wONL9oMId9u5g==
2 changes: 1 addition & 1 deletion config/credentials/production.yml.enc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
AxlCeUg6T11ONc/6YWPkWk+sJi5dMmJ0KhtVbxxZb+4s/CUFi5F3dLER1vqmyu9Zt+IDrwojE2gvm+Yw3ympbeg5km40o88QJRz76FZoRrvOPRsT76iYW9Fy2qXCUJyd04HNIbW2P2onAY4OtwVCQYf65EtazDh1B5BFJneEa/cIHs/QKFAqE9hxPA05Uvzc+O1F8xmg3v7Dp0cLvRl2JpSRTr0JDBktcWRsS4g+pnQwC2la22SVgg2RYgzerEU6BJKAs0wlf0VuytXdKNw92rfCl11pAZyh3/3Ocv2mY78sgpK3FOObXfpnPHAoNI1pLL1sJveM34L1s6tDAUSQ1rAe7fQO1qDtkooZ+Ak/IyHUUTvl62C5zFafnBmjrAUvHSr11l/LrSjkWlsV9KpFXBlKQYs4rJAsDPPr3AJ4YnmyZyxgrQYrB3jNRB1IENRD87t6pg6geFlbPbnTmmbMl2oW2HwllStgDFkOUgcGRBXgdDVv1YgR9aAIP2jZBhLD1jt3a+XxqsrJBRvdGMa3rtTGj4s8+FrVG0iLibvd/hoVlMldVEj6HTQYI2lIzTQ4cI/chw==--IinvJwfP8oNrT+d2--V47XslB/SUn6I78Fb4MUYg==
Y85/mpXDwuasGg7odbPklWamaevc8nlJJExz5YTnGBR/S5wCGTkkH7hwUBPINNTkYXpe3N1QN2ILq+NuGqTJhgtpy6Glwnk743UlxsFUChUQmSlccJfML1YQWKx3R1VSAFWjOoHiV9vnA0RcKf9tIQ5FWY5S8/IMn4SlpRbhU0zJfkHAb3Hbx5juTWmOWHh4BD+AmKYqu9lpVCCwQkitRJX/8AwLUBprbcaHm0nh9u4PL/Q/Fj0/UadHF2RCWDnpRxUAygqgMpCBxqZwpHzN3cRrATL3aDYFZt0CZzpkpnHBKQcuSs1K/oki06Jas+Z585iO00S6r4NRJtkPVkznFqu0DJ8JkUKriLDXOwbvcEHOHtCS0t2hCxYLPZvSg51qBsZbgS5No3+cfXPeGjIecq+h1c7zpg70PNieO6290IM/jpNYA9ZEt9nPXwUodAauMkvcocXlOxX4mOaYsL7fMiXhgu/JzqwK64maSXpCBaEEPcOwIkaKhTwn+w+iABnTYnhZeD2t3TMoFpzkI83w7cg9Mbetm1N9faX43dsYXRW93tFwSkJtF4b//CIxOyG9xFxruGNwnBqy8+IuoaSyBVF3WecnWjj+7TGSgmgl7FmiaUSjDDJkh27K8QDHbpevQM/Rc5U=--8GtVk0JHu3Dkz4jI--/FAbx4kRZsAKWLg9/+IWbw==
2 changes: 1 addition & 1 deletion config/credentials/test.yml.enc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
lsnR22as6mTVDa2pPJo+EjZ+OYq92HViKKhnKj1GwMWspKIJTkndzJ3xJYVkGJbrY2tAmHr+VXNauNaDukCsovBhHry9TN2Ngr4SzCU2x+hpzMgqBHt4mdIrAZWVTr+paA1hr/rS8k7ZGnK/uUx7ifboyMwHCX2tVV+m7ZHkBciHIFaZs2rwJDB4fsI9tKk/9DkXOmELV3VuRmw6eUnFyW84gjj2vQ18mczrNx6Sbq6zakhassqcXTgJRgey--KkLl5vLri+smsHTB--By5/dkptNeOUBKDJyAfRYA==
zsvjeQzFV0vM7h3jsx7RUyA3WTQsEYWvEMreEvVbi4L0HN7sDNrP7kay2FZS5VEwrW5mJZdnu63BXa8fK/h1agvaaiOO9FhDXfyK+VT86TAfsLa1gsBK9mHjWSdXCJE9TZj9OjOtR3R/qHOI+2uPUnysh7Om4a0ckiu4Jwex3OcbgCYj2+G2JQtwHkWhlyBthGxLjuDDFfx+qxkJWkN7V9FhN0FkkPaflyj4FjR9BUf3/CB8pvHXqJ1lmxVScYsyhh50mc+CKyVptpqbLi9Jou3SiUePREX03ynV0KPR+7mT3FH9gCj4QyzzS1t3JOUfrgqeVFAzdV1TW01olinOyG2aMrZn1aA7GWfDeIr/GwnaPfUMmZNj4RQ=--KmPWCR7xHr6jUSx5--1L2S0bUzD2Bc+JFAHX7xKg==
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class CreateUserCaseContactTypesReminders < ActiveRecord::Migration[7.0]
def change
create_table :user_case_contact_types_reminders do |t|
t.belongs_to :user, null: false, foreign_key: true
t.datetime :reminder_sent

t.timestamps
end
end
end
9 changes: 9 additions & 0 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,14 @@
t.string "version", null: false
end

create_table "user_case_contact_types_reminders", force: :cascade do |t|
t.bigint "user_id", null: false
t.datetime "reminder_sent"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_user_case_contact_types_reminders_on_user_id"
end

create_table "user_sms_notification_events", force: :cascade do |t|
t.bigint "user_id", null: false
t.bigint "sms_notification_event_id", null: false
Expand Down Expand Up @@ -484,6 +492,7 @@
add_foreign_key "sent_emails", "users"
add_foreign_key "supervisor_volunteers", "users", column: "supervisor_id"
add_foreign_key "supervisor_volunteers", "users", column: "volunteer_id"
add_foreign_key "user_case_contact_types_reminders", "users"
add_foreign_key "user_sms_notification_events", "sms_notification_events"
add_foreign_key "user_sms_notification_events", "users"
add_foreign_key "users", "casa_orgs"
Expand Down
97 changes: 97 additions & 0 deletions lib/tasks/case_contact_types_reminder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
class CaseContactTypesReminder
NEW_CASE_CONTACT_PAGE_PATH = Rails.application.credentials[:BASE_URL]
FIRST_MESSAGE = "It's been 60 days or more since you've reached out to these members of your youth's network:\n"
THIRD_MESSAGE = "If you have made contact with them in the past 60 days, remember to log it: "

def send!
if NEW_CASE_CONTACT_PAGE_PATH.blank?
raise "NEW_CASE_CONTACT_PAGE_PATH environment variable not defined"
end
responses = []
eligible_volunteers = Volunteer.where(receive_sms_notifications: true)
.where.not(phone_number: nil)
.select { |v| !last_reminder_within_quarter(v) }

eligible_volunteers.each do |volunteer|
uncontacted_case_contact_type_names = uncontacted_case_contact_types(volunteer)
if uncontacted_case_contact_type_names.count > 0
responses.push(
{
volunteer: volunteer,
messages: send_sms_messages(volunteer, uncontacted_case_contact_type_names)
}
)
update_reminder_sent_time(volunteer)
end
end

responses
end

private

def uncontacted_case_contact_types(volunteer)
contacted_types = volunteer.case_contacts.where("occurred_at > ?", 2.months.ago).joins(:contact_types).pluck(:name)
ContactType.all.pluck(:name).uniq - contacted_types
end

def send_sms_messages(volunteer, uncontacted_case_contact_type_names)
volunteer_casa_org = volunteer.casa_org
if !valid_casa_twilio_creds(volunteer_casa_org)
return
end

twilio_service = TwilioService.new(volunteer_casa_org.twilio_api_key_sid, volunteer_casa_org.twilio_api_key_secret, volunteer_casa_org.twilio_account_sid)
Comment thread
harsohailB marked this conversation as resolved.
sms_params = {
From: volunteer_casa_org.twilio_phone_number,
Body: nil,
To: volunteer.phone_number
}

messages = [
FIRST_MESSAGE,
uncontacted_case_contact_type_names.map { |name| "• #{name}" }.join("\n"),
THIRD_MESSAGE + new_case_contact_page_short_link
]

responses = []
messages.each do |content|
sms_params[:Body] = content
responses.push(twilio_service.send_sms(sms_params))
end

responses
end

def valid_casa_twilio_creds(casa_org)
casa_org.twilio_phone_number? && casa_org.twilio_account_sid? && casa_org.twilio_api_key_sid? && casa_org.twilio_api_key_secret?
end

def last_reminder_within_quarter(volunteer)
reminder = UserCaseContactTypesReminder.find_by(user_id: volunteer.id)

if reminder
return reminder.reminder_sent > 3.months.ago
end

false
end

def update_reminder_sent_time(volunteer)
reminder = UserCaseContactTypesReminder.find_by(user_id: volunteer.id)

if reminder
reminder.reminder_sent = DateTime.now
else
reminder = UserCaseContactTypesReminder.new(user_id: volunteer.id, reminder_sent: DateTime.now)
end

reminder.save
end

def new_case_contact_page_short_link
short_url_service = ShortUrlService.new
short_url_service.create_short_url(NEW_CASE_CONTACT_PAGE_PATH + "/case_contacts/new")
short_url_service.short_url
end
end
7 changes: 7 additions & 0 deletions lib/tasks/send_case_contact_types_reminder.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
desc "Send an SMS to volunteers reminding them to connect with the contact types they have not connected with in the past 60 or more days"
require_relative "./case_contact_types_reminder"
task send_case_contact_types_reminder: :environment do
every 1.weeks do
CaseContactTypesReminder.new.send!
end
end
6 changes: 6 additions & 0 deletions spec/factories/user_case_contact_types_reminders.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FactoryBot.define do
factory :user_case_contact_types_reminder do
user { create(:user) }
reminder_sent { DateTime.now }
end
end
95 changes: 95 additions & 0 deletions spec/lib/tasks/case_contact_types_reminder_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
require "rails_helper"
require_relative "../../../lib/tasks/case_contact_types_reminder"
require "support/webmock_helper"

RSpec.describe CaseContactTypesReminder do
Copy link
Copy Markdown
Collaborator

@xihai01 xihai01 Jun 12, 2022

Choose a reason for hiding this comment

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

nit: i think the tests here can be organized better to avoid too much duplication. To me, I feel the contexts are being crammed with too many cases.

let!(:casa_org) do
create(
:casa_org,
twilio_phone_number: "+15555555555",
twilio_account_sid: "articuno34",
twilio_api_key_sid: "Aladdin",
twilio_api_key_secret: "open sesame"
)
end
let!(:volunteer) do
create(
:volunteer,
casa_org_id: casa_org.id,
phone_number: "+12222222222",
receive_sms_notifications: true
)
end
let!(:contact_type) { create(:contact_type, name: "test") }
let!(:case_contact) do
create(
:case_contact,
creator: volunteer,
contact_types: [contact_type],
occurred_at: 4.months.ago
)
end

before do
stubbed_requests
WebMock.disable_net_connect!
end

context "volunteer with uncontacted contact types, sms notifications on, and no reminder in last quarter" do
it "should send sms reminder" do
responses = CaseContactTypesReminder.new.send!
expect(responses.count).to match 1
expect(responses[0][:messages][0].body).to match CaseContactTypesReminder::FIRST_MESSAGE
expect(responses[0][:messages][1].body).to match contact_type.name
expect(responses[0][:messages][2].body).to match CaseContactTypesReminder::THIRD_MESSAGE + "https://42ni.short.gy/jzTwdF"
end
end

context "volunteer with contacted contact types within last 60 days, sms notifications on, and no reminder in last quarter" do
it "should send not sms reminder" do
CaseContact.update_all(occurred_at: 1.months.ago)
responses = CaseContactTypesReminder.new.send!
expect(responses.count).to match 0
end
end

context "volunteer with uncontacted contact types, sms notifications off, and no reminder in last quarter" do
it "should not send sms reminder" do
Volunteer.update_all(receive_sms_notifications: false)
responses = CaseContactTypesReminder.new.send!
expect(responses.count).to match 0
end
end

context "volunteer with uncontacted contact types, sms notifications on, and reminder in last quarter" do
it "should not send sms reminder" do
create(:user_case_contact_types_reminder, user_id: volunteer.id)
Volunteer.update_all(receive_sms_notifications: true)
responses = CaseContactTypesReminder.new.send!
expect(responses.count).to match 0
end
end

context "volunteer with uncontacted contact types, sms notifications on, and reminder out of last quarter" do
it "should send sms reminder" do
UserCaseContactTypesReminder.destroy_all
Volunteer.all do |v|
create(:user_case_contact_types_reminder, user_id: v.id, reminder_sent: 4.months.ago)
end
responses = CaseContactTypesReminder.new.send!
expect(responses.count).to match 1
expect(responses[0][:messages][0].body).to match CaseContactTypesReminder::FIRST_MESSAGE
expect(responses[0][:messages][1].body).to match contact_type.name
expect(responses[0][:messages][2].body).to match CaseContactTypesReminder::THIRD_MESSAGE + "https://42ni.short.gy/jzTwdF"
end
end

context "volunteer with uncontacted contact types, sms notifications on, no reminder in last quarter, no phone number" do
it "should not send sms reminder" do
UserCaseContactTypesReminder.destroy_all
Volunteer.update_all(phone_number: nil)
responses = CaseContactTypesReminder.new.send!
expect(responses.count).to match 0
end
end
end
5 changes: 5 additions & 0 deletions spec/models/user_case_contact_types_reminder_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require "rails_helper"

RSpec.describe UserCaseContactTypesReminder, type: :model do
it { is_expected.to belong_to(:user) }
end
15 changes: 8 additions & 7 deletions spec/services/short_url_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,28 @@
require "support/webmock_helper"

RSpec.describe ShortUrlService do
let!(:original_url) { "https://www.google.com/" }
let!(:notification_object) { ShortUrlService.new }
let!(:short_io_domain) { Rails.application.credentials[:SHORT_IO_DOMAIN] }

describe "short.io API" do
before :each do
stubbed_requests
WebMock.disable_net_connect!
@original_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
@short_domain = "42ni.short.gy"
@notification_object = ShortUrlService.new(@short_domain, "1337")
end

it "returns a successful response with correct http request" do
response = @notification_object.create_short_url(@original_url)
response = notification_object.create_short_url(original_url)
expect(a_request(:post, "https://api.short.io/links")
.with(body: {originalURL: @original_url, domain: @short_domain}.to_json, headers: {"Accept" => "application/json", "Content-Type" => "application/json", "Authorization" => "1337"}))
.with(body: {originalURL: original_url, domain: short_io_domain}.to_json, headers: {"Accept" => "application/json", "Content-Type" => "application/json", "Authorization" => "1337"}))
.to have_been_made.once
expect(response.code).to match 200
expect(response.body).to match "{\"shortURL\":\"https://42ni.short.gy/jzTwdF\"}"
end

it "returns a short url" do
@notification_object.create_short_url(@original_url)
short_url = @notification_object.short_url
notification_object.create_short_url(original_url)
short_url = notification_object.short_url
expect(short_url).to be_an_instance_of(String)
expect(short_url).to match "https://42ni.short.gy/jzTwdF"
end
Expand Down
6 changes: 3 additions & 3 deletions spec/services/twilio_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
RSpec.describe TwilioService do
describe "twilio API" do
context "SMS messaging" do
before :each do
before :all do
stubbed_requests
WebMock.disable_net_connect!
@acc_sid = "articuno34"
@api_key = "Aladdin"
@api_secret = "open sesame"
@short_url = ShortUrlService.new("42ni.short.gy", "1337")
@short_url = ShortUrlService.new
@twilio = TwilioService.new(@api_key, @api_secret, @acc_sid)
end

it "can send a SMS with a short url successfully" do
@short_url.create_short_url("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
@short_url.create_short_url("https://www.google.com/")
params = {
From: "+15555555555",
Body: "Execute Order 66 - ",
Expand Down
Loading