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

Add SMTP Configuration and Notification Mailers #427

Merged
merged 19 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
17 changes: 10 additions & 7 deletions app/controllers/evaluations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,18 @@ def index
where(evaluator_submission_assignments: {
user_id: current_user.id,
status: [:assigned, :recused]
}).
includes(:challenge, :evaluation_form).
distinct
}).includes(:challenge, :evaluation_form).distinct
end

def submissions
@phase = Phase.joins(:challenge_phases_evaluators).
where(challenge_phases_evaluators: { user_id: current_user.id }).
find(params[:id])
where(challenge_phases_evaluators: { user_id: current_user.id }).find(params[:id])

@challenge = @phase.challenge

@assigned_submissions = @phase.evaluator_submission_assignments.
where(evaluator: current_user).where(status: %i[assigned recused]).
includes(:submission, :evaluation).ordered_by_status
where(evaluator: current_user).where(status: %i[assigned recused]).includes(:submission, :evaluation).
ordered_by_status

@submissions_count = helpers.calculate_submissions_count(@assigned_submissions)
end
Expand Down Expand Up @@ -82,6 +79,8 @@ def recuse
current_user.evaluator_submission_assignments.where(submission_id: params[:submission_id]).first

if EvaluatorRecusalService.new(@evaluator_submission_assignment).call
send_recusal_notification

flash[:notice] = I18n.t("evaluations.recusal.success")
redirect_to submissions_evaluation_path(@evaluator_submission_assignment.phase), status: :see_other
else
Expand Down Expand Up @@ -119,6 +118,10 @@ def build_evaluation
end
end

def send_recusal_notification
NotificationMailer.recusal(@evaluator_submission_assignment).deliver_now
end

# Auth Helpers
def can_access_evaluation?
@evaluator_submission_assignment && @evaluator_submission_assignment.user_id == current_user.id
Expand Down
26 changes: 18 additions & 8 deletions app/controllers/evaluator_submission_assignments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ def create
status: :assigned
)
if @evaluator_submission_assignment.save
NotificationMailer.evaluation_assignment(@evaluator_submission_assignment).deliver_now

redirect_to submission_path(@submission), notice: I18n.t("evaluator_submission_assignments.assigned.success")
else
redirect_to submission_path(@submission), notice: I18n.t("evaluator_submission_assignments.assigned.failure")
Expand Down Expand Up @@ -84,16 +86,15 @@ def update_assignment_status(new_status)
@assignment.update(status: new_status)
end

def send_evaluation_assignment_notification(new_status)
NotificationMailer.evaluation_assignment(@assignment).deliver_now if new_status == :assigned
end

def handle_successful_update(new_status)
send_evaluation_assignment_notification(new_status)

flash[:success] = t("evaluator_submission_assignments.#{new_status}.success")
if request&.referer&.include?("submissions")
redirect_to request.referer
else
respond_to do |format|
format.html { redirect_to_assignment_path }
format.json { render json: { success: true, message: flash[:success] } }
end
end
handle_update_response
end

def handle_failed_update(new_status)
Expand All @@ -110,4 +111,13 @@ def redirect_to_assignment_path
evaluator_id: params[:evaluator_id]
)
end

def handle_update_response
return redirect_to request.referer if request&.referer&.include?("submissions")

respond_to do |format|
format.html { redirect_to_assignment_path }
format.json { render json: { success: true, message: flash[:success] } }
end
end
end
2 changes: 1 addition & 1 deletion app/mailers/application_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
# The main app mailer for sending emails.
# The app does not process inbound messages.
class ApplicationMailer < ActionMailer::Base
default from: "[email protected]"
default from: "[email protected]"
layout "mailer"
end
67 changes: 67 additions & 0 deletions app/mailers/notification_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen_string_literal: true

# Mailer class for sending notification emails related to evaluation invitations,
# evaluation assignments, evaluator role change requests, and recusals
class NotificationMailer < ApplicationMailer
include PhasesHelper
include EvaluationFormsHelper

def evaluation_invitation(invitation)
setup_challenge_attrs(invitation.phase.challenge, invitation.phase)
@user = invitation

mail(
to: invitation.email,
subject: I18n.t('mailers.evaluation_invitation.subject', challenge_title: invitation.phase.challenge.title)
)
end

def role_request(user, challenge, phase)
setup_challenge_attrs(challenge, phase)
@user = user

mail(
to: user.email,
subject: I18n.t('mailers.evaluation_invitation.subject', challenge_title: challenge.title)
)
end

