diff --git a/config/routes.rb b/config/routes.rb index 7427c3e8c1..ba864d5d27 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -51,6 +51,7 @@ end mount Decidim::Core::Engine => "/" + mount Decidim::Stats::Engine, at: "/stats", as: "decidim_stats" mount Decidim::EphemeralParticipation::Engine, at: "/", as: "decidim_ephemeral_participation" mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development? authenticate :user, ->(u) { u.admin? } do diff --git a/decidim-stats/README.md b/decidim-stats/README.md index 76bb7af3fc..225edaeef1 100644 --- a/decidim-stats/README.md +++ b/decidim-stats/README.md @@ -38,6 +38,13 @@ participatory_space_type,participatory_space_id,component_id,action,metric_type, participatory_processes,2,4,comment,age_group,20-24,6 ``` +## Authorization data export page + +This module adds a new admin page, “Export verification data” accessible under the Admin > Participants menu as “Export Data.” + +On this page, admins can fill out a form to specify a Start Date, End Date, and Verification Method. Using these parameters, the form allows the export of census data gathered during the user verification process. Internally, it exports selected metadata fields from the Authorization model. + + ## Development guide The module has different pieces: diff --git a/decidim-stats/app/controllers/decidim/stats/authorization_exports_controller.rb b/decidim-stats/app/controllers/decidim/stats/authorization_exports_controller.rb new file mode 100644 index 0000000000..49d05ebda4 --- /dev/null +++ b/decidim-stats/app/controllers/decidim/stats/authorization_exports_controller.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Decidim + module Stats + class AuthorizationExportsController < Decidim::Admin::ApplicationController + layout "decidim/admin/users" + + def index + enforce_permission_to :index, :authorization_workflow + + @workflows = Decidim::Verifications.workflows.select do |manifest| + current_organization.available_authorizations.include?(manifest.name.to_s) + end + + @form = form(AuthorizationExportsForm).instance + end + + def create + AuthorizationExportsJob.perform_later( + current_user, + current_organization, + name: authorization_params[:authorization_handler_name], + start_date: authorization_params[:start_date], + end_date: authorization_params[:end_date] + ) + + flash[:notice] = t("decidim.admin.exports.notice") + + redirect_to authorization_exports_path + end + + private + + def authorization_params + params.require(:authorization_exports).permit(:authorization_handler_name, :start_date, :end_date) + end + end + end +end diff --git a/decidim-stats/app/form/decidim/stats/authorization_exports_form.rb b/decidim-stats/app/form/decidim/stats/authorization_exports_form.rb new file mode 100644 index 0000000000..22aa5ead5e --- /dev/null +++ b/decidim-stats/app/form/decidim/stats/authorization_exports_form.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Decidim + module Stats + # A form to validate the date range for authorization exports + class AuthorizationExportsForm < Form + include TranslatableAttributes + + attribute :start_date, Decidim::Attributes::LocalizedDate + attribute :end_date, Decidim::Attributes::LocalizedDate + attribute :authorization_handler_name, String + + validates :start_date, presence: true + validates :end_date, presence: true + end + end +end diff --git a/decidim-stats/app/jobs/decidim/stats/authorization_exports_job.rb b/decidim-stats/app/jobs/decidim/stats/authorization_exports_job.rb new file mode 100644 index 0000000000..996e1dab23 --- /dev/null +++ b/decidim-stats/app/jobs/decidim/stats/authorization_exports_job.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module Decidim + module Stats + class AuthorizationExportsJob < ApplicationJob + queue_as :default + + def perform(user, organization, filters) + ExportMailer.export( + user, + export_file_name, + export_data(organization, filters) + ).deliver_now + end + + def export_data(organization, filters) + Decidim::Exporters::CSV.new( + collection(organization, filters), + serializer + ).export + end + + def export_file_name + "authorizations_export" + end + + def collection(organization, filters) + Decidim::Authorization.joins(:user) + .where( + granted_at: filters[:start_date]..filters[:end_date], + name: filters[:name] + ) + Decidim::Authorization.joins(:user) + .where(decidim_users: { decidim_organization_id: organization.id }) + .where( + granted_at: filters[:start_date]..filters[:end_date], + name: filters[:name] + ) + end + + def serializer + Decidim::Stats::AuthorizationSerializer + end + end + end +end diff --git a/decidim-stats/app/serializers/decidim/stats/authorization_serializer.rb b/decidim-stats/app/serializers/decidim/stats/authorization_serializer.rb new file mode 100644 index 0000000000..62121834ab --- /dev/null +++ b/decidim-stats/app/serializers/decidim/stats/authorization_serializer.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Decidim + module Stats + # This class serializes a Authorization so can be exported to CSV + class AuthorizationSerializer < Decidim::Exporters::Serializer + attr_reader :authorization + + def initialize(authorization) + @authorization = authorization + end + + def serialize + { + user_hash:, + date_of_birth: metadata["date_of_birth"], + postal_code: metadata["postal_code"], + scope_name: metadata["scope"], + scope_id: metadata["scope_id"], + scope_code: metadata["scope_code"], + gender: metadata_extras["gender"], + granted_at: authorization.granted_at + } + end + + private + + def metadata + authorization.metadata || {} + end + + def metadata_extras + metadata["extras"] || {} + end + + def user_hash + return "" unless user + + Digest::SHA256.hexdigest(user.id.to_s).last(8) + end + + def user + authorization.user + end + end + end +end diff --git a/decidim-stats/app/views/decidim/stats/authorization_exports/index.html.erb b/decidim-stats/app/views/decidim/stats/authorization_exports/index.html.erb new file mode 100644 index 0000000000..17ed76d9a0 --- /dev/null +++ b/decidim-stats/app/views/decidim/stats/authorization_exports/index.html.erb @@ -0,0 +1,39 @@ +<% add_decidim_page_title(t("title", scope: "decidim.stats.authorization_exports")) %> + +
+

+ <%= t("title", scope: "decidim.stats.authorization_exports") %> +

+
+ +
+
+
+
+
+
+ <%= decidim_form_for @form, url: authorization_exports_path, multipart: true, html: { class: "form form-defaults" } do |form| %> +
+ <%= form.date_field :start_date, label: t("start_date", scope: "decidim.stats.authorization_exports") %> +
+ +
+ <%= form.date_field :end_date, label: t("end_date", scope: "decidim.stats.authorization_exports") %> +
+ +
+ <%= form.select :authorization_handler_name, @workflows.map { |workflow| [workflow.fullname, workflow.name] }, label: t("authorization_handler_name", scope: "decidim.stats.authorization_exports") %> +
+ +
+
+ <%= submit_tag t("submit", scope: "decidim.stats.authorization_exports"), class: "button button__sm button__secondary" %> +
+
+ <% end %> +
+
+
+
+
+
diff --git a/decidim-stats/config/locales/ca.yml b/decidim-stats/config/locales/ca.yml new file mode 100644 index 0000000000..193d64cd8d --- /dev/null +++ b/decidim-stats/config/locales/ca.yml @@ -0,0 +1,10 @@ +ca: + decidim: + stats: + authorization_exports: + title: Exportar dades de verificació + authorization_handler_name: Mètode de verificació + end_date: Data fi + start_date: Data inici + submit: Exportar dades + menu: Exportar dades diff --git a/decidim-stats/config/locales/en.yml b/decidim-stats/config/locales/en.yml new file mode 100644 index 0000000000..904335942c --- /dev/null +++ b/decidim-stats/config/locales/en.yml @@ -0,0 +1,10 @@ +en: + decidim: + stats: + authorization_exports: + title: Export verification data + authorization_handler_name: Verification method + end_date: End date + start_date: Start date + submit: Export verification data + menu: Export data diff --git a/decidim-stats/config/locales/es.yml b/decidim-stats/config/locales/es.yml new file mode 100644 index 0000000000..6381d8ffde --- /dev/null +++ b/decidim-stats/config/locales/es.yml @@ -0,0 +1,10 @@ +es: + decidim: + stats: + authorization_exports: + title: Exportar datos de verificación + authorization_handler_name: Método de verificación + end_date: Fecha fin + start_date: Fecha inicio + submit: Exportar datos + menu: Exportar datos diff --git a/decidim-stats/lib/decidim/stats/engine.rb b/decidim-stats/lib/decidim/stats/engine.rb index ddd3554c74..282cd70c25 100644 --- a/decidim-stats/lib/decidim/stats/engine.rb +++ b/decidim-stats/lib/decidim/stats/engine.rb @@ -4,6 +4,20 @@ module Decidim module Stats class Engine < ::Rails::Engine isolate_namespace Decidim::Stats + + routes do + resources :authorization_exports, only: [:index, :create] + end + + initializer "decidim_stats.admin_menus" do + Decidim.menu :admin_user_menu do |menu| + menu.add_item :authorization_exports, + I18n.t("authorization_exports.menu", scope: "decidim.stats"), + decidim_stats.authorization_exports_path, + active: is_active_link?(decidim_stats.authorization_exports_path), + icon_name: "download-line" + end + end end end end