-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Inactive user detection and email notification (#865)
* Create an inactive user email with a job to trigger them * Try to fix standardrb with open Range and TargetRubyVersion * Add a script to generate volume in order to test the inactive user query * Add a rake task to trigger the inactive users emails * Throttle inactive user emails * Account as refused expired matches too * Consider refused matches as activity * Update email template * Clean-up controllers that were used for anonymizing the user
- Loading branch information
1 parent
2f71c5f
commit 007e499
Showing
26 changed files
with
494 additions
and
176 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ruby_version: 2.7.3 |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# This controller holds temporary redirection logic. | ||
# After a while, it would be fine removing each action as it shouldn't be used anymore. | ||
class RedirectsController < ApplicationController | ||
rescue_from ActiveRecord::RecordNotFound, with: :redirect_to_root_with_message | ||
rescue_from ArgumentError, with: :redirect_to_root_with_message | ||
|
||
def confirm_destroy_from_match | ||
redirect_to confirm_destroy_path( | ||
Match.find_by!(match_confirmation_token: params[:match_confirmation_token]).user | ||
) | ||
end | ||
|
||
def confirm_destroy_from_slot_alert | ||
redirect_to confirm_destroy_path( | ||
SlotAlert.find_by!(token: params[:token]).user | ||
) | ||
end | ||
|
||
private | ||
|
||
def skip_pundit? | ||
true | ||
end | ||
|
||
def redirect_to_root_with_message | ||
flash[:error] = "Désolé, ce lien n’est plus valide." | ||
redirect_to root_path | ||
end | ||
|
||
def confirm_destroy_path(user) | ||
raise ArgumentError if user.anonymized_at | ||
|
||
token = user.signed_id(purpose: "users.destroy", expires_in: 1.minute) | ||
confirm_destroy_profile_path(authentication_token: token) | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
class SendInactiveUserEmailsJob < ApplicationJob | ||
queue_as :low | ||
|
||
DEFAULT_MIN_UNANSWERED_MATCHES = 2 # At least two refused OR unanswered matches | ||
DEFAULT_AGE_RANGE = 0..200 # Covers all ages (except unborns) | ||
DEFAULT_SIGNED_UP_RANGE = nil..nil # Covers all dates | ||
|
||
def perform(*args) | ||
self.class.inactive_user_ids(*args).each do |user_id| | ||
UserMailer | ||
.with(user_id: user_id) | ||
.send_inactive_user_unsubscription_request | ||
.deliver_later | ||
end | ||
end | ||
|
||
def self.inactive_user_ids(min_unanswered_matches = DEFAULT_MIN_UNANSWERED_MATCHES, age_range = DEFAULT_AGE_RANGE, signed_up_date_range = DEFAULT_SIGNED_UP_RANGE) | ||
sql = <<~SQL.squish | ||
with target_users as ( | ||
select id | ||
from users | ||
where anonymized_at is null | ||
and birthdate <= :max_birthdate | ||
and birthdate >= :min_birthdate | ||
and created_at <= :max_created_at | ||
and created_at >= :min_created_at | ||
), match_stats_per_user as ( | ||
select | ||
user_id | ||
, count(*) filter (where confirmed_at is not null) as confirmed_matches_count | ||
, count(*) filter (where refused_at is not null) as refused_matches_count | ||
, count(*) filter (where expires_at < now() and confirmed_at is null and refused_at is null) as unanswered_matches_count | ||
, count(*) filter (where expires_at >= now() and confirmed_at is null and refused_at is null) as pending_matches_count | ||
from matches | ||
group by user_id | ||
) | ||
select u.id | ||
from target_users as u | ||
join match_stats_per_user as s | ||
on s.user_id = u.id | ||
and s.unanswered_matches_count >= :min_unanswered_matches | ||
and s.refused_matches_count = 0 | ||
and s.pending_matches_count = 0 | ||
and s.confirmed_matches_count = 0 | ||
SQL | ||
params = { | ||
min_unanswered_matches: min_unanswered_matches, | ||
min_birthdate: (age_range.end || 200).years.ago.to_date, | ||
max_birthdate: (age_range.begin || 0).years.ago.to_date, | ||
min_created_at: signed_up_date_range.begin || 200.years.ago, | ||
max_created_at: signed_up_date_range.end || Date.current.end_of_day | ||
} | ||
User.connection.select_values( | ||
ActiveRecord::Base.send(:sanitize_sql_array, [sql, params]) | ||
) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
class UserMailer < ApplicationMailer | ||
default from: "Covidliste <[email protected]>" | ||
|
||
MIN_SEND_INTERVAL = 7.days | ||
|
||
def send_inactive_user_unsubscription_request | ||
@user = User.find(params[:user_id]) | ||
|
||
return if @user.email.blank? | ||
return if @user.last_inactive_user_email_sent_at && @user.last_inactive_user_email_sent_at >= MIN_SEND_INTERVAL.ago | ||
|
||
@user.touch(:last_inactive_user_email_sent_at) | ||
|
||
mail( | ||
to: @user.email, | ||
subject: "Attendez-vous toujours une dose de vaccin ?" | ||
) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
20 changes: 20 additions & 0 deletions
20
app/views/user_mailer/send_inactive_user_unsubscription_request.mjml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<% token = @user.signed_id(purpose: "users.destroy", expires_in: 7.days) %> | ||
<mj-section padding-top="15px" padding-bottom="15px"> | ||
<mj-column> | ||
<mj-text padding-bottom="0px"> | ||
<h1>Avez-vous toujours besoin d'une dose de vaccin ?</h1> | ||
<br /> | ||
<strong>Si vous ne souhaitez plus être informé de doses disponibles,</strong>, | ||
laissez votre place à quelqu'un d'autre en supprimant votre compte : | ||
</mj-text> | ||
<mj-button href="<%= confirm_destroy_profile_url(authentication_token: token) %>" padding-bottom="0px"> | ||
Supprimer mon compte | ||
</mj-button> | ||
<mj-text> | ||
<strong>Si le lien ne fonctionne pas</strong>, copiez et collez l’adresse suivante dans votre navigateur : | ||
<br /> | ||
<%= confirm_destroy_profile_url(authentication_token: token) %> | ||
</mj-text> | ||
<%= render partial: "mailer/social_networks", formats: [:html] %> | ||
</mj-column> | ||
</mj-section> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,39 @@ | ||
<div class="container"> | ||
<div class="d-flex align-items-center flex-column my-4 my-lg-5"> | ||
<h4 class="text-center"> | ||
Pourquoi souhaitez vous supprimer votre compte ? | ||
</h4> | ||
<% token = user.signed_id(purpose: "users.destroy", expires_in: 1.hour) %> | ||
<% if user.matches.confirmed.any? %> | ||
<div class="container"> | ||
<div class="d-flex align-items-center flex-column my-4 my-lg-5"> | ||
<h4 class="text-center mb-4"> | ||
Vous avez confirmé votre rendez-vous. | ||
</h4> | ||
<p class="alert alert-info"> | ||
Vous ne pouvez pas supprimer vos informations actuellement car vous avez confirmé un rendez-vous de vaccination. | ||
<br> | ||
Votre profil sera anonymisé quelques jours après le RDV. | ||
</p> | ||
</div> | ||
</div> | ||
<% else %> | ||
<div class="container"> | ||
<div class="d-flex align-items-center flex-column my-4 my-lg-5"> | ||
<h4 class="text-center"> | ||
Pourquoi souhaitez vous supprimer votre compte ? | ||
</h4> | ||
<% token = user.signed_id(purpose: "users.destroy", expires_in: 1.hour) %> | ||
|
||
<p class="mt-4 text-center"> | ||
<strong> | ||
Cette information est nécessaire pour nous améliorer notre service. Prenez le temps d'y répondre sérieusement. | ||
Par ailleurs, cette information n'est pas associée à votre compte. | ||
</strong> | ||
</p> | ||
|
||
<%= simple_form_for "", url: profile_path(authentication_token: token), method: :delete do |f| %> | ||
<div class="mt-4"> | ||
<%= f.input :reason, required: true, label: '', class: 'mt-4', collection: User::ANONYMIZED_REASONS.map{|k, v| [v, k]}.shuffle, as: :radio_buttons, item_label_class: 'mt-y' %> | ||
<%= f.submit "Supprimer mon compte !", | ||
id: dom_id(user, :delete), | ||
class: "btn btn-outline-danger btn-lg mt-4", | ||
data: {confirm: "En confirmant, votre compte ainsi que toutes les données associées seront supprimées de nos serveurs. Êtes-vous sûr(e) ?"} %> | ||
</div> | ||
<% end %> | ||
<p class="mt-4 text-center"> | ||
<strong> | ||
Cette information est nécessaire pour nous améliorer notre service. Prenez le temps d'y répondre sérieusement. | ||
Par ailleurs, cette information n'est pas associée à votre compte. | ||
</strong> | ||
</p> | ||
<%= simple_form_for "", url: profile_path(authentication_token: token), method: :delete do |f| %> | ||
<div class="mt-4"> | ||
<%= f.input :reason, required: true, label: '', class: 'mt-4', collection: User::ANONYMIZED_REASONS.map{|k, v| [v, k]}.shuffle, as: :radio_buttons, item_label_class: 'mt-y' %> | ||
<%= f.submit "Supprimer mon compte !", | ||
id: dom_id(user, :delete), | ||
class: "btn btn-outline-danger btn-lg mt-4", | ||
data: {confirm: "En confirmant, votre compte ainsi que toutes les données associées seront supprimées de nos serveurs. Êtes-vous sûr(e) ?"} %> | ||
</div> | ||
<% end %> | ||
</div> | ||
</div> | ||
</div> | ||
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
# Handle old routes redirections (before devise users path migration) | ||
|
||
get "/login", to: redirect("/users/login", status: 302), as: :legacy_new_user_session | ||
post "/login", to: redirect("/users/login", status: 302), as: :legacy_user_session | ||
delete "/logout", to: redirect("/users/logout", status: 302), as: :legacy_destroy_user_session | ||
get "/profile", to: redirect("/users/profile", status: 302), as: :legacy_profile | ||
get "/confirmation/new", to: redirect("/users/confirmation/new", status: 302), as: :legacy_new_user_confirmation | ||
get "/confirmation", to: redirect { |_, request| "/users/confirmation#{request.params.present? ? "?" + request.params.to_query : ""}" }, as: :legacy_user_confirmation | ||
get "/matches/users/edit", controller: :redirects, action: :confirm_destroy_from_match, as: :legacy_edit_matches_users | ||
get "/slot_alerts/users/edit", controller: :redirects, action: :confirm_destroy_from_slot_alert, as: :legacy_edit_slot_alerts_users |
5 changes: 5 additions & 0 deletions
5
db/migrate/20210530071920_add_last_inactive_user_email_sent_at.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
class AddLastInactiveUserEmailSentAt < ActiveRecord::Migration[6.1] | ||
def change | ||
add_column :users, :last_inactive_user_email_sent_at, :datetime | ||
end | ||
end |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.