Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def create

Decidim::StratifiedSortitions::Admin::ImportSample.call(@form, stratified_sortition, current_user) do
on(:ok) do
Decidim.traceability.perform_action!("import_sample", stratified_sortition, current_user, visibility: "all")
flash[:notice] = I18n.t("sample_imports.create.success", scope: "decidim.stratified_sortitions.admin")
redirect_to upload_sample_stratified_sortition_path(stratified_sortition)
end
Expand All @@ -46,6 +47,7 @@ def remove_multiple

Decidim::StratifiedSortitions::Admin::RemoveUploadedSamples.call(stratified_sortition) do
on(:ok) do
Decidim.traceability.perform_action!("remove_samples", stratified_sortition, current_user, visibility: "all")
flash[:notice] = I18n.t("sample_imports.remove_uploaded_samples.success", scope: "decidim.stratified_sortitions.admin")
redirect_to upload_sample_stratified_sortition_path(stratified_sortition)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ def execute_stratified_sortition
@result = FairSortitionService.new(stratified_sortition).call
if @result.success?
stratified_sortition.update!(status: "executed")
Decidim.traceability.perform_action!("execute", stratified_sortition, current_user, visibility: "all")
flash[:notice] = I18n.t("stratified_sortitions.execute.success", scope: "decidim.stratified_sortitions.admin")
else
flash[:error] = @result.error
Expand Down Expand Up @@ -185,10 +186,24 @@ def export_results

SortitionResultsExportJob.perform_later(current_user, stratified_sortition, format)

Decidim.traceability.perform_action!("export_results", stratified_sortition, current_user, visibility: "all")
flash[:notice] = I18n.t("decidim.admin.exports.notice")
redirect_to execute_stratified_sortition_path(stratified_sortition)
end

def log_view_participants
portfolio = stratified_sortition.panel_portfolio

unless portfolio&.sampled?
head :unprocessable_entity
return
end

Decidim.traceability.perform_action!("view_participants", stratified_sortition, current_user, visibility: "all")

head :ok
end

private

def collection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class StratifiedSortition < ApplicationRecord

component_manifest_name "stratified_sortitions"

def self.log_presenter_class_for(_log)
Decidim::StratifiedSortitions::AdminLog::StratifiedSortitionPresenter
end

