Skip to content

Commit

Permalink
AO3-6712 Allow invites to be resent and index invitee_email (otwcode#…
Browse files Browse the repository at this point in the history
…4791)

* AO3-6712 Revert AO3-6666

This reverts commit 7e15f32.

* AO3-6712 Add invitee email index to invitations

* AO3-6712 Make sure button has hover style and i18n

* AO3-6712 Normalize

* AO3-6712 Fix a mistake

* AO3-6712 Fix step that was looking for value instead of label text
  • Loading branch information
weeklies authored Feb 23, 2025
1 parent 25865ac commit 129e6e9
Show file tree
Hide file tree
Showing 18 changed files with 274 additions and 47 deletions.
31 changes: 27 additions & 4 deletions app/controllers/invite_requests_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,40 @@ def index
# GET /invite_requests/1
def show
@invite_request = InviteRequest.find_by(email: params[:email])
@position_in_queue = @invite_request.position if @invite_request.present?
unless (request.xml_http_request?) || @invite_request
flash[:error] = "You can search for the email address you signed up with below. If you can't find it, your invitation may have already been emailed to that address; please check your email spam folder as your spam filters may have placed it there."
redirect_to status_invite_requests_path and return

if @invite_request.present?
@position_in_queue = @invite_request.position
else
@invitation = Invitation.unredeemed.from_queue.find_by(invitee_email: params[:email])
end

respond_to do |format|
format.html
format.js
end
end

def resend
@invitation = Invitation.unredeemed.from_queue.find_by(invitee_email: params[:email])

if @invitation.nil?
flash[:error] = t("invite_requests.resend.not_found")
elsif !@invitation.can_resend?
flash[:error] = t("invite_requests.resend.not_yet",
count: ArchiveConfig.HOURS_BEFORE_RESEND_INVITATION)
else
@invitation.send_and_set_date(resend: true)

if @invitation.errors.any?
flash[:error] = @invitation.errors.full_messages.first
else
flash[:notice] = t("invite_requests.resend.success", email: @invitation.invitee_email)
end
end

redirect_to status_invite_requests_path
end

# POST /invite_requests
def create
unless AdminSetting.current.invite_from_queue_enabled?
Expand Down
54 changes: 33 additions & 21 deletions app/models/invitation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ def recipient_is_not_registered
scope :unsent, -> { where(invitee_email: nil, redeemed_at: nil) }
scope :unredeemed, -> { where('invitee_email IS NOT NULL and redeemed_at IS NULL') }
scope :redeemed, -> { where('redeemed_at IS NOT NULL') }
scope :from_queue, -> { where(external_author: nil).where(creator_type: [nil, "Admin"]) }

before_validation :generate_token, on: :create
after_save :send_and_set_date
after_save :send_and_set_date, if: :saved_change_to_invitee_email?
after_save :adjust_user_invite_status

#Create a certain number of invitations for all valid users
Expand Down Expand Up @@ -58,30 +59,41 @@ def mark_as_redeemed(user=nil)
save
end

private
def send_and_set_date(resend: false)
return if invitee_email.blank?

def generate_token
self.token = Digest::SHA1.hexdigest([Time.now, rand].join)
if self.external_author
archivist = self.external_author.external_creatorships.collect(&:archivist).collect(&:login).uniq.join(", ")
# send invite synchronously for now -- this should now work delayed but just to be safe
UserMailer.invitation_to_claim(self.id, archivist).deliver_now
else
# send invitations actively sent by a user synchronously to avoid delays
UserMailer.invitation(self.id).deliver_now
end

# Skip callbacks within after_save by using update_column to avoid a callback loop
if resend
attrs = { resent_at: Time.current }
# This applies to old invites when AO3-6094 wasn't fixed.
attrs[:sent_at] = self.created_at if self.sent_at.nil?
self.update_columns(attrs)
else
self.update_column(:sent_at, Time.current)
end
rescue StandardError => e
errors.add(:base, :notification_could_not_be_sent, error: e.message)
end

