Use chartkick globally

* Clean up legacy charting imports
* Update existing donut and line charts
* Create helpers for parsing out existing chart data
* Move chart partials to a more common path
This commit is contained in:
James Mason 2018-12-19 12:59:52 -08:00
parent 3bbac3667f
commit 516e53d9df
29 changed files with 331 additions and 577 deletions

View file

@ -128,7 +128,6 @@ gem 'ajax-datatables-rails'
gem 'jquery-datatables-rails'
# for charts
gem 'chart-js-rails'
gem 'chartkick'
# for displaying maps

View file

@ -111,8 +111,6 @@ GEM
fastimage
case_transform (0.2)
activesupport
chart-js-rails (0.1.6)
railties (> 3.1)
chartkick (3.0.1)
childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11)
@ -633,7 +631,6 @@ DEPENDENCIES
capybara
carrierwave
carrierwave-bombshelter
chart-js-rails
chartkick
chromedriver-helper
climate_control

View file

@ -20,8 +20,9 @@
//= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
//= require cocoon
//= require bootstrap
//= require Chart.bundle
//= require chartkick
//= require osem
//= require osem-dashboard
//= require jquery-smooth-scroll
//= require trianglify
//= require tinycolor

View file

@ -1,146 +0,0 @@
$(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()
});
});
$(".line_chart").each(function(){
draw_line_chart(animate, $(this));
});
$(".doughnut_chart").each(function(){
if($(this).is(":visible")){
draw_doughnut_chart(animate, $(this));
}
});
}, 30);
}
function draw_doughnut_chart(animation, $this){
var options = get_animation({}, animation);
var tmp = $this.data('chart');
if(jQuery.isEmptyObject(tmp)){
// Append error message if there is no data
$this.parent().append("<h4 class=\"text-warning\">No data!</h4>");
// Remove canvas
$this.remove();
}else{
var data = [];
for (var key in tmp) {
data.push(tmp[key]);
}
var ctx = $this.get(0).getContext("2d");
new Chart(ctx).Doughnut(data, options);
}
}
function get_animation(options, animation){
if (!animation){
options.animation = false;
} else {
options.animation = true;
}
return options;
}
function draw_line_chart(animation, $canvas){
var chart_data = create_dataset($canvas);
var weeks = $canvas.parent().data('weeks');
var data = {
labels : weeks,
datasets : chart_data
}
var options = get_animation(wholeNumberAxisFix(data), animation);
var ctx = $canvas.get(0).getContext("2d");
new Chart(ctx).Line(data, options);
}
function wholeNumberAxisFix(data){
var maxValue = false;
for(datasetIndex = 0; datasetIndex < data.datasets.length; ++datasetIndex){
var setMax = Math.max.apply(null, data.datasets[datasetIndex].data);
if (maxValue === false || setMax > maxValue) maxValue = setMax;
}
var steps = maxValue;
var stepWidth = 1;
if (maxValue > 10) {
stepWidth = Math.floor(maxValue / 10);
steps = Math.ceil(maxValue / stepWidth);
}
return { scaleOverride: true, scaleSteps: steps, scaleStepWidth: stepWidth, scaleStartValue: 0 };
}
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){
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];
if(options.data == null || options.data.length == 0){
options.data = [0];
}
result.push(options)
}
}
return result
}
function getSelectedConferences($canvas){
var name = $canvas.data('name');
var id = '#' + name + 'Checkboxes'
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_name = $(this).parent().data('chart');
var $canvas = $('#line_chart_' + chart_name);
draw_line_chart(false, $canvas);
});
$(window).on('resize', function(){
size(false);
});
$('#doughnut_tabs a').click(function (e) {
e.preventDefault();
$(this).tab('show');
size(false);
});
size(true);
});

View file

@ -37,43 +37,33 @@ module Admin
@top_submitter = Conference.get_top_submitter
@submissions = {}
@cfp_weeks = [0]
@registrations = {}
@registration_weeks = [0]
@tickets = {}
@ticket_weeks = [0]
@registrations = []
@submissions = []
@tickets = []
@conferences.each do |c|
# Event submissions over time chart
@submissions[c.short_title] = c.get_submissions_per_week
@cfp_weeks.push(@submissions[c.short_title].length)
# Conference registrations over time chart
@registrations[c.short_title] = c.get_registrations_per_week
@registration_weeks.push(@registrations[c.short_title].length)
@registrations << {
name: c.short_title,
data: c.get_registrations_per_week
}
# Event submissions over time chart
@submissions << {
name: c.short_title,
data: c.get_submissions_per_week
}
# Tickets sold over time chart
@tickets[c.short_title] = c.get_tickets_sold_per_week
@ticket_weeks.push(@tickets[c.short_title].length)
@tickets << {
name: c.short_title,
data: c.get_tickets_sold_per_week
}
end
@cfp_weeks = @cfp_weeks.max
@submissions = normalize_array_length(@submissions, @cfp_weeks)
@cfp_weeks = @cfp_weeks > 0 ? (1..@cfp_weeks).to_a : 1
@registration_weeks = @registration_weeks.max
@registrations = normalize_array_length(@registrations, @registration_weeks)
@registration_weeks = @registration_weeks > 0 ? (1..@registration_weeks).to_a : 1
@ticket_weeks = @ticket_weeks.max
@tickets = normalize_array_length(@tickets, @ticket_weeks)
@ticket_weeks = @ticket_weeks > 0 ? (1..@ticket_weeks).to_a : 1
@event_distribution = Conference.event_distribution
@user_distribution = Conference.user_distribution
@event_distribution_colors = Event::COLORS.values
@user_distribution = User.distribution
@user_distribution_colors = User::DISTRIBUTION_COLORS.values
end
def new
@ -138,35 +128,9 @@ module Admin
@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 = @conference.get_submissions_data
@cfp_weeks = 0
if @submissions_data['Weeks']
@cfp_weeks = @submissions_data['Weeks']
@submissions_data = @submissions_data.except('Weeks')
end
@tickets_data = @conference.get_tickets_data
@ticket_weeks = 0
if @tickets_data['Weeks']
@ticket_weeks = @tickets_data['Weeks']
@tickets_data = @tickets_data.except('Weeks')
end
# Set line color using a hash function
@tickets = []
@tickets_data.each_key do |title|
@tickets.append(short_title: title, color: "\##{Digest::MD5.hexdigest(title)[0..5]}")
end
@registrations = @conference.get_registrations_per_week
@submissions = @conference.get_submissions_data
@tickets = @conference.get_tickets_data
# Doughnut charts
@event_type_distribution = @conference.event_type_distribution

