diff --git a/app/controllers/polls_controller.rb b/app/controllers/polls_controller.rb index 338f72cc4..a13bfd33d 100644 --- a/app/controllers/polls_controller.rb +++ b/app/controllers/polls_controller.rb @@ -25,6 +25,8 @@ class PollsController < ApplicationController @commentable = @poll @comment_tree = CommentTree.new(@commentable, params[:page], @current_order) + + @stats = Poll::Stats.new(@poll).generate end end diff --git a/app/models/abilities/everyone.rb b/app/models/abilities/everyone.rb index 3004ca8ad..73f3220ab 100644 --- a/app/models/abilities/everyone.rb +++ b/app/models/abilities/everyone.rb @@ -23,6 +23,7 @@ module Abilities can [:read], Legislation::Question can [:create], Legislation::Answer can [:search, :comments, :read, :create, :new_comment], Legislation::Annotation + can :read_stats, Poll end end end diff --git a/app/models/poll/stats.rb b/app/models/poll/stats.rb new file mode 100644 index 000000000..1df281434 --- /dev/null +++ b/app/models/poll/stats.rb @@ -0,0 +1,125 @@ +class Poll + class Stats + + def initialize(poll) + @poll = poll + end + + def generate + stats = %w[total_participants total_participants_web total_web_valid total_web_white total_web_null + total_participants_booth total_booth_valid total_booth_white total_booth_null + total_valid_votes total_white_votes total_null_votes valid_percentage_web valid_percentage_booth + total_valid_percentage white_percentage_web white_percentage_booth total_white_percentage + null_percentage_web null_percentage_booth total_null_percentage total_participants_web_percentage + total_participants_booth_percentage] + stats.map { |stat_name| [stat_name.to_sym, send(stat_name)] }.to_h + end + + private + + def total_participants + total_participants_web + total_participants_booth + end + + def total_participants_web + total_web_valid + total_web_white + total_web_null + end + + def total_participants_web_percentage + (total_participants) == 0 ? 0 : total_participants_web * 100 / total_participants + end + + def total_participants_booth + voters.where(origin: 'booth').count + end + + def total_participants_booth_percentage + (total_participants) == 0 ? 0 : total_participants_booth * 100 / total_participants.to_f + end + + def total_web_valid + voters.where(origin: 'web').count + end + + def valid_percentage_web + (total_valid_votes) == 0 ? 0 : total_web_valid * 100 / total_valid_votes.to_f + end + + def total_web_white + 0 + end + + def white_percentage_web + 0 + end + + def total_web_null + 0 + end + + def null_percentage_web + 0 + end + + def total_booth_valid + recounts.sum(:total_amount) + end + + def valid_percentage_booth + (total_valid_votes) == 0 ? 0 : total_booth_valid * 100 / total_valid_votes.to_f + end + + def total_booth_white + recounts.sum(:white_amount) + end + + def white_percentage_booth + (total_white_votes) == 0 ? 0 : total_booth_white * 100 / total_white_votes.to_f + end + + def total_booth_null + recounts.sum(:null_amount) + end + + def null_percentage_booth + (total_null_votes == 0) ? 0 : total_booth_null * 100 / total_null_votes.to_f + end + + def total_valid_votes + total_web_valid + total_booth_valid + end + + def total_valid_percentage + (total_participants) == 0 ? 0 : total_valid_votes * 100 / total_participants.to_f + end + + def total_white_votes + total_web_white + total_booth_white + end + + def total_white_percentage + (total_participants) == 0 ? 0 : total_white_votes * 100 / total_participants.to_f + end + + def total_null_votes + total_web_null + total_booth_null + end + + def total_null_percentage + (total_participants) == 0 ? 0 : total_null_votes * 100 / total_participants.to_f + end + + def voters + @poll.voters + end + + def recounts + @poll.recounts + end + + def stats_cache(key, &block) + Rails.cache.fetch("polls_stats/#{@poll.id}/#{key}/v7", &block) + end + + end +end \ No newline at end of file diff --git a/app/views/polls/_results_subnavigation.html.erb b/app/views/polls/_results_subnavigation.html.erb new file mode 100644 index 000000000..9c2a7e8b0 --- /dev/null +++ b/app/views/polls/_results_subnavigation.html.erb @@ -0,0 +1,20 @@ +
+
+ +
+
\ No newline at end of file diff --git a/app/views/polls/_show.html.erb b/app/views/polls/_show.html.erb new file mode 100644 index 000000000..ee75ea5e3 --- /dev/null +++ b/app/views/polls/_show.html.erb @@ -0,0 +1,126 @@ +
+
+ <%= render "callout" %> + + <% if @poll.voted_in_booth?(current_user) %> +
+ <%= t("polls.show.already_voted_in_booth") %> +
+ <% else %> + + <% if current_user && @poll.voted_in_web?(current_user) %> +
+ <%= t("polls.show.already_voted_in_web") %> +
+ <% end %> + <% end %> + + <% @questions.each do |question| %> + <%= render 'polls/questions/question', question: question, token: @token %> + <% end %> + + <% if poll_voter_token(@poll, current_user).empty? %> + + <% end %> + + <%= link_to t("polls.show.participate_in_other_polls"), polls_path, class: "button hollow" %> +
+
+ +
+
+
+

