Merge pull request #2474 from consul/admin_newsletter_email_refactor

Admin newsletter email refactor
This commit is contained in:
Alberto Calderón Queimadelos
2018-02-21 13:03:35 +01:00
committed by GitHub
20 changed files with 164 additions and 81 deletions

View File

@@ -13,6 +13,6 @@ class Admin::EmailsDownloadController < Admin::BaseController
private private
def users_segment_emails_csv(users_segment) def users_segment_emails_csv(users_segment)
UserSegments.send(users_segment).pluck(:email).to_csv UserSegments.send(users_segment).newsletter.pluck(:email).to_csv
end end
end end

View File

@@ -46,18 +46,21 @@ class Admin::NewslettersController < Admin::BaseController
def deliver def deliver
@newsletter = Newsletter.find(params[:id]) @newsletter = Newsletter.find(params[:id])
if @newsletter.valid?
Mailer.newsletter(@newsletter).deliver_later Mailer.newsletter(@newsletter).deliver_later
@newsletter.update(sent_at: Time.current) @newsletter.update(sent_at: Time.current)
flash[:notice] = t("admin.newsletters.send_success")
else
flash[:error] = t("admin.segment_recipient.invalid_recipients_segment")
end
redirect_to [:admin, @newsletter], notice: t("admin.newsletters.send_success") redirect_to [:admin, @newsletter]
end end
private private
def newsletter_params def newsletter_params
newsletter_params = params.require(:newsletter) params.require(:newsletter).permit(:subject, :segment_recipient, :from, :body)
.permit(:subject, :segment_recipient, :from, :body)
newsletter_params.merge(segment_recipient: newsletter_params[:segment_recipient].to_i)
end end
end end

View File

@@ -0,0 +1,15 @@
module UserSegmentsHelper
def user_segments_options
UserSegments::SEGMENTS.collect do |user_segment_name|
[t("admin.segment_recipient.#{user_segment_name}"), user_segment_name]
end
end
def segment_name(user_segment)
if user_segment && UserSegments.respond_to?(user_segment)
I18n.t("admin.segment_recipient.#{user_segment}")
else
I18n.t("admin.segment_recipient.invalid_recipients_segment")
end
end
end

View File

@@ -1,23 +1,28 @@
class Newsletter < ActiveRecord::Base class Newsletter < ActiveRecord::Base
enum segment_recipient: { all_users: 1,
proposal_authors: 2,
investment_authors: 3,
feasible_and_undecided_investment_authors: 4,
selected_investment_authors: 5,
winner_investment_authors: 6 }
validates :subject, presence: true validates :subject, presence: true
validates :segment_recipient, presence: true validates :segment_recipient, presence: true
validates :from, presence: true validates :from, presence: true
validates :body, presence: true validates :body, presence: true
validate :validate_segment_recipient
validates_format_of :from, :with => /@/ validates_format_of :from, :with => /@/
def list_of_recipients def list_of_recipients
UserSegments.send(segment_recipient) UserSegments.send(segment_recipient).newsletter if valid_segment_recipient?
end
def valid_segment_recipient?
segment_recipient && UserSegments.respond_to?(segment_recipient)
end end
def draft? def draft?
sent_at.nil? sent_at.nil?
end end
private
def validate_segment_recipient
errors.add(:segment_recipient, :invalid) unless valid_segment_recipient?
end
end end

View File

@@ -10,8 +10,7 @@
<%= t('admin.emails_download.index.download_segment_help_text') %> <%= t('admin.emails_download.index.download_segment_help_text') %>
</p> </p>
<%= select_tag :users_segment, options_for_select(UserSegments::SEGMENTS <%= select_tag :users_segment, options_for_select(user_segments_options) %>
.collect { |s| [t("admin.segment_recipient.#{s}"), s] }) %>
<div class="margin-top"> <div class="margin-top">
<%= submit_tag t('admin.emails_download.index.download_emails_button'), class: "button" %> <%= submit_tag t('admin.emails_download.index.download_emails_button'), class: "button" %>

View File

