Merge pull request #2462 from consul/admin-newsletter-emails
Admin newsletter emails
This commit is contained in:
@@ -75,6 +75,7 @@
|
||||
//= require sortable
|
||||
//= require table_sortable
|
||||
//= require investment_report_alert
|
||||
//= require send_newsletter_alert
|
||||
|
||||
var initialize_modules = function() {
|
||||
App.Comments.initialize();
|
||||
@@ -117,6 +118,7 @@ var initialize_modules = function() {
|
||||
App.Sortable.initialize();
|
||||
App.TableSortable.initialize();
|
||||
App.InvestmentReportAlert.initialize();
|
||||
App.SendNewsletterAlert.initialize();
|
||||
};
|
||||
|
||||
$(function(){
|
||||
|
||||
4
app/assets/javascripts/send_newsletter_alert.js.coffee
Normal file
4
app/assets/javascripts/send_newsletter_alert.js.coffee
Normal file
@@ -0,0 +1,4 @@
|
||||
App.SendNewsletterAlert =
|
||||
initialize: ->
|
||||
$('#js-send-newsletter-alert').on 'click', ->
|
||||
confirm(this.dataset.alert);
|
||||
@@ -10,6 +10,7 @@
|
||||
// 08. CMS
|
||||
// 09. Map
|
||||
// 10. Budgets
|
||||
// 11. Newsletters
|
||||
//
|
||||
|
||||
// 01. Global styles
|
||||
@@ -1092,3 +1093,24 @@ table {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 11. Newsletters
|
||||
// -----------------
|
||||
|
||||
.newsletter-body-content {
|
||||
|
||||
table,
|
||||
tbody,
|
||||
tr,
|
||||
th,
|
||||
td {
|
||||
border: 0;
|
||||
|
||||
&:nth-child(even),
|
||||
&:nth-child(even) td:last-child,
|
||||
&:nth-child(odd) td:last-child,
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,47 @@
|
||||
class Admin::NewslettersController < Admin::BaseController
|
||||
|
||||
def index
|
||||
@newsletters = Newsletter.all
|
||||
end
|
||||
|
||||
def show
|
||||
@newsletter = Newsletter.find(params[:id])
|
||||
end
|
||||
|
||||
def new
|
||||
@newsletter = Newsletter.new
|
||||
end
|
||||
|
||||
def create
|
||||
@newsletter = Newsletter.new(newsletter_params)
|
||||
|
||||
if @newsletter.save
|
||||
notice = t("admin.newsletters.create_success")
|
||||
redirect_to [:admin, @newsletter], notice: notice
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@newsletter = Newsletter.find(params[:id])
|
||||
end
|
||||
|
||||
def update
|
||||
@newsletter = Newsletter.find(params[:id])
|
||||
|
||||
if @newsletter.update(newsletter_params)
|
||||
redirect_to [:admin, @newsletter], notice: t("admin.newsletters.update_success")
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@newsletter = Newsletter.find(params[:id])
|
||||
@newsletter.destroy
|
||||
|
||||
redirect_to admin_newsletters_path, notice: t("admin.newsletters.delete_success")
|
||||
end
|
||||
|
||||
def users
|
||||
@@ -9,4 +50,20 @@ class Admin::NewslettersController < Admin::BaseController
|
||||
send_file(File.join(zip.path), type: 'application/zip')
|
||||
end
|
||||
|
||||
def deliver
|
||||
@newsletter = Newsletter.find(params[:id])
|
||||
Mailer.newsletter(@newsletter).deliver_later
|
||||
|
||||
@newsletter.update(sent_at: Time.current)
|
||||
|
||||
redirect_to [:admin, @newsletter], notice: t("admin.newsletters.send_success")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def newsletter_params
|
||||
newsletter_params = params.require(:newsletter)
|
||||
.permit(:subject, :segment_recipient, :from, :body)
|
||||
newsletter_params.merge(segment_recipient: newsletter_params[:segment_recipient].to_i)
|
||||
end
|
||||
end
|
||||
@@ -126,6 +126,15 @@ class Mailer < ApplicationMailer
|
||||
end
|
||||
end
|
||||
|
||||
def newsletter(newsletter)
|
||||
@newsletter = newsletter
|
||||
@email_to = newsletter.list_of_recipients
|
||||
|
||||
@email_to.map(&:email).uniq.each do |recipient|
|
||||
mail(to: recipient, from: @newsletter.from, subject: @newsletter.subject)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def with_user(user, &block)
|
||||
|
||||
@@ -82,6 +82,8 @@ module Abilities
|
||||
|
||||
can [:create], Document
|
||||
can [:create, :destroy], DirectUpload
|
||||
|
||||
can [:deliver], Newsletter, hidden_at: nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
23
app/models/newsletter.rb
Normal file
23
app/models/newsletter.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
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 :segment_recipient, presence: true
|
||||
validates :from, presence: true
|
||||
validates :body, presence: true
|
||||
|
||||
validates_format_of :from, :with => /@/
|
||||
|
||||
def list_of_recipients
|
||||
UserSegments.send(segment_recipient)
|
||||
end
|
||||
|
||||
def draft?
|
||||
sent_at.nil?
|
||||
end
|
||||
end
|
||||
@@ -199,11 +199,16 @@
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<li <%= "class=active" if controller_name == "newsletter" %>>
|
||||
<%= link_to admin_newsletters_path do %>
|
||||
<li class="section-title">
|
||||
<a href="#">
|
||||
<span class="icon-zip"></span>
|
||||
<%= t("admin.menu.newsletter") %>
|
||||
<% end %>
|
||||
<strong><%= t("admin.menu.emails") %></strong>
|
||||
</a>
|
||||
<ul id="emails_menu" <%= "class=is-active" if controller_name == "newsletters" %>>
|
||||
<li <%= "class=active" if controller_name == "newsletters" %>>
|
||||
<%= link_to t("admin.menu.newsletters"), admin_newsletters_path %>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
14
app/views/admin/newsletters/_form.html.erb
Normal file
14
app/views/admin/newsletters/_form.html.erb
Normal file
@@ -0,0 +1,14 @@
|
||||
<%= form_for [:admin, @newsletter] do |f| %>
|
||||
<%= render 'shared/errors', resource: @newsletter %>
|
||||
|
||||
<%= f.select :segment_recipient, options_for_select(Newsletter.segment_recipients
|
||||
.collect { |k,v| [t("admin.segment_recipient.#{k}"), v] },
|
||||
@newsletter[:segment_recipient]) %>
|
||||
<%= f.text_field :subject %>
|
||||
<%= f.text_field :from %>
|
||||
<%= f.cktext_area :body, ckeditor: { language: I18n.locale } %>
|
||||
|
||||
<div class="margin-top">
|
||||
<%= f.submit class: "button success" %>
|
||||
</div>
|
||||
<% end %>
|
||||
4
app/views/admin/newsletters/edit.html.erb
Normal file
4
app/views/admin/newsletters/edit.html.erb
Normal file
@@ -0,0 +1,4 @@
|
||||
<%= back_link_to %>
|
||||
<h2><%= t("admin.newsletters.edit.title") %></h2>
|
||||
|
||||
<%= render "form" %>
|
||||
@@ -1,3 +1,47 @@
|
||||
<h2><%= t("admin.newsletters.index.title") %></h2>
|
||||
<h2 class="inline-block"><%= t("admin.newsletters.index.title") %></h2>
|
||||
<%= link_to t("admin.newsletters.index.new_newsletter"), new_admin_newsletter_path,
|
||||
class: "button float-right" %>
|
||||
|
||||
<%= link_to t("admin.newsletters.index.button"), users_admin_newsletters_path, class: "button" %>
|
||||
<% if @newsletters.any? %>
|
||||
<table id="newsletters">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= t("admin.newsletters.index.subject") %></th>
|
||||
<th><%= t("admin.newsletters.index.segment_recipient") %></th>
|
||||
<th><%= t("admin.newsletters.index.sent") %></th>
|
||||
<th class="small-5 text-right"><%= t("admin.newsletters.index.actions") %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @newsletters.order(created_at: :desc).each do |newsletter| %>
|
||||
<tr id="<%= dom_id(newsletter) %>" class="newsletter">
|
||||
<td>
|
||||
<%= newsletter.subject %>
|
||||
</td>
|
||||
<td>
|
||||
<%= t("admin.segment_recipient.#{newsletter.segment_recipient}") %>
|
||||
</td>
|
||||
<td>
|
||||
<% if newsletter.draft? %>
|
||||
<%= t("admin.newsletters.index.draft") %>
|
||||
<% else %>
|
||||
<%= l newsletter.sent_at.to_date %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<%= link_to t("admin.newsletters.index.edit"), edit_admin_newsletter_path(newsletter),
|
||||
method: :get, class: "button hollow" %>
|
||||
<%= link_to t("admin.newsletters.index.delete"), admin_newsletter_path(newsletter),
|
||||
method: :delete, class: "button hollow alert" %>
|
||||
<%= link_to t("admin.newsletters.index.preview"), admin_newsletter_path(newsletter),
|
||||
class: "button" %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<div data-alert class="callout primary margin-top clear">
|
||||
<%= t("admin.newsletters.index.empty_newsletters") %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
4
app/views/admin/newsletters/new.html.erb
Normal file
4
app/views/admin/newsletters/new.html.erb
Normal file
@@ -0,0 +1,4 @@
|
||||
<%= back_link_to %>
|
||||
<h2><%= t("admin.newsletters.new.title") %></h2>
|
||||
|
||||
<%= render "form" %>
|
||||
51
app/views/admin/newsletters/show.html.erb
Normal file
51
app/views/admin/newsletters/show.html.erb
Normal file
@@ -0,0 +1,51 @@
|
||||
<%= back_link_to %>
|
||||
|
||||
<h2><%= t("admin.newsletters.show.title") %></h2>
|
||||
|
||||
<%- recipients_count = @newsletter.list_of_recipients.count %>
|
||||
|
||||
<div class="small-12 column">
|
||||
<div class="callout highlight">
|
||||
<div class="row">
|
||||
<div class="small-12 medium-4 column">
|
||||
<strong><%= t("admin.newsletters.show.sent_at") %></strong><br>
|
||||
<% if @newsletter.draft? %>
|
||||
<%= t("admin.newsletters.index.draft") %>
|
||||
<% else %>
|
||||
<%= l @newsletter.sent_at.to_date %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="small-12 medium-4 column">
|
||||
<strong><%= t("admin.newsletters.show.from") %></strong><br>
|
||||
<%= @newsletter.from %>
|
||||
</div>
|
||||
<div class="small-12 medium-4 column">
|
||||
<strong><%= t("admin.newsletters.show.subject") %></strong><br>
|
||||
<%= @newsletter.subject %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="small-12 column">
|
||||
<strong><%= t("admin.newsletters.show.segment_recipient") %></strong><br>
|
||||
<%= t("admin.segment_recipient.#{@newsletter.segment_recipient}") %>
|
||||
<%= t("admin.newsletters.show.affected_users", n: recipients_count) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<strong><%= t("admin.newsletters.show.body") %></strong>
|
||||
<p class="help-text" id="phase-description-help-text">
|
||||
<%= t("admin.newsletters.show.body_help_text") %>
|
||||
</p>
|
||||
<div class="newsletter-body-content">
|
||||
<%= render file: "app/views/mailer/newsletter.html.erb", layout: '/app/views/layouts/mailer.html.erb' %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if @newsletter.draft? %>
|
||||
<%= link_to t("admin.newsletters.show.send"), deliver_admin_newsletter_path(@newsletter),
|
||||
"data-alert": t("admin.newsletters.show.send_alert", n: recipients_count),
|
||||
method: :post,
|
||||
id: "js-send-newsletter-alert",
|
||||
class: "button success" %>
|
||||
<% end %>
|
||||
5
app/views/mailer/newsletter.html.erb
Normal file
5
app/views/mailer/newsletter.html.erb
Normal file
@@ -0,0 +1,5 @@
|
||||
<td style="padding-bottom: 20px; padding-left: 10px;">
|
||||
<p style="font-family: 'Open Sans','Helvetica Neue',arial,sans-serif;font-size: 14px;line-height: 24px;">
|
||||
<%= safe_html_with_links @newsletter.body.html_safe %>
|
||||
</p>
|
||||
</td>
|
||||
@@ -37,6 +37,9 @@ en:
|
||||
manager:
|
||||
one: "Manager"
|
||||
other: "Managers"
|
||||
newsletter:
|
||||
one: "Newsletter"
|
||||
other: "Newsletters"
|
||||
vote:
|
||||
one: "Vote"
|
||||
other: "Votes"
|
||||
@@ -234,6 +237,11 @@ en:
|
||||
poll/question/answer/video:
|
||||
title: Title
|
||||
url: External video
|
||||
newsletter:
|
||||
segment_recipient: Recipients
|
||||
subject: Subject
|
||||
from: From
|
||||
body: Email content
|
||||
errors:
|
||||
models:
|
||||
user:
|
||||
|
||||
@@ -465,7 +465,8 @@ en:
|
||||
administrators: Administrators
|
||||
managers: Managers
|
||||
moderators: Moderators
|
||||
newsletter: Newsletters
|
||||
emails: Sending of emails
|
||||
newsletters: Newsletters
|
||||
valuators: Valuators
|
||||
poll_officers: Poll officers
|
||||
polls: Polls
|
||||
@@ -514,10 +515,45 @@ en:
|
||||
delete: Delete
|
||||
search:
|
||||
title: 'Moderators: User search'
|
||||
segment_recipient:
|
||||
all_users: All users
|
||||
proposal_authors: Proposal authors
|
||||
investment_authors: Investment authors 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
|
||||
winner_investment_authors: Authors of winner investments in the current budget
|
||||
newsletters:
|
||||
create_success: Newsletter created successfully
|
||||
update_success: Newsletter updated successfully
|
||||
send_success: Newsletter sent successfully
|
||||
delete_success: Newsletter deleted successfully
|
||||
index:
|
||||
title: Newsletters
|
||||
button: Download zip with users list
|
||||
new_newsletter: New newsletter
|
||||
subject: Subject
|
||||
segment_recipient: Recipients
|
||||
sent: Sent
|
||||
actions: Actions
|
||||
draft: Draft
|
||||
edit: Edit
|
||||
delete: Delete
|
||||
preview: Preview
|
||||
empty_newsletters: There are no newsletters to show
|
||||
new:
|
||||
title: New newsletter
|
||||
edit:
|
||||
title: Edit newsletter
|
||||
show:
|
||||
title: Newsletter preview
|
||||
send: Send
|
||||
affected_users: (%{n} affected users)
|
||||
sent_at: Sent at
|
||||
subject: Subject
|
||||
segment_recipient: Recipients
|
||||
from: From
|
||||
body: Email content
|
||||
body_help_text: This is how the users will see the email
|
||||
send_alert: Are you sure you want to send this newsletter to %{n} users?
|
||||
valuators:
|
||||
index:
|
||||
title: Valuators
|
||||
|
||||
@@ -37,6 +37,9 @@ es:
|
||||
manager:
|
||||
one: "Gestor"
|
||||
other: "Gestores"
|
||||
newsletter:
|
||||
one: "Newsletter"
|
||||
other: "Newsletters"
|
||||
vote:
|
||||
one: "Voto"
|
||||
other: "Votos"
|
||||
@@ -230,6 +233,11 @@ es:
|
||||
poll/question/answer/video:
|
||||
title: Título
|
||||
url: Vídeo externo
|
||||
newsletter:
|
||||
segment_recipient: Destinatarios
|
||||
subject: Asunto
|
||||
from: Enviado por
|
||||
body: Contenido del email
|
||||
errors:
|
||||
models:
|
||||
user:
|
||||
|
||||
@@ -464,7 +464,8 @@ es:
|
||||
administrators: Administradores
|
||||
managers: Gestores
|
||||
moderators: Moderadores
|
||||
newsletter: Envío de Newsletters
|
||||
emails: Envío de emails
|
||||
newsletters: Newsletters
|
||||
valuators: Evaluadores
|
||||
poll_officers: Presidentes de mesa
|
||||
polls: Votaciones
|
||||
@@ -513,10 +514,45 @@ es:
|
||||
delete: Borrar
|
||||
search:
|
||||
title: 'Moderadores: Búsqueda de usuarios'
|
||||
segment_recipient:
|
||||
all_users: Todos los usuarios
|
||||
proposal_authors: Usuarios autores de propuestas
|
||||
investment_authors: Usuarios autores de proyectos de gasto 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
|
||||
winner_investment_authors: Usuarios autores de proyectos de gasto ganadoras en los actuales presupuestos
|
||||
newsletters:
|
||||
create_success: Newsletter creada correctamente
|
||||
update_success: Newsletter actualizada correctamente
|
||||
send_success: Newsletter enviada correctamente
|
||||
delete_success: Newsletter borrada correctamente
|
||||
index:
|
||||
title: Envío de newsletters
|
||||
button: Descargar zip con lista de usuarios
|
||||
new_newsletter: Crear newsletter
|
||||
subject: Asunto
|
||||
segment_recipient: Destinatarios
|
||||
sent: Enviado
|
||||
actions: Acciones
|
||||
draft: Borrador
|
||||
edit: Editar
|
||||
delete: Borrar
|
||||
preview: Previsualizar
|
||||
empty_newsletters: No hay newsletters para mostrar
|
||||
new:
|
||||
title: Nueva newsletter
|
||||
edit:
|
||||
title: Editar newsletter
|
||||
show:
|
||||
title: Vista previa de newsletter
|
||||
send: Enviar
|
||||
affected_users: (%{n} usuarios afectados)
|
||||
sent_at: Enviado
|
||||
subject: Asunto
|
||||
segment_recipient: Destinatarios
|
||||
from: Enviado por
|
||||
body: Contenido del email
|
||||
body_help_text: Así es como verán el email los usuarios
|
||||
send_alert: ¿Estás seguro/a de que quieres enviar esta newsletter a %{n} usuarios?
|
||||
valuators:
|
||||
index:
|
||||
title: Evaluadores
|
||||
|
||||
@@ -142,7 +142,10 @@ namespace :admin do
|
||||
|
||||
resource :activity, controller: :activity, only: :show
|
||||
|
||||
resources :newsletters, only: :index do
|
||||
resources :newsletters do
|
||||
member do
|
||||
post :deliver
|
||||
end
|
||||
get :users, on: :collection
|
||||
end
|
||||
|
||||
|
||||
@@ -828,4 +828,6 @@ section "Creating legislation processes" do
|
||||
end
|
||||
end
|
||||
|
||||
require_relative 'dev_seeds/newsletters'
|
||||
|
||||
log "All dev seeds created successfuly 👍"
|
||||
|
||||
17
db/dev_seeds/newsletters.rb
Normal file
17
db/dev_seeds/newsletters.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
section "Creating Newsletters" do
|
||||
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.",
|
||||
"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 it’s actually our duty to future generations and their quest to ensure the survival of the human species."
|
||||
]
|
||||
|
||||
5.times do |n|
|
||||
Newsletter.create!(
|
||||
subject: "Newsletter subject #{n}",
|
||||
segment_recipient: Newsletter.segment_recipients.values.sample,
|
||||
from: 'no-reply@consul.dev',
|
||||
body: newsletter_body.sample,
|
||||
sent_at: [Time.now, nil].sample
|
||||
)
|
||||
end
|
||||
end
|
||||
13
db/migrate/20180205170054_create_newsletters.rb
Normal file
13
db/migrate/20180205170054_create_newsletters.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
class CreateNewsletters < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :newsletters do |t|
|
||||
t.string :subject
|
||||
t.integer :segment_recipient
|
||||
t.string :from
|
||||
t.text :body
|
||||
t.date :sent_at, default: nil
|
||||
|
||||
t.timestamps null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
12
db/schema.rb
12
db/schema.rb
@@ -11,7 +11,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20180129190950) do
|
||||
ActiveRecord::Schema.define(version: 20180205170054) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@@ -624,6 +624,16 @@ ActiveRecord::Schema.define(version: 20180129190950) do
|
||||
|
||||
add_index "moderators", ["user_id"], name: "index_moderators_on_user_id", using: :btree
|
||||
|
||||
create_table "newsletters", force: :cascade do |t|
|
||||
t.string "subject"
|
||||
t.integer "segment_recipient"
|
||||
t.string "from"
|
||||
t.text "body"
|
||||
t.date "sent_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "notifications", force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.integer "notifiable_id"
|
||||
|
||||
42
lib/user_segments.rb
Normal file
42
lib/user_segments.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
class UserSegments
|
||||
def self.all_users
|
||||
User.newsletter.active
|
||||
end
|
||||
|
||||
def self.proposal_authors
|
||||
author_ids = Proposal.not_archived.not_retired.pluck(:author_id).uniq
|
||||
author_ids(author_ids)
|
||||
end
|
||||
|
||||
def self.investment_authors
|
||||
author_ids = current_budget_investments.pluck(:author_id).uniq
|
||||
author_ids(author_ids)
|
||||
end
|
||||
|
||||
def self.feasible_and_undecided_investment_authors
|
||||
author_ids = current_budget_investments.where(feasibility: %w(feasible undecided))
|
||||
.pluck(:author_id).uniq
|
||||
|
||||
author_ids(author_ids)
|
||||
end
|
||||
|
||||
def self.selected_investment_authors
|
||||
author_ids = current_budget_investments.selected.pluck(:author_id).uniq
|
||||
author_ids(author_ids)
|
||||
end
|
||||
|
||||
def self.winner_investment_authors
|
||||
author_ids = current_budget_investments.winners.pluck(:author_id).uniq
|
||||
author_ids(author_ids)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.current_budget_investments
|
||||
Budget.current.investments
|
||||
end
|
||||
|
||||
def self.author_ids(author_ids)
|
||||
all_users.where(id: author_ids)
|
||||
end
|
||||
end
|
||||
@@ -943,4 +943,11 @@ LOREM_IPSUM
|
||||
factory :related_content do
|
||||
end
|
||||
|
||||
factory :newsletter do
|
||||
sequence(:subject) { |n| "Subject #{n}" }
|
||||
segment_recipient [1, 2, 3, 4, 5, 6].sample
|
||||
sequence(:from) { |n| "noreply#{n}@consul.dev" }
|
||||
sequence(:body) { |n| "Body #{n}" }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
129
spec/features/admin/newsletter/emails_spec.rb
Normal file
129
spec/features/admin/newsletter/emails_spec.rb
Normal file
@@ -0,0 +1,129 @@
|
||||
require 'rails_helper'
|
||||
|
||||
feature "Admin newsletter emails" do
|
||||
|
||||
background do
|
||||
admin = create(:administrator)
|
||||
login_as(admin.user)
|
||||
create(:budget)
|
||||
end
|
||||
|
||||
scenario "Show" do
|
||||
newsletter = create(:newsletter, subject: "This is a subject",
|
||||
segment_recipient: 1,
|
||||
from: "no-reply@consul.dev",
|
||||
body: "This is a body")
|
||||
|
||||
visit admin_newsletter_path(newsletter)
|
||||
|
||||
expect(page).to have_content "This is a subject"
|
||||
expect(page).to have_content I18n.t("admin.segment_recipient.#{newsletter.segment_recipient}")
|
||||
expect(page).to have_content "no-reply@consul.dev"
|
||||
expect(page).to have_content "This is a body"
|
||||
end
|
||||
|
||||
scenario "Index" do
|
||||
3.times { create(:newsletter) }
|
||||
|
||||
visit admin_newsletters_path
|
||||
|
||||
expect(page).to have_css(".newsletter", count: 3)
|
||||
|
||||
Newsletter.all.each do |newsletter|
|
||||
within("#newsletter_#{newsletter.id}") do
|
||||
expect(page).to have_content newsletter.subject
|
||||
expect(page).to have_content I18n.t("admin.segment_recipient.#{newsletter.segment_recipient}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scenario "Create" do
|
||||
visit admin_newsletters_path
|
||||
click_link "New newsletter"
|
||||
|
||||
fill_in_newsletter_form(subject: "This is a subject",
|
||||
segment_recipient: "Proposal authors",
|
||||
body: "This is a body" )
|
||||
click_button "Create Newsletter"
|
||||
|
||||
expect(page).to have_content "Newsletter created successfully"
|
||||
expect(page).to have_content "This is a subject"
|
||||
expect(page).to have_content "Proposal authors"
|
||||
expect(page).to have_content "no-reply@consul.dev"
|
||||
expect(page).to have_content "This is a body"
|
||||
end
|
||||
|
||||
scenario "Update" do
|
||||
newsletter = create(:newsletter)
|
||||
|
||||
visit admin_newsletters_path
|
||||
within("#newsletter_#{newsletter.id}") do
|
||||
click_link "Edit"
|
||||
end
|
||||
|
||||
fill_in_newsletter_form(subject: "This is a subject",
|
||||
segment_recipient: "Investment authors in the current budget",
|
||||
body: "This is a body" )
|
||||
click_button "Update Newsletter"
|
||||
|
||||
expect(page).to have_content "Newsletter updated successfully"
|
||||
expect(page).to have_content "This is a subject"
|
||||
expect(page).to have_content "Investment authors in the current budget"
|
||||
expect(page).to have_content "no-reply@consul.dev"
|
||||
expect(page).to have_content "This is a body"
|
||||
end
|
||||
|
||||
scenario "Destroy" do
|
||||
newsletter = create(:newsletter)
|
||||
|
||||
visit admin_newsletters_path
|
||||
within("#newsletter_#{newsletter.id}") do
|
||||
click_link "Delete"
|
||||
end
|
||||
|
||||
expect(page).to have_content "Newsletter deleted successfully"
|
||||
expect(page).to have_css(".newsletter", count: 0)
|
||||
end
|
||||
|
||||
scenario 'Errors on create' do
|
||||
visit new_admin_newsletter_path
|
||||
|
||||
click_button "Create Newsletter"
|
||||
|
||||
expect(page).to have_content error_message
|
||||
end
|
||||
|
||||
scenario "Errors on update" do
|
||||
newsletter = create(:newsletter)
|
||||
visit edit_admin_newsletter_path(newsletter)
|
||||
|
||||
fill_in "newsletter_subject", with: ""
|
||||
click_button "Update Newsletter"
|
||||
|
||||
expect(page).to have_content error_message
|
||||
end
|
||||
|
||||
scenario "Send newsletter email", :js do
|
||||
newsletter = create(:newsletter)
|
||||
visit admin_newsletter_path(newsletter)
|
||||
|
||||
click_link "Send"
|
||||
|
||||
total_users = newsletter.list_of_recipients.count
|
||||
page.accept_confirm("Are you sure you want to send this newsletter to #{total_users} users?")
|
||||
|
||||
expect(page).to have_content "Newsletter sent successfully"
|
||||
end
|
||||
|
||||
scenario "Select list of users to send newsletter" do
|
||||
Newsletter.segment_recipients.each_key do |user_group|
|
||||
visit new_admin_newsletter_path
|
||||
|
||||
fill_in_newsletter_form
|
||||
select I18n.t("admin.segment_recipient.#{user_group}"), from: 'newsletter_segment_recipient'
|
||||
click_button "Create Newsletter"
|
||||
|
||||
expect(page).to have_content(I18n.t("admin.segment_recipient.#{user_group}"))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,26 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
feature 'Admin newsletters emails' do
|
||||
|
||||
let(:download_button_text) { 'Download zip with users list' }
|
||||
|
||||
background do
|
||||
@admin = create(:administrator)
|
||||
@newsletter_user = create(:user, newsletter: true)
|
||||
@non_newsletter_user = create(:user, newsletter: false)
|
||||
login_as(@admin.user)
|
||||
visit admin_newsletters_path
|
||||
end
|
||||
|
||||
scenario 'Index' do
|
||||
expect(page).to have_content download_button_text
|
||||
end
|
||||
|
||||
scenario 'Download newsletter email zip' do
|
||||
click_link download_button_text
|
||||
downloaded_file_content = Zip::InputStream.open(StringIO.new(page.body)).get_next_entry.get_input_stream {|is| is.read }
|
||||
expect(downloaded_file_content).to include(@admin.user.email, @newsletter_user.email)
|
||||
expect(downloaded_file_content).not_to include(@non_newsletter_user.email)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -491,6 +491,32 @@ feature 'Emails' do
|
||||
|
||||
end
|
||||
|
||||
context "Newsletter" do
|
||||
|
||||
scenario "Send newsletter email to selected users" do
|
||||
admin = create(:administrator)
|
||||
login_as(admin.user)
|
||||
|
||||
visit new_admin_newsletter_path
|
||||
fill_in_newsletter_form
|
||||
click_button "Create Newsletter"
|
||||
|
||||
expect(page).to have_content "Newsletter created successfully"
|
||||
|
||||
click_link "Send"
|
||||
|
||||
UserSegments.send(Newsletter.first.segment_recipient).each do |user|
|
||||
expect(unread_emails_for(user.email).count).to eq 1
|
||||
end
|
||||
|
||||
email = open_last_email
|
||||
expect(email).to have_subject('This is a different subject')
|
||||
expect(email).to deliver_from('no-reply@consul.dev')
|
||||
expect(email.body.encoded).to include('This is a different body')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "Users without email" do
|
||||
scenario "should not receive emails" do
|
||||
user = create(:user, :verified, email_on_comment: true)
|
||||
@@ -504,5 +530,6 @@ feature 'Emails' do
|
||||
|
||||
expect { open_last_email }.to raise_error "No email has been sent!"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
153
spec/lib/user_segments_spec.rb
Normal file
153
spec/lib/user_segments_spec.rb
Normal file
@@ -0,0 +1,153 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe UserSegments do
|
||||
let(:user1) { create(:user) }
|
||||
let(:user2) { create(:user) }
|
||||
let(:user3) { create(:user) }
|
||||
|
||||
describe "#all_users" do
|
||||
it "returns all active users with newsletter enabled" do
|
||||
active_user1 = create(:user, newsletter: true)
|
||||
active_user2 = create(:user, newsletter: true)
|
||||
active_user3 = create(:user, newsletter: false)
|
||||
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_user2
|
||||
expect(described_class.all_users).not_to include active_user3
|
||||
expect(described_class.all_users).not_to include erased_user
|
||||
end
|
||||
end
|
||||
|
||||
describe "#proposal_authors" do
|
||||
it "returns users that have created a proposal" do
|
||||
proposal = create(:proposal, author: user1)
|
||||
|
||||
proposal_authors = described_class.proposal_authors
|
||||
expect(proposal_authors).to include user1
|
||||
expect(proposal_authors).not_to include user2
|
||||
end
|
||||
|
||||
it "does not return duplicated users" do
|
||||
proposal1 = create(:proposal, author: user1)
|
||||
proposal2 = create(:proposal, author: user1)
|
||||
|
||||
proposal_authors = described_class.proposal_authors
|
||||
expect(proposal_authors).to contain_exactly(user1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#investment_authors" do
|
||||
it "returns users that have created a budget investment" do
|
||||
investment = create(:budget_investment, author: user1)
|
||||
budget = create(:budget)
|
||||
investment.update(budget: budget)
|
||||
|
||||
investment_authors = described_class.investment_authors
|
||||
expect(investment_authors).to include user1
|
||||
expect(investment_authors).not_to include user2
|
||||
end
|
||||
|
||||
it "does not return duplicated users" do
|
||||
investment1 = create(:budget_investment, author: user1)
|
||||
investment2 = create(:budget_investment, author: user1)
|
||||
budget = create(:budget)
|
||||
investment1.update(budget: budget)
|
||||
investment2.update(budget: budget)
|
||||
|
||||
investment_authors = described_class.investment_authors
|
||||
expect(investment_authors).to contain_exactly(user1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#feasible_and_undecided_investment_authors" do
|
||||
it "returns authors of a feasible or an undecided budget investment" do
|
||||
feasible_investment = create(:budget_investment, :feasible, author: user1)
|
||||
undecided_investment = create(:budget_investment, :undecided, author: user2)
|
||||
unfeasible_investment = create(:budget_investment, :unfeasible, author: user3)
|
||||
budget = create(:budget)
|
||||
feasible_investment.update(budget: budget)
|
||||
undecided_investment.update(budget: budget)
|
||||
unfeasible_investment.update(budget: budget)
|
||||
|
||||
investment_authors = described_class.feasible_and_undecided_investment_authors
|
||||
expect(investment_authors).to include user1
|
||||
expect(investment_authors).to include user2
|
||||
expect(investment_authors).not_to include user3
|
||||
end
|
||||
|
||||
it "does not return duplicated users" do
|
||||
feasible_investment = create(:budget_investment, :feasible, author: user1)
|
||||
undecided_investment = create(:budget_investment, :undecided, author: user1)
|
||||
budget = create(:budget)
|
||||
feasible_investment.update(budget: budget)
|
||||
undecided_investment.update(budget: budget)
|
||||
|
||||
investment_authors = described_class.feasible_and_undecided_investment_authors
|
||||
expect(investment_authors).to contain_exactly(user1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#selected_investment_authors" do
|
||||
it "returns authors of selected budget investments" do
|
||||
selected_investment = create(:budget_investment, :selected, author: user1)
|
||||
unselected_investment = create(:budget_investment, :unselected, author: user2)
|
||||
budget = create(:budget)
|
||||
selected_investment.update(budget: budget)
|
||||
unselected_investment.update(budget: budget)
|
||||
|
||||
investment_authors = described_class.selected_investment_authors
|
||||
expect(investment_authors).to include user1
|
||||
expect(investment_authors).not_to include user2
|
||||
end
|
||||
|
||||
it "does not return duplicated users" do
|
||||
selected_investment1 = create(:budget_investment, :selected, author: user1)
|
||||
selected_investment2 = create(:budget_investment, :selected, author: user1)
|
||||
budget = create(:budget)
|
||||
selected_investment1.update(budget: budget)
|
||||
selected_investment2.update(budget: budget)
|
||||
|
||||
investment_authors = described_class.selected_investment_authors
|
||||
expect(investment_authors).to contain_exactly(user1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#winner_investment_authors" do
|
||||
it "returns authors of winner budget investments" do
|
||||
winner_investment = create(:budget_investment, :winner, author: user1)
|
||||
selected_investment = create(:budget_investment, :selected, author: user2)
|
||||
budget = create(:budget)
|
||||
winner_investment.update(budget: budget)
|
||||
selected_investment.update(budget: budget)
|
||||
|
||||
investment_authors = described_class.winner_investment_authors
|
||||
expect(investment_authors).to include user1
|
||||
expect(investment_authors).not_to include user2
|
||||
end
|
||||
|
||||
it "does not return duplicated users" do
|
||||
winner_investment1 = create(:budget_investment, :winner, author: user1)
|
||||
winner_investment2 = create(:budget_investment, :winner, author: user1)
|
||||
budget = create(:budget)
|
||||
winner_investment1.update(budget: budget)
|
||||
winner_investment2.update(budget: budget)
|
||||
|
||||
investment_authors = described_class.winner_investment_authors
|
||||
expect(investment_authors).to contain_exactly(user1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#current_budget_investments" do
|
||||
it "only returns investments from the current budget" do
|
||||
investment1 = create(:budget_investment, author: create(:user))
|
||||
investment2 = create(:budget_investment, author: create(:user))
|
||||
budget = create(:budget)
|
||||
investment1.update(budget: budget)
|
||||
|
||||
current_budget_investments = described_class.current_budget_investments
|
||||
expect(current_budget_investments).to include investment1
|
||||
expect(current_budget_investments).not_to include investment2
|
||||
end
|
||||
end
|
||||
end
|
||||
34
spec/models/newsletter_spec.rb
Normal file
34
spec/models/newsletter_spec.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Newsletter do
|
||||
let(:newsletter) { build(:newsletter) }
|
||||
|
||||
it "is valid" do
|
||||
expect(newsletter).to be_valid
|
||||
end
|
||||
|
||||
it 'is not valid without a subject' do
|
||||
newsletter.subject = nil
|
||||
expect(newsletter).not_to be_valid
|
||||
end
|
||||
|
||||
it 'is not valid without a segment_recipient' do
|
||||
newsletter.segment_recipient = nil
|
||||
expect(newsletter).not_to be_valid
|
||||
end
|
||||
|
||||
it 'is not valid without a from' do
|
||||
newsletter.from = nil
|
||||
expect(newsletter).not_to be_valid
|
||||
end
|
||||
|
||||
it 'is not valid without a body' do
|
||||
newsletter.body = nil
|
||||
expect(newsletter).not_to be_valid
|
||||
end
|
||||
|
||||
it 'validates from attribute email format' do
|
||||
newsletter.from = "this_is_not_an_email"
|
||||
expect(newsletter).not_to be_valid
|
||||
end
|
||||
end
|
||||
@@ -363,4 +363,11 @@ module CommonActions
|
||||
end
|
||||
end
|
||||
|
||||
def fill_in_newsletter_form(options = {})
|
||||
fill_in "newsletter_subject", with: (options[:subject] || "This is a different subject")
|
||||
select (options[:segment_recipient] || 'All users'), from: 'newsletter_segment_recipient'
|
||||
fill_in "newsletter_from", with: (options[:from] || "no-reply@consul.dev")
|
||||
fill_in "newsletter_body", with: (options[:body] || "This is a different body")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user