has_many :strata, class_name: "Decidim::StratifiedSortitions::Stratum", foreign_key: "decidim_stratified_sortition_id", dependent: :destroy
has_many :sample_imports, class_name: "Decidim::StratifiedSortitions::SampleImport", dependent: :destroy
has_many :sample_participants, class_name: "Decidim::StratifiedSortitions::SampleParticipant", foreign_key: "decidim_stratified_sortition_id", dependent: :destroy
Expand Down
15 changes: 15 additions & 0 deletions app/packs/src/decidim/stratified_sortitions/results_tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ document.addEventListener("DOMContentLoaded", () => {
confirmBtn.addEventListener("click", () => {
activateTab("participants-section");

// Log the view_participants action
const logUrl = confirmBtn.dataset.logUrl;
const csrfToken = confirmBtn.dataset.authenticityToken;
if (logUrl && csrfToken) {
fetch(logUrl, {
method: "POST",
headers: {
"X-CSRF-Token": csrfToken,
"Content-Type": "application/json",
},
}).catch(() => {
// Silently ignore log errors
});
}

// Close the modal
const modal = document.getElementById("confirm-participants-modal");
if (modal) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module Decidim
module StratifiedSortitions
module AdminLog
# This class holds the logic to present a `Decidim::StratifiedSortitions::StratifiedSortition`
# for the `AdminLog` log.
#
# Usage should be automatic and you should not need to call this class
# directly, but here is an example:
#
# action_log = Decidim::ActionLog.last
# view_helpers # => this comes from the views
# StratifiedSortitionPresenter.new(action_log, view_helpers).present
class StratifiedSortitionPresenter < Decidim::Log::BasePresenter
private

def action_string
case action
when "create", "update", "delete", "duplicate",
"execute", "export_results", "view_participants",
"import_sample", "remove_samples"
"decidim.stratified_sortitions.admin_log.stratified_sortition.#{action}"
else
super
end
end

def i18n_labels_scope
"activemodel.attributes.stratified_sortition"
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
<button class="button button__lg button__transparent-secondary w-full md:w-auto" data-dialog-close="confirm-participants-modal">
<span><%= t("decidim.stratified_sortitions.admin.stratified_sortitions.execute.confirm_participants_cancel") %></span>
</button>
<button type="button" class="button button__lg button__secondary w-full md:w-auto" id="confirm-show-participants">
<button type="button" class="button button__lg button__secondary w-full md:w-auto" id="confirm-show-participants"
data-log-url="<%= log_view_participants_stratified_sortition_path(stratified_sortition) %>"
data-authenticity-token="<%= form_authenticity_token %>">
<span><%= t("decidim.stratified_sortitions.admin.stratified_sortitions.execute.confirm_participants_ok") %></span>
</button>
</div>
Expand Down
11 changes: 11 additions & 0 deletions config/locales/ca.yml
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,17 @@ ca:
count:
one: 1 sorteig estratificat
other: "%{count} sortejos estratificats"
admin_log:
stratified_sortition:
create: "%{user_name} ha creat el sorteig estratificat %{resource_name} a %{space_name}"
update: "%{user_name} ha actualitzat el sorteig estratificat %{resource_name} a %{space_name}"
delete: "%{user_name} ha eliminat el sorteig estratificat %{resource_name} a %{space_name}"
duplicate: "%{user_name} ha duplicat el sorteig estratificat %{resource_name}"
import_sample: "%{user_name} ha importat una mostra al sorteig estratificat %{resource_name} a %{space_name}"
remove_samples: "%{user_name} ha eliminat els registres importats del sorteig estratificat %{resource_name} a %{space_name}"
execute: "%{user_name} ha executat el sorteig estratificat %{resource_name} a %{space_name}"
export_results: "%{user_name} ha exportat els resultats del sorteig estratificat %{resource_name} a %{space_name}"
view_participants: "%{user_name} ha consultat el llistat de participants seleccionades del sorteig estratificat %{resource_name} a %{space_name}"
errors:
panel_portfolio:
already_sampled: "La cartera ja ha estat mostrejada"
Expand Down
11 changes: 11 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,17 @@ en:
count:
one: 1 stratified sortition
other: "%{count} stratified sortitions"
admin_log:
stratified_sortition:
create: "%{user_name} created the stratified sortition %{resource_name} on %{space_name}"
update: "%{user_name} updated the stratified sortition %{resource_name} on %{space_name}"
delete: "%{user_name} deleted the stratified sortition %{resource_name} on %{space_name}"
duplicate: "%{user_name} duplicated the stratified sortition %{resource_name}"
import_sample: "%{user_name} imported a sample to the stratified sortition %{resource_name} on %{space_name}"
remove_samples: "%{user_name} removed the imported records from the stratified sortition %{resource_name} on %{space_name}"
execute: "%{user_name} executed the stratified sortition %{resource_name} on %{space_name}"
export_results: "%{user_name} exported the results of the stratified sortition %{resource_name} on %{space_name}"
view_participants: "%{user_name} viewed the selected participants list of the stratified sortition %{resource_name} on %{space_name}"
errors:
panel_portfolio:
already_sampled: "Portfolio already sampled"
Expand Down
11 changes: 11 additions & 0 deletions config/locales/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,17 @@ es:
count:
one: 1 sorteo estratificado
other: "%{count} sorteos estratificados"
admin_log:
stratified_sortition:
create: "%{user_name} ha creado el sorteo estratificado %{resource_name} en %{space_name}"
update: "%{user_name} ha actualizado el sorteo estratificado %{resource_name} en %{space_name}"
delete: "%{user_name} ha eliminado el sorteo estratificado %{resource_name} en %{space_name}"
duplicate: "%{user_name} ha duplicado el sorteo estratificado %{resource_name}"
import_sample: "%{user_name} ha importado una muestra en el sorteo estratificado %{resource_name} en %{space_name}"
remove_samples: "%{user_name} ha eliminado los registros importados del sorteo estratificado %{resource_name} en %{space_name}"
execute: "%{user_name} ha ejecutado el sorteo estratificado %{resource_name} en %{space_name}"
export_results: "%{user_name} ha exportado los resultados del sorteo estratificado %{resource_name} en %{space_name}"
view_participants: "%{user_name} ha consultado el listado de participantes seleccionadas del sorteo estratificado %{resource_name} en %{space_name}"
errors:
panel_portfolio:
already_sampled: "La cartera ya ha sido muestreada"
Expand Down
1 change: 1 addition & 0 deletions lib/decidim/stratified_sortitions/admin_engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class AdminEngine < ::Rails::Engine
post :execute_stratified_sortition, on: :member
post :export_results, on: :member
post :export_charts_pdf, on: :member
post :log_view_participants, on: :member
end

resources :samples, only: [:show, :create] do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# frozen_string_literal: true

require "spec_helper"

module Decidim
module StratifiedSortitions
module Admin
describe SamplesController do
routes { Decidim::StratifiedSortitions::AdminEngine.routes }

let(:component) { stratified_sortition.component }
let(:stratified_sortition) { create(:stratified_sortition) }
let(:user) { create(:user, :confirmed, :admin, organization: component.organization) }

let!(:stratum) do
create(:stratum, stratified_sortition:, kind: "value", name: { en: "Gender" })
end
let!(:substratum) do
create(:substratum, stratum:, name: { en: "Man" }, value: "M", max_quota_percentage: "50")
end

before do
request.env["decidim.current_organization"] = component.organization
request.env["decidim.current_component"] = component
sign_in user, scope: :user
end

describe "create (import_sample)" do
let(:params) do
{
id: stratified_sortition.id,
}
end

before do
allow_any_instance_of(Decidim::StratifiedSortitions::Admin::ImportSample)
.to receive(:call) { |instance| instance.send(:broadcast, :ok) }
end

it "redirects to the upload_sample page" do
post(:create, params:)
expect(response).to redirect_to(upload_sample_stratified_sortition_path(stratified_sortition))
end

it "traces the import_sample action" do
expect { post(:create, params:) }
.to change(Decidim::ActionLog, :count).by(1)
expect(Decidim::ActionLog.last.action).to eq("import_sample")
end
end

describe "remove_multiple (remove_samples)" do
let(:params) do
{
id: stratified_sortition.id,
}
end

let(:sample_import) { create(:sample_import, stratified_sortition:) }
let!(:participant) do
create(:sample_participant,
decidim_stratified_sortition: stratified_sortition,
decidim_stratified_sortitions_sample_import: sample_import)
end

it "redirects to the upload_sample page" do
delete(:remove_multiple, params:)
expect(response).to redirect_to(upload_sample_stratified_sortition_path(stratified_sortition))
end

it "traces the remove_samples action" do
expect { delete(:remove_multiple, params:) }
.to change(Decidim::ActionLog, :count).by(1)
expect(Decidim::ActionLog.last.action).to eq("remove_samples")
end

it "removes the sample participants" do
expect { delete(:remove_multiple, params:) }
.to change(Decidim::StratifiedSortitions::SampleParticipant, :count).by(-1)
end
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,114 @@ module Admin
expect(Decidim::StratifiedSortitions::Admin::SortitionResultsExportJob)
.to have_been_enqueued.with(anything, anything, "csv")
end

it "traces the export_results action" do
expect { post(:export_results, params:) }
.to change(Decidim::ActionLog, :count).by(1)
expect(Decidim::ActionLog.last.action).to eq("export_results")
end
end
end

describe "execute_stratified_sortition" do
let(:params) do
{
participatory_process_slug: component.participatory_space.slug,
id: stratified_sortition.id,
}
end

let(:fair_service_result) { double("result", success?: true, error: nil) }
let(:fair_service) { double("fair_sortition_service", call: fair_service_result) }

before do
allow(FairSortitionService).to receive(:new).and_return(fair_service)
end

context "when the sortition executes successfully" do
it "redirects to the execute page" do
post(:execute_stratified_sortition, params:)
expect(response).to redirect_to(execute_stratified_sortition_path(stratified_sortition))
end

it "sets a notice flash message" do
post(:execute_stratified_sortition, params:)
expect(flash[:notice]).to be_present
end

it "traces the execute action" do
expect { post(:execute_stratified_sortition, params:) }
.to change(Decidim::ActionLog, :count).by(1)
expect(Decidim::ActionLog.last.action).to eq("execute")
end
end

context "when the sortition fails" do
let(:fair_service_result) { double("result", success?: false, error: "Something went wrong") }

it "redirects to the execute page" do
post(:execute_stratified_sortition, params:)
expect(response).to redirect_to(execute_stratified_sortition_path(stratified_sortition))
end

it "sets an error flash message" do
post(:execute_stratified_sortition, params:)
expect(flash[:error]).to be_present
end

it "does not trace the execute action" do
expect { post(:execute_stratified_sortition, params:) }
.not_to change(Decidim::ActionLog, :count)
end
end
end

describe "log_view_participants" do
let(:params) do
{
participatory_process_slug: component.participatory_space.slug,
id: stratified_sortition.id,
}
end

context "when the portfolio is not sampled" do
it "returns unprocessable_entity" do
post(:log_view_participants, params:)
expect(response).to have_http_status(:unprocessable_entity)
end

it "does not trace the action" do
expect { post(:log_view_participants, params:) }
.not_to change(Decidim::ActionLog, :count)
end
end

context "when the portfolio is sampled" do
let(:sample_import) { create(:sample_import, stratified_sortition:) }
let!(:participant) do
create(:sample_participant,
decidim_stratified_sortition: stratified_sortition,
decidim_stratified_sortitions_sample_import: sample_import)
end
let!(:portfolio) do
create(:panel_portfolio,
:sampled,
stratified_sortition:,
panels: [[participant.id]],
probabilities: [1.0],
selection_probabilities: { participant.id => 1.0 })
end

it "returns ok" do
post(:log_view_participants, params:)
expect(response).to have_http_status(:ok)
end

it "traces the view_participants action" do
expect { post(:log_view_participants, params:) }
.to change(Decidim::ActionLog, :count).by(1)
expect(Decidim::ActionLog.last.action).to eq("view_participants")
end
end
end

Expand Down
Loading