Merge pull request #1278 from commandprompt/multiple_speakers_manual_event_mgmt_pr

Implements support for multiple speakers per event and adding events manually by conf admins
This commit is contained in:
Stella Rouzi 2017-04-14 16:23:02 +03:00 committed by GitHub
commit c31fea71a8
24 changed files with 180 additions and 89 deletions

View file

@ -193,6 +193,9 @@ gem 'stripe'
# Provides Sprockets implementation for Rails Asset Pipeline
gem 'sprockets-rails'
# for multiple speakers select on proposal/event forms
gem 'selectize-rails'
# Use guard and spring for testing in development
group :development do
# to launch specs when files are modified

View file

@ -459,6 +459,7 @@ GEM
sass (~> 3.2.2)
sprockets (~> 2.8, < 2.12)
sprockets-rails (~> 2.0)
selectize-rails (0.12.4)
shoulda-matchers (2.6.1)
activesupport (>= 3.0.0)
simplecov (0.11.2)
@ -621,6 +622,7 @@ DEPENDENCIES
rubocop
ruby-oembed
sass-rails (>= 4.0.2)
selectize-rails
shoulda-matchers
spring-commands-rspec
sprockets-rails
@ -638,3 +640,4 @@ DEPENDENCIES
BUNDLED WITH
1.14.3

View file

