diff --git a/app/commands/decidim/superspaces/admin/create_superspace.rb b/app/commands/decidim/superspaces/admin/create_superspace.rb index 84d5c60..59b99cb 100644 --- a/app/commands/decidim/superspaces/admin/create_superspace.rb +++ b/app/commands/decidim/superspaces/admin/create_superspace.rb @@ -58,7 +58,8 @@ def create_superspace! title: form.title, description: form.description, locale: form.locale, - hero_image: form.hero_image + hero_image: form.hero_image, + show_statistics: form.show_statistics } @superspace = Decidim.traceability.create!( diff --git a/app/commands/decidim/superspaces/admin/update_superspace.rb b/app/commands/decidim/superspaces/admin/update_superspace.rb index d513ccf..a42bd6a 100644 --- a/app/commands/decidim/superspaces/admin/update_superspace.rb +++ b/app/commands/decidim/superspaces/admin/update_superspace.rb @@ -44,7 +44,8 @@ def update_superspace! title: form.title, description: form.description, hero_image: form.hero_image, - locale: form.locale + locale: form.locale, + show_statistics: form.show_statistics ) update_associations(assembly_ids, participatory_process_ids, conference_ids) end diff --git a/app/forms/decidim/superspaces/admin/superspace_form.rb b/app/forms/decidim/superspaces/admin/superspace_form.rb index 062334b..7390b35 100644 --- a/app/forms/decidim/superspaces/admin/superspace_form.rb +++ b/app/forms/decidim/superspaces/admin/superspace_form.rb @@ -14,6 +14,7 @@ class SuperspaceForm < Decidim::Form attribute :assembly_ids attribute :participatory_process_ids attribute :conference_ids + attribute :show_statistics, Boolean validates :title, translatable_presence: true validates :locale, presence: true diff --git a/app/models/decidim/superspaces/superspace.rb b/app/models/decidim/superspaces/superspace.rb index 1e53431..b101a3b 100644 --- a/app/models/decidim/superspaces/superspace.rb +++ b/app/models/decidim/superspaces/superspace.rb @@ -35,6 +35,10 @@ def conferences find_spaces_by_type("Decidim::Conference") end + def statistics + Decidim::Superspaces::SuperspaceStatsPresenter.new(self).collection + end + def self.log_presenter_class_for(_log) = Decidim::Superspaces::AdminLog::SuperspacePresenter private diff --git a/app/presenters/decidim/superspaces/superspace_stats_presenter.rb b/app/presenters/decidim/superspaces/superspace_stats_presenter.rb new file mode 100644 index 0000000..8b9e083 --- /dev/null +++ b/app/presenters/decidim/superspaces/superspace_stats_presenter.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Decidim + module Superspaces + # This class holds the logic to present superspace stats. + # It inherits from `Decidim::StatsPresenter` and overrides the methods + # needed to adapt the stats to the superspace context. + + class SuperspaceStatsPresenter < Decidim::StatsPresenter + include Decidim::IconHelper + + private + + def participatory_space = __getobj__ + + def participatory_processes + @participatory_processes ||= participatory_space.participatory_processes + participatory_space.assemblies + participatory_space.conferences + end + + def participatory_space_participants_stats + Decidim::Superspaces::StatsParticipantsCount.for(participatory_space) + end + + def participatory_space_followers_stats(_conditions) + Decidim::Superspaces::StatsFollowersCount.for(participatory_space) + end + + def published_components + @published_components ||= Component.where(participatory_space: participatory_processes).published + end + + def participatory_space_sym = :superspace + end + end +end diff --git a/app/queries/decidim/superspaces/stats_followers_count.rb b/app/queries/decidim/superspaces/stats_followers_count.rb new file mode 100644 index 0000000..cdd670b --- /dev/null +++ b/app/queries/decidim/superspaces/stats_followers_count.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module Decidim + module Superspaces + class StatsFollowersCount < Decidim::Query + # This class is responsible for calculating the number of followers of a superspace. + # The number of followers in a superspace is equal to the sum of the number of followers in all the participatory spaces that belong to the superspace. + + def self.for(superspace) + return 0 unless superspace.is_a?(Decidim::Superspaces::Superspace) + + new(superspace).query + end + + def initialize(superspace) + @superspace = superspace + end + + def query + count = space_query + components_query + + data = [{ participatory_space: superspace.to_s, stat_title: "followers_count", stat_value: count }] + + data.map do |d| + [d[:participatory_space].to_sym, d[:stat_title].to_sym, d[:stat_value].to_i] + end + end + + private + + attr_reader :superspace + + def components_query + Decidim.component_manifests.sum do |component| + component.stats + .filter(tag: :followers) + .with_context(space_components) + .map { |_name, value| value } + .sum + end + end + + def space_query + Decidim.participatory_space_manifests.sum do |space| + space.stats + .filter(tag: :followers) + .with_context(participatory_space_items) + .map { |_name, value| value } + .sum + end + end + + def participatory_space_items + @participatory_space_items ||= superspace.participatory_spaces + end + + def space_components + @space_components ||= Decidim::Component.where(participatory_space: participatory_space_items).published + end + end + end +end diff --git a/app/queries/decidim/superspaces/stats_participants_count.rb b/app/queries/decidim/superspaces/stats_participants_count.rb new file mode 100644 index 0000000..081a15d --- /dev/null +++ b/app/queries/decidim/superspaces/stats_participants_count.rb @@ -0,0 +1,177 @@ +# frozen_string_literal: true + +module Decidim + module Superspaces + class StatsParticipantsCount < Decidim::Query + # This class is responsible for calculating the number of participants of a superspace. + # The number of participants in a superspace is equal to the sum of participants of the participatory spaces that belong to the superspace. + # If a user has participated in more than one participatory space, it will only be counted once. + + def self.for(superspace) + return 0 unless superspace.is_a?(Decidim::Superspaces::Superspace) + + new(superspace).query + end + + def initialize(superspace) + @superspace = superspace + end + + def query + participatory_process_class_name = Decidim::ParticipatoryProcess.class.name + assemblies_class_name = Decidim::Assembly.class.name + conferences_class_name = Decidim::Conference.class.name + + solution = [ + comments_query(participatory_process_class_name, participatory_space_ids), + comments_query(assemblies_class_name, assemblies_ids), + comments_query(conferences_class_name, conferences_ids), + debates_query(space_components), + debates_query(assemblies_components), + debates_query(conferences_components), + meetings_query(space_components), + meetings_query(assemblies_components), + meetings_query(conferences_components), + endorsements_query(space_components), + endorsements_query(assemblies_components), + endorsements_query(conferences_components), + project_votes_query(space_components), + project_votes_query(assemblies_components), + project_votes_query(conferences_components), + proposals_query(proposals_components), + proposals_query(assemblies_proposals_components), + proposals_query(conferences_proposals_components), + proposal_votes_query(proposals_components), + proposal_votes_query(assemblies_proposals_components), + proposal_votes_query(conferences_proposals_components), + survey_answer_query(space_components), + survey_answer_query(assemblies_components), + survey_answer_query(conferences_components) + ].flatten.uniq.count + + data = [{ participatory_space: @superspace.to_s, stat_title: "participants_count", stat_value: solution }] + + data.map do |d| + [d[:participatory_space].to_sym, d[:stat_title].to_sym, d[:stat_value].to_i] + end + end + + private + + def participatory_space_ids + @participatory_space_ids ||= @superspace.participatory_processes.map(&:id) + end + + def assemblies_ids + @assemblies_ids ||= @superspace.assemblies.map(&:id) + end + + def conferences_ids + @conferences_ids ||= @superspace.conferences.map(&:id) + end + + def comments_query(class_name, ids) + return [] unless Decidim.module_installed?(:comments) + + Decidim::Comments::Comment + .where(decidim_participatory_space_type: class_name) + .where(decidim_participatory_space_id: ids) + .pluck(:decidim_author_id) + .uniq + end + + def debates_query(components) + return [] unless Decidim.module_installed?(:debates) + + Decidim::Debates::Debate + .where( + component: components, + decidim_author_type: Decidim::UserBaseEntity.name + ) + .not_hidden + .pluck(:decidim_author_id) + .uniq + end + + def meetings_query(components) + return [] unless Decidim.module_installed?(:meetings) + + meetings = Decidim::Meetings::Meeting.where(component: components).not_hidden + registrations = Decidim::Meetings::Registration.where(decidim_meeting_id: meetings).distinct.pluck(:decidim_user_id) + organizers = meetings.where(decidim_author_type: Decidim::UserBaseEntity.name).distinct.pluck(:decidim_author_id) + + [registrations, organizers].flatten.uniq + end + + def endorsements_query(components) + Decidim::Endorsement + .where(resource: components) + .pluck(:decidim_author_id) + .uniq + end + + def proposals_query(proposals_components) + return [] unless Decidim.module_installed?(:proposals) + + Decidim::Coauthorship + .where( + coauthorable: proposals_components, + decidim_author_type: Decidim::UserBaseEntity.name + ) + .pluck(:decidim_author_id) + .uniq + end + + def proposal_votes_query(proposals_components) + return [] unless Decidim.module_installed?(:proposals) + + Decidim::Proposals::ProposalVote + .where( + proposal: proposals_components + ) + .final + .pluck(:decidim_author_id) + .uniq + end + + def project_votes_query(components) + return [] unless Decidim.module_installed?(:budgets) + + Decidim::Budgets::Order.joins(budget: [:component]) + .where(budget: { + decidim_components: { id: components.pluck(:id) } + }) + .pluck(:decidim_user_id) + .uniq + end + + def survey_answer_query(components) + Decidim::Forms::Answer.newsletter_participant_ids(components) + end + + def space_components + Decidim::Component.where(participatory_space: @superspace.participatory_processes) + end + + def assemblies_components + Decidim::Component.where(participatory_space: @superspace.assemblies) + end + + def conferences_components + Decidim::Component.where(participatory_space: @superspace.conferences) + end + + def proposals_components + @proposals_components ||= Decidim::Proposals::FilteredProposals.for(space_components).published.not_hidden + end + + def assemblies_proposals_components + @assemblies_proposals_components ||= Decidim::Proposals::FilteredProposals.for(assemblies_components).published.not_hidden + end + + def conferences_proposals_components + @conferences_proposals_components ||= Decidim::Proposals::FilteredProposals.for(conferences_components).published.not_hidden + end + end + end +end diff --git a/app/views/decidim/superspaces/admin/superspaces/_form.html.erb b/app/views/decidim/superspaces/admin/superspaces/_form.html.erb index fde4eae..d781c5f 100644 --- a/app/views/decidim/superspaces/admin/superspaces/_form.html.erb +++ b/app/views/decidim/superspaces/admin/superspaces/_form.html.erb @@ -76,6 +76,9 @@ +