<%= t("polls.show.more_info_title") %>

+ <%= safe_html_with_links simple_format(@poll.description) %> +
+ + <% if false %> + + <% end %> +
+
+ +
+
+ + <% @poll_questions_answers.each do |answer| %> +
+ + <% if answer.description.present? %> +

<%= answer.title %>

+ <% end %> + + <% if answer.images.any? %> + <%= render "gallery", answer: answer %> + <% end %> + + <% if answer.description.present? %> + + <% end %> + + <% if answer.documents.present? %> + + <% end %> + + <% if answer.videos.present? %> + + <% end %> +
+ <% end %> +
+ +
+ +
+ <%= render "filter_subnav" %> + +
+ <%= render "comments" %> +
+
\ No newline at end of file diff --git a/app/views/polls/show.html.erb b/app/views/polls/show.html.erb index f2ad579d4..880d0d421 100644 --- a/app/views/polls/show.html.erb +++ b/app/views/polls/show.html.erb @@ -28,131 +28,17 @@ + +
+ <%= render "results_subnavigation" %> -
-
- <%= render "callout" %> - - <% if @poll.voted_in_booth?(current_user) %> -
- <%= t("polls.show.already_voted_in_booth") %> -
- <% else %> - - <% if current_user && @poll.voted_in_web?(current_user) %> -
- <%= t("polls.show.already_voted_in_web") %> -
- <% end %> - <% end %> - - <% @questions.each do |question| %> - <%= render 'polls/questions/question', question: question, token: @token %> - <% end %> - - <% if poll_voter_token(@poll, current_user).empty? %> - - <% end %> - - <%= link_to t("polls.show.participate_in_other_polls"), polls_path, class: "button hollow" %> +
+ <%= render "polls/stats/show" %>
-
- -
-
-
-

<%= t("polls.show.more_info_title") %>

- <%= safe_html_with_links simple_format(@poll.description) %> -
- - <% if false %> - - <% end %> -
-
- -
-
- - <% @poll_questions_answers.each do |answer| %> -
- - <% if answer.description.present? %> -

<%= answer.title %>

- <% end %> - - <% if answer.images.any? %> - <%= render "gallery", answer: answer %> - <% end %> - - <% if answer.description.present? %> - - <% end %> - - <% if answer.documents.present? %> - - <% end %> - - <% if answer.videos.present? %> - - <% end %> -
- <% end %> -
- + +
+ <%= render "show" %> +
-
- <%= render "filter_subnav" %> - -
- <%= render "comments" %> -
-
diff --git a/app/views/polls/stats/_show.html.erb b/app/views/polls/stats/_show.html.erb new file mode 100644 index 000000000..1aeb7cf69 --- /dev/null +++ b/app/views/polls/stats/_show.html.erb @@ -0,0 +1,85 @@ +
+ +
+

<%= t("polls.show.stats.total_participation") %>

+ +