def send_and_set_date
if self.saved_change_to_invitee_email? && !self.invitee_email.blank?
begin
if self.external_author
archivist = self.external_author.external_creatorships.collect(&:archivist).collect(&:login).uniq.join(", ")
# send invite synchronously for now -- this should now work delayed but just to be safe
UserMailer.invitation_to_claim(self.id, archivist).deliver_now
else
# send invitations actively sent by a user synchronously to avoid delays
UserMailer.invitation(self.id).deliver_now
end
def can_resend?
# created_at fallback is a vestige of the already fixed AO3-6094.
checked_date = self.resent_at || self.sent_at || self.created_at
checked_date < ArchiveConfig.HOURS_BEFORE_RESEND_INVITATION.hours.ago
end

# Skip callbacks within after_save by using update_column to avoid a callback loop
self.update_column(:sent_at, Time.now)
rescue Exception => exception
errors.add(:base, "Notification email could not be sent: #{exception.message}")
end
end
private

def generate_token
self.token = Digest::SHA1.hexdigest([Time.current, rand].join)
end

#Update the user's out_of_invites status
Expand Down
2 changes: 2 additions & 0 deletions app/views/invitations/_invitation.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
<dd><%= invitation.created_at %></dd>
<dt>Sent at</dt>
<dd><%= invitation.sent_at %></dd>
<dt>Last resent at</dt>
<dd><%= invitation.resent_at %></dd>
<dt>Redeemed at</dt>
<dd><%= invitation.redeemed_at %></dd>
</dl>
28 changes: 28 additions & 0 deletions app/views/invite_requests/_invitation.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!--Descriptive page name, messages and instructions-->
<h2 class="heading" id="invite-heading">
<%= t(".title", email: invitation.invitee_email) %>
</h2>
<!--/descriptions-->

<!--main content-->
<p>
<% status = invitation.resent_at ? "resent" : "not_resent" %>
<%= t(".info.#{status}",
sent_at: l((invitation.sent_at || invitation.created_at).to_date),
resent_at: invitation.resent_at ? l(invitation.resent_at.to_date) : nil) %>
</p>

<p>
<% if invitation.can_resend? %>
<%# i18n-tasks-use t("invite_requests.invitation.after_cooldown_period.not_resent")
i18n-tasks-use t("invite_requests.invitation.after_cooldown_period.resent_html")-%>
<% status = invitation.resent_at ? "resent_html" : "not_resent" %>
<%= t(".after_cooldown_period.#{status}",
count: ArchiveConfig.HOURS_BEFORE_RESEND_INVITATION,
contact_support_link: link_to(t(".contact_support"), new_feedback_report_path)) %>
<%= button_to t(".resend_button"), resend_invite_requests_path(email: invitation.invitee_email) %>
<% else %>
<%= t(".before_cooldown_period", count: ArchiveConfig.HOURS_BEFORE_RESEND_INVITATION) %>
<% end %>
</p>
<!--/content-->
3 changes: 3 additions & 0 deletions app/views/invite_requests/_no_invitation.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<p class="notice">
<%= t(".email_not_found") %>
</p>
10 changes: 8 additions & 2 deletions app/views/invite_requests/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<%= render "invite_request", invite_request: @invite_request %>
<% if @invite_request %>
<%= render "invite_request", invite_request: @invite_request %>
<% elsif @invitation %>
<%= render "invitation", invitation: @invitation %>
<% else %>
<%= render "no_invitation" %>
<% end %>

<p>
<%= ts("To check on the status of your invitation, go to the %{status_page} and enter your email in the space provided!", status_page: link_to("Invitation Request Status page", status_invite_requests_path)).html_safe %>
<%= t(".instructions_html", status_link: link_to("Invitation Request Status page", status_invite_requests_path)) %>
</p>
11 changes: 10 additions & 1 deletion app/views/invite_requests/show.js.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
<% if @invite_request %>
$j("#invite-status").html("<%= escape_javascript(render "invite_requests/invite_request", invite_request: @invite_request) %>");
<% elsif @invitation %>
$j("#invite-status").html("<%= escape_javascript(render "invitation", invitation: @invitation) %>");

