diff --git a/Gemfile b/Gemfile index 37a9f48..42f20dd 100644 --- a/Gemfile +++ b/Gemfile @@ -54,7 +54,7 @@ gem 'jbuilder' gem 'search_object' -gem 'her' +gem 'faraday' group :development do gem 'spring' diff --git a/Gemfile.lock b/Gemfile.lock index e15f505..2e7cd4a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -150,11 +150,6 @@ GEM guard (~> 2.1) guard-compat (~> 1.1) 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) hirb (0.7.3) http-cookie (1.0.3) @@ -404,10 +399,10 @@ DEPENDENCIES devise-i18n factory_girl_rails faker + faraday font-awesome-sass globalize guard-rspec - her hirb i18n-tasks jbuilder diff --git a/app/assets/stylesheets/management/_dashboard.scss b/app/assets/stylesheets/management/_dashboard.scss index 93486ce..3433f8a 100644 --- a/app/assets/stylesheets/management/_dashboard.scss +++ b/app/assets/stylesheets/management/_dashboard.scss @@ -1,3 +1,7 @@ .huge { font-size: 40px; -} \ No newline at end of file +} + +.large { + font-size: 2rem; +} diff --git a/app/controllers/management/conferences_controller.rb b/app/controllers/management/conferences_controller.rb index 7b4e4a9..c5ed982 100644 --- a/app/controllers/management/conferences_controller.rb +++ b/app/controllers/management/conferences_controller.rb @@ -44,6 +44,26 @@ module Management redirect_to management_root_path(current_conference: nil) 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 def find_conference @@ -52,7 +72,8 @@ module Management def conference_params 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, :minimum_length, :_destroy], tracks_attributes: [:id, :name, :color, :css_class, :description, diff --git a/app/models/conference.rb b/app/models/conference.rb index 05f0ad7..6e17ba9 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -36,6 +36,14 @@ class Conference < ActiveRecord::Base submissions.group_by { |s| s.confirmed_at.to_date } end + def update_vote_data! + ConferenceVoteSummary.new(conference: self).save + end + + def has_vote_results? + vote_data_updated_at.present? + end + private 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) 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 diff --git a/app/models/event.rb b/app/models/event.rb index 2199d46..afa7546 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -60,6 +60,14 @@ class Event < ActiveRecord::Base } 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 def track_belongs_to_the_selected_conference diff --git a/app/views/management/conferences/_form.html.slim b/app/views/management/conferences/_form.html.slim index d551b33..c0e7209 100644 --- a/app/views/management/conferences/_form.html.slim +++ b/app/views/management/conferences/_form.html.slim @@ -12,6 +12,7 @@ = f.input :start_date, as: :date = f.input :end_date, as: :date = f.input :description + = f.input :vote_data_endpoint hr .row .col-lg-12 diff --git a/app/views/management/conferences/show.html.slim b/app/views/management/conferences/show.html.slim index b289d59..5c0aca4 100644 --- a/app/views/management/conferences/show.html.slim +++ b/app/views/management/conferences/show.html.slim @@ -85,3 +85,50 @@ .row .col-lg-12 .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') diff --git a/app/views/management/conferences/vote_results.html.slim b/app/views/management/conferences/vote_results.html.slim new file mode 100644 index 0000000..a69824e --- /dev/null +++ b/app/views/management/conferences/vote_results.html.slim @@ -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') diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 15ae509..2c7ce0d 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -15,9 +15,31 @@ bg: contacts: "Данни за контакт" event_propositions: "Предложения за събития" 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: + full_vote_results: "Пълни резултати от гласуването" + vote_data_never_updated: "Резулатите от гласуването не са изтеглени" + voting_results: "Резултати от гласуването" + vote_data_updated_at: "последно обновяване %{updated_at}" + vote_ratio: "%{votes} от общо %{total_votes} гласа" summary: 'Обобщение' cfp_status: 'Състояние на CFP:' + fetch_vote_results: "Изтегли резултатите от гласуването" + percent: "%" + rank: "Позиция" events: edit: edit: "Редакция на %{event_type} „%{event_title}“" diff --git a/config/routes.rb b/config/routes.rb index 9fc0e1f..90f5254 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -33,6 +33,11 @@ Rails.application.routes.draw do root to: 'conferences#index' resources :conferences do + member do + patch :update_vote_data + get :vote_results + end + resources :events resources :volunteers resources :propositions diff --git a/db/migrate/20161010163747_add_vote_data_to_conferences.rb b/db/migrate/20161010163747_add_vote_data_to_conferences.rb new file mode 100644 index 0000000..4903789 --- /dev/null +++ b/db/migrate/20161010163747_add_vote_data_to_conferences.rb @@ -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 diff --git a/db/migrate/20161010170711_add_vote_data_to_events.rb b/db/migrate/20161010170711_add_vote_data_to_events.rb new file mode 100644 index 0000000..1c30cfd --- /dev/null +++ b/db/migrate/20161010170711_add_vote_data_to_events.rb @@ -0,0 +1,6 @@ +class AddVoteDataToEvents < ActiveRecord::Migration + def change + add_column :events, :number_of_votes, :integer + add_column :events, :rank, :integer + end +end