diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index a9e44d249..44258bc7a 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -79,6 +79,7 @@
//= require send_newsletter_alert
//= require managers
//= require globalize
+//= require send_admin_notification_alert
var initialize_modules = function() {
App.Comments.initialize();
@@ -124,6 +125,7 @@ var initialize_modules = function() {
App.SendNewsletterAlert.initialize();
App.Managers.initialize();
App.Globalize.initialize();
+ App.SendAdminNotificationAlert.initialize();
};
$(function(){
diff --git a/app/assets/javascripts/send_admin_notification_alert.js.coffee b/app/assets/javascripts/send_admin_notification_alert.js.coffee
new file mode 100644
index 000000000..8c1c928e5
--- /dev/null
+++ b/app/assets/javascripts/send_admin_notification_alert.js.coffee
@@ -0,0 +1,4 @@
+App.SendAdminNotificationAlert =
+ initialize: ->
+ $('#js-send-admin_notification-alert').on 'click', ->
+ confirm(this.dataset.alert);
diff --git a/app/controllers/admin/admin_notifications_controller.rb b/app/controllers/admin/admin_notifications_controller.rb
new file mode 100644
index 000000000..1dd03038a
--- /dev/null
+++ b/app/controllers/admin/admin_notifications_controller.rb
@@ -0,0 +1,67 @@
+class Admin::AdminNotificationsController < Admin::BaseController
+
+ def index
+ @admin_notifications = AdminNotification.all
+ end
+
+ def show
+ @admin_notification = AdminNotification.find(params[:id])
+ end
+
+ def new
+ @admin_notification = AdminNotification.new
+ end
+
+ def create
+ @admin_notification = AdminNotification.new(admin_notification_params)
+
+ if @admin_notification.save
+ notice = t("admin.admin_notifications.create_success")
+ redirect_to [:admin, @admin_notification], notice: notice
+ else
+ render :new
+ end
+ end
+
+ def edit
+ @admin_notification = AdminNotification.find(params[:id])
+ end
+
+ def update
+ @admin_notification = AdminNotification.find(params[:id])
+
+ if @admin_notification.update(admin_notification_params)
+ notice = t("admin.admin_notifications.update_success")
+ redirect_to [:admin, @admin_notification], notice: notice
+ else
+ render :edit
+ end
+ end
+
+ def destroy
+ @admin_notification = AdminNotification.find(params[:id])
+ @admin_notification.destroy
+
+ notice = t("admin.admin_notifications.delete_success")
+ redirect_to admin_admin_notifications_path, notice: notice
+ end
+
+ def deliver
+ @admin_notification = AdminNotification.find(params[:id])
+
+ if @admin_notification.valid?
+ @admin_notification.deliver
+ flash[:notice] = t("admin.admin_notifications.send_success")
+ else
+ flash[:error] = t("admin.segment_recipient.invalid_recipients_segment")
+ end
+
+ redirect_to [:admin, @admin_notification]
+ end
+
+ private
+
+ def admin_notification_params
+ params.require(:admin_notification).permit(:title, :body, :link, :segment_recipient)
+ end
+end
diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb
index 588f2ed6b..ee6878502 100644
--- a/app/controllers/notifications_controller.rb
+++ b/app/controllers/notifications_controller.rb
@@ -44,7 +44,11 @@ class NotificationsController < ApplicationController
when "Topic"
community_topic_path @notification.linkable_resource.community, @notification.linkable_resource
else
- url_for @notification.linkable_resource
+ if @notification.linkable_resource.is_a?(AdminNotification)
+ @notification.linkable_resource.link || notifications_path
+ else
+ url_for @notification.linkable_resource
+ end
end
end
diff --git a/app/models/admin_notification.rb b/app/models/admin_notification.rb
new file mode 100644
index 000000000..eccd90910
--- /dev/null
+++ b/app/models/admin_notification.rb
@@ -0,0 +1,44 @@
+class AdminNotification < ActiveRecord::Base
+ include Notifiable
+
+ validates :title, presence: true
+ validates :body, presence: true
+ validates :segment_recipient, presence: true
+ validate :validate_segment_recipient
+
+ before_validation :complete_link_url
+
+ def list_of_recipients
+ UserSegments.send(segment_recipient) if valid_segment_recipient?
+ end
+
+ def valid_segment_recipient?
+ segment_recipient && UserSegments.respond_to?(segment_recipient)
+ end
+
+ def draft?
+ sent_at.nil?
+ end
+
+ def list_of_recipients_count
+ list_of_recipients.try(:count) || 0
+ end
+
+ def deliver
+ list_of_recipients.each { |user| Notification.add(user, self) }
+ self.update(sent_at: Time.current, recipients_count: list_of_recipients.count)
+ end
+
+ private
+
+ def validate_segment_recipient
+ errors.add(:segment_recipient, :invalid) unless valid_segment_recipient?
+ end
+
+ def complete_link_url
+ return unless link.present?
+ unless self.link[/\Ahttp:\/\//] || self.link[/\Ahttps:\/\//]
+ self.link = "http://#{self.link}"
+ end
+ end
+end
diff --git a/app/models/notification.rb b/app/models/notification.rb
index 33bf7701c..dacedb762 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -53,9 +53,19 @@ class Notification < ActiveRecord::Base
"proposal_notification"
when "Comment"
"replies_to"
+ when "AdminNotification"
+ nil
else
"comments_on"
end
end
-end
\ No newline at end of file
+ def link
+ if notifiable.is_a?(AdminNotification) && notifiable.link.blank?
+ nil
+ else
+ self
+ end
+ end
+
+end
diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb
index b830acb46..e5af32cc4 100644
--- a/app/views/admin/_menu.html.erb
+++ b/app/views/admin/_menu.html.erb
@@ -79,18 +79,21 @@
<% end %>
- <% messages_sections = %w(newsletters emails_download) %>
- <% messages_menu_active = messages_sections.include?(controller_name) %>
-
>
+ <% newsletters_notifications_sections = %w(newsletters emails_download admin_notifications) %>
+ <% newsletters_menu_active = newsletters_notifications_sections.include?(controller_name) %>
+ >
- <%= t("admin.menu.emails") %>
+ <%= t("admin.menu.newsletters_and_notifications") %>
- >
- - >
+
>
+ - >
<%= link_to t("admin.menu.newsletters"), admin_newsletters_path %>
- - >
+
- >
+ <%= link_to t("admin.menu.admin_notifications"), admin_admin_notifications_path %>
+
+ - >
<%= link_to t("admin.menu.emails_download"), admin_emails_download_index_path %>
@@ -231,7 +234,6 @@
- >
<%= link_to t("admin.menu.site_customization.content_blocks"), admin_site_customization_content_blocks_path%>
-
diff --git a/app/views/admin/admin_notifications/_form.html.erb b/app/views/admin/admin_notifications/_form.html.erb
new file mode 100644
index 000000000..4053bb66d
--- /dev/null
+++ b/app/views/admin/admin_notifications/_form.html.erb
@@ -0,0 +1,13 @@
+<%= form_for [:admin, @admin_notification] do |f| %>
+ <%= render 'shared/errors', resource: @admin_notification %>
+
+ <%= f.select :segment_recipient, options_for_select(user_segments_options,
+ @admin_notification[:segment_recipient]) %>
+ <%= f.text_field :title %>
+ <%= f.text_field :link %>
+ <%= f.text_area :body %>
+
+
+ <%= f.submit class: "button success" %>
+
+<% end %>
diff --git a/app/views/admin/admin_notifications/edit.html.erb b/app/views/admin/admin_notifications/edit.html.erb
new file mode 100644
index 000000000..cb14f0a0b
--- /dev/null
+++ b/app/views/admin/admin_notifications/edit.html.erb
@@ -0,0 +1,4 @@
+<%= back_link_to %>
+<%= t("admin.admin_notifications.edit.section_title") %>
+
+<%= render "form" %>
diff --git a/app/views/admin/admin_notifications/index.html.erb b/app/views/admin/admin_notifications/index.html.erb
new file mode 100644
index 000000000..d16ee476e
--- /dev/null
+++ b/app/views/admin/admin_notifications/index.html.erb
@@ -0,0 +1,56 @@
+<%= t("admin.admin_notifications.index.section_title") %>
+<%= link_to t("admin.admin_notifications.index.new_notification"), new_admin_admin_notification_path,
+ class: "button float-right" %>
+
+<% if @admin_notifications.any? %>
+
+
+
+ | <%= t("admin.admin_notifications.index.title") %> |
+ <%= t("admin.admin_notifications.index.segment_recipient") %> |
+ <%= t("admin.admin_notifications.index.sent") %> |
+ <%= t("admin.admin_notifications.index.actions") %> |
+
+
+
+ <% @admin_notifications.order(created_at: :desc).each do |admin_notification| %>
+
+ |
+ <%= admin_notification.title %>
+ |
+
+ <%= segment_name(admin_notification.segment_recipient) %>
+ |
+
+ <% if admin_notification.draft? %>
+ <%= t("admin.admin_notifications.index.draft") %>
+ <% else %>
+ <%= l admin_notification.sent_at.to_date %>
+ <% end %>
+ |
+
+ <% if admin_notification.draft? %>
+ <%= link_to t("admin.admin_notifications.index.edit"),
+ edit_admin_admin_notification_path(admin_notification),
+ method: :get, class: "button hollow" %>
+ <%= link_to t("admin.admin_notifications.index.delete"),
+ admin_admin_notification_path(admin_notification),
+ method: :delete, class: "button hollow alert" %>
+ <%= link_to t("admin.admin_notifications.index.preview"),
+ admin_admin_notification_path(admin_notification),
+ class: "button" %>
+ <% else %>
+ <%= link_to t("admin.admin_notifications.index.view"),
+ admin_admin_notification_path(admin_notification),
+ class: "button" %>
+ <% end %>
+ |
+
+ <% end %>
+
+
+<% else %>
+
+ <%= t("admin.admin_notifications.index.empty_notifications") %>
+
+<% end %>
diff --git a/app/views/admin/admin_notifications/new.html.erb b/app/views/admin/admin_notifications/new.html.erb
new file mode 100644
index 000000000..69bf2a80b
--- /dev/null
+++ b/app/views/admin/admin_notifications/new.html.erb
@@ -0,0 +1,4 @@
+<%= back_link_to %>
+<%= t("admin.admin_notifications.new.section_title") %>
+
+<%= render "form" %>
diff --git a/app/views/admin/admin_notifications/show.html.erb b/app/views/admin/admin_notifications/show.html.erb
new file mode 100644
index 000000000..92c0e37fc
--- /dev/null
+++ b/app/views/admin/admin_notifications/show.html.erb
@@ -0,0 +1,77 @@
+<%= back_link_to admin_admin_notifications_path %>
+
+<%= t("admin.admin_notifications.show.section_title") %>
+
+
+
+
+
+ <%= t("admin.admin_notifications.show.sent_at") %>
+ <% if @admin_notification.draft? %>
+ <%= t("admin.admin_notifications.index.draft") %>
+ <% else %>
+ <%= l(@admin_notification.sent_at.to_date) %>
+ <% end %>
+
+
+ <%= t("admin.admin_notifications.show.title") %>
+ <%= @admin_notification.title %>
+
+
+
+
+
+ <%= t("admin.admin_notifications.show.body") %>
+ <%= @admin_notification.body %>
+
+
+ <%= t("admin.admin_notifications.show.link") %>
+ <%= @admin_notification.link %>
+
+
+
+
+ <%= t("admin.admin_notifications.show.segment_recipient") %>
+ <%= segment_name(@admin_notification.segment_recipient) %>
+ <% if @admin_notification.draft? %>
+ <%= t("admin.admin_notifications.show.will_get_notified",
+ n: @admin_notification.list_of_recipients_count) %>
+ <% else %>
+ <%= t("admin.admin_notifications.show.got_notified",
+ n: @admin_notification.recipients_count) %>
+ <% end %>
+
+
+
+
+
+ <% if @admin_notification.draft? %>
+ <%= t("admin.admin_notifications.show.preview_guide") %>
+ <% else %>
+ <%= t("admin.admin_notifications.show.sent_guide") %>
+ <% end %>
+
+
+
+
+ -
+ <% locals = { notification: nil,
+ title: @admin_notification.title,
+ body: @admin_notification.body,
+ timestamp: Time.current } %>
+ <% link_text = render partial: '/notifications/notification_body', locals: locals %>
+ <%= link_to_if @admin_notification.link.present?, link_text, @admin_notification.link %>
+
+
+
+
+
+<% if @admin_notification.draft? && @admin_notification.valid_segment_recipient? %>
+ <%= link_to t("admin.admin_notifications.show.send"),
+ deliver_admin_admin_notification_path(@admin_notification),
+ "data-alert": t("admin.admin_notifications.show.send_alert",
+ n: @admin_notification.list_of_recipients_count),
+ method: :post,
+ id: "js-send-admin_notification-alert",
+ class: "button success" %>
+<% end %>
diff --git a/app/views/notifications/_notification.html.erb b/app/views/notifications/_notification.html.erb
index 2d8b115e5..34f1b96b1 100644
--- a/app/views/notifications/_notification.html.erb
+++ b/app/views/notifications/_notification.html.erb
@@ -1,21 +1,11 @@
-">
-
- <% if notification.notifiable_available? %>
- <%= link_to notification do %>
-
-
- <%= t("notifications.notification.action.#{notification.notifiable_action}",
- count: notification.counter) %>
-
-
- <%= notification.notifiable_title %>
-
-
-
-
- <%= l notification.timestamp, format: :datetime %>
-
- <% end %>
+
+ <% if notification.try(:notifiable_available?) %>
+ <% locals = { notification: notification,
+ timestamp: notification.timestamp,
+ title: notification.notifiable_title,
+ body: notification.notifiable.try(:body) } %>
+ <% link_text = render partial: '/notifications/notification_body', locals: locals %>
+ <%= link_to_if notification.link.present?, link_text, notification.link %>
<% else %>
diff --git a/app/views/notifications/_notification_body.html.erb b/app/views/notifications/_notification_body.html.erb
new file mode 100644
index 000000000..d6ed86673
--- /dev/null
+++ b/app/views/notifications/_notification_body.html.erb
@@ -0,0 +1,17 @@
+
+ <% if notification && notification.notifiable_action %>
+
+ <%= t("notifications.notification.action.#{notification.notifiable_action}",
+ count: notification.counter) %>
+
+ <% end %>
+
+ <%= title %>
+
+ <% if body %>
+
<%= body %>
+ <% end %>
+
+
+ <%= l(timestamp, format: :datetime) %>
+
diff --git a/app/views/notifications/index.html.erb b/app/views/notifications/index.html.erb
index eccd3ec6a..e005aad88 100644
--- a/app/views/notifications/index.html.erb
+++ b/app/views/notifications/index.html.erb
@@ -24,7 +24,9 @@
<% else %>
- <%= render @notifications %>
+ <% @notifications.each do |notification| %>
+ <%= render partial: '/notifications/notification', locals: {notification: notification} %>
+ <% end %>
<% end %>
diff --git a/config/locales/en/activerecord.yml b/config/locales/en/activerecord.yml
index 8e44f6ea9..d3d19f1ed 100644
--- a/config/locales/en/activerecord.yml
+++ b/config/locales/en/activerecord.yml
@@ -283,6 +283,10 @@ en:
attributes:
segment_recipient:
invalid: "The user recipients segment is invalid"
+ admin_notification:
+ attributes:
+ segment_recipient:
+ invalid: "The user recipients segment is invalid"
map_location:
attributes:
map:
diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml
index 760741453..54bb5adc6 100644
--- a/config/locales/en/admin.yml
+++ b/config/locales/en/admin.yml
@@ -518,8 +518,9 @@ en:
administrators: Administrators
managers: Managers
moderators: Moderators
- emails: Sending of emails
+ newsletters_and_notifications: Newsletters & Notifications
newsletters: Newsletters
+ admin_notifications: Notifications
emails_download: Emails download
valuators: Valuators
poll_officers: Poll officers
@@ -613,6 +614,41 @@ en:
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?
+ admin_notifications:
+ create_success: Notification created successfully
+ update_success: Notification updated successfully
+ send_success: Notification sent successfully
+ delete_success: Notification deleted successfully
+ index:
+ section_title: Notifications
+ new_notification: New notification
+ title: Title
+ segment_recipient: Recipients
+ sent: Sent
+ actions: Actions
+ draft: Draft
+ edit: Edit
+ delete: Delete
+ preview: Preview
+ view: View
+ empty_notifications: There are no notifications to show
+ new:
+ section_title: New notification
+ edit:
+ section_title: Edit notification
+ show:
+ section_title: Notification preview
+ send: Send
+ will_get_notified: (%{n} users will be notified)
+ got_notified: (%{n} users got notified)
+ sent_at: Sent at
+ title: Title
+ body: Text
+ link: Link
+ segment_recipient: Recipients
+ preview_guide: "This is how the users will see the notification:"
+ sent_guide: "This is how the users see the notification:"
+ send_alert: Are you sure you want to send this notification to %{n} users?
emails_download:
index:
title: Emails download
diff --git a/config/locales/en/seeds.yml b/config/locales/en/seeds.yml
index 4e40cfd63..a3587c9e3 100644
--- a/config/locales/en/seeds.yml
+++ b/config/locales/en/seeds.yml
@@ -53,3 +53,18 @@ en:
recounting_poll: "Recounting Poll"
expired_poll_without_stats: "Expired Poll without Stats & Results"
expired_poll_with_stats: "Expired Poll with Stats & Results"
+ admin_notifications:
+ internal_link:
+ title: 'Do you have a proposal?'
+ body: 'Remember you can create a proposal with your ideas and people will discuss & support it.'
+ link: '/proposals'
+ external_link:
+ title: Help us translate consul
+ body: 'If you are proficient in a language, please help us translate consul!.'
+ link: 'https://crwd.in/consul'
+ without_link:
+ title: 'You can now geolocate proposals & investments'
+ body: 'When you create a proposal or investment you now can specify a point on a map'
+ not_sent:
+ title: 'We are closing the Participatory Budget!!'
+ body: 'Hurry up and create a last proposal before it ends next in few days!'
diff --git a/config/locales/es/activerecord.yml b/config/locales/es/activerecord.yml
index 2da027031..52bb51e9f 100644
--- a/config/locales/es/activerecord.yml
+++ b/config/locales/es/activerecord.yml
@@ -283,6 +283,10 @@ es:
attributes:
segment_recipient:
invalid: "El segmento de usuarios es inválido"
+ admin_notification:
+ attributes:
+ segment_recipient:
+ invalid: "El segmento de usuarios es inválido"
map_location:
attributes:
map:
diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml
index 909e2509a..c57b42197 100644
--- a/config/locales/es/admin.yml
+++ b/config/locales/es/admin.yml
@@ -519,8 +519,9 @@ es:
administrators: Administradores
managers: Gestores
moderators: Moderadores
- emails: Envío de emails
+ newsletters_and_notifications: Newsletters & Notificaciones
newsletters: Newsletters
+ admin_notifications: Notificaciones
emails_download: Descarga de emails
valuators: Evaluadores
poll_officers: Presidentes de mesa
@@ -614,6 +615,41 @@ es:
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?
+ admin_notifications:
+ create_success: Notificación creada correctamente
+ update_success: Notificación actualizada correctamente
+ send_success: Notificación enviada correctamente
+ delete_success: Notificación borrada correctamente
+ index:
+ section_title: Envío de notificaciones
+ new_notification: Crear notificación
+ title: Título
+ segment_recipient: Destinatarios
+ sent: Enviado
+ actions: Acciones
+ draft: Borrador
+ edit: Editar
+ delete: Borrar
+ preview: Previsualizar
+ view: Visualizar
+ empty_notifications: No hay notificaciones para mostrar
+ new:
+ section_title: Nueva notificación
+ edit:
+ section_title: Editar notificación
+ show:
+ section_title: Vista previa de notificación
+ send: Enviar
+ will_get_notified: (%{n} usuarios serán notificados)
+ got_notified: (%{n} usuarios fueron notificados)
+ sent_at: Enviado
+ title: Título
+ body: Texto
+ link: Enlace
+ segment_recipient: Destinatarios
+ preview_guide: "Así es como los usuarios verán la notificación:"
+ sent_guide: "Así es como los usuarios ven la notificación:"
+ send_alert: ¿Estás seguro/a de que quieres enviar esta notificación a %{n} usuarios?
emails_download:
index:
title: Descarga de emails
diff --git a/config/locales/es/seeds.yml b/config/locales/es/seeds.yml
index 1d6563bef..d8a40471b 100644
--- a/config/locales/es/seeds.yml
+++ b/config/locales/es/seeds.yml
@@ -53,3 +53,18 @@ es:
recounting_poll: "Votación en Recuento"
expired_poll_without_stats: "Votación Finalizada (sin Estadísticas o Resultados)"
expired_poll_with_stats: "Votación Finalizada (con Estadísticas y Resultado)"
+ admin_notifications:
+ internal_link:
+ title: 'Tienes una propuesta?'
+ body: 'Recuerda que puedes crear propuestas y los ciudadanos las debatirán y apoyarán.'
+ link: '/proposals'
+ external_link:
+ title: 'Ayúdanos a traducir CONSUL'
+ body: 'Si dominas un idioma, ayúdanos a completar su traducción en CONSUL.'
+ link: 'https://crwd.in/consul'
+ without_link:
+ title: 'Ahora puedes geolocalizar propuestas y proyectos de inversión'
+ body: 'Cuando crees una propuesta o proyecto de inversión podrás especificar su localización en el mapa'
+ not_sent:
+ title: 'Últimos días para crear proyectos de Presupuestos Participativos'
+ body: 'Quedan pocos dias para que se cierre el plazo de presentación de proyectos de inversión para los presupuestos participativos!'
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 001456f6e..ec7e4bb5f 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -159,6 +159,12 @@ namespace :admin do
get :users, on: :collection
end
+ resources :admin_notifications do
+ member do
+ post :deliver
+ end
+ end
+
resources :emails_download, only: :index do
get :generate_csv, on: :collection
end
diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb
index 966307e10..7b7af9342 100644
--- a/db/dev_seeds.rb
+++ b/db/dev_seeds.rb
@@ -34,5 +34,6 @@ require_relative 'dev_seeds/legislation_processes'
require_relative 'dev_seeds/newsletters'
require_relative 'dev_seeds/notifications'
require_relative 'dev_seeds/widgets'
+require_relative 'dev_seeds/admin_notifications'
log "All dev seeds created successfuly 👍"
diff --git a/db/dev_seeds/admin_notifications.rb b/db/dev_seeds/admin_notifications.rb
new file mode 100644
index 000000000..a07077790
--- /dev/null
+++ b/db/dev_seeds/admin_notifications.rb
@@ -0,0 +1,28 @@
+section "Creating Admin Notifications & Templates" do
+ AdminNotification.create!(
+ title: I18n.t('seeds.admin_notification.internal_link.title'),
+ body: I18n.t('seeds.admin_notification.internal_link.body'),
+ link: Setting['url'] + I18n.t('seeds.admin_notification.internal_link.link'),
+ segment_recipient: 'administrators'
+ ).deliver
+
+ AdminNotification.create!(
+ title: I18n.t('seeds.admin_notification.external_link.title'),
+ body: I18n.t('seeds.admin_notification.external_link.body'),
+ link: I18n.t('seeds.admin_notification.external_link.link'),
+ segment_recipient: 'administrators'
+ ).deliver
+
+ AdminNotification.create!(
+ title: I18n.t('seeds.admin_notification.without_link.title'),
+ body: I18n.t('seeds.admin_notification.without_link.body'),
+ segment_recipient: 'administrators'
+ ).deliver
+
+ AdminNotification.create!(
+ title: I18n.t('seeds.admin_notification.not_sent.title'),
+ body: I18n.t('seeds.admin_notification.not_sent.body'),
+ segment_recipient: 'administrators',
+ sent_at: nil
+ )
+end
diff --git a/db/migrate/20180221002503_create_admin_notifications.rb b/db/migrate/20180221002503_create_admin_notifications.rb
new file mode 100644
index 000000000..041931495
--- /dev/null
+++ b/db/migrate/20180221002503_create_admin_notifications.rb
@@ -0,0 +1,14 @@
+class CreateAdminNotifications < ActiveRecord::Migration
+ def change
+ create_table :admin_notifications do |t|
+ t.string :title
+ t.text :body
+ t.string :link
+ t.string :segment_recipient
+ t.integer :recipients_count
+ t.date :sent_at, default: nil
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 45871b884..878c16869 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -30,6 +30,17 @@ ActiveRecord::Schema.define(version: 20180711224810) do
add_index "activities", ["actionable_id", "actionable_type"], name: "index_activities_on_actionable_id_and_actionable_type", using: :btree
add_index "activities", ["user_id"], name: "index_activities_on_user_id", using: :btree
+ create_table "admin_notifications", force: :cascade do |t|
+ t.string "title"
+ t.text "body"
+ t.string "link"
+ t.string "segment_recipient"
+ t.integer "recipients_count"
+ t.date "sent_at"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
create_table "administrators", force: :cascade do |t|
t.integer "user_id"
end
diff --git a/spec/factories.rb b/spec/factories.rb
index 77deec79a..beb64aa2d 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -1003,6 +1003,20 @@ LOREM_IPSUM
sequence(:body) { |n| "Body #{n}" }
end
+ factory :admin_notification do
+ title { |n| "Admin Notification title #{n}" }
+ body { |n| "Admin Notification body #{n}" }
+ link nil
+ segment_recipient UserSegments::SEGMENTS.sample
+ recipients_count nil
+ sent_at nil
+
+ trait :sent do
+ recipients_count 1
+ sent_at Time.current
+ end
+ end
+
factory :widget_card, class: 'Widget::Card' do
sequence(:title) { |n| "Title #{n}" }
sequence(:description) { |n| "Description #{n}" }
diff --git a/spec/features/admin/admin_notifications_spec.rb b/spec/features/admin/admin_notifications_spec.rb
new file mode 100644
index 000000000..a97170e7c
--- /dev/null
+++ b/spec/features/admin/admin_notifications_spec.rb
@@ -0,0 +1,236 @@
+require 'rails_helper'
+
+feature "Admin Notifications" do
+
+ background do
+ admin = create(:administrator)
+ login_as(admin.user)
+ create(:budget)
+ end
+
+ context "Show" do
+ scenario "Valid Admin Notification" do
+ notification = create(:admin_notification, title: 'Notification title',
+ body: 'Notification body',
+ link: 'https://www.decide.madrid.es/vota',
+ segment_recipient: :all_users)
+
+ visit admin_admin_notification_path(notification)
+
+ expect(page).to have_content('Notification title')
+ expect(page).to have_content('Notification body')
+ expect(page).to have_content('https://www.decide.madrid.es/vota')
+ expect(page).to have_content('All users')
+ end
+
+ scenario "Notification with invalid segment recipient" do
+ invalid_notification = create(:admin_notification)
+ invalid_notification.update_attribute(:segment_recipient, 'invalid_segment')
+
+ visit admin_admin_notification_path(invalid_notification)
+
+ expect(page).to have_content("Recipients user segment is invalid")
+ end
+ end
+
+ context "Index" do
+ scenario "Valid Admin Notifications" do
+ draft = create(:admin_notification, segment_recipient: :all_users, title: 'Not yet sent')
+ sent = create(:admin_notification, :sent, segment_recipient: :administrators,
+ title: 'Sent one')
+
+ visit admin_admin_notifications_path
+
+ expect(page).to have_css(".admin_notification", count: 2)
+
+ within("#admin_notification_#{draft.id}") do
+ expect(page).to have_content('Not yet sent')
+ expect(page).to have_content('All users')
+ expect(page).to have_content('Draft')
+ end
+
+ within("#admin_notification_#{sent.id}") do
+ expect(page).to have_content('Sent one')
+ expect(page).to have_content('Administrators')
+ expect(page).to have_content(I18n.l(Date.current))
+ end
+ end
+
+ scenario "Notifications with invalid segment recipient" do
+ invalid_notification = create(:admin_notification)
+ invalid_notification.update_attribute(:segment_recipient, 'invalid_segment')
+
+ visit admin_admin_notifications_path
+
+ expect(page).to have_content("Recipients user segment is invalid")
+ end
+ end
+
+ scenario "Create" do
+ visit admin_admin_notifications_path
+ click_link "New notification"
+
+ fill_in_admin_notification_form(segment_recipient: 'Proposal authors',
+ title: 'This is a title',
+ body: 'This is a body',
+ link: 'http://www.dummylink.dev')
+
+ click_button "Create Admin notification"
+
+ expect(page).to have_content "Notification created successfully"
+ expect(page).to have_content "Proposal authors"
+ expect(page).to have_content "This is a title"
+ expect(page).to have_content "This is a body"
+ expect(page).to have_content "http://www.dummylink.dev"
+ end
+
+ context "Update" do
+ scenario "A draft notification can be updated" do
+ notification = create(:admin_notification)
+
+ visit admin_admin_notifications_path
+ within("#admin_notification_#{notification.id}") do
+ click_link "Edit"
+ end
+
+
+ fill_in_admin_notification_form(segment_recipient: 'All users',
+ title: 'Other title',
+ body: 'Other body',
+ link: '')
+
+ click_button "Update Admin notification"
+
+ expect(page).to have_content "Notification updated successfully"
+ expect(page).to have_content "All users"
+ expect(page).to have_content "Other title"
+ expect(page).to have_content "Other body"
+ expect(page).not_to have_content "http://www.dummylink.dev"
+ end
+
+ scenario "Sent notification can not be updated" do
+ notification = create(:admin_notification, :sent)
+
+ visit admin_admin_notifications_path
+ within("#admin_notification_#{notification.id}") do
+ expect(page).not_to have_link("Edit")
+ end
+ end
+ end
+
+ context "Destroy" do
+ scenario "A draft notification can be destroyed" do
+ notification = create(:admin_notification)
+
+ visit admin_admin_notifications_path
+ within("#admin_notification_#{notification.id}") do
+ click_link "Delete"
+ end
+
+ expect(page).to have_content "Notification deleted successfully"
+ expect(page).to have_css(".notification", count: 0)
+ end
+
+ scenario "Sent notification can not be destroyed" do
+ notification = create(:admin_notification, :sent)
+
+ visit admin_admin_notifications_path
+ within("#admin_notification_#{notification.id}") do
+ expect(page).not_to have_link("Delete")
+ end
+ end
+ end
+
+ context "Visualize" do
+ scenario "A draft notification can be previewed" do
+ notification = create(:admin_notification, segment_recipient: :administrators)
+
+ visit admin_admin_notifications_path
+ within("#admin_notification_#{notification.id}") do
+ click_link "Preview"
+ end
+
+ expect(page).to have_content "This is how the users will see the notification:"
+ expect(page).to have_content "Administrators (1 users will be notified)"
+ end
+
+ scenario "A sent notification can be viewed" do
+ notification = create(:admin_notification, :sent, recipients_count: 7,
+ segment_recipient: :administrators)
+
+ visit admin_admin_notifications_path
+ within("#admin_notification_#{notification.id}") do
+ click_link "View"
+ end
+
+ expect(page).to have_content "This is how the users see the notification:"
+ expect(page).to have_content "Administrators (7 users got notified)"
+ end
+ end
+
+ scenario 'Errors on create' do
+ visit new_admin_admin_notification_path
+
+ click_button "Create Admin notification"
+
+ expect(page).to have_content error_message
+ end
+
+ scenario "Errors on update" do
+ notification = create(:admin_notification)
+ visit edit_admin_admin_notification_path(notification)
+
+ fill_in :admin_notification_title, with: ''
+ click_button "Update Admin notification"
+
+ expect(page).to have_content error_message
+ end
+
+ context "Send notification", :js do
+ scenario "A draft Admin notification can be sent", :js do
+ 2.times { create(:user) }
+ notification = create(:admin_notification, segment_recipient: :all_users)
+ total_users = notification.list_of_recipients.count
+ confirm_message = "Are you sure you want to send this notification to #{total_users} users?"
+
+ visit admin_admin_notification_path(notification)
+
+ accept_confirm { click_link "Send" }
+
+ expect(page).to have_content "Notification sent successfully"
+
+ User.all.each do |user|
+ expect(user.notifications.count).to eq(1)
+ end
+ end
+
+ scenario "A sent Admin notification can not be sent", :js do
+ notification = create(:admin_notification, :sent)
+
+ visit admin_admin_notification_path(notification)
+
+ expect(page).not_to have_link("Send")
+ end
+
+ scenario "Admin notification with invalid segment recipient cannot be sent", :js do
+ invalid_notification = create(:admin_notification)
+ invalid_notification.update_attribute(:segment_recipient, 'invalid_segment')
+ visit admin_admin_notification_path(invalid_notification)
+
+ expect(page).not_to have_link("Send")
+ end
+ end
+
+ scenario "Select list of users to send notification" do
+ UserSegments::SEGMENTS.each do |user_segment|
+ segment_recipient = I18n.t("admin.segment_recipient.#{user_segment}")
+
+ visit new_admin_admin_notification_path
+
+ fill_in_admin_notification_form(segment_recipient: segment_recipient)
+ click_button "Create Admin notification"
+
+ expect(page).to have_content(I18n.t("admin.segment_recipient.#{user_segment}"))
+ end
+ end
+end
diff --git a/spec/features/notifications_spec.rb b/spec/features/notifications_spec.rb
index 58aebb472..f8e77ac0d 100644
--- a/spec/features/notifications_spec.rb
+++ b/spec/features/notifications_spec.rb
@@ -128,4 +128,50 @@ feature "Notifications" do
expect(page).to_not have_css("#notifications")
end
+ context "Admin Notifications" do
+ let(:admin_notification) do
+ create(:admin_notification, title: 'Notification title',
+ body: 'Notification body',
+ link: 'https://www.external.link.dev/',
+ segment_recipient: 'all_users')
+ end
+
+ let!(:notification) do
+ create(:notification, user: user, notifiable: admin_notification)
+ end
+
+ before do
+ login_as user
+ end
+
+ scenario "With external link" do
+ visit notifications_path
+ expect(page).to have_content('Notification title')
+ expect(page).to have_content('Notification body')
+
+ first("#notification_#{notification.id} a").click
+ expect(page.current_url).to eq('https://www.external.link.dev/')
+ end
+
+ scenario "With internal link" do
+ admin_notification.update_attributes(link: '/stats')
+
+ visit notifications_path
+ expect(page).to have_content('Notification title')
+ expect(page).to have_content('Notification body')
+
+ first("#notification_#{notification.id} a").click
+ expect(page).to have_current_path('/stats')
+ end
+
+ scenario "Without a link" do
+ admin_notification.update_attributes(link: '/stats')
+
+ visit notifications_path
+ expect(page).to have_content('Notification title')
+ expect(page).to have_content('Notification body')
+ expect(page).not_to have_link(notification_path(notification), visible: false)
+ end
+ end
+
end
diff --git a/spec/models/admin_notification_spec.rb b/spec/models/admin_notification_spec.rb
new file mode 100644
index 000000000..eeb974e83
--- /dev/null
+++ b/spec/models/admin_notification_spec.rb
@@ -0,0 +1,91 @@
+require 'rails_helper'
+
+describe AdminNotification do
+ let(:admin_notification) { build(:admin_notification) }
+
+ it "is valid" do
+ expect(admin_notification).to be_valid
+ end
+
+ it 'is not valid without a title' do
+ admin_notification.title = nil
+ expect(admin_notification).not_to be_valid
+ end
+
+ it 'is not valid without a body' do
+ admin_notification.body = nil
+ expect(admin_notification).not_to be_valid
+ end
+
+ it 'is not valid without a segment_recipient' do
+ admin_notification.segment_recipient = nil
+ expect(admin_notification).not_to be_valid
+ end
+
+ describe '#complete_link_url' do
+ it 'does not change link if there is no value' do
+ expect(admin_notification.link).to be_nil
+ end
+
+ it 'fixes a link without http://' do
+ admin_notification.link = 'lol.consul.dev'
+
+ expect(admin_notification).to be_valid
+ expect(admin_notification.link).to eq('http://lol.consul.dev')
+ end
+
+ it 'fixes a link with wwww. but without http://' do
+ admin_notification.link = 'www.lol.consul.dev'
+
+ expect(admin_notification).to be_valid
+ expect(admin_notification.link).to eq('http://www.lol.consul.dev')
+ end
+
+ it 'does not modify a link with http://' do
+ admin_notification.link = 'http://lol.consul.dev'
+
+ expect(admin_notification).to be_valid
+ expect(admin_notification.link).to eq('http://lol.consul.dev')
+ end
+
+ it 'does not modify a link with https://' do
+ admin_notification.link = 'https://lol.consul.dev'
+
+ expect(admin_notification).to be_valid
+ expect(admin_notification.link).to eq('https://lol.consul.dev')
+ end
+
+ it 'does not modify a link with http://wwww.' do
+ admin_notification.link = 'http://www.lol.consul.dev'
+
+ expect(admin_notification).to be_valid
+ expect(admin_notification.link).to eq('http://www.lol.consul.dev')
+ end
+ end
+
+ describe '#valid_segment_recipient?' do
+ it 'is false when segment_recipient value is invalid' do
+ admin_notification.update(segment_recipient: 'invalid_segment_name')
+ error = 'The user recipients segment is invalid'
+
+ expect(admin_notification).not_to be_valid
+ expect(admin_notification.errors.messages[:segment_recipient]).to include(error)
+ end
+ end
+
+ describe '#list_of_recipients' do
+ let(:erased_user) { create(:user, username: 'erased_user') }
+
+ before do
+ 2.times { create(:user) }
+ erased_user.erase
+ admin_notification.update(segment_recipient: 'all_users')
+ end
+
+ it 'returns list of all active users' do
+ expect(admin_notification.list_of_recipients.count).to eq(2)
+ expect(admin_notification.list_of_recipients).not_to include(erased_user)
+ end
+ end
+
+end
diff --git a/spec/support/common_actions/notifications.rb b/spec/support/common_actions/notifications.rb
index 4ae98c3d0..e387ce1fd 100644
--- a/spec/support/common_actions/notifications.rb
+++ b/spec/support/common_actions/notifications.rb
@@ -53,4 +53,11 @@ module Notifications
field_check_message = 'Please check the marked fields to know how to correct them:'
/\d errors? prevented this #{resource_model} from being saved. #{field_check_message}/
end
+
+ def fill_in_admin_notification_form(options = {})
+ select (options[:segment_recipient] || 'All users'), from: :admin_notification_segment_recipient
+ fill_in :admin_notification_title, with: (options[:title] || 'This is the notification title')
+ fill_in :admin_notification_body, with: (options[:body] || 'This is the notification body')
+ fill_in :admin_notification_link, with: (options[:link] || 'https://www.decide.madrid.es/vota')
+ end
end