diff --git a/INSTALL.md b/INSTALL.md index 33023af6..02b496eb 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -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 diff --git a/app.json b/app.json index 6b28f6b9..f77f6c9c 100644 --- a/app.json +++ b/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 }, diff --git a/app/controllers/admin/programs_controller.rb b/app/controllers/admin/programs_controller.rb index 77b45a57..772ae779 100644 --- a/app/controllers/admin/programs_controller.rb +++ b/app/controllers/admin/programs_controller.rb @@ -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 diff --git a/app/controllers/schedules_controller.rb b/app/controllers/schedules_controller.rb index 0b135328..c759d337 100644 --- a/app/controllers/schedules_controller.rb +++ b/app/controllers/schedules_controller.rb @@ -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 diff --git a/app/models/event_type.rb b/app/models/event_type.rb index 50d8d551..47da514f 100644 --- a/app/models/event_type.rb +++ b/app/models/event_type.rb @@ -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 diff --git a/app/models/program.rb b/app/models/program.rb index f8b46b18..ff0ab094 100644 --- a/app/models/program.rb +++ b/app/models/program.rb @@ -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 diff --git a/app/serializers/event_serializer.rb b/app/serializers/event_serializer.rb index c2dffbeb..d58d23d2 100644 --- a/app/serializers/event_serializer.rb +++ b/app/serializers/event_serializer.rb @@ -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 diff --git a/app/views/admin/event_types/_form.html.haml b/app/views/admin/event_types/_form.html.haml index 3f3702b4..51a0ad99 100644 --- a/app/views/admin/event_types/_form.html.haml +++ b/app/views/admin/event_types/_form.html.haml @@ -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} diff --git a/app/views/admin/programs/_form.html.haml b/app/views/admin/programs/_form.html.haml index 8eb5ea47..035093f0 100644 --- a/app/views/admin/programs/_form.html.haml +++ b/app/views/admin/programs/_form.html.haml @@ -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?) } diff --git a/app/views/admin/programs/show.html.haml b/app/views/admin/programs/show.html.haml index 085a47c1..47e50fb6 100644 --- a/app/views/admin/programs/show.html.haml +++ b/app/views/admin/programs/show.html.haml @@ -49,6 +49,11 @@ Yes - else No + %dt + Schedule interval + %dd + = @program.schedule_interval + minutes %h3 Voting Options %hr diff --git a/app/views/admin/schedules/_day_tab.html.haml b/app/views/admin/schedules/_day_tab.html.haml index 61f6e862..7bfe3ec6 100644 --- a/app/views/admin/schedules/_day_tab.html.haml +++ b/app/views/admin/schedules/_day_tab.html.haml @@ -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, | diff --git a/app/views/admin/schedules/_event.html.haml b/app/views/admin/schedules/_event.html.haml index 3c9c0c75..3a13323b 100644 --- a/app/views/admin/schedules/_event.html.haml +++ b/app/views/admin/schedules/_event.html.haml @@ -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 diff --git a/app/views/schedules/_carousel.html.haml b/app/views/schedules/_carousel.html.haml index 5c5acc29..ce28e909 100644 --- a/app/views/schedules/_carousel.html.haml +++ b/app/views/schedules/_carousel.html.haml @@ -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 diff --git a/app/views/schedules/show.xml.haml b/app/views/schedules/show.xml.haml index 953a331f..d84e98de 100644 --- a/app/views/schedules/show.xml.haml +++ b/app/views/schedules/show.xml.haml @@ -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| diff --git a/config/initializers/schedule_parameters.rb b/config/initializers/schedule_parameters.rb deleted file mode 100644 index 7d1ca08d..00000000 --- a/config/initializers/schedule_parameters.rb +++ /dev/null @@ -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 diff --git a/db/migrate/20170302145716_add_schedule_interval_to_programs.rb b/db/migrate/20170302145716_add_schedule_interval_to_programs.rb new file mode 100644 index 00000000..617aaf1e --- /dev/null +++ b/db/migrate/20170302145716_add_schedule_interval_to_programs.rb @@ -0,0 +1,5 @@ +class AddScheduleIntervalToPrograms < ActiveRecord::Migration + def change + add_column :programs, :schedule_interval, :integer, default: 15, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 34c30a04..ab95b4dc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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" diff --git a/dotenv.example b/dotenv.example index b9c2a668..ec2715ea 100644 --- a/dotenv.example +++ b/dotenv.example @@ -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 diff --git a/spec/models/program_spec.rb b/spec/models/program_spec.rb index f491499b..ac720def 100644 --- a/spec/models/program_spec.rb +++ b/spec/models/program_spec.rb @@ -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'