diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index b2e7d847..6875c7b8 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -38,6 +38,12 @@ jobs: with: ruby-version: 2.6.6 bundler-cache: true + - name: Use Node.js + uses: actions/setup-node@v1 + with: + node-version: '12.x' + - name: Install JavaScript libraries via npm + run: npm install - name: Prepare spec run: | rm -f osem_test osem_development diff --git a/.gitignore b/.gitignore index c65dc0b1..fcdf9332 100644 --- a/.gitignore +++ b/.gitignore @@ -121,3 +121,6 @@ build-iPhoneSimulator/ # Used by RuboCop. Remote config files pulled in from inherit_from directive. .rubocop-https?--* + +# Ignore Javascript package binaries +/node_modules diff --git a/.travis.yml b/.travis.yml index b35784c6..00d5b582 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,6 +43,7 @@ jobs: - gem install bundler -v $(tail -n 1 Gemfile.lock) - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > $CCTR - chmod +x $CCTR + - npm install before_script: - RAILS_ENV=test bundle exec rake db:bootstrap --trace - RAILS_ENV=test bundle exec bin/rails webdrivers:chromedriver:update diff --git a/Gemfile b/Gemfile index 7198bdf7..7c1eb7f1 100644 --- a/Gemfile +++ b/Gemfile @@ -27,6 +27,7 @@ gem 'paper_trail' # for upload management gem 'carrierwave' gem 'carrierwave-bombshelter' +gem 'mimemagic' gem 'mini_magick' # for internationalizing diff --git a/Gemfile.lock b/Gemfile.lock index 38978051..10c6a0bf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -171,11 +171,15 @@ GEM railties (>= 3.0.0) faker (1.9.3) i18n (>= 0.7) - faraday (1.3.0) + faraday (1.4.1) + faraday-excon (~> 1.1) faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.1) multipart-post (>= 1.2, < 3) - ruby2_keywords + ruby2_keywords (>= 0.0.4) + faraday-excon (1.1.0) faraday-net_http (1.0.1) + faraday-net_http_persistent (1.1.0) fastimage (2.1.4) feature (1.4.0) ffi (1.10.0) @@ -293,7 +297,7 @@ GEM mime-types (3.3.1) mime-types-data (~> 3.2015) mime-types-data (3.2021.0225) - mimemagic (0.4.3) + mimemagic (0.3.10) nokogiri (~> 1) rake mina (1.2.3) @@ -301,7 +305,7 @@ GEM rake mini_magick (4.9.5) mini_mime (1.0.3) - mini_portile2 (2.5.0) + mini_portile2 (2.5.1) minitest (5.14.4) momentjs-rails (2.20.1) railties (>= 3.1) @@ -320,7 +324,7 @@ GEM nenv (0.3.0) netrc (0.11.0) nio4r (2.5.7) - nokogiri (1.11.2) + nokogiri (1.11.3) mini_portile2 (~> 2.5.0) racc (~> 1.4) notiffany (0.1.1) @@ -332,7 +336,7 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) - octokit (4.20.0) + octokit (4.21.0) faraday (>= 0.9) sawyer (~> 0.8.0, >= 0.5.3) omniauth (1.9.1) @@ -497,7 +501,7 @@ GEM mime-types (>= 1.16, < 4.0) netrc (~> 0.8) rexml (3.2.5) - rolify (5.3.0) + rolify (5.2.0) rqrcode (0.10.1) chunky_png (~> 1.0) rspec (3.6.0) @@ -637,7 +641,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.5) - unicode-display_width (1.6.0) + unicode-display_width (1.6.1) unicode_utils (1.4.0) unobtrusive_flash (3.3.1) railties @@ -719,6 +723,7 @@ DEPENDENCIES leaflet-rails letter_opener letter_opener_web (~> 1.0) + mimemagic mina mini_magick money-rails diff --git a/README.md b/README.md index 1a549b45..9bb3cff4 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,3 @@ -### Spring 2021 Student Project -[![Build Status](https://travis-ci.com/CactusPuppy/snapcon.svg?branch=master)](https://travis-ci.com/CactusPuppy/snapcon) -[![Maintainability](https://api.codeclimate.com/v1/badges/6c7fc446b3b10866ba71/maintainability)](https://codeclimate.com/github/CactusPuppy/snapcon/maintainability) -[![Test Coverage](https://api.codeclimate.com/v1/badges/6c7fc446b3b10866ba71/test_coverage)](https://codeclimate.com/github/CactusPuppy/snapcon/test_coverage) -[![Security Status](https://hakiri.io/github/CactusPuppy/snapcon/master.svg)](https://hakiri.io/github/CactusPuppy/snapcon/master) -[![Depfu](https://badges.depfu.com/badges/16eb1ffb3a9f1a36c4e595a5ae2a1dca/overview.svg)](https://depfu.com/github/CactusPuppy/snapcon?project_id=22682) -[![Bluejay Dashboard](https://img.shields.io/badge/Bluejay-Dashboard_5-blue.svg)](http://dashboard.bluejay.governify.io/dashboard/script/dashboardLoader.js?dashboardURL=https://reporter.bluejay.governify.io/api/v4/dashboards/tpa-CS169L-GH-CactusPuppy_snapcon/main) - - - Deploy - - - - Pivotal Tracker - - ---- - [![Build Status](https://travis-ci.com/snap-cloud/snapcon.svg?branch=master)](https://travis-ci.com/snap-cloud/snapcon) [![Maintainability](https://api.codeclimate.com/v1/badges/b7b0d559a03bf218663a/maintainability)](https://codeclimate.com/github/snap-cloud/snapcon/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/b7b0d559a03bf218663a/test_coverage)](https://codeclimate.com/github/snap-cloud/snapcon/test_coverage) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 6548a3fe..c5b49ae7 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -55,6 +55,8 @@ //= require bootstrap-select //= require osem-survey //= require pagy +//= require fullcalendar-scheduler/main.js +//= require fullcalendar $(document).ready(function() { $('a[disabled=disabled]').click(function(event){ diff --git a/app/assets/javascripts/fullcalendar.js.erb b/app/assets/javascripts/fullcalendar.js.erb new file mode 100644 index 00000000..22ee857d --- /dev/null +++ b/app/assets/javascripts/fullcalendar.js.erb @@ -0,0 +1,28 @@ +$( document ).ready(function() { + let calendarEl = document.getElementById('vert-schedule-full-calendar'); + if (!calendarEl) return; //check that we need a vertical schedule + let fullcalData = $('#fullcalendar'); + + let license_key = "<%= Rails.configuration.fullcalendar[:license_key] %>"; + + var calendar = new FullCalendar.Calendar(calendarEl, { + schedulerLicenseKey: license_key, + nowIndicator: true, + now: fullcalData.data('now'), + expandRows: true, + allDaySlot: false, + slotMinTime: fullcalData.data('startHour') + ':00:00', + slotMaxTime: fullcalData.data('endHour') + ':00:00', + validRange: { + start: fullcalData.data('startDate'), + end: fullcalData.data('endDate') + }, + timeZone: fullcalData.data('timezone'), + initialDate: fullcalData.data('day'), + initialView: 'resourceTimeGridDay', + resources: fullcalData.data('rooms'), + events: fullcalData.data('events') + }); + + calendar.render(); +}); diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index f5c58982..520eedbe 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -26,4 +26,6 @@ *= require osem-navbar *= require mastodon *= require conferences + + *= require fullcalendar-scheduler/main.css */ diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb index 35e6b709..cfd7eafc 100644 --- a/app/controllers/conferences_controller.rb +++ b/app/controllers/conferences_controller.rb @@ -40,7 +40,7 @@ class ConferencesController < ApplicationController @image_url = "#{request.protocol}#{request.host}#{@conference.picture}" - if splashpage.include_cfp + if splashpage.include_cfp? cfps = @conference.program.cfps @call_for_events = cfps.find { |call| call.cfp_type == 'events' } if @call_for_events.try(:open?) @@ -50,31 +50,27 @@ class ConferencesController < ApplicationController @call_for_tracks = cfps.find { |call| call.cfp_type == 'tracks' } @call_for_booths = cfps.find { |call| call.cfp_type == 'booths' } end - if splashpage.include_program + if splashpage.include_program? @highlights = @conference.highlighted_events.eager_load(:speakers) - if splashpage.include_tracks + if splashpage.include_tracks? @tracks = @conference.confirmed_tracks.eager_load( :room ).order('tracks.name') end - if splashpage.include_booths + if splashpage.include_booths? @booths = @conference.confirmed_booths.order('title') end - if splashpage.include_happening_now - events_schedules_list = get_happening_now_events_schedules(@conference) - @events_schedules_limit = EVENTS_PER_PAGE - @events_schedules_length = events_schedules_list.length - @pagy, @events_schedules = pagy_array(events_schedules_list, items: @events_schedules_limit, link_extra: 'data-remote="true"') - @happening_now_url = happening_now_conference_schedule_path(conference_id: @conference.short_title, format: :json) + if splashpage.include_happening_now? + load_happening_now end end - if splashpage.include_registrations || splashpage.include_tickets + if splashpage.include_registrations? || splashpage.include_tickets? @tickets = @conference.tickets.visible.order('price_cents') end - if splashpage.include_lodgings + if splashpage.include_lodgings? @lodgings = @conference.lodgings.order('id') end - if splashpage.include_sponsors + if splashpage.include_sponsors? @sponsorship_levels = @conference.sponsorship_levels.eager_load( :sponsors ).order('sponsorship_levels.position ASC', 'sponsors.name') diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index 87636b2c..92625ea9 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true +EVENTS_PER_PAGE = Rails.configuration.conference[:events_per_page] + class ProposalsController < ApplicationController + include ConferenceHelper before_action :authenticate_user!, except: [:show, :new, :create] load_resource :conference, find_by: :short_title load_resource :program, through: :conference, singleton: true @@ -19,6 +22,7 @@ class ProposalsController < ApplicationController @event_schedule = @event.event_schedules.find_by(schedule_id: @program.selected_schedule_id) @speakers_ordered = @event.speakers_ordered @surveys_after_event = @event.surveys.after_event.select(&:active?) + load_happening_now end def new diff --git a/app/controllers/schedules_controller.rb b/app/controllers/schedules_controller.rb index bdefb939..e0c5f911 100644 --- a/app/controllers/schedules_controller.rb +++ b/app/controllers/schedules_controller.rb @@ -78,6 +78,25 @@ class SchedulesController < ApplicationController end end + def vertical_schedule + dates = @conference.start_date..@conference.end_date + # the schedule takes you to today if it is a date of the schedule + current_day = @conference.current_conference_day + @day = current_day.present? ? current_day : dates.first + event_schedules = @program.selected_event_schedules( + includes: [{ event: %i[event_type speakers submitter] }] + ) + + unless event_schedules + redirect_to events_conference_schedule_path(@conference.short_title) + return + end + + @rooms = FullCalendarFormatter.rooms_to_resources(@conference.venue.rooms) if @conference.venue + @event_schedules = FullCalendarFormatter.event_schedules_to_resources(event_schedules) + @now = Time.now.in_time_zone(@conference.timezone).strftime('%FT%T%:z') + end + def app @qr_code = RQRCode::QRCode.new(conference_schedule_url).as_svg(offset: 20, color: '000', shape_rendering: 'crispEdges', module_size: 11) end diff --git a/app/helpers/conference_helper.rb b/app/helpers/conference_helper.rb index cf2c06ce..4240ad54 100644 --- a/app/helpers/conference_helper.rb +++ b/app/helpers/conference_helper.rb @@ -79,10 +79,42 @@ module ConferenceHelper end def get_happening_now_events_schedules(conference) - events_schedules = conference.program.selected_event_schedules( - includes: [:room, { event: %i[track event_type speakers submitter] }] - ).select(&:happening_now?) + events_schedules = filter_events_schedules(conference, :happening_now?) events_schedules ||= [] events_schedules end + + def get_happening_next_events_schedules(conference) + events_schedules = filter_events_schedules(conference, :happening_later?) + + if events_schedules.empty? + return [] + end + + # events_schedules have been sorted by start_time in selected_event_schedules + happening_next_time = events_schedules[0].start_time + events_schedules = events_schedules.select { |s| s.start_time == happening_next_time } + + events_schedules + end + + def load_happening_now + events_schedules_list = get_happening_now_events_schedules(@conference) + @is_happening_next = false + if events_schedules_list.empty? + events_schedules_list = get_happening_next_events_schedules(@conference) + @is_happening_next = true + end + @events_schedules_limit = EVENTS_PER_PAGE + @events_schedules_length = events_schedules_list.length + @pagy, @events_schedules = pagy_array(events_schedules_list, items: @events_schedules_limit, link_extra: 'data-remote="true"') + end + + private + + def filter_events_schedules(conference, filter) + conference.program.selected_event_schedules( + includes: [:room, { event: %i[track event_type speakers submitter] }] + ).select(&filter) + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index ec17e14c..68c83066 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -31,7 +31,7 @@ class Ability event.state == 'confirmed' end - can [:show, :events, :happening_now, :app], Schedule do |schedule| + can [:show, :events, :happening_now, :app, :vertical_schedule], Schedule do |schedule| schedule.program.schedule_public end diff --git a/app/models/event_schedule.rb b/app/models/event_schedule.rb index 036a89f2..7be7a7d2 100644 --- a/app/models/event_schedule.rb +++ b/app/models/event_schedule.rb @@ -70,6 +70,14 @@ class EventSchedule < ApplicationRecord event_time_range.overlaps?(now_range) end + def happening_later? + # TODO: Save start_time with local timezone info when making an event schedule + in_tz_start = start_time.in_time_zone(timezone) + in_tz_start -= in_tz_start.utc_offset + + in_tz_start >= Time.now + end + def self.withdrawn_or_canceled_event_schedules(schedule_ids) EventSchedule .unscoped @@ -125,6 +133,14 @@ class EventSchedule < ApplicationRecord other.schedule_id == schedule_id end + def start_time_in_conference_timezone + time_in_conference_timezone(start_time) + end + + def end_time_in_conference_timezone + time_in_conference_timezone(end_time) + end + private def replaced_event_schedules @@ -179,4 +195,10 @@ class EventSchedule < ApplicationRecord errors.add(:schedule, "must be one of #{event.track.name} track's schedules") unless event.track.schedules.include?(schedule) end + + def time_in_conference_timezone(time) + time_in_tz = time.in_time_zone(timezone) + time_in_tz -= time_in_tz.utc_offset + time_in_tz + end end diff --git a/app/services/full_calendar_formatter.rb b/app/services/full_calendar_formatter.rb new file mode 100644 index 00000000..499b8e04 --- /dev/null +++ b/app/services/full_calendar_formatter.rb @@ -0,0 +1,42 @@ +class FullCalendarFormatter + def self.rooms_to_resources(rooms) + rooms.map { |room| room_to_resource(room) }.to_json + end + + def self.event_schedules_to_resources(event_schedules) + return '[]' if event_schedules.empty? + + conference = event_schedules.first.schedule.program.conference + event_schedules.map { |event_schedule| event_schedule_to_resource(conference, event_schedule) }.to_json + end + + class << self + include FormatHelper + + private + + def room_to_resource(room) + { + id: room.guid, + title: room.name + } + end + + def event_schedule_to_resource(conference, event_schedule) + event_type_color = event_schedule.event.event_type.color + url = Rails.application.routes.url_helpers.conference_program_proposal_path(conference.short_title, event_schedule.event.id) + + { + id: event_schedule.event.guid, + title: event_schedule.event.title, + start: event_schedule.start_time_in_conference_timezone, + end: event_schedule.end_time_in_conference_timezone, + resourceId: event_schedule.room.guid, + url: url, + borderColor: event_type_color, + backgroundColor: event_type_color, + textColor: contrast_color(event_type_color) + } + end + end +end diff --git a/app/views/admin/commercials/index.html.haml b/app/views/admin/commercials/index.html.haml index 2b91c1a6..d318707c 100644 --- a/app/views/admin/commercials/index.html.haml +++ b/app/views/admin/commercials/index.html.haml @@ -1,7 +1,7 @@ .row .col-md-12 .page-header - %h1 Session Materials + %h1 #{@conference.title} Materials %p.text-muted Conference materials will be displayed on the events in the = link_to 'schedule,', conference_schedule_path(@conference.short_title) diff --git a/app/views/admin/emails/_help.html.haml b/app/views/admin/emails/_help.html.haml index d62589b0..1222c1ac 100644 --- a/app/views/admin/emails/_help.html.haml +++ b/app/views/admin/emails/_help.html.haml @@ -57,7 +57,7 @@ - if @conference.program.schedule_public %td {schedule_link} %td The link to complete schedule of the conference - - if @conference.splashpage && @conference.splashpage.public + - if @conference.splashpage && @conference.splashpage.public? %tr %td {conference_splash_link} %td The link to conference splash page diff --git a/app/views/admin/splashpages/show.html.haml b/app/views/admin/splashpages/show.html.haml index 08edb140..e3732b89 100644 --- a/app/views/admin/splashpages/show.html.haml +++ b/app/views/admin/splashpages/show.html.haml @@ -49,7 +49,7 @@ %i{ class: "fa-li #{icon_for_todo @splashpage.include_social_media?}" } Display social media links %li - - if @conference.splashpage && @conference.splashpage.public + - if @conference.splashpage && @conference.splashpage.public? %i{ class: "fa-li #{icon_for_todo @splashpage.public?}" } %text-muted.publicorprivate Public - else diff --git a/app/views/conferences/_about_and_happening_now.haml b/app/views/conferences/_about_and_happening_now.haml index 1bb1adae..3b327c4f 100644 --- a/app/views/conferences/_about_and_happening_now.haml +++ b/app/views/conferences/_about_and_happening_now.haml @@ -3,7 +3,8 @@ = render 'happening_now', conference: conference, events_schedules: events_schedules, pagy: pagy, events_schedules_length: events_schedules_length, - events_schedules_limit: events_schedules_limit + events_schedules_limit: events_schedules_limit, + is_happening_next: is_happening_next = content_for :about do #about @@ -14,9 +15,9 @@ .container .row -# happening now events are displayed second in md or lg view - - if conference.splashpage.include_happening_now && conference.splashpage.include_program + - if conference.splashpage.include_happening_now? && conference.splashpage.include_program? - if conference.description.present? - .col-md-6.col-md-push-6.col-lg-4.col-lg-push-8 + .col-md-6.col-md-push-6.col-lg-4.col-lg-push-8{ style: 'margin-top: 60px' } = yield :happening_now - else .col-md-12 diff --git a/app/views/conferences/_happening_now.haml b/app/views/conferences/_happening_now.haml index 66355a82..ccf0181f 100644 --- a/app/views/conferences/_happening_now.haml +++ b/app/views/conferences/_happening_now.haml @@ -1,12 +1,15 @@ -- if conference.splashpage.include_program && conference.splashpage.include_happening_now - - if events_schedules.any? - .row - %h2.text-center{ style: 'margin-bottom:30px' } Happening Now - - events_schedules.each do |event_schedule| - = render 'schedules/event', conference: conference, event_schedule: event_schedule, event: event_schedule.event, is_brief: true - - if events_schedules_length > events_schedules_limit - .container{ style: 'width:100%; text-align:center' } - != pagy_bootstrap_nav_js(pagy) - - else - .row - %h3.text-center There are no events happening now. +- if events_schedules.present? && events_schedules.any? + .row + %h3.text-left{ style: 'margin-bottom:30px; padding-left:20px' } + - if is_happening_next + Events Happening Next + - else + Events Happening Now + - events_schedules.each do |event_schedule| + = render 'schedules/event', conference: conference, event_schedule: event_schedule, event: event_schedule.event, is_brief: true + - if events_schedules_length > events_schedules_limit + .container{ style: 'width:100%; text-align:center' } + != pagy_bootstrap_nav_js(pagy) +- else + .row + %h3.text-center There are no events scheduled yet. diff --git a/app/views/conferences/_header.haml b/app/views/conferences/_header.haml index 9a1316f6..4ee7123c 100644 --- a/app/views/conferences/_header.haml +++ b/app/views/conferences/_header.haml @@ -1,6 +1,11 @@ - cache [conference, venue, '#splash#header'] do #banner{ style: ("background-image: url(#{conference.picture_url})" if conference.picture_url) } .container + - if user_signed_in? + - if current_user.ticket_purchases.by_conference(@conference).empty? + .alert.bg-info{ :role => "alert", :style => "margin-bottom: 100px" } + You have not purchased any tickets for this conference yet. + = link_to "Book your tickets now!", "#tickets" .row .col-md-6.col-md-offset-3{ id: (conference.picture? ? "header-image" : "header-no-image") } .row diff --git a/app/views/conferences/show.html.haml b/app/views/conferences/show.html.haml index 3a73c0b0..ed642f34 100644 --- a/app/views/conferences/show.html.haml +++ b/app/views/conferences/show.html.haml @@ -49,21 +49,22 @@ = render 'about_and_happening_now', conference: @conference, events_schedules: @events_schedules, pagy: @pagy, events_schedules_length: @events_schedules_length, - events_schedules_limit: @events_schedules_limit + events_schedules_limit: @events_schedules_limit, + is_happening_next: @is_happening_next -# calls for content, or program - - if @conference.splashpage.include_cfp + - if @conference.splashpage.include_cfp? = render 'call_for_content', conference: @conference, call_for_events: @call_for_events, call_for_tracks: @call_for_tracks, call_for_booths: @call_for_booths, event_types: @event_types, tracks: @track_names - - if @conference.splashpage.include_program + - if @conference.splashpage.include_program? = render 'program', conference: @conference, tracks: @tracks, highlights: @highlights, booths: @booths -# attendance/registration - - if @conference.splashpage.include_registrations + - if @conference.splashpage.include_registrations? - if @conference.registration_open? = render 'registration', conference: @conference, registration_period: @conference.registration_period, @@ -72,20 +73,20 @@ = render 'tickets', conference: @conference, tickets: @tickets -# geo - - if @conference.splashpage.include_venue && @conference.venue + - if @conference.splashpage.include_venue? && @conference.venue = render 'venue', conference: @conference, venue: @conference.venue, commercial: @conference.venue.commercial - if @conference.splashpage.include_lodgings && @conference.lodgings.any? = render 'lodging', venue: @conference.venue, lodgings: @lodgings -# sponsorship - - if @conference.splashpage.include_sponsors + - if @conference.splashpage.include_sponsors? = render 'sponsors', conference: @conference, sponsorship_levels: @sponsorship_levels, sponsors: @sponsors -# footer - - if @conference.splashpage.include_social_media + - if @conference.splashpage.include_social_media? - if @conference.contact.has_social_media? = render 'social_media', contact: @conference.contact = render 'footer' diff --git a/app/views/conferences/show.js.erb b/app/views/conferences/show.js.erb index fb35b420..61c0a3a3 100644 --- a/app/views/conferences/show.js.erb +++ b/app/views/conferences/show.js.erb @@ -1,5 +1,6 @@ $('#happening-now').html("<%= j(render 'happening_now', conference: @conference, events_schedules: @events_schedules, pagy: @pagy, events_schedules_length: @events_schedules_length, - events_schedules_limit: @events_schedules_limit)%>"); + events_schedules_limit: @events_schedules_limit, + is_happening_next: @is_happening_next) %>"); Pagy.init(document.getElementById('happening-now')); diff --git a/app/views/proposals/show.html.haml b/app/views/proposals/show.html.haml index 9d5cfe73..432757c2 100644 --- a/app/views/proposals/show.html.haml +++ b/app/views/proposals/show.html.haml @@ -25,9 +25,10 @@ .col-md-4 %p{style: "margin-top: 20px"} - if @event_schedule&.start_time - = link_to google_calendar_link(@event_schedule), target: '_blank', class: 'btn btn-success btn-mini' do - %i.fa.fa-calendar - Add to Google Calendar (beta) + - if user_signed_in? + = link_to google_calendar_link(@event_schedule), target: '_blank', class: 'btn btn-success btn-mini' do + %i.fa.fa-calendar + Add to Google Calendar (beta) - if can?(:update, @event) && @event.require_registration? = link_to 'Registrations', registrations_conference_program_proposal_path(@conference.short_title, @event), class: 'btn btn-mini btn-success' - if can?(:edit, @event) @@ -51,7 +52,7 @@ - @event.volunteers.each do |volunteer| = render 'volunteer_info', speaker: volunteer, show_bio: false - .col-md-9 + .col-md-6 .row .col-md-12 .lead @@ -138,6 +139,13 @@ - if @surveys_after_event.any? && @event.ended? .page-header = render partial: 'surveys/list', locals: { surveys: @surveys_after_event, conference: @conference } + .col-md-3 + #happening-now + = render 'conferences/happening_now', conference: @conference, + events_schedules: @events_schedules, pagy: @pagy, + events_schedules_length: @events_schedules_length, + events_schedules_limit: @events_schedules_limit, + is_happening_next: @is_happening_next - if @event.committee_review.present? || @event.submission_text.present? && can?(:edit, @event) %hr diff --git a/app/views/proposals/show.js.erb b/app/views/proposals/show.js.erb new file mode 100644 index 00000000..53220cb8 --- /dev/null +++ b/app/views/proposals/show.js.erb @@ -0,0 +1,6 @@ +$('#happening-now').html("<%= j(render 'conferences/happening_now', conference: @conference, + events_schedules: @events_schedules, pagy: @pagy, + events_schedules_length: @events_schedules_length, + events_schedules_limit: @events_schedules_limit, + is_happening_next: @is_happening_next) %>"); +Pagy.init(document.getElementById('happening-now')); diff --git a/app/views/schedules/vertical_schedule.haml b/app/views/schedules/vertical_schedule.haml new file mode 100644 index 00000000..9e5ce379 --- /dev/null +++ b/app/views/schedules/vertical_schedule.haml @@ -0,0 +1,12 @@ +.container#program + = render 'schedule_tabs', active: 'vertical_schedule' + + %h1.text-center + Vertical Schedule for + = @conference.title + + #vert-schedule-full-calendar + #fullcalendar{ data: { rooms: @rooms, events: @event_schedules, day: @day, + 'start-hour': @conference.start_hour, 'end-hour': @conference.end_hour, + 'start-date': @conference.start_date, 'end-date': @conference.end_date + 1.day, + 'timezone': @conference.timezone, 'now': @now } } diff --git a/config/application.rb b/config/application.rb index 4e5e9b87..320e4fd3 100644 --- a/config/application.rb +++ b/config/application.rb @@ -80,5 +80,11 @@ module Osem end end end + + config.assets.paths << Rails.root.join('node_modules') + + config.fullcalendar = { + license_key: ENV['FULL_CALENDAR_LICENSE_KEY'] + } end end diff --git a/config/routes.rb b/config/routes.rb index 1c31e0d0..a901509e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -202,6 +202,7 @@ Osem::Application.routes.draw do member do get :events get :happening_now + get :vertical_schedule end end end diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..88e0e172 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,11 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "fullcalendar-scheduler": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/fullcalendar-scheduler/-/fullcalendar-scheduler-5.6.0.tgz", + "integrity": "sha512-0jx/Q+6QqzvIGWU/52OE27UVjVAiM9E0o/qRwRcEhyuKlL54ypTF/yXP/wo0ctUtui5dDsilGNsSsba1xmARJA==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..d8f5ba1a --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "fullcalendar-scheduler": "^5.6.0" + } +} diff --git a/spec/controllers/schedules_controller_spec.rb b/spec/controllers/schedules_controller_spec.rb index 22cb7c1a..dbf9e38b 100644 --- a/spec/controllers/schedules_controller_spec.rb +++ b/spec/controllers/schedules_controller_spec.rb @@ -72,4 +72,20 @@ describe SchedulesController do end end end + + describe 'GET #vertical_schedule' do + let!(:program) { conference.program } + + context 'as a conference participant' do + context 'who visits the schedule page' do + before(:each) do + get :vertical_schedule, params: { conference_id: conference.short_title } + end + + it 'returns a successful response' do + expect(response.status).to eq(200) + end + end + end + end end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 75af7370..557d777f 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -80,25 +80,6 @@ FactoryBot.define do last_sign_in_at { Date.today } is_disabled { false } - after(:build) do |user| - url_mailbluster = 'https://api.mailbluster.com/api/leads/' - response_body = "{ - \"message\": \"Lead created\", - \"lead\": { - \"id\": 329395, - \"firstName\": \"#{user.name}\", - \"lastName\": \"\", - \"fullName\": \"#{user.name}\", - \"email\": \"#{user.email}\", - \"subscribed\": true, - \"tags\": [ - #{ENV['OSEM_NAME'] || 'snapcon'} - ], - } - }" - WebMock.stub_request(:post, url_mailbluster) - .to_return(body: response_body, status: 200) - end # Called by every user creation after(:create) do |user| diff --git a/spec/features/commercials_spec.rb b/spec/features/commercials_spec.rb index d6fc7f38..c9b30a6e 100644 --- a/spec/features/commercials_spec.rb +++ b/spec/features/commercials_spec.rb @@ -10,11 +10,18 @@ feature Commercial do let!(:participant) { create(:user) } context 'in admin area' do - scenario 'adds, updates, deletes of a conference', feature: true, js: true do + + before do sign_in organizer - visit admin_conference_commercials_path(conference.short_title) + end + scenario 'contains the conference name in the title', feature: true, js: true do + header = conference.title + ' Materials' + expect(page).to have_content(header) + end + + scenario 'adds, updates, deletes of a conference', feature: true, js: true do # Create valid commercial fill_in 'commercial_url', with: 'https://www.youtube.com/watch?v=M9bq_alk-sw' click_button 'Save Materials' diff --git a/spec/features/proposals_spec.rb b/spec/features/proposals_spec.rb index e7377bb2..856d28c9 100644 --- a/spec/features/proposals_spec.rb +++ b/spec/features/proposals_spec.rb @@ -212,4 +212,128 @@ feature Event do expect(page.find('#event_submission_text').value).to eq(event_type.submission_instructions) end end + + context 'as a user, looking at a conference with scheduled events' do + before(:each) do + @program = conference.program + @selected_schedule = create(:schedule, program: @program) + @program.update_attributes!(selected_schedule: @selected_schedule) + @scheduled_event1 = create(:event, program: @program, state: 'confirmed', abstract: '`markdown`') + @event_schedule1 = create(:event_schedule, event: @scheduled_event1, schedule: @selected_schedule, start_time: conference.start_hour + 1.hour) + end + + scenario 'for a scheduled event, can add an event to google calendar if signed in', feature: true do + sign_in participant + visit conference_program_proposal_path(conference.short_title, @scheduled_event1.id) + expect(page).to have_content('Add to Google Calendar (beta)') + end + + scenario 'for a scheduled event, cannot add an event to google calendar if not signed on', feature: true do + visit conference_program_proposal_path(conference.short_title, @scheduled_event1.id) + expect(page).not_to have_content('Add to Google Calendar (beta)') + end + end + + context 'happening now or next section' do + let!(:conference1) { create(:full_conference, start_date: 1.day.ago, end_date: 7.days.from_now, start_hour: 0, end_hour: 24) } + let!(:program) { conference1.program } + let!(:selected_schedule) { create(:schedule, program: program) } + let!(:splashpage) { create(:full_splashpage, conference: conference1, public: true) } + + let!(:scheduled_event1) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed') + end + let!(:scheduled_event2) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed') + end + let!(:scheduled_event3) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed') + end + let!(:scheduled_event4) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed') + end + let!(:current_time) { Time.now.in_time_zone(conference1.timezone) } + + let!(:events_list) { [scheduled_event1, scheduled_event2, scheduled_event3, scheduled_event4] } + + before :each do + sign_in participant + end + + scenario 'No events happening now or next' do + events_list.each do |event| + visit conference_program_proposal_path(conference1.short_title, event.id) + happening_now = page.find('#happening-now') + expect(happening_now).to have_content('There are no events scheduled yet.') + end + end + + scenario 'shows all events happening next if nothing is happening now' do + event_schedule1 = create(:event_schedule, event: scheduled_event1, schedule: selected_schedule, start_time: (current_time + 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule2 = create(:event_schedule, event: scheduled_event2, schedule: selected_schedule, start_time: (current_time + 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) + + events_list.each do |event| + visit conference_program_proposal_path(conference1.short_title, event.id) + happening_now = page.find('#happening-now') + expect(happening_now).to have_content(event_schedule1.event.title) + expect(happening_now).to have_content(event_schedule2.event.title) + expect(happening_now).not_to have_content(scheduled_event3.title) + expect(happening_now).not_to have_content(scheduled_event4.title) + end + end + + scenario 'only shows all events happening now if something is happening now and next' do + event_schedule1 = create(:event_schedule, event: scheduled_event1, schedule: selected_schedule, start_time: (current_time + 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule2 = create(:event_schedule, event: scheduled_event2, schedule: selected_schedule, start_time: (current_time + 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule3 = create(:event_schedule, event: scheduled_event3, schedule: selected_schedule, start_time: current_time.strftime('%a, %d %b %Y %H:%M:%S')) + events_list.each do |event| + visit conference_program_proposal_path(conference1.short_title, event.id) + happening_now = page.find('#happening-now') + expect(happening_now).not_to have_content(event_schedule1.event.title) + expect(happening_now).not_to have_content(event_schedule2.event.title) + expect(happening_now).to have_content(event_schedule3.event.title) + expect(happening_now).not_to have_content(scheduled_event4.title) + end + end + + scenario 'only shows events happening at the earliest time, not at a later time in the future' do + event_schedule1 = create(:event_schedule, event: scheduled_event1, schedule: selected_schedule, start_time: (current_time + 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule2 = create(:event_schedule, event: scheduled_event2, schedule: selected_schedule, start_time: (current_time + 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule3 = create(:event_schedule, event: scheduled_event3, schedule: selected_schedule, start_time: (current_time + 2.hours).strftime('%a, %d %b %Y %H:%M:%S')) + events_list.each do |event| + visit conference_program_proposal_path(conference1.short_title, event.id) + happening_now = page.find('#happening-now') + expect(happening_now).to have_content(event_schedule1.event.title) + expect(happening_now).to have_content(event_schedule2.event.title) + expect(happening_now).not_to have_content(event_schedule3.event.title) + expect(happening_now).not_to have_content(scheduled_event4.title) + end + end + + scenario 'only shows 3 events happening now because of pagination' do + event_schedule1 = create(:event_schedule, event: scheduled_event1, schedule: selected_schedule, start_time: current_time.strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule2 = create(:event_schedule, event: scheduled_event2, schedule: selected_schedule, start_time: current_time.strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule3 = create(:event_schedule, event: scheduled_event3, schedule: selected_schedule, start_time: current_time.strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule4 = create(:event_schedule, event: scheduled_event4, schedule: selected_schedule, start_time: current_time.strftime('%a, %d %b %Y %H:%M:%S')) + + events_list.each do |event| + visit conference_program_proposal_path(conference1.short_title, event.id) + happening_now = page.find('#happening-now') + expect(happening_now).to have_content(event_schedule1.event.title) + expect(happening_now).to have_content(event_schedule2.event.title) + expect(happening_now).to have_content(event_schedule3.event.title) + + visit conference_program_proposal_path(conference1.short_title, event.id, page: 2) + happening_now = page.find('#happening-now') + expect(happening_now).not_to have_content(event_schedule3.event.title) + expect(happening_now).not_to have_content(event_schedule1.event.title) + expect(happening_now).not_to have_content(event_schedule2.event.title) + expect(happening_now).to have_content(event_schedule4.event.title) + end + end + end end diff --git a/spec/features/splashpage_spec.rb b/spec/features/splashpage_spec.rb index 69e1c6ae..d5810ca7 100644 --- a/spec/features/splashpage_spec.rb +++ b/spec/features/splashpage_spec.rb @@ -80,4 +80,127 @@ feature Splashpage do end end end + + context 'happening now section', feature: true, js: true do + let!(:conference2) { create(:full_conference, start_date: 1.day.ago, end_date: 7.days.from_now, start_hour: 0, end_hour: 24) } + let!(:program) { conference2.program } + let!(:selected_schedule) { create(:schedule, program: program) } + let!(:splashpage) { create(:full_splashpage, conference: conference2, public: true)} + + let!(:scheduled_event1) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed', abstract: '`markdown`') + end + let!(:scheduled_event2) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed') + end + let!(:scheduled_event3) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed') + end + let!(:scheduled_event4) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed') + end + let!(:current_time) { Time.now.in_time_zone(conference2.timezone) } + + before :each do + sign_in participant + end + + scenario 'displays \'There are no events scheduled yet.\' if nothing is happening now and next' do + visit conference_path(conference2.short_title) + happening_now = page.find('#happening-now') + expect(happening_now).to have_content('There are no events scheduled yet.') + end + + scenario 'shows all events happening next if nothing is happening now' do + event_schedule1 = create(:event_schedule, event: scheduled_event1, schedule: selected_schedule, start_time: (current_time + 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule2 = create(:event_schedule, event: scheduled_event2, schedule: selected_schedule, start_time: (current_time + 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) + visit conference_path(conference2.short_title) + happening_now = page.find('#happening-now') + expect(happening_now).to have_content(event_schedule1.event.title) + expect(happening_now).to have_content(event_schedule2.event.title) + end + + scenario 'only shows all events happening now if something is happening now and next' do + event_schedule1 = create(:event_schedule, event: scheduled_event1, schedule: selected_schedule, start_time: (current_time + 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule2 = create(:event_schedule, event: scheduled_event2, schedule: selected_schedule, start_time: (current_time + 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule3 = create(:event_schedule, event: scheduled_event3, schedule: selected_schedule, start_time: current_time.strftime('%a, %d %b %Y %H:%M:%S')) + visit conference_path(conference2.short_title) + happening_now = page.find('#happening-now') + expect(happening_now).to have_content(event_schedule3.event.title) + expect(happening_now).not_to have_content(event_schedule1.event.title) + expect(happening_now).not_to have_content(event_schedule2.event.title) + end + + scenario 'only shows events happening at the earliest time, not at a later time in the future' do + event_schedule1 = create(:event_schedule, event: scheduled_event1, schedule: selected_schedule, start_time: (current_time + 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule2 = create(:event_schedule, event: scheduled_event2, schedule: selected_schedule, start_time: (current_time + 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule3 = create(:event_schedule, event: scheduled_event3, schedule: selected_schedule, start_time: (current_time + 2.hours).strftime('%a, %d %b %Y %H:%M:%S')) + visit conference_path(conference2.short_title) + happening_now = page.find('#happening-now') + expect(happening_now).to have_content(event_schedule1.event.title) + expect(happening_now).to have_content(event_schedule2.event.title) + expect(happening_now).not_to have_content(event_schedule3.event.title) + end + + scenario 'only shows 3 events happening now because of pagination' do + event_schedule1 = create(:event_schedule, event: scheduled_event1, schedule: selected_schedule, start_time: current_time.strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule2 = create(:event_schedule, event: scheduled_event2, schedule: selected_schedule, start_time: current_time.strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule3 = create(:event_schedule, event: scheduled_event3, schedule: selected_schedule, start_time: current_time.strftime('%a, %d %b %Y %H:%M:%S')) + event_schedule4 = create(:event_schedule, event: scheduled_event4, schedule: selected_schedule, start_time: current_time.strftime('%a, %d %b %Y %H:%M:%S')) + + visit conference_path(conference2.short_title) + happening_now = page.find('#happening-now') + expect(happening_now).to have_content(event_schedule1.event.title) + expect(happening_now).to have_content(event_schedule2.event.title) + expect(happening_now).to have_content(event_schedule3.event.title) + expect(happening_now).not_to have_content(event_schedule4.event.title) + + visit conference_path(conference2.short_title, page: 2) + happening_now = page.find('#happening-now') + expect(happening_now).to have_content(event_schedule4.event.title) + end + end + + context 'clarify registration status' do + let!(:splashpage) { create(:splashpage, conference: conference, public: true)} + let!(:ticket_1) { create(:ticket) } + let!(:free_ticket) { create(:ticket, price_cents: 0) } + + scenario 'user signed in with no tickets', feature: true do + sign_in participant + visit conference_path(conference.short_title) + expect(page).to have_content 'You have not purchased any tickets for this conference yet.' + end + + scenario 'user signed in with 1 free ticket', feature: true do + sign_in participant + create(:ticket_purchase, conference: conference, user: participant, ticket: free_ticket, quantity: 1) + visit conference_path(conference.short_title) + expect(page).not_to have_content 'You have not purchased any tickets for this conference yet.' + end + + scenario 'user signed in with 1 paid ticket', feature: true do + sign_in participant + create(:ticket_purchase, conference: conference, user: participant, ticket: ticket_1, quantity: 1) + visit conference_path(conference.short_title) + expect(page).not_to have_content 'You have not purchased any tickets for this conference yet.' + end + + scenario 'user signed in with multiple ticket', feature: true do + sign_in participant + create(:ticket_purchase, conference: conference, user: participant, ticket: ticket_1, quantity: 1) + create(:ticket_purchase, conference: conference, user: participant, ticket: free_ticket, quantity: 1) + visit conference_path(conference.short_title) + expect(page).not_to have_content 'You have not purchased any tickets for this conference yet.' + end + + scenario 'user not signed in', feature: true do + visit conference_path(conference.short_title) + expect(page).not_to have_content 'You have not purchased any tickets for this conference yet.' + end + end end diff --git a/spec/helpers/conference_helper_spec.rb b/spec/helpers/conference_helper_spec.rb index 4ea48496..5065411f 100644 --- a/spec/helpers/conference_helper_spec.rb +++ b/spec/helpers/conference_helper_spec.rb @@ -99,4 +99,39 @@ describe ConferenceHelper, type: :helper do expect(conference_color(conference2)).to eq('#0B3559') end end + + describe '#get_happening_next_events_schedules' do + let!(:conference2) { create(:full_conference, start_date: 1.day.ago, end_date: 7.days.from_now, start_hour: 0, end_hour: 24) } + let!(:program) { conference2.program } + let!(:selected_schedule) { create(:schedule, program: program) } + let!(:scheduled_event1) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed', abstract: '`markdown`') + end + let!(:current_time) { Time.now.in_time_zone(conference2.timezone) } + let!(:event_schedule1) { create(:event_schedule, event: scheduled_event1, schedule: selected_schedule, start_time: (current_time + 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) } + let!(:scheduled_event2) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed') + end + let!(:event_schedule2) { create(:event_schedule, event: scheduled_event2, schedule: selected_schedule, start_time: (current_time + 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) } + let!(:scheduled_event3) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed') + end + let!(:event_schedule3) { create(:event_schedule, event: scheduled_event3, schedule: selected_schedule, start_time: (current_time - 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) } + let!(:scheduled_event4) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed') + end + let!(:event_schedule4) { create(:event_schedule, event: scheduled_event4, schedule: selected_schedule, start_time: (current_time + 2.hours).strftime('%a, %d %b %Y %H:%M:%S')) } + + it 'returns all the events happening at the earliest time in the future but not later or in the past' do + events_schedules = get_happening_next_events_schedules(conference2) + expect(events_schedules).to include(event_schedule1) + expect(events_schedules).to include(event_schedule2) + expect(events_schedules).to_not include(event_schedule3) + expect(events_schedules).to_not include(event_schedule4) + end + end end diff --git a/spec/models/event_schedule_spec.rb b/spec/models/event_schedule_spec.rb index b160cf15..26cb5dff 100644 --- a/spec/models/event_schedule_spec.rb +++ b/spec/models/event_schedule_spec.rb @@ -165,4 +165,31 @@ describe EventSchedule do end end end + + describe 'happening_later' do + let!(:conference2) { create(:full_conference, start_date: 1.day.ago, end_date: 7.days.from_now, start_hour: 0, end_hour: 24) } + let!(:program) { conference2.program } + let!(:selected_schedule) { create(:schedule, program: program) } + let!(:scheduled_event1) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed', abstract: '`markdown`') + end + let!(:event_schedule1) { create(:event_schedule, event: scheduled_event1, schedule: selected_schedule, start_time: (Time.now.in_time_zone(conference2.timezone) + 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) } + let!(:scheduled_event2) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed') + end + let!(:event_schedule2) { create(:event_schedule, event: scheduled_event2, schedule: selected_schedule, start_time: (Time.now.in_time_zone(conference2.timezone) + 2.hour).strftime('%a, %d %b %Y %H:%M:%S')) } + let!(:scheduled_event3) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, state: 'confirmed') + end + let!(:event_schedule3) { create(:event_schedule, event: scheduled_event3, schedule: selected_schedule, start_time: (Time.now.in_time_zone(conference2.timezone) - 1.hour).strftime('%a, %d %b %Y %H:%M:%S')) } + + it 'returns true if the event is happening in the future' do + expect(event_schedule1.happening_later?).to be true + expect(event_schedule2.happening_later?).to be true + expect(event_schedule3.happening_later?).to be false + end + end end diff --git a/spec/services/full_calendar_formatter_spec.rb b/spec/services/full_calendar_formatter_spec.rb new file mode 100644 index 00000000..63af2d08 --- /dev/null +++ b/spec/services/full_calendar_formatter_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe FullCalendarFormatter do + let!(:room1) { create(:room) } + let!(:room2) { create(:room) } + let!(:rooms) { [room1, room2] } + let!(:conference) { create(:full_conference) } + let!(:program) { conference.program } + let!(:selected_schedule) { create(:schedule, program: program) } + let!(:event_type1) { create(:event_type, color: '#ffffff') } + let!(:event1) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, event_type: event_type1) + end + let!(:event_schedule1) { create(:event_schedule, event: event1, schedule: selected_schedule, room: room1) } + let!(:event_type2) { create(:event_type, color: '#000000') } + let!(:event2) do + program.update_attributes!(selected_schedule: selected_schedule) + create(:event, program: program, room: room2, event_type: event_type2) + end + let!(:event_schedule2) { create(:event_schedule, event: event2, schedule: selected_schedule, room: room2) } + let!(:event_schedules) { [event_schedule1, event_schedule2] } + + describe 'JSON formatting for FullCalendar' do + it 'translates rooms to resources' do + resources = described_class.rooms_to_resources(rooms) + expected_json = [ + { + id: room1.guid, + title: room1.name + }, + { + id: room2.guid, + title: room2.name + } + ].to_json + expect(resources).to eq(expected_json) + end + + it 'translates event schedules to resources' do + resources = described_class.event_schedules_to_resources(event_schedules) + expected_json = [ + { + id: event_schedule1.event.guid, + title: event_schedule1.event.title, + start: event_schedule1.start_time_in_conference_timezone, + end: event_schedule1.end_time_in_conference_timezone, + resourceId: room1.guid, + url: Rails.application.routes.url_helpers.conference_program_proposal_path(conference.short_title, event1.id), + borderColor: event1.event_type.color, + backgroundColor: event1.event_type.color, + textColor: 'black' + }, + { + id: event_schedule2.event.guid, + title: event_schedule2.event.title, + start: event_schedule2.start_time_in_conference_timezone, + end: event_schedule2.end_time_in_conference_timezone, + resourceId: room2.guid, + url: Rails.application.routes.url_helpers.conference_program_proposal_path(conference.short_title, event2.id), + borderColor: event2.event_type.color, + backgroundColor: event2.event_type.color, + textColor: 'white' + } + ].to_json + + expect(resources).to eq(expected_json) + end + end +end