View file

@ -16,6 +16,7 @@ module Admin
@event_types = @program.event_types
@tracks_distribution_confirmed = @conference.tracks_distribution(:confirmed)
@event_distribution = @conference.event_distribution
@event_distribution_colors = Event::COLORS.values
@scheduled_event_distribution = @conference.scheduled_event_distribution
@file_name = "events_for_#{@conference.short_title}"
@event_export_option = params[:event_export_option]

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
module ChartHelper
def chart_values(distribution_hash)
Hash[
distribution_hash.collect do |key, data|
[key, data['value']]
end
]
end
def chart_colors(distribution_hash)
distribution_hash.collect do |_key, data|
data['color']
end
end
end

View file

@ -176,24 +176,22 @@ class Conference < ApplicationRecord
# ====Returns
# * +Array+ -> e.g. 'Submitted' => [0, 3, 3, 5] -> first week 0 events, second week 3 events.
def get_submissions_data
result = {}
if program&.cfp && program&.events
result = get_events_per_week_by_state
return [] unless program&.cfp && program&.events
start_week = program.cfp.start_week
end_week = end_date.strftime('%W').to_i
weeks = weeks(start_week, end_week)
result.each do |state, values|
get_events_per_week_by_state.collect do |state, values|
if state == 'Submitted'
result['Submitted'] = pad_array_left_kumulative(start_week, values)
{
name: 'Submitted',
data: add_week_indices(pad_array_left_kumulative(start_week, values))
}
else
result[state] = pad_array_left_not_kumulative(start_week, values)
{
name: state,
data: add_week_indices(pad_array_left_not_kumulative(start_week, values))
}
end
end
result['Weeks'] = weeks > 0 ? (1..weeks).to_a : 0
end
result
end
##
@ -202,9 +200,7 @@ class Conference < ApplicationRecord
# ====Returns
# * +Array+ -> e.g. [0, 3, 3, 5] -> first week 0, second week 3 registrations
def get_registrations_per_week
result = []
if registrations &&
return [] unless registrations &&
registration_period &&
registration_period.start_date &&
registration_period.end_date
@ -212,9 +208,7 @@ class Conference < ApplicationRecord
reg = registrations.group(:week).order(:week).count
start_week = get_registration_start_week
weeks = registration_weeks
result = calculate_items_per_week(start_week, weeks, reg)
end
result
calculate_items_per_week(start_week, weeks, reg)
end
##
@ -223,15 +217,12 @@ class Conference < ApplicationRecord
# ====Returns
# * +Array+ -> e.g. [0, 3, 3, 5] -> first week 0, second week 3 tickets sold
def get_tickets_sold_per_week
result = []
return [] unless tickets && ticket_purchases && registration_period
if tickets && ticket_purchases && registration_period
tickets_sold = ticket_purchases.paid.group(:week).sum(:quantity)
start_week = get_registration_start_week
weeks = registration_weeks
result = calculate_items_per_week(start_week, weeks, tickets_sold)
end
result
calculate_items_per_week(start_week, weeks, tickets_sold)
end
##
@ -241,8 +232,8 @@ class Conference < ApplicationRecord
# ====Returns
# * +Array+ -> e.g. 'Free Access' => [0, 3, 3, 5] -> first week 0 tickets sold, second week 3 tickets sold.
def get_tickets_data
result = {}
if tickets && ticket_purchases && registration_period
return [] unless tickets && ticket_purchases && registration_period
tickets_per_ticket_id_and_week = ticket_purchases.paid.group(:ticket_id, :week).sum(:quantity)
start_week = get_registration_start_week
@ -261,13 +252,12 @@ class Conference < ApplicationRecord
tickets_by_id_per_week[ticket_week[0]][ticket_week[1]] = value
end
tickets_by_id_per_week.each do |ticket, values|
result[Ticket.find(ticket).title] = pad_array_left_not_kumulative(start_week, values)
tickets_by_id_per_week.collect do |ticket, values|
{
name: Ticket.find(ticket).title,
data: add_week_indices(pad_array_left_not_kumulative(start_week, values))
}
end
result['Weeks'] = weeks > 0 ? (1..weeks).to_a : 0
end
result
end
##
@ -400,7 +390,9 @@ class Conference < ApplicationRecord
# ====Returns
# * +hash+ -> hash
def event_distribution
Conference.calculate_event_distribution_hash(program.events.select(:state).group(:state).count)
Conference.calculate_event_distribution_hash(
program.events.select(:state).group(:state).count
)
end
##
@ -465,22 +457,6 @@ class Conference < ApplicationRecord
result
end
##
# Returns a hash with user distribution => {value: count of user state, color: color}
# active: signed in during the last 3 months
# unconfirmed: registered but not confirmed
# dead: not signed in during the last year
#
# ====Returns
# * +hash+ -> hash
def self.user_distribution
active_user = User.where('last_sign_in_at > ?', Date.today - 3.months).count
unconfirmed_user = User.where('confirmed_at IS NULL').count
dead_user = User.where('last_sign_in_at < ?', Date.today - 1.year).count
calculate_user_distribution_hash(active_user, unconfirmed_user, dead_user)
end
##
# Returns a hash with per ticket sales => { "Title" => { value: number of tickets sold,
# color: generated from the title using a hash function }, ...}
@ -1110,20 +1086,19 @@ class Conference < ApplicationRecord
end
##
# Helper method. Calculates hash with corresponding colors of event state distribution.
# Helper method. Calculates hash of all event states in a consistent order.
#
# ====Returns
# * +hash+ -> hash
def self.calculate_event_distribution_hash(states)
result = {}
states.each do |key, value|
result[key.capitalize] =
{
'value' => value,
'color' => Event.get_state_color(key)
}
def self.calculate_event_distribution_hash(counts)
return {} if counts.values.sum == 0
Hash[
Event.state_machine.states.collect do |state|
state_name = state.name.to_s
[state_name.capitalize, counts[state_name] || 0]
end
result
]
end
##
@ -1211,6 +1186,12 @@ class Conference < ApplicationRecord
result += Array.new(weeks - result.length, sum)
end
result
add_week_indices(result)
end
def add_week_indices(values)
Hash[
values.collect.with_index { |value, index| ["Wk #{index + 1}", value] }
]
end
end

