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")) %> + +