[Bugfix] Confirmed / Unconfirmed events over time for conference dashboard

This commit is contained in:
Chrisbr 2014-07-07 14:58:53 +02:00
parent 609f2fd564
commit d1b038c5a7
9 changed files with 555 additions and 21 deletions

View file

@ -95,6 +95,9 @@ gem 'factory_girl_rails'
gem 'ahoy_matey' gem 'ahoy_matey'
gem 'activeuuid' gem 'activeuuid'
# We use whenever for recurring jobs
gem 'whenever', :require => false
# Use guard and spring for testing in development # Use guard and spring for testing in development
group :development do group :development do
# rspec Guard rules # rspec Guard rules

View file

@ -75,6 +75,7 @@ GEM
nio4r (>= 0.5.0) nio4r (>= 0.5.0)
chart-js-rails (0.0.6) chart-js-rails (0.0.6)
railties (> 3.1) railties (> 3.1)
chronic (0.10.2)
climate_control (0.0.3) climate_control (0.0.3)
activesupport (>= 3.0) activesupport (>= 3.0)
cliver (0.3.2) cliver (0.3.2)
@ -373,6 +374,9 @@ GEM
warden (1.2.3) warden (1.2.3)
rack (>= 1.0) rack (>= 1.0)
websocket-driver (0.3.3) websocket-driver (0.3.3)
whenever (0.9.2)
activesupport (>= 2.3.4)
chronic (>= 0.6.3)
xpath (2.0.0) xpath (2.0.0)
nokogiri (~> 1.3) nokogiri (~> 1.3)
yajl-ruby (1.2.0) yajl-ruby (1.2.0)
@ -434,3 +438,4 @@ DEPENDENCIES
transitions transitions
turbolinks turbolinks
uglifier (>= 1.3.0) uglifier (>= 1.3.0)
whenever

View file

@ -126,19 +126,12 @@ class Admin::ConferenceController < ApplicationController
@submissions = Conference.get_event_state_line_colors @submissions = Conference.get_event_state_line_colors
@submissions_data = {} @submissions_data = {}
@cfp_weeks = [0] @submissions_data = @conference.get_submissions_data
@submissions_data['Submitted'] = @conference.get_submissions_per_week @cfp_weeks = 0
@cfp_weeks.push(@submissions_data['Submitted'].length) if @submissions_data['Weeks']
@cfp_weeks = @submissions_data['Weeks']
@submissions_data['Confirmed'] = @conference.get_submissions_per_week_by_status('confirmed') @submissions_data = @submissions_data.except('Weeks')
@cfp_weeks.push(@submissions_data['Confirmed'].length) end
@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 # Doughnut charts
@event_type_distribution = @conference.event_type_distribution @event_type_distribution = @conference.event_type_distribution

View file

