Implement vote results
This commit is contained in:
parent
0fb61f7a7f
commit
28071e1d7a
2
Gemfile
2
Gemfile
|
@ -54,7 +54,7 @@ gem 'jbuilder'
|
||||||
|
|
||||||
gem 'search_object'
|
gem 'search_object'
|
||||||
|
|
||||||
gem 'her'
|
gem 'faraday'
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
gem 'spring'
|
gem 'spring'
|
||||||
|
|
|
@ -150,11 +150,6 @@ GEM
|
||||||
guard (~> 2.1)
|
guard (~> 2.1)
|
||||||
guard-compat (~> 1.1)
|
guard-compat (~> 1.1)
|
||||||
rspec (>= 2.99.0, < 4.0)
|
rspec (>= 2.99.0, < 4.0)
|
||||||
her (0.8.2)
|
|
||||||
activemodel (>= 3.0.0, <= 6.0.0)
|
|
||||||
activesupport (>= 3.0.0, <= 6.0.0)
|
|
||||||
faraday (>= 0.8, < 1.0)
|
|
||||||
multi_json (~> 1.7)
|
|
||||||
highline (1.7.8)
|
highline (1.7.8)
|
||||||
hirb (0.7.3)
|
hirb (0.7.3)
|
||||||
http-cookie (1.0.3)
|
http-cookie (1.0.3)
|
||||||
|
@ -404,10 +399,10 @@ DEPENDENCIES
|
||||||
devise-i18n
|
devise-i18n
|
||||||
factory_girl_rails
|
factory_girl_rails
|
||||||
faker
|
faker
|
||||||
|
faraday
|
||||||
font-awesome-sass
|
font-awesome-sass
|
||||||
globalize
|
globalize
|
||||||
guard-rspec
|
guard-rspec
|
||||||
her
|
|
||||||
hirb
|
hirb
|
||||||
i18n-tasks
|
i18n-tasks
|
||||||
jbuilder
|
jbuilder
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
.huge {
|
.huge {
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.large {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
|
@ -44,6 +44,26 @@ module Management
|
||||||
redirect_to management_root_path(current_conference: nil)
|
redirect_to management_root_path(current_conference: nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_vote_data
|
||||||
|
@conference = find_conference
|
||||||
|
|
||||||
|
begin
|
||||||
|
if @conference.update_vote_data!
|
||||||
|
flash[:notice] = t '.vote_data_successfully_updated'
|
||||||
|
else
|
||||||
|
flash[:alert] = t '.error_during_vote_data_save'
|
||||||
|
end
|
||||||
|
rescue StandardError => e
|
||||||
|
flash[:alert] = t '.error_during_connection_with_voting_endpoint', error: e.message
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to :back
|
||||||
|
end
|
||||||
|
|
||||||
|
def vote_results
|
||||||
|
@conference = find_conference
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def find_conference
|
def find_conference
|
||||||
|
@ -52,7 +72,8 @@ module Management
|
||||||
|
|
||||||
def conference_params
|
def conference_params
|
||||||
params.require(:conference).permit(
|
params.require(:conference).permit(
|
||||||
:title, :email, :start_date, :end_date, :description, :host_name, :planned_cfp_end_date,
|
:title, :email, :start_date, :end_date, :description, :host_name,
|
||||||
|
:planned_cfp_end_date, :vote_data_endpoint,
|
||||||
event_types_attributes: [:id, :name, :description, :maximum_length,
|
event_types_attributes: [:id, :name, :description, :maximum_length,
|
||||||
:minimum_length, :_destroy],
|
:minimum_length, :_destroy],
|
||||||
tracks_attributes: [:id, :name, :color, :css_class, :description,
|
tracks_attributes: [:id, :name, :color, :css_class, :description,
|
||||||
|
|
|
@ -36,6 +36,14 @@ class Conference < ActiveRecord::Base
|
||||||
submissions.group_by { |s| s.confirmed_at.to_date }
|
submissions.group_by { |s| s.confirmed_at.to_date }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_vote_data!
|
||||||
|
ConferenceVoteSummary.new(conference: self).save
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_vote_results?
|
||||||
|
vote_data_updated_at.present?
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def planned_cfp_end_date_is_before_start_date
|
def planned_cfp_end_date_is_before_start_date
|
||||||
|
@ -49,4 +57,50 @@ class Conference < ActiveRecord::Base
|
||||||
errors.add(:end_date, :cannot_be_before_start_date)
|
errors.add(:end_date, :cannot_be_before_start_date)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class ConferenceVoteSummary
|
||||||
|
include ActiveModel::Model
|
||||||
|
|
||||||
|
attr_accessor :conference
|
||||||
|
|
||||||
|
def number_of_ballots
|
||||||
|
@number_of_ballots ||= remote_summary_data['number_of_ballots']
|
||||||
|
end
|
||||||
|
|
||||||
|
def ranking
|
||||||
|
@ranking ||= remote_summary_data['ranking'].map { |ranking_entry| EventRanking.new ranking_entry }
|
||||||
|
end
|
||||||
|
|
||||||
|
def save
|
||||||
|
conference.transaction do
|
||||||
|
conference.update number_of_ballots_cast: number_of_ballots
|
||||||
|
ranking.all?(&:save) or raise ActiveRecord::Rollback
|
||||||
|
conference.touch :vote_data_updated_at
|
||||||
|
conference.save or raise ActiveRecord::Rollback
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def conn
|
||||||
|
@conn ||= Faraday.new(url: conference.vote_data_endpoint + '/summary.json',
|
||||||
|
headers: {'Content-Type' => 'application/json'})
|
||||||
|
end
|
||||||
|
|
||||||
|
def remote_summary_data
|
||||||
|
@remote_summary_data ||= JSON.parse(conn.get do |request|
|
||||||
|
request.body = {summary: {talk_ids: Conference.last.events.pluck(:id)}}.to_json
|
||||||
|
end.body)
|
||||||
|
end
|
||||||
|
|
||||||
|
class EventRanking
|
||||||
|
include ActiveModel::Model
|
||||||
|
|
||||||
|
attr_accessor :talk_id, :votes, :place
|
||||||
|
|
||||||
|
def save
|
||||||
|
Event.find(talk_id).update(number_of_votes: votes, rank: place)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -60,6 +60,14 @@ class Event < ActiveRecord::Base
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def per_cent_of_votes
|
||||||
|
if conference.has_vote_results? and conference.number_of_ballots_cast > 0
|
||||||
|
Rational(number_of_votes * 100, conference.number_of_ballots_cast)
|
||||||
|
else
|
||||||
|
Float::NAN
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def track_belongs_to_the_selected_conference
|
def track_belongs_to_the_selected_conference
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
= f.input :start_date, as: :date
|
= f.input :start_date, as: :date
|
||||||
= f.input :end_date, as: :date
|
= f.input :end_date, as: :date
|
||||||
= f.input :description
|
= f.input :description
|
||||||
|
= f.input :vote_data_endpoint
|
||||||
hr
|
hr
|
||||||
.row
|
.row
|
||||||
.col-lg-12
|
.col-lg-12
|
||||||
|
|
|
@ -85,3 +85,50 @@
|
||||||
.row
|
.row
|
||||||
.col-lg-12
|
.col-lg-12
|
||||||
.submissions-chart#submissions-chart data-submissions="#{submissions_chart_data(@conference).to_json}"
|
.submissions-chart#submissions-chart data-submissions="#{submissions_chart_data(@conference).to_json}"
|
||||||
|
- if @conference.vote_data_endpoint.present?
|
||||||
|
hr
|
||||||
|
.row
|
||||||
|
.col-lg-12
|
||||||
|
h2
|
||||||
|
=> t '.voting_results'
|
||||||
|
small
|
||||||
|
- if @conference.vote_data_updated_at.present?
|
||||||
|
= t '.vote_data_updated_at', updated_at: l(@conference.vote_data_updated_at, format: :long)
|
||||||
|
.panel.panel-default
|
||||||
|
table.table.table-striped.table-hover.record-table
|
||||||
|
- if @conference.vote_data_updated_at.present?
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th.text-right = t('.rank')
|
||||||
|
th.text-right = t('.percent')
|
||||||
|
th = Event.model_name.human.mb_chars.capitalize
|
||||||
|
th
|
||||||
|
tbody
|
||||||
|
- if @conference.vote_data_updated_at.present?
|
||||||
|
- current_conference.events.order(rank: :asc).group_by(&:rank).to_a[0..9].each do |rank, events|
|
||||||
|
- events.each.with_index do |event, index|
|
||||||
|
tr
|
||||||
|
- if index == 0
|
||||||
|
td.text-right rowspan="#{events.count}"
|
||||||
|
.large
|
||||||
|
span.label.label-info = event.rank
|
||||||
|
td.text-right rowspan="#{events.count}"
|
||||||
|
span title="#{t('.vote_ratio', votes: event.number_of_votes, total_votes: @conference.number_of_ballots_cast)}"
|
||||||
|
= number_to_percentage(event.per_cent_of_votes, strip_insignificant_zeros: true, precision: 2)
|
||||||
|
td = event.title
|
||||||
|
td.actions = action_buttons(@conference, event, [:show])
|
||||||
|
- else
|
||||||
|
tr
|
||||||
|
td.text-center colspan="20"
|
||||||
|
p.large
|
||||||
|
= t '.vote_data_never_updated'
|
||||||
|
p
|
||||||
|
=< link_to update_vote_data_management_conference_path, method: :patch, class: ['btn', 'btn-primary'] do
|
||||||
|
= icon :refresh, t('.fetch_vote_results')
|
||||||
|
- if @conference.vote_data_updated_at.present?
|
||||||
|
.panel-footer.text-right
|
||||||
|
.btn-group
|
||||||
|
= link_to vote_results_management_conference_path, class: ['btn', 'btn-info'] do
|
||||||
|
= icon 'list-ol', t('.full_vote_results')
|
||||||
|
= link_to update_vote_data_management_conference_path, method: :patch, class: ['btn', 'btn-primary'] do
|
||||||
|
= icon :refresh, t('.fetch_vote_results')
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
- content_for :title
|
||||||
|
= t '.vote_results'
|
||||||
|
|
||||||
|
.row
|
||||||
|
.col-lg-12
|
||||||
|
h1.page-header
|
||||||
|
= t '.vote_results'
|
||||||
|
- if @conference.vote_data_updated_at.present?
|
||||||
|
small<
|
||||||
|
= t '.vote_data_updated_at', updated_at: l(@conference.vote_data_updated_at, format: :long)
|
||||||
|
- if @conference.vote_data_endpoint.present?
|
||||||
|
.row
|
||||||
|
.col-lg-12
|
||||||
|
.panel.panel-default
|
||||||
|
table.table.table-striped.table-hover.record-table
|
||||||
|
- if @conference.vote_data_updated_at.present?
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th.text-right = t('.rank')
|
||||||
|
th.text-right = t('.percent')
|
||||||
|
th = Event.model_name.human.mb_chars.capitalize
|
||||||
|
th
|
||||||
|
tbody
|
||||||
|
- if @conference.vote_data_updated_at.present?
|
||||||
|
- current_conference.events.order(rank: :asc).group_by(&:rank).each do |rank, events|
|
||||||
|
- events.each.with_index do |event, index|
|
||||||
|
tr
|
||||||
|
- if index == 0
|
||||||
|
td.text-right rowspan="#{events.count}"
|
||||||
|
.large
|
||||||
|
span.label.label-info = event.rank
|
||||||
|
td.text-right rowspan="#{events.count}"
|
||||||
|
span title="#{t('.vote_ratio', votes: event.number_of_votes, total_votes: @conference.number_of_ballots_cast)}"
|
||||||
|
= number_to_percentage(event.per_cent_of_votes, strip_insignificant_zeros: true, precision: 2)
|
||||||
|
td = event.title
|
||||||
|
td.actions = action_buttons(@conference, event, [:show])
|
||||||
|
- else
|
||||||
|
tr
|
||||||
|
td.text-center colspan="20"
|
||||||
|
p.large
|
||||||
|
= t '.vote_data_never_updated'
|
||||||
|
p
|
||||||
|
=< link_to update_vote_data_management_conference_path, method: :patch, class: ['btn', 'btn-primary'] do
|
||||||
|
= icon :refresh, t('.fetch_vote_results')
|
||||||
|
- if @conference.vote_data_updated_at.present?
|
||||||
|
.panel-footer.text-right
|
||||||
|
.btn-group
|
||||||
|
= link_to management_conference_path, class: ['btn', 'btn-info'] do
|
||||||
|
= icon :users, t('.back_to', conference: @conference.title)
|
||||||
|
= link_to update_vote_data_management_conference_path, method: :patch, class: ['btn', 'btn-primary'] do
|
||||||
|
= icon :refresh, t('.fetch_vote_results')
|
|
@ -15,9 +15,31 @@ bg:
|
||||||
contacts: "Данни за контакт"
|
contacts: "Данни за контакт"
|
||||||
event_propositions: "Предложения за събития"
|
event_propositions: "Предложения за събития"
|
||||||
conferences:
|
conferences:
|
||||||
|
update_vote_data:
|
||||||
|
vote_data_successfully_updated: "Резултатите от гласуването бяха обновени успешно"
|
||||||
|
error_during_vote_data_save: "Възникна грешка при запазването на резултатите от гласуването"
|
||||||
|
error_during_connection_with_voting_endpoint: "Възникна грешка при опит за изтегляне на резултатите от гласуването: %{error}"
|
||||||
|
vote_results:
|
||||||
|
back_to: "Обратно към %{conference}"
|
||||||
|
vote_results: "Резултати от гласуването"
|
||||||
|
vote_data_never_updated: "Резулатите от гласуването не са изтеглени"
|
||||||
|
voting_results: "Резултати от гласуването"
|
||||||
|
vote_data_updated_at: "последно обновяване %{updated_at}"
|
||||||
|
vote_ratio: "%{votes} от общо %{total_votes} гласа"
|
||||||
|
fetch_vote_results: "Изтегли резултатите от гласуването"
|
||||||
|
percent: "%"
|
||||||
|
rank: "Позиция"
|
||||||
show:
|
show:
|
||||||
|
full_vote_results: "Пълни резултати от гласуването"
|
||||||
|
vote_data_never_updated: "Резулатите от гласуването не са изтеглени"
|
||||||
|
voting_results: "Резултати от гласуването"
|
||||||
|
vote_data_updated_at: "последно обновяване %{updated_at}"
|
||||||
|
vote_ratio: "%{votes} от общо %{total_votes} гласа"
|
||||||
summary: 'Обобщение'
|
summary: 'Обобщение'
|
||||||
cfp_status: 'Състояние на CFP:'
|
cfp_status: 'Състояние на CFP:'
|
||||||
|
fetch_vote_results: "Изтегли резултатите от гласуването"
|
||||||
|
percent: "%"
|
||||||
|
rank: "Позиция"
|
||||||
events:
|
events:
|
||||||
edit:
|
edit:
|
||||||
edit: "Редакция на %{event_type} „%{event_title}“"
|
edit: "Редакция на %{event_type} „%{event_title}“"
|
||||||
|
|
|
@ -33,6 +33,11 @@ Rails.application.routes.draw do
|
||||||
root to: 'conferences#index'
|
root to: 'conferences#index'
|
||||||
|
|
||||||
resources :conferences do
|
resources :conferences do
|
||||||
|
member do
|
||||||
|
patch :update_vote_data
|
||||||
|
get :vote_results
|
||||||
|
end
|
||||||
|
|
||||||
resources :events
|
resources :events
|
||||||
resources :volunteers
|
resources :volunteers
|
||||||
resources :propositions
|
resources :propositions
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
class AddVoteDataToConferences < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :conferences, :vote_data_endpoint, :string
|
||||||
|
add_column :conferences, :number_of_ballots_cast, :integer
|
||||||
|
add_column :conferences, :vote_data_updated_at, :datetime
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,6 @@
|
||||||
|
class AddVoteDataToEvents < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :events, :number_of_votes, :integer
|
||||||
|
add_column :events, :rank, :integer
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue