Merge pull request #1319 from JewelSam/master

#1220 adding the cell size attribute to the database
This commit is contained in:
Ana María Martínez Gómez 2017-04-03 10:23:49 +02:00 committed by GitHub
commit f3e712a1bc
19 changed files with 104 additions and 35 deletions

View file

@ -43,7 +43,6 @@ There are a couple of environment variables you can set to configure OSEM. Check
| OSEM_FACEBOOK_SECRET | *string* | OMNIAUTH Developer Secret for Facebook
| OSEM_GITHUB_KEY | *string* | OMNIAUTH Developer Key for GitHub
| OSEM_GITHUB_SECRET | *string* | OMNIAUTH Developer Secret for GitHub
| OSEM_SCHEDULE_CELL_SIZE | *integer* | Schedule timeslot size to use (in minutes), should be greater than zero, should be divisor of 60
| OSEM_SMTP_ADDRESS | smtp.opensuse.org | The smtp server to use
| OSEM_SMTP_PORT | *int* | The port on the smtp server
| OSEM_SMTP_USERNAME | *string* | The user for the smtp server

View file

@ -54,10 +54,6 @@
"description": "The user for the smtp server",
"required": false
},
"OSEM_SCHEDULE_CELL_SIZE": {
"description": "Schedule timeslot size in minutes",
"required": false
},
"RACK_ENV": {
"required": false
},

View file

@ -12,13 +12,15 @@ module Admin
@program = @conference.program
@program.assign_attributes(program_params)
send_mail_on_schedule_public = @program.notify_on_schedule_public?
event_schedules_count_was = @program.event_schedules.count
if @program.update_attributes(program_params)
ConferenceScheduleUpdateMailJob.perform_later(@conference) if send_mail_on_schedule_public
respond_to do |format|
format.html do
redirect_to admin_conference_program_path(@conference.short_title),
notice: 'The program was successfully updated.'
notice = 'The program was successfully updated.'
notice += ' You changed schedule interval and some events were unscheduled.' if @program.event_schedules.count != event_schedules_count_was
redirect_to admin_conference_program_path(@conference.short_title), notice: notice
end
format.js { render json: {} }
end
@ -36,7 +38,7 @@ module Admin
private
def program_params
params.require(:program).permit(:rating, :schedule_public, :schedule_fluid, :languages, :blind_voting, :voting_start_date, :voting_end_date, :selected_schedule_id)
params.require(:program).permit(:rating, :schedule_public, :schedule_interval, :schedule_fluid, :languages, :blind_voting, :voting_start_date, :voting_end_date, :selected_schedule_id)
end
end
end

View file

@ -14,7 +14,7 @@ class SchedulesController < ApplicationController
@events_xml = schedules.map(&:event).group_by{ |event| event.time.to_date } if schedules
@dates = @conference.start_date..@conference.end_date
@step_minutes = EventType::LENGTH_STEP.minutes
@step_minutes = @program.schedule_interval.minutes
@conf_start = @conference.start_hour
@conf_period = @conference.end_hour - @conf_start

View file

@ -15,17 +15,13 @@ class EventType < ActiveRecord::Base
alias_attribute :name, :title
# If LENGTH_STEP must be divisor of 60, otherwise the schedule wont be displayed properly
LENGTH_STEP = defined?(SCHEDULE_CELL_SIZE) ? SCHEDULE_CELL_SIZE : 15
private
##
# Check if length is multiple of LENGTH_STEP. Used as validation.
# Check if length is a divisor of program schedule cell size. Used as validation.
#
def length_step
errors.add(:length, "must be multiple of #{LENGTH_STEP}") if length % LENGTH_STEP != 0
errors.add(:length, "must be a divisor of #{program.schedule_interval}") if program && length % program.schedule_interval != 0
end
def capitalize_color

View file

@ -38,6 +38,7 @@ class Program < ActiveRecord::Base
where(state: :confirmed, is_highlight: true)
end
end
has_many :event_schedules, through: :events
has_many :event_users, through: :events
has_many :speakers, -> { distinct }, through: :event_users, source: :user do
@ -52,11 +53,15 @@ class Program < ActiveRecord::Base
# validates :conference_id, presence: true, uniqueness: true
validates :rating, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 10, only_integer: true }
validates :schedule_interval, numericality: { greater_than_or_equal_to: 5, less_than_or_equal_to: 60 }, presence: true
validate :schedule_interval_divisor_60
validate :voting_start_date_before_end_date
validate :voting_dates_exist
after_create :create_event_types
after_create :create_difficulty_levels
after_save :unschedule_unfit_events, if: :schedule_interval_changed?
after_save :normalize_event_types_length, if: :schedule_interval_changed?
validate :check_languages_format
# Returns all event_schedules for the selected schedule ordered by start_time
@ -199,4 +204,31 @@ class Program < ActiveRecord::Base
# We check if every language is a valid ISO 639-1 language
errors.add(:languages, 'must be ISO 639-1 valid codes') unless languages_array.select{ |x| ISO_639.find(x).nil? }.empty?
end
##
# Check if schedule_interval is a divisor of 60 minutes
#
def schedule_interval_divisor_60
errors.add(:schedule_interval, 'must be a divisor of 60') if schedule_interval > 0 && 60 % schedule_interval > 0
end
##
# Unschedule all the events which don't fit
#
def unschedule_unfit_events
unfit_schedules = event_schedules.select do |event_schedule|
event_schedule.start_time.min % schedule_interval > 0
end
EventSchedule.where(id: unfit_schedules.map(&:id)).destroy_all
end
##
# Change event type length according schedule interval
#
def normalize_event_types_length
event_types.each do |event_type|
new_length = event_type.length > schedule_interval ? event_type.length - (event_type.length % schedule_interval) : schedule_interval
event_type.update_attributes length: new_length
end
end
end