@ -3,6 +3,7 @@
class Conference < ActiveRecord::Base class Conference < ActiveRecord::Base
require 'uri' require 'uri'
serialize :events_per_week, Hash
attr_accessible :title, :short_title, :social_tag, :contact_email, :timezone, :html_export_path, attr_accessible :title, :short_title, :social_tag, :contact_email, :timezone, :html_export_path,
:start_date, :end_date, :rooms_attributes, :tracks_attributes, :start_date, :end_date, :rooms_attributes, :tracks_attributes,
@ -185,18 +186,28 @@ class Conference < ActiveRecord::Base
end end
## ##
# Returns an array with the summarized event submissions per week. # Returns an hash with submitted, confirmed and unconfirmed event submissions
# per week.
# #
# ====Returns # ====Returns
# * +Array+ -> e.g. [0, 3, 3, 5] -> first week 0 events, second week 3 events. # * +Array+ -> e.g. 'Submitted' => [0, 3, 3, 5] -> first week 0 events, second week 3 events.
def get_submissions_per_week_by_status(state) def get_submissions_data
result = [] result = {}
if call_for_papers && events if call_for_papers && events
submissions = events.where('state = ?', state).group(:week).count result = get_events_per_week_by_state
start_week = call_for_papers.start_week start_week = call_for_papers.start_week
weeks = call_for_papers.weeks end_week = end_date.strftime('%W').to_i
result = calculate_items_per_week(start_week, weeks, submissions) weeks = weeks(start_week, end_week)
result.each do |state, values|
if state == 'Submitted'
result['Submitted'] = pad_array_left_kumulative(start_week, values)
else
result[state] = pad_array_left_not_kumulative(start_week, values)
end
end
result['Weeks'] = weeks > 0 ? (1..weeks).to_a : 0
end end
result result
end end
@ -487,8 +498,142 @@ class Conference < ActiveRecord::Base
result result
end end
##
# Writes an snapshot of the actual event distribution to the database
# Triggered each every Sunday 11:55 pm form whenever (config/schedule.rb).
#
def self.write_event_distribution_to_db
week = DateTime.now.end_of_week
Conference.where('end_date > ?', Date.today).each do |conference|
result = {}
Event.state_machine.states.each do |state|
count = conference.events.where('state = ?', state.name).count
result[state.name] = count
end
if !conference.events_per_week
conference.events_per_week = {}
end
# Write to database
conference.events_per_week[week] = result
conference.save!
end
end
private private
##
# Calculates the weeks from a start and a end week.
#
# ====Returns
# * +Fixnum+ -> weeks
def weeks(start_week, end_week)
weeks = end_week - start_week + 1
weeks_of_year = Date.new(start_date.year, 12, 31).strftime('%W').to_i
weeks < 0 ? weeks + weeks_of_year : weeks
end
##
# Returns a Hash with the events with the state confirmend / unconfirmed per week.
#
# ====Returns
# * +Hash+ -> e.g. 'Confirmed' => { 3 => 5, 4 => 6 }
def get_events_per_week_by_state
result = {
'Submitted' => {},
'Confirmed' => {},
'Unconfirmed' => {}
}
# Completed weeks
events_per_week.each do |week, values|
values.each do |state, value|
if [:confirmed, :unconfirmed].include?(state)
if !result[state.to_s.capitalize]
result[state.to_s.capitalize] = {}
end
result[state.to_s.capitalize][week.strftime('%W').to_i] = value
end
end
end
# Actual week
this_week = Date.today.end_of_week.strftime('%W').to_i
result['Confirmed'][this_week] = events.where('state = ?', :confirmed).count
result['Unconfirmed'][this_week] = events.where('state = ?', :unconfirmed).count
result['Submitted'] = events.group(:week).count
result['Submitted'][this_week] = events.where(week: this_week).count
result
end
##
# Returns an array from the hash values with left padding.
#
# ====Returns
# * +Array+ -> [0, 0, 1, 2, 3, 0, 0]
def pad_array_left_not_kumulative(start_week, hash)
hash = assert_keys_are_continuously(hash)
first_week = hash.keys[0]
left = pad_left(first_week, start_week)
left + hash.values
end
##
# Returns an array from the hash values with left padding.
#
# ====Returns
# * +Array+ -> [0, 0, 1, 2, 3, 3, 3]
def pad_array_left_kumulative(start_week, hash)
hash = assert_keys_are_continuously(hash)
result = cumulative_sum(hash.values)
first_week = hash.keys[0]
left = pad_left(first_week, start_week)
left + result
end
##
# Cumulative sums an array.
#
# ====Returns
# * +Array+ -> [1, 2, 3, 4] --> [1, 3, 7, 11]
def cumulative_sum(array)
sum = 0
array.map { |x| sum += x }
end
##
# Returns the left padding.
#
# ====Returns
# * +Array+
def pad_left(first_week, start_week)
left = []
if first_week > start_week
left = Array.new(first_week - start_week - 1, 0)
end
left
end
##
# Asserts that all keys in the hash are continuously.
# If not, the missing key is inserted with value 0.
#
# ====Returns
# * +Hash+ { 1 => 1, 2 => 0, 3 => 0, 4 => 3 }
def assert_keys_are_continuously(hash)
keys = hash.keys
(keys.min..keys.max).each do |key|
if !hash[key]
hash[key] = 0
end
end
hash.sort.to_h
end
## ##
# Returns the progress of the set up conference list in percent # Returns the progress of the set up conference list in percent
# #

View file

@ -30,6 +30,7 @@ class Event < ActiveRecord::Base
before_create :generate_guid before_create :generate_guid
validate :abstract_limit validate :abstract_limit
validate :before_end_of_conference
validate :biography_exists validate :biography_exists
validates :title, presence: true validates :title, presence: true
validates :abstract, presence: true validates :abstract, presence: true
@ -207,4 +208,10 @@ class Event < ActiveRecord::Base
self.week = created_at.strftime('%W') self.week = created_at.strftime('%W')
save! save!
end end
def before_end_of_conference
errors.
add(:created_at, "can't be after the conference end date!") if conference.end_date &&
(Date.today > conference.end_date)
end
end end

24
config/schedule.rb Normal file
View file

@ -0,0 +1,24 @@
# Use this file to easily define all of your cron jobs.
#
# It's helpful, but not entirely necessary to understand cron before proceeding.
# http://en.wikipedia.org/wiki/Cron
# Example:
#
# set :output, "/path/to/my/cron_log.log"
#
# every 2.hours do
# command "/usr/bin/some_great_command"
# runner "MyModel.some_method"
# rake "some:great:rake:task"
# end
#
# every 4.days do
# runner "AnotherModel.prune_old_records"
# end
# Learn more: http://github.com/javan/whenever
every :sunday, at: '23:55 pm' do
runner 'Conference.write_event_distribution_to_db'
end

