Implements #150
Adds an entry inside moderation section that allows moderators to check pending tasks and mark them as solved.
This commit is contained in:
27
app/controllers/moderation/administrator_tasks_controller.rb
Normal file
27
app/controllers/moderation/administrator_tasks_controller.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Moderation::AdministratorTasksController < Moderation::BaseController
|
||||
helper_method :administrator_task
|
||||
|
||||
def index
|
||||
authorize! :index, AdministratorTask
|
||||
@administrator_tasks = AdministratorTask.pending
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize! :edit, administrator_task
|
||||
end
|
||||
|
||||
def update
|
||||
authorize! :update, administrator_task
|
||||
|
||||
administrator_task.update(user: current_user, executed_at: Time.now)
|
||||
redirect_to moderation_administrator_tasks_path, { flash: { notice: t('.success') } }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def administrator_task
|
||||
@administrator_task ||= AdministratorTask.find(params[:id])
|
||||
end
|
||||
end
|
||||
@@ -4,7 +4,7 @@
|
||||
class ProposalsDashboardController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
helper_method :proposal, :proposed_actions
|
||||
helper_method :proposal, :proposed_actions, :proposal_dashboard_action
|
||||
respond_to :html
|
||||
layout 'proposals_dashboard'
|
||||
|
||||
@@ -21,12 +21,41 @@ class ProposalsDashboardController < ApplicationController
|
||||
|
||||
def execute
|
||||
authorize! :dashboard, proposal
|
||||
|
||||
ProposalExecutedDashboardAction.create(proposal: proposal, proposal_dashboard_action: proposal_dashboard_action, executed_at: Time.now)
|
||||
redirect_to proposal_dashboard_index_path(proposal.to_param)
|
||||
end
|
||||
|
||||
def new_request
|
||||
authorize! :dashboard, proposal
|
||||
@proposal_executed_dashboard_action = ProposalExecutedDashboardAction.new
|
||||
end
|
||||
|
||||
def create_request
|
||||
authorize! :dashboard, proposal
|
||||
|
||||
source_params = proposal_executed_dashboard_action_params.merge(
|
||||
proposal: proposal,
|
||||
proposal_dashboard_action: proposal_dashboard_action,
|
||||
executed_at: Time.now
|
||||
)
|
||||
|
||||
@proposal_executed_dashboard_action = ProposalExecutedDashboardAction.new(source_params)
|
||||
if @proposal_executed_dashboard_action.save
|
||||
AdministratorTask.create(source: @proposal_executed_dashboard_action)
|
||||
|
||||
redirect_to proposal_dashboard_index_path(proposal.to_param), { flash: { info: t('.success') } }
|
||||
else
|
||||
render :new_request
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def proposal_executed_dashboard_action_params
|
||||
params.require(:proposal_executed_dashboard_action).permit(:comments)
|
||||
end
|
||||
|
||||
def proposal_dashboard_action
|
||||
@proposal_dashboard_action ||= ProposalDashboardAction.find(params[:id])
|
||||
end
|
||||
|
||||
@@ -63,6 +63,7 @@ module Abilities
|
||||
cannot :moderate, ProposalNotification, author_id: user.id
|
||||
|
||||
can :index, ProposalNotification
|
||||
can :manage, AdministratorTask
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
13
app/models/administrator_task.rb
Normal file
13
app/models/administrator_task.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AdministratorTask < ActiveRecord::Base
|
||||
belongs_to :source, polymorphic: true
|
||||
belongs_to :user
|
||||
|
||||
validates :source, presence: true
|
||||
|
||||
default_scope { order(created_at: :asc) }
|
||||
|
||||
scope :pending, -> { where(executed_at: nil) }
|
||||
scope :done, -> { where.not(executed_at: nil) }
|
||||
end
|
||||
@@ -59,4 +59,8 @@ class ProposalDashboardAction < ActiveRecord::Base
|
||||
def request_to_administrators?
|
||||
request_to_administrators || false
|
||||
end
|
||||
|
||||
def request_to_administrators?
|
||||
request_to_administrators
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,7 +4,14 @@ class ProposalExecutedDashboardAction < ActiveRecord::Base
|
||||
belongs_to :proposal
|
||||
belongs_to :proposal_dashboard_action
|
||||
|
||||
has_many :administrator_tasks, as: :source, dependent: :destroy
|
||||
|
||||
validates :proposal, presence: true, uniqueness: { scope: :proposal_dashboard_action }
|
||||
validates :proposal_dashboard_action, presence: true
|
||||
validates :executed_at, presence: true
|
||||
validates :comments, presence: true, allow_blank: false, if: :comments_required?
|
||||
|
||||
def comments_required?
|
||||
proposal_dashboard_action&.request_to_administrators? || false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,5 +42,12 @@
|
||||
<%= t("moderation.menu.users") %>
|
||||
<% end %>
|
||||
</li>
|
||||
|
||||
<li <%= 'class=is-active' if controller_name == 'administrator_tasks' %>>
|
||||
<%= link_to moderation_administrator_tasks_path do %>
|
||||
<span class="icon-check"></span>
|
||||
<%= t 'moderation.menu.administrator_tasks' %>
|
||||
<% end %>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
15
app/views/moderation/administrator_tasks/_form.html.erb
Normal file
15
app/views/moderation/administrator_tasks/_form.html.erb
Normal file
@@ -0,0 +1,15 @@
|
||||
<%= form_for [:moderation, administrator_task] do |f| %>
|
||||
|
||||
<div class="callout">
|
||||
<h5><%=t '.proposal', title: administrator_task.source.proposal.title %></h5>
|
||||
<p><%=t '.request', title: administrator_task.source.proposal_dashboard_action.title %></p>
|
||||
<%== administrator_task.source.comments unless administrator_task.source.comments.blank? %>
|
||||
<%= link_to t('.check_details'), proposal_path(administrator_task.source.proposal), target: '_blank' %>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="actions small-12 large-3 medium-3 column">
|
||||
<%= f.submit(class: 'button expanded', value: t('.solve')) %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
9
app/views/moderation/administrator_tasks/edit.html.erb
Normal file
9
app/views/moderation/administrator_tasks/edit.html.erb
Normal file
@@ -0,0 +1,9 @@
|
||||
<div class="row">
|
||||
<div class="small-12 column">
|
||||
<%= back_link_to moderation_administrator_tasks_path, t('.back') %>
|
||||
|
||||
<h1><%= t('.solving') %></h1>
|
||||
|
||||
<%= render 'form' %>
|
||||
</div>
|
||||
</div>
|
||||
34
app/views/moderation/administrator_tasks/index.html.erb
Normal file
34
app/views/moderation/administrator_tasks/index.html.erb
Normal file
@@ -0,0 +1,34 @@
|
||||
<h2 class="inline-block">
|
||||
<%= AdministratorTask.model_name.human(count: 2) %>
|
||||
</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= AdministratorTask.human_attribute_name(:source) %></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<% if @administrator_tasks.empty? %>
|
||||
<tr>
|
||||
<td colspan="100%"><%= t '.no_records' %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
|
||||
<% @administrator_tasks.each do |task| %>
|
||||
<tr id="<%= dom_id(task) %>">
|
||||
<td>
|
||||
<%= task.source.proposal.title %>:
|
||||
<%= task.source.proposal_dashboard_action.title %>
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
<%= link_to t('.solve'),
|
||||
edit_moderation_administrator_task_path(task),
|
||||
class: 'edit-banner button hollow' %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
12
app/views/proposals_dashboard/_form.html.erb
Normal file
12
app/views/proposals_dashboard/_form.html.erb
Normal file
@@ -0,0 +1,12 @@
|
||||
<%= form_for @proposal_executed_dashboard_action,
|
||||
url: create_request_proposal_dashboard_url(proposal, proposal_dashboard_action) do |f| %>
|
||||
<%= render 'shared/errors', resource: @proposal_executed_dashboard_action %>
|
||||
|
||||
<div class="ckeditor small-12 column">
|
||||
<%= f.label :comments %>
|
||||
<%= f.cktext_area :comments, ckeditor: { language: I18n.locale }, label: false %>
|
||||
</div>
|
||||
<div class="actions small-12 column">
|
||||
<%= f.submit(class: 'button', value: t('.request')) %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -15,11 +15,16 @@
|
||||
<td>
|
||||
<% if action.proposals.where(id: proposal.id).any? %>
|
||||
<%=l action.proposal_executed_dashboard_actions.find_by(proposal: proposal).executed_at, format: :short %>
|
||||
<% else %>
|
||||
<% if action.request_to_administrators? %>
|
||||
<%= link_to t('.execute'),
|
||||
new_request_proposal_dashboard_path(proposal, action) %>
|
||||
<% else %>
|
||||
<%= link_to t('.execute'),
|
||||
execute_proposal_dashboard_path(proposal.to_param, action.to_param),
|
||||
method: 'post',
|
||||
data: { confirm: t('admin.actions.confirm') } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
13
app/views/proposals_dashboard/new_request.html.erb
Normal file
13
app/views/proposals_dashboard/new_request.html.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
<div class="proposals-dashboard-new-request-form row">
|
||||
|
||||
<div class="small-12 medium-9 column">
|
||||
<%= back_link_to %>
|
||||
|
||||
<h1><%= proposal_dashboard_action.title %></h1>
|
||||
<div data-alert class="callout primary">
|
||||
<%= proposal_dashboard_action.description %>
|
||||
</div>
|
||||
|
||||
<%= render 'proposals_dashboard/form' %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -109,6 +109,9 @@ en:
|
||||
proposal_dashboard_action:
|
||||
one: Proposal dashboard action
|
||||
other: Proposal dashboard actions
|
||||
administrator_task:
|
||||
one: Task
|
||||
other: Tasks
|
||||
attributes:
|
||||
budget:
|
||||
name: "Name"
|
||||
@@ -272,6 +275,12 @@ en:
|
||||
order: Order
|
||||
active: Active
|
||||
action_type: Type
|
||||
proposal_executed_dashboard_action:
|
||||
comments: Comments for the administrator
|
||||
administrator_task:
|
||||
source: Source
|
||||
user: Executed by
|
||||
executed_at: Executed at
|
||||
errors:
|
||||
models:
|
||||
user:
|
||||
|
||||
@@ -502,6 +502,10 @@ en:
|
||||
actions: Actions
|
||||
proposed_action:
|
||||
execute: Execute
|
||||
form:
|
||||
request: Request
|
||||
create_request:
|
||||
success: The request for the administrator has been successfully sent.
|
||||
polls:
|
||||
all: "All"
|
||||
no_dates: "no date assigned"
|
||||
|
||||
@@ -49,6 +49,7 @@ en:
|
||||
proposals: Proposals
|
||||
proposal_notifications: Proposals notifications
|
||||
users: Block users
|
||||
administrator_tasks: Pending tasks
|
||||
proposals:
|
||||
index:
|
||||
block_authors: Block authors
|
||||
@@ -95,3 +96,17 @@ en:
|
||||
search_placeholder: email or name of user
|
||||
title: Block users
|
||||
notice_hide: User blocked. All of this user's debates and comments have been hidden.
|
||||
administrator_tasks:
|
||||
index:
|
||||
solve: Solve
|
||||
no_records: There are no pending tasks
|
||||
edit:
|
||||
back: Back to pending tasks list
|
||||
solving: Solve pending task
|
||||
form:
|
||||
solve: Mark as solved
|
||||
proposal: "The proposal: %{title}"
|
||||
request: "Has requested: %{title}"
|
||||
check_details: Check the proposal details
|
||||
update:
|
||||
success: The task has been marked as solved.
|
||||
|
||||
@@ -109,6 +109,9 @@ es:
|
||||
proposal_dashboard_action:
|
||||
one: Acción del panel de control de propuestas
|
||||
other: Acciones del panel de control de propuestas
|
||||
administrator_task:
|
||||
one: Tarea
|
||||
other: Tareas
|
||||
attributes:
|
||||
budget:
|
||||
name: "Nombre"
|
||||
@@ -273,6 +276,12 @@ es:
|
||||
order: Orden
|
||||
active: Activa
|
||||
action_type: Tipo
|
||||
proposal_executed_dashboard_action:
|
||||
comments: Comentarios para el administrador
|
||||
administrator_task:
|
||||
source: Fuente
|
||||
user: Ejecutado por
|
||||
executed_at: Ejecutado el
|
||||
errors:
|
||||
models:
|
||||
user:
|
||||
|
||||
@@ -502,6 +502,10 @@ es:
|
||||
actions: Acciones
|
||||
proposed_action:
|
||||
execute: Ejecutar
|
||||
form:
|
||||
request: Solicitar
|
||||
create_request:
|
||||
success: La petición ha sido correctamente enviada al administrador.
|
||||
polls:
|
||||
all: "Todas"
|
||||
no_dates: "sin fecha asignada"
|
||||
|
||||
@@ -49,6 +49,7 @@ es:
|
||||
proposals: Propuestas
|
||||
proposal_notifications: Notificaciones de propuestas
|
||||
users: Bloquear usuarios
|
||||
administrator_tasks: Tareas pendientes
|
||||
proposals:
|
||||
index:
|
||||
block_authors: Bloquear autores
|
||||
@@ -95,3 +96,17 @@ es:
|
||||
search_placeholder: email o nombre de usuario
|
||||
title: Bloquear usuarios
|
||||
notice_hide: Usuario bloqueado. Se han ocultado todos sus debates y comentarios.
|
||||
administrator_tasks:
|
||||
index:
|
||||
solve: Resolver
|
||||
no_records: No hay tareas pendientes
|
||||
edit:
|
||||
back: Volver a la lista de tareas pendientes
|
||||
solving: Resolver tarea pendiente
|
||||
form:
|
||||
solve: Marcar como resuelta
|
||||
proposal: "La propuesta: %{title}"
|
||||
request: "Ha solicitado: %{title}"
|
||||
check_details: Ver los detalles de la propuesta
|
||||
update:
|
||||
success: La tarea ha sido marcada como resuelta
|
||||
|
||||
@@ -27,4 +27,6 @@ namespace :moderation do
|
||||
put :hide, on: :member
|
||||
put :moderate, on: :collection
|
||||
end
|
||||
|
||||
resources :administrator_tasks, only: %i[index edit update]
|
||||
end
|
||||
|
||||
@@ -6,6 +6,8 @@ resources :proposals do
|
||||
|
||||
member do
|
||||
post :execute
|
||||
get :new_request
|
||||
post :create_request
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
11
db/migrate/20180615102215_create_administrator_tasks.rb
Normal file
11
db/migrate/20180615102215_create_administrator_tasks.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class CreateAdministratorTasks < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :administrator_tasks do |t|
|
||||
t.references :source, polymorphic: true, index: true
|
||||
t.references :user, index: true, foreign_key: true
|
||||
t.datetime :executed_at
|
||||
|
||||
t.timestamps null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
15
db/schema.rb
15
db/schema.rb
@@ -11,7 +11,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20180711224810) do
|
||||
ActiveRecord::Schema.define(version: 20180615102215) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@@ -30,6 +30,18 @@ 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 "administrator_tasks", force: :cascade do |t|
|
||||
t.integer "source_id"
|
||||
t.string "source_type"
|
||||
t.integer "user_id"
|
||||
t.datetime "executed_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
add_index "administrator_tasks", ["source_type", "source_id"], name: "index_administrator_tasks_on_source_type_and_source_id", using: :btree
|
||||
add_index "administrator_tasks", ["user_id"], name: "index_administrator_tasks_on_user_id", using: :btree
|
||||
|
||||
create_table "administrators", force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
end
|
||||
@@ -1301,6 +1313,7 @@ ActiveRecord::Schema.define(version: 20180711224810) do
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
add_foreign_key "administrator_tasks", "users"
|
||||
add_foreign_key "administrators", "users"
|
||||
add_foreign_key "annotations", "legacy_legislations"
|
||||
add_foreign_key "annotations", "users"
|
||||
|
||||
@@ -1081,4 +1081,20 @@ LOREM_IPSUM
|
||||
comments { Faker::Lorem.sentence(10) }
|
||||
end
|
||||
end
|
||||
|
||||
factory :administrator_task do
|
||||
source { |s| s.association(:proposal_executed_dashboard_action, :with_comments) }
|
||||
user
|
||||
executed_at { Time.now }
|
||||
|
||||
trait :pending do
|
||||
user { nil }
|
||||
executed_at { nil }
|
||||
end
|
||||
|
||||
trait :done do
|
||||
user
|
||||
executed_at { Time.now }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
74
spec/features/moderation/administrator_tasks_spec.rb
Normal file
74
spec/features/moderation/administrator_tasks_spec.rb
Normal file
@@ -0,0 +1,74 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe 'Administrator tasks moderation' do
|
||||
let(:moderator) { create(:moderator) }
|
||||
|
||||
before do
|
||||
login_as moderator.user
|
||||
end
|
||||
|
||||
context 'when accessing the pending task list' do
|
||||
context 'and no pending task' do
|
||||
before do
|
||||
visit moderation_administrator_tasks_path
|
||||
end
|
||||
|
||||
it 'informs that there are no pending tasks' do
|
||||
expect(page).to have_content('There are no pending tasks')
|
||||
end
|
||||
end
|
||||
|
||||
context 'and there are pending tasks' do
|
||||
let!(:task) { create :administrator_task, :pending }
|
||||
|
||||
before do
|
||||
visit moderation_administrator_tasks_path
|
||||
end
|
||||
|
||||
it 'shows the related proposal title' do
|
||||
expect(page).to have_content(task.source.proposal.title)
|
||||
end
|
||||
|
||||
it 'shows the requested action title' do
|
||||
expect(page).to have_content(task.source.proposal_dashboard_action.title)
|
||||
end
|
||||
|
||||
it 'has a link that allows solving the request' do
|
||||
expect(page).to have_link('Solve')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when solving a pending task' do
|
||||
let!(:task) { create :administrator_task, :pending }
|
||||
|
||||
before do
|
||||
visit moderation_administrator_tasks_path
|
||||
click_link 'Solve'
|
||||
end
|
||||
|
||||
it 'contains a link to the proposal' do
|
||||
expect(page).to have_link('Check the proposal details')
|
||||
end
|
||||
|
||||
it 'contains a button that solves the request' do
|
||||
expect(page).to have_button('Mark as solved')
|
||||
end
|
||||
|
||||
it 'shows the comments added by the user during the request' do
|
||||
expect(page).to have_content(task.source.comments)
|
||||
end
|
||||
|
||||
context 'and the Mark as solved button is pressed' do
|
||||
before do
|
||||
click_button 'Mark as solved'
|
||||
end
|
||||
|
||||
it 'The proposal dissapears from the list' do
|
||||
expect(page).not_to have_content(task.source.proposal.title)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -21,6 +21,7 @@ describe Abilities::Moderator do
|
||||
let(:hidden_debate) { create(:debate, :hidden) }
|
||||
let(:hidden_comment) { create(:comment, :hidden) }
|
||||
let(:hidden_proposal) { create(:proposal, :hidden) }
|
||||
let(:administrator_task) { create(:administrator_task) }
|
||||
|
||||
it { should be_able_to(:index, Debate) }
|
||||
it { should be_able_to(:show, debate) }
|
||||
@@ -31,6 +32,9 @@ describe Abilities::Moderator do
|
||||
|
||||
it { should be_able_to(:read, Organization) }
|
||||
|
||||
it { is_expected.to be_able_to :manage, AdministratorTask }
|
||||
it { is_expected.to be_able_to :manage, administrator_task }
|
||||
|
||||
describe "organizations" do
|
||||
let(:pending_organization) { create(:organization) }
|
||||
let(:rejected_organization) { create(:organization, :rejected) }
|
||||
|
||||
@@ -6,12 +6,17 @@ describe ProposalExecutedDashboardAction do
|
||||
build :proposal_executed_dashboard_action,
|
||||
proposal: proposal,
|
||||
proposal_dashboard_action: proposal_dashboard_action,
|
||||
executed_at: executed_at
|
||||
executed_at: executed_at,
|
||||
comments: comments
|
||||
end
|
||||
|
||||
let(:proposal) { create :proposal }
|
||||
let(:proposal_dashboard_action) { create :proposal_dashboard_action }
|
||||
let(:proposal_dashboard_action) do
|
||||
create :proposal_dashboard_action, request_to_administrators: request_to_administrators, link: Faker::Internet.url
|
||||
end
|
||||
let(:request_to_administrators) { false }
|
||||
let(:executed_at) { Time.now }
|
||||
let(:comments) { '' }
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
|
||||
@@ -33,6 +38,22 @@ describe ProposalExecutedDashboardAction do
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
context 'when the action sends a request to the administrators' do
|
||||
let(:request_to_administrators) { true }
|
||||
|
||||
context 'and comments are blank' do
|
||||
let(:comments) { '' }
|
||||
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
context 'and comments have value' do
|
||||
let(:comments) { Faker::Lorem.sentence }
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it has been already executed' do
|
||||
let!(:executed) { create(:proposal_executed_dashboard_action, proposal: proposal, proposal_dashboard_action: proposal_dashboard_action) }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user