Merged session reminder feature branch.

Squashed commit of the following:

commit 84293ed637
Author: Steven Miers <steven.miers@gmail.com>
Date:   Thu Jul 9 17:52:26 2015 -0500

    VRFS-3300 : Reminder notification functionality -- sends in-app/1-day/upcoming emails as necessary.

commit 521b0f4ba7
Author: Steven Miers <steven.miers@gmail.com>
Date:   Thu Jul 9 17:50:33 2015 -0500

    VRFS-3300 : Reminder mails on user mailer.   Also add tests to spec.

commit ed487e11a3
Author: Steven Miers <steven.miers@gmail.com>
Date:   Thu Jul 9 17:48:04 2015 -0500

    VRFS-3300 : Music session reminder job and spec

commit e5c7f50cd2
Author: Steven Miers <steven.miers@gmail.com>
Date:   Thu Jul 9 17:46:55 2015 -0500

    VRFS-3300 : Reminder cron setting
This commit is contained in:
Steven Miers 2015-07-10 10:51:12 -05:00
parent 614cfcbe85
commit 2761ea8ed7
15 changed files with 383 additions and 93 deletions

View File

@ -54,6 +54,7 @@ require "jam_ruby/resque/scheduled/user_progress_emailer"
require "jam_ruby/resque/scheduled/daily_job"
require "jam_ruby/resque/scheduled/daily_session_emailer"
require "jam_ruby/resque/scheduled/new_musician_emailer"
require "jam_ruby/resque/scheduled/music_session_reminder"
require "jam_ruby/resque/scheduled/music_session_scheduler"
require "jam_ruby/resque/scheduled/active_music_session_cleaner"
require "jam_ruby/resque/scheduled/score_history_sweeper"

View File

@ -182,7 +182,7 @@
email = user.email
subject = "Your band has a new follower on JamKazam"
unique_args = {:type => "new_band_follower"}
@body = msg
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
@ -390,13 +390,23 @@
end
end
def scheduled_session_reminder(user, msg, session)
def scheduled_session_reminder_upcoming(user, session)
subject = "Your JamKazam session starts in 1 hour!"
unique_args = {:type => "scheduled_session_reminder_upcoming"}
send_scheduled_session_reminder(user, session, subject, unique_args)
end
def scheduled_session_reminder_day(user, session)
subject = "JamKazam Session Reminder"
unique_args = {:type => "scheduled_session_reminder_day"}
send_scheduled_session_reminder(user, session, subject, unique_args)
end
def send_scheduled_session_reminder(user, session, subject, unique_args)
return if !user.subscribe_email
email = user.email
subject = "Session Rescheduled"
unique_args = {:type => "scheduled_session_reminder"}
@body = msg
@user = user
@session_name = session.name
@session_date = session.pretty_scheduled_start(true)
@session_url = "#{APP_CONFIG.external_root_url}/sessions/#{session.id}/details"
@ -448,7 +458,7 @@
@sessions_and_latency = sessions_and_latency
@title = 'New Scheduled Sessions Matched to You'
mail(:to => receiver.email,
mail(:to => receiver.email,
:subject => EmailBatchScheduledSessions.subject) do |format|
format.text
format.html
@ -461,7 +471,7 @@
email = user.email
subject = "A band that you follow has joined a session"
unique_args = {:type => "band_session_join"}
@body = msg
@session_url = "#{APP_CONFIG.external_root_url}/sessions/#{session_id}"
sendgrid_category "Notification"
@ -482,7 +492,7 @@
email = user.email
subject = "A musician has saved a new recording on JamKazam"
unique_args = {:type => "musician_recording_saved"}
@body = msg
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
@ -502,7 +512,7 @@
email = user.email
subject = "A band has saved a new recording on JamKazam"
unique_args = {:type => "band_recording_saved"}
@body = msg
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
@ -522,7 +532,7 @@
email = user.email
subject = "You have been invited to join a band on JamKazam"
unique_args = {:type => "band_invitation"}
@body = msg
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]

View File

