From 798257499364cda6164d334bfc1ea29ae266d94a Mon Sep 17 00:00:00 2001 From: iagirre Date: Wed, 16 May 2018 13:03:56 +0200 Subject: [PATCH 1/8] Add migration to generate new columns The migration to generate the columns needed for the feature. There are three new columns: moderated: a boolean that, when true, it means a moderator has hidden a proposal notification. The notification is hidden immediately and it's shown in the moderators proposal notifications index. hidden_at: used by acts_as_paranoid to hide the notification from the list. It's like deleting it, but without deleting definitely from DB. ignored_at: used to mark as a notification as ignored, so that it will appear in marked as reviewed and not in the pending list. WARNING! this doesn't mean that it will disappear from the 'All' filter. --- ...5911_add_moderation_flags_to_proposal_notifications.rb | 8 ++++++++ db/schema.rb | 8 ++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20180516105911_add_moderation_flags_to_proposal_notifications.rb diff --git a/db/migrate/20180516105911_add_moderation_flags_to_proposal_notifications.rb b/db/migrate/20180516105911_add_moderation_flags_to_proposal_notifications.rb new file mode 100644 index 000000000..f62c1e1cf --- /dev/null +++ b/db/migrate/20180516105911_add_moderation_flags_to_proposal_notifications.rb @@ -0,0 +1,8 @@ +class AddModerationFlagsToProposalNotifications < ActiveRecord::Migration + def change + add_column :proposal_notifications, :moderated, :boolean, default: false + add_column :proposal_notifications, :hidden_at, :datetime + add_column :proposal_notifications, :ignored_at, :datetime + add_column :proposal_notifications, :confirmed_hide_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index d1408273d..916342063 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -874,8 +874,12 @@ ActiveRecord::Schema.define(version: 20180519132610) do t.text "body" t.integer "author_id" t.integer "proposal_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "moderated", default: false + t.datetime "hidden_at" + t.datetime "ignored_at" + t.datetime "confirmed_hide_at" end create_table "proposals", force: :cascade do |t| From 21b1d00205a8a20a2691696b6806156b501d5c4b Mon Sep 17 00:00:00 2001 From: iagirre Date: Wed, 16 May 2018 13:03:56 +0200 Subject: [PATCH 2/8] Add backend for the moderators Add new routes for the proposal notifications edition and abilities to let moderators edit it (mark as ignored, hide, etc.). The notifications are not flaggable because they doesn't work like that, but in a similar way. The moderator/administrator is in charge of hidding them through the UI, so the normal users don't flag it as inappropriate. New controller Moderation::ProposalNotification to manage the moderators work. --- .../proposal_notifications_controller.rb | 30 +++++++++++++++++++ app/controllers/concerns/moderate_actions.rb | 2 +- app/controllers/concerns/polymorphic.rb | 2 +- .../proposal_notifications_controller.rb | 24 +++++++++++++++ app/controllers/proposals_controller.rb | 1 + app/models/abilities/moderation.rb | 11 +++++++ app/models/proposal_notification.rb | 28 ++++++++++++++++- app/models/user.rb | 2 ++ config/routes/admin.rb | 7 +++++ config/routes/moderation.rb | 5 ++++ 10 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 app/controllers/admin/proposal_notifications_controller.rb create mode 100644 app/controllers/moderation/proposal_notifications_controller.rb diff --git a/app/controllers/admin/proposal_notifications_controller.rb b/app/controllers/admin/proposal_notifications_controller.rb new file mode 100644 index 000000000..625e670fa --- /dev/null +++ b/app/controllers/admin/proposal_notifications_controller.rb @@ -0,0 +1,30 @@ +class Admin::ProposalNotificationsController < Admin::BaseController + + has_filters %w{without_confirmed_hide all with_confirmed_hide}, only: :index + + before_action :load_proposal, only: [:confirm_hide, :restore] + + def index + @proposal_notifications = ProposalNotification.only_hidden.send(@current_filter).order(hidden_at: :desc) + .page(params[:page]) + end + + def confirm_hide + @proposal_notification.confirm_hide + redirect_to request.query_parameters.merge(action: :index) + end + + def restore + @proposal_notification.restore + @proposal_notification.ignore_flag + Activity.log(current_user, :restore, @proposal_notification) + redirect_to request.query_parameters.merge(action: :index) + end + + private + + def load_proposal + @proposal_notification = ProposalNotification.with_hidden.find(params[:id]) + end + +end diff --git a/app/controllers/concerns/moderate_actions.rb b/app/controllers/concerns/moderate_actions.rb index 05aa0e97f..98e67bc2b 100644 --- a/app/controllers/concerns/moderate_actions.rb +++ b/app/controllers/concerns/moderate_actions.rb @@ -57,4 +57,4 @@ module ModerateActions :author_id end -end \ No newline at end of file +end diff --git a/app/controllers/concerns/polymorphic.rb b/app/controllers/concerns/polymorphic.rb index 6ac4b5c1c..8fd4ab312 100644 --- a/app/controllers/concerns/polymorphic.rb +++ b/app/controllers/concerns/polymorphic.rb @@ -26,4 +26,4 @@ module Polymorphic send("#{resource_name}_params") end -end \ No newline at end of file +end diff --git a/app/controllers/moderation/proposal_notifications_controller.rb b/app/controllers/moderation/proposal_notifications_controller.rb new file mode 100644 index 000000000..64a2cae5a --- /dev/null +++ b/app/controllers/moderation/proposal_notifications_controller.rb @@ -0,0 +1,24 @@ +class Moderation::ProposalNotificationsController < Moderation::BaseController + include ModerateActions + + has_filters %w{pending_review all ignored}, only: :index + has_orders %w{created_at moderated}, only: :index + + before_action :load_resources, only: [:index, :moderate] + + load_and_authorize_resource + + def hide + ProposalNotification.find(params[:id]).update(moderated: true) + end + + private + + def resource_name + 'proposal_notification' + end + + def resource_model + ProposalNotification + end +end diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index 7e679a05d..f43bc8ad9 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -24,6 +24,7 @@ class ProposalsController < ApplicationController def show super @notifications = @proposal.notifications + @notifications = @proposal.notifications.not_moderated @related_contents = Kaminari.paginate_array(@proposal.relationed_contents).page(params[:page]).per(5) redirect_to proposal_path(@proposal), status: :moved_permanently if request.path != proposal_path(@proposal) diff --git a/app/models/abilities/moderation.rb b/app/models/abilities/moderation.rb index f0f823de1..801e752ed 100644 --- a/app/models/abilities/moderation.rb +++ b/app/models/abilities/moderation.rb @@ -52,6 +52,17 @@ module Abilities can :block, User cannot :block, User, id: user.id + + can :hide, ProposalNotification, hidden_at: nil + cannot :hide, ProposalNotification, author_id: user.id + + can :ignore_flag, ProposalNotification, ignored_at: nil, hidden_at: nil + cannot :ignore_flag, ProposalNotification, author_id: user.id + + can :moderate, ProposalNotification + cannot :moderate, ProposalNotification, author_id: user.id + + can :index, ProposalNotification end end end diff --git a/app/models/proposal_notification.rb b/app/models/proposal_notification.rb index 590f19208..4d931d065 100644 --- a/app/models/proposal_notification.rb +++ b/app/models/proposal_notification.rb @@ -10,7 +10,19 @@ class ProposalNotification < ActiveRecord::Base validates :proposal, presence: true validate :minimum_interval - scope :public_for_api, -> { where(proposal_id: Proposal.public_for_api.pluck(:id)) } + scope :public_for_api, -> { where(proposal_id: Proposal.public_for_api.pluck(:id)) } + scope :sort_by_created_at, -> { reorder(created_at: :desc) } + scope :sort_by_moderated, -> { reorder(moderated: :desc) } + + scope :moderated, -> { where(moderated: true) } + scope :not_moderated, -> { where(moderated: false) } + scope :pending_review, -> { moderated.where(ignored_at: nil) } + scope :ignored, -> { moderated.where.not(ignored_at: nil) } + + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases + + after_create :set_author def minimum_interval return true if proposal.try(:notifications).blank? @@ -25,4 +37,18 @@ class ProposalNotification < ActiveRecord::Base proposal end + def ignore_flag + update(ignored_at: Time.current) + end + + def ignored? + ignored_at.present? + end + + private + + def set_author + self.update(author_id: self.proposal.author_id) if self.proposal + end + end diff --git a/app/models/user.rb b/app/models/user.rb index aaec3e411..47e7f8c2c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -182,12 +182,14 @@ class User < ActiveRecord::Base debates_ids = Debate.where(author_id: id).pluck(:id) comments_ids = Comment.where(user_id: id).pluck(:id) proposal_ids = Proposal.where(author_id: id).pluck(:id) + proposal_notification_ids = ProposalNotification.where(author_id: id).pluck(:id) hide Debate.hide_all debates_ids Comment.hide_all comments_ids Proposal.hide_all proposal_ids + ProposalNotification.hide_all proposal_notification_ids end def erase(erase_reason = nil) diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 374b4512c..001456f6e 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -38,6 +38,13 @@ namespace :admin do get :summary, on: :collection end + resources :proposal_notifications, only: :index do + member do + put :restore + put :confirm_hide + end + end + resources :budgets do member do put :calculate_winners diff --git a/config/routes/moderation.rb b/config/routes/moderation.rb index 77eb0a216..90dc9be27 100644 --- a/config/routes/moderation.rb +++ b/config/routes/moderation.rb @@ -22,4 +22,9 @@ namespace :moderation do put :hide, on: :member put :moderate, on: :collection end + + resources :proposal_notifications, only: :index do + put :hide, on: :member + put :moderate, on: :collection + end end From f58bc5d886abdd369b8988def773a978694ff798 Mon Sep 17 00:00:00 2001 From: iagirre Date: Wed, 16 May 2018 15:16:33 +0200 Subject: [PATCH 3/8] Add the menu entry for prop. notifications A new menu for the sidebar has been added, so that the moderator can access to the index from the menu. --- app/views/moderation/_menu.html.erb | 7 +++++++ app/views/moderation/proposal_notifications/index.html.erb | 0 config/i18n-tasks.yml | 1 + config/locales/en/admin.yml | 1 + config/locales/en/moderation.yml | 1 + config/locales/es/admin.yml | 1 + config/locales/es/moderation.yml | 1 + 7 files changed, 12 insertions(+) create mode 100644 app/views/moderation/proposal_notifications/index.html.erb diff --git a/app/views/moderation/_menu.html.erb b/app/views/moderation/_menu.html.erb index 44232ce2c..8d7abb85d 100644 --- a/app/views/moderation/_menu.html.erb +++ b/app/views/moderation/_menu.html.erb @@ -11,6 +11,13 @@ <%= t("moderation.menu.proposals") %> <% end %> + +
  • > + <%= link_to moderation_proposal_notifications_path do %> + + <%= t("moderation.menu.proposal_notifications") %> + <% end %> +
  • <% end %> <% if feature?(:debates) %> diff --git a/app/views/moderation/proposal_notifications/index.html.erb b/app/views/moderation/proposal_notifications/index.html.erb new file mode 100644 index 000000000..e69de29bb diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 2ad35f3c1..15a62d714 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -129,6 +129,7 @@ ignore_unused: - 'admin.banners.index.filters.*' - 'admin.debates.index.filter*' - 'admin.proposals.index.filter*' + - 'admin.proposal_notifications.index.filter*' - 'admin.budgets.index.filter*' - 'admin.budget_investments.index.filter*' - 'admin.spending_proposals.index.filter*' diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index 965278bb9..377841d43 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -504,6 +504,7 @@ en: hidden_comments: Hidden comments hidden_debates: Hidden debates hidden_proposals: Hidden proposals + hidden_proposal_notifications: Hidden proposal notifications hidden_users: Hidden users administrators: Administrators managers: Managers diff --git a/config/locales/en/moderation.yml b/config/locales/en/moderation.yml index f6648d49d..48ff68eb8 100644 --- a/config/locales/en/moderation.yml +++ b/config/locales/en/moderation.yml @@ -47,6 +47,7 @@ en: flagged_comments: Comments flagged_debates: Debates proposals: Proposals + proposal_notifications: Proposals notifications users: Block users proposals: index: diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index 141360e98..11c3cf423 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -504,6 +504,7 @@ es: hidden_comments: Comentarios ocultos hidden_debates: Debates ocultos hidden_proposals: Propuestas ocultas + hidden_proposal_notifications: Notificationes de propuesta ocultas hidden_users: Usuarios bloqueados administrators: Administradores managers: Gestores diff --git a/config/locales/es/moderation.yml b/config/locales/es/moderation.yml index 43fc98790..abf16754f 100644 --- a/config/locales/es/moderation.yml +++ b/config/locales/es/moderation.yml @@ -47,6 +47,7 @@ es: flagged_comments: Comentarios flagged_debates: Debates proposals: Propuestas + proposal_notifications: Notificaciones de propuestas users: Bloquear usuarios proposals: index: From 12ccf466f96e2dee50dfcdb0f73675ccc4f5c4d5 Mon Sep 17 00:00:00 2001 From: iagirre Date: Wed, 16 May 2018 15:54:02 +0200 Subject: [PATCH 4/8] Add the index to moderate the notifications Add the index for moderating the notifications. The tranlations needed have also been added, along with the JS to make it disappear at that moment. --- .../proposal_notifications/hide.js.erb | 3 + .../proposal_notifications/index.html.erb | 67 +++++++++++++++++++ config/i18n-tasks.yml | 2 + config/locales/en/moderation.yml | 19 ++++++ config/locales/es/moderation.yml | 19 ++++++ 5 files changed, 110 insertions(+) create mode 100644 app/views/moderation/proposal_notifications/hide.js.erb diff --git a/app/views/moderation/proposal_notifications/hide.js.erb b/app/views/moderation/proposal_notifications/hide.js.erb new file mode 100644 index 000000000..be8381f6f --- /dev/null +++ b/app/views/moderation/proposal_notifications/hide.js.erb @@ -0,0 +1,3 @@ +var proposal_notification_id = '<%= dom_id(@proposal_notification) %>'; +App.ModeratorProposalNotifications.add_class_faded(proposal_notification_id); +App.ModeratorProposalNotifications.hide_moderator_actions(proposal_notification_id); diff --git a/app/views/moderation/proposal_notifications/index.html.erb b/app/views/moderation/proposal_notifications/index.html.erb index e69de29bb..bc8615c35 100644 --- a/app/views/moderation/proposal_notifications/index.html.erb +++ b/app/views/moderation/proposal_notifications/index.html.erb @@ -0,0 +1,67 @@ +

    <%= t("moderation.proposal_notifications.index.title") %>

    + +<%= render 'shared/filter_subnav', i18n_namespace: "moderation.proposal_notifications.index" %> + +

    <%= page_entries_info @proposal_notifications %>

    +
    + <%= t("moderation.proposal_notifications.index.order") %> + <%= render 'shared/order_selector', i18n_namespace: "moderation.proposal_notifications.index" %> +
    + +<%= form_tag moderate_moderation_proposal_notifications_path(request.query_parameters), method: :put do %> +

    + <%= t('shared.check') %>: + <%= link_to t('shared.check_all'), '#', data: {check_all: "proposal_notification_ids[]"} %> + | + <%= link_to t('shared.check_none'), '#', data: {check_none: "proposal_notification_ids[]"} %> +

    + + + + + + + <% @proposal_notifications.each do |proposal_notification| %> + + + + + <% end %> +
    + <%= t("moderation.proposal_notifications.index.headers.proposal_notification") %> + + <%= t("moderation.proposal_notifications.index.headers.moderate") %> +
    + <%= link_to proposal_notification.title, proposal_notification, target: "_blank" %> +
    + <%= l proposal_notification.updated_at.to_date %> +
    +
    + <%= proposal_notification.body %> +
    +
    + <%= check_box_tag "proposal_notification_ids[]", proposal_notification.id, nil, id: "#{dom_id(proposal_notification)}_check" %> +
    + + <%= submit_tag t('moderation.proposal_notifications.index.block_authors'), + name: "block_authors", + class: "button hollow alert", + data: {confirm: t('moderation.proposal_notifications.index.confirm')} + %> + +
    + <%= submit_tag t('moderation.proposal_notifications.index.hide_proposal_notifications'), + name: "hide_proposal_notifications", + class: "button hollow alert", + data: {confirm: t('moderation.proposal_notifications.index.confirm')} + %> + <%= submit_tag t('moderation.proposal_notifications.index.ignore_flags'), + name: "ignore_flags", + class: "button hollow", + data: {confirm: t('moderation.proposal_notifications.index.confirm')} + %> +
    + + <%= paginate @proposal_notifications %> + +<% end %> diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 15a62d714..a79e521bf 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -152,6 +152,8 @@ ignore_unused: - 'moderation.proposals.index.order*' - 'moderation.debates.index.filter*' - 'moderation.debates.index.order*' + - 'moderation.proposal_notifications.index.filter*' + - 'moderation.proposal_notifications.index.order*' - 'valuation.spending_proposals.index.filter*' - 'valuation.budgets.index.filter*' - 'valuation.budget_investments.index.filter*' diff --git a/config/locales/en/moderation.yml b/config/locales/en/moderation.yml index 48ff68eb8..5ed2f69bb 100644 --- a/config/locales/en/moderation.yml +++ b/config/locales/en/moderation.yml @@ -68,6 +68,25 @@ en: created_at: Most recent flags: Most flagged title: Proposals + proposal_notifications: + index: + block_authors: Block authors + confirm: Are you sure? + filter: Filter + filters: + all: All + pending_review: Pending review + ignored: Mark as viewed + headers: + moderate: Moderate + proposal_notification: Proposal notification + hide_proposal_notifications: Hide proposals + ignore_flags: Mark as viewed + order: Order by + orders: + created_at: Most recent + moderated: Moderated + title: Proposal notifications users: index: hidden: Blocked diff --git a/config/locales/es/moderation.yml b/config/locales/es/moderation.yml index abf16754f..c51013aee 100644 --- a/config/locales/es/moderation.yml +++ b/config/locales/es/moderation.yml @@ -68,6 +68,25 @@ es: created_at: Más recientes flags: Más denunciadas title: Propuestas + proposal_notifications: + index: + block_authors: Bloquear autores + confirm: '¿Estás seguro?' + filter: Filtro + filters: + all: Todas + pending_review: Pendientes de revisión + ignored: Marcadas como revisadas + headers: + moderate: Moderar + proposal_notification: Notificación de propuesta + hide_proposal_notifications: Ocultar notificaciones + ignore_flags: Marcar como revisadas + order: Ordenar por + orders: + created_at: Más recientes + moderated: Moderadas + title: Notificaciones de propuestas users: index: hidden: Bloqueado From 8dadb228f1692c0d6d41c268e5221efaa05accd5 Mon Sep 17 00:00:00 2001 From: iagirre Date: Fri, 18 May 2018 11:12:41 +0200 Subject: [PATCH 5/8] Add specs to test the prop. notifications hide action Specs that test if the proposal notifications hide action works. It also tests if the admin part works (mark as reviewed, ignore them, etc.) --- spec/factories.rb | 17 ++ .../admin/proposal_notifications_spec.rb | 94 +++++++++ .../moderation/proposal_notifications_spec.rb | 189 ++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 spec/features/admin/proposal_notifications_spec.rb create mode 100644 spec/features/moderation/proposal_notifications_spec.rb diff --git a/spec/factories.rb b/spec/factories.rb index 3aeaef9e2..50dd0dec8 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -749,6 +749,23 @@ FactoryBot.define do sequence(:title) { |n| "Thank you for supporting my proposal #{n}" } sequence(:body) { |n| "Please let others know so we can make it happen #{n}" } proposal + association :author, factory: :user + + trait :moderated do + moderated true + end + + trait :ignored do + ignored_at Date.current + end + + trait :hidden do + hidden_at Date.current + end + + trait :with_confirmed_hide do + confirmed_hide_at Time.current + end end factory :direct_message do diff --git a/spec/features/admin/proposal_notifications_spec.rb b/spec/features/admin/proposal_notifications_spec.rb new file mode 100644 index 000000000..9186e228f --- /dev/null +++ b/spec/features/admin/proposal_notifications_spec.rb @@ -0,0 +1,94 @@ +require 'rails_helper' + +feature 'Admin proposal notifications' do + + background do + admin = create(:administrator) + login_as(admin.user) + end + + scenario 'List shows all relevant info' do + proposal_notification = create(:proposal_notification, :hidden) + visit admin_proposal_notifications_path + + expect(page).to have_content(proposal_notification.title) + expect(page).to have_content(proposal_notification.body) + end + + scenario 'Restore' do + proposal_notification = create(:proposal_notification, :hidden, created_at: Date.current - 5.days) + visit admin_proposal_notifications_path + + click_link 'Restore' + + expect(page).not_to have_content(proposal_notification.title) + + expect(proposal_notification.reload).not_to be_hidden + expect(proposal_notification).to be_ignored + end + + scenario 'Confirm hide' do + proposal_notification = create(:proposal_notification, :hidden, created_at: Date.current - 5.days) + visit admin_proposal_notifications_path + + click_link 'Confirm moderation' + + expect(page).not_to have_content(proposal_notification.title) + click_link('Confirmed') + expect(page).to have_content(proposal_notification.title) + + expect(proposal_notification.reload).to be_confirmed_hide + end + + scenario "Current filter is properly highlighted" do + visit admin_proposal_notifications_path + expect(page).not_to have_link('Pending') + expect(page).to have_link('All') + expect(page).to have_link('Confirmed') + + visit admin_proposal_notifications_path(filter: 'Pending') + expect(page).not_to have_link('Pending') + expect(page).to have_link('All') + expect(page).to have_link('Confirmed') + + visit admin_proposal_notifications_path(filter: 'all') + expect(page).to have_link('Pending') + expect(page).not_to have_link('All') + expect(page).to have_link('Confirmed') + + visit admin_proposal_notifications_path(filter: 'with_confirmed_hide') + expect(page).to have_link('All') + expect(page).to have_link('Pending') + expect(page).not_to have_link('Confirmed') + end + + scenario "Filtering proposals" do + create(:proposal_notification, :hidden, title: "Unconfirmed notification") + create(:proposal_notification, :hidden, :with_confirmed_hide, title: "Confirmed notification") + + visit admin_proposal_notifications_path(filter: 'pending') + expect(page).to have_content('Unconfirmed notification') + expect(page).not_to have_content('Confirmed notification') + + visit admin_proposal_notifications_path(filter: 'all') + expect(page).to have_content('Unconfirmed notification') + expect(page).to have_content('Confirmed notification') + + visit admin_proposal_notifications_path(filter: 'with_confirmed_hide') + expect(page).not_to have_content('Unconfirmed notification') + expect(page).to have_content('Confirmed notification') + end + + scenario "Action links remember the pagination setting and the filter" do + per_page = Kaminari.config.default_per_page + (per_page + 2).times { create(:proposal_notification, :hidden, :with_confirmed_hide) } + + visit admin_proposal_notifications_path(filter: 'with_confirmed_hide', page: 2) + + click_on('Restore', match: :first, exact: true) + + expect(current_url).to include('filter=with_confirmed_hide') + expect(current_url).to include('page=2') + end + +end diff --git a/spec/features/moderation/proposal_notifications_spec.rb b/spec/features/moderation/proposal_notifications_spec.rb new file mode 100644 index 000000000..4af3a815d --- /dev/null +++ b/spec/features/moderation/proposal_notifications_spec.rb @@ -0,0 +1,189 @@ +require 'rails_helper' + +feature 'Moderate proposal notifications' do + + scenario 'Hide', :js do + citizen = create(:user) + proposal = create(:proposal) + proposal_notification = create(:proposal_notification, proposal: proposal, created_at: Date.current - 4.days) + moderator = create(:moderator) + + login_as(moderator.user) + visit proposal_path(proposal) + click_link "Notifications (1)" + + within("#proposal_notification_#{proposal_notification.id}") do + accept_confirm { click_link 'Hide' } + end + + expect(page).to have_css("#proposal_notification_#{proposal.id}.faded") + + logout + login_as(citizen) + visit proposal_path(proposal) + + expect(page).to have_content "Notifications (0)" + end + + scenario 'Can not hide own proposal notification' do + moderator = create(:moderator) + proposal = create(:proposal, author: moderator.user) + proposal_notification = create(:proposal_notification, proposal: proposal, created_at: Date.current - 4.days) + + login_as(moderator.user) + visit proposal_path(proposal) + + within("#proposal_notification_#{proposal_notification.id}") do + expect(page).not_to have_link('Hide') + expect(page).not_to have_link('Block author') + end + end + + feature '/moderation/ screen' do + + background do + moderator = create(:moderator) + login_as(moderator.user) + end + + feature 'moderate in bulk' do + feature "When a proposal has been selected for moderation" do + background do + proposal = create(:proposal) + @proposal_notification = create(:proposal_notification, proposal: proposal, created_at: Date.current - 4.days) + visit moderation_proposal_notifications_path + within('.menu.simple') do + click_link "All" + end + + within("#proposal_notification_#{@proposal_notification.id}") do + check "proposal_notification_#{@proposal_notification.id}_check" + end + end + + scenario 'Hide the proposal' do + click_on "Hide proposals" + expect(page).not_to have_css("#proposal_notification_#{@proposal_notification.id}") + expect(@proposal_notification.reload).to be_hidden + expect(@proposal_notification.author).not_to be_hidden + end + + scenario 'Block the author' do + author = create(:user) + @proposal_notification.update(author: author) + click_on "Block authors" + expect(page).not_to have_css("#proposal_notification_#{@proposal_notification.id}") + expect(@proposal_notification.reload).to be_hidden + expect(author.reload).to be_hidden + end + + scenario 'Ignore the proposal' do + click_button "Mark as viewed" + + expect(@proposal_notification.reload).to be_ignored + expect(@proposal_notification.reload).not_to be_hidden + expect(@proposal_notification.author).not_to be_hidden + end + end + + scenario "select all/none", :js do + create_list(:proposal_notification, 2) + + visit moderation_proposal_notifications_path + + within('.js-check') { click_on 'All' } + + expect(all('input[type=checkbox]')).to all(be_checked) + + within('.js-check') { click_on 'None' } + + all('input[type=checkbox]').each do |checkbox| + expect(checkbox).not_to be_checked + end + end + + scenario "remembering page, filter and order" do + create_list(:proposal, 52) + + visit moderation_proposal_notifications_path(filter: 'all', page: '2', order: 'created_at') + + click_button "Mark as viewed" + + expect(page).to have_selector('.js-order-selector[data-order="created_at"]') + + expect(current_url).to include('filter=all') + expect(current_url).to include('page=2') + expect(current_url).to include('order=created_at') + end + end + + scenario "Current filter is properly highlighted" do + visit moderation_proposal_notifications_path + expect(page).not_to have_link('Pending review') + expect(page).to have_link('All') + expect(page).to have_link('Mark as viewed') + + visit moderation_proposal_notifications_path(filter: 'all') + within('.menu.simple') do + expect(page).not_to have_link('All') + expect(page).to have_link('Pending review') + expect(page).to have_link('Mark as viewed') + end + + visit moderation_proposal_notifications_path(filter: 'pending_review') + within('.menu.simple') do + expect(page).to have_link('All') + expect(page).not_to have_link('Pending review') + expect(page).to have_link('Mark as viewed') + end + + visit moderation_proposal_notifications_path(filter: 'ignored') + within('.menu.simple') do + expect(page).to have_link('All') + expect(page).to have_link('Pending review') + expect(page).not_to have_link('Marked as viewed') + end + end + + scenario "Filtering proposals" do + proposal = create(:proposal) + create(:proposal_notification, title: "Regular proposal", proposal: proposal) + create(:proposal_notification, :moderated, title: "Pending proposal", proposal: proposal) + create(:proposal_notification, :hidden, title: "Hidden proposal", proposal: proposal) + create(:proposal_notification, :moderated, :ignored, title: "Ignored proposal", proposal: proposal) + + visit moderation_proposal_notifications_path(filter: 'all') + expect(page).to have_content('Regular proposal') + expect(page).to have_content('Pending proposal') + expect(page).not_to have_content('Hidden proposal') + expect(page).to have_content('Ignored proposal') + + visit moderation_proposal_notifications_path(filter: 'pending_review') + expect(page).not_to have_content('Regular proposal') + expect(page).to have_content('Pending proposal') + expect(page).not_to have_content('Hidden proposal') + expect(page).not_to have_content('Ignored proposal') + + visit moderation_proposal_notifications_path(filter: 'ignored') + expect(page).not_to have_content('Regular proposal') + expect(page).not_to have_content('Pending proposal') + expect(page).not_to have_content('Hidden proposal') + expect(page).to have_content('Ignored proposal') + end + + scenario "sorting proposal notifications" do + moderated_notification = create(:proposal_notification, :moderated, title: "Moderated notification", created_at: Time.current - 1.day) + moderated_new_notification = create(:proposal_notification, :moderated, title: "Moderated new notification", created_at: Time.current - 12.hours) + newer_notification = create(:proposal_notification, title: "Newer notification", created_at: Time.current) + old_moderated_notification = create(:proposal_notification, :moderated, title: "Older notification", created_at: Time.current - 2.days) + + visit moderation_proposal_notifications_path(filter: 'all', order: 'created_at') + + expect(moderated_new_notification.title).to appear_before(moderated_notification.title) + + visit moderation_proposal_notifications_path(filter: 'all', order: 'moderated') + + expect(old_moderated_notification.title).to appear_before(newer_notification.title) + end + end +end From 88a8aaa0836639a8be329d977e632bc5234eea2a Mon Sep 17 00:00:00 2001 From: iagirre Date: Fri, 18 May 2018 11:41:29 +0200 Subject: [PATCH 6/8] Add hide button in the user interface The little menu with the hide notification link and block user link has been added to each proposal notification. JS for adding the fade efect has also been added. --- app/assets/javascripts/application.js | 1 + .../moderator_proposal_notifications.js.coffee | 7 +++++++ app/assets/stylesheets/layout.scss | 3 ++- app/views/proposal_notifications/_actions.html.erb | 14 ++++++++++++++ app/views/proposals/_notifications.html.erb | 14 ++++++++++---- config/locales/en/activerecord.yml | 3 +++ config/locales/es/activerecord.yml | 3 +++ 7 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 app/assets/javascripts/moderator_proposal_notifications.js.coffee create mode 100644 app/views/proposal_notifications/_actions.html.erb diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 1c97069b0..a9e44d249 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -33,6 +33,7 @@ //= require moderator_comment //= require moderator_debates //= require moderator_proposals +//= require moderator_proposal_notifications //= require prevent_double_submission //= require gettext //= require annotator diff --git a/app/assets/javascripts/moderator_proposal_notifications.js.coffee b/app/assets/javascripts/moderator_proposal_notifications.js.coffee new file mode 100644 index 000000000..b2e8fbf44 --- /dev/null +++ b/app/assets/javascripts/moderator_proposal_notifications.js.coffee @@ -0,0 +1,7 @@ +App.ModeratorProposalNotifications = + + add_class_faded: (id) -> + $("##{id}").addClass("faded") + + hide_moderator_actions: (id) -> + $("##{id} .js-moderator-proposal-notifications-actions:first").hide() diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss index 94ef4a7a9..3914f0fa7 100644 --- a/app/assets/stylesheets/layout.scss +++ b/app/assets/stylesheets/layout.scss @@ -1963,7 +1963,8 @@ table { } } -.comment-body { +.comment-body, +.notification-body { img { margin-right: $line-height / 2; diff --git a/app/views/proposal_notifications/_actions.html.erb b/app/views/proposal_notifications/_actions.html.erb new file mode 100644 index 000000000..bcac9cb4f --- /dev/null +++ b/app/views/proposal_notifications/_actions.html.erb @@ -0,0 +1,14 @@ +
    + + <% if can? :hide, notification %> + <%= link_to t("admin.actions.hide").capitalize, hide_moderation_proposal_notification_path(notification), + method: :put, remote: true, data: { confirm: t('admin.actions.confirm') } %> + <% end %> + + <% if can? :hide, notification.author %> +  •  + <%= link_to t("admin.actions.hide_author").capitalize, hide_moderation_user_path(notification.author_id), + method: :put, data: { confirm: t('admin.actions.confirm') } %> + <% end %> + +
    diff --git a/app/views/proposals/_notifications.html.erb b/app/views/proposals/_notifications.html.erb index 6a693c601..fe96ba741 100644 --- a/app/views/proposals/_notifications.html.erb +++ b/app/views/proposals/_notifications.html.erb @@ -1,6 +1,6 @@