From 4abd5d218595652fc24531abd226641bffdda610 Mon Sep 17 00:00:00 2001 From: Chrisbr Date: Mon, 16 Jun 2014 13:51:43 +0200 Subject: [PATCH] Implements instance dashboard --- app/assets/javascripts/dashboard.js | 70 +-- app/assets/stylesheets/osem.css | 8 + .../admin/conference_controller.rb | 62 ++- app/models/conference.rb | 192 ++++++++- .../conference/_doughnut_chart.html.haml | 6 + .../conference/_event_distribution.html.haml | 8 - .../admin/conference/_line_chart.html.haml | 29 ++ .../_recent_registrations.html.haml | 29 +- .../conference/_recent_submissions.html.haml | 35 +- .../admin/conference/_recent_users.html.haml | 35 +- .../admin/conference/_registrations.html.haml | 20 - .../admin/conference/_submissions.html.haml | 20 - .../admin/conference/_top_boxes.html.haml | 21 - .../admin/conference/_top_submitter.html.haml | 33 +- .../conference/_user_distribution.html.haml | 8 - app/views/admin/conference/index.html.haml | 102 +++-- app/views/admin/conference/show.html.haml | 102 ++++- spec/models/conference_spec.rb | 407 ++++++++++++++++++ 18 files changed, 970 insertions(+), 217 deletions(-) create mode 100644 app/views/admin/conference/_doughnut_chart.html.haml delete mode 100644 app/views/admin/conference/_event_distribution.html.haml create mode 100644 app/views/admin/conference/_line_chart.html.haml delete mode 100644 app/views/admin/conference/_registrations.html.haml delete mode 100644 app/views/admin/conference/_submissions.html.haml delete mode 100644 app/views/admin/conference/_top_boxes.html.haml delete mode 100644 app/views/admin/conference/_user_distribution.html.haml diff --git a/app/assets/javascripts/dashboard.js b/app/assets/javascripts/dashboard.js index a81eec29..f260ef38 100644 --- a/app/assets/javascripts/dashboard.js +++ b/app/assets/javascripts/dashboard.js @@ -7,10 +7,9 @@ $(function() { clearTimeout(t); t = setTimeout(function(){ $("canvas").each(function(i,el){ - $(el).attr({ - "width":$(el).parent().width(), - "height":$(el).parent().outerHeight() - }); + $(el).attr({ + "width":$(el).parent().width() + }); }); $(".line_chart").each(function(){ @@ -18,13 +17,11 @@ $(function() { }); $(".doughnut_chart").each(function(){ - draw_doughnut_chart(animate, $(this)); + if($(this).is(":visible")){ + draw_doughnut_chart(animate, $(this)); + } }); - var m = 0; - $(".widget").height(""); - $(".widget").each(function(i,el){ m = Math.max(m,$(el).height()); }); - $(".widget").height(m); }, 30); } @@ -57,24 +54,23 @@ $(function() { return options; } - function draw_line_chart(animation, $this){ + function draw_line_chart(animation, $canvas){ var options = get_animation({}, animation); - - var chart_data = create_dataset($this); - var weeks = $this.parent().data('weeks'); + var chart_data = create_dataset($canvas); + var weeks = $canvas.parent().data('weeks'); var data = { labels : weeks, datasets : chart_data } - var ctx = $this.get(0).getContext("2d"); + var ctx = $canvas.get(0).getContext("2d"); new Chart(ctx).Line(data, options); } - function create_dataset($this){ - var selected = getSelectedConferences($this); - var chart_data = $this.parent().data('chart'); - var conferences = $this.parent().data('conferences'); + function create_dataset($canvas){ + var selected = getSelectedConferences($canvas); + var chart_data = $canvas.parent().data('chart'); + var conferences = $canvas.parent().data('conferences'); var result = []; for(var i in conferences){ @@ -89,25 +85,43 @@ $(function() { return result } - function getSelectedConferences($this){ - var name = $this.data('name'); + function getSelectedConferences($canvas){ + var name = $canvas.data('name'); var id = '#' + name + 'Checkboxes' - var selected = [] - $(id + ' input').each(function(){ - if($(this).is(":checked")) { - selected.push($(this).attr('name')); + var selected = []; + var $checkboxes = $(id + ' input'); + // If there are checkboxes -> get selected + // Else -> use the active conference + if($checkboxes.length){ + $(id + ' input').each(function(){ + if($(this).is(":checked")) { + selected.push($(this).attr('name')); + } + }); + }else{ + var active = $canvas.parent().data('active'); + for(i in active){ + selected.push(active[i].short_title) } - }) + } return selected; } $('.conferenceCheckboxes input').change(function(){ - var chart = $(this).parent().data('chart'); - var $canvas = $('#' + chart + 'Chart'); + var chart_name = $(this).parent().data('chart'); + var $canvas = $('#line_chart_' + chart_name); draw_line_chart(false, $canvas); }); - $(window).on('resize', function(){ size(false); }); + $(window).on('resize', function(){ + size(false); + }); + + $('#doughnut_tabs a').click(function (e) { + e.preventDefault(); + $(this).tab('show'); + size(false); + }); size(true); }); diff --git a/app/assets/stylesheets/osem.css b/app/assets/stylesheets/osem.css index 396957dd..41bde9ff 100644 --- a/app/assets/stylesheets/osem.css +++ b/app/assets/stylesheets/osem.css @@ -83,3 +83,11 @@ body { .top-submitter img { margin: 0 auto; } + +#doughnut .tab-content { + padding-bottom: 20px; +} + +#submissions { + padding-bottom: 20px; +} diff --git a/app/controllers/admin/conference_controller.rb b/app/controllers/admin/conference_controller.rb index 50eff4e3..7317252a 100644 --- a/app/controllers/admin/conference_controller.rb +++ b/app/controllers/admin/conference_controller.rb @@ -17,8 +17,10 @@ class Admin::ConferenceController < ApplicationController @total_submissions = Event.count @new_submissions = Event.where('created_at > ?', current_user.last_sign_in_at).count - @conferences = Conference.select('id, short_title, color, start_date, - registration_end_date, registration_start_date') + @active_conferences = Conference.get_active_conferences_for_dashboard # pending or the last two + @deactive_conferences = Conference. + get_conferences_without_active_for_dashboard(@active_conferences) # conferences without active + @conferences = @active_conferences + @deactive_conferences @recent_users = User.limit(5).order(created_at: :desc) @recent_events = Event.limit(5).order(created_at: :desc) @@ -86,9 +88,63 @@ class Admin::ConferenceController < ApplicationController def show @conference = Conference.find_by(short_title: params[:id]) + + # Overview and since last login information + @total_reg = @conference.registrations.count + @new_reg = @conference.registrations.where('created_at > ?', current_user.last_sign_in_at).count + + @total_submissions = @conference.events.count + @new_submissions = @conference.events. + where('created_at > ?', current_user.last_sign_in_at).count + + @program_length = @conference.current_program_hours + @new_program_length = @conference.new_program_hours(current_user.last_sign_in_at) + + # Step by step list @conference_progress = @conference.get_status + + # Line charts + @registrations = { @conference.short_title => @conference.get_registrations_per_week } + @registration_weeks = [0] + @registration_weeks.push(@registrations[@conference.short_title].length) + + @registration_weeks = @registration_weeks.max + @registrations = normalize_array_length(@registrations, @registration_weeks) + @registration_weeks = @registration_weeks > 0 ? (1..@registration_weeks).to_a : 1 + + @submissions = Conference.get_event_state_line_colors + + @submissions_data = {} + @cfp_weeks = [0] + @submissions_data['Submitted'] = @conference.get_submissions_per_week + @cfp_weeks.push(@submissions_data['Submitted'].length) + + @submissions_data['Confirmed'] = @conference.get_submissions_per_week_by_status('confirmed') + @cfp_weeks.push(@submissions_data['Confirmed'].length) + + @submissions_data['Unconfirmed'] = @conference.get_submissions_per_week_by_status('unconfirmed') + @cfp_weeks.push(@submissions_data['Unconfirmed'].length) + + @cfp_weeks = @cfp_weeks.max + @submissions_data = normalize_array_length(@submissions_data, @cfp_weeks) + @cfp_weeks = @cfp_weeks > 0 ? (1..@cfp_weeks).to_a : 1 + + # Doughnut charts + @event_type_distribution = @conference.event_type_distribution + @event_type_distribution_confirmed = @conference.event_type_distribution(:confirmed) + + @difficulty_levels_distribution = @conference.difficulty_levels_distribution + @difficulty_levels_distribution_confirmed = @conference. + difficulty_levels_distribution(:confirmed) + + @tracks_distribution = @conference.tracks_distribution + @tracks_distribution_confirmed = @conference.tracks_distribution(:confirmed) + + # Recent actions information + @recent_events = @conference.events.limit(5).order(created_at: :desc) + @recent_registrations = @conference.registrations.limit(5).order(created_at: :desc) + @top_submitter = @conference.get_top_submitter - @event_distribution = @conference.event_distribution respond_to do |format| format.html diff --git a/app/models/conference.rb b/app/models/conference.rb index 2343b5e1..cdc82963 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -169,6 +169,23 @@ class Conference < ActiveRecord::Base result end + ## + # Returns an array with the summarized event submissions per week. + # + # ====Returns + # * +Array+ -> e.g. [0, 3, 3, 5] -> first week 0 events, second week 3 events. + def get_submissions_per_week_by_status(state) + result = [] + + if call_for_papers && events + submissions = events.where('state = ?', state).group("strftime('%W', created_at)").count + start_week = call_for_papers.start_week + weeks = call_for_papers.weeks + result = calculate_items_per_week(start_week, weeks, submissions) + end + result + end + ## # Returns an array with the summarized registrations per week. # @@ -326,6 +343,106 @@ class Conference < ActiveRecord::Base calculate_user_distribution_hash(active_user, unconfirmed_user, dead_user) end + ## + # Calculates the overall programm hours + # + # ====Returns + # * +hash+ -> Fixnum hours + def current_program_hours + events_grouped = events.group(:event_type_id) + events_counted = events_grouped.count + calculate_program_hours(events_grouped, events_counted) + end + + ## + # Calculates the overall programm hours since date + # + # ====Returns + # * +hash+ -> Fixnum hours + def new_program_hours(date) + events_grouped = events.where('created_at > ?', date).group(:event_type_id) + events_counted = events_grouped.count + calculate_program_hours(events_grouped, events_counted) + end + + ## + # Calculates the difficulty level distribution from all events. + # + # ====Returns + # * +hash+ -> difficulty level => {color, value} + def difficulty_levels_distribution(state = nil) + calculate_event_distribution(:difficulty_level_id, :difficulty_level, state) + end + + ## + # Calculates the event_type distribution from all events. + # + # ====Returns + # * +hash+ -> event_type => {color, value} + def event_type_distribution(state = nil) + calculate_event_distribution(:event_type_id, :event_type, state) + end + + ## + # Calculates the track distribution from all events. + # + # ====Returns + # * +hash+ -> track => {color, value} + def tracks_distribution(state = nil) + if state + tracks_grouped = events.where('state = ?', state).group(:track_id) + else + tracks_grouped = events.group(:track_id) + end + tracks_counted = tracks_grouped.count + + calculate_track_distribution_hash(tracks_grouped, tracks_counted) + end + + ## + # Return all pending conferences. If there are no pending conferences, the last two + # past conferences are returned + # + # ====Returns + # * +ActiveRecord+ + def self.get_active_conferences_for_dashboard + result = Conference.where('start_date > ?', Time.now). + select('id, short_title, color, start_date, + registration_end_date, registration_start_date') + + if result.length == 0 + result = Conference. + select('id, short_title, color, start_date, registration_end_date, + registration_start_date').limit(2). + order(start_date: :desc) + end + result + end + + ## + # Return all conferences minus the active conferences + # + # ====Returns + # * +ActiveRecord+ + def self.get_conferences_without_active_for_dashboard(active_conferences) + result = Conference.select('id, short_title, color, start_date, + registration_end_date, registration_start_date').order(start_date: :desc) + result - active_conferences + end + + ## + # A list with the three event states submitted, confirmed, unconfirmed with corresponding colors + # + # ====Returns + # * +List+ + def self.get_event_state_line_colors + result = [] + result.push(short_title: 'Submitted', color: 'blue') + result.push(short_title: 'Confirmed', color: 'green') + result.push(short_title: 'Unconfirmed', color: 'orange') + result + end + private ## @@ -407,6 +524,73 @@ class Conference < ActiveRecord::Base !!registration_start_date && !!registration_end_date end + # Calculates the distribution from events. + # + # ====Returns + # * +hash+ -> object_type => {color, value} + def calculate_event_distribution(group_by_id, association_symbol, state = nil) + if state + grouped = events.where('state = ?', 'confirmed').group(group_by_id) + else + grouped = events.group(group_by_id) + end + counted = grouped.count + + calculate_distribution_hash(grouped, counted, association_symbol) + end + + ## + # Helper method to calculate the correct data format for the doughnut charts. + # + # ====Returns + # * +hash+ -> object_type => {color, value} + def calculate_distribution_hash(grouped, counter, symbol) + result = {} + + grouped.each do |event| + object = event.send(symbol) + if object + result[object.title] = { + 'value' => counter[object.id], + 'color' => object.color + } + end + end + result + end + + ## + # Helper method to calculate the correct data format for the doughnut charts + # for track distribution of events. + # + # ====Returns + # * +hash+ -> object_type => {color, value} + def calculate_track_distribution_hash(tracks_grouped, tracks_counter) + result = {} + tracks_grouped.each do |event| + if event.track + result[event.track.name] = { + 'value' => tracks_counter[event.track_id], + 'color' => event.track.color + } + end + end + result + end + + ## + # Helper method to calculate the program hours. + # + # ====Returns + # * +Fixnums+ summed programm hours + def calculate_program_hours(events_grouped, events_counted) + result = 0 + events_grouped.each do |event| + result += events_counted[event.event_type_id] * event.event_type.length + end + result + end + ## # Helper method for calculating hash with corresponding colors of user distribution states. # @@ -496,14 +680,18 @@ class Conference < ActiveRecord::Base # def add_color if !color - self.color = %w( + self.color = get_color + end + end + + def get_color + %w( #000000 #0000FF #00FF00 #FF0000 #FFFF00 #9900CC #CC0066 #00FFFF #FF00FF #C0C0C0 #00008B #FFD700 #FFA500 #FF1493 #FF00FF #F0FFFF #EE82EE #D2691E #C0C0C0 #A52A2A #9ACD32 #9400D3 #8B008B #8B0000 #87CEEB #808080 #800080 #008B8B #006400 ).sample - end end # Calculates items per week from a hash. diff --git a/app/views/admin/conference/_doughnut_chart.html.haml b/app/views/admin/conference/_doughnut_chart.html.haml new file mode 100644 index 00000000..9e2990f9 --- /dev/null +++ b/app/views/admin/conference/_doughnut_chart.html.haml @@ -0,0 +1,6 @@ +.text-center + %h4 #{title} + %canvas.doughnut_chart{"data-chart"=>data.to_json} +- if data + - data.each do |key, value| + %span{"style"=>"border-bottom: 3px solid #{value['color']}"} #{key}: #{value['value']} \ No newline at end of file diff --git a/app/views/admin/conference/_event_distribution.html.haml b/app/views/admin/conference/_event_distribution.html.haml deleted file mode 100644 index d88f5934..00000000 --- a/app/views/admin/conference/_event_distribution.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -.well - .text-center - %h4 Event distribution - %canvas.doughnut_chart{"data-chart"=>@event_distribution.to_json} - .row - - if @event_distribution - - @event_distribution.each do |key, value| - %span{"style"=>"border-bottom: 3px solid #{value['color']}"} #{key}: #{value['value']} \ No newline at end of file diff --git a/app/views/admin/conference/_line_chart.html.haml b/app/views/admin/conference/_line_chart.html.haml new file mode 100644 index 00000000..c57a6e09 --- /dev/null +++ b/app/views/admin/conference/_line_chart.html.haml @@ -0,0 +1,29 @@ +.row + .col-md-12 + .text-center + %h4 + = title +.row + .col-md-12 + .chart_data{ "data-chart"=>"#{y.to_json}", "data-conferences"=>"#{conferences.to_json}", + "data-deactive"=>"#{deactive_conferences.to_json}", "data-weeks"=>"#{x.to_json}", + "data-active"=>"#{active_conferences.to_json}"} + %canvas.line_chart{ :id => "line_chart_#{name}", "data-name"=>"#{name}" } +.row + .col-md-12 + .text-center + = unit +.row + .col-md-12 + .conferenceCheckboxes{ :id => "#{name}Checkboxes", "data-name"=>"#{name}" } + - if active_conferences && deactive_conferences && conferences && conferences.length > 1 + - active_conferences.each do |conference| + %div + %span{ "style"=>"border-bottom: 3px solid #{conference[:color]};", "data-chart"=> "#{name}" } + %input{ "type"=>"checkbox", "name"=>"#{conference[:short_title]}", "checked"=>"checked"} + #{conference[:short_title]} + - deactive_conferences.each do |conference| + %div + %span{"style"=>"border-bottom: 3px solid #{conference[:color]};", "data-chart"=> "#{name}" } + %input{"type"=>"checkbox", "name"=>"#{conference[:short_title]}" } + #{conference[:short_title]} diff --git a/app/views/admin/conference/_recent_registrations.html.haml b/app/views/admin/conference/_recent_registrations.html.haml index eefab05a..8211c33f 100644 --- a/app/views/admin/conference/_recent_registrations.html.haml +++ b/app/views/admin/conference/_recent_registrations.html.haml @@ -1,18 +1,19 @@ - if recent_registrations && !recent_registrations.empty? - %table.table - %thead - %tr - %th # - %th Public Name - %th Conference - %th Date - - recent_registrations.each_with_index do |registration, index| - %tbody - %tr - %td #{index + 1} - %td #{registration.public_name} - %td #{registration.conference.title} - %td #{registration.created_at.strftime('%m/%d/%Y')} + .table-responsive + %table.table + %thead + %tr + %th # + %th Public Name + %th Conference + %th Date + - recent_registrations.each_with_index do |registration, index| + %tbody + %tr + %td #{index + 1} + %td #{registration.public_name} + %td #{registration.conference.title} + %td #{registration.created_at.strftime('%m/%d/%Y')} - else %h5.text-warning.text-center No registrations! \ No newline at end of file diff --git a/app/views/admin/conference/_recent_submissions.html.haml b/app/views/admin/conference/_recent_submissions.html.haml index e13f559a..ac522c5b 100644 --- a/app/views/admin/conference/_recent_submissions.html.haml +++ b/app/views/admin/conference/_recent_submissions.html.haml @@ -1,21 +1,22 @@ - if recent_events && !recent_events.empty? - %table.table - %thead - %tr - %th # - %th Submitter - %th Title - %th Conference - %th Status - - recent_events.each_with_index do |event, index| - %tbody - %tr - %td #{index + 1} - %td #{event.submitter} - %td #{event.title} - %td #{event.conference.title} - %td - .span{'class'=>label_for(event.state)} #{event.state.humanize} + .table-responsive + %table.table + %thead + %tr + %th # + %th Submitter + %th Title + %th Conference + %th Status + - recent_events.each_with_index do |event, index| + %tbody + %tr + %td #{index + 1} + %td #{event.submitter} + %td #{event.title} + %td #{event.conference.title} + %td + .span{'class'=>label_for(event.state)} #{event.state.humanize} - else %h5.text-warning.text-center No submissions! \ No newline at end of file diff --git a/app/views/admin/conference/_recent_users.html.haml b/app/views/admin/conference/_recent_users.html.haml index 016c08ae..d750f309 100644 --- a/app/views/admin/conference/_recent_users.html.haml +++ b/app/views/admin/conference/_recent_users.html.haml @@ -1,22 +1,23 @@ - if recent_users && !recent_users.empty? - %table.table - %thead - %tr - %th # - %th E-Mail - %th Date registered - %th Status - - recent_users.each_with_index do |user, index| - %tbody + .table-responsive + %table.table + %thead %tr - %td #{index} - %td #{user.email} - %td #{user.created_at.strftime('%m/%d/%Y')} - %td - - if user.confirmed? - %span.label.label-success Confirmed - -else - %span.label.label-danger Unconfirmed + %th # + %th E-Mail + %th Date registered + %th Status + - recent_users.each_with_index do |user, index| + %tbody + %tr + %td #{index} + %td #{user.email} + %td #{user.created_at.strftime('%m/%d/%Y')} + %td + - if user.confirmed? + %span.label.label-success Confirmed + -else + %span.label.label-danger Unconfirmed - else %h5.text-warning.text-center No sign ups! \ No newline at end of file diff --git a/app/views/admin/conference/_registrations.html.haml b/app/views/admin/conference/_registrations.html.haml deleted file mode 100644 index 6f112864..00000000 --- a/app/views/admin/conference/_registrations.html.haml +++ /dev/null @@ -1,20 +0,0 @@ -.well#registrations - .row - .text-center - %h4 Conference registrations over time - .row - .registrationsChart{"data-chart"=>"#{registrations.to_json}", "data-conferences"=>"#{conferences.to_json}", "data-weeks"=>"#{registration_weeks.to_json}"} - %canvas.line_chart#registrationsChart{"data-name"=>"registrations"} - .row - .text-center - weeks - .row - .conferenceCheckboxes#registrationsCheckboxes - - conferences.each do |conference| - %div - %span{"style"=>"border-bottom: 3px solid #{conference.color};", "data-chart"=> "registrations"} - - 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/app/views/admin/conference/_submissions.html.haml b/app/views/admin/conference/_submissions.html.haml deleted file mode 100644 index 8d7673fa..00000000 --- a/app/views/admin/conference/_submissions.html.haml +++ /dev/null @@ -1,20 +0,0 @@ -.well#submissions - .row - .text-center - %h4 Event submissions over time - .row - .submissionsChart{"data-chart"=>"#{submissions.to_json}", "data-conferences"=>"#{conferences.to_json}", "data-weeks"=>"#{cfp_weeks.to_json}"} - %canvas.line_chart#submissionsChart{"data-name"=>"submissions"} - .row - .text-center - weeks - .row - .conferenceCheckboxes#submissionsCheckboxes - - conferences.each do |conference| - %div - %span{"style"=>"border-bottom: 3px solid #{conference.color};", "data-chart"=> "submissions"} - - 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/app/views/admin/conference/_top_boxes.html.haml b/app/views/admin/conference/_top_boxes.html.haml deleted file mode 100644 index a5a26000..00000000 --- a/app/views/admin/conference/_top_boxes.html.haml +++ /dev/null @@ -1,21 +0,0 @@ -.col-sm-4 - .dashbox.well.well-sm.text-center - .icon - %i.glyphicon.glyphicon-user - .text - %label.text-muted total user: #{@total_user} - %label.text-muted new user: #{@new_user} -.col-sm-4 - .dashbox.well.well-sm.text-center - .icon - %i.glyphicon.glyphicon-star - .text - %label.text-muted total registrations: #{@total_reg} - %label.text-muted new registrations: #{@new_reg} -.col-sm-4 - .dashbox.well.well-sm.text-center - .icon - %i.glyphicon.glyphicon-comment - .text - %label.text-muted total submissions: #{@total_submissions} - %label.text-muted new submissions: #{@new_submissions} \ No newline at end of file diff --git a/app/views/admin/conference/_top_submitter.html.haml b/app/views/admin/conference/_top_submitter.html.haml index 0a79ed54..6a072f72 100644 --- a/app/views/admin/conference/_top_submitter.html.haml +++ b/app/views/admin/conference/_top_submitter.html.haml @@ -1,17 +1,18 @@ .row - %h3 - Top Submitter - - if @top_submitter && !@top_submitter.empty? - - @top_submitter.each do |key, value| - .row.top-submitter - .col-md-2 - = image_tag(key.gravatar_url(size: '25'), title: "Yo #{key.public_name}!", :alt => '', 'class'=>'img-circle img-responsive text-center') - .col-md-10 - %h4 - #{key} - %div - %small - #{pluralize(value, 'submission')} - - else - %h4.text-warning - No submissions! + .col-md-12 + %h3 + Top Submitter + - if @top_submitter && !@top_submitter.empty? + - @top_submitter.each do |key, value| + .row.top-submitter + .col-md-2 + = image_tag(key.gravatar_url(size: '25'), title: "Yo #{key.public_name}!", :alt => '', 'class'=>'img-circle img-responsive text-center') + .col-md-10 + %h4 + #{key} + %div + %small + #{pluralize(value, 'submission')} + - else + %h4.text-warning + No submissions! diff --git a/app/views/admin/conference/_user_distribution.html.haml b/app/views/admin/conference/_user_distribution.html.haml deleted file mode 100644 index 272e9d04..00000000 --- a/app/views/admin/conference/_user_distribution.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -.well - .text-center - %h4 User distribution - %canvas.doughnut_chart{"data-chart"=>user_distribution.to_json} - .row - - if user_distribution - - user_distribution.each do |key, value| - %span{"style"=>"border-bottom: 3px solid #{value['color']}"} #{key}: #{value['value']} \ No newline at end of file diff --git a/app/views/admin/conference/index.html.haml b/app/views/admin/conference/index.html.haml index 7374fa4f..a8d44eae 100644 --- a/app/views/admin/conference/index.html.haml +++ b/app/views/admin/conference/index.html.haml @@ -1,46 +1,70 @@ .row - = render partial: 'top_boxes', locals: { total_user: @total_user, new_user: @new_user, active_user: @active_user, - total_reg: @total_reg, new_reg: @new_reg, submissions_count: @submissions_count } -.row + .col-sm-4 + .dashbox.text-center + .icon + %i.glyphicon.glyphicon-user + .text + %label.text-muted total user: #{@total_user} + %label.text-muted new user: #{@new_user} + .col-sm-4 + .dashbox.text-center + .icon + %i.glyphicon.glyphicon-star + .text + %label.text-muted total registrations: #{@total_reg} + %label.text-muted new registrations: #{@new_reg} + .col-sm-4 + .dashbox.text-center + .icon + %i.glyphicon.glyphicon-comment + .text + %label.text-muted total submissions: #{@total_submissions} + %label.text-muted new submissions: #{@new_submissions} +.row#registrations .col-md-8 - = render partial: 'submissions', locals: { conferences: @conferences, submissions: @submissions, - cfp_weeks: @cfp_weeks } + = render partial: 'line_chart', locals: { title: 'Registrations over time', + name: 'registrations', + conferences: @conferences, + active_conferences: @active_conferences, + deactive_conferences: @deactive_conferences, + y: @registrations, + x: @registration_weeks, + unit: 'weeks' } .col-md-4 - = render partial: 'event_distribution', locals: { event_distribution: @event_distribution } + = render partial: 'doughnut_chart', locals: { title: 'Events',data: @event_distribution } +.row#submissions + .col-md-8 + = render partial: 'line_chart', locals: { title: 'Submissions over time', + name: 'submissions', + conferences: @conferences, + active_conferences: @active_conferences, + deactive_conferences: @deactive_conferences, + y: @submissions, + x: @cfp_weeks, + unit: 'weeks' } + .col-md-4 + = render partial: 'doughnut_chart', locals: { title: 'User',data: @user_distribution } .row .col-md-8 - = render partial: 'registrations', locals: { conferences: @conferences, registrations: @registrations, - registration_weeks: @registration_weeks } - .col-md-4 - = render partial: 'user_distribution', locals: { user_distribution: @user_distribution } - .col-md-8 - .row - %ul.nav.nav-tabs#recentTable - %li.active - %a{:href=>"#recent_user", "data-toggle"=>"tab"} - %span.glyphicon.glyphicon-user - Recent Users - %li - %a{:href=>"#recent_reg", "data-toggle"=>"tab"} - %span.glyphicon.glyphicon-star - Recent Registrations - %li - %a{:href=>"#recent_submissions", "data-toggle"=>"tab"} - %span.glyphicon.glyphicon-comment - Recent Submissions - .tab-content - .tab-pane.active#recent_user - = render partial: 'recent_users', locals: {recent_users: @recent_users} - .tab-pane#recent_reg - = render partial: 'recent_registrations', locals: {recent_registrations: @recent_registrations} - .tab-pane#recent_submissions - = render partial: 'recent_submissions', locals: {recent_events: @recent_events} + %ul.nav.nav-tabs#recentTable + %li.active + %a{:href=>"#recent_user", "data-toggle"=>"tab"} + %span.glyphicon.glyphicon-user + Recent Users + %li + %a{:href=>"#recent_reg", "data-toggle"=>"tab"} + %span.glyphicon.glyphicon-star + Recent Registrations + %li + %a{:href=>"#recent_submissions", "data-toggle"=>"tab"} + %span.glyphicon.glyphicon-comment + Recent Submissions + .tab-content + .tab-pane.active#recent_user + = render partial: 'recent_users', locals: {recent_users: @recent_users} + .tab-pane#recent_reg + = render partial: 'recent_registrations', locals: {recent_registrations: @recent_registrations} + .tab-pane#recent_submissions + = render partial: 'recent_submissions', locals: {recent_events: @recent_events} .col-md-4 = render partial: 'top_submitter', locals: {top_submitter: @top_submitter} - - -:javascript - $('#recentTable a').click(function (e) { - e.preventDefault(); - $(this).tab('show'); - }) diff --git a/app/views/admin/conference/show.html.haml b/app/views/admin/conference/show.html.haml index 7aa12860..3d0f68b0 100644 --- a/app/views/admin/conference/show.html.haml +++ b/app/views/admin/conference/show.html.haml @@ -3,9 +3,103 @@ Dashboard for #{@conference.title} %hr .row - .col-md-4 - = render partial: 'todo_list', locals: { conference_progress: @conference_progress } + .col-sm-4 + .dashbox.text-center + .icon + %i.glyphicon.glyphicon-star + .text + %label.text-muted total registrations: #{@total_reg} + %label.text-muted new registrations: #{@new_reg} + .col-sm-4 + .dashbox.text-center + .icon + %i.glyphicon.glyphicon-comment + .text + %label.text-muted total submissions: #{@total_submissions} + %label.text-muted new submissions: #{@new_submissions} + .col-sm-4 + .dashbox.text-center + .icon + %i.glyphicon.glyphicon-user + .text + %label.text-muted programm hours: #{@program_length} h + %label.text-muted new programm hours: #{@new_program_length} h +.row + .col-md-12 + .row + .col-md-8 + .row#registrations + .col-md-12 + = render partial: 'line_chart', locals: { title: 'Registrations over time', + name: 'registrations', + conferences: [@conference], + active_conferences: [@conference], + deactive_conferences: [], + y: @registrations, + x: @registration_weeks, + unit: 'weeks' } + .row#submissions + .col-md-12 + = render partial: 'line_chart', locals: { title: 'Submissions over time', + name: 'submissions', + conferences: @submissions, + active_conferences: @submissions, + deactive_conferences: [], + y: @submissions_data, + x: @cfp_weeks, + unit: 'weeks' } + .col-md-4 + = render partial: 'todo_list', locals: { conference_progress: @conference_progress } +.row + .col-md-12#doughnut + %ul.nav.nav-tabs#doughnut_tabs + %li.active + %a{:href=>"#distribution_all", "data-toggle"=>"tab"} + %span.glyphicon.glyphicon-star + All + %li + %a{:href=>"#distribution_confirmed", "data-toggle"=>"tab"} + %span.glyphicon.glyphicon-comment + Confirmed + .tab-content + .tab-pane.active#distribution_all + .row + .col-md-4 + = render partial: 'doughnut_chart', locals: {title: 'Event types', data: @event_type_distribution} + .col-md-4 + = render partial: 'doughnut_chart', locals: {title: 'Difficulty levels', data: @difficulty_levels_distribution} + .col-md-4 + = render partial: 'doughnut_chart', locals: {title: 'Tracks', data: @tracks_distribution} + .tab-pane#distribution_confirmed + .row + .col-md-4 + = render partial: 'doughnut_chart', locals: {title: 'Event types', data: @event_type_distribution_confirmed} + .col-md-4 + = render partial: 'doughnut_chart', locals: {title: 'Difficulty levels', data: @difficulty_levels_distribution_confirmed} + .col-md-4 + = render partial: 'doughnut_chart', locals: {title: 'Tracks', data: @tracks_distribution_confirmed} + +.row + .col-md-8 + %ul.nav.nav-tabs#recentTable + %li.active + %a{:href=>"#recent_reg", "data-toggle"=>"tab"} + %span.glyphicon.glyphicon-star + Recent Registrations + %li + %a{:href=>"#recent_submissions", "data-toggle"=>"tab"} + %span.glyphicon.glyphicon-comment + Recent Submissions + .tab-content + .tab-pane.active#recent_reg + = render partial: 'recent_registrations', locals: {recent_registrations: @recent_registrations} + .tab-pane#recent_submissions + = render partial: 'recent_submissions', locals: {recent_events: @recent_events} .col-md-4 = render partial: 'top_submitter', locals: {top_submitter: @top_submitter} - .col-md-4 - = render partial: 'event_distribution', locals: { event_distribution: @event_distribution } + +:javascript + $('#recentTable a').click(function (e) { + e.preventDefault(); + $(this).tab('show'); + }) diff --git a/spec/models/conference_spec.rb b/spec/models/conference_spec.rb index ae404f3c..31948ee9 100644 --- a/spec/models/conference_spec.rb +++ b/spec/models/conference_spec.rb @@ -6,6 +6,413 @@ describe Conference do let(:subject) { create(:conference) } + describe 'program hours' do + before(:each) do + @long = create(:event_type, length: 100) + @short = create(:event_type, length: 10) + end + + describe '#actual_program_hours' do + it 'calculates correct values with events' do + create(:event, conference: subject, event_type: @long) + create(:event, conference: subject, event_type: @long) + create(:event, conference: subject, event_type: @short) + create(:event, conference: subject, event_type: @short) + result = 220 + expect(subject.current_program_hours).to eq(result) + end + + it 'calculates correct values without events' do + result = 0 + expect(subject.current_program_hours).to eq(result) + end + end + + describe '#new_program_hours' do + it 'calculates correct values with events' do + + create(:event, conference: subject, event_type: @long, created_at: Time.now - 3.days) + create(:event, conference: subject, event_type: @long) + create(:event, conference: subject, event_type: @short, created_at: Time.now - 3.days) + create(:event, conference: subject, event_type: @short) + result = 110 + expect(subject.new_program_hours(Time.now - 5.minutes)).to eq(result) + end + + it 'calculates correct values without events' do + result = 0 + expect(subject.new_program_hours(Time.now - 5.minutes)).to eq(result) + end + end + end + + describe '#difficulty_levels' do + before (:each) do + subject.email_settings = create(:email_settings) + @easy = create(:difficulty_level, title: 'Easy', color: '#000000') + @hard = create(:difficulty_level, title: 'Hard', color: '#ffffff') + end + + describe '#difficulty_levels_distribution' do + + it 'calculates correct for different difficulty levels' do + create(:event, conference: subject, difficulty_level: @easy) + create(:event, conference: subject, difficulty_level: @easy) + create(:event, conference: subject, difficulty_level: @hard) + result = {} + result['Hard'] = { + 'value' => 1, + 'color' => '#ffffff', + } + result['Easy'] = { + 'value' => 2, + 'color' => '#000000', + } + expect(subject.difficulty_levels_distribution).to eq(result) + end + + it 'calculates correct for one difficulty levels' do + create(:event, conference: subject, difficulty_level: @easy) + result = {} + result['Easy'] = { + 'value' => 1, + 'color' => '#000000', + } + expect(subject.difficulty_levels_distribution).to eq(result) + end + + it 'calculates correct for no difficulty levels' do + result = {} + expect(subject.difficulty_levels_distribution).to eq(result) + end + end + + describe '#difficulty_levels_distribution_confirmed' do + it 'calculates correct for different difficulty levels without confirmed' do + create(:event, conference: subject, difficulty_level: @easy) + create(:event, conference: subject, difficulty_level: @easy) + create(:event, conference: subject, difficulty_level: @hard) + result = {} + + expect(subject.difficulty_levels_distribution(:confirmed)).to eq(result) + end + + it 'calculates correct for different difficulty levels with confirmed' do + confirmed_easy = create(:event, conference: subject, difficulty_level: @easy) + create(:event, conference: subject, difficulty_level: @easy) + confirmed_hard = create(:event, conference: subject, difficulty_level: @hard) + + options = {} + options[:send_mail] = 'false' + confirmed_easy.accept!(options) + confirmed_hard.accept!(options) + confirmed_easy.confirm! + confirmed_hard.confirm! + + result = {} + result['Hard'] = { + 'value' => 1, + 'color' => '#ffffff' + } + result['Easy'] = { + 'value' => 1, + 'color' => '#000000' + } + expect(subject.difficulty_levels_distribution(:confirmed)).to eq(result) + end + + it 'calculates correct for one difficulty levels' do + confirmed = create(:event, conference: subject, difficulty_level: @easy) + options = {} + options[:send_mail] = 'false' + confirmed.accept!(options) + confirmed.confirm! + + result = {} + result['Easy'] = { + 'value' => 1, + 'color' => '#000000', + } + expect(subject.difficulty_levels_distribution(:confirmed)).to eq(result) + end + + it 'calculates correct for no difficulty levels' do + result = {} + expect(subject.difficulty_levels_distribution(:confirmed)).to eq(result) + end + end + end + + describe 'event type distribution' do + before (:each) do + subject.email_settings = create(:email_settings) + @workshop = create(:event_type, title: 'Workshop', color: '#000000') + @lecture = create(:event_type, title: 'Lecture', color: '#ffffff') + end + + describe '#event_type_distribution' do + it 'calculates correct for different event types' do + create(:event, conference: subject, event_type: @workshop) + create(:event, conference: subject, event_type: @workshop) + create(:event, conference: subject, event_type: @lecture) + result = {} + result['Workshop'] = { + 'value' => 2, + 'color' => '#000000', + } + result['Lecture'] = { + 'value' => 1, + 'color' => '#ffffff', + } + expect(subject.event_type_distribution).to eq(result) + end + + it 'calculates correct for one event types' do + create(:event, conference: subject, event_type: @workshop) + result = {} + result['Workshop'] = { + 'value' => 1, + 'color' => '#000000', + } + expect(subject.event_type_distribution).to eq(result) + end + + it 'calculates correct for no event types' do + result = {} + expect(subject.event_type_distribution).to eq(result) + end + end + + describe '#event_type_distribution_confirmed' do + it 'calculates correct for different event types without confirmed' do + create(:event, conference: subject, event_type: @workshop) + create(:event, conference: subject, event_type: @workshop) + create(:event, conference: subject, event_type: @lecture) + result = {} + + expect(subject.event_type_distribution(:confirmed)).to eq(result) + end + + it 'calculates correct for different event types with confirmed' do + confirmed_ws = create(:event, conference: subject, event_type: @workshop) + create(:event, conference: subject, event_type: @workshop) + confirmed_lt = create(:event, conference: subject, event_type: @lecture) + + options = {} + options[:send_mail] = 'false' + confirmed_ws.accept!(options) + confirmed_lt.accept!(options) + confirmed_ws.confirm! + confirmed_lt.confirm! + + result = {} + result['Lecture'] = { + 'value' => 1, + 'color' => '#ffffff' + } + result['Workshop'] = { + 'value' => 1, + 'color' => '#000000' + } + expect(subject.event_type_distribution(:confirmed)).to eq(result) + end + + it 'calculates correct for one event types' do + confirmed = create(:event, conference: subject, event_type: @workshop) + options = {} + options[:send_mail] = 'false' + confirmed.accept!(options) + confirmed.confirm! + + result = {} + result['Workshop'] = { + 'value' => 1, + 'color' => '#000000', + } + expect(subject.event_type_distribution(:confirmed)).to eq(result) + end + + it 'calculates correct for no event types' do + result = {} + expect(subject.event_type_distribution(:confirmed)).to eq(result) + end + end + end + + describe 'tracks_distribution' do + before (:each) do + subject.email_settings = create(:email_settings) + @track_one = create(:track, name: 'Track One', color: '#000000') + @track_two = create(:track, name: 'Track Two', color: '#ffffff') + end + + describe '#tracks_distribution' do + it 'calculates correct for different tracks' do + create(:event, conference: subject, track: @track_one) + create(:event, conference: subject, track: @track_one) + create(:event, conference: subject, track: @track_two) + result = {} + result['Track One'] = { + 'value' => 2, + 'color' => '#000000', + } + result['Track Two'] = { + 'value' => 1, + 'color' => '#ffffff', + } + expect(subject.tracks_distribution).to eq(result) + end + + it 'calculates correct for one track' do + create(:event, conference: subject, track: @track_one) + result = {} + result['Track One'] = { + 'value' => 1, + 'color' => '#000000', + } + expect(subject.tracks_distribution).to eq(result) + end + + it 'calculates correct for no track' do + result = {} + expect(subject.tracks_distribution).to eq(result) + end + end + + describe '#tracks_distribution_confirmed' do + it 'calculates correct for different tracks without confirmed' do + create(:event, conference: subject, track: @track_one) + create(:event, conference: subject, track: @track_one) + create(:event, conference: subject, track: @track_two) + result = {} + + expect(subject.tracks_distribution(:confirmed)).to eq(result) + end + + it 'calculates correct for different tracks with confirmed' do + confirmed_one = create(:event, conference: subject, track: @track_one) + create(:event, conference: subject, track: @track_one) + confirmed_two = create(:event, conference: subject, track: @track_two) + + options = {} + options[:send_mail] = 'false' + confirmed_one.accept!(options) + confirmed_two.accept!(options) + confirmed_one.confirm! + confirmed_two.confirm! + + result = {} + result['Track One'] = { + 'value' => 1, + 'color' => '#000000' + } + result['Track Two'] = { + 'value' => 1, + 'color' => '#ffffff' + } + expect(subject.tracks_distribution(:confirmed)).to eq(result) + end + + it 'calculates correct for one track' do + confirmed = create(:event, conference: subject, track: @track_one) + options = {} + options[:send_mail] = 'false' + confirmed.accept!(options) + confirmed.confirm! + + result = {} + result['Track One'] = { + 'value' => 1, + 'color' => '#000000', + } + expect(subject.tracks_distribution(:confirmed)).to eq(result) + end + + it 'calculates correct for no track' do + result = {} + expect(subject.tracks_distribution(:confirmed)).to eq(result) + end + end + end + + describe '#get_active_conferences' do + + it 'returns pending conferences' do + a = create(:conference, + short_title: 'a', start_date: Time.now + 14.days, + end_date: Time.now + 21.days) + b = create(:conference, + short_title: 'b', start_date: Time.now + 21.days, + end_date: Time.now + 28.days) + c = create(:conference, + short_title: 'c', start_date: Time.now + 21.days, + end_date: Time.now + 28.days) + create(:conference, + short_title: 'd', start_date: Time.now - 1.year, + end_date: Time.now - 360.days) + result = [a, b, c] + + expect(Conference.get_active_conferences_for_dashboard).to match_array(result) + end + + it 'returns the last two past conferences if there are no pending conferences' do + subject.start_date = Time.now - 10.days + subject.end_date = Time.now - 5.days + c = create(:conference, + short_title: 'c', start_date: Time.now - 1.year, + end_date: Time.now - 360.days) + result = [subject, c] + + expect(Conference.get_active_conferences_for_dashboard).to match_array(result) + end + + it 'returns empty array if there are no conferences' do + expect(Conference.get_active_conferences_for_dashboard).to match_array([]) + end + end + + describe '#get_deactive_conferences' do + it 'returns all conferences without the active conferences' do + a = create(:conference, start_date: Time.now - 3.year, end_date: Time.now - 1080.days) + b = create(:conference, start_date: Time.now - 2.year, end_date: Time.now - 720.days) + c = create(:conference, start_date: Time.now - 1.year, end_date: Time.now - 360.days) + result = [a, b, c] + + expect(Conference.get_conferences_without_active_for_dashboard([subject])). + to match_array(result) + end + + it 'returns all conferences if there are no active conferences' do + subject.start_date = Time.now - 10.days + subject.end_date = Time.now - 5.days + expect(Conference.get_conferences_without_active_for_dashboard([])).to match_array([subject]) + end + + it 'returns empty array if there are no conferences' do + expect(Conference.get_conferences_without_active_for_dashboard([])).to match_array([]) + end + + it 'returns no conferences if all conferences are pending' do + expect(Conference.get_conferences_without_active_for_dashboard([subject])).to match_array([]) + end + + it 'return no conferences if there are only two conferences and no pending' do + a = create(:conference, start_date: Time.now - 2.year, end_date: Time.now - 720.days) + b = create(:conference, start_date: Time.now - 1.year, end_date: Time.now - 360.days) + expect(Conference.get_conferences_without_active_for_dashboard([a, b])).to match_array([]) + end + end + + describe '#get_submission_line_colors' do + it ' returns correct values' do + result = [] + result.push(short_title: 'Submitted', color: 'blue') + result.push(short_title: 'Confirmed', color: 'green') + result.push(short_title: 'Unconfirmed', color: 'orange') + expect(Conference.get_event_state_line_colors).to eq(result) + end + end + describe '#event_distribution' do before(:each) do