Skip to content

Commit

Permalink
dm-4297 innovation editor batch email (#757)
Browse files Browse the repository at this point in the history
* add `send_email_to_editor` method in `AdminMailer`

* add `send_email_to_all_editors` and `collect_users_and_their_practices_info` model methods to `Practice`

* edit `collect_users_and_their_practices_info` method

* add "Send Email to All Editors" button in index action and `send_email_to_all_editors_form` action in `admin/practices`

* add css for `admin-email-all-practice-editors` action

* add view for `Send Email to All Practice Editors` form

* add view for "Send Email to Practice Editor" email template

* add config.action_mailer.asset_host to `production.rb` and `development.rb`

* add dm-footer-logo.png to assets/images

* add current_user to `Practice#send_email_to_all_editors` call

filters collected practices data to `current_user` if running from deployed DEV or STG applications

* update image footer in `send_email_to_editor` email view

* add mailer asset host to dev and prod config

* add specs and factory for practice batch email feature

* add tinymce editor to "Send Email to All Practice Editors" form

* add spacing below "Send Email" button in "Send Email to All Practice Editors"

* edit `send_email_to_editor` form, adds `html_safe` to message

* add margin to `admin-email-all-practice-editors` button css

* spacing

* edit text in `send_email_to_editor.html.erb`

* edit `send_email_to_all_editors_form.html.erb`

Adds popup modal that previews the email before sending

* update `send_email_to_all_editors_form.html.erb` to use uswds modal

* edit batch email confirmation text

* edit `send_email_to_all_editors_form.html.erb`

edits script logic, adds pop up modal with preview, breaks into individual files

* edit `send_email_to_editor.hmtl.erb`

edits header tag, text, adds reply email at bottom

* update batch email spec

* spacing

* edit `Practice#collect_users_and_their_practices_info`

adds check to make sure a practice is published before being included in batch email

* edit batch email feature test

* edit batch email js to alter element text via `textContent` as per codeQL warning

* update practice model spec

* update practice model spec

* edit batch email template

edits image tag in footer to add host

* spacing

* revert change to `batch_email` js

change `textContent` to `innerHTML`

* edit `:send_email_to_all_editors` collection action

calls `sanitize` on email params

* spacing

* update tinyMCE options for batch email input

removes unnecessary options

* edit `Practice#collect_users_and_their_practices_info`

adds comment explaining guard clause that checks environment

edit block var name
  • Loading branch information
PhilipDeFraties authored Jan 23, 2024
1 parent 690fe71 commit 11dac2a
Show file tree
Hide file tree
Showing 14 changed files with 347 additions and 0 deletions.
16 changes: 16 additions & 0 deletions app/admin/practices.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@
end
end

collection_action :send_email_to_all_editors_form, method: :get do
render 'send_email_to_all_editors_form'
end

collection_action :send_email_to_all_editors, method: :post do
subject = ActionController::Base.helpers.sanitize(params[:email][:subject])
message = ActionController::Base.helpers.sanitize(params[:email][:message])

Practice.send_email_to_all_editors(subject, message, current_user)

redirect_to admin_practices_path, notice: "Your batch email to Innovation editors has been sent."
end

collection_action :export_published_practices_with_queri_format, method: :get do
practices = Practice.published.sort_a_to_z

Expand Down Expand Up @@ -87,6 +100,9 @@
div do
link_to 'Published Practices QUERI Download', export_published_practices_with_queri_format_admin_practices_path, class: 'admin-download-published-practices float-right display-block text-bold border-0 radius-md margin-bottom-105'
end
div do
link_to "Send Email to All Editors", send_email_to_all_editors_form_admin_practices_path, class: 'admin-email-all-practice-editors float-right display-block text-bold border-0 radius-md margin-bottom-105'
end
selectable_column unless params[:scope] == "get_practice_owner_emails"
id_column unless params[:scope] == "get_practice_owner_emails"
column 'Practice Name', :name
Expand Down
Binary file added app/assets/images/dm-footer-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 51 additions & 0 deletions app/assets/javascripts/batch_email.es6
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
document.addEventListener('DOMContentLoaded', function() {
let textArea = document.getElementById('emailMessage');
let editorInitialized = false;

function initTinyMCE() {
if (editorInitialized) return;
tinymce.init({
selector: 'textarea#emailMessage',
menubar: false,
plugins: ["link", "lists"],
toolbar:
'undo redo | bold italic | link bullist numlist superscript subscript | removeformat',
link_title: false,
link_assume_external_targets: false,
paste_data_images: false,
images_upload_url: '',
automatic_uploads: false,
file_picker_types: '',
});
editorInitialized = true;
}

textArea.addEventListener('focus', initTinyMCE);

document.getElementById('previewEmailButton').addEventListener('click', function(event) {
event.preventDefault();

if (editorInitialized) {
tinymce.remove('#emailMessage');
editorInitialized = false;
}

document.getElementById('previewSubject').innerHTML = "Subject: " + document.getElementById('emailSubject').value
document.getElementById('previewMessage').innerHTML = textArea.value;

document.getElementById('previewModal').style.display = 'block';
});

let previewModal = document.getElementById('previewModal');
previewModal.querySelector('[data-close-modal]').addEventListener('click', function() {
previewModal.style.display = 'none';
});

window.confirmSendEmail = function() {
let sendEmail = confirm("This email will be sent to all Innovation editors, are you sure?");
if (sendEmail) {
document.getElementById('emailForm').submit();
}
previewModal.style.display = 'none';
};
});
10 changes: 10 additions & 0 deletions app/assets/stylesheets/active_admin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,16 @@
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 1.0em;
}