@@ -1,8 +1,7 @@
<%= form_for [:admin, @newsletter] do |f| %> <%= form_for [:admin, @newsletter] do |f| %>
<%= render 'shared/errors', resource: @newsletter %> <%= render 'shared/errors', resource: @newsletter %>
<%= f.select :segment_recipient, options_for_select(Newsletter.segment_recipients <%= f.select :segment_recipient, options_for_select(user_segments_options,
.collect { |k,v| [t("admin.segment_recipient.#{k}"), v] },
@newsletter[:segment_recipient]) %> @newsletter[:segment_recipient]) %>
<%= f.text_field :subject %> <%= f.text_field :subject %>
<%= f.text_field :from %> <%= f.text_field :from %>

View File

@@ -19,7 +19,7 @@
<%= newsletter.subject %> <%= newsletter.subject %>
</td> </td>
<td> <td>
<%= t("admin.segment_recipient.#{newsletter.segment_recipient}") %> <%= segment_name(newsletter.segment_recipient) %>
</td> </td>
<td> <td>
<% if newsletter.draft? %> <% if newsletter.draft? %>

View File

@@ -2,7 +2,7 @@
<h2><%= t("admin.newsletters.show.title") %></h2> <h2><%= t("admin.newsletters.show.title") %></h2>
<%- recipients_count = @newsletter.list_of_recipients.count %> <% recipients_count = @newsletter.valid_segment_recipient? ? @newsletter.list_of_recipients.count : 0 %>
<div class="small-12 column"> <div class="small-12 column">
<div class="callout highlight"> <div class="callout highlight">
@@ -27,7 +27,7 @@
<div class="row"> <div class="row">
<div class="small-12 column"> <div class="small-12 column">
<strong><%= t("admin.newsletters.show.segment_recipient") %></strong><br> <strong><%= t("admin.newsletters.show.segment_recipient") %></strong><br>
<%= t("admin.segment_recipient.#{@newsletter.segment_recipient}") %> <%= segment_name(@newsletter.segment_recipient) %>
<%= t("admin.newsletters.show.affected_users", n: recipients_count) %> <%= t("admin.newsletters.show.affected_users", n: recipients_count) %>
</div> </div>
</div> </div>
@@ -42,8 +42,9 @@
</div> </div>
</div> </div>
<% if @newsletter.draft? %> <% if @newsletter.draft? && @newsletter.valid_segment_recipient? %>
<%= link_to t("admin.newsletters.show.send"), deliver_admin_newsletter_path(@newsletter), <%= link_to t("admin.newsletters.show.send"),
deliver_admin_newsletter_path(@newsletter),
"data-alert": t("admin.newsletters.show.send_alert", n: recipients_count), "data-alert": t("admin.newsletters.show.send_alert", n: recipients_count),
method: :post, method: :post,
id: "js-send-newsletter-alert", id: "js-send-newsletter-alert",

View File

@@ -261,6 +261,10 @@ en:
attachment: attachment:
min_image_width: "Image Width must be at least %{required_min_width}px" min_image_width: "Image Width must be at least %{required_min_width}px"
min_image_height: "Image Height must be at least %{required_min_height}px" min_image_height: "Image Height must be at least %{required_min_height}px"
newsletter:
attributes:
segment_recipient:
invalid: "The user recipients segment is invalid"
map_location: map_location:
attributes: attributes:
map: map:

View File

@@ -523,6 +523,7 @@ en:
feasible_and_undecided_investment_authors: Authors of feasible or undecided investments in the current budget feasible_and_undecided_investment_authors: Authors of feasible or undecided investments in the current budget
selected_investment_authors: Authors of selected investments in the current budget selected_investment_authors: Authors of selected investments in the current budget
winner_investment_authors: Authors of winner investments in the current budget winner_investment_authors: Authors of winner investments in the current budget
invalid_recipients_segment: "Recipients user segment is invalid"
newsletters: newsletters:
create_success: Newsletter created successfully create_success: Newsletter created successfully
update_success: Newsletter updated successfully update_success: Newsletter updated successfully