View file

@ -0,0 +1,90 @@
class AddEventsPerWeekToConference < ActiveRecord::Migration
class TempVersion < ActiveRecord::Base
self.table_name = 'versions'
serialize :object_changes, HashWithIndifferentAccess
end
class TempEvent < ActiveRecord::Base
self.table_name = 'events'
end
class TempConference < ActiveRecord::Base
self.table_name = 'conferences'
serialize :events_per_week, Hash
end
def up
add_column :conferences, :events_per_week, :text
TempVersion.where(item_type: 'Event').each do |event_version|
event = TempEvent.find(event_version.item_id)
conference = TempConference.find(event.conference_id)
week = event_version.created_at.end_of_week
no_events = {
new: 0,
withdrawn: 0,
unconfirmed: 0,
confirmed: 0,
canceled: 0,
rejected: 0,
}
if !conference.events_per_week
conference.events_per_week = {
week => no_events
}
elsif !conference.events_per_week[week]
conference.events_per_week[week] = no_events
end
if event_version.object_changes &&
event_version.event == 'create'
# Increment the new state
conference.events_per_week[week][:new] += 1
elsif event_version.object_changes &&
event_version.object_changes[:state]
prev_state = event_version.object_changes[:state][0].to_sym
next_state = event_version.object_changes[:state][1].to_sym
# Backward compatibility: deprecated state :review now :new
if prev_state == :review
prev_state = :new
elsif next_state == :review
next_state = :new
end
# Increment the next state
conference.events_per_week[week][next_state] += 1
# Decrement the previous state
conference.events_per_week[week][prev_state] -= 1
end
conference.save
end
# Cumulate the previous weeks to get a snapshot
TempConference.all.each do |conference|
hash = conference.events_per_week.sort.to_h
previous = nil
hash.each do |week, values|
if previous
values.each do |state, value|
hash[week][state] += previous[state]
end
end
previous = values
end
conference.events_per_week = hash
conference.save
end
end
def down
remove_column :conferences, :events_per_week
end
end

View file

@ -125,6 +125,7 @@ ActiveRecord::Schema.define(version: 20140714141156) do
t.datetime "banner_photo_updated_at" t.datetime "banner_photo_updated_at"
t.boolean "include_banner_in_splash", default: false t.boolean "include_banner_in_splash", default: false
t.string "instagram_url" t.string "instagram_url"
t.text "events_per_week"
end end
create_table "conferences_questions", id: false, force: true do |t| create_table "conferences_questions", id: false, force: true do |t|

View file

