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 | Notifications mechanism #69

Merged
merged 24 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d0e1dd9
Reminder scheduler
AlanSoto31 Jul 26, 2024
730fd61
Noticed setup and create Event Notifier
AlanSoto31 Jul 26, 2024
5d4eaf0
Include multiple reminder cadence
AlanSoto31 Jul 26, 2024
a34201c
Notifications preferences
AlanSoto31 Jul 27, 2024
408470b
Abstract reminder logic from Event model
AlanSoto31 Jul 27, 2024
3147d36
Undo notifications preferences
AlanSoto31 Jul 27, 2024
a2bb0e4
Merge branch 'main' into feature/notifications-mechanism
AlanSoto31 Jul 27, 2024
748266b
minor fix
AlanSoto31 Jul 27, 2024
7d3f177
Merge branch 'main' into feature/notifications-mechanism
LuigiR0jas Jul 30, 2024
9b9bb1d
Merge branch 'main' into feature/notifications-mechanism
LuigiR0jas Jul 31, 2024
7e6cbe9
Update app/notifiers/event_notifier.rb
AlanSoto31 Jul 31, 2024
622b67b
Merge branch 'main' into feature/notifications-mechanism
AlanSoto31 Jul 31, 2024
0aa26e7
Apply recurring_tasks approach
AlanSoto31 Jul 31, 2024
67138d3
Merge branch 'main' into feature/notifications-mechanism
AlanSoto31 Jul 31, 2024
25640c1
Specs for EventReminderjob
AlanSoto31 Aug 1, 2024
8e52937
Update rexml
AlanSoto31 Aug 1, 2024
a804aab
Merge branch 'main' into feature/notifications-mechanism
AlanSoto31 Aug 2, 2024
c69edd4
Rename notifier
AlanSoto31 Aug 2, 2024
e3f83c3
Prevent delivering diplicated reminders
AlanSoto31 Aug 2, 2024
a82c08f
Delivers lost reminder in next job iteration
AlanSoto31 Aug 3, 2024
dca0b7f
Session reminder improvements
Sergio-e Aug 7, 2024
643b452
Merge branch 'main' into feature/notifications-mechanism
LuigiR0jas Aug 7, 2024
76c18a0
Add sent_reminders to AVO session
Sergio-e Aug 7, 2024
534b3b9
Add feature flag readme
Sergio-e Aug 7, 2024
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
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ gem "ransack"
gem "bootsnap", require: false
gem "draper"
gem "inline_svg"
gem "noticed"
gem "puma", ">= 5.0"
gem "rqrcode", "~> 2.0"
gem "tzinfo-data", platforms: %i[windows jruby]
Expand Down Expand Up @@ -77,4 +78,5 @@ group :test do
gem "rails-controller-testing"
gem "rspec-instafail", require: false
gem "rspec-retry"
gem "timecop"
end
7 changes: 6 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ GEM
racc (~> 1.4)
nokogiri (1.16.6-x86_64-linux)
racc (~> 1.4)
noticed (2.4.0)
rails (>= 6.1.0)
pagy (8.6.3)
parallel (1.25.1)
parser (3.3.3.0)
Expand Down Expand Up @@ -390,7 +392,7 @@ GEM
io-console (~> 0.5)
request_store (1.7.0)
rack (>= 1.4)
rexml (3.3.3)
rexml (3.3.4)
strscan
rouge (4.3.0)
rqrcode (2.2.0)
Expand Down Expand Up @@ -506,6 +508,7 @@ GEM
railties (>= 7.0.0)
thor (1.3.1)
thread_safe (0.3.6)
timecop (0.9.10)
timeliness (0.4.5)
timeout (0.4.1)
tty-which (0.5.0)
Expand Down Expand Up @@ -573,6 +576,7 @@ DEPENDENCIES
inline_svg
letter_opener (~> 1.10)
mission_control-jobs
noticed
propshaft
pry-byebug
puma (>= 5.0)
Expand All @@ -595,6 +599,7 @@ DEPENDENCIES
standard
stimulus-rails
tailwindcss-rails (~> 2.6)
timecop
turbo-rails
tzinfo-data
validates_timeliness (~> 7.0.0.beta1)
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,7 @@ Run tests by using `bundle exec rspec`.
- If you want to see the logs you can use `:log`, e.g. `it "xxx", :log do`
- Use `data-test-id` to find elements instead of classes/ids, e.g. `data-test-id="decline_modal"`
- Use the methods in the `DataTestId` module to select HTML elements, e.g., `find_dti("decline_modal")`