View File

@@ -257,6 +257,10 @@ es:
attachment: attachment:
min_image_width: "La imagen debe tener al menos %{required_min_width}px de largo" min_image_width: "La imagen debe tener al menos %{required_min_width}px de largo"
min_image_height: "La imagen debe tener al menos %{required_min_height}px de alto" min_image_height: "La imagen debe tener al menos %{required_min_height}px de alto"
newsletter:
attributes:
segment_recipient:
invalid: "El segmento de usuarios es inválido"
map_location: map_location:
attributes: attributes:
map: map:

View File

@@ -522,6 +522,7 @@ es:
feasible_and_undecided_investment_authors: Usuarios autores de proyectos de gasto viables o sin decidir en los actuales presupuestos feasible_and_undecided_investment_authors: Usuarios autores de proyectos de gasto viables o sin decidir en los actuales presupuestos
selected_investment_authors: Usuarios autores de proyectos de gasto seleccionadas en los actuales presupuestos selected_investment_authors: Usuarios autores de proyectos de gasto seleccionadas en los actuales presupuestos
winner_investment_authors: Usuarios autores de proyectos de gasto ganadoras en los actuales presupuestos winner_investment_authors: Usuarios autores de proyectos de gasto ganadoras en los actuales presupuestos
invalid_recipients_segment: "El segmento de destinatarios es inválido"
newsletters: newsletters:
create_success: Newsletter creada correctamente create_success: Newsletter creada correctamente
update_success: Newsletter actualizada correctamente update_success: Newsletter actualizada correctamente

View File

@@ -1,14 +1,20 @@
section "Creating Newsletters" do section "Creating Newsletters" do
newsletter_body = [ newsletter_body = [
"We choose to go to the moon in this decade and do the other things, not because they are easy, but because they are hard, because that goal will serve to organize and measure the best of our energies and skills, because that challenge is one that we are willing to accept, one we are unwilling to postpone, and one which we intend to win.", "We choose to go to the moon in this decade and do the other things, not because they are easy"\
"Spaceflights cannot be stopped. This is not the work of any one man or even a group of men. It is a historical process which mankind is carrying out in accordance with the natural laws of human development.", ", but because they are hard, because that goal will serve to organize and measure the best of"\
"Many say exploration is part of our destiny, but its actually our duty to future generations and their quest to ensure the survival of the human species." " our energies and skills, because that challenge is one that we are willing to accept, one we"\
" are unwilling to postpone, and one which we intend to win.",
"Spaceflights cannot be stopped. This is not the work of any one man or even a group of men."\
" It is a historical process which mankind is carrying out in accordance with the natural laws"\
" of human development.",
"Many say exploration is part of our destiny, but its actually our duty to future generations"\
" and their quest to ensure the survival of the human species."
] ]
5.times do |n| 5.times do |n|
Newsletter.create!( Newsletter.create!(
subject: "Newsletter subject #{n}", subject: "Newsletter subject #{n}",
segment_recipient: Newsletter.segment_recipients.values.sample, segment_recipient: UserSegments::SEGMENTS.sample,
from: 'no-reply@consul.dev', from: 'no-reply@consul.dev',
body: newsletter_body.sample, body: newsletter_body.sample,
sent_at: [Time.now, nil].sample sent_at: [Time.now, nil].sample

View File

@@ -0,0 +1,5 @@
class ChangeNewsletterSegmentRecipientToString < ActiveRecord::Migration
def change
change_column :newsletters, :segment_recipient, :string, null: false
end
end

View File

@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180205170054) do ActiveRecord::Schema.define(version: 20180220211105) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@@ -626,7 +626,7 @@ ActiveRecord::Schema.define(version: 20180205170054) do
create_table "newsletters", force: :cascade do |t| create_table "newsletters", force: :cascade do |t|
t.string "subject" t.string "subject"
t.integer "segment_recipient" t.string "segment_recipient", null: false
t.string "from" t.string "from"
t.text "body" t.text "body"
t.date "sent_at" t.date "sent_at"

View File

@@ -7,34 +7,28 @@ class UserSegments
winner_investment_authors) winner_investment_authors)
def self.all_users def self.all_users
User.newsletter.active User.active
end end
def self.proposal_authors def self.proposal_authors
author_ids = Proposal.not_archived.not_retired.pluck(:author_id).uniq author_ids(Proposal.not_archived.not_retired.pluck(:author_id).uniq)
author_ids(author_ids)
end end
def self.investment_authors def self.investment_authors
author_ids = current_budget_investments.pluck(:author_id).uniq author_ids(current_budget_investments.pluck(:author_id).uniq)
author_ids(author_ids)
end end
def self.feasible_and_undecided_investment_authors def self.feasible_and_undecided_investment_authors
author_ids = current_budget_investments.where(feasibility: %w(feasible undecided)) feasibility = %w(feasible undecided)
.pluck(:author_id).uniq author_ids(current_budget_investments.where(feasibility: feasibility).pluck(:author_id).uniq)
author_ids(author_ids)
end end
def self.selected_investment_authors def self.selected_investment_authors
author_ids = current_budget_investments.selected.pluck(:author_id).uniq author_ids(current_budget_investments.selected.pluck(:author_id).uniq)
author_ids(author_ids)
end end
def self.winner_investment_authors def self.winner_investment_authors
author_ids = current_budget_investments.winners.pluck(:author_id).uniq author_ids(current_budget_investments.winners.pluck(:author_id).uniq)
author_ids(author_ids)
end end
private private