@ -6,6 +6,272 @@ describe Conference do
let(:subject) { create(:conference) } let(:subject) { create(:conference) }
describe '#write_event_distribution_to_db' do
it 'updates pending conferences' do
create(:conference,
start_date: Date.today - 2.weeks,
end_date: Date.today - 1.weeks)
subject.start_date = Date.today + 1.weeks
subject.end_date = Date.today + 2.weeks
result = {
DateTime.now.end_of_week =>
{
confirmed: 0,
unconfirmed: 0,
new: 0,
withdrawn: 0,
canceled: 0,
rejected: 0
},
}
Conference.write_event_distribution_to_db
subject.reload
expect(subject.events_per_week).to eq(result)
end
it 'does not update past conferences' do
old_conference = create(:conference,
start_date: Date.today - 2.weeks,
end_date: Date.today - 1.weeks)
Conference.write_event_distribution_to_db
old_conference.reload
expect(old_conference.events_per_week).to eq({})
end
it 'computes the correct result' do
subject.email_settings = create(:email_settings)
subject.start_date = Date.today + 6.weeks
subject.end_date = Date.today + 7.weeks
subject.save
subject.call_for_papers = create(:call_for_papers, start_date: Date.today - 3.weeks)
create(:event, conference: subject, created_at: Date.today)
options = {}
options[:send_mail] = 'false'
withdrawn = create(:event, conference: subject)
withdrawn.withdraw!
unconfirmed = create(:event, conference: subject)
unconfirmed.accept!(options)
rejected = create(:event, conference: subject)
rejected.reject!(options)
confirmed = create(:event, conference: subject)
confirmed.accept!(options)
confirmed.confirm!
canceled = create(:event, conference: subject)
canceled.accept!(options)
canceled.cancel!
Conference.write_event_distribution_to_db
result = {
DateTime.now.end_of_week =>
{
confirmed: 1,
unconfirmed: 1,
new: 1,
withdrawn: 1,
canceled: 1,
rejected: 1
},
}
subject.reload
expect(subject.events_per_week).to eq(result)
end
it 'does not overwrite old entries' do
subject.email_settings = create(:email_settings)
subject.start_date = Date.today + 6.weeks
subject.end_date = Date.today + 7.weeks
db_data = {
DateTime.now.end_of_week - 2.weeks =>
{
confirmed: 1,
unconfirmed: 2,
new: 0,
withdrawn: 0,
canceled: 0,
rejected: 0
},
DateTime.now.end_of_week - 1.weeks =>
{
confirmed: 3,
unconfirmed: 4,
new: 0,
withdrawn: 0,
canceled: 0,
rejected: 0
},
}
subject.events_per_week = db_data
subject.save
subject.call_for_papers = create(:call_for_papers, start_date: Date.today - 3.weeks)
create(:event, conference: subject, created_at: Date.today)
unconfirmed = create(:event, conference: subject)
confirmed = create(:event, conference: subject)
options = {}
options[:send_mail] = 'false'
unconfirmed.accept!(options)
confirmed.accept!(options)
confirmed.confirm!
Conference.write_event_distribution_to_db
result = {
DateTime.now.end_of_week - 2.weeks =>
{
confirmed: 1,
unconfirmed: 2,
new: 0,
withdrawn: 0,
canceled: 0,
rejected: 0
},
DateTime.now.end_of_week - 1.weeks =>
{
confirmed: 3,
unconfirmed: 4,
new: 0,
withdrawn: 0,
canceled: 0,
rejected: 0
},
DateTime.now.end_of_week =>
{
confirmed: 1,
unconfirmed: 1,
new: 1,
withdrawn: 0,
canceled: 0,
rejected: 0
},
}
subject.reload
expect(subject.events_per_week).to eq(result)
end
end
describe '#get_submissions_data' do
it 'returns emtpy hash if there is no cfp or events' do
expect(subject.get_submissions_data).to eq({})
end
it 'calculates the correct result with data from database' do
subject.start_date = Date.today + 6.weeks
subject.end_date = Date.today + 7.weeks
# Inject last two weeks to database
db_data = {
Date.today.end_of_week - 2.weeks => {
confirmed: 1,
unconfirmed: 2,
},
Date.today.end_of_week - 1.weeks => {
confirmed: 3,
unconfirmed: 4,
}
}
subject.events_per_week = db_data
subject.save
subject.call_for_papers = create(:call_for_papers, start_date: Date.today - 2.weeks)
create(:event, conference: subject, 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]
}
expect(subject.get_submissions_data).to eq(result)
end
it 'calculates the correct result without data from database' do
subject.start_date = Date.today + 6.weeks
subject.end_date = Date.today + 7.weeks
subject.save
subject.call_for_papers = create(:call_for_papers, start_date: Date.today)
create(:event, conference: subject)
result = {
'Submitted' => [1],
'Confirmed' => [0],
'Unconfirmed' => [0],
'Weeks' => [1, 2, 3, 4, 5, 6, 7, 8]
}
expect(subject.get_submissions_data).to eq(result)
end
it 'pads left correct' do
subject.email_settings = create(:email_settings)
subject.start_date = Date.today + 6.weeks
subject.end_date = Date.today + 7.weeks
subject.save
subject.call_for_papers = create(:call_for_papers, start_date: Date.today - 3.weeks)
create(:event, conference: subject, created_at: Date.today)
unconfirmed = create(:event, conference: subject)
confirmed = create(:event, conference: subject)
options = {}
options[:send_mail] = 'false'
unconfirmed.accept!(options)
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]
}
expect(subject.get_submissions_data).to eq(result)
end
it 'calculates correct with missing weeks' do
subject.start_date = Date.today + 6.weeks
subject.end_date = Date.today + 7.weeks
# Inject last two weeks to database
db_data = {
Date.today.end_of_week - 3.weeks => {
confirmed: 1,
unconfirmed: 2,
},
Date.today.end_of_week - 1.weeks => {
confirmed: 3,
unconfirmed: 4,
}
}
subject.events_per_week = db_data
subject.save
subject.call_for_papers = create(:call_for_papers, start_date: Date.today - 3.weeks)
create(:event, conference: subject, 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]
}
expect(subject.get_submissions_data).to eq(result)
end
end
describe '#get_top_submitter' do describe '#get_top_submitter' do
# It is necessary to use bang version of let to build roles before user # It is necessary to use bang version of let to build roles before user
let!(:organizer_role) { create(:organizer_role) } let!(:organizer_role) { create(:organizer_role) }