From 40c61ed71739267a227c6de65834562ace25a0c5 Mon Sep 17 00:00:00 2001 From: Chrisbr Date: Fri, 30 May 2014 10:10:47 +0200 Subject: [PATCH] Implements event submissions over time chart --- Gemfile | 1 + Gemfile.lock | 3 + app/assets/javascripts/application.js | 4 +- app/assets/javascripts/dashboard.js | 80 +++++++++++++++++++ app/assets/stylesheets/osem.css | 5 ++ .../admin/conference_controller.rb | 17 +++- app/models/call_for_papers.rb | 16 ++++ app/models/conference.rb | 37 +++++++++ app/models/event.rb | 4 + app/views/admin/conference/index.html.haml | 27 +++++-- .../conferences_controller_spec.rb | 11 +++ spec/models/conference_spec.rb | 19 +++++ 12 files changed, 215 insertions(+), 9 deletions(-) create mode 100644 app/assets/javascripts/dashboard.js diff --git a/Gemfile b/Gemfile index 346aee78..2c549672 100644 --- a/Gemfile +++ b/Gemfile @@ -59,6 +59,7 @@ gem 'axlsx_rails' # Use d3js for building our statistics gem 'd3_rails' +gem 'chart-js-rails' # We use coveralls for measuring test coverage gem 'coveralls', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 2b1c3bd1..334b3703 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -63,6 +63,8 @@ GEM celluloid-io (0.15.0) celluloid (>= 0.15.0) nio4r (>= 0.5.0) + chart-js-rails (0.0.6) + railties (> 3.1) climate_control (0.0.3) activesupport (>= 3.0) cocaine (0.5.4) @@ -313,6 +315,7 @@ DEPENDENCIES cancan capybara capybara-webkit + chart-js-rails cocoon coveralls d3_rails diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 0cf50f85..512a0179 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -18,8 +18,10 @@ //= require jquery.dataTables //= require cocoon //= require bootstrap -//= require osem +//= require Chart //= require d3 +//= require osem +//= require dashboard $(document).ready(function() { $('a[disabled=disabled]').click(function(event){ diff --git a/app/assets/javascripts/dashboard.js b/app/assets/javascripts/dashboard.js new file mode 100644 index 00000000..36526ba4 --- /dev/null +++ b/app/assets/javascripts/dashboard.js @@ -0,0 +1,80 @@ +$(function() { + var t; + function size(animate){ + if (animate == undefined){ + animate = false; + } + clearTimeout(t); + t = setTimeout(function(){ + $("canvas").each(function(i,el){ + $(el).attr({ + "width":$(el).parent().width(), + "height":$(el).parent().outerHeight() + }); + }); + + redraw(animate); + + var m = 0; + $(".widget").height(""); + $(".widget").each(function(i,el){ m = Math.max(m,$(el).height()); }); + $(".widget").height(m); + }, 30); + } + + function redraw(animation){ + var options = {}; + if (!animation){ + options.animation = false; + } else { + options.animation = true; + } + + var chart_data = create_dataset(); + var weeks = $('.submissionsChart').data('weeks'); + var data = { + labels : weeks, + datasets : chart_data + } + + var canvas = document.getElementById("chart"); + var ctx = canvas.getContext("2d"); + new Chart(ctx).Line(data, options); + } + + function create_dataset(){ + var selected = getSelectedConferences(); + var chart_data = $('.submissionsChart').data('chart'); + var conferences = $('.submissionsChart').data('conferences'); + var result = []; + + for(var i in conferences){ + if(selected.indexOf(conferences[i].short_title) >= 0){ + var options = {}; + options.fillColor = "rgba(255,255,255,0.0)"; + options.strokeColor = conferences[i].color; + options.data = chart_data[conferences[i].short_title]; + result.push(options) + } + } + return result + } + + function getSelectedConferences(){ + var selected = [] + $('.conferenceCheckboxes input').each(function(){ + if($(this).is(":checked")) { + selected.push($(this).attr('name')); + } + }) + return selected; + } + + $('.conferenceCheckboxes input').change(function(){ + redraw(false); + }); + + $(window).on('resize', function(){ size(false); }); + + size(true); +}); diff --git a/app/assets/stylesheets/osem.css b/app/assets/stylesheets/osem.css index 41c72d59..08c9f1a7 100644 --- a/app/assets/stylesheets/osem.css +++ b/app/assets/stylesheets/osem.css @@ -50,3 +50,8 @@ body > #messages { display: block; } } + +.conferenceCheckboxes div { + display: inline-block; + padding: 0px 10px 0px 0px; +} diff --git a/app/controllers/admin/conference_controller.rb b/app/controllers/admin/conference_controller.rb index 89347905..86de0667 100644 --- a/app/controllers/admin/conference_controller.rb +++ b/app/controllers/admin/conference_controller.rb @@ -2,8 +2,21 @@ class Admin::ConferenceController < ApplicationController before_filter :verify_organizer def index - @conferences = Conference.all - if @conferences.count == 0 + @conferences = Conference.select('id, short_title, color, start_date') + + # Event submissions over time + @weeks = CallForPapers.max_weeks + @result = {} + + @conferences.each do |c| + submission = c.get_submissions_per_week(@weeks) + @result[c.short_title] = submission + end + + @weeks = @weeks > 0 ? (1..@weeks).to_a : 1 + + # Redirect to new form if there is no conference + if Conference.count == 0 redirect_to new_admin_conference_path return end diff --git a/app/models/call_for_papers.rb b/app/models/call_for_papers.rb index e92a2447..73dcd22b 100644 --- a/app/models/call_for_papers.rb +++ b/app/models/call_for_papers.rb @@ -7,4 +7,20 @@ class CallForPapers < ActiveRecord::Base validates_presence_of :start_date, :end_date, :hard_deadline validates :rating, :numericality => { :greater_than_or_equal_to => 0, :less_than_or_equal_to => 10 } + def self.max_weeks + all = CallForPapers.all + result = [0] + all.each do |cfp| + result.push(cfp.end_week - cfp.start_week) + end + result.max + end + + def start_week + start_date.strftime('%W').to_i + end + + def end_week + end_date.strftime('%W').to_i + end end diff --git a/app/models/conference.rb b/app/models/conference.rb index b5fcf15c..de26c3c4 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -123,6 +123,43 @@ class Conference < ActiveRecord::Base return false end + ## + # Returns an array with the summarized event submissions per week. + # ====Params: + # * +weeks+ -> Integer with number of weeks. This is necessary to compare conferences. + # + # ====Returns + # * +Array+ -> e.g. [0, 3, 3, 5] -> first week 0 events, second week 3 events. + def get_submissions_per_week(weeks) + result = [] + + if call_for_papers && events + submissions = events.select('id, created_at').group_by { |t| t.week } + start_week = call_for_papers.start_week + sum = 0 + + (0..weeks - 1).each do |week| + if submissions["#{week + start_week}"] + sum += submissions["#{week + start_week}"].length + end + result.push(sum) + end + else + result = Array.new(weeks, 0) + end + result + end + + ## + # Checks if the conference is pending. + # + # ====Returns + # * +false+ -> If the conference start date is in the past. + # * +true+ -> If the conference start date is in the future. + def pending? + start_date > Date.today + end + private ## diff --git a/app/models/event.rb b/app/models/event.rb index f40c86b4..00442843 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -174,6 +174,10 @@ class Event < ActiveRecord::Base end end + def week + created_at.strftime('%W') + end + private def abstract_limit diff --git a/app/views/admin/conference/index.html.haml b/app/views/admin/conference/index.html.haml index 727472f8..7f67e87b 100644 --- a/app/views/admin/conference/index.html.haml +++ b/app/views/admin/conference/index.html.haml @@ -1,7 +1,22 @@ .row - %h1 Global Dashboard - %hr -.row - %ul - - @conferences.each do |conference| - %li= link_to conference.title, admin_conference_path(conference.short_title) + .col-md-12 + .well + .row + .text-center + %h4 Event submissions over time + .row + .submissionsChart{"data-chart"=>"#{@result.to_json}", "data-conferences"=>"#{@conferences.to_json}", "data-weeks"=>"#{@weeks.to_json}"} + %canvas#chart + .row + .text-center + weeks + .row + .conferenceCheckboxes + - @conferences.each do |conference| + %div + %span{"style"=>"border-bottom: 3px solid #{conference.color};"} + - if conference.pending? + %input{"type"=>"checkbox", "name"=>"#{conference.short_title}", "checked"=>"checked"} + - else + %input{"type"=>"checkbox", "name"=>"#{conference.short_title}"} + #{conference.short_title} diff --git a/spec/controllers/conferences_controller_spec.rb b/spec/controllers/conferences_controller_spec.rb index 2fc25a23..580e96ff 100644 --- a/spec/controllers/conferences_controller_spec.rb +++ b/spec/controllers/conferences_controller_spec.rb @@ -147,11 +147,22 @@ describe Admin::ConferenceController do describe 'GET #index' do context 'with more than 0 conferences' do it 'populates an array with conferences' do + conference con2 = create(:conference) get :index expect(assigns(:conferences)).to match_array([conference, con2]) end + it 'assigns cfp_max an array with maximum weeks' do + conference + date = Date.new(2014, 05, 28) + conference.call_for_papers = create(:call_for_papers, + start_date: date, + end_date: date + 14) + get :index + expect(assigns(:weeks)).to match_array([1, 2]) + end + it 'renders the index template' do conference get :index diff --git a/spec/models/conference_spec.rb b/spec/models/conference_spec.rb index ecaac1be..c9c5f9c4 100644 --- a/spec/models/conference_spec.rb +++ b/spec/models/conference_spec.rb @@ -6,6 +6,25 @@ describe Conference do let(:subject) { create(:conference) } + describe '#pending?' do + + context 'is pending' do + + it '#pending? is true' do + subject.start_date = Date.today + 10 + expect(subject.pending?).to be true + end + end + + context 'is not pending' do + + it '#pending? is false' do + subject.start_date = Date.today - 10 + expect(subject.pending?).to be false + end + end + end + describe '#registration_open?' do context 'closed registration' do