@ -1,10 +0,0 @@
<% provide(:title, 'Scheduled Session Reminder') %>
<p><%= @body %></p>
<p>
<%= @session_name %><br/>
<%= @session_date %>
</p>
<p><a style="color: #ffcc00;" href="<%= @session_url %>">View Session Details</a></p>

View File

@ -1,6 +0,0 @@
<%= @body %>
<%= @session_name %>
<%= @session_date %>
See session details at <%= @session_url %>.

View File

@ -0,0 +1,18 @@
<% provide(:title, 'JamKazam Session Reminder') %>
<div>
Hi <%= @user.first_name %>,
</div>
<br/>
<div>
<span>This is a reminder that your JamKazam session</span>
<a href='<%=@session_url%>'><%= @session_name %></a>
<span>is scheduled for tomorrow. We hope you have fun!</span>
</div>
<br/>
<div>
Best Regards,
<br/>
Team JamKazam
</div>

View File

@ -0,0 +1,8 @@
Hi <%= @user.first_name %>,
This is a reminder that your JamKazam session <%=@session_name%> is scheduled for tomorrow. We hope you have fun!
Best Regards,
Team JamKazam
See session details at <%= @session_url %>.

View File

@ -0,0 +1,17 @@
<% provide(:title, 'Your JamKazam session starts in 1 hour!') %>
<div>
Hi <%= @user.first_name %>,
</div>
<br/>
<div>
<span>This is a reminder that your JamKazam session</span>
<a href='<%=@session_url%>'><%= @session_name %></a>
<span>starts in 1 hour. We hope you have fun!</span>
</div>
<br/>
<div>
Best Regards,
<br/>
Team JamKazam
</div>

View File

@ -0,0 +1,10 @@
Hi <%= @user.first_name %>,
This is a reminder that your JamKazam session
<%=@session_name%>
starts in 1 hour. We hope you have fun!
Best Regards,
Team JamKazam
See session details at <%= @session_url %>.

View File

@ -26,7 +26,9 @@ module NotificationTypes
SCHEDULED_SESSION_RSVP_CANCELLED_ORG = "SCHEDULED_SESSION_RSVP_CANCELLED_ORG"
SCHEDULED_SESSION_CANCELLED = "SCHEDULED_SESSION_CANCELLED"
SCHEDULED_SESSION_RESCHEDULED = "SCHEDULED_SESSION_RESCHEDULED"
SCHEDULED_SESSION_REMINDER = "SCHEDULED_SESSION_REMINDER"
SCHEDULED_SESSION_REMINDER_DAY = "SCHEDULED_SESSION_REMINDER_DAY"
SCHEDULED_SESSION_REMINDER_UPCOMING = "SCHEDULED_SESSION_REMINDER_UPCOMING"
SCHEDULED_SESSION_REMINDER_IMMINENT = "SCHEDULED_SESSION_REMINDER_IMMINENT"
SCHEDULED_SESSION_COMMENT = "SCHEDULED_SESSION_COMMENT"
# recording notifications

View File