def evaluation_assignment(evaluator_submission_assignment)
setup_assignment_attrs(evaluator_submission_assignment)
@due_date = @submission.phase.end_date.strftime("%m/%d/%Y")

mail(
to: @evaluator.email,
subject: I18n.t('mailers.evaluation_assignment.subject', submission_id: @submission.id)
)
end

def recusal(evaluator_submission_assignment)
setup_assignment_attrs(evaluator_submission_assignment)

mail(
to: @challenge_managers.map(&:email),
subject: I18n.t('mailers.recusal.subject', submission_id: @submission.id)
)
end

private

def setup_challenge_attrs(challenge, phase)
@challenge_phase_title = challenge_phase_title(challenge, phase)
@challenge_managers = challenge.challenge_managers.includes(:user).map(&:user)
attach_logo
end

def setup_assignment_attrs(assignment)
@evaluator = assignment.evaluator
@submission = assignment.submission
setup_challenge_attrs(@submission.challenge, @submission.phase)
@assignment = assignment
end

def attach_logo
logo_path = Rails.public_path.join('platform-assets/images/challenge_gov_logo.png')
attachments.inline['challenge_gov_logo.png'] = File.read(logo_path)
end
end
4 changes: 3 additions & 1 deletion app/services/evaluator_invitation_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ def handle_invitation(email, invitation_params)
existing_invitation ? resend_invitation(existing_invitation) : create_new_invitation(invitation_params)
end

# TODO: Implement sending the invitation email here
def resend_invitation(invitation)
NotificationMailer.evaluation_invitation(invitation).deliver_now