View File

@@ -945,7 +945,7 @@ LOREM_IPSUM
factory :newsletter do factory :newsletter do
sequence(:subject) { |n| "Subject #{n}" } sequence(:subject) { |n| "Subject #{n}" }
segment_recipient [1, 2, 3, 4, 5, 6].sample segment_recipient UserSegments::SEGMENTS.sample
sequence(:from) { |n| "noreply#{n}@consul.dev" } sequence(:from) { |n| "noreply#{n}@consul.dev" }
sequence(:body) { |n| "Body #{n}" } sequence(:body) { |n| "Body #{n}" }
end end

View File

@@ -8,9 +8,10 @@ feature "Admin newsletter emails" do
create(:budget) create(:budget)
end end
scenario "Show" do context "Show" do
scenario "Valid newsletter" do
newsletter = create(:newsletter, subject: "This is a subject", newsletter = create(:newsletter, subject: "This is a subject",
segment_recipient: 1, segment_recipient: 'all_users',
from: "no-reply@consul.dev", from: "no-reply@consul.dev",
body: "This is a body") body: "This is a body")
@@ -22,7 +23,18 @@ feature "Admin newsletter emails" do
expect(page).to have_content "This is a body" expect(page).to have_content "This is a body"
end end
scenario "Index" do scenario "Invalid newsletter" do
invalid_newsletter = create(:newsletter)
invalid_newsletter.update_attribute(:segment_recipient, 'invalid_segment')
visit admin_newsletter_path(invalid_newsletter)
expect(page).to have_content("Recipients user segment is invalid")
end
end
context "Index" do
scenario "Valid newsletters" do
3.times { create(:newsletter) } 3.times { create(:newsletter) }
visit admin_newsletters_path visit admin_newsletters_path
@@ -30,13 +42,24 @@ feature "Admin newsletter emails" do
expect(page).to have_css(".newsletter", count: 3) expect(page).to have_css(".newsletter", count: 3)
Newsletter.all.each do |newsletter| Newsletter.all.each do |newsletter|
segment_recipient = I18n.t("admin.segment_recipient.#{newsletter.segment_recipient}")
within("#newsletter_#{newsletter.id}") do within("#newsletter_#{newsletter.id}") do
expect(page).to have_content newsletter.subject expect(page).to have_content newsletter.subject
expect(page).to have_content I18n.t("admin.segment_recipient.#{newsletter.segment_recipient}") expect(page).to have_content segment_recipient
end end
end end
end end
scenario "Invalid newsletter" do
invalid_newsletter = create(:newsletter)
invalid_newsletter.update_attribute(:segment_recipient, 'invalid_segment')
visit admin_newsletters_path
expect(page).to have_content("Recipients user segment is invalid")
end
end
scenario "Create" do scenario "Create" do
visit admin_newsletters_path visit admin_newsletters_path
click_link "New newsletter" click_link "New newsletter"
@@ -103,7 +126,8 @@ feature "Admin newsletter emails" do
expect(page).to have_content error_message expect(page).to have_content error_message
end end
scenario "Send newsletter email", :js do context "Send newsletter", :js do
scenario "Sends newsletter emails", :js do
newsletter = create(:newsletter) newsletter = create(:newsletter)
visit admin_newsletter_path(newsletter) visit admin_newsletter_path(newsletter)
@@ -115,15 +139,23 @@ feature "Admin newsletter emails" do
expect(page).to have_content "Newsletter sent successfully" expect(page).to have_content "Newsletter sent successfully"
end end
scenario "Invalid newsletter cannot be sent", :js do
invalid_newsletter = create(:newsletter)
invalid_newsletter.update_attribute(:segment_recipient, 'invalid_segment')
visit admin_newsletter_path(invalid_newsletter)
expect(page).not_to have_link("Send")
end
end
scenario "Select list of users to send newsletter" do scenario "Select list of users to send newsletter" do
Newsletter.segment_recipients.each_key do |user_group| UserSegments::SEGMENTS.each do |user_segment|
visit new_admin_newsletter_path visit new_admin_newsletter_path
fill_in_newsletter_form fill_in_newsletter_form(segment_recipient: I18n.t("admin.segment_recipient.#{user_segment}"))
select I18n.t("admin.segment_recipient.#{user_group}"), from: 'newsletter_segment_recipient'
click_button "Create Newsletter" click_button "Create Newsletter"
expect(page).to have_content(I18n.t("admin.segment_recipient.#{user_group}")) expect(page).to have_content(I18n.t("admin.segment_recipient.#{user_segment}"))
end end
end end
end end

