diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 66c106dead..65b83f1956 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,15 +37,15 @@ jobs: ruby-version: ${{ env.RUBY_VERSION }} bundler-cache: true - uses: nanasess/setup-chromedriver@v2 + - run: | + sudo apt install wkhtmltopdf - run: bundle exec rake db:test:prepare name: Setup database - name: Precompile assets run: | npm install bundle exec rake assets:precompile - - run: | - mkdir node_modules - bundle exec rspec + - run: bundle exec rspec name: Run specs - uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/test_census_sms.yml b/.github/workflows/test_census_sms.yml index 84332e4584..ad9f89b8bf 100644 --- a/.github/workflows/test_census_sms.yml +++ b/.github/workflows/test_census_sms.yml @@ -37,15 +37,15 @@ jobs: ruby-version: ${{ env.RUBY_VERSION }} bundler-cache: true - uses: nanasess/setup-chromedriver@v2 + - run: | + sudo apt install wkhtmltopdf - run: bundle exec rake db:test:prepare name: Setup database - name: Precompile assets run: | npm install bundle exec rake assets:precompile - - run: | - mkdir node_modules - bundle exec rspec decidim-census_sms + - run: bundle exec rspec decidim-census_sms name: Run specs - uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/test_stats.yml b/.github/workflows/test_stats.yml index e53d2e1951..a1236acdce 100644 --- a/.github/workflows/test_stats.yml +++ b/.github/workflows/test_stats.yml @@ -37,15 +37,15 @@ jobs: ruby-version: ${{ env.RUBY_VERSION }} bundler-cache: true - uses: nanasess/setup-chromedriver@v2 + - run: | + sudo apt install wkhtmltopdf - run: bundle exec rake db:test:prepare name: Setup database - name: Precompile assets run: | npm install bundle exec rake assets:precompile - - run: | - mkdir node_modules - bundle exec rspec decidim-stats + - run: bundle exec rspec decidim-stats name: Run specs - uses: actions/upload-artifact@v4 if: always() diff --git a/Gemfile b/Gemfile index 1b71316c27..4aa8e3b5f4 100644 --- a/Gemfile +++ b/Gemfile @@ -25,7 +25,6 @@ gem "decidim-vocdoni", git: "https://github.com/decidim-vocdoni/decidim-module-v gem "origami" gem "wicked_pdf", "< 2.8" -gem "wkhtmltopdf-binary" gem "progressbar" gem "puma" diff --git a/Gemfile.lock b/Gemfile.lock index 2c9af5480a..7f9161af51 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/AjuntamentdeBarcelona/decidim - revision: 4bfc73be0724b60918f93867ae0a03d662aea1fd + revision: f3b125243e409476b07c75a7438eafc529dd96c8 branch: release/0.28-stable-bcn specs: decidim (0.28.4) @@ -136,7 +136,6 @@ GIT decidim-forms (0.28.4) decidim-core (= 0.28.4) wicked_pdf (~> 2.1) - wkhtmltopdf-binary (~> 0.12) decidim-generators (0.28.4) decidim-core (= 0.28.4) decidim-initiatives (0.28.4) @@ -146,7 +145,6 @@ GIT decidim-verifications (= 0.28.4) hexapdf (~> 0.32.0) wicked_pdf (~> 2.1) - wkhtmltopdf-binary (~> 0.12) decidim-meetings (0.28.4) decidim-core (= 0.28.4) decidim-forms (= 0.28.4) @@ -978,7 +976,6 @@ GEM activesupport wisper (2.0.1) wisper-rspec (1.1.0) - wkhtmltopdf-binary (0.12.6.6) xpath (3.2.0) nokogiri (~> 1.8) zeitwerk (2.6.18) @@ -1041,7 +1038,6 @@ DEPENDENCIES stackprof web-console wicked_pdf (< 2.8) - wkhtmltopdf-binary RUBY VERSION ruby 3.1.1p18 diff --git a/Rakefile b/Rakefile index bf012913fa..03f34b28d0 100644 --- a/Rakefile +++ b/Rakefile @@ -116,7 +116,3 @@ namespace :decidim_surveys_patch do end end end - -Rake::Task["assets:precompile"].enhance do - FileUtils.remove_dir("node_modules", true) -end 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