## Feature Flags

Use ENV variables to enable features, the name should follow the convention `"#{feature_name}_ENABLED"`. For example, to enable the `payment` feature, use `ENV["PAYMENT_ENABLED"]="true"`.
9 changes: 7 additions & 2 deletions app/avo/resources/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ def fields
field :id, as: :id
field :title, as: :text, sortable: true
field :description, as: :textarea
field :starts_at, as: :date_time, help: Time.zone.name, sortable: true
field :ends_at, as: :date_time, help: Time.zone.name, sortable: true
field :starts_at, as: :date_time,
help: "The datetime field will use your browser's current timezone.", sortable: true,
format: "FFFF"
field :ends_at, as: :date_time,
help: "The datetime field will use your browser's current timezone.", sortable: true,
format: "FFFF"
field :sent_reminders, only_on: :show
field :location, as: :belongs_to
field :conference, as: :belongs_to
field :speakers, as: :has_and_belongs_to_many, can_create: false
Expand Down
34 changes: 34 additions & 0 deletions app/jobs/session_reminder_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
class SessionReminderJob < ApplicationJob
REMINDER_TIME_BEFORE_EVENT = [5.minutes].freeze

def perform
if Feature.disabled?(:session_reminders)
return Rails.logger.info("Skipping session reminders. Feature is disabled")
end

now = Time.zone.now
Rails.logger.info "Searching for sessions to remind users about. Time is #{now}"

REMINDER_TIME_BEFORE_EVENT.each do |time_before_session|
# Grace time is the time window in which we send reminders that should have been sent already.
grace_time = 2.minutes

start_reminder_time = (now + time_before_session - grace_time).end_of_minute
end_reminder_time = (start_reminder_time + grace_time).beginning_of_minute

Rails.logger.info "Searching for sessions with starts_at between #{start_reminder_time} and #{end_reminder_time}"

Session.where(starts_at: start_reminder_time..end_reminder_time).find_each do |session|
next if session.sent_reminders.include?(time_before_session.inspect)

session.sent_reminders << time_before_session.inspect
session.sent_reminders.uniq!
session.save!

SessionReminderNotifier
.with(record: session, time_before_session: time_before_session.inspect)
.deliver(session.attendees.with_at_least_one_notification_enabled)
end
end
end
end
7 changes: 7 additions & 0 deletions app/mailers/session_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class SessionMailer < ApplicationMailer
def reminder
recipient = params[:recipient]

mail(to: recipient.email)
end
end
9 changes: 9 additions & 0 deletions app/models/feature.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class Feature
def self.enabled?(feature)
ENV["#{feature.to_s.upcase}_ENABLED"] == "true"
end

def self.disabled?(feature)
!enabled?(feature)
end
end
21 changes: 11 additions & 10 deletions app/models/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
#
# Table name: sessions
#
# id :integer not null, primary key
# description :string
# ends_at :datetime not null
# starts_at :datetime not null
# title :string not null
# created_at :datetime not null
# updated_at :datetime not null
# conference_id :integer not null
# location_id :integer not null
# id :integer not null, primary key
# description :string
# ends_at :datetime not null
# sent_reminders :json
# starts_at :datetime not null
# title :string not null
# created_at :datetime not null
# updated_at :datetime not null
# conference_id :integer not null
# location_id :integer not null
#
# Indexes
#
Expand All @@ -22,7 +23,7 @@ class Session < ApplicationRecord
belongs_to :conference

has_and_belongs_to_many :speakers
has_and_belongs_to_many :users # attendees
has_and_belongs_to_many :attendees, class_name: "User", join_table: "sessions_users"
has_and_belongs_to_many :tags

validates :title, presence: true
Expand Down
6 changes: 6 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class User < ApplicationRecord

has_and_belongs_to_many :sessions

has_many :notifications, as: :recipient, dependent: :destroy, class_name: "Noticed::Notification"

