Skip to content

Commit

Permalink
Export weights (#17)
Browse files Browse the repository at this point in the history
* add label generation for the manifest

* tweak exported

* add weights to the exporter

* fix tests

* add graphql entry
  • Loading branch information
microstudi authored Oct 26, 2023
1 parent eeac7ac commit 9af4f20
Show file tree
Hide file tree
Showing 17 changed files with 416 additions and 39 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ if Decidim::DecidimAwesome.enabled?(:weighted_proposal_voting)
voting.show_votes_count_view = "decidim/decidim_awesome/voting/no_admins_vote/show_votes_count"
# voting.show_votes_count_view = "" # hide votes count if needed
voting.proposal_m_cell_footer = "decidim/decidim_awesome/voting/no_admins_vote/proposal_m_cell_footer"
# define a weight validator (optional, by default all weights are valid)
voting.weight_validator do |weight, context|
# don't allow admins to vote
next if context[:user].admin?
Expand All @@ -194,6 +195,14 @@ if Decidim::DecidimAwesome.enabled?(:weighted_proposal_voting)

weight.in? [1, 2, 3, 5]
end

# optionally, define a label generator block
# by default labels are extracted from a I18n key following this rule
# "decidim.decidim_awesome.votings.manifests.{MANIFEST_NAME}.weight_{WEIGHT}"
#
# voting.label_generator do |weight, context|
# "Weight #{weight.round}"
# end
end
end
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def vote_manifest
end

def weight
params[:weight].to_i
params[:weight].to_i if params.has_key?(:weight)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module HasVoteWeight
has_one :vote_weight, foreign_key: "proposal_vote_id", class_name: "Decidim::DecidimAwesome::VoteWeight", dependent: :destroy

delegate :weight, to: :vote_weight, allow_nil: true
delegate :update_vote_weight_totals!, to: :vote_weight, allow_nil: true

def weight=(new_weight)
vote_weight = VoteWeight.find_or_initialize_by(vote: self)
Expand Down
34 changes: 34 additions & 0 deletions app/models/concerns/decidim/decidim_awesome/has_weight_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,40 @@ module HasWeightCache
def weight_count(weight)
(weight_cache && weight_cache.totals[weight.to_s]) || 0
end

def vote_weights
@vote_weights ||= all_vote_weights.to_h { |weight| [manifest&.label_for(weight) || weight.to_s, weight_count(weight)] }
end

def manifest
@manifest ||= DecidimAwesome.voting_registry.find(component.settings.awesome_voting_manifest)
end

def all_vote_weights
@all_vote_weights ||= self.class.all_vote_weights_for(component)
end

def update_vote_weights!
weight_cache ||= Decidim::DecidimAwesome::WeightCache.find_or_initialize_by(proposal: self)
weight_cache.totals = {}
votes.each do |vote|
weight_cache.totals[vote.weight] ||= 0
weight_cache.totals[vote.weight] += 1
end
weight_cache.save!
self.weight_cache = weight_cache
@vote_weights = nil
@all_vote_weights = nil
end

# collects all different weights stored along the different proposals in a different component
def self.all_vote_weights_for(component)
Decidim::DecidimAwesome::VoteWeight.where(
proposal_vote_id: Decidim::Proposals::ProposalVote.where(
proposal: Decidim::Proposals::Proposal.where(component: component)
)
).pluck(:weight)
end
end
end
end
Expand Down
6 changes: 3 additions & 3 deletions app/models/decidim/decidim_awesome/vote_weight.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ class VoteWeight < ApplicationRecord

delegate :proposal, to: :vote

after_destroy :update_vote_weight_totals
after_save :update_vote_weight_totals
after_destroy :update_vote_weight_totals!
after_save :update_vote_weight_totals!

def update_vote_weight_totals
def update_vote_weight_totals!
cache = Decidim::DecidimAwesome::WeightCache.find_or_initialize_by(proposal: proposal)
cache.totals = cache.totals || {}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true

module Decidim
module DecidimAwesome
module ProposalSerializerOverride
extend ActiveSupport::Concern

included do
# Public: Exports a hash with the serialized data for this proposal.
def serialize
{
id: proposal.id,
category: {
id: proposal.category.try(:id),
name: proposal.category.try(:name) || empty_translatable
},
scope: {
id: proposal.scope.try(:id),
name: proposal.scope.try(:name) || empty_translatable
},
participatory_space: {
id: proposal.participatory_space.id,
url: Decidim::ResourceLocatorPresenter.new(proposal.participatory_space).url
},
component: { id: component.id },
title: proposal.title,
body: convert_to_plain_text(proposal.body),
address: proposal.address,
latitude: proposal.latitude,
longitude: proposal.longitude,
state: proposal.state.to_s,
reference: proposal.reference,
answer: ensure_translatable(proposal.answer),
supports: proposal.proposal_votes_count,
weights: proposal_vote_weights,
endorsements: {
total_count: proposal.endorsements.size,
user_endorsements: user_endorsements
},
comments: proposal.comments_count,
attachments: proposal.attachments.size,
followers: proposal.follows.size,
published_at: proposal.published_at,
url: url,
meeting_urls: meetings,
related_proposals: related_proposals,
is_amend: proposal.emendation?,
original_proposal: {
title: proposal&.amendable&.title,
url: original_proposal_url
}
}
end

private

def proposal_vote_weights
proposal.update_vote_weights!
proposal.vote_weights
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Decidim
module DecidimAwesome
module ProposalTypeOverride
extend ActiveSupport::Concern

included do
field :vote_weights, GraphQL::Types::JSON, description: "The corresponding weights count to the proposal votes", null: true

def vote_weights
current_component = object.component
object.vote_weights unless current_component.current_settings.votes_hidden?
end
end
end
end
end
7 changes: 7 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,13 @@ en:
validators:
too_much_caps: Is using too many capital letters (over %{percent}% of the
text)
votings:
manifests:
three_flags:
weight_0: Abstain
weight_1: Red
weight_2: Yellow
weight_3: Green
layouts:
decidim:
admin:
Expand Down
4 changes: 4 additions & 0 deletions lib/decidim/decidim_awesome/checksums.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ decidim-core:
decidim-0.25: 63532a04fcf07cf92b789833a9421416
decidim-0.26: 636943ccb2e994fe0124dc87e72e49e9
decidim-proposals:
/lib/decidim/proposals/proposal_serializer.rb:
decidim-0.26: 2d817775f79a19cfcf9cfae8e5b68c5a
/lib/decidim/api/proposal_type.rb:
decidim-0.26: e38e4875e408ec4f658e28725c5a94d4
/app/forms/decidim/proposals/proposal_wizard_create_step_form.rb:
decidim-0.24: db69edd0ba8ffa3965a5c44a6bfaba8d
decidim-0.27: b495d37088ecebcbe4ac9563bb3498d5
Expand Down
4 changes: 3 additions & 1 deletion lib/decidim/decidim_awesome/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class Engine < ::Rails::Engine
Decidim::Proposals::ProposalVote.include(Decidim::DecidimAwesome::HasVoteWeight)
# add vote weight cache to proposal
Decidim::Proposals::Proposal.include(Decidim::DecidimAwesome::HasWeightCache)
Decidim::Proposals::ProposalSerializer.include(Decidim::DecidimAwesome::ProposalSerializerOverride)
Decidim::Proposals::ProposalType.include(Decidim::DecidimAwesome::ProposalTypeOverride)
end

Decidim::MenuPresenter.include(Decidim::DecidimAwesome::MenuPresenterOverride)
Expand Down Expand Up @@ -103,7 +105,7 @@ class Engine < ::Rails::Engine
# voting.show_votes_count_view = "" # hide votes count if needed
voting.proposal_m_cell_footer = "decidim/decidim_awesome/voting/three_flags/proposal_m_cell_footer"
voting.weight_validator do |weight, _context|
weight.in? [1, 2, 3]
weight.in? [0, 1, 2, 3]
end
end

Expand Down
15 changes: 14 additions & 1 deletion lib/decidim/decidim_awesome/voting_manifest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,23 @@ def weight_validator(&block)
# validates the weight using the Proc defined by weight_validator
# Receives the weight and a context with the user and the proposal to be voted
def valid_weight?(weight, context = {})
return unless @on_weight_validation
return true unless @on_weight_validation

@on_weight_validation.call(weight, **context)
end

# registers an optional label generator block
def label_generator(&block)
@on_label_generation = block
end

# returns the label used in export or other places for a given weight
# defaults to a I18n key scoped under decidim_awesome
def label_for(weight, context = {})
return I18n.t("decidim.decidim_awesome.votings.manifests.#{name}.weight_#{weight}", default: "weight_#{weight}") unless @on_label_generation

@on_label_generation.call(weight, **context)
end
end
end
end
4 changes: 2 additions & 2 deletions spec/lib/system_checker_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ module Decidim::DecidimAwesome
expect(subject.overrides["decidim-core"].files.length).to eq(6)
end

it "has 9 modified files in proposals" do
expect(subject.overrides["decidim-proposals"].files.length).to eq(9)
it "has 11 modified files in proposals" do
expect(subject.overrides["decidim-proposals"].files.length).to eq(11)
end

context "when file" do
Expand Down
101 changes: 72 additions & 29 deletions spec/lib/voting_manifest_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,6 @@ module Decidim::DecidimAwesome
subject { described_class.new(name: name) }
let(:name) { :test }

before do
subject.weight_validator do |weight, context|
next if context && context[:already_voted_this_weight]

weight.in? [1, 2, 3, 4, 5]
end
end

it { is_expected.to be_valid }

context "when no name" do
Expand All @@ -23,40 +15,91 @@ module Decidim::DecidimAwesome
it { is_expected.to be_invalid }
end

it "validates weights" do
it "any weight is valid" do
expect(subject).to be_valid_weight(0)
expect(subject).to be_valid_weight(1)
expect(subject).to be_valid_weight(3)
expect(subject).to be_valid_weight(5)
expect(subject).not_to be_valid_weight(0)
expect(subject).not_to be_valid_weight(1.5)
expect(subject).not_to be_valid_weight(6)
expect(subject).to be_valid_weight(1.5)
expect(subject).to be_valid_weight(-1.5)
end

it "returns automatic labels" do
expect(subject.label_for(0)).to eq("weight_0")
expect(subject.label_for(1)).to eq("weight_1")
expect(subject.label_for(1.5)).to eq("weight_1.5")
expect(subject.label_for(-1.5)).to eq("weight_-1.5")
end

context "when I18n labels exist" do
let(:name) { :three_flags }

it "returns I18n labels" do
expect(subject.label_for(0)).to eq("Abstain")
expect(subject.label_for(1)).to eq("Red")
expect(subject.label_for(2)).to eq("Yellow")
expect(subject.label_for(3)).to eq("Green")
expect(subject.label_for(3.5)).to eq("weight_3.5")
end
end

context "when label_generator block" do
before do
subject.label_generator do |weight, _context|
"custom weight: #{weight.round}"
end
end

it "returns custom labels" do
expect(subject.label_for(0)).to eq("custom weight: 0")
expect(subject.label_for(1)).to eq("custom weight: 1")
expect(subject.label_for(1.5)).to eq("custom weight: 2")
expect(subject.label_for(-1.5)).to eq("custom weight: -2")
end
end

context "when context is set" do
let(:voted) { false }
let(:context) do
{
foo: "bar",
already_voted_this_weight: voted
}
context "when weight_validator block" do
before do
subject.weight_validator do |weight, context|
next if context && context[:already_voted_this_weight]

weight.in? [1, 2, 3, 4, 5]
end
end

it "validates weights" do
expect(subject).to be_valid_weight(1, context)
expect(subject).not_to be_valid_weight(0, context)
expect(subject).to be_valid_weight(5, context)
expect(subject).not_to be_valid_weight(6, context)
expect(subject).to be_valid_weight(1)
expect(subject).to be_valid_weight(3)
expect(subject).to be_valid_weight(5)
expect(subject).not_to be_valid_weight(0)
expect(subject).not_to be_valid_weight(1.5)
expect(subject).not_to be_valid_weight(6)
end

context "and voted" do
let(:voted) { true }
context "when context is set" do
let(:voted) { false }
let(:context) do
{
foo: "bar",
already_voted_this_weight: voted
}
end

it "validates weights" do
expect(subject).not_to be_valid_weight(1, context)
expect(subject).to be_valid_weight(1, context)
expect(subject).not_to be_valid_weight(0, context)
expect(subject).not_to be_valid_weight(5, context)
expect(subject).to be_valid_weight(5, context)
expect(subject).not_to be_valid_weight(6, context)
end

context "and voted" do
let(:voted) { true }

it "validates weights" do
expect(subject).not_to be_valid_weight(1, context)
expect(subject).not_to be_valid_weight(0, context)
expect(subject).not_to be_valid_weight(5, context)
expect(subject).not_to be_valid_weight(6, context)
end
end
end
end
end
Expand Down
Loading

0 comments on commit 9af4f20

Please sign in to comment.