View file

@ -83,6 +83,15 @@ class Event < ApplicationRecord
end
end
COLORS = {
new: '#0000FF', # blue
withdrawn: '#FF8000', # orange
unconfirmed: '#FFFF00', # yellow
confirmed: '#00FF00', # green
canceled: '#848484', # grey
rejected: '#FF0000' # red
}.freeze
##
# Checkes if the event has a start_time and a room for the selected schedule if there is any
# ====Returns
@ -176,16 +185,7 @@ class Event < ApplicationRecord
end
def self.get_state_color(state)
color = {
new: '#0000FF', # blue
withdrawn: '#FF8000', # orange
confirmed: '#00FF00', # green
unconfirmed: '#FFFF00', # yellow
rejected: '#FF0000', # red
canceled: '#848484' # grey
}[state.to_sym]
color || '#00FFFF' # azure
COLORS[state.to_sym] || '#00FFFF' # azure
end
def update_state(transition, mail = false, subject = false, send_mail = false, send_mail_param)

View file

@ -32,6 +32,13 @@ class User < ApplicationRecord
# add scope
scope :comment_notifiable, ->(conference) {joins(:roles).where('roles.name IN (?)', [:organizer, :cfp]).where('roles.resource_type = ? AND roles.resource_id = ?', 'Conference', conference.id)}
# scopes for user distributions
scope :active, lambda {
where('last_sign_in_at > ?', Date.today - 3.months).where(is_disabled: false)
}
scope :unconfirmed, -> { where('confirmed_at IS NULL') }
scope :dead, -> { where('last_sign_in_at < ?', Date.today - 1.year) }
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
@ -79,7 +86,6 @@ class User < ApplicationRecord
accepts_nested_attributes_for :roles
scope :admin, -> { where(is_admin: true) }
scope :active, -> { where(is_disabled: false) }
validates :email, presence: true
@ -91,6 +97,12 @@ class User < ApplicationRecord
validate :biography_limit
DISTRIBUTION_COLORS = {
'Active' => 'green',
'Unconfirmed' => 'red',
'Dead' => 'black'
}.freeze
##
# Checkes if the user attended the event
# This is used for events that require registration
@ -152,6 +164,22 @@ class User < ApplicationRecord
user
end
##
# Returns a hash with user distribution => {value: count of user state, color: color}
# active: signed in during the last 3 months
# unconfirmed: registered but not confirmed
# dead: not signed in during the last year
#
# ====Returns
# * +hash+ -> hash
def self.distribution
{
'Active' => User.active.count,
'Unconfirmed' => User.unconfirmed.count,
'Dead' => User.dead.count
}
end
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
login = conditions.delete(:login)

View file

@ -1,23 +0,0 @@
.text-center
%h4 #{title}
%canvas.doughnut_chart{ id: "dough_#{title.parameterize.underscore}", 'data-chart' => data.to_json }
- if data
- data.each do |key, value|
%span{ 'style' => "border-bottom: 3px solid #{value['color']}" } #{key}: #{value['value']}
:javascript
$(document).ready( function(){
var d = $("#dough_#{title.parameterize.underscore}");
var dt = d.get(0).getContext('2d');
$(window).resize( respondCanvas );
function respondCanvas(){
dt.canvas.width = 150;
dt.canvas.height = 150;
}
respondCanvas();
});

View file

@ -1,47 +0,0 @@
.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]}
:javascript
$(document).ready( function(){
var c = $("#line_chart_#{name}");
var ct = c.get(0).getContext('2d');
var container = $(c).parent();
$(window).resize( respondCanvas );
function respondCanvas(){
c.attr('width', $(container).width() );
c.attr('height', $(container).height() );
}
respondCanvas();
});

View file

