From b556fe900026ab8306ad120eaff65c9b9f96cdc6 Mon Sep 17 00:00:00 2001 From: Bertocq Date: Wed, 28 Feb 2018 20:52:18 +0100 Subject: [PATCH 01/12] Create AdminNotification database table --- .../20180221002503_create_admin_notifications.rb | 14 ++++++++++++++ db/schema.rb | 11 +++++++++++ 2 files changed, 25 insertions(+) create mode 100644 db/migrate/20180221002503_create_admin_notifications.rb 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 From 45488d117a96ec9ee71674b8a50ab18705da11b5 Mon Sep 17 00:00:00 2001 From: Bertocq Date: Wed, 28 Feb 2018 20:55:08 +0100 Subject: [PATCH 02/12] Create AdminNotification model, spec and factory --- app/models/admin_notification.rb | 44 +++++++++++++ spec/factories.rb | 14 ++++ spec/models/admin_notification_spec.rb | 91 ++++++++++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 app/models/admin_notification.rb create mode 100644 spec/models/admin_notification_spec.rb 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/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/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 From a10169bac0351730d2088c3650d61bce3269bdc0 Mon Sep 17 00:00:00 2001 From: Bertocq Date: Wed, 28 Feb 2018 20:56:11 +0100 Subject: [PATCH 03/12] Add specs for Admin AdminNotification management --- .../admin/admin_notifications_spec.rb | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 spec/features/admin/admin_notifications_spec.rb diff --git a/spec/features/admin/admin_notifications_spec.rb b/spec/features/admin/admin_notifications_spec.rb new file mode 100644 index 000000000..b994cf9f6 --- /dev/null +++ b/spec/features/admin/admin_notifications_spec.rb @@ -0,0 +1,238 @@ +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) + + click_link "Send" + + page.accept_confirm(confirm_message) + + 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 From a683fcff98ed2f21449b277619544cbeb199b56c Mon Sep 17 00:00:00 2001 From: Bertocq Date: Wed, 28 Feb 2018 20:57:39 +0100 Subject: [PATCH 04/12] Refactor notification partials and index view The notification body has been extracted to a new partial to allow notifications without link to be rendered without needing an if-else duplicating view code. Note the `link_to_if` at _notification partial, as well as the optional body attribute. --- .../notifications/_notification.html.erb | 26 ++++++------------- .../notifications/_notification_body.html.erb | 17 ++++++++++++ app/views/notifications/index.html.erb | 4 ++- 3 files changed, 28 insertions(+), 19 deletions(-) create mode 100644 app/views/notifications/_notification_body.html.erb 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 %> From 4a5235f96fb2cc59fa4c81e0ddbd702ca2b7b960 Mon Sep 17 00:00:00 2001 From: Bertocq Date: Wed, 28 Feb 2018 21:00:45 +0100 Subject: [PATCH 05/12] Allow notifications with explicit/unexistent links Notifications usually link to the associated notifiable, but the new AdminNotifications have a link attribute that may be empty or contain an external or internal url. --- app/controllers/notifications_controller.rb | 6 +++++- app/models/notification.rb | 12 +++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) 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/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 From f695a7faf39b72118477a591c9400fb5941e2d65 Mon Sep 17 00:00:00 2001 From: Bertocq Date: Wed, 28 Feb 2018 21:03:24 +0100 Subject: [PATCH 06/12] Add AdminNotification management at admin panel In the same fashion Newsletters is managed, with the only difference that the preview is using the notification partial in the same way the index of notifications. --- app/assets/javascripts/application.js | 2 + .../send_admin_notification_alert.js.coffee | 4 + .../admin/admin_notifications_controller.rb | 67 ++++++++++++++++ app/views/admin/_menu.html.erb | 18 +++-- .../admin/admin_notifications/_form.html.erb | 13 ++++ .../admin/admin_notifications/edit.html.erb | 4 + .../admin/admin_notifications/index.html.erb | 56 ++++++++++++++ .../admin/admin_notifications/new.html.erb | 4 + .../admin/admin_notifications/show.html.erb | 77 +++++++++++++++++++ config/routes/admin.rb | 6 ++ 10 files changed, 243 insertions(+), 8 deletions(-) create mode 100644 app/assets/javascripts/send_admin_notification_alert.js.coffee create mode 100644 app/controllers/admin/admin_notifications_controller.rb create mode 100644 app/views/admin/admin_notifications/_form.html.erb create mode 100644 app/views/admin/admin_notifications/edit.html.erb create mode 100644 app/views/admin/admin_notifications/index.html.erb create mode 100644 app/views/admin/admin_notifications/new.html.erb create mode 100644 app/views/admin/admin_notifications/show.html.erb 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/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? %> + + + + + + + + + + + <% @admin_notifications.order(created_at: :desc).each do |admin_notification| %> + + + + + + + <% end %> + +
    <%= 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_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 %> +
    +<% 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/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 From e010f95485e60faabc2c669946ef86495090c89d Mon Sep 17 00:00:00 2001 From: Bertocq Date: Wed, 28 Feb 2018 21:05:02 +0100 Subject: [PATCH 07/12] Increase notifications spec with AdminNotification scenarios --- spec/features/notifications_spec.rb | 46 +++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) 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 From 2aa4b5cad611e5dce1ec179ed52cc9edf72117cf Mon Sep 17 00:00:00 2001 From: Bertocq Date: Wed, 28 Feb 2018 21:05:22 +0100 Subject: [PATCH 08/12] Add AdminNotification seed data --- db/dev_seeds.rb | 1 + db/dev_seeds/admin_notifications.rb | 36 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 db/dev_seeds/admin_notifications.rb 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..2e87c6db1 --- /dev/null +++ b/db/dev_seeds/admin_notifications.rb @@ -0,0 +1,36 @@ +section "Creating Admin Notifications & Templates" do + AdminNotification.create!( + title: 'New usage Terms & Conditions!', + segment_recipient: 'administrators', + body: 'We have improved our usage terms & conditions! please check them out to be up to date.', + link: 'http://localhost:3000/condiciones-de-uso' + ).deliver + + AdminNotification.create!( + title: 'Help us translate consul 馃', + segment_recipient: 'administrators', + body: 'If you are proficient in a language, please help us translate consul!.', + link: 'https://crwd.in/consul' + ).deliver + + AdminNotification.create!( + title: 'You can now geolocate proposals & investments', + segment_recipient: 'administrators', + body: 'When you create a proposal or investment you now can specify a point on a map 馃椇' + ).deliver + + AdminNotification.create!( + title: 'We just opened a new Participatory Budget!', + segment_recipient: 'administrators', + link: 'https://www.decide.madrid.es/presupuestos2018/1', + body: 'Start creating proposals for budget investments!' + ).deliver + + AdminNotification.create!( + title: 'We are closing the 2018 Participatory Budget!!', + segment_recipient: 'administrators', + link: 'https://www.decide.madrid.es/presupuestos2018/1', + body: 'Hurry up and create a last proposal before it ends next in two days!', + sent_at: nil + ) +end From 812002dd7be5e6e46b599e66340300abc9ffb97c Mon Sep 17 00:00:00 2001 From: Bertocq Date: Wed, 28 Feb 2018 21:05:34 +0100 Subject: [PATCH 09/12] Add admin notification related translations --- config/locales/en/activerecord.yml | 4 ++++ config/locales/en/admin.yml | 38 +++++++++++++++++++++++++++++- config/locales/es/activerecord.yml | 4 ++++ config/locales/es/admin.yml | 38 +++++++++++++++++++++++++++++- 4 files changed, 82 insertions(+), 2 deletions(-) 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 48ee46db6..b8468967a 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -516,8 +516,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 @@ -611,6 +612,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/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 8929f9e3b..e64657aee 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -517,8 +517,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 @@ -612,6 +613,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 From a883e842babcf7293e2886034c074dafacde1845 Mon Sep 17 00:00:00 2001 From: Bertocq Date: Fri, 6 Apr 2018 11:27:49 +0200 Subject: [PATCH 10/12] Make admin notifications seed data translatable --- config/locales/en/seeds.yml | 15 +++++++++++++ config/locales/es/seeds.yml | 15 +++++++++++++ db/dev_seeds/admin_notifications.rb | 34 +++++++++++------------------ 3 files changed, 43 insertions(+), 21 deletions(-) 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/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/db/dev_seeds/admin_notifications.rb b/db/dev_seeds/admin_notifications.rb index 2e87c6db1..a07077790 100644 --- a/db/dev_seeds/admin_notifications.rb +++ b/db/dev_seeds/admin_notifications.rb @@ -1,36 +1,28 @@ section "Creating Admin Notifications & Templates" do AdminNotification.create!( - title: 'New usage Terms & Conditions!', - segment_recipient: 'administrators', - body: 'We have improved our usage terms & conditions! please check them out to be up to date.', - link: 'http://localhost:3000/condiciones-de-uso' + 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: 'Help us translate consul 馃', - segment_recipient: 'administrators', - body: 'If you are proficient in a language, please help us translate consul!.', - link: 'https://crwd.in/consul' + 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: 'You can now geolocate proposals & investments', - segment_recipient: 'administrators', - body: 'When you create a proposal or investment you now can specify a point on a map 馃椇' + 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: 'We just opened a new Participatory Budget!', + title: I18n.t('seeds.admin_notification.not_sent.title'), + body: I18n.t('seeds.admin_notification.not_sent.body'), segment_recipient: 'administrators', - link: 'https://www.decide.madrid.es/presupuestos2018/1', - body: 'Start creating proposals for budget investments!' - ).deliver - - AdminNotification.create!( - title: 'We are closing the 2018 Participatory Budget!!', - segment_recipient: 'administrators', - link: 'https://www.decide.madrid.es/presupuestos2018/1', - body: 'Hurry up and create a last proposal before it ends next in two days!', sent_at: nil ) end From bbf3faa783964f327f27ad45428f56c9946df326 Mon Sep 17 00:00:00 2001 From: decabeza Date: Wed, 25 Jul 2018 19:12:48 +0200 Subject: [PATCH 11/12] Fixes admin notifications specs --- spec/features/admin/admin_notifications_spec.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/features/admin/admin_notifications_spec.rb b/spec/features/admin/admin_notifications_spec.rb index b994cf9f6..a97170e7c 100644 --- a/spec/features/admin/admin_notifications_spec.rb +++ b/spec/features/admin/admin_notifications_spec.rb @@ -195,9 +195,7 @@ feature "Admin Notifications" do visit admin_admin_notification_path(notification) - click_link "Send" - - page.accept_confirm(confirm_message) + accept_confirm { click_link "Send" } expect(page).to have_content "Notification sent successfully" From 8b6a8703e0bd753417e19b739be67da186f4ed25 Mon Sep 17 00:00:00 2001 From: decabeza Date: Wed, 25 Jul 2018 19:13:31 +0200 Subject: [PATCH 12/12] Adds missing fill_in_admin_notification_form in common actions --- spec/support/common_actions/notifications.rb | 7 +++++++ 1 file changed, 7 insertions(+) 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