.admin-email-all-practice-editors {
text-decoration: none !important;
background-color: #5ea3d3;
padding: 10px 20px;
color: #ffffff !important;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 1.0em;
margin-right: 2px;
}
}

.admin_site_metrics {
Expand Down
13 changes: 13 additions & 0 deletions app/mailers/admin_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,17 @@ def send_set_password(options = {})

mail(to: @user.email, subject: subject)
end

def send_email_to_editor(mailer_args)
@subject = mailer_args[:subject]
@message = mailer_args[:message]

user_info = mailer_args[:user_info]
@user_name = user_info[:user_name] || "Innovation Editor"
@user_email = user_info[:email]

@practices = mailer_args[:practices]

mail(to: user_info[:email], subject: @subject)
end
end
61 changes: 61 additions & 0 deletions app/models/practice.rb
Original file line number Diff line number Diff line change
Expand Up @@ -471,4 +471,65 @@ def as_json(*)
def self.ransackable_attributes(auth_object = nil)
["name", "support_network_email", "user_email"]
end

def self.send_email_to_all_editors(subject, message, current_user)
user_practices_data = collect_users_and_their_practices_info(current_user)

mailer_args = {
subject: subject,
message: message,
}

user_practices_data.each do |user_data|
mailer_args[:user_info] = user_data[:user_info]
mailer_args[:practices] = user_data[:practices]

AdminMailer.send_email_to_editor(
mailer_args
).deliver_now
end
end

def self.collect_users_and_their_practices_info(current_user)
user_practices = {}

includes(:user).each do |practice|
if practice.user.present? && practice.published?
user_practices[practice.user] ||= Set.new
user_practices[practice.user] << practice
end
end

PracticeEditor.includes(:user, :practice).each do |editor|
if editor.practice.published?
user_practices[editor.user] ||= Set.new
user_practices[editor.user] << editor.practice
end
end

host_options = Rails.application.config.action_mailer.default_url_options

# disallow bulk emails from being sent from deployed STG and DEV apps, should only send the
# email to the current_user if listed as `user` or `practice_editor` of a practice
unless Rails.env.test? || Rails.env.development? || ENV['PROD_SERVERNAME'] == 'PROD'
user_practices = user_practices.select do |user_practice|
user_practice == current_user
end
end

user_practices.map do |user, practices|
{
user_info: {
user_name: user.first_name,
email: user.email
},
practices: practices.map do |practice|
{
practice_name: practice.name,
show_url: Rails.application.routes.url_helpers.practice_url(practice, host_options),
}
end
}
end
end
end
25 changes: 25 additions & 0 deletions app/views/admin/practices/_email_preview_modal.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!-- The contents of the preview modal matches the email template: send_email_to_editor.html.erb -->
<div class="usa-modal-wrapper" id="previewModal" hidden>
<div class="usa-modal-overlay"></div>
<div class="usa-modal">
<div class="usa-modal__main">
<div class="usa-modal-header">
<h3 class="usa-modal-heading" id="previewSubject">Email Preview</h3>
</div>
<p>Hello [User Name],</p>
<p id="previewMessage"></p>
<p>Visit your Innovation(s) at the following link(s) and click "Edit" to begin editing. Please note that only one person can edit the innovation at a time.</p>
<ul>
<li><a href="#">Sample Innovation Link</a></li>
</ul>
<p>
If you have any questions, issues, or feedback to share with us please send an email to <%= mail_to '[email protected] ', '[email protected] ' %>
</p>
<div class="usa-modal-footer">
<%= image_tag('dm-footer-logo.png', alt: "Footer Logo") %>
<button class="usa-button" data-close-modal>Cancel</button>
<button onclick="confirmSendEmail()" class="usa-button">Send Email</button>
</div>
</div>
</div>
</div>
17 changes: 17 additions & 0 deletions app/views/admin/practices/send_email_to_all_editors_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<%= javascript_include_tag 'batch_email' %>

<%= form_for :email, url: send_email_to_all_editors_admin_practices_path, method: :post, html: { id: 'emailForm' } do |f| %>
<div>
<%= f.label :subject, "Email Subject" %><br>
<%= f.text_field :subject, id: 'emailSubject' %>
</div>
<div>
<%= f.label :message, "Message" %><br>
<%= f.text_area :message, id: 'emailMessage' %>
</div>
<div class="margin-top-4">
<button type="button" class="usa-button" id="previewEmailButton">Preview Email</button>
</div>
<% end %>

<%= render 'admin/practices/email_preview_modal' %>
26 changes: 26 additions & 0 deletions app/views/admin_mailer/send_email_to_editor.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body>
<p>Hello<%= ' ' + @user_name %>, </p>
<p>
<%= @message.html_safe %>
</p>
<p>Visit your Innovation(s) at the following link(s) and click "Edit" to begin editing. Please note that only one person can edit the innovation at a time.</p>
<ul>
<% @practices.each do |practice| %>
<li>
<%= link_to "#{practice[:practice_name]}", practice[:show_url] %>
</li>
<% end %>
</ul>
<p>
If you have any questions, issues, or feedback to share with us please send an email to <%= mail_to '[email protected] ', '[email protected] ' %>
</p>
</body>
<footer>
<%= image_tag('dm-footer-logo.png', alt: "Diffusion Marketplace Logo", host: "https://marketplace.va.gov") %>
</footer>
</html>
1 change: 1 addition & 0 deletions config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_deliveries = true
config.action_mailer.asset_host = ENV.fetch('HOSTNAME')

# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
Expand Down
1 change: 1 addition & 0 deletions config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
config.action_mailer.smtp_settings = mail_settings

config.action_mailer.default_url_options = { host: ENV.fetch('HOSTNAME') }
config.action_mailer.asset_host = ENV.fetch('HOSTNAME')

# Inserts middleware to perform automatic connection switching.
# The `database_selector` hash is used to pass options to the DatabaseSelector
Expand Down
10 changes: 10 additions & 0 deletions spec/factories/practice_editors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FactoryBot.define do
factory :practice_editor do
association :practice
association :user

after(:build) do |practice_editor|
practice_editor.email = practice_editor.user.email
end
end
end
55 changes: 55 additions & 0 deletions spec/features/admin/email_all_editors_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
require 'rails_helper'

describe 'Admin email all editors button', type: :feature, js: true do
let(:admin) { create(:user, :admin) }
let!(:users) { create_list(:user, 3) }
let!(:practices) { create_list(:practice, 3, user: admin, published: true) }

before do
ActionMailer::Base.deliveries.clear

practices.each do |practice|
create(:practice_editor, user: users.first, practice: practice)
end

users.each_with_index do |user, index|
create(:practice_editor, user: user, practice: practices[index])
end

login_as(admin, scope: :user, run_callbacks: false)
visit admin_practices_path
end

it 'sends an email to all editors when the button is clicked' do
expect(page).to have_content('Published Practices QUERI Download')

click_link 'Send Email to All Editors'

fill_in 'emailSubject', with: 'Important Update'
find('#emailMessage').click


execute_script("tinyMCE.get('emailMessage').setContent('Please review the latest changes.')")

click_button 'Preview Email'

within('#previewModal') do
click_button 'Send Email'
page.driver.browser.switch_to.alert.accept
end

expect(page).to have_current_path(admin_practices_path)
expect(page).to have_content('Your batch email to Innovation editors has been sent.')

(users + [admin]).each do |user|
email = ActionMailer::Base.deliveries.select { |e| e.to.include?(user.email) }.first
expect(email).not_to be_nil
expect(email.subject).to eq 'Important Update'

user_practices = user == admin ? practices : PracticeEditor.where(user: user).map(&:practice)
user_practices.each do |practice|
expect(email.body.encoded).to include practice_path(practice)
end
end
end
end
Loading

0 comments on commit 11dac2a

Please sign in to comment.