@ -1,52 +1,32 @@
= javascript_include_tag 'Chart.min'
.row
.col-sm-3.col-xs-3
= render "shared/big_statistic",
= render "big_statistic",
icon: "user", subtitle: "User", value: @total_user, delta: @new_user
.col-sm-3.col-xs-3
= render "shared/big_statistic",
= render "big_statistic",
icon: "check-square", subtitle: "Registration", value: @total_reg, delta: @new_reg
.col-sm-3.col-xs-3
= render "shared/big_statistic",
= render "big_statistic",
icon: "file-text", subtitle: "Submission", value: @total_submissions, delta: @new_submissions
.col-sm-3.col-xs-3
= render "shared/big_statistic",
= render "big_statistic",
icon: "archive", subtitle: "Withdrawn", value: @total_withdrawn, delta: @new_withdrawn, reverse: true
.row#registrations
.col-md-8
= 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' }
= render 'line_chart', title: 'Registrations per week', data: @registrations
.col-md-4
= render partial: 'doughnut_chart', locals: { title: 'Events', data: @event_distribution }
= render 'donut_chart', title: 'Events',
data: @event_distribution, colors: @event_distribution_colors
.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' }
= render 'line_chart', title: 'Submissions per week', data: @submissions
.col-md-4
= render partial: 'doughnut_chart', locals: { title: 'User', data: @user_distribution }
= render 'donut_chart', title: 'Users',
data: @user_distribution, colors: @user_distribution_colors
.row#tickets
.col-md-8
= render partial: 'line_chart', locals: { title: 'Tickets sold over time',
name: 'tickets',
conferences: @conferences,
active_conferences: @active_conferences,
deactive_conferences: @deactive_conferences,
y: @tickets,
x: @ticket_weeks,
unit: 'weeks' }
= render 'line_chart', title: 'Tickets sold per week', data: @tickets
%br
.row
.col-md-8

View file

@ -1,21 +1,19 @@
= javascript_include_tag 'Chart.min'
%h1
%span.fa.fa-tachometer
Dashboard for #{@conference.title}
%hr
.row
.col-sm-3.col-xs-3
= render "shared/big_statistic",
= render "big_statistic",
icon: "user", subtitle: "Registration", value: @total_reg, delta: @new_reg
.col-sm-3.col-xs-3
= render "shared/big_statistic",
= render "big_statistic",
icon: "check-square", subtitle: "Submission", value: @total_submissions, delta: @new_submissions
.col-sm-3.col-xs-3
= render "shared/big_statistic",
= render "big_statistic",
icon: "file-text", subtitle: "Hour", value: @program_length, delta: @new_program_length
.col-sm-3.col-xs-3
= render "shared/big_statistic",
= render "big_statistic",
icon: "archive", subtitle: "Withdrawn", value: @total_withdrawn, delta: @new_withdrawn
.row
@ -24,37 +22,19 @@
.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' }
= render 'line_chart',
title: 'Registrations per week', data: @registrations
.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' }
= render 'line_chart',
title: 'Submissions per week', data: @submissions
.col-md-4
= render partial: 'todo_list', locals: { conference_progress: @conference_progress,
conference: @conference }
= render 'todo_list',
conference_progress: @conference_progress, conference: @conference
.row#tickets
.col-md-8
= render partial: 'line_chart', locals: { title: 'Tickets sold over time',
name: 'tickets',
conferences: @tickets,
active_conferences: @tickets,
deactive_conferences: [],
y: @tickets_data,
x: @ticket_weeks,
unit: 'weeks' }
= render 'line_chart',
title: 'Tickets sold per week', data: @tickets
%br
.row
.col-md-12#doughnut
@ -75,27 +55,36 @@
.tab-pane.active#distribution_all
.row
.col-md-4
= render partial: 'doughnut_chart', locals: {title: 'Event types', data: @event_type_distribution}
= render 'donut_chart', title: 'Event types',
combined_data: @event_type_distribution
.col-md-4
= render partial: 'doughnut_chart', locals: {title: 'Difficulty levels', data: @difficulty_levels_distribution}
= render 'donut_chart', title: 'Difficulty levels',
combined_data: @difficulty_levels_distribution
.col-md-4
= render partial: 'doughnut_chart', locals: {title: 'Tracks', data: @tracks_distribution}
= render 'donut_chart', title: 'Tracks',
combined_data: @tracks_distribution
.tab-pane#distribution_confirmed
.row
.col-md-4
= render partial: 'doughnut_chart', locals: {title: 'Event types', data: @event_type_distribution_confirmed}
= render 'donut_chart', title: 'Event types',
combined_data: @event_type_distribution_confirmed
.col-md-4
= render partial: 'doughnut_chart', locals: {title: 'Difficulty levels', data: @difficulty_levels_distribution_confirmed}
= render 'donut_chart', title: 'Difficulty levels',
combined_data: @difficulty_levels_distribution_confirmed
.col-md-4
= render partial: 'doughnut_chart', locals: {title: 'Tracks', data: @tracks_distribution_confirmed}
= render 'donut_chart', title: 'Tracks',
combined_data: @tracks_distribution_confirmed
.tab-pane#distribution_withdrawn
.row
.col-md-4
= render partial: 'doughnut_chart', locals: {title: 'Event types', data: @event_type_distribution_withdrawn}
= render 'donut_chart', title: 'Event types',
combined_data: @event_type_distribution_withdrawn
.col-md-4
= render partial: 'doughnut_chart', locals: {title: 'Difficulty levels', data: @difficulty_levels_distribution_withdrawn}
= render 'donut_chart', title: 'Difficulty levels',
combined_data: @difficulty_levels_distribution_withdrawn
.col-md-4
= render partial: 'doughnut_chart', locals: {title: 'Tracks', data: @tracks_distribution_withdrawn}
= render 'donut_chart', title: 'Tracks',
combined_data: @tracks_distribution_withdrawn
.row
.col-md-8

View file