@ -46,6 +46,7 @@
//= require unobtrusive_flash
//= require unobtrusive_flash_bootstrap
//= require countable
//= require selectize
$(document).ready(function() {
$('a[disabled=disabled]').click(function(event){

View file

@ -16,4 +16,6 @@
*= require bootstrap3-switch
*= require osem-payments
*= require osem-navbar
*= require selectize
*= require selectize.bootstrap3
*/

View file

@ -106,7 +106,11 @@ background-image: -webkit-gradient(
}
.speakerinfo {
margin-top: 20px;
margin-top: 40px;
}
.speakerbio {
margin-top: 10px;
}
.schedule-title, .schedule-subtitle, .schedule-speaker, .schedule-track {

View file

@ -5,7 +5,7 @@ module Admin
load_and_authorize_resource :event, through: :program
load_and_authorize_resource :events_registration, only: :toggle_attendance
before_action :get_event, except: [:index, :create]
before_action :get_event, except: [:index, :create, :new]
# FIXME: The timezome should only be applied on output, otherwise
# you get lost in timezone conversions...
@ -62,6 +62,7 @@ module Admin
@comments = @event.root_comments
@comment_count = @event.comment_threads.count
@user = @event.submitter
@users = User.all.order(:name)
@url = admin_conference_program_event_path(@conference.short_title, @event)
@languages = @program.languages_list
end
@ -79,6 +80,8 @@ module Admin
end
def update
@users = User.all.order(:name)
@languages = @program.languages_list
if @event.update_attributes(event_params)
if request.xhr?
@ -94,7 +97,26 @@ module Admin
end
end
def create; end
def create
@url = admin_conference_program_events_path(@conference.short_title, @event)
@users = User.all.order(:name)
@languages = @program.languages_list
@event.submitter = current_user
if @event.save
ahoy.track 'Event submission', title: 'New submission'
redirect_to admin_conference_program_events_path(@conference.short_title), notice: 'Event was successfully submitted.'
else
flash[:error] = "Could not submit proposal: #{@event.errors.full_messages.join(', ')}"
render action: 'new'
end
end
def new
@url = admin_conference_program_events_path(@conference.short_title, @event)
@languages = @program.languages_list
@users = User.all.order(:name)
end
def accept
send_mail = @event.program.conference.email_settings.send_on_accepted
@ -161,7 +183,8 @@ module Admin
# Set only in admin/events controller
:track_id, :state, :language, :is_highlight, :max_attendees,
# Not used anymore?
:proposal_additional_speakers, :user, :users_attributes)
:proposal_additional_speakers, :user, :users_attributes,
speaker_ids: [])
end
def comment_params

View file

@ -13,9 +13,8 @@ class ProposalsController < ApplicationController
end
def show
# FIXME: We should show more than the first speaker
@speaker = @event.speakers.first || @event.submitter
@event_schedule = @event.event_schedules.find_by(schedule_id: @program.selected_schedule_id)
@speakers_ordered = @event.speakers_ordered
end
def new
@ -26,6 +25,7 @@ class ProposalsController < ApplicationController
def edit
@url = conference_program_proposal_path(@conference.short_title, params[:id])
@users = User.all.order(:name)
@languages = @program.languages_list
end
@ -48,11 +48,8 @@ class ProposalsController < ApplicationController
# User which creates the proposal is both `submitter` and `speaker` of proposal
# by default.
# TODO: Allow submitter to add speakers to proposals
@event.event_users.new(user: current_user,
event_role: 'submitter')
@event.event_users.new(user: current_user,
event_role: 'speaker')
@event.speakers = [current_user]
@event.submitter = current_user
if @event.save
ahoy.track 'Event submission', title: 'New submission'
redirect_to conference_program_proposals_path(@conference.short_title), notice: 'Proposal was successfully submitted.'
@ -64,6 +61,7 @@ class ProposalsController < ApplicationController
def update
@url = conference_program_proposal_path(@conference.short_title, params[:id])
@users = User.all.order(:name)
if @event.update(event_params)
redirect_to conference_program_proposals_path(conference_id: @conference.short_title),
@ -147,7 +145,9 @@ class ProposalsController < ApplicationController
def event_params
params.require(:event).permit(:event_type_id, :track_id, :difficulty_level_id,
:title, :subtitle, :abstract, :description,
:require_registration, :max_attendees, :language)
:require_registration, :max_attendees, :language,
speaker_ids: []
)
end
def user_params

View file

@ -8,7 +8,13 @@ class Event < ActiveRecord::Base
has_many :event_users, dependent: :destroy
has_many :users, through: :event_users
has_many :speakers, through: :event_users, source: :user
has_many :speaker_event_users, -> { where(event_role: 'speaker') }, class_name: 'EventUser'
has_many :speakers, through: :speaker_event_users, source: :user
has_one :submitter_event_user, -> { where(event_role: 'submitter') }, class_name: 'EventUser'
has_one :submitter, through: :submitter_event_user, source: :user
has_many :votes, dependent: :destroy
has_many :voters, through: :votes, source: :user
has_many :commercials, as: :commercialable, dependent: :destroy
@ -23,6 +29,7 @@ class Event < ActiveRecord::Base
belongs_to :program
accepts_nested_attributes_for :event_users, allow_destroy: true
accepts_nested_attributes_for :speakers, allow_destroy: true
accepts_nested_attributes_for :users
before_create :generate_guid
@ -33,6 +40,7 @@ class Event < ActiveRecord::Base
validates :abstract, presence: true
validates :event_type, presence: true
validates :program, presence: true
validates :speakers, presence: true
validates :max_attendees, numericality: { only_integer: true, greater_than_or_equal_to: 1, allow_nil: true }
validate :max_attendees_no_more_than_room_size
@ -113,20 +121,16 @@ class Event < ActiveRecord::Base
@total_rating > 0 ? number_with_precision(@total_rating / @total.to_f, precision: 2, strip_insignificant_zeros: true) : 0
end
def submitter
result = event_users.where(event_role: 'submitter').first
if result.nil?
user = nil
# Perhaps the event_users haven't been saved, if this is a new proposal
event_users.each do |u|
if u.event_role == 'submitter'
user = u.user
end
end
user
else
result.user
# get event speakers with the event sumbmitter at the first position
# if the submitter is also a speaker for this event
def speakers_ordered
speakers_list = speakers.to_a
if speakers_list.reject! { |speaker| speaker == submitter }
speakers_list.unshift(submitter)
end
speakers_list
end
def transition_possible?(transition)

View file

@ -41,6 +41,7 @@ class User < ActiveRecord::Base
has_many :event_users, dependent: :destroy
has_many :events, -> { uniq }, through: :event_users
has_many :presented_events, -> { joins(:event_users).where(event_users: {event_role: 'speaker'}).uniq }, through: :event_users, source: :event
has_many :registrations, dependent: :destroy
has_many :events_registrations, through: :registrations
has_many :ticket_purchases, dependent: :destroy

View file

@ -0,0 +1,11 @@
.row
.col-md-12
.page-header
%h1
-if @event.new_record?
New
= @event.title
Event
.row
.col-md-12
= render 'proposals/proposal_form'

View file

@ -131,9 +131,26 @@
%i Hidden
%tr
%td
%b Biography
%b Speakers
%td
= markdown(@event.submitter.biography)
- if @program.show_voting?
- @event.speakers.each do |speaker|
%div
= link_to speaker.name, admin_user_path(speaker)
(
= link_to speaker.email, "mailto: #{speaker.email}"
)
- else
%i Hidden
%tr
%td
%b Biographies
%td
- @event.speakers.each do |speaker|
- unless speaker.biography.blank?
%b
= speaker.name
= markdown(speaker.biography)
%tr
%td
%b Submitted on

View file

@ -4,7 +4,9 @@
%h1
Events
= "(#{@events.length})" if @events.any?
.btn-group.pull-right
.pull-right
- if can? :create, Event
=link_to 'Add Event', new_admin_conference_program_event_path(@conference.short_title), class: 'button btn btn-default btn-info'
- if can? :read, Event
.btn-group
%button.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown', type: 'button', class: 'btn btn-success' }
@ -54,8 +56,8 @@
%th
%b Submitter
%th
%b Speaker
- if @program.languages.present?
%b Speakers
-if @program.languages.present?
%th
%b Language
%th
@ -99,10 +101,9 @@
%i Hidden
%td
- if @program.show_voting?
- if speaker = event.speakers.first
= link_to speaker.name, admin_user_path(speaker)
- else
Unknown speaker
- event.speakers_ordered.each do |speaker|
.speaker
= link_to speaker.name, admin_user_path(speaker)
- else
%i Hidden

View file

@ -4,6 +4,10 @@
= f.input :subtitle, as: :string
= f.input :speakers, as: :select,
collection: options_for_select(@users.map {|user| ["#{user.name} (#{user.email})", user.id]}, @event.speakers.map(&:id)),
include_blank: false, label: 'Speakers', input_html: { class: 'select-help-toggle', multiple: 'true' }
- if @program.tracks.any?
= f.input :track_id, as: :select,
collection: @program.tracks.map {|track| ["#{track.name}", track.id] },
@ -60,4 +64,15 @@
%p.text-right
= f.submit 'Update Proposal', class: 'btn btn-success'
- if @event.new_record?
= f.submit 'Create Proposal', class: 'btn btn-success'
- else
= f.submit 'Update Proposal', class: 'btn btn-success'
:javascript
$(document).ready(function() {
$('#event_speaker_ids').selectize({
plugins: ['remove_button'],
maxItems: 5
} )
});

View file

@ -3,8 +3,9 @@
%meta{ property: "og:url", content: conference_program_proposal_url(@conference.short_title, @event) }
%meta{ property: "og:description", content: @event.abstract }
%meta{ property: "og:site_name", content: (ENV['OSEM_NAME'] || 'OSEM') }
%meta{ property: "og:image", content: @speaker.gravatar_url }
%meta{ property: "og:image:secure_url", content: @speaker.gravatar_url }
- if @speakers_ordered.any?
%meta{ property: "og:image", content: @speakers_ordered.first.gravatar_url }
%meta{ property: "og:image:secure_url", content: @speakers_ordered.first.gravatar_url }
.container
.row
@ -25,21 +26,25 @@
.row
.col-md-3
.speakerinfo
.col-md-12
= image_tag @speaker.gravatar_url(size: 200), class: 'img-responsive img-rounded'
.col-md-12
%h3
by
= link_to @speaker.name, user_path(@speaker.id)
= "(#{@speaker.email})" if @speaker.email_public
- if @speaker.affiliation?
%br
%span.muted
from
= @speaker.affiliation
-if @speaker.biography?
= markdown(@speaker.biography)
%h3
Presented by:
- @speakers_ordered.each do |speaker|
.speakerinfo
.row
.col-md-4
= image_tag speaker.gravatar_url(:size => 120), class: 'img-responsive img-rounded'
.col-md-8
%h4
= link_to speaker.name, user_path(speaker.id)
= "(#{speaker.email})"
- if speaker.affiliation?
.text-muted
from
= speaker.affiliation
-if speaker.biography?
.row.speakerbio
.col-md-12
= markdown(speaker.biography)
.col-md-9
.row
.col-md-12

View file

@ -1,9 +1,9 @@
.panel.panel-default.event-panel{ onClick: 'eventClicked(event, this);', "data-url" => "#{url_for(conference_program_proposal_path(@conference.short_title, event.id))}" }
.panel-body
- if speaker = event.speakers.first
= image_tag speaker.gravatar_url, class: "img-circle pull-right all-speaker-pic", |
alt: speaker.name, |
title: speaker.name |
- event.speakers_ordered.each do |speaker|
= image_tag speaker.gravatar_url, :class => "img-circle pull-right all-speaker-pic", |
:alt => speaker.name, |
:title => speaker.name |
%p
= canceled_replacement_event_label(event, event_schedule)

View file

@ -14,3 +14,9 @@
alt: speaker.name, |
title: speaker.name, |
style: "height: #{ speaker_height(@rooms) }px; width: #{ speaker_width(@rooms) }px;"
- event.speakers_ordered.each do |speaker|
= image_tag speaker.gravatar_url, :class => "img-circle pull-right speaker-pic", |
:alt => speaker.name, |
:title => speaker.name, |
:style => "height: #{ speaker_height(@rooms) }px; width: #{ speaker_width(@rooms) }px;"

View file

@ -11,11 +11,11 @@
= markdown(@user.biography)
.row
.col-md-12
- if @user.events.confirmed.any?
- if @user.presented_events.confirmed.any?
%h3
= "#{@user.name} presents #{pluralize(@user.events.confirmed.count, 'Event')}:"
= "#{@user.name} presents #{pluralize(@user.presented_events.confirmed.count, 'Event')}:"
%ul.list-unstyled
- @user.events.confirmed.each do |event|
- @user.presented_events.confirmed.each do |event|
%li
%h4
= link_to event.title, conference_program_proposal_path(event.program.conference.short_title, event.id)

View file

@ -10,8 +10,8 @@ describe Api::V1::SpeakersController do
describe 'GET #index' do
before do
event.event_users << create(:speaker, user: speaker)
conference_event.event_users << create(:speaker, user: conference_speaker)
event.speakers = [speaker]
conference_event.speakers = [conference_speaker]
end
context 'without conference scope' do
@ -20,7 +20,6 @@ describe Api::V1::SpeakersController do
get :index, format: :json
json = JSON.parse(response.body)['speakers']
expect(response).to be_success
expect(json.length).to eq(2)
expect(json[0]['name']).to eq('Speaker')
expect(json[1]['name']).to eq('Conf_Speaker')

View file

@ -180,9 +180,8 @@ describe ProposalsController do
get :show, conference_id: conference.short_title, id: event.id
end
it 'assigns event and speaker variables' do
it 'assigns event variable' do
expect(assigns(:event)).to eq event
expect(assigns(:speaker)).to eq event.submitter
end
it 'renders show template' do

View file

@ -8,7 +8,8 @@ FactoryGirl.define do
program
after(:build) do |event|
event.event_users << build(:submitter) unless event.submitter # so that we don't have two submitters
event.submitter = build(:submitter).user unless event.submitter # so that we don't have two submitters
event.speakers << build(:speaker).user unless event.speakers.any?
# set an event_type if none is passed to the factory.
# needs to be created here because otherwise it doesn't belong to the
# same conference as the event

View file

@ -8,8 +8,7 @@ describe Mailbot do
before { conference.contact.update_attributes(email: 'conf@domain.com') }
context 'onboarding and proposal' do
let(:event_user) { create(:submitter, user: user) }
let(:event) { create(:event, program: conference.program, event_users: [event_user]) }
let(:event) { create(:event, program: conference.program, submitter: user) }
shared_examples 'mailer actions' do
it 'assigns the email subject' do

View file

@ -3,8 +3,7 @@ require 'spec_helper'
describe EmailSettings do
let(:conference) { create(:conference, short_title: 'goto', start_date: Date.new(2014, 05, 01), end_date: Date.new(2014, 05, 06)) }
let(:user) { create(:user, username: 'johnd', email: 'john@doe.com', name: 'John Doe') }
let(:event_user) { create(:submitter, user: user) }
let(:event) { create(:event, program: conference.program, title: 'Talk about talks', event_users: [event_user]) }
let(:event) { create(:event, program: conference.program, title: 'Talk about talks', submitter: user) }
let(:expected_hash) do
{
'email' => 'john@doe.com',

View file

@ -236,9 +236,7 @@ describe Event do
describe '#submitter' do
it 'returns the user that submitted the event' do
submitter = create(:user)
submitted_event = create(:event)
submitted_event.event_users = [create(:event_user, user: submitter, event_role: 'submitter')]
submitted_event = create(:event, submitter: submitter)
expect(submitted_event.submitter).to eq submitter
end
end
@ -288,9 +286,10 @@ describe Event do
describe '#speaker_names' do
context 'returns the speakers of the event' do
it 'when submitter is a speaker too' do
speaker1 = create(:user, name: 'user speaker 1')
new_event.event_users = [create(:event_user, user: speaker1, event_role: 'submitter')]
new_event.event_users << [create(:event_user, user: speaker1, event_role: 'speaker')]
submitter = create(:user, name: 'user speaker 1')
new_event.submitter = submitter
new_event.speakers = [submitter]
expect(new_event.speaker_names).to eq 'user speaker 1'
end
@ -299,10 +298,10 @@ describe Event do
submitter = create(:user, name: 'user submitter 1')
speaker1 = create(:user, name: 'user speaker 1')
new_event.event_users = [create(:event_user, user: submitter, event_role: 'submitter')]
new_event.event_users << [create(:event_user, user: speaker1, event_role: 'speaker')]
new_event.submitter = submitter
new_event.speakers = [speaker1]
expect(new_event.speaker_names).to eq 'user submitter 1 and user speaker 1'
expect(new_event.speaker_names).to eq 'user speaker 1'
end
it 'when there are multiple speakers' do
@ -310,11 +309,10 @@ describe Event do
speaker1 = create(:user, name: 'user speaker 1')
speaker2 = create(:user, name: 'user speaker 2')
new_event.event_users = [create(:event_user, user: submitter, event_role: 'submitter')]
new_event.event_users << [create(:event_user, user: speaker1, event_role: 'speaker')]
new_event.event_users << [create(:event_user, user: speaker2, event_role: 'speaker')]
new_event.submitter = submitter
new_event.speakers = [speaker1, speaker2]
expect(new_event.speaker_names).to eq 'user submitter 1, user speaker 1, and user speaker 2'
expect(new_event.speaker_names).to eq 'user speaker 1 and user speaker 2'
end
end
end

View file

@ -3,7 +3,7 @@ describe EventSerializer, type: :serializer do
let(:event) { create(:event, title: 'Some Talk', abstract: 'Lorem ipsum dolor sit amet') }
let(:serializer) { EventSerializer.new(event) }
context 'event does not have date, speakers, room and tracks assigned' do
context 'event does not have date, room and tracks assigned' do
it 'sets guid, title, length, abstract and type' do
expected_json = {
event: {
@ -13,7 +13,7 @@ describe EventSerializer, type: :serializer do
scheduled_date: '',
language: nil,
abstract: 'Lorem ipsum dolor sit amet',
speaker_ids: [],
speaker_ids: event.speaker_ids,
type: 'Example Event Type',
room: nil,
track: nil
@ -25,13 +25,13 @@ describe EventSerializer, type: :serializer do
end
context 'event has date, speakers, room and tracks assigned' do
let(:speaker) { create(:speaker) }
let(:speaker) { create(:user) }
let(:room) { create(:room) }
let(:track) { create(:track) }
before do
event.language = 'English'
event.event_users << speaker
event.language = 'English'
event.speakers = [speaker]
create(:event_schedule, event: event, room: room, start_time: Date.new(2014, 03, 04))
event.track = track
end
@ -45,7 +45,7 @@ describe EventSerializer, type: :serializer do
scheduled_date: ' 2014-03-04T00:00:00+0000 ',
language: 'English',
abstract: 'Lorem ipsum dolor sit amet',
speaker_ids: [speaker.user.id],
speaker_ids: [speaker.id],
type: 'Example Event Type',
room: room.guid,
track: track.guid