<%# Correct heading size for JavaScript display %>
$j(document).ready(function(){
$j('#invite-heading').replaceWith(function () {
return '<h3 class="heading">' + $j(this).html() + "</h3>";
});
})
<% else %>
$j("#invite-status").html("<p>Sorry, we can't find the email address you entered. If you had used it to join our invitation queue, it's possible that your invitation may have already been emailed to you; please check your spam folder, as your spam filters may have placed it there.</p>");
$j("#invite-status").html("<%= escape_javascript(render "no_invitation") %>");
<% end %>
10 changes: 6 additions & 4 deletions app/views/invite_requests/status.html.erb
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<!--Descriptive page name, messages and instructions-->
<h2 class="heading">
<%= ts("Invitation Request Status") %>
<%= t(".heading") %>
</h2>

<p>
<%= ts("There are currently %{count} people on the waiting list.", count: InviteRequest.count) %>
<%= t(".waiting_list", count: InviteRequest.count) %>
<% if AdminSetting.current.invite_from_queue_enabled? %>
<%= ts("We are sending out %{invites} invitations per day.", invites: AdminSetting.current.invite_from_queue_number) %>
<%= t(".send_rate", invites: AdminSetting.current.invite_from_queue_number) %>
<% end %>
</p>
<!--/descriptions-->
Expand All @@ -17,7 +17,9 @@
<p>
<%= label_tag :email %>
<%= text_field_tag :email %>
<%= submit_tag ts("Look me up") %>
<span class="submit actions">
<%= submit_tag t(".search") %>
</span>
</p>
</fieldset>
<% end %>
Expand Down
2 changes: 2 additions & 0 deletions config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ DELIMITER_FOR_OUTPUT: ', '
INVITE_FROM_QUEUE_ENABLED: true
INVITE_FROM_QUEUE_NUMBER: 10
INVITE_FROM_QUEUE_FREQUENCY: 7

HOURS_BEFORE_RESEND_INVITATION: 24
# this is whether or not people without invitations can create accounts
ACCOUNT_CREATION_ENABLED: false
DAYS_TO_PURGE_UNACTIVATED: 7
Expand Down
4 changes: 4 additions & 0 deletions config/locales/controllers/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ en:
invite_requests:
index:
page_title: Invitation Requests
resend:
not_found: Could not find an invitation associated with that email.
not_yet: You cannot resend an invitation that was sent in the last %{count} hours.
success: Invitation resent to %{email}.
kudos:
create:
success: Thank you for leaving kudos!
Expand Down
5 changes: 5 additions & 0 deletions config/locales/models/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ en:
attributes:
user_defined_tags_count:
at_most: must not add up to more than %{count}. You have entered %{value} of these tags, so you must remove %{diff} of them.
invitation:
attributes:
base:
format: "%{message}"
notification_could_not_be_sent: 'Notification email could not be sent: %{error}'
kudo:
attributes:
commentable:
Expand Down
28 changes: 28 additions & 0 deletions config/locales/views/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1400,10 +1400,38 @@ en:
waiting_list_count:
one: There is currently %{count} person on the waiting list.
other: There are currently %{count} people on the waiting list.
invitation:
after_cooldown_period:
not_resent:
one: Because your invitation was sent more than an hour ago, you can have your invitation resent.
other: Because your invitation was sent more than %{count} hours ago, you can have your invitation resent.
resent_html:
one: Because your invitation was resent more than an hour ago, you can have your invitation resent again, or you may want to %{contact_support_link}.
other: Because your invitation was resent more than %{count} hours ago, you can have your invitation resent again, or you may want to %{contact_support_link}.
before_cooldown_period:
one: If it has been more than an hour since you should have received your invitation, but you have not received it after checking your spam folder, you can visit this page to resend the invitation.
other: If it has been more than %{count} hours since you should have received your invitation, but you have not received it after checking your spam folder, you can visit this page to resend the invitation.
contact_support: contact Support
info:
not_resent: Your invitation was emailed to this address on %{sent_at}. If you can't find it, please check your email spam folder as your spam filters may have placed it there.
resent: Your invitation was emailed to this address on %{sent_at} and resent on %{resent_at}. If you can't find it, please check your email spam folder as your spam filters may have placed it there.
resend_button: Resend Invitation
title: Invitation Status for %{email}
invite_request:
date: 'At our current rate, you should receive an invitation on or around: %{date}.'
position_html: You are currently number %{position} on our waiting list!
title: Invitation Status for %{email}
no_invitation:
email_not_found: Sorry, we can't find the email address you entered.
show:
instructions_html: To check on the status of your invitation, go to the %{status_link} and enter your email in the space provided.
status:
heading: Invitation Request Status
search: Look me up
send_rate: We are sending out %{invites} invitations per day.
waiting_list:
one: There is currently %{count} person on the waiting list.
other: There are currently %{count} people on the waiting list.
kudos:
guest_header:
one: "%{count} guest has also left kudos"
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
collection do
get :manage
get :status
post :resend
end
end