validates :email, presence: true, uniqueness: true, format: {with: URI::MailTo::EMAIL_REGEXP}
validates :password_digest, presence: true
validates :password, length: {minimum: 8}, if: -> { password.present? }
Expand All @@ -34,6 +36,10 @@ class User < ApplicationRecord

accepts_nested_attributes_for :profile

scope :with_at_least_one_notification_enabled, -> {
joins(:profile).where("profiles.in_app_notifications = ? OR profiles.mail_notifications = ?", true, true)
}

generates_token_for :password_reset, expires_in: PASSWORD_RESET_EXPIRATION do
password_salt&.last(10)
end
Expand Down
2 changes: 2 additions & 0 deletions app/notifiers/application_notifier.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class ApplicationNotifier < Noticed::Event
end
17 changes: 17 additions & 0 deletions app/notifiers/session_reminder_notifier.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class SessionReminderNotifier < ApplicationNotifier
deliver_by :email do |config|
config.mailer = "SessionMailer"
config.method = "reminder"
config.if = -> { recipient.profile&.mail_notifications }
end

notification_methods do
def title
if params[:time_before_session].match?(/^0\s/)
"Starting Now"
else
"Starting in about #{params[:time_before_session]}"
end
end
end
end
2 changes: 2 additions & 0 deletions app/views/main/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
<h1>Homepage</h1>
<%= link_to "Profile", profile_path(current_profile&.uuid) %>

<p>Current time is <%= Time.zone.now %></p>
2 changes: 2 additions & 0 deletions app/views/session_mailer/reminder.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<h1><%= params[:notification].title %></h1>
<p><%= params[:record].title %></p>
4 changes: 4 additions & 0 deletions config/solid_queue.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ default: &default
dispatchers:
- polling_interval: 1
batch_size: 500
recurring_tasks:
session_reminder:
class: SessionReminderJob
schedule: every minute
workers:
- queues: "*"
threads: 3
Expand Down
1 change: 1 addition & 0 deletions db/migrate/20240628204535_create_sessions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ def change
t.string :description
t.datetime :starts_at, null: false
t.datetime :ends_at, null: false
t.json :sent_reminders, default: []
t.references :location, null: false, foreign_key: true
t.references :conference, null: false, foreign_key: true

Expand Down
37 changes: 37 additions & 0 deletions db/migrate/20240726171427_create_noticed_tables.noticed.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# This migration comes from noticed (originally 20231215190233)
class CreateNoticedTables < ActiveRecord::Migration[6.1]
def change
primary_key_type, foreign_key_type = primary_and_foreign_key_types
create_table :noticed_events, id: primary_key_type do |t|
t.string :type
t.belongs_to :record, polymorphic: true, type: foreign_key_type
if t.respond_to?(:jsonb)
t.jsonb :params
else
t.json :params
end

t.timestamps
end

create_table :noticed_notifications, id: primary_key_type do |t|
t.string :type
t.belongs_to :event, null: false, type: foreign_key_type
t.belongs_to :recipient, polymorphic: true, null: false, type: foreign_key_type
t.datetime :read_at
t.datetime :seen_at

t.timestamps
end
end

private

def primary_and_foreign_key_types
config = Rails.configuration.generators
setting = config.options[config.orm][:primary_key_type]
primary_key_type = setting || :primary_key
foreign_key_type = setting || :bigint
[primary_key_type, foreign_key_type]
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# This migration comes from noticed (originally 20240129184740)
class AddNotificationsCountToNoticedEvent < ActiveRecord::Migration[6.1]
def change
add_column :noticed_events, :notifications_count, :integer
end
end
27 changes: 26 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 10 additions & 9 deletions spec/factories/sessions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
#
# Table name: sessions
#
# id :integer not null, primary key
# description :string
# ends_at :datetime not null
# starts_at :datetime not null
# title :string not null
# created_at :datetime not null
# updated_at :datetime not null
# conference_id :integer not null
# location_id :integer not null
# id :integer not null, primary key
# description :string
# ends_at :datetime not null
# sent_reminders :json
# starts_at :datetime not null
# title :string not null
# created_at :datetime not null
# updated_at :datetime not null
# conference_id :integer not null
# location_id :integer not null
#
# Indexes
#
Expand Down
Loading