+ <%= t("polls.show.stats.total_votes") %>
+ <%= @stats[:total_participants] %> +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
<%= t("polls.show.stats.votes") %><%= t("polls.show.stats.web") %><%= t("polls.show.stats.booth") %><%= t("polls.show.stats.total") %>
<%= t("polls.show.stats.valid") %><%= @stats[:total_web_valid] %> + (<%= number_to_percentage(@stats[:valid_percentage_web], + strip_insignificant_zeros: true, + precision: 2) %>)<%= @stats[:total_booth_valid] %> + (<%= number_to_percentage(@stats[:valid_percentage_booth], + strip_insignificant_zeros: true, + precision: 2) %>)<%= @stats[:total_valid_votes] %> + (<%= number_to_percentage(@stats[:total_valid_percentage], + strip_insignificant_zeros: true, + precision: 2) %>)
<%= t("polls.show.stats.white") %><%= @stats[:total_web_white] %> + (<%= number_to_percentage(@stats[:white_percentage_web], + strip_insignificant_zeros: true, + precision: 2) %>)<%= @stats[:total_booth_white] %> + (<%= number_to_percentage(@stats[:white_percentage_booth], + strip_insignificant_zeros: true, + precision: 2) %>)<%= @stats[:total_white_votes] %> + (<%= number_to_percentage(@stats[:total_white_percentage], + strip_insignificant_zeros: true, + precision: 2) %>)
<%= t("polls.show.stats.null_votes") %><%= @stats[:total_web_null] %> + (<%= number_to_percentage(@stats[:null_percentage_web], + strip_insignificant_zeros: true, + precision: 2) %>)<%= @stats[:total_booth_null] %> + (<%= number_to_percentage(@stats[:null_percentage_booth], + strip_insignificant_zeros: true, + precision: 2) %>)<%= @stats[:total_null_votes] %> + (<%= number_to_percentage(@stats[:total_null_percentage], + strip_insignificant_zeros: true, + precision: 2) %>)
<%= t("polls.show.stats.total") %><%= @stats[:total_participants_web] %> + (<%= number_to_percentage(@stats[:total_participants_web_percentage], + strip_insignificant_zeros: true, + precision: 2) %>)<%= @stats[:total_participants_booth] %> + (<%= number_to_percentage(@stats[:total_participants_booth_percentage], + strip_insignificant_zeros: true, + precision: 2) %>)<%= @stats[:total_participants_web] + @stats[:total_participants_booth] %>
+
+
\ No newline at end of file diff --git a/config/locales/en/general.yml b/config/locales/en/general.yml index ec1e1e767..85bfbe699 100644 --- a/config/locales/en/general.yml +++ b/config/locales/en/general.yml @@ -498,6 +498,19 @@ en: read_less: "Read less about %{answer}" participate_in_other_polls: Participate in other polls videos: "External video" + info_menu: "Information" + stats_menu: "Participation statistics" + stats: + title: "Participation data" + total_participation: "Total participation" + total_votes: "Total amount of given votes" + votes: "VOTES" + web: "WEB" + booth: "BOOTH" + total: "TOTAL" + valid: "Valid" + white: "White votes" + null_votes: "Invalid" poll_questions: create_question: "Create question" show: diff --git a/config/locales/es/general.yml b/config/locales/es/general.yml index c1e71cb33..a15952cc9 100644 --- a/config/locales/es/general.yml +++ b/config/locales/es/general.yml @@ -498,6 +498,19 @@ es: read_less: "Leer menos sobre %{answer}" participate_in_other_polls: Participar en otras votaciones videos: "Vídeo externo" + info_menu: "Información" + stats_menu: "Estadísticas de participación" + stats: + title: "Datos de participación" + total_participation: "Participación total" + total_votes: "Nº total de votos emitidos" + votes: "VOTOS" + web: "WEB" + booth: "URNAS" + total: "TOTAL" + valid: "Válidos" + white: "En blanco" + null_votes: "Nulos" poll_questions: create_question: "Crear pregunta para votación" show: diff --git a/config/routes.rb b/config/routes.rb index 1fb74aa53..ae4f7280c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -111,7 +111,7 @@ Rails.application.routes.draw do resources :annotations do get :search, on: :collection end - + resources :polls, only: [:show, :index] do resources :questions, controller: 'polls/questions', shallow: true do post :answer, on: :member diff --git a/spec/features/polls/polls_spec.rb b/spec/features/polls/polls_spec.rb index e36720038..7bf51473f 100644 --- a/spec/features/polls/polls_spec.rb +++ b/spec/features/polls/polls_spec.rb @@ -367,4 +367,17 @@ feature 'Polls' do end end + + context "Results and stats" do + scenario "See polls statistics", :js do + user = create(:user) + poll = create(:poll, summary: "Summary", description: "Description") + login_as user + visit poll_path(poll) + + click_link "Participation statistics" + + expect(page).to have_content("Total participation") + end + end end diff --git a/spec/models/poll/stats_spec.rb b/spec/models/poll/stats_spec.rb new file mode 100644 index 000000000..ee431fa97 --- /dev/null +++ b/spec/models/poll/stats_spec.rb @@ -0,0 +1,45 @@ +require 'rails_helper' + +describe Poll::Stats do + + describe "Calculate stats" do + it "Generate the correct stats" do + poll = create(:poll) + booth = create(:poll_booth) + booth_assignment = create(:poll_booth_assignment, poll: poll, booth: booth) + create(:poll_voter, poll: poll, origin: 'web') + 3.times {create(:poll_voter, poll: poll, origin: 'booth')} + create(:poll_voter, poll: poll) + create(:poll_recount, origin: 'booth', white_amount: 1, null_amount: 0, total_amount: 2, booth_assignment_id: booth_assignment.id) + stats = Poll::Stats.new(poll).generate + + expect(stats[:total_participants]).to eq(5) + expect(stats[:total_participants_web]).to eq(2) + expect(stats[:total_participants_booth]).to eq(3) + expect(stats[:total_valid_votes]).to eq(4) + expect(stats[:total_white_votes]).to eq(1) + expect(stats[:total_null_votes]).to eq(0) + + expect(stats[:total_web_valid]).to eq(2) + expect(stats[:total_web_white]).to eq(0) + expect(stats[:total_web_null]).to eq(0) + + expect(stats[:total_booth_valid]).to eq(2) + expect(stats[:total_booth_white]).to eq(1) + expect(stats[:total_booth_null]).to eq(0) + + expect(stats[:total_participants_web_percentage]).to eq(40) + expect(stats[:total_participants_booth_percentage]).to eq(60) + expect(stats[:valid_percentage_web]).to eq(50) + expect(stats[:white_percentage_web]).to eq(0) + expect(stats[:null_percentage_web]).to eq(0) + expect(stats[:valid_percentage_booth]).to eq(50) + expect(stats[:white_percentage_booth]).to eq(100) + expect(stats[:null_percentage_booth]).to eq(0) + expect(stats[:total_valid_percentage]).to eq(80) + expect(stats[:total_white_percentage]).to eq(20) + expect(stats[:total_null_percentage]).to eq(0) + end + end + +end \ No newline at end of file