Expand Down
7 changes: 7 additions & 0 deletions db/migrate/20231027172035_add_resent_at_to_invitations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class AddResentAtToInvitations < ActiveRecord::Migration[6.1]
uses_departure! if Rails.env.staging? || Rails.env.production?

def change
add_column :invitations, :resent_at, :datetime
end
end
7 changes: 7 additions & 0 deletions db/migrate/20240415202120_add_index_to_invitations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class AddIndexToInvitations < ActiveRecord::Migration[6.1]
uses_departure! if Rails.env.staging? || Rails.env.production?

def change
add_index :invitations, :invitee_email
end
end
30 changes: 28 additions & 2 deletions features/other_a/invite_queue.feature
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Feature: Invite queue management
# check your place in the queue - invalid address
When I check how long "[email protected]" will have to wait in the invite request queue
Then I should see "Invitation Request Status"
And I should see "If you can't find it, your invitation may have already been emailed to that address; please check your email spam folder as your spam filters may have placed it there."
And I should see "Sorry, we can't find the email address you entered."
And I should not see "You are currently number"

# check your place in the queue - correct address
Expand Down Expand Up @@ -98,7 +98,7 @@ Feature: Invite queue management
Then 1 email should be delivered to test@archiveofourown.org
When I check how long "[email protected]" will have to wait in the invite request queue
Then I should see "Invitation Request Status"
And I should see "If you can't find it, your invitation may have already been emailed to that address;"
And I should see "If you can't find it, please check your email spam folder as your spam filters may have placed it there."

# invite can be used
When I am logged in as an admin
Expand Down Expand Up @@ -155,3 +155,29 @@ Feature: Invite queue management
And I fill in "invite_request_email" with "[email protected]"
And I press "Add me to the list"
Then I should see "Email is already being used by an account holder."

Scenario: Users can resend their invitation after enough time has passed
Given account creation is enabled
And the invitation queue is enabled
And account creation requires an invitation
And the invite_from_queue_at is yesterday
And an invitation request for "[email protected]"
When the scheduled check_invite_queue job is run
Then 1 email should be delivered to invitee@example.org

When I check how long "[email protected]" will have to wait in the invite request queue
Then I should see "Invitation Request Status"
And I should see "If you can't find it, please check your email spam folder as your spam filters may have placed it there."
And I should not see "Because your invitation was sent more than 24 hours ago, you can have your invitation resent."
And I should not see "Resend Invitation"

When all emails have been delivered
And it is currently 25 hours from now
And I check how long "[email protected]" will have to wait in the invite request queue
Then I should see "Invitation Request Status"
And I should see "If you can't find it, please check your email spam folder as your spam filters may have placed it there."
And I should see "Because your invitation was sent more than 24 hours ago, you can have your invitation resent."
And I should see "Resend Invitation"

When I press "Resend Invitation"
Then 1 email should be delivered to invitee@example.org
Loading

0 comments on commit 129e6e9

Please sign in to comment.