if invitation.update(last_invite_sent: Time.current)
{
success: true,
Expand All @@ -39,6 +40,7 @@ def create_new_invitation(invitation_params)
)
)
if invitation.save
NotificationMailer.evaluation_invitation(invitation).deliver_now
{
success: true,
message: I18n.t(
Expand Down
3 changes: 3 additions & 0 deletions app/services/evaluator_management_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def remove_evaluator(evaluator_type, evaluator_id)
def self.accept_evaluator_invitation(user)
invitations = EvaluatorInvitation.where(email: user.email)
invitations.each do |invite|
user.update(first_name: invite.first_name, last_name: invite.last_name)
ChallengePhasesEvaluator.create(challenge: invite.challenge, phase: invite.phase, user:)
invite.destroy
end
Expand Down Expand Up @@ -77,6 +78,8 @@ def invalid_role(user)
end

def handle_evaluator_role_requested(user)
NotificationMailer.role_request(user, @challenge, @phase).deliver_now

user.update!(status: 'evaluator_role_requested')
ChallengePhasesEvaluator.find_or_create_by(challenge: @challenge, phase: @phase, user:)
{
Expand Down
56 changes: 53 additions & 3 deletions app/views/layouts/mailer.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,61 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
/* Email styles need to be inline */
.mailer-button {
background-color: #1A4480;
border: 0;
border-radius: 0.25rem;
color: #ffffff;
cursor: pointer;
display: inline-block;
font-family: Source Sans Pro Web, Helvetica Neue, Helvetica, Roboto, Arial, sans-serif;
font-size: 1.06rem;
font-weight: 600;
line-height: 1;
padding: 0.75rem 2.5rem;
text-align: center;
text-decoration: none;
margin-top: 3rem;
}

.mailer-header-logo {
width: 100%;
max-width: 100%;
height: auto;
display: block;
margin: 0;
padding: 0;
}

.mailer-content {
padding-left: 40px;
padding-right: 40px;
font-family: Source Sans Pro Web, Helvetica Neue, Helvetica, Roboto, Arial, sans-serif;
}

.button-container {
text-align: center;
width: 100%;
}
</style>
</head>

<body>
<%= yield %>
<div class="mailer-header-logo">
<%= image_tag attachments['challenge_gov_logo.png'].url, style: 'width: 100%; max-width: 100%; height: auto;' if attachments['challenge_gov_logo.png'] %>
</div>
<div class="mailer-content">
<%= yield %>
</div>
<div class="button-container">
<% unless action_name == 'role_request' %>
<div class="button-container">
<%= link_to "Login to Challenge.gov",
"#{Rails.env.development? ? 'http' : 'https'}://#{Rails.configuration.action_mailer.default_url_options[:host]}",
class: "mailer-button",
target: "_blank",
rel: "noopener noreferrer" %>
</div>
<% end %>
</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<% if @challenge_managers.length > 1 %>
If you have any questions you may reach out to any of the challenge managers:
<% @challenge_managers.each do |cm| %>
<%= "#{cm.first_name} #{cm.last_name}" %> at <%= "#{cm.email}" %>,
<% end %>
<% else %>
If you have any questions you may reach out to the challenge manager <%= "#{@challenge_managers.first.first_name} #{@challenge_managers.first.last_name}" %> at <%= "#{@challenge_managers.first.email}" %>
<% end %>
or to [email protected].
11 changes: 11 additions & 0 deletions app/views/notification_mailer/evaluation_assignment.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<h2><strong>You have been assigned to evaluate a submission.</strong></h2>

A challenge manager assigned you to evaluate <%= link_to "submission #{@submission.id}", new_submission_evaluation_url(@submission), target: "_blank" %> for <%= @challenge_phase_title %>. Evaluations are due by <%= @due_date %>.

<br/><br/>

<strong>Please login to Challenge.gov via Login.gov to review and evaluate submissions assigned to you.</strong>

<br/><br/>

<%= render 'challenge_managers_contact' %>
12 changes: 12 additions & 0 deletions app/views/notification_mailer/evaluation_invitation.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<h2><strong>You have been invited to <%= @challenge_phase_title %> Evaluation Panel.</strong></h2>

A challenge manager invited you to participate in an evaluation process for <%= @challenge_phase_title %>.
<strong>To participate in the submission evaluation process, please create a Challenge.gov account via Login.gov.</strong>

<br/><br/>

After you complete the account creation step, we will send you a separate email letting you know once a challenge manager assigns submissions for you to evaluate.

<br/><br/>

<%= render 'challenge_managers_contact' %>
11 changes: 11 additions & 0 deletions app/views/notification_mailer/recusal.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<h2><strong>An evaluator recused from evaluating a submission you assigned them to.</strong></h2>

An evaluator <%= "#{@evaluator.email}" %> recused themselves from evaluating <%= link_to "submission #{@submission.id}", submission_url(@submission), target: "_blank" %> for <%= @challenge_phase_title %>.

<br/><br/>

<strong>Please login to Challenge.gov via Login.gov to unassign the recused evaluator and assign another evaluator.</strong>

<br/><br/>

<%= render 'challenge_managers_contact' %>
12 changes: 12 additions & 0 deletions app/views/notification_mailer/role_request.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<h2><strong>You have been invited to <%= @challenge_phase_title %> Evaluation Panel.</strong></h2>

A challenge manager invited you to participate in an evaluation process for <%= @challenge_phase_title %>. You currently have a Challenge.gov account as a <%= @user.role %>.
<strong>To participate in the submission evaluation process, please respond to this email to request your role to be changed to an evaluator.</strong>

<br/><br/>

After your Challenge.gov account is updated, we will send you a separate email letting you know once a challenge manager assigns submissions for you to evaluate.

<br/><br/>

<%= render 'challenge_managers_contact' %>
12 changes: 12 additions & 0 deletions config/environments/dev.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@

config.action_mailer.perform_caching = false

config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true

config.action_mailer.smtp_settings = {
domain: ENV.fetch('HOST'),
address: ENV.fetch('SMTP_SERVER'),
port: ENV.fetch('SMTP_PORT', 587).to_i
}

config.action_mailer.default_url_options = { host: ENV.fetch('HOST', 'challenge-dev.app.cloud.gov') }

# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
Expand Down
10 changes: 9 additions & 1 deletion config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false

# Store emails locally for development
config.action_mailer.delivery_method = :file
config.action_mailer.file_settings = {
location: Rails.root.join('tmp/mails')
}
config.action_mailer.perform_deliveries = true
config.action_mailer.default_url_options = { host: "localhost:#{ENV.fetch('PORT', 3000)}" }

config.action_mailer.perform_caching = false

# Print deprecation notices to the Rails logger.
Expand Down Expand Up @@ -84,4 +92,4 @@

# Raise error when a before_action's only/except options reference missing actions
config.action_controller.raise_on_missing_callback_actions = true
end
end
12 changes: 12 additions & 0 deletions config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@

config.action_mailer.perform_caching = false

config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = false

config.action_mailer.smtp_settings = {
domain: ENV.fetch('HOST'),
address: ENV.fetch('SMTP_SERVER'),
port: ENV.fetch('SMTP_PORT', 587).to_i
}

config.action_mailer.default_url_options = { host: ENV.fetch('HOST', 'challenge.gov') }

# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
Expand Down
Loading