View file

@ -26,6 +26,6 @@ class EventSerializer < ActiveModel::Serializer
end
def length
object.event_type.try(:length) || EventType::LENGTH_STEP
object.event_type.try(:length) || object.event_type.program.schedule_interval
end
end

View file

@ -10,7 +10,7 @@
.col-md-12
= semantic_form_for(@event_type, url: (@event_type.new_record? ? admin_conference_program_event_types_path : admin_conference_program_event_type_path(@conference.short_title, @event_type))) do |f|
= f.input :title
= f.input :length, input_html: {size: 3, type: 'number', step: EventType::LENGTH_STEP, min: EventType::LENGTH_STEP}
= f.input :length, input_html: {size: 3, type: 'number', step: @event_type.program.schedule_interval, min: @event_type.program.schedule_interval}
= f.input :description
= f.input :minimum_abstract_length, input_html: {size: 3}
= f.input :maximum_abstract_length, input_html: {size: 3}

View file

@ -9,6 +9,7 @@
= f.input :schedule_fluid, label: 'Allow submitters to change their event after it is scheduled'
= f.input :rating, hint: 'Enter the number of different rating levels you want to have for voting on proposals. Enter 0 if you do not want to vote on proposals.'
= f.input :languages, hint: "Enter the languages allowed for events as values of #{link_to('ISO 639-1', 'http://www.loc.gov/standards/iso639-2/php/code_list.php', target: "_blank")} language codes separated with commas. The first language would be the default language. Leave it blank if you do not want to specify languages.".html_safe
= f.input :schedule_interval, hint: "It is the minimal time interval of your schedule. The value should be 5, 6, 10, 12, 15, 20, 30 or 60. Warning! Some events could be unscheduled when changing this value."
= f.input :blind_voting, hint: 'Enable this feature if you do not want to show voting results and voters prior to user submitting a vote. For the feature to work you need to set the voting dates below as well'
= f.input :voting_start_date, as: :string, input_html: { id: 'datetimepicker-voting_start_date', readonly: true, value: (f.object.voting_start_date.to_formatted_s(:db_without_seconds) unless f.object.voting_start_date.nil?) }
= f.input :voting_end_date, as: :string, input_html: { id: 'datetimepicker-voting_start_date', readonly: true, value: (f.object.voting_end_date.to_formatted_s(:db_without_seconds) unless f.object.voting_end_date.nil?) }

View file

@ -49,6 +49,11 @@
Yes
- else
No
%dt
Schedule interval
%dd
= @program.schedule_interval
minutes
%h3 Voting Options
%hr

View file

