Skip to content

Commit

Permalink
Accountability bulk actions (decidim#13730)
Browse files Browse the repository at this point in the history
  • Loading branch information
mllocs authored Jan 8, 2025
1 parent 1ef2224 commit 2c7da23
Show file tree
Hide file tree
Showing 39 changed files with 1,168 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# frozen_string_literal: true

module Decidim
module Accountability
module Admin
# A command with all the business logic when an admin batch updates results dates.
class UpdateResultDates < Decidim::Command
# Public: Initializes the command.
#
# start_date - the start date to update
# end_date - the end date to update
# result_ids - the results ids to update
# current_user - the user performing the action
def initialize(start_date, end_date, result_ids, current_user)
@start_date = start_date
@end_date = end_date
@result_ids = result_ids
@current_user = current_user
end

# Executes the command. Broadcasts these events:
#
# - :ok when everything is valid
# - :invalid if the form was not valid and we could not proceed.
#
# Returns nothing.
def call
return broadcast(:invalid) if (start_date.blank? && end_date.blank?) || result_ids.blank?

update_results_dates

broadcast(:ok)
end

private

attr_reader :start_date, :end_date, :result_ids, :current_user

def update_results_dates
Decidim::Accountability::Result.where(id: result_ids).find_each do |result|
next if result.start_date == start_date && result.end_date == end_date

result.update!(start_date:, end_date:)

# Trace the action to keep track of changes
Decidim.traceability.perform_action!(
"update",
result,
current_user
)
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# frozen_string_literal: true

module Decidim
module Accountability
module Admin
# A command with all the business logic when an admin batch updates results status.
class UpdateResultStatus < Decidim::Command
# Public: Initializes the command.
#
# status_id - the status id to update
# result_ids - the results ids to update
# current_user - the user performing the action
def initialize(status_id, result_ids, current_user)
@status_id = status_id
@result_ids = result_ids
@current_user = current_user
end

# Executes the command. Broadcasts these events:
#
# - :ok when everything is valid
# - :invalid if the form was not valid and we could not proceed.
#
# Returns nothing.
def call
return broadcast(:invalid) if status_id.blank? || result_ids.blank?

update_results_status

broadcast(:ok)
end

private

attr_reader :status_id, :result_ids, :current_user

def update_results_status
Decidim::Accountability::Result.where(id: result_ids).find_each do |result|
next if result.decidim_accountability_status_id == status_id

status = Decidim::Accountability::Status.find_by(id: status_id)

next if status.blank?

result.update!(
decidim_accountability_status_id: status_id,
progress: status.progress
)

# Trace the action to keep track of changes
Decidim.traceability.perform_action!(
"update",
result,
current_user
)
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Decidim
module Accountability
module Admin
# A command with all the business logic when an admin batch updates results taxonomies.
class UpdateResultTaxonomies < UpdateResourcesTaxonomies
# Public: Initializes the command.
#
# taxonomy_ids - the taxonomy ids to update
# result_ids - the results ids to update.
def initialize(taxonomy_ids, result_ids, organization)
super(taxonomy_ids, Decidim::Accountability::Result.where(id: result_ids), organization)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# frozen_string_literal: true

module Decidim
module Accountability
module Admin
class ResultsBulkActionsController < Admin::ApplicationController
include Decidim::ApplicationHelper
include Decidim::SanitizeHelper
include Decidim::Admin::ComponentTaxonomiesHelper

def update_taxonomies
enforce_permission_to :create, :bulk_update

Admin::UpdateResultTaxonomies.call(result_params[:taxonomies], result_ids, current_organization) do
on(:invalid_taxonomies) do
flash[:alert] = I18n.t(
"results.update_taxonomies.select_a_taxonomy",
scope: "decidim.accountability.admin"
)
end

on(:invalid_resources) do
flash[:alert] = I18n.t(
"results.update_taxonomies.select_a_result",
scope: "decidim.accountability.admin"
)
end

on(:update_resources_taxonomies) do |response|
if response[:successful].any?
flash[:notice] = t(
"results.update_taxonomies.success",
taxonomies: response[:taxonomies].map { |taxonomy| decidim_escape_translated(taxonomy.name) }.to_sentence,
results: response[:successful].map { |resource| decidim_escape_translated(resource.title) }.to_sentence,
scope: "decidim.accountability.admin"
)
end
if response[:errored].any?
flash[:alert] = t(
"results.update_taxonomies.invalid",
taxonomies: response[:taxonomies].map { |taxonomy| decidim_escape_translated(taxonomy.name) }.to_sentence,
results: response[:errored].map { |resource| decidim_escape_translated(resource.title) }.to_sentence,
scope: "decidim.accountability.admin"
)
end
end
end

redirect_to results_path
end

def update_status
enforce_permission_to :create, :bulk_update

UpdateResultStatus.call(result_params[:decidim_accountability_status_id], result_ids, current_user) do
on(:ok) do
flash[:notice] = I18n.t("results.update_status.success", scope: "decidim.accountability.admin")
redirect_to results_path
end

on(:invalid) do
flash[:alert] = I18n.t("results.update_status.invalid", scope: "decidim.accountability.admin")
redirect_to results_path
end
end
end

def update_dates
enforce_permission_to :create, :bulk_update

UpdateResultDates.call(result_params[:start_date], result_params[:end_date], result_ids, current_user) do
on(:ok) do
flash[:notice] = I18n.t("results.update_dates.success", scope: "decidim.accountability.admin")
redirect_to results_path
end

on(:invalid) do
flash[:alert] = I18n.t("results.update_dates.invalid", scope: "decidim.accountability.admin")
redirect_to results_path
end
end
end

private

def result_ids
result_params[:result_ids].map { |ids| ids.split(",") }.flatten.map(&:to_i)
end

def result_params
@result_params ||= params.require(:result_bulk_actions).permit(
:decidim_accountability_status_id,
:start_date,
:end_date,
result_ids: [],
taxonomies: []
)
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ module Admin
class ResultsController < Admin::ApplicationController
include Decidim::ApplicationHelper
include Decidim::SanitizeHelper
include Decidim::Admin::ComponentTaxonomiesHelper
include Decidim::Accountability::Admin::Filterable
include Decidim::Admin::HasTrashableResources

helper_method :results, :parent_result, :parent_results, :statuses, :present
helper_method :results, :parent_result, :parent_results, :statuses, :present, :bulk_actions_form

def collection
parent_id = params[:parent_id].presence
@collection ||= Result.where(component: current_component, parent_id:).page(params[:page]).per(15)
@collection ||= Result.where(component: current_component, parent_id:).page(params[:page]).per(15).order(created_at: :asc)
end

def new
Expand Down Expand Up @@ -103,6 +104,10 @@ def parent_results
def statuses
@statuses ||= Status.where(component: current_component)
end

def bulk_actions_form
@bulk_actions_form ||= ResultBulkActionsForm.new(result_ids: [])
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Decidim
module Accountability
module Admin
# This class holds a Form to create/update results from Decidim's admin panel.
class ResultBulkActionsForm < Decidim::Form
include Decidim::TranslationsHelper
include Decidim::HasTaxonomyFormAttributes

attribute :result_ids, Array[Integer]
attribute :start_date, Decidim::Attributes::LocalizedDate
attribute :end_date, Decidim::Attributes::LocalizedDate
attribute :decidim_accountability_status_id, Integer
end
end
end
end
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
import "src/decidim/accountability/admin/index"
import "src/decidim/accountability/admin/result_form"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "src/decidim/accountability/admin/index"
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import Counter from "src/decidim/accountability/admin/index/counter";
import ActionButton from "src/decidim/accountability/admin/index/action_button";
import ActionForm from "src/decidim/accountability/admin/index/action_form";
import ActionSelector from "src/decidim/accountability/admin/index/action_selector";
import SelectAll from "src/decidim/accountability/admin/index/select_all";

$(() => {
$("#result_decidim_accountability_status_id").change(function () {
/* eslint-disable no-invalid-this */
const progress = $(this).find(":selected").data("progress")
if (progress || progress === 0) {
$("#result_progress").val(progress);
}
});
const counter = new Counter();
const actionButton = new ActionButton(counter);
const actionForm = new ActionForm(counter);
const actionSelector = new ActionSelector();
const selectAll = new SelectAll(counter);

counter.init();
actionButton.init();
actionForm.init();
actionSelector.init();
selectAll.init();
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class ActionButton {
constructor(counter) {
this.counter = counter;
this.actionButton = document.querySelector("[data-action-button]");
this.actionForms = document.querySelectorAll("[data-action-form]");
}

init() {
this.counter.checkboxes.forEach((checkbox) => {
checkbox.addEventListener("change", () => this.onCheckboxChange());
});

this.toggleActionButton();
}

onCheckboxChange() {
this.toggleActionButton();
this.toggleActionForms();
}

toggleActionButton() {
const selectedIds = this.counter.getSelectedItems();

if (selectedIds.length > 0) {
this.actionButton.classList.remove("hide");
} else {
this.actionButton.classList.add("hide");
}
}

toggleActionForms() {
const selectedIds = this.counter.getSelectedItems();

if (selectedIds.length === 0) {
this.actionForms.forEach((form) => {
form.classList.add("hide");
});
}
}
}

export default ActionButton;
Loading

0 comments on commit 2c7da23

Please sign in to comment.