@ -73,6 +73,10 @@ module JamRuby
@@message_factory = MessageFactory.new
################### HELPERS ###################
def notified?(music_session, notification_type)
Notification.where("session_id=? AND description=?", music_session, notification_type).count != 0
end
def retrieve_friends(connection, user_id)
friend_ids = []
connection.exec("SELECT f.friend_id as friend_id FROM friendships f WHERE f.user_id = $1", [user_id]) do |friend_results|
@ -203,9 +207,15 @@ module JamRuby
when NotificationTypes::SCHEDULED_SESSION_RESCHEDULED
return "The following session has been rescheduled."
when NotificationTypes::SCHEDULED_SESSION_REMINDER
when NotificationTypes::SCHEDULED_SESSION_REMINDER_DAY
return "A session to which you have RSVPd will begin in one hour, so get ready to play!"
when NotificationTypes::SCHEDULED_SESSION_REMINDER_UPCOMING
return "A session to which you have RSVPd will begin in one hour, so get ready to play!"
when NotificationTypes::SCHEDULED_SESSION_REMINDER_IMMINENT
return "A session to which you have RSVPd is scheduled to start in 5 minutes!"
when NotificationTypes::SCHEDULED_SESSION_COMMENT
return "New message about session."
@ -515,7 +525,7 @@ module JamRuby
end
def send_session_join(active_music_session, connection, user)
notification_msg = format_msg(NotificationTypes::SESSION_JOIN, {:user => user})
msg = @@message_factory.session_join(
@ -553,8 +563,8 @@ module JamRuby
end
def send_musician_session_join(music_session, user)
if music_session.musician_access || music_session.fan_access
if music_session.musician_access || music_session.fan_access
friends = Friendship.where(:friend_id => user.id)
user_followers = user.followers
@ -804,7 +814,7 @@ module JamRuby
def send_scheduled_session_cancelled(music_session)
return if music_session.nil?
rsvp_requests = RsvpRequest.index(music_session)
target_users = rsvp_requests.where(:canceled => false).map { |r| r.user }
@ -890,33 +900,52 @@ module JamRuby
end
end
def send_scheduled_session_reminder(music_session)
# Send session reminders to sessions that
# start in less than 24 hours, and haven't been
# notified for a particular interval yet:
def send_session_reminders
MusicSession.where("scheduled_start > NOW() AND scheduled_start <= (NOW()+INTERVAL '1 DAYS')").each do |candidate_session|
tm = candidate_session.scheduled_start
if (tm>(12.hours.from_now) && !notified?(candidate_session, NotificationTypes::SCHEDULED_SESSION_REMINDER_DAY))
# Send 24 hour reminders:
send_session_reminder_day(candidate_session)
elsif (tm<=(65.minutes.from_now) && tm>(15.minutes.from_now) && !notified?(candidate_session, NotificationTypes::SCHEDULED_SESSION_REMINDER_UPCOMING))
# Send 1 hour reminders:
send_session_reminder_upcoming(candidate_session)
elsif (tm<=(10.minutes.from_now) && !notified?(candidate_session, NotificationTypes::SCHEDULED_SESSION_REMINDER_IMMINENT))
# Send 5 minute reminders:
send_session_reminder_imminent(candidate_session)
end
end
end
return if music_session.nil?
def send_session_reminder_day(music_session)
send_session_reminder(music_session, NotificationTypes::SCHEDULED_SESSION_REMINDER_DAY) do |music_session, target_user, notification|
begin
UserMailer.scheduled_session_reminder_day(target_user, music_session).deliver
rescue => e
@@log.error("Unable to send SCHEDULED_SESSION_REMINDER_DAY email to user #{target_user.email} #{e}")
end
end
end
rsvp_requests = RsvpRequest.index(music_session)
target_users = rsvp_requests.where(:canceled => false).map { |r| r.user }
# remove the creator from the array
target_users = target_users.uniq - [music_session.creator]
target_users.each do |target_user|
source_user = music_session.creator
notification = Notification.new
notification.description = NotificationTypes::SCHEDULED_SESSION_REMINDER
notification.source_user_id = source_user.id
notification.target_user_id = target_user.id
notification.session_id = music_session.id
notification.save
notification_msg = format_msg(notification.description, {:session => music_session})
def send_session_reminder_upcoming(music_session)
send_session_reminder(music_session, NotificationTypes::SCHEDULED_SESSION_REMINDER_UPCOMING) do |music_session, target_user, notification|
begin
UserMailer.scheduled_session_reminder_upcoming(target_user, music_session).deliver
rescue => e
@@log.error("Unable to send SCHEDULED_SESSION_REMINDER_UPCOMING email to user #{target_user.email} #{e}")
end
end
end
def send_session_reminder_imminent(music_session)
send_session_reminder(music_session, NotificationTypes::SCHEDULED_SESSION_REMINDER_IMMINENT) do |music_session, target_user, notification|
if target_user.online
msg = @@message_factory.scheduled_session_reminder(
target_user.id,
music_session.id,
notification_msg,
format_msg(notification.description, {:session => music_session}),
music_session.name,
music_session.pretty_scheduled_start(false),
notification.id,
@ -925,12 +954,27 @@ module JamRuby
@@mq_router.publish_to_user(target_user.id, msg)
end
end
end
begin
UserMailer.scheduled_session_reminder(target_user, notification_msg, music_session).deliver
rescue => e
@@log.error("Unable to send SCHEDULED_SESSION_REMINDER email to user #{target_user.email} #{e}")
end
# @param music_session - the session for which to send reminder
# @param reminder_type - the type of reminder; one of:
# => SCHEDULED_SESSION_REMINDER_DAY 24 hours
# => SCHEDULED_SESSION_REMINDER_UPCOMING 15 minutes
# => SCHEDULED_SESSION_REMINDER_IMMINENT 5 minutes (in-app)
def send_session_reminder(music_session, reminder_type)
raise ArgumentError, "Block required" unless block_given?
source_user = music_session.creator
rsvp_requests = RsvpRequest.index(music_session)
rsvp_requests.where(:canceled => false).each do |rsvp|
target_user = rsvp.user
notification = Notification.new
notification.description = reminder_type
notification.source_user_id = source_user.id
notification.target_user_id = target_user.id
notification.session_id = music_session.id
notification.save
yield(music_session, target_user, notification)
end
end
@ -984,12 +1028,12 @@ module JamRuby
def send_band_session_join(music_session, band)
# if the session is private, don't send any notifications
if music_session.musician_access || music_session.fan_access
if music_session.musician_access || music_session.fan_access
notification_msg = format_msg(NotificationTypes::BAND_SESSION_JOIN, {:band => band})
followers = band.followers.map { |bf| bf.user }
# do not send band session notifications to band members
followers = followers - band.users
@ -1328,7 +1372,7 @@ module JamRuby
end
def send_band_invitation_accepted(band, band_invitation, sender, receiver)
notification = Notification.new
notification.band_id = band.id
notification.description = NotificationTypes::BAND_INVITATION_ACCEPTED
@ -1362,7 +1406,7 @@ module JamRuby
msg = @@message_factory.musician_session_fresh(
music_session.id,
user.id,
user.id,
user.name,
user.photo_url
)

View File

@ -0,0 +1,31 @@
require 'json'
require 'resque'
require 'resque-retry'
require 'net/http'
require 'digest/md5'
module JamRuby
class MusicSessionReminder
extend Resque::Plugins::JamLonelyJob
@queue = :music_session_reminder
@@log = Logging.logger[MusicSessionReminder]
def self.lock_timeout
120
end
def self.perform
@@log.debug("MusicSessionReminder waking up")
MusicSessionReminder.new.run
@@log.debug("MusicSessionReminder done")
end
def run
Notification.send_session_reminders()
end
end
end

View File

@ -18,6 +18,13 @@ describe Notification do
@session = FactoryGirl.create(:music_session)
@band = FactoryGirl.create(:band)
@slot1 = FactoryGirl.build(:rsvp_slot, :music_session => @session, :instrument => JamRuby::Instrument.find('electric guitar'))
@slot1.save
@slot2 = FactoryGirl.build(:rsvp_slot, :music_session => @session, :instrument => JamRuby::Instrument.find('drums'))
@slot2.save
@friend_request = FactoryGirl.create(:friend_request, user: @sender, friend: @receiver)
end
@ -199,7 +206,7 @@ describe Notification do
it "does not send email when user is offline and opts out of emails" do
FactoryGirl.create(:friendship, :user => @receiver, :friend => @recording.owner)
FactoryGirl.create(:friendship, :user => @recording.owner, :friend => @receiver)
@receiver.subscribe_email = false
@receiver.save!
@ -284,7 +291,7 @@ describe Notification do
@recording.band = @band
@recording.save!
follower.subscribe_email = false
follower.save!
@ -671,35 +678,14 @@ describe Notification do
end
end
describe "send scheduled session reminder" do
# it "sends email when user is offline and subscribes to emails" do
# session.creator = sender
# session.save!
# calls = count_publish_to_user_calls
# notification = Notification.send_scheduled_session_cancelled(session)
# UserMailer.deliveries.length.should == 1
# calls[:count].should == 1
# end
# it "does not send email when user is offline and opts out of emails" do
# session.creator = sender
# session.save!
# receiver.subscribe_email = false
# receiver.save!
# calls = count_publish_to_user_calls
# notification = Notification.send_scheduled_session_cancelled(session)
# UserMailer.deliveries.length.should == 0
# calls[:count].should == 1
# end
describe "reminders" do
let(:mail) { UserMailer.deliveries[0] }
before :each do
UserMailer.deliveries.clear
end
it "sends no notification if session is nil" do
calls = count_publish_to_user_calls
notification = Notification.send_scheduled_session_reminder(nil)
notification = Notification.send_session_reminders()
UserMailer.deliveries.length.should == 0
calls[:count].should == 0
@ -707,12 +693,65 @@ describe Notification do
it "sends no notification if there are no rsvp requests" do
calls = count_publish_to_user_calls
notification = Notification.send_scheduled_session_reminder(@session)
notification = Notification.send_session_reminders()
UserMailer.deliveries.length.should == 0
calls[:count].should == 0
end
end
it "sends email 24 hours before" do
@session.creator = @sender
@session.scheduled_start = Time.now + 23.hours
@session.save!
notification = Notification.send_session_reminders()
UserMailer.deliveries.length.should == 1
calls = count_publish_to_user_calls
calls[:count].should == 0
mail.html_part.body.include?("is scheduled for tomorrow").should be_true
mail.text_part.body.include?("is scheduled for tomorrow").should be_true
mail.html_part.body.include?("starts in 1 hour").should be_false
mail.text_part.body.include?("starts in 1 hour").should be_false
end
it "sends email 1 hour before" do
@session.creator = @sender
@session.scheduled_start = Time.now + 59.minutes
@session.save!
notification = Notification.send_session_reminders()
UserMailer.deliveries.length.should == 1
calls = count_publish_to_user_calls
calls[:count].should == 0
mail.html_part.body.include?("is scheduled for tomorrow").should be_false
mail.text_part.body.include?("is scheduled for tomorrow").should be_false
mail.html_part.body.include?("starts in 1 hour").should be_true
mail.text_part.body.include?("starts in 1 hour").should be_true
end
it "sends notice 5 minutes before" do
UserMailer.deliveries.length.should == 0
receiver_connection = FactoryGirl.create(:connection, user: @receiver)
@receiver.reload
rsvp = RsvpRequest.create({:session_id => @session.id, :rsvp_slots => [@slot1.id, @slot2.id], :message => "We be jammin!"}, @receiver)
UserMailer.deliveries.clear
calls = count_publish_to_user_calls
@session.creator = @sender
@session.scheduled_start = Time.now + 4.minutes
@session.save!
notification = Notification.send_session_reminders()
calls[:count].should == 1
UserMailer.deliveries.length.should == 0
end
end # reminders
describe "send scheduled session comment" do
# it "sends email when user is offline and subscribes to emails" do

View File

@ -0,0 +1,79 @@
require 'spec_helper'
describe 'MusicSessionReminder' do
let(:mail) { UserMailer.deliveries[0] }
before :each do
UserMailer.deliveries.clear
MusicSession.delete_all
User.delete_all
@receiver = FactoryGirl.create(:user)
@sender = FactoryGirl.create(:user)
@session = FactoryGirl.create(:music_session)
@slot1 = FactoryGirl.build(:rsvp_slot, :music_session => @session, :instrument => JamRuby::Instrument.find('electric guitar'))
@slot1.save
end
it "sends email 24 hours before" do
@session.creator = @sender
@session.scheduled_start = Time.now + 23.hours
@session.save!
JamRuby::MusicSessionReminder.perform
UserMailer.deliveries.length.should == 1
calls = count_publish_to_user_calls
calls[:count].should == 0
mail.html_part.body.include?("is scheduled for tomorrow").should be_true
mail.text_part.body.include?("is scheduled for tomorrow").should be_true
mail.html_part.body.include?("starts in 1 hour").should be_false
mail.text_part.body.include?("starts in 1 hour").should be_false
end
it "sends email 1 hour before" do
@session.creator = @sender
@session.scheduled_start = Time.now + 59.minutes
@session.save!
JamRuby::MusicSessionReminder.perform
UserMailer.deliveries.length.should == 1
calls = count_publish_to_user_calls
calls[:count].should == 0
mail.html_part.body.include?("is scheduled for tomorrow").should be_false
mail.text_part.body.include?("is scheduled for tomorrow").should be_false
mail.html_part.body.include?("starts in 1 hour").should be_true
mail.text_part.body.include?("starts in 1 hour").should be_true
end
it "sends notice 5 minutes before" do
UserMailer.deliveries.length.should == 0
receiver_connection = FactoryGirl.create(:connection, user: @receiver)
@receiver.reload
rsvp = RsvpRequest.create({:session_id => @session.id, :rsvp_slots => [@slot1.id, @slot2.id], :message => "We be jammin!"}, @receiver)
UserMailer.deliveries.clear
calls = count_publish_to_user_calls
@session.creator = @sender
@session.scheduled_start = Time.now + 4.minutes
@session.save!
JamRuby::MusicSessionReminder.perform
calls[:count].should == 1
UserMailer.deliveries.length.should == 0
end
def count_publish_to_user_calls
result = {count: 0}
MQRouter.any_instance.stub(:publish_to_user) do |receiver_id, msg|
result[:count] += 1
result[:msg] = msg
end
result
end
end #spec

View File

@ -123,7 +123,7 @@ describe UserMailer do
before(:each) do
user.update_email = "my_new_email@jamkazam.com"
UserMailer.updating_email(user).deliver
UserMailer.updating_email(user).deliver
end
it { UserMailer.deliveries.length.should == 1 }
@ -137,6 +137,48 @@ describe UserMailer do
it { mail.text_part.body.include?("to confirm your change in email").should be_true }
end
describe "notifications" do
let(:mail) { UserMailer.deliveries[0] }
let(:music_session) { FactoryGirl.create(:music_session) }
it "should send upcoming email" do
user.update_email = "my_new_email@jamkazam.com"
UserMailer.scheduled_session_reminder_upcoming(music_session.creator, music_session).deliver
UserMailer.deliveries.length.should == 1
mail['from'].to_s.should == UserMailer::DEFAULT_SENDER
mail['to'].to_s.should == music_session.creator.email# rsvp_requests.first.user.email
mail.multipart?.should == true # because we send plain + htm
# verify that the messages are correctly configured
mail.html_part.body.include?("This is a reminder that your JamKazam session").should be_true
mail.text_part.body.include?("This is a reminder that your JamKazam session").should be_true
mail.html_part.body.include?("starts in 1 hour").should be_true
mail.text_part.body.include?("starts in 1 hour").should be_true
end
it "should send 1-day reminder" do
user.update_email = "my_new_email@jamkazam.com"
UserMailer.scheduled_session_reminder_day(music_session.creator, music_session).deliver
UserMailer.deliveries.length.should == 1
mail['from'].to_s.should == UserMailer::DEFAULT_SENDER
mail['to'].to_s.should == music_session.creator.email# rsvp_requests.first.user.email
mail.multipart?.should == true # because we send plain + htm
# verify that the messages are correctly configured
mail.html_part.body.include?("This is a reminder that your JamKazam session").should be_true
mail.text_part.body.include?("This is a reminder that your JamKazam session").should be_true
mail.html_part.body.include?("is scheduled for tomorrow").should be_true
mail.text_part.body.include?("is scheduled for tomorrow").should be_true
end
end
# describe "sends new musicians email" do

View File

@ -70,6 +70,11 @@ ScoreHistorySweeper:
class: "JamRuby::ScoreHistorySweeper"
description: "Creates 'ScoreHistory' tables from Scores (disabled for now)"
SessionReminder:
cron: */5 * * * *
class: "JamRuby::MusicSessionReminder"
description: "Creates session reminder emails and notifications as needed."
RecordingsCleaner:
cron: 0 * * * *
class: "JamRuby::RecordingsCleaner"