mirror of
https://github.com/openSUSE/osem.git
synced 2026-05-04 13:45:05 -04:00
Add schedule interval as attribute of a program
Also, the part of the schedule event is deleted and the length of the event types changes to the nearest suitable after changing the length of the interval. This closes #1220
This commit is contained in:
@@ -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
|
||||
|
||||
4
app.json
4
app.json
@@ -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
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,7 +13,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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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?) }
|
||||
|
||||
@@ -49,6 +49,11 @@
|
||||
Yes
|
||||
- else
|
||||
No
|
||||
%dt
|
||||
Schedule interval
|
||||
%dd
|
||||
= @program.schedule_interval
|
||||
minutes
|
||||
|
||||
%h3 Voting Options
|
||||
%hr
|
||||
|
||||
@@ -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, |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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|
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddScheduleIntervalToPrograms < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :programs, :schedule_interval, :integer, default: 15, null: false
|
||||
end
|
||||
end
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user