From f6e74461ba9368be3a7f786ce6c26d7d0244b34a Mon Sep 17 00:00:00 2001 From: JewelSam Date: Thu, 2 Mar 2017 20:53:20 +0300 Subject: [PATCH] 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 --- INSTALL.md | 1 - app.json | 4 -- app/controllers/admin/programs_controller.rb | 8 ++-- app/controllers/schedules_controller.rb | 2 +- app/models/event_type.rb | 8 +--- app/models/program.rb | 32 +++++++++++++++ app/serializers/event_serializer.rb | 2 +- app/views/admin/event_types/_form.html.haml | 2 +- app/views/admin/programs/_form.html.haml | 1 + app/views/admin/programs/show.html.haml | 5 +++ app/views/admin/schedules/_day_tab.html.haml | 6 +-- app/views/admin/schedules/_event.html.haml | 4 +- app/views/schedules/_carousel.html.haml | 4 +- app/views/schedules/show.xml.haml | 2 +- config/initializers/schedule_parameters.rb | 6 --- ...45716_add_schedule_interval_to_programs.rb | 5 +++ db/schema.rb | 3 +- dotenv.example | 3 -- spec/models/program_spec.rb | 41 +++++++++++++++++++ 19 files changed, 104 insertions(+), 35 deletions(-) delete mode 100644 config/initializers/schedule_parameters.rb create mode 100644 db/migrate/20170302145716_add_schedule_interval_to_programs.rb 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'