@ -1,5 +1,5 @@
- compact_grid = EventType::LENGTH_STEP < 15
- cells_per_hour = 60 / EventType::LENGTH_STEP
- compact_grid = @program.schedule_interval < 15
- cells_per_hour = 60 / @program.schedule_interval
/ use smaller cell heights for more compact grids
- cell_height = compact_grid ? 32 : 58
- date_event_schedules = @event_schedules.select{ |e| e.start_time.to_date.eql? date }
@ -11,7 +11,7 @@
= room.name
- (@conference.start_hour * cells_per_hour..@conference.end_hour * cells_per_hour).each do |slot|
- hour = slot / cells_per_hour
- minutes = (EventType::LENGTH_STEP * (slot % cells_per_hour)).to_s.rjust(2, '0')
- minutes = (@program.schedule_interval * (slot % cells_per_hour)).to_s.rjust(2, '0')
- time = "#{hour}:#{minutes}"
.schedule-room-slot{ id: "schedule-room-#{room.guid}-#{hour}-#{minutes}", |
room_id: room.id, |

View file

@ -1,6 +1,6 @@
- cells_length = event.event_type.length / EventType::LENGTH_STEP
- cells_length = event.event_type.length / @program.schedule_interval
/ this height fits the room cells
- compact_grid = EventType::LENGTH_STEP < 15
- compact_grid = @program.schedule_interval < 15
- single_cell_height = compact_grid ? 32 : 58
- height = (cells_length * single_cell_height)
- height -= 23 unless compact_grid

View file

@ -1,4 +1,4 @@
- intervals = hrs_per_slide * 60 / EventType::LENGTH_STEP + 1
- intervals = hrs_per_slide * 60 / @conference.program.schedule_interval + 1
- width = 85 / intervals
- carousel_number = (@conf_period / hrs_per_slide.to_f).ceil
.carousel.slide{ id: "carousel-#{ date }-#{ hrs_per_slide }", |
@ -41,7 +41,7 @@
- if event_schedule
/ There is an event, calculate the span and show it
- event_span = (event_schedule.end_time.to_i - start_room_time.to_i) / 60 / EventType::LENGTH_STEP
- event_span = (event_schedule.end_time.to_i - start_room_time.to_i) / 60 / @conference.program.schedule_interval
- span = ((event_span + i - 1 ) > intervals ? intervals + 1 - i : event_span)
= render partial: 'schedule_item', locals: {event: event_schedule.event, event_schedule: event_schedule, span: span, width: width}
- else

View file

@ -6,7 +6,7 @@
%start= @conference.start_date
%end= @conference.end_date
%days= (@conference.end_date - @conference.start_date).to_i + 1
%timeslot_duration= length_timestamp(EventType::LENGTH_STEP)
%timeslot_duration= length_timestamp(@conference.program.schedule_interval)
- if @events_xml.any?
- @events_xml.keys.each.with_index(1) do |day, index|

View file

@ -1,6 +0,0 @@
#sanitize the OSEM_SCHEUDLE_CELL_SIZE to be used for EventType::LENGTH_STEP
sched_cell_size = ENV['OSEM_SCHEDULE_CELL_SIZE'].to_i
if (sched_cell_size > 0 and 60 % sched_cell_size == 0)
SCHEDULE_CELL_SIZE = sched_cell_size
end

View file

@ -0,0 +1,5 @@
class AddScheduleIntervalToPrograms < ActiveRecord::Migration
def change
add_column :programs, :schedule_interval, :integer, default: 15, null: false
end
end

View file

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170213145807) do
ActiveRecord::Schema.define(version: 20170302145716) do
create_table "ahoy_events", force: :cascade do |t|
t.uuid "visit_id", limit: 16
@ -289,6 +289,7 @@ ActiveRecord::Schema.define(version: 20170213145807) do
t.datetime "voting_start_date"
t.datetime "voting_end_date"
t.integer "selected_schedule_id"
t.integer "schedule_interval", default: 15, null: false
end
add_index "programs", ["selected_schedule_id"], name: "index_programs_on_selected_schedule_id"

View file

@ -57,6 +57,3 @@ OSEM_SMTP_DOMAIN=""
# Enable the usage of the devise ichain plugin
OSEM_ICHAIN_ENABLED=false
# Schedule grid parameters, cell size in minutes
OSEM_SCHEDULE_CELL_SIZE=15

View file

@ -13,6 +13,7 @@ describe Program do
it { is_expected.to have_many(:tracks).dependent(:destroy) }
it { is_expected.to have_many(:difficulty_levels).dependent(:destroy) }
it { is_expected.to have_many(:events).dependent(:destroy) }
it { is_expected.to have_many(:event_schedules).through(:events) }
it { is_expected.to have_many(:event_users).through(:events) }
it { is_expected.to have_many(:speakers).through(:event_users).source(:user) }
@ -32,6 +33,18 @@ describe Program do
it { is_expected.to validate_numericality_of(:rating).is_greater_than_or_equal_to(0).is_less_than_or_equal_to(10).only_integer }
it { is_expected.to validate_numericality_of(:schedule_interval).is_greater_than_or_equal_to(5).is_less_than_or_equal_to(60) }
describe 'schedule_interval_divisor_60' do
it 'is valid, when schedule_interval is divisor of 60' do
expect(build(:program, schedule_interval: 20)).to be_valid
end
it 'is not valid, when schedule_interval is not divisor of 60' do
expect(build(:program, schedule_interval: 35)).to_not be_valid
end
end
describe 'voting_start_date_before_end_date' do
it 'is valid, when voting_start_date is the same day as voting_end_date' do
expect(build(:program, voting_start_date: Date.today, voting_end_date: Date.today)).to be_valid
@ -167,6 +180,34 @@ describe Program do
end
end
describe 'excecutes after_save functions' do
it 'and unschedule unfit events if schedule interval was changed' do
start_date = program.conference.start_date.to_datetime.change(hour: program.conference.start_hour)
create(:event_schedule, event: create(:event, program: program), start_time: start_date.change(min: program.schedule_interval))
create(:event_schedule, event: create(:event, program: program), start_time: start_date)
expect(program.event_schedules.count).to eq 2
program.schedule_interval = 10
program.save!
program.reload
expect(program.event_schedules.count).to eq 1
expect(program.event_schedules.first.start_time).to eq start_date
end
it 'and change event type length if schedule interval was changed' do
program.schedule_interval = 5
program.save!
program.event_types.first.update_attributes length: 5
program.event_types.last.update_attributes length: 25
create(:event_type, program: program, length: 30)
program.schedule_interval = 10
program.save!
expect(program.event_types.pluck(:length).sort).to eq [10, 20, 30]
end
end
describe 'languages' do
it "is not valid if languages aren't two letters separated by commas" do
program.languages = 'eng, De es'