@ -1,5 +1,3 @@
= javascript_include_tag 'Chart.min'
.row
.col-md-12
.page-header
@ -47,17 +45,14 @@
= f.submit 'Add', class: 'btn btn-primary'
.row
.col-md-4
= render 'admin/conferences/doughnut_chart',
title: 'Events state',
data: @event_distribution
= render 'donut_chart', title: 'Events state',
data: @event_distribution, colors: @event_distribution_colors
.col-md-4
= render 'admin/conferences/doughnut_chart',
title: 'Confirmed events scheduled',
data: @scheduled_event_distribution
= render 'donut_chart', title: 'Confirmed events scheduled',
combined_data: @scheduled_event_distribution
.col-md-4
= render 'admin/conferences/doughnut_chart',
title: 'Tracks of confirmed events',
data: @tracks_distribution_confirmed
= render 'donut_chart', title: 'Tracks of confirmed events',
combined_data: @tracks_distribution_confirmed
.row
.col-md-12
.margin-event-table

View file

@ -1,5 +1,3 @@
= javascript_include_tag 'Chart.min'
.container
.row
.col-md-12.page-header
@ -9,11 +7,11 @@
Tickets sold for the conference
.row
.col-md-4
= render partial: 'admin/conferences/doughnut_chart',
locals: { title: 'Tickets sold', data: @tickets_sold_distribution }
= render 'donut_chart', title: 'Tickets sold',
combined_data: @tickets_sold_distribution
.col-md-4
= render partial: 'admin/conferences/doughnut_chart',
locals: {title: 'Tickets turnover', data: @tickets_turnover_distribution}
= render 'donut_chart', title: 'Tickets turnover',
combined_data: @tickets_turnover_distribution
%br
- if @physical_tickets.any?
.row

View file

@ -1,5 +1,3 @@
= javascript_include_tag 'Chart.min'
.row
.col-md-12
.page-header
@ -14,10 +12,12 @@
%p.text-muted
All the people who registered to your event
.col-md-4
= render partial: 'admin/conferences/doughnut_chart', locals: {title: 'Affiliation', data: @affiliation_distribution}
= render 'donut_chart', title: 'Affiliation',
combined_data: @affiliation_distribution
- unless @conference.pending?
.col-md-4
= render partial: 'admin/conferences/doughnut_chart', locals: {title: 'Attended registrations', data: @registration_distribution}
= render 'donut_chart', title: 'Attended registrations',
combined_data: @registration_distribution
.row
.col-md-12
%div.margin-event-table

View file

@ -1,6 +1,3 @@
= javascript_include_tag "//www.google.com/jsapi", "chartkick"
= javascript_include_tag 'chartkick'
= bar_chart @survey.survey_questions.map{ |q| [q.title, q.survey_replies.count] }
- @survey.survey_questions.each.with_index(1) do |survey_question, question_index|

View file

@ -0,0 +1,16 @@
:ruby
combined_data ||= {}
data ||= chart_values(combined_data)
colors ||= chart_colors(combined_data)
options = {
donut: true,
legend: 'bottom',
download: true,
messages: { empty: 'No data' }
}
options[:colors] = colors if colors.present?
.text-center
%h4= title
= pie_chart data, options

View file

@ -0,0 +1,15 @@
:ruby
options = {
legend: 'bottom',
download: true,
messages: { empty: 'No data' }
}
.row
.col-md-12
.text-center
%h4
= title
.row
.col-md-12
= line_chart data, options

View file

@ -50,4 +50,5 @@
Performance data is available on
#{link_to "Skylight", ENV["SKYLIGHT_PUBLIC_DASHBOARD_URL"]}.
= yield :script_body
= yield :charts_js
= piwik_tracking_tag

View file

@ -9,4 +9,3 @@ Rails.application.config.assets.version = '1.0'
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
# Rails.application.config.assets.precompile += %w( search.js )
Rails.application.config.assets.precompile += %w( Chart.min.js )

View file

@ -0,0 +1,4 @@
Chartkick.options = {
height: '150px',
content_for: :charts_js
}

View file

@ -177,17 +177,6 @@ describe Admin::ConferencesController do
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, 26)
create(:cfp,
program: conference.program,
start_date: date,
end_date: date + 14)
get :index
expect(assigns(:cfp_weeks)).to match_array([1, 2, 3])
end
it 'renders the index template' do
conference
get :index

View file

@ -31,6 +31,7 @@ FactoryBot.define do
Quisque cursus facilisis consequat. Etiam volutpat ligula turpis, at
gravida.
EOS
last_sign_in_at { Date.today }
is_disabled { false }
after(:create) do |user|

View file

@ -5,8 +5,12 @@ require 'spec_helper'
describe Commercial do
describe '.find_since_last_login' do
let!(:user) do
create(:user, last_sign_in_at: nil)
end
it 'returns none if last_sign_in_at is nil' do
expect(Comment.find_since_last_login(create(:user))).to eq(Comment.none)
expect(Comment.find_since_last_login(user)).to eq(Comment.none)
end
end
end

View file