View File

@@ -6,15 +6,11 @@ describe UserSegments do
let(:user3) { create(:user) } let(:user3) { create(:user) }
describe "#all_users" do describe "#all_users" do
it "returns all active users with newsletter enabled" do it "returns all active users enabled" do
active_user1 = create(:user, newsletter: true) active_user = create(:user)
active_user2 = create(:user, newsletter: true)
active_user3 = create(:user, newsletter: false)
erased_user = create(:user, erased_at: Time.current) erased_user = create(:user, erased_at: Time.current)
expect(described_class.all_users).to include active_user1 expect(described_class.all_users).to include active_user
expect(described_class.all_users).to include active_user2
expect(described_class.all_users).not_to include active_user3
expect(described_class.all_users).not_to include erased_user expect(described_class.all_users).not_to include erased_user
end end
end end

View File

@@ -17,6 +17,11 @@ describe Newsletter do
expect(newsletter).not_to be_valid expect(newsletter).not_to be_valid
end end
it 'is not valid with an inexistent user segment for segment_recipient' do
newsletter.segment_recipient = 'invalid_user_segment_name'
expect(newsletter).not_to be_valid
end
it 'is not valid without a from' do it 'is not valid without a from' do
newsletter.from = nil newsletter.from = nil
expect(newsletter).not_to be_valid expect(newsletter).not_to be_valid
@@ -31,4 +36,17 @@ describe Newsletter do
newsletter.from = "this_is_not_an_email" newsletter.from = "this_is_not_an_email"
expect(newsletter).not_to be_valid expect(newsletter).not_to be_valid
end end
describe '#list_of_recipients' do
before do
create(:user, newsletter: true, username: 'newsletter_user')
create(:user, newsletter: false)
newsletter.update(segment_recipient: 'all_users')
end
it 'returns list of recipients excluding users with disabled newsletter' do
expect(newsletter.list_of_recipients.count).to eq(1)
expect(newsletter.list_of_recipients.first.username).to eq('newsletter_user')
end
end
end end