@ -166,7 +166,7 @@ describe Conference do
describe '#get_submissions_data' do
it 'returns emtpy hash if there is no cfp or events' do
expect(subject.get_submissions_data).to eq({})
expect(subject.get_submissions_data).to eq []
end
it 'calculates the correct result with data from database' do
@ -191,12 +191,7 @@ describe Conference do
create(:event, program: subject.program, created_at: Date.today - 2.weeks)
result = {
'Submitted' => [1, 1, 1],
'Confirmed' => [1, 3, 0],
'Unconfirmed' => [2, 4, 0],
'Weeks' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
result = [{ name: 'Submitted', data: { 'Wk 1' => 1, 'Wk 2' => 1, 'Wk 3' => 1 } }, { name: 'Confirmed', data: { 'Wk 1' => 1, 'Wk 2' => 3, 'Wk 3' => 0 } }, { name: 'Unconfirmed', data: { 'Wk 1' => 2, 'Wk 2' => 4, 'Wk 3' => 0 } }]
expect(subject.get_submissions_data).to eq(result)
end
@ -207,12 +202,11 @@ describe Conference do
create(:cfp, start_date: Date.today, program: subject.program)
create(:event, program: subject.program)
result = {
'Submitted' => [1],
'Confirmed' => [0],
'Unconfirmed' => [0],
'Weeks' => [1, 2, 3, 4, 5, 6, 7, 8]
}
result = [
{ name: 'Submitted', data: { 'Wk 1'=>1 } },
{ name: 'Confirmed', data: { 'Wk 1'=>0 } },
{ name: 'Unconfirmed', data: { 'Wk 1'=>0 } }
]
expect(subject.get_submissions_data).to eq(result)
end
@ -232,12 +226,20 @@ describe Conference do
confirmed.accept!(options)
confirmed.confirm!
result = {
'Submitted' => [0, 0, 3],
'Confirmed' => [0, 0, 1],
'Unconfirmed' => [0, 0, 1],
'Weeks' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
result = [
{
name: 'Submitted',
data: { 'Wk 1' => 0, 'Wk 2' => 0, 'Wk 3' => 3 }
},
{
name: 'Confirmed',
data: { 'Wk 1' => 0, 'Wk 2' => 0, 'Wk 3' => 1 }
},
{
name: 'Unconfirmed',
data: { 'Wk 1' => 0, 'Wk 2' => 0, 'Wk 3' => 1 }
}
]
expect(subject.get_submissions_data).to eq(result)
end
@ -263,12 +265,20 @@ describe Conference do
create(:event, program: subject.program, created_at: Date.today - 3.weeks)
result = {
'Submitted' => [1, 1, 1, 1],
'Confirmed' => [1, 0, 3, 0],
'Unconfirmed' => [2, 0, 4, 0],
'Weeks' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
result = [
{
name: 'Submitted',
data: { 'Wk 1' => 1, 'Wk 2' => 1, 'Wk 3' => 1, 'Wk 4' => 1 }
},
{
name: 'Confirmed',
data: { 'Wk 1' => 1, 'Wk 2' => 0, 'Wk 3' => 3, 'Wk 4' => 0 }
},
{
name: 'Unconfirmed',
data: { 'Wk 1' => 2, 'Wk 2' => 0, 'Wk 3' => 4, 'Wk 4' => 0 }
}
]
expect(subject.get_submissions_data).to eq(result)
end
end
@ -768,13 +778,7 @@ describe Conference do
canceled.accept!(@options)
canceled.cancel!
@result = {}
@result['New'] = { 'value' => 1, 'color' => '#0000FF' }
@result['Withdrawn'] = { 'value' => 1, 'color' => '#FF8000' }
@result['Unconfirmed'] = { 'value' => 1, 'color' => '#FFFF00' }
@result['Rejected'] = { 'value' => 1, 'color' => '#FF0000' }
@result['Confirmed'] = { 'value' => 1, 'color' => '#00FF00' }
@result['Canceled'] = { 'value' => 1, 'color' => '#848484' }
@result = { 'New' => 1, 'Withdrawn' => 1, 'Unconfirmed' => 1, 'Confirmed' => 1, 'Canceled' => 1, 'Rejected' => 1 }
end
it '#event_distribution does calculate correct values with events' do
@ -789,7 +793,7 @@ describe Conference do
it 'event_distribution does calculate correct values with just a new event' do
conference = create(:conference)
create(:event, program: conference.program)
result = { 'New' => { 'value' => 1, 'color' => '#0000FF' } }
result = { 'New' => 1, 'Withdrawn' => 0, 'Unconfirmed' => 0, 'Confirmed' => 0, 'Canceled' => 0, 'Rejected' => 0 }
expect(conference.event_distribution).to eq(result)
end
@ -797,7 +801,7 @@ describe Conference do
conference = create(:conference)
event = create(:event, program: conference.program)
event.withdraw!
result = { 'Withdrawn' => { 'value' => 1, 'color' => '#FF8000' } }
result = { 'New' => 0, 'Withdrawn' => 1, 'Unconfirmed' => 0, 'Confirmed' => 0, 'Canceled' => 0, 'Rejected' => 0 }
expect(conference.event_distribution).to eq(result)
end
@ -805,7 +809,7 @@ describe Conference do
conference = create(:conference)
event = create(:event, program: conference.program)
event.accept!(@options)
result = { 'Unconfirmed' => { 'value' => 1, 'color' => '#FFFF00' } }
result = { 'New' => 0, 'Withdrawn' => 0, 'Unconfirmed' => 1, 'Confirmed' => 0, 'Canceled' => 0, 'Rejected' => 0 }
expect(conference.event_distribution).to eq(result)
end
@ -813,7 +817,7 @@ describe Conference do
conference = create(:conference)
event = create(:event, program: conference.program)
event.reject!(@options)
result = { 'Rejected' => { 'value' => 1, 'color' => '#FF0000' } }
result = { 'New' => 0, 'Withdrawn' => 0, 'Unconfirmed' => 0, 'Confirmed' => 0, 'Canceled' => 0, 'Rejected' => 1 }
expect(conference.event_distribution).to eq(result)
end
@ -823,7 +827,7 @@ describe Conference do
event = create(:event, program: conference.program)
event.accept!(@options)
event.confirm!
result = { 'Confirmed' => { 'value' => 1, 'color' => '#00FF00' } }
result = { 'New' => 0, 'Withdrawn' => 0, 'Unconfirmed' => 0, 'Confirmed' => 1, 'Canceled' => 0, 'Rejected' => 0 }
expect(conference.event_distribution).to eq(result)
end
@ -832,7 +836,7 @@ describe Conference do
event = create(:event, program: conference.program)
event.accept!(@options)
event.cancel!
result = { 'Canceled' => { 'value' => 1, 'color' => '#848484' } }
result = { 'New' => 0, 'Withdrawn' => 0, 'Unconfirmed' => 0, 'Confirmed' => 0, 'Canceled' => 1, 'Rejected' => 0 }
expect(conference.event_distribution).to eq(result)
end
@ -848,15 +852,15 @@ describe Conference do
it 'self#event_distribution does calculate correct values with just a new event' do
@conference.program.events.clear
create(:event, program: @conference.program)
result = { 'New' => { 'value' => 1, 'color' => '#0000FF' } }
result = { 'New' => 1, 'Withdrawn' => 0, 'Unconfirmed' => 0, 'Confirmed' => 0, 'Canceled' => 0, 'Rejected' => 0 }
expect(Conference.event_distribution).to eq(result)
end
it 'self#event_distribution does calculate correct values
with just a new events from different conferences' do
create(:event, program: @conference.program)
@result['New'] = { 'value' => 2, 'color' => '#0000FF' }
expect(Conference.event_distribution).to eq(@result)
result = { 'New' => 2, 'Withdrawn' => 1, 'Unconfirmed' => 1, 'Confirmed' => 1, 'Canceled' => 1, 'Rejected' => 1 }
expect(Conference.event_distribution).to eq(result)
end
end
@ -865,44 +869,15 @@ describe Conference do
let!(:organizer_role) { Role.find_by(name: 'organizer', resource: conference) }
let!(:organizer) { create(:user, role_ids: [organizer_role.id]) }
it 'self#event_distribution calculates correct values with user' do
create(:user, last_sign_in_at: Date.today - 3.months + 1.day) # active
create(:user, confirmed_at: nil) # unconfirmed
create(:user, last_sign_in_at: Date.today - 1.year - 1.day) # dead
it 'self#user_distribution calculates correct values with user' do
result = {}
result['Active'] = { 'color' => 'green', 'value' => 1 }
result['Unconfirmed'] = { 'color' => 'red', 'value' => 1 }
result['Dead'] = { 'color' => 'black', 'value' => 1 }
expect(Conference.user_distribution).to eq(result)
end
it 'self#event_distribution calculates correct with only active user' do
create(:user, last_sign_in_at: Date.today - 3.months + 1.day) # active
result = {}
result['Active'] = { 'color' => 'green', 'value' => 1 }
expect(Conference.user_distribution).to eq(result)
end
it 'self#event_distribution calculates correct values with only unconfirmed user' do
create(:user, confirmed_at: nil) # unconfirmed
result = {}
result['Unconfirmed'] = { 'color' => 'red', 'value' => 1 }
expect(Conference.user_distribution).to eq(result)
end
it 'self#event_distribution calculates correct values with only dead user' do
create(:user, last_sign_in_at: Time.now - 1.year - 1.day) # dead
result = {}
result['Dead'] = { 'color' => 'black', 'value' => 1 }
expect(Conference.user_distribution).to eq(result)
end
it 'self#event_distribution calculates correct values without user' do
expect(Conference.user_distribution).to eq({})
expect(
Conference.calculate_user_distribution_hash(1, 1, 1)
).to eq(result)
end
end
@ -1152,7 +1127,7 @@ describe Conference do
cfp.end_date = Date.new(2014, 05, 26) + 21
cfp.save!
subject.program.events += [create(:event, created_at: Date.new(2014, 05, 26) - 7)]
expect(subject.get_submissions_per_week).to eq([1, 1, 1, 1, 1])
expect(subject.get_submissions_per_week.values).to eq([1, 1, 1, 1, 1])
end
it 'does calculate correct if cfp end date is altered' do
@ -1161,7 +1136,7 @@ describe Conference do
cfp.end_date = Date.new(2014, 05, 26) + 21
cfp.save!
subject.program.events += [create(:event, created_at: Date.new(2014, 05, 26) + 28)]
expect(subject.get_submissions_per_week).to eq([0, 0, 0, 0, 1])
expect(subject.get_submissions_per_week.values).to eq([0, 0, 0, 0, 1])
end
it 'pads with zeros if there are no submissions' do
@ -1169,7 +1144,7 @@ describe Conference do
cfp.start_date = Date.new(2014, 05, 26)
cfp.end_date = Date.new(2014, 05, 26) + 21
cfp.save!
expect(subject.get_submissions_per_week).to eq([0, 0, 0, 0])
expect(subject.get_submissions_per_week.values).to eq([0, 0, 0, 0])
end
it 'summarized correct if there are no submissions in one week' do
@ -1180,7 +1155,7 @@ describe Conference do
subject.program.events += [create(:event, created_at: Date.new(2014, 05, 26) + 7)]
subject.program.events += [create(:event, created_at: Date.new(2014, 05, 26) + 14)]
subject.program.events += [create(:event, created_at: Date.new(2014, 05, 26) + 28)]
expect(subject.get_submissions_per_week).to eq([0, 1, 2, 2, 3])
expect(subject.get_submissions_per_week.values).to eq([0, 1, 2, 2, 3])
end
it 'summarized correct if there are submissions every week except the first' do
@ -1190,7 +1165,7 @@ describe Conference do
cfp.save!
subject.program.events += [create(:event, created_at: Date.new(2014, 05, 26) + 7)]
subject.program.events += [create(:event, created_at: Date.new(2014, 05, 26) + 14)]
expect(subject.get_submissions_per_week).to eq([0, 1, 2, 2])
expect(subject.get_submissions_per_week.values).to eq([0, 1, 2, 2])
end
it 'summarized correct if there are submissions every week' do
@ -1201,7 +1176,9 @@ describe Conference do
subject.program.events += [create(:event, created_at: Date.new(2014, 05, 26))]
subject.program.events += [create(:event, created_at: Date.new(2014, 05, 26) + 7)]
subject.program.events += [create(:event, created_at: Date.new(2014, 05, 26) + 14)]
expect(subject.get_submissions_per_week).to eq([1, 2, 3, 3])
expect(subject.get_submissions_per_week).to eq(
'Wk 1' => 1, 'Wk 2' => 2, 'Wk 3' => 3, 'Wk 4' => 3
)
end
it 'pads left' do
@ -1210,7 +1187,7 @@ describe Conference do
cfp.end_date = Date.new(2014, 05, 26) + 21
cfp.save!
subject.program.events += [create(:event, created_at: Date.new(2014, 05, 26) + 21)]
expect(subject.get_submissions_per_week).to eq([0, 0, 0, 1])
expect(subject.get_submissions_per_week.values).to eq([0, 0, 0, 1])
end
it 'pads middle' do
@ -1220,7 +1197,7 @@ describe Conference do
cfp.save!
subject.program.events += [create(:event, created_at: Date.new(2014, 05, 26))]
subject.program.events += [create(:event, created_at: Date.new(2014, 05, 26) + 21)]
expect(subject.get_submissions_per_week).to eq([1, 1, 1, 2])
expect(subject.get_submissions_per_week.values).to eq([1, 1, 1, 2])
end
it 'pads right' do
@ -1229,7 +1206,7 @@ describe Conference do
cfp.end_date = Date.new(2014, 05, 26) + 21
cfp.save!
subject.program.events += [create(:event, created_at: Date.new(2014, 05, 26))]
expect(subject.get_submissions_per_week).to eq([1, 1, 1, 1])
expect(subject.get_submissions_per_week.values).to eq([1, 1, 1, 1])
end
end
@ -1240,7 +1217,7 @@ describe Conference do
start_date: Date.new(2014, 05, 26),
end_date: Date.new(2014, 05, 26) + 21, conference: subject)
expect(subject.get_registrations_per_week).to eq([0, 0, 0, 0])
expect(subject.get_registrations_per_week.values).to eq([0, 0, 0, 0])
end
it 'summarized correct if there are no registrations in one week' do
@ -1255,7 +1232,7 @@ describe Conference do
create(:registration, conference: subject,
created_at: Date.new(2014, 05, 26) + 28)
expect(subject.get_registrations_per_week).to eq([0, 1, 2, 2, 3])
expect(subject.get_registrations_per_week.values).to eq([0, 1, 2, 2, 3])
end
it 'returns [1] if there is one registration on the first day' do
@ -1265,7 +1242,7 @@ describe Conference do
create(:registration, conference: subject,
created_at: Date.new(2014, 05, 26))
expect(subject.get_registrations_per_week).to eq([1, 1])
expect(subject.get_registrations_per_week.values).to eq([1, 1])
end
it 'summarized correct if there are registrations every week' do
@ -1279,7 +1256,7 @@ describe Conference do
create(:registration, conference: subject,
created_at: Date.new(2014, 05, 26) + 14)
expect(subject.get_registrations_per_week).to eq([1, 2, 3, 3])
expect(subject.get_registrations_per_week.values).to eq([1, 2, 3, 3])
end
it 'summarized correct if there are registrations every week except the first' do
@ -1294,7 +1271,7 @@ describe Conference do
create(:registration, conference: subject,
created_at: Date.new(2014, 05, 26) + 28)
expect(subject.get_registrations_per_week).to eq([0, 1, 2, 2, 3])
expect(subject.get_registrations_per_week.values).to eq([0, 1, 2, 2, 3])
end
it 'pads left' do
@ -1309,7 +1286,7 @@ describe Conference do
create(:registration, conference: subject,
created_at: Date.new(2014, 05, 26) + 35)
expect(subject.get_registrations_per_week).to eq([0, 0, 0, 1, 2, 3])
expect(subject.get_registrations_per_week.values).to eq([0, 0, 0, 1, 2, 3])
end
it 'pads middle' do
@ -1322,7 +1299,7 @@ describe Conference do
create(:registration, conference: subject,
created_at: Date.new(2014, 05, 26) + 35)
expect(subject.get_registrations_per_week).to eq([1, 1, 1, 1, 1, 2])
expect(subject.get_registrations_per_week.values).to eq([1, 1, 1, 1, 1, 2])
end
it 'pads right' do
@ -1335,7 +1312,7 @@ describe Conference do
create(:registration, conference: subject,
created_at: Date.new(2014, 05, 26) + 7)
expect(subject.get_registrations_per_week).to eq([1, 2, 2, 2, 2, 2])
expect(subject.get_registrations_per_week.values).to eq([1, 2, 2, 2, 2, 2])
end
end

View file

@ -99,6 +99,23 @@ describe User do
expect(User.comment_notifiable(conference)).not_to include(user)
end
end
describe 'user distribution scopes' do
it 'scopes active users' do
create(:user, last_sign_in_at: Date.today - 3.months + 1.day) # active
expect(User.active.count).to eq(1)
end
it 'scopes unconfirmed users' do
create(:user, confirmed_at: nil) # unconfirmed
expect(User.unconfirmed.count).to eq(1)
end
it 'scopes dead users' do
create(:user, last_sign_in_at: Time.zone.now - 1.year - 1.day) # dead
expect(User.dead.count).to eq(1)
end
end
end
describe 'methods' do