From c5db5dba1e96e996ec902d2e51daea5df1fca917 Mon Sep 17 00:00:00 2001 From: siddhantbajaj Date: Mon, 10 Jul 2017 17:45:05 +0530 Subject: [PATCH 01/74] Ticket show page --- app/assets/stylesheets/osem.css.scss | 6 +- app/views/physical_ticket/show.html.haml | 77 ++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/osem.css.scss b/app/assets/stylesheets/osem.css.scss index bc8b6388..5a331436 100644 --- a/app/assets/stylesheets/osem.css.scss +++ b/app/assets/stylesheets/osem.css.scss @@ -89,4 +89,8 @@ p.comment-body { .changeset{ display: none; -} \ No newline at end of file +} + +.box{ + height: 230px; +} diff --git a/app/views/physical_ticket/show.html.haml b/app/views/physical_ticket/show.html.haml index e69de29b..26fa7853 100644 --- a/app/views/physical_ticket/show.html.haml +++ b/app/views/physical_ticket/show.html.haml @@ -0,0 +1,77 @@ +.container + .row + .col-md-12 + .page-header + %h1 + Ticket for + = @conference.title + %p.text-muted + - if @conference.venue + at + %strong + #{@conference.venue.name}, + #{@conference.venue.street}, + #{@conference.venue.city} / #{@conference.venue.country_name}. + %small + = date_string(@conference.start_date, @conference.end_date) + .row + .col-md-5.box.well + %h3.text-center + Ticket Holder + %p.text-left + %strong + Name + %br + = @user.name + %br + %br + %strong + Email + %br + = @user.email + .col-md-5.col-md-offset-2.box.well + - if @conference.picture? + - width = @conference.picture.image[:width] + - height = @conference.picture.image[:height] + - if 10 * width > 15 * height + = image_tag(@conference.picture_url, width: '150') + - else + = image_tag(@conference.picture_url, height: '100') + - else + = image_tag('/img/osem-logo.png', class: 'img-responsive') + %p.text-left + %br + %strong + Organization + %br + = @conference.organization.name + .col-md-5.box.well + %p.text-left + %strong + Event + %br + = @conference.title + %br + = @conference.start_date.strftime('%B %d, %Y') + %br + %br + %strong + Ticket + %br + = @physical_ticket.ticket.title + %br + %br + %strong + Ticket Ref. + %br + = @physical_ticket.ticket_purchase.id + %br + .col-md-5.col-md-offset-2.box.well + .row + .col-md-12 + %p.text-left + = link_to 'Generate PDF', + conference_physical_ticket_path(@conference.short_title, + @physical_ticket.id, + format: :pdf), + class: 'button btn btn-default btn-info' From 4779bef9f7d486e5d316a732589929b268193735 Mon Sep 17 00:00:00 2001 From: siddhantbajaj Date: Wed, 19 Jul 2017 17:47:02 +0530 Subject: [PATCH 02/74] Fixed flickering proposal feature test Test were sometimes failing because of the delay between the click_link and the actual completion of the code that is run as a result of that click. --- spec/features/proposals_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/features/proposals_spec.rb b/spec/features/proposals_spec.rb index b5620475..70bcbf63 100644 --- a/spec/features/proposals_spec.rb +++ b/spec/features/proposals_spec.rb @@ -32,7 +32,7 @@ feature Event do click_button 'New' click_link "reject_event_#{@event.id}" - expect(flash).to eq('Event rejected!') + expect(page).to have_content 'Event rejected!' @event.reload expect(@event.state).to eq('rejected') end @@ -43,7 +43,7 @@ feature Event do click_button 'New' click_link "accept_event_#{@event.id}" - expect(flash).to eq('Event accepted!') + expect(page).to have_content 'Event accepted!' expect(page.has_content?('Unconfirmed')).to be true @event.reload expect(@event.state).to eq('unconfirmed') @@ -56,7 +56,7 @@ feature Event do click_button 'Rejected' click_link "restart_event_#{@event.id}" - expect(flash).to eq('Review started!') + expect(page).to have_content 'Review started!' @event.reload expect(@event.state).to eq('new') end From 0501ecd8f6fce5883fc68b5cfe340846c8e1c6f7 Mon Sep 17 00:00:00 2001 From: siddhantbajaj Date: Thu, 20 Jul 2017 17:30:27 +0530 Subject: [PATCH 03/74] Refactored proposal feature spec. Fixes #1561. --- spec/features/proposals_spec.rb | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/spec/features/proposals_spec.rb b/spec/features/proposals_spec.rb index 70bcbf63..9dda06bf 100644 --- a/spec/features/proposals_spec.rb +++ b/spec/features/proposals_spec.rb @@ -28,7 +28,7 @@ feature Event do scenario 'rejects a proposal', feature: true, js: true do visit admin_conference_program_events_path(conference.short_title) - expect(page.has_content?('Example Proposal')).to be true + expect(page).to have_content 'Example Proposal' click_button 'New' click_link "reject_event_#{@event.id}" @@ -39,12 +39,12 @@ feature Event do scenario 'accepts a proposal', feature: true, js: true do visit admin_conference_program_events_path(conference.short_title) - expect(page.has_content?('Example Proposal')).to be true + expect(page).to have_content 'Example Proposal' click_button 'New' click_link "accept_event_#{@event.id}" expect(page).to have_content 'Event accepted!' - expect(page.has_content?('Unconfirmed')).to be true + expect(page).to have_content 'Unconfirmed' @event.reload expect(@event.state).to eq('unconfirmed') end @@ -52,7 +52,7 @@ feature Event do scenario 'restarts review of a proposal', feature: true, js: true do @event.reject!(@options) visit admin_conference_program_events_path(conference.short_title) - expect(page.has_content?('Example Proposal')).to be true + expect(page).to have_content 'Example Proposal' click_button 'Rejected' click_link "restart_event_#{@event.id}" @@ -83,7 +83,7 @@ feature Event do fill_in 'event_abstract', with: 'Lorem ipsum abstract' click_button 'Create Proposal' - expect(flash).to eq('Proposal was successfully submitted.') + expect(page).to have_content 'Proposal was successfully submitted.' expect(Event.count).to eq(expected_count_event) expect(User.count).to eq(expected_count_user) @@ -101,7 +101,7 @@ feature Event do select('Easy', from: 'event[difficulty_level_id]') click_button 'Update Proposal' - expect(flash).to eq('Proposal was successfully updated.') + expect(page).to have_content 'Proposal was successfully updated.' end scenario 'signed_in user submits a valid proposal', feature: true, js: true do @@ -119,7 +119,7 @@ feature Event do fill_in 'event_description', with: 'Lorem ipsum description' click_button 'Create Proposal' - expect(flash).to eq('Proposal was successfully submitted.') + expect(page).to have_content 'Proposal was successfully submitted.' expect(current_path).to eq(conference_program_proposals_path(conference.short_title)) expect(Event.count).to eq(expected_count) @@ -128,11 +128,10 @@ feature Event do scenario 'confirms a proposal', feature: true, js: true do sign_in participant visit conference_program_proposals_path(conference.short_title) - expect(page.has_content?('Example Proposal')).to be true + expect(page).to have_content 'Example Proposal' expect(@event.state).to eq('unconfirmed') click_link "confirm_proposal_#{@event.id}" - expect(flash) - .to eq('The proposal was confirmed. Please register to attend the conference.') + expect(page).to have_content 'The proposal was confirmed. Please register to attend the conference.' expect(current_path).to eq(new_conference_conference_registration_path(conference.short_title)) @event.reload expect(@event.state).to eq('confirmed') @@ -142,9 +141,9 @@ feature Event do sign_in participant @event.confirm! visit conference_program_proposals_path(conference.short_title) - expect(page.has_content?('Example Proposal')).to be true + expect(page).to have_content 'Example Proposal' click_link "delete_proposal_#{@event.id}" - expect(flash).to eq('Proposal was successfully withdrawn.') + expect(page).to have_content 'Proposal was successfully withdrawn.' @event.reload expect(@event.state).to eq('withdrawn') end From 1c9891215d27a9d4764c7fae7e3bd7fca96df71b Mon Sep 17 00:00:00 2001 From: siddhantbajaj Date: Thu, 20 Jul 2017 18:14:38 +0530 Subject: [PATCH 04/74] Authorize ticket purchase --- app/controllers/ticket_purchases_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/ticket_purchases_controller.rb b/app/controllers/ticket_purchases_controller.rb index 792122e2..f15fc993 100644 --- a/app/controllers/ticket_purchases_controller.rb +++ b/app/controllers/ticket_purchases_controller.rb @@ -2,6 +2,7 @@ class TicketPurchasesController < ApplicationController before_filter :authenticate_user! load_resource :conference, find_by: :short_title authorize_resource :conference_registrations, class: Registration + authorize_resource def create current_user.ticket_purchases.by_conference(@conference).unpaid.destroy_all From 34456fa205da4dce4eee16944c716fa7733e0911 Mon Sep 17 00:00:00 2001 From: siddhantbajaj Date: Fri, 21 Jul 2017 04:13:23 +0530 Subject: [PATCH 05/74] Remove float point number on y axis --- app/assets/javascripts/osem-dashboard.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/osem-dashboard.js b/app/assets/javascripts/osem-dashboard.js index c8d5b853..83d2b468 100644 --- a/app/assets/javascripts/osem-dashboard.js +++ b/app/assets/javascripts/osem-dashboard.js @@ -55,7 +55,6 @@ $(function() { } function draw_line_chart(animation, $canvas){ - var options = get_animation({}, animation); var chart_data = create_dataset($canvas); var weeks = $canvas.parent().data('weeks'); var data = { @@ -63,10 +62,27 @@ $(function() { datasets : chart_data } + var options = get_animation(wholeNumberAxisFix(data), animation); var ctx = $canvas.get(0).getContext("2d"); new Chart(ctx).Line(data, options); } + function wholeNumberAxisFix(data){ + var maxValue = false; + for(datasetIndex = 0; datasetIndex < data.datasets.length; ++datasetIndex){ + var setMax = Math.max.apply(null, data.datasets[datasetIndex].data); + if (maxValue === false || setMax > maxValue) maxValue = setMax; + } + + var steps = maxValue; + var stepWidth = 1; + if (maxValue > 10) { + stepWidth = Math.floor(maxValue / 10); + steps = Math.ceil(maxValue / stepWidth); + } + return { scaleOverride: true, scaleSteps: steps, scaleStepWidth: stepWidth, scaleStartValue: 0 }; + } + function create_dataset($canvas){ var selected = getSelectedConferences($canvas); var chart_data = $canvas.parent().data('chart'); From c6b0a1f52e9936aeff7705d3037b409a89e3e0c0 Mon Sep 17 00:00:00 2001 From: nasia Date: Sun, 16 Jul 2017 18:20:12 +0300 Subject: [PATCH 06/74] Add tests for booth controller --- app/models/booth.rb | 2 +- app/views/admin/booths/_form.html.haml | 2 +- .../admin/booths_controller_spec.rb | 148 ++++++++++++++++++ spec/factories/booth_request.rb | 8 + spec/factories/booths.rb | 4 +- spec/models/booth_spec.rb | 4 +- 6 files changed, 161 insertions(+), 7 deletions(-) create mode 100644 spec/controllers/admin/booths_controller_spec.rb create mode 100644 spec/factories/booth_request.rb diff --git a/app/models/booth.rb b/app/models/booth.rb index 911f9485..5c20550a 100644 --- a/app/models/booth.rb +++ b/app/models/booth.rb @@ -55,7 +55,7 @@ class Booth < ActiveRecord::Base transitions to: :rejected, from: [:new, :to_reject] end event :cancel do - transitions to: :canceled, from: [:accepted, :rejected] + transitions to: :canceled, from: [:accepted, :rejected, :to_accept, :to_reject] end end diff --git a/app/views/admin/booths/_form.html.haml b/app/views/admin/booths/_form.html.haml index e4a7d05d..f70079fa 100644 --- a/app/views/admin/booths/_form.html.haml +++ b/app/views/admin/booths/_form.html.haml @@ -5,7 +5,7 @@ .row .col-md-8 = semantic_form_for(@booth, url: @booth.new_record? ? admin_conference_booths_path(@conference.short_title) : admin_conference_booth_path(@conference.short_title, @booth.id), html: { multipart: true }) do |f| - = f.input :title, as: :string, required: true + = f.input :title, as: :string, autofocus: true, required: true = f.input :description, input_html: { rows: 5, data: { provide: 'markdown-editable' } }, required: true, hint: 'This field becomes public upon request acceptance' = f.input :reasoning, input_html: { rows: 5, data: { provide: 'markdown-editable' } }, required: true, diff --git a/spec/controllers/admin/booths_controller_spec.rb b/spec/controllers/admin/booths_controller_spec.rb new file mode 100644 index 00000000..569862d1 --- /dev/null +++ b/spec/controllers/admin/booths_controller_spec.rb @@ -0,0 +1,148 @@ +require 'spec_helper' + +describe Admin::BoothsController do + + let(:admin) { create(:admin) } + let(:conference) { create(:conference) } + let(:booth) { create(:booth, title: 'Title', conference: conference) } + let(:admin) { create(:admin) } + + context 'not logged in user' do + + describe 'GET index' do + it 'does not render admin/booths#index' do + get :index, conference_id: conference.short_title + expect(response).to redirect_to(user_session_path) + end + end + + describe 'GET show' do + it 'does not render admin/booths#show' do + get :show, id: booth.id, conference_id: conference.short_title + expect(response).to redirect_to(user_session_path) + end + end + end + + context 'user is admin' do + before :each do + sign_in admin + end + + describe 'GET index' do + before { get :index, conference_id: conference.short_title } + + it 'assigns attributes for booths' do + expect(assigns(:booths)).to eq([booth]) + end + + it 'renders index template' do + expect(response).to render_template('index') + end + end + + describe 'GET new' do + before { get :new, conference_id: conference.short_title } + + it 'assigns attributes for booths' do + expect(assigns(:booth)).to be_a_new(Booth) + end + + it 'renders new template' do + expect(response).to render_template('new') + end + end + + describe 'POST #create' do + context 'successfully created' do + before { post :create, booth: attributes_for(:booth), conference_id: conference.short_title } + + it 'creates a new booth' do + expected = expect do + post :create, booth: attributes_for(:booth), conference_id: conference.short_title + end + expected.to change { Booth.count }.by(1) + end + + it 'redirects to admin booth index' do + expect(response).to redirect_to(admin_conference_booths_path) + end + + it 'has responsibles' do + expect(booth.responsibles.count).to_not eq(0) + end + + it 'shows success message' do + expect(flash[:notice]).to match('Booth successfully created.') + end + end + + context 'create action fails' do + before { post :create, booth: attributes_for(:booth, title: ''), conference_id: conference.short_title } + + it 'does not create any record' do + expected = expect do + post :create, booth: attributes_for(:booth, title: ''), conference_id: conference.short_title + end + expected.to_not change(Booth, :count) + end + + it 'redirects to new' do + expect(response).to render_template('new') + end + + it 'shows flash message' do + expect(flash[:error]).to eq("Creating booth failed. Title can't be blank.") + end + end + end + + describe 'GET #edit' do + before { get :edit, id: booth.id, conference_id: conference.short_title } + + it 'renders edit template' do + expect(response).to render_template('edit') + end + + it 'assigns booth variable' do + expect(assigns(:booth)).to eq booth + end + end + + describe 'PATCH #update' do + context 'updates suchessfully' do + before { patch :update, id: booth.id, booth: attributes_for(:booth, title: 'different'), conference_id: conference.short_title } + it 'redirects to admin booth index path' do + expect(response).to redirect_to admin_conference_booths_path + end + + it 'shows success message' do + expect(flash[:notice]).to match 'Successfully updated booth.' + end + + it 'updates booth' do + booth.reload + expect(booth.title).to eq('different') + end + end + end + + describe 'DELETE #destroy' do + context 'deletes successfully' do + before { delete :destroy, id: booth.id, conference_id: conference.short_title } + + it 'booth deleted' do + expect(Booth.count).to eq(0) + end + + it 'redirects to admin booth index path' do + expect(response).to redirect_to(admin_conference_booths_path) + end + + it 'show success message' do + expect(flash[:notice]).to match('Booth successfully destroyed.') + end + end + end + end +end diff --git a/spec/factories/booth_request.rb b/spec/factories/booth_request.rb new file mode 100644 index 00000000..fba0cace --- /dev/null +++ b/spec/factories/booth_request.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :booth_request do + booth + user + role 'responsible' + + end +end diff --git a/spec/factories/booths.rb b/spec/factories/booths.rb index a67d30f0..b1cd1956 100644 --- a/spec/factories/booths.rb +++ b/spec/factories/booths.rb @@ -8,8 +8,6 @@ FactoryGirl.define do conference - after(:build) do |booth| - booth.responsibles << create(:user) - end + responsible_ids { [create(:user).id] } end end diff --git a/spec/models/booth_spec.rb b/spec/models/booth_spec.rb index f13eb536..5e86a393 100644 --- a/spec/models/booth_spec.rb +++ b/spec/models/booth_spec.rb @@ -38,8 +38,8 @@ describe 'Booth' do states_transitions = { new: { restart: false, withdraw: true, accept: true, to_accept: true, to_reject: true, reject: true, cancel: false }, withdrawn: { restart: true, withdraw: false, accept: false, to_accept: false, to_reject: false, reject: false, cancel: false }, - to_accept: { restart: true, withdraw: true, accept: true, to_accept: false, to_reject: true, reject: false, cancel: false }, - to_reject: { restart: true, withdraw: true, accept: false, to_accept: true, to_reject: false, reject: true, cancel: false }, + to_accept: { restart: true, withdraw: true, accept: true, to_accept: false, to_reject: true, reject: false, cancel: true }, + to_reject: { restart: true, withdraw: true, accept: false, to_accept: true, to_reject: false, reject: true, cancel: true }, accepted: { restart: false, withdraw: true, accept: false, to_accept: false, to_reject: false, reject: false, cancel: true }, rejected: { restart: false, withdraw: true, accept: false, to_accept: false, to_reject: false, reject: false, cancel: true }, canceled: { restart: true, withdraw: false, accept: false, to_accept: false, to_reject: false, reject: false, cancel: false } } From 2bf7e4bbd343e0a4a062762dd0dfd4a421b997e9 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Sat, 15 Jul 2017 03:02:03 +0300 Subject: [PATCH 07/74] Fix user already subscribed exception Prevent excpetion at user subscription when already is subscribed to a conference and add hanling to the similar unsubscribe event. remove double validation at app/models/subscription.rb update subscription_controller_spec --- app/controllers/subscriptions_controller.rb | 10 ++++++---- app/models/subscription.rb | 1 - spec/controllers/subscriptions_controller_spec.rb | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/controllers/subscriptions_controller.rb b/app/controllers/subscriptions_controller.rb index 351f9e9d..76d5c74a 100644 --- a/app/controllers/subscriptions_controller.rb +++ b/app/controllers/subscriptions_controller.rb @@ -5,17 +5,19 @@ class SubscriptionsController < ApplicationController def create @subscription = current_user.subscriptions.build(conference_id: @conference.id) - if @subscription.save! - redirect_to root_path, notice: "You have been subscribed to receive email notifications for #{@conference.short_title}." + if @subscription.save + redirect_to root_path, notice: "You have subscribed to receive email notifications for #{@conference.title}." else - redirect_to root_path, error: subscription.errors.full_messages.to_sentence + redirect_to root_path, error: @subscription.errors.full_messages.to_sentence end end def destroy @subscription = current_user.subscriptions.find_by(conference_id: @conference.id) + + redirect_to(root_path, error: "You are not subscribed to #{@conference.title}.") && return unless @subscription if @subscription.destroy - redirect_to root_path, notice: "You have been unsubscribed and now you will not be receiving email notifications for #{@conference.short_title}." + redirect_to root_path, notice: "You have unsubscribed and you will not be receiving email notifications for #{@conference.title}." else redirect_to root_path, error: @subscription.errors.full_messages.to_sentence end diff --git a/app/models/subscription.rb b/app/models/subscription.rb index aa688ae8..984c46f3 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -1,5 +1,4 @@ class Subscription < ActiveRecord::Base - validates :user_id, uniqueness: { scope: [:conference_id] } belongs_to :conference belongs_to :user diff --git a/spec/controllers/subscriptions_controller_spec.rb b/spec/controllers/subscriptions_controller_spec.rb index 193ae23d..ac20a34b 100644 --- a/spec/controllers/subscriptions_controller_spec.rb +++ b/spec/controllers/subscriptions_controller_spec.rb @@ -24,7 +24,7 @@ describe SubscriptionsController do it 'shows success message in flash notice' do post :create, conference_id: conference.short_title - expect(flash[:notice]).to match("You have been subscribed to receive email notifications for #{conference.short_title}") + expect(flash[:notice]).to match("You have subscribed to receive email notifications for #{conference.title}") end it 'subscribes user to conference' do @@ -47,7 +47,7 @@ describe SubscriptionsController do it 'shows success message in flash notice' do delete :destroy, conference_id: conference.short_title - expect(flash[:notice]).to match("You have been unsubscribed and now you will not be receiving email notifications for #{conference.short_title}.") + expect(flash[:notice]).to match("You have unsubscribed and you will not be receiving email notifications for #{conference.title}.") end end end From 9e154a1dcd5d28102d852eb6a26eefba2cf054db Mon Sep 17 00:00:00 2001 From: siddhantbajaj Date: Mon, 10 Jul 2017 20:43:27 +0530 Subject: [PATCH 08/74] Added Token field for physical_ticket Added token field in physical_ticket model. This token will also be stored in the qr code and will uniqely identify the ticket. --- app/controllers/physical_ticket_controller.rb | 2 +- app/models/physical_ticket.rb | 15 +++++++++++++++ app/views/admin/physical_ticket/index.html.haml | 6 +++--- app/views/physical_ticket/index.html.haml | 4 ++-- app/views/physical_ticket/show.html.haml | 2 +- ...0170721001700_add_index_to_physical_tickets.rb | 6 ++++++ db/schema.rb | 5 ++++- .../physical_ticket_controller_spec.rb | 2 +- 8 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 db/migrate/20170721001700_add_index_to_physical_tickets.rb diff --git a/app/controllers/physical_ticket_controller.rb b/app/controllers/physical_ticket_controller.rb index 6bb7f50b..fb25ae63 100644 --- a/app/controllers/physical_ticket_controller.rb +++ b/app/controllers/physical_ticket_controller.rb @@ -1,7 +1,7 @@ class PhysicalTicketController < ApplicationController before_action :authenticate_user! load_resource :conference, find_by: :short_title - load_and_authorize_resource + load_and_authorize_resource find_by: :token authorize_resource :conference_registrations, class: Registration def index diff --git a/app/models/physical_ticket.rb b/app/models/physical_ticket.rb index 6142c875..689d118a 100644 --- a/app/models/physical_ticket.rb +++ b/app/models/physical_ticket.rb @@ -4,4 +4,19 @@ class PhysicalTicket < ActiveRecord::Base has_one :conference, through: :ticket_purchase has_one :user, through: :ticket_purchase has_many :ticket_scannings + + before_create :set_token + + private + + def set_token + self.token = generate_token + end + + def generate_token + loop do + token = SecureRandom.hex(10) + break token unless PhysicalTicket.exists?(token: token) + end + end end diff --git a/app/views/admin/physical_ticket/index.html.haml b/app/views/admin/physical_ticket/index.html.haml index fe128932..96c5911f 100644 --- a/app/views/admin/physical_ticket/index.html.haml +++ b/app/views/admin/physical_ticket/index.html.haml @@ -32,12 +32,12 @@ .btn-group = link_to 'Show', conference_physical_ticket_path(@conference.short_title, - physical_ticket.id), + physical_ticket.token), class: 'btn btn-primary' = link_to 'Generate PDF', conference_physical_ticket_path(@conference.short_title, - physical_ticket.id, - format: :pdf), + physical_ticket.token, + format: :pdf), class: 'button btn btn-default btn-info' - else %h5 No Tickets sold! diff --git a/app/views/physical_ticket/index.html.haml b/app/views/physical_ticket/index.html.haml index b077400b..cb0710ad 100644 --- a/app/views/physical_ticket/index.html.haml +++ b/app/views/physical_ticket/index.html.haml @@ -24,11 +24,11 @@ .btn-group = link_to 'Show', conference_physical_ticket_path(@conference.short_title, - physical_ticket.id), + physical_ticket.token), class: 'btn btn-primary' = link_to 'Generate PDF', conference_physical_ticket_path(@conference.short_title, - physical_ticket.id, + physical_ticket.token, format: :pdf), class: 'button btn btn-default btn-info' - else diff --git a/app/views/physical_ticket/show.html.haml b/app/views/physical_ticket/show.html.haml index 26fa7853..067f0a9b 100644 --- a/app/views/physical_ticket/show.html.haml +++ b/app/views/physical_ticket/show.html.haml @@ -72,6 +72,6 @@ %p.text-left = link_to 'Generate PDF', conference_physical_ticket_path(@conference.short_title, - @physical_ticket.id, + @physical_ticket.token, format: :pdf), class: 'button btn btn-default btn-info' diff --git a/db/migrate/20170721001700_add_index_to_physical_tickets.rb b/db/migrate/20170721001700_add_index_to_physical_tickets.rb new file mode 100644 index 00000000..46a0b8c0 --- /dev/null +++ b/db/migrate/20170721001700_add_index_to_physical_tickets.rb @@ -0,0 +1,6 @@ +class AddIndexToPhysicalTickets < ActiveRecord::Migration + def change + add_column :physical_tickets, :token, :string + add_index :physical_tickets, :token, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 64ac0393..d49dfa03 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: 20170711102511) do +ActiveRecord::Schema.define(version: 20170721001700) do create_table "ahoy_events", force: :cascade do |t| t.integer "visit_id" @@ -317,8 +317,11 @@ ActiveRecord::Schema.define(version: 20170711102511) do t.integer "ticket_purchase_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "token" end + add_index "physical_tickets", ["token"], name: "index_physical_tickets_on_token", unique: true + create_table "programs", force: :cascade do |t| t.integer "conference_id" t.integer "rating", default: 0 diff --git a/spec/controllers/physical_ticket_controller_spec.rb b/spec/controllers/physical_ticket_controller_spec.rb index 7a430479..863e27af 100644 --- a/spec/controllers/physical_ticket_controller_spec.rb +++ b/spec/controllers/physical_ticket_controller_spec.rb @@ -9,7 +9,7 @@ describe PhysicalTicketController do describe 'GET #show' do before :each do sign_in user - get :show, id: physical_ticket.id, conference_id: conference.short_title + get :show, id: physical_ticket.token, conference_id: conference.short_title end it 'assigns ticket_layout' do From 906f08baf867a11379061a70bb54cd516e6214ea Mon Sep 17 00:00:00 2001 From: siddhantbajaj Date: Mon, 17 Jul 2017 21:22:53 +0530 Subject: [PATCH 09/74] Send mail on ticket confirmation. Added mailer method to send ticket confirmation email along with the attached pdf for ticket. --- app/mailers/mailbot.rb | 16 ++++++++++++++++ app/models/ticket_purchase.rb | 1 + .../ticket_confirmation_template.text.erb | 8 ++++++++ 3 files changed, 25 insertions(+) create mode 100644 app/views/mailbot/ticket_confirmation_template.text.erb diff --git a/app/mailers/mailbot.rb b/app/mailers/mailbot.rb index 3d70f18d..f5b7f375 100644 --- a/app/mailers/mailbot.rb +++ b/app/mailers/mailbot.rb @@ -8,6 +8,22 @@ class Mailbot < ActionMailer::Base conference.email_settings.registration_body)) end + def ticket_confirmation_mail(ticket_purchase) + @ticket_purchase = ticket_purchase + @conference = ticket_purchase.conference + @user = ticket_purchase.user + + PhysicalTicket.last(ticket_purchase.quantity).each do |physical_ticket| + pdf = TicketPdf.new(@conference, @user, physical_ticket, @conference.ticket_layout.to_sym, "ticket_for_#{@conference.short_title}_#{physical_ticket.id}") + attachments["ticket_for_#{@conference.short_title}_#{physical_ticket.id}"] = pdf.render + end + + mail(to: @user.email, + from: @conference.contact.email, + template_name: 'ticket_confirmation_template', + subject: "#{@conference.title} | Ticket Confirmation and PDF!") + end + def acceptance_mail(event) conference = event.program.conference diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index a19678a7..a2ed2747 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -69,6 +69,7 @@ class TicketPurchase < ActiveRecord::Base PhysicalTicket.transaction do quantity.times { physical_tickets.create } end + Mailbot.ticket_confirmation_mail(self).deliver_later end end diff --git a/app/views/mailbot/ticket_confirmation_template.text.erb b/app/views/mailbot/ticket_confirmation_template.text.erb new file mode 100644 index 00000000..08f5175a --- /dev/null +++ b/app/views/mailbot/ticket_confirmation_template.text.erb @@ -0,0 +1,8 @@ +Dear <%= @user.name %>, + +Thanks! You have successfully booked <%= @ticket_purchase.quantity %> <%= @ticket_purchase.ticket.title %> ticket(s) for the event <%= @conference.title %>. Your transaction id is <%= @ticket_purchase.id %>. + +Please, find the ticket(s) pdf attached. + +Best wishes, +<%= @conference.title %> Team From 80a95442795a833db09d4a7614b0c77aa3058cd4 Mon Sep 17 00:00:00 2001 From: shlok007 Date: Fri, 14 Jul 2017 21:09:28 +0530 Subject: [PATCH 10/74] move admin/users to be accessed only by site admins --- app/models/admin_ability.rb | 1 - spec/features/cfp_ability_spec.rb | 6 ++++++ spec/features/info_desk_ability_spec.rb | 6 ++++++ spec/features/organizer_ability_spec.rb | 6 ++++++ spec/models/admin_ability_spec.rb | 4 ++++ 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/models/admin_ability.rb b/app/models/admin_ability.rb index 4244d049..716aecb0 100644 --- a/app/models/admin_ability.rb +++ b/app/models/admin_ability.rb @@ -18,7 +18,6 @@ class AdminAbility end def common_abilities_for_roles(user) - can :manage, User, id: user.id can :manage, Registration, user_id: user.id can :index, Conference diff --git a/spec/features/cfp_ability_spec.rb b/spec/features/cfp_ability_spec.rb index 46db11c2..d40a54a4 100644 --- a/spec/features/cfp_ability_spec.rb +++ b/spec/features/cfp_ability_spec.rb @@ -269,6 +269,12 @@ feature 'Has correct abilities' do visit edit_admin_conference_resource_path(conference.short_title, conference.resources.first) expect(current_path).to eq(edit_admin_conference_resource_path(conference.short_title, conference.resources.first)) + visit admin_users_path + expect(current_path).to eq(root_path) + + visit admin_user_path(user_cfp) + expect(current_path).to eq(root_path) + visit admin_revision_history_path expect(current_path).to eq(root_path) end diff --git a/spec/features/info_desk_ability_spec.rb b/spec/features/info_desk_ability_spec.rb index 20aa586b..c005f9de 100644 --- a/spec/features/info_desk_ability_spec.rb +++ b/spec/features/info_desk_ability_spec.rb @@ -237,6 +237,12 @@ feature 'Has correct abilities' do visit admin_conference_program_tracks_path(conference.short_title) expect(current_path).to eq(root_path) + visit admin_users_path + expect(current_path).to eq(root_path) + + visit admin_user_path(user_info_desk) + expect(current_path).to eq(root_path) + visit admin_conference_emails_path(conference.short_title) expect(current_path).to eq(root_path) end diff --git a/spec/features/organizer_ability_spec.rb b/spec/features/organizer_ability_spec.rb index 06a2a836..3ed6ee39 100644 --- a/spec/features/organizer_ability_spec.rb +++ b/spec/features/organizer_ability_spec.rb @@ -266,6 +266,12 @@ feature 'Has correct abilities' do visit edit_admin_conference_resource_path(conference.short_title, conference.resources.first) expect(current_path).to eq(edit_admin_conference_resource_path(conference.short_title, conference.resources.first)) + visit admin_users_path + expect(current_path).to eq(root_path) + + visit admin_user_path(user_organizer) + expect(current_path).to eq(root_path) + visit admin_revision_history_path expect(current_path).to eq(admin_revision_history_path) end diff --git a/spec/models/admin_ability_spec.rb b/spec/models/admin_ability_spec.rb index 9baae4a9..5398b7d2 100644 --- a/spec/models/admin_ability_spec.rb +++ b/spec/models/admin_ability_spec.rb @@ -64,6 +64,10 @@ describe 'User with admin role' do it{ should_not be_able_to(:edit, Role.find_by(name: 'organization_admin', resource: other_organization)) } it{ should_not be_able_to(:show, Role.find_by(name: 'organization_admin', resource: other_organization)) } + it{ should_not be_able_to(:new, User.new) } + it{ should_not be_able_to(:create, User.new) } + it{ should_not be_able_to(:manage, User) } + %w[organizer cfp info_desk volunteers_coordinator].each do |role| it{ should_not be_able_to(:toggle_user, Role.find_by(name: role, resource: other_conference)) } it{ should_not be_able_to(:update, Role.find_by(name: role, resource: other_conference)) } From 50c66e2e0221b84aade66cb7aec5a5cb619b2d89 Mon Sep 17 00:00:00 2001 From: Wexpo Lyu Date: Fri, 4 Aug 2017 04:01:42 -0500 Subject: [PATCH 11/74] Explicitly add `.pdf` to make it a PDF file. Some SMTP providers would treat all files w/o suffix as `.txt`s. This patch would make it work in most cases. --- app/mailers/mailbot.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/mailers/mailbot.rb b/app/mailers/mailbot.rb index f5b7f375..837e2c1e 100644 --- a/app/mailers/mailbot.rb +++ b/app/mailers/mailbot.rb @@ -15,7 +15,7 @@ class Mailbot < ActionMailer::Base PhysicalTicket.last(ticket_purchase.quantity).each do |physical_ticket| pdf = TicketPdf.new(@conference, @user, physical_ticket, @conference.ticket_layout.to_sym, "ticket_for_#{@conference.short_title}_#{physical_ticket.id}") - attachments["ticket_for_#{@conference.short_title}_#{physical_ticket.id}"] = pdf.render + attachments["ticket_for_#{@conference.short_title}_#{physical_ticket.id}.pdf"] = pdf.render end mail(to: @user.email, From a4b82bc83060897b60d002e46433cd8feb18680f Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 24 May 2017 18:43:40 +0300 Subject: [PATCH 12/74] Update sass-rails to 5.0.6 --- Gemfile.lock | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8f7ef5d6..ae118542 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -473,11 +473,12 @@ GEM rubyzip (1.2.1) safe_yaml (1.0.4) sass (3.2.19) - sass-rails (4.0.4) - railties (>= 4.0.0, < 5.0) - sass (~> 3.2.2) - sprockets (~> 2.8, < 2.12) - sprockets-rails (~> 2.0) + sass-rails (5.0.6) + railties (>= 4.0.0, < 6) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) selectize-rails (0.12.4) shellany (0.0.1) shoulda-matchers (2.8.0) From da8b72343bce28269599ae6635af732b5b119037 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Tue, 18 Jul 2017 10:15:53 +0300 Subject: [PATCH 13/74] Update font-awesome-rails to 4.7.0.2 --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ae118542..17f96de1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -180,8 +180,8 @@ GEM fastimage (2.0.0) addressable (~> 2) ffi (1.9.18) - font-awesome-rails (4.1.0.0) - railties (>= 3.2, < 5.0) + font-awesome-rails (4.7.0.2) + railties (>= 3.2, < 5.2) formatador (0.2.5) formtastic (3.1.3) actionpack (>= 3.2.13) From d94fdb2a28520e96f03d0a5192a00ba7086d2880 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Tue, 18 Jul 2017 10:16:32 +0300 Subject: [PATCH 14/74] Update dotenv-rails to 2.2.1 --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 17f96de1..3e2deeb4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -162,10 +162,10 @@ GEM docile (1.1.5) domain_name (0.5.20160310) unf (>= 0.0.5, < 1.0.0) - dotenv (2.1.1) - dotenv-rails (2.1.1) - dotenv (= 2.1.1) - railties (>= 4.0, < 5.1) + dotenv (2.2.1) + dotenv-rails (2.2.1) + dotenv (= 2.2.1) + railties (>= 3.2, < 5.2) erubis (2.7.0) execjs (2.6.0) factory_girl (4.5.0) From f74f2d3f52f9fb3a8c68cd50aab07192bad74c1e Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 24 May 2017 18:53:16 +0300 Subject: [PATCH 15/74] Update responders to 2.4.0 --- Gemfile.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3e2deeb4..0b90d844 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -425,8 +425,9 @@ GEM redcarpet (3.2.3) referer-parser (0.2.1) request_store (1.1.0) - responders (2.1.1) - railties (>= 4.2.0, < 5.1) + responders (2.4.0) + actionpack (>= 4.2.0, < 5.3) + railties (>= 4.2.0, < 5.3) rest-client (1.8.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 3.0) From d9649a4446bce677eebc753636bb00bd66069063 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 24 May 2017 19:36:42 +0300 Subject: [PATCH 16/74] Update jquery-rails to 4.3.1 --- Gemfile.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 0b90d844..d8c8c850 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -235,8 +235,9 @@ GEM jquery-datatables-rails (2.2.3) jquery-rails sass-rails - jquery-rails (3.1.4) - railties (>= 3.0, < 5.0) + jquery-rails (4.3.1) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) thor (>= 0.14, < 2.0) jquery-ui-rails (4.2.1) railties (>= 3.2.16) From 7e2bb8043f2612a16ec49e20defdb57a405f2c90 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 24 May 2017 19:49:48 +0300 Subject: [PATCH 17/74] Update coffee-rails to 4.2.2 --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index d8c8c850..216b7dc0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -117,13 +117,13 @@ GEM rest-client cocoon (1.2.6) coderay (1.1.1) - coffee-rails (4.1.1) + coffee-rails (4.2.2) coffee-script (>= 2.2.0) - railties (>= 4.0.0, < 5.1.x) + railties (>= 4.0.0) coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.10.0) + coffee-script-source (1.12.2) countable-rails (0.0.1) railties (>= 3.1) countries (1.2.5) From 57f450e857f19d36a86ee61c38aa33165d06c4cb Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 24 May 2017 20:26:50 +0300 Subject: [PATCH 18/74] Update delayed_job_active_record to 4.1.2 --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 216b7dc0..8246c724 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -145,10 +145,10 @@ GEM dante (0.2.0) database_cleaner (1.3.0) debug_inspector (0.0.2) - delayed_job (4.1.1) - activesupport (>= 3.0, < 5.0) - delayed_job_active_record (4.1.0) - activerecord (>= 3.0, < 5) + delayed_job (4.1.3) + activesupport (>= 3.0, < 5.2) + delayed_job_active_record (4.1.2) + activerecord (>= 3.0, < 5.2) delayed_job (>= 3.0, < 5) devise (4.2.0) bcrypt (~> 3.0) From 4e1d45382cb27affaacf38920cd6de60fbb7c709 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 24 May 2017 20:35:38 +0300 Subject: [PATCH 19/74] Update awesome_nested_set to 3.1.3 --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 1b0a3c1d..08170e30 100644 --- a/Gemfile +++ b/Gemfile @@ -57,7 +57,7 @@ gem 'unobtrusive_flash', '>=3' gem 'transitions', :require => %w( transitions active_record/transitions ) # for comments -gem 'awesome_nested_set', '~> 3.0.0.rc.5' +gem 'awesome_nested_set', '~> 3.1.3' gem 'acts_as_commentable_with_threading' # as templating language diff --git a/Gemfile.lock b/Gemfile.lock index 8246c724..85e7438f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -72,8 +72,8 @@ GEM autoprefixer-rails (5.1.9) execjs json - awesome_nested_set (3.0.0.rc.5) - activerecord (>= 4.0.0, < 5) + awesome_nested_set (3.1.3) + activerecord (>= 4.0.0, < 5.2) aws_cf_signer (0.1.3) axlsx_rails (0.2.0) axlsx (>= 2.0.1) @@ -569,7 +569,7 @@ DEPENDENCIES acts_as_list ahoy_matey autoprefixer-rails - awesome_nested_set (~> 3.0.0.rc.5) + awesome_nested_set (~> 3.1.3) axlsx! axlsx_rails bootstrap-sass (~> 3.3.4.1) From 27996531248eadeb3437fc34d30a817db5f21963 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 24 May 2017 21:05:24 +0300 Subject: [PATCH 20/74] Update omniauth to 1.6.1 --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 85e7438f..1288d067 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -219,7 +219,7 @@ GEM rubocop (>= 0.47.0) sysexits (~> 1.1) hashdiff (0.3.4) - hashie (2.1.1) + hashie (3.5.5) hike (1.2.3) hoptoad_notifier (2.4.11) activesupport @@ -302,9 +302,9 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (~> 1.2) - omniauth (1.2.1) - hashie (>= 1.2, < 3) - rack (~> 1.0) + omniauth (1.6.1) + hashie (>= 3.4.6, < 3.6.0) + rack (>= 1.6.2, < 3) omniauth-facebook (1.6.0) omniauth-oauth2 (~> 1.1) omniauth-github (1.1.2) From 2824336ead6f6ed5ea938f3410168df63ec15433 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 24 May 2017 21:49:53 +0300 Subject: [PATCH 21/74] Update sprockets to 3.7.1 --- Gemfile.lock | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1288d067..ecc7ec98 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -124,6 +124,7 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) + concurrent-ruby (1.0.5) countable-rails (0.0.1) railties (>= 3.1) countries (1.2.5) @@ -220,7 +221,6 @@ GEM sysexits (~> 1.1) hashdiff (0.3.4) hashie (3.5.5) - hike (1.2.3) hoptoad_notifier (2.4.11) activesupport builder @@ -497,11 +497,9 @@ GEM spring (1.6.3) spring-commands-rspec (1.0.4) spring (>= 0.9.1) - sprockets (2.11.3) - hike (~> 1.2) - multi_json (~> 1.0) - rack (~> 1.0) - tilt (~> 1.1, != 1.3.0) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) sprockets-rails (2.3.3) actionpack (>= 3.0) activesupport (>= 3.0) From cb8010bafe3bb914c0ca72ea4f49879a486c1fe9 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 24 May 2017 22:03:39 +0300 Subject: [PATCH 22/74] Update omniauth-oauth2 to 1.4.0 --- Gemfile.lock | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ecc7ec98..8a1c97f7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -296,12 +296,12 @@ GEM notiffany (0.1.1) nenv (~> 0.1) shellany (~> 0.0) - oauth2 (0.9.4) - faraday (>= 0.8, < 0.10) + oauth2 (1.3.1) + faraday (>= 0.8, < 0.12) jwt (~> 1.0) multi_json (~> 1.3) multi_xml (~> 0.5) - rack (~> 1.2) + rack (>= 1.2, < 3) omniauth (1.6.1) hashie (>= 3.4.6, < 3.6.0) rack (>= 1.6.2, < 3) @@ -313,10 +313,8 @@ GEM omniauth-google-oauth2 (0.2.4) omniauth (~> 1.0) omniauth-oauth2 (~> 1.1) - omniauth-oauth2 (1.1.2) - faraday (>= 0.8, < 0.10) - multi_json (~> 1.3) - oauth2 (~> 0.9.3) + omniauth-oauth2 (1.4.0) + oauth2 (~> 1.0) omniauth (~> 1.2) omniauth-openid (1.0.1) omniauth (~> 1.0) From 9b7562fe2e394a47bc4c91c3666fbe09556cba7b Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Sun, 4 Jun 2017 19:50:48 +0300 Subject: [PATCH 23/74] Update autoprefixer-rails to 7.1.1 --- Gemfile.lock | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8a1c97f7..faaa8246 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -69,9 +69,8 @@ GEM uuidtools arel (6.0.4) ast (2.3.0) - autoprefixer-rails (5.1.9) + autoprefixer-rails (7.1.1) execjs - json awesome_nested_set (3.1.3) activerecord (>= 4.0.0, < 5.2) aws_cf_signer (0.1.3) From 033dbe4c40fd9c428b0167e84668c11e127de9e7 Mon Sep 17 00:00:00 2001 From: Wexpo Lyu Date: Fri, 4 Aug 2017 01:01:36 -0500 Subject: [PATCH 24/74] Fix the dropdown to get enough bottom padding. --- app/assets/stylesheets/osem-navbar.css.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/osem-navbar.css.scss b/app/assets/stylesheets/osem-navbar.css.scss index ef05c0a9..bfaac9ac 100644 --- a/app/assets/stylesheets/osem-navbar.css.scss +++ b/app/assets/stylesheets/osem-navbar.css.scss @@ -29,7 +29,7 @@ } } .dropdown-menu { - padding: 17px 17px 0px 17px; + padding: 17px; min-width: 225px; } } From 32c5af2a17f08162e113c8d32298b71b9839a0e9 Mon Sep 17 00:00:00 2001 From: nasia Date: Thu, 20 Jul 2017 14:20:46 +0300 Subject: [PATCH 25/74] Add booths to non admin --- .haml-lint_todo.yml | 6 +- .rubocop_todo.yml | 2 + app/controllers/admin/booths_controller.rb | 22 ++-- app/controllers/booths_controller.rb | 97 +++++++++++++++ app/helpers/format_helper.rb | 13 +++ app/models/ability.rb | 6 + app/models/booth.rb | 10 +- app/views/admin/booths/_form.html.haml | 33 ------ app/views/admin/booths/edit.html.haml | 2 +- app/views/admin/booths/index.html.haml | 11 +- app/views/admin/booths/new.html.haml | 2 +- app/views/admin/booths/show.html.haml | 3 +- app/views/booths/_form.html.haml | 30 +++++ app/views/booths/edit.html.haml | 6 + app/views/booths/index.html.haml | 51 ++++++++ app/views/booths/new.html.haml | 4 + app/views/booths/show.html.haml | 60 ++++++++++ config/routes.rb | 8 +- .../admin/booths_controller_spec.rb | 18 --- spec/controllers/booths_controller_spec.rb | 110 ++++++++++++++++++ spec/factories/booths.rb | 1 + spec/models/booth_spec.rb | 17 +-- 22 files changed, 426 insertions(+), 86 deletions(-) create mode 100644 app/controllers/booths_controller.rb delete mode 100644 app/views/admin/booths/_form.html.haml create mode 100644 app/views/booths/_form.html.haml create mode 100644 app/views/booths/edit.html.haml create mode 100644 app/views/booths/index.html.haml create mode 100644 app/views/booths/new.html.haml create mode 100644 app/views/booths/show.html.haml create mode 100644 spec/controllers/booths_controller_spec.rb diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index b42ccb06..52d5f5ca 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -11,7 +11,6 @@ linters: # Offense count: 945 LineLength: exclude: - - "app/views/admin/booths/_form.html.haml" - "app/views/admin/booths/index.html.haml" - "app/views/admin/booths/show.html.haml" - "app/views/admin/campaigns/_form.html.haml" @@ -109,6 +108,9 @@ linters: - "app/views/admin/versions/_object_desc_and_link.html.haml" - "app/views/admin/versions/index.html.haml" - "app/views/admin/volunteers/index.html.haml" + - "app/views/booths/_form.html.haml" + - "app/views/booths/index.html.haml" + - "app/views/booths/show.html.haml" - "app/views/commercials/edit.html.haml" - "app/views/commercials/new.html.haml" - "app/views/conference_registrations/_form.html.haml" @@ -369,6 +371,7 @@ linters: - "app/views/admin/tracks/show.html.haml" - "app/views/admin/venues/show.html.haml" - "app/views/admin/versions/index.html.haml" + - "app/views/booths/index.html.haml" - "app/views/conference_registrations/_form.html.haml" - "app/views/conference_registrations/_ticket.html.haml" - "app/views/conference_registrations/show.html.haml" @@ -397,6 +400,7 @@ linters: - "app/views/admin/schedules/_day_tab.html.haml" - "app/views/admin/schedules/_event.html.haml" - "app/views/admin/versions/_object_desc_and_link.html.haml" + - "app/views/booths/index.html.haml" - "app/views/schedules/_carousel.html.haml" # Offense count: 29 diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7a9ab03c..8d752446 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -333,6 +333,8 @@ Metrics/MethodLength: # Configuration parameters: CountComments. Metrics/ModuleLength: Max: 159 + Exclude: + - 'app/helpers/format_helper.rb' # Offense count: 15 Metrics/PerceivedComplexity: diff --git a/app/controllers/admin/booths_controller.rb b/app/controllers/admin/booths_controller.rb index f5096c34..e5b55552 100644 --- a/app/controllers/admin/booths_controller.rb +++ b/app/controllers/admin/booths_controller.rb @@ -7,9 +7,13 @@ module Admin def show; end - def new; end + def new + @url = admin_conference_booths_path(@conference.short_title) + end def create + @url = admin_conference_booths_path(@conference.short_title) + @booth = @conference.booths.new(booth_params) @booth.submitter = current_user @@ -23,9 +27,13 @@ module Admin end end - def edit; end + def edit + @url = admin_conference_booth_path(@conference.short_title, @booth.id) + end def update + @url = admin_conference_booth_path(@conference.short_title, @booth.id) + @booth.update_attributes(booth_params) if @booth.save @@ -38,16 +46,6 @@ module Admin end end - def destroy - if @booth.destroy - redirect_to admin_conference_booths_path, - notice: 'Booth successfully destroyed.' - else - redirect_to admin_conference_booths_path, - error: "Booth couldn't be deleted. #{@booth.errors.full_messages.join('. ')}." - end - end - def accept update_state(:accept, 'Booth accepted!') end diff --git a/app/controllers/booths_controller.rb b/app/controllers/booths_controller.rb new file mode 100644 index 00000000..99fde3bc --- /dev/null +++ b/app/controllers/booths_controller.rb @@ -0,0 +1,97 @@ +class BoothsController < ApplicationController + before_action :authenticate_user! + load_resource :conference, find_by: :short_title + load_and_authorize_resource through: :conference + skip_authorize_resource only: [:withdraw, :confirm, :restart] + + def index + @booths = current_user.booths.where(conference_id: @conference.id).uniq + end + + def show; end + + def new + @url = conference_booths_path(@conference.short_title) + end + + def create + @url = conference_booths_path(@conference.short_title) + + @booth.submitter = current_user + + if @booth.save + redirect_to conference_booths_path, + notice: 'Booth successfully created.' + else + flash[:error] = "Creating booth failed. #{@booth.errors.full_messages.to_sentence}." + render :new + end + end + + def edit + @url = conference_booth_path(@conference.short_title, @booth.id) + end + + def update + @url = conference_booth_path(@conference.short_title, @booth.id) + @booth.update_attributes(booth_params) + + if @booth.save + redirect_to conference_booths_path, + notice: 'Booth successfully updated!' + else + flash[:error] = "Booth could not be updated. #{@booth.errors.full_messages.to_sentence}." + end + end + + def destroy; end + + def withdraw + authorize! :update, @booth + @url = conference_booth_path(@conference.short_title, @booth.id) + + @booth.withdraw! + + if @booth.save + redirect_to conference_booths_path, + notice: 'Booth successfully withdrawn' + else + flash[:error] = "Booth could not be withdrawn. #{@booth.errors.full_messages.to_sentence}." + end + end + + def confirm + authorize! :update, @booth + @url = conference_booth_path(@conference.short_title, @booth.id) + + @booth.confirm! + + if @booth.save + redirect_to conference_booths_path, + notice: 'Booth successfully confirmed' + else + flash[:error] = "Booth could not be confirmed. #{@booth.errors.full_messages.to_sentence}." + end + end + + def restart + authorize! :update, @booth + @url = conference_booth_path(@conference.short_title, @booth.id) + + @booth.restart! + + if @booth.save + redirect_to conference_booths_path, + notice: 'Booth successfully re-submitted' + else + flash[:error] = "Booth could not be re-submitted. #{@booth.errors.full_messages.to_sentence}." + end + end + + private + + def booth_params + params.require(:booth).permit(:title, :description, :reasoning, :state, :picture, :conference_id, + :created_at, :updated_at, :submitter_relationship, :website_url, responsible_ids: []) + end +end diff --git a/app/helpers/format_helper.rb b/app/helpers/format_helper.rb index 334c70bf..65578ba6 100644 --- a/app/helpers/format_helper.rb +++ b/app/helpers/format_helper.rb @@ -15,6 +15,19 @@ module FormatHelper end end + def booth_status_icon(booth) + case booth.state + when 'new', 'to_reject', 'to_accept' + 'fa-eye' + when 'accepted' + 'fa-check text-muted' + when 'confirmed' + 'fa-check text-success' + when 'rejected', 'withdrawn', 'canceled' + 'fa-ban' + end + end + def event_progress_color(progress) progress = progress.to_i if progress == 100 diff --git a/app/models/ability.rb b/app/models/ability.rb index afc51a68..0ebabfdc 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -75,6 +75,12 @@ class Ability can [:new, :create], Payment, user_id: user.id can [:index, :show], PhysicalTicket, user: user + can [:new, :create], Booth + + can [:edit, :update, :index, :show], Booth do |booth| + booth.users.include?(user) + end + can [:create, :destroy], Subscription, user_id: user.id can [:new, :create], Event do |event| diff --git a/app/models/booth.rb b/app/models/booth.rb index 5c20550a..853bca9e 100644 --- a/app/models/booth.rb +++ b/app/models/booth.rb @@ -35,12 +35,16 @@ class Booth < ActiveRecord::Base state :to_reject state :rejected state :canceled + state :confirmed event :restart do - transitions to: :new, from: [:withdrawn, :to_accept, :to_reject, :canceled] + transitions to: :new, from: [:withdrawn, :rejected, :canceled] end event :withdraw do - transitions to: :withdrawn, from: [:new, :to_accept, :accepted, :to_reject, :rejected] + transitions to: :withdrawn, from: [:new, :to_accept, :accepted, :to_reject, :rejected, :confirmed] + end + event :confirm do + transitions to: :confirmed, from: [:accepted] end event :to_accept do transitions to: :to_accept, from: [:new, :to_reject] @@ -55,7 +59,7 @@ class Booth < ActiveRecord::Base transitions to: :rejected, from: [:new, :to_reject] end event :cancel do - transitions to: :canceled, from: [:accepted, :rejected, :to_accept, :to_reject] + transitions to: :canceled, from: [:accepted, :rejected, :to_accept, :to_reject, :confirmed] end end diff --git a/app/views/admin/booths/_form.html.haml b/app/views/admin/booths/_form.html.haml deleted file mode 100644 index f70079fa..00000000 --- a/app/views/admin/booths/_form.html.haml +++ /dev/null @@ -1,33 +0,0 @@ -.row - .col-md-12 - .page-header - %title Request a Booth -.row - .col-md-8 - = semantic_form_for(@booth, url: @booth.new_record? ? admin_conference_booths_path(@conference.short_title) : admin_conference_booth_path(@conference.short_title, @booth.id), html: { multipart: true }) do |f| - = f.input :title, as: :string, autofocus: true, required: true - = f.input :description, input_html: { rows: 5, data: { provide: 'markdown-editable' } }, required: true, - hint: 'This field becomes public upon request acceptance' - = f.input :reasoning, input_html: { rows: 5, data: { provide: 'markdown-editable' } }, required: true, - label: 'How it fits the conference' - = f.input :submitter_relationship, input_html: { rows: 5, data: { provide: 'markdown-editable' } }, required: true, - label: 'Submitter\'s relation', - hint: 'e.g. employee, comunity manager, etc' - = f.input :website_url - = responsibles_selector_input f - = image_tag f.object.picture.thumb.url if f.object.picture? - = f.input :picture - - %p.text-right - - if @booth.new_record? - = f.submit 'Create Booth Request', class: 'btn btn-success' - - else - = f.submit 'Update Booth Request', class: 'btn btn-success' - -:javascript - $(document).ready(function() { - $('#booth_responsible_ids').selectize({ - plugins: ['remove_button'], - minItems: 2 - } ) - }); diff --git a/app/views/admin/booths/edit.html.haml b/app/views/admin/booths/edit.html.haml index 9f9a5a61..0cac17e7 100644 --- a/app/views/admin/booths/edit.html.haml +++ b/app/views/admin/booths/edit.html.haml @@ -2,4 +2,4 @@ Editing = @booth.title -= render 'form' += render 'booths/form' diff --git a/app/views/admin/booths/index.html.haml b/app/views/admin/booths/index.html.haml index 15833bd7..27f505ad 100644 --- a/app/views/admin/booths/index.html.haml +++ b/app/views/admin/booths/index.html.haml @@ -41,8 +41,9 @@ = link_to booth.submitter.name, admin_user_path(booth.submitter) if booth.submitter %td .responsibles - - booth.responsibles.each do |responsible| + - booth.responsibles.each_with_index do |responsible, i| = link_to responsible.name, admin_user_path(responsible) + = ", " unless i == booth.responsibles.length - 1 %td .btn-group %button{ type: 'button', class: 'btn btn-link dropdown-toggle', 'data-toggle' => 'dropdown' } @@ -51,9 +52,5 @@ %ul.dropdown-menu{ role: 'menu' } = render 'change_state_dropdown', booth: booth %td - .btn-group{ role: "group" } - = link_to 'Edit', edit_admin_conference_booth_path(@conference.short_title, booth.id), - class: 'btn btn-primary' - = link_to 'Delete', admin_conference_booth_path(@conference.short_title, booth.id), - method: :delete, class: 'btn btn-danger', - data: {confirm: "Do you really want to delete this booth request?"} + = link_to 'Edit', edit_admin_conference_booth_path(@conference.short_title, booth.id), + class: 'btn btn-primary' diff --git a/app/views/admin/booths/new.html.haml b/app/views/admin/booths/new.html.haml index db1c4736..99ec9b20 100644 --- a/app/views/admin/booths/new.html.haml +++ b/app/views/admin/booths/new.html.haml @@ -1,3 +1,3 @@ %h1 New booth -= render 'form' += render 'booths/form' diff --git a/app/views/admin/booths/show.html.haml b/app/views/admin/booths/show.html.haml index 8c692bc4..6ed823e5 100644 --- a/app/views/admin/booths/show.html.haml +++ b/app/views/admin/booths/show.html.haml @@ -39,12 +39,13 @@ %td.col-md-2 %b Responsibles %td - - @booth.responsibles.each do |responsibles| + - @booth.responsibles.each_with_index do |responsibles, i| .responsibles = link_to responsibles.name, admin_user_path(responsibles) ( = responsibles.email ) + = " , " unless i == @booth.responsibles.length - 1 %tr %td.col-md-2 %b Submitted on diff --git a/app/views/booths/_form.html.haml b/app/views/booths/_form.html.haml new file mode 100644 index 00000000..55a51432 --- /dev/null +++ b/app/views/booths/_form.html.haml @@ -0,0 +1,30 @@ +.container + .row + .col-md-8 + = semantic_form_for(@booth, url: @url, html: { multipart: true }) do |f| + = f.input :title, as: :string, autofocus: true, required: true + = f.input :description, input_html: { rows: 5, data: { provide: 'markdown-editable' } }, required: true, + hint: 'This field becomes public upon request acceptance' + = f.input :reasoning, input_html: { rows: 5, data: { provide: 'markdown-editable' } }, required: true, + label: 'How it fits the conference' + = f.input :submitter_relationship, input_html: { rows: 5, data: { provide: 'markdown-editable' } }, required: true, + label: 'Submitter\'s relation', + hint: 'e.g. employee, comunity manager, etc' + = f.input :website_url + = responsibles_selector_input f + = image_tag f.object.picture.thumb.url if f.object.picture? + = f.input :picture + + %p.text-right + - if @booth.new_record? + = f.submit 'Create Booth Request', class: 'btn btn-success' + - else + = f.submit 'Update Booth Request', class: 'btn btn-success' + + :javascript + $(document).ready(function() { + $('#booth_responsible_ids').selectize({ + plugins: ['remove_button'], + minItems: 2 + } ) + }); diff --git a/app/views/booths/edit.html.haml b/app/views/booths/edit.html.haml new file mode 100644 index 00000000..9f60196d --- /dev/null +++ b/app/views/booths/edit.html.haml @@ -0,0 +1,6 @@ +.container + %h1 + Editing + = @booth.title + + = render 'form' diff --git a/app/views/booths/index.html.haml b/app/views/booths/index.html.haml new file mode 100644 index 00000000..d6c392dd --- /dev/null +++ b/app/views/booths/index.html.haml @@ -0,0 +1,51 @@ +.container + .row + .col-md-12.page-header + %h1 + Your booth requests for + = @conference.title + .row + .col-md-12 + .margin-booth-table + %table.table.table-striped.table-hover + %thead + %th + %b State + %th + %b Logo + %th + %b Title + %th + %b Actions + - @booths.each do |booth| + %tr + %td{ style: "padding:20px 8px 20px 8px;" } + - if (booth.state == 'to_accept' || booth.state == 'to_reject') + - show_state = 'new' + - else + - show_state = booth.state + %span{ title: show_state, class: "fa #{booth_status_icon(booth)}" } + %td + - if booth.logo_link + = image_tag(booth.picture.thumb.url, width: '20%') + %td + = link_to booth.title, conference_booth_path(@conference.short_title, booth) + %td + -if can? :edit, booth + = link_to 'Edit', edit_conference_booth_path(@conference.short_title, booth.id), + class: 'btn btn-default' + - if booth.transition_possible? :withdraw + = link_to 'Withdraw', + withdraw_conference_booth_path(@conference.short_title, booth), + method: :patch, class: 'btn btn-mini btn-warning', id: "withdraw_booth_#{booth.id}", + data: { confirm: 'Are you sure you really want to withdraw this request?' } + - if booth.transition_possible? :confirm + = link_to 'Confirm', + confirm_conference_booth_path(@conference.short_title, booth), + method: :patch, class: 'btn btn-mini btn-success', id: "confirm_booth_#{booth.id}" + - if booth.transition_possible? :restart + = link_to 'Re-submit', + restart_conference_booth_path(@conference.short_title, booth), + method: :patch, class: 'btn btn-mini btn-success', id: "restart_booth_#{booth.id}" + .pull-right + = link_to 'Add Booth', new_conference_booth_path(@conference.short_title), class: 'button btn btn-primary' diff --git a/app/views/booths/new.html.haml b/app/views/booths/new.html.haml new file mode 100644 index 00000000..169efd17 --- /dev/null +++ b/app/views/booths/new.html.haml @@ -0,0 +1,4 @@ +.container + %h1 Request a booth + + = render 'form' diff --git a/app/views/booths/show.html.haml b/app/views/booths/show.html.haml new file mode 100644 index 00000000..6b12229d --- /dev/null +++ b/app/views/booths/show.html.haml @@ -0,0 +1,60 @@ +.container + .row + .col-md-12 + %h3 + - if @booth.logo_link + = image_tag(@booth.picture.thumb.url, size: '20%', alt: '') + = @booth.title + .btn-group.pull-right + = link_to 'Edit', edit_admin_conference_booth_path(@conference.short_title, @booth), class: 'btn btn-mini btn-primary' + + .row + .col-md-12 + %table.table + %tr + %td.col-md-2 + %b Description + %td + = markdown(@booth.description) + %tr + %td.col-md-2 + %b Reasoning + %td + = markdown(@booth.reasoning) + %tr + %td.col-md-2 + %b Website + %td + - if @booth.website_url.present? + = link_to @booth.website_url, @booth.website_url + %tr + %td.col-md-2 + %b Submitter + %td + = link_to @booth.submitter.name, user_path(@booth.submitter) + %tr + %td.col-md-2 + %b Submitter's relationship + %td + = @booth.submitter_relationship + %tr + %td.col-md-2 + %b Responsibles + %td + - @booth.responsibles.each_with_index do |responsibles, i| + .responsibles + = link_to responsibles.name, user_path(responsibles) + ( + = responsibles.email + ) + = ", " unless i == @booth.responsibles.length - 1 + %tr + %td.col-md-2 + %b Submitted on + %td + = @booth.created_at + %tr + %td.col-md-2 + %b Last updated on + %td + = @booth.updated_at diff --git a/config/routes.rb b/config/routes.rb index 5a17d314..be507d35 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -41,7 +41,6 @@ Osem::Application.routes.draw do member do patch :accept patch :restart - patch :withdrawn patch :to_accept patch :reject patch :reset @@ -131,6 +130,13 @@ Osem::Application.routes.draw do end resources :organizations, only: [:index] resources :conferences, only: [:index, :show] do + resources :booths do + member do + patch :withdraw + patch :confirm + patch :restart + end + end resource :program, only: [] do resources :proposals, except: :destroy do get 'commercials/render_commercial' => 'commercials#render_commercial' diff --git a/spec/controllers/admin/booths_controller_spec.rb b/spec/controllers/admin/booths_controller_spec.rb index 569862d1..37004531 100644 --- a/spec/controllers/admin/booths_controller_spec.rb +++ b/spec/controllers/admin/booths_controller_spec.rb @@ -126,23 +126,5 @@ describe Admin::BoothsController do end end end - - describe 'DELETE #destroy' do - context 'deletes successfully' do - before { delete :destroy, id: booth.id, conference_id: conference.short_title } - - it 'booth deleted' do - expect(Booth.count).to eq(0) - end - - it 'redirects to admin booth index path' do - expect(response).to redirect_to(admin_conference_booths_path) - end - - it 'show success message' do - expect(flash[:notice]).to match('Booth successfully destroyed.') - end - end - end end end diff --git a/spec/controllers/booths_controller_spec.rb b/spec/controllers/booths_controller_spec.rb new file mode 100644 index 00000000..243bc6e2 --- /dev/null +++ b/spec/controllers/booths_controller_spec.rb @@ -0,0 +1,110 @@ +require 'spec_helper' + +describe BoothsController do + + let(:user) { create(:user) } + let(:conference) { create(:conference) } + let(:booth) { create(:booth, title: 'Title', conference: conference) } + + context 'user is signed in with submitter role' do + before :each do + sign_in booth.submitter + end + + describe 'GET index' do + before { get :index, conference_id: conference.short_title } + + it 'assigns attributes for booths' do + expect(assigns(:booths)).to eq([booth]) + end + + it 'renders index template' do + expect(response).to render_template('index') + end + end + + describe 'GET #new' do + before { get :new, conference_id: conference.short_title } + + it 'assigns attributes for booths' do + expect(assigns(:booth)).to be_a_new(Booth) + end + + it 'renders new template' do + expect(response).to render_template('new') + end + end + + describe 'POST #create' do + context 'successfully created' do + before { post :create, booth: attributes_for(:booth), conference_id: conference.short_title } + + it 'creates a new booth' do + expect(Booth.count).to_not eq(0) + end + + it 'redirects to booth index' do + expect(response).to redirect_to(conference_booths_path) + end + + it 'has responsibles' do + expect(booth.responsibles.count).to_not eq(0) + end + + it 'shows success message' do + expect(flash[:notice]).to match('Booth successfully created.') + end + end + + context 'create action fails' do + before { post :create, booth: attributes_for(:booth, title: ''), conference_id: conference.short_title } + + it 'does not create any record' do + expected = expect do + post :create, booth: attributes_for(:booth, title: ''), conference_id: conference.short_title + end + expected.to_not change(Booth, :count) + end + + it 'redirects to new' do + expect(response).to render_template('new') + end + + it 'shows flash message' do + expect(flash[:error]).to eq("Creating booth failed. Title can't be blank.") + end + end + end + + describe 'GET #edit' do + before { get :edit, id: booth.id, conference_id: conference.short_title } + + it 'renders edit template' do + expect(response).to render_template('edit') + end + + it 'assigns booth variable' do + expect(assigns(:booth)).to eq booth + end + end + + describe 'PATCH #update' do + context 'updates suchessfully' do + before { patch :update, id: booth.id, booth: attributes_for(:booth, title: 'different'), conference_id: conference.short_title } + + it 'redirects to booth index path' do + expect(response).to redirect_to conference_booths_path + end + + it 'shows success message' do + expect(flash[:notice]).to match 'Booth successfully updated!' + end + + it 'updates booth' do + booth.reload + expect(booth.title).to eq('different') + end + end + end + end +end diff --git a/spec/factories/booths.rb b/spec/factories/booths.rb index b1cd1956..1ac3ff87 100644 --- a/spec/factories/booths.rb +++ b/spec/factories/booths.rb @@ -8,6 +8,7 @@ FactoryGirl.define do conference + submitter { create(:user) } responsible_ids { [create(:user).id] } end end diff --git a/spec/models/booth_spec.rb b/spec/models/booth_spec.rb index 5e86a393..287653ac 100644 --- a/spec/models/booth_spec.rb +++ b/spec/models/booth_spec.rb @@ -33,16 +33,17 @@ describe 'Booth' do end end - states = [:new, :withdrawn, :to_accept, :accepted, :to_reject, :rejected, :canceled] + states = [:new, :withdrawn, :to_accept, :accepted, :to_reject, :rejected, :canceled, :confirmed] transitions = [:restart, :withdraw, :accept, :reject, :to_accept, :to_reject, :cancel] - states_transitions = { new: { restart: false, withdraw: true, accept: true, to_accept: true, to_reject: true, reject: true, cancel: false }, - withdrawn: { restart: true, withdraw: false, accept: false, to_accept: false, to_reject: false, reject: false, cancel: false }, - to_accept: { restart: true, withdraw: true, accept: true, to_accept: false, to_reject: true, reject: false, cancel: true }, - to_reject: { restart: true, withdraw: true, accept: false, to_accept: true, to_reject: false, reject: true, cancel: true }, - accepted: { restart: false, withdraw: true, accept: false, to_accept: false, to_reject: false, reject: false, cancel: true }, - rejected: { restart: false, withdraw: true, accept: false, to_accept: false, to_reject: false, reject: false, cancel: true }, - canceled: { restart: true, withdraw: false, accept: false, to_accept: false, to_reject: false, reject: false, cancel: false } } + states_transitions = { new: { restart: false, withdraw: true, accept: true, to_accept: true, to_reject: true, reject: true, cancel: false, confirm: false }, + withdrawn: { restart: true, withdraw: false, accept: false, to_accept: false, to_reject: false, reject: false, cancel: false, confirm: false }, + to_accept: { restart: false, withdraw: true, accept: true, to_accept: false, to_reject: true, reject: false, cancel: true, confirm: false }, + to_reject: { restart: false, withdraw: true, accept: false, to_accept: true, to_reject: false, reject: true, cancel: true, confirm: false }, + accepted: { restart: false, withdraw: true, accept: false, to_accept: false, to_reject: false, reject: false, cancel: true, confirm: true }, + rejected: { restart: true, withdraw: true, accept: false, to_accept: false, to_reject: false, reject: false, cancel: true, confirm: false }, + canceled: { restart: true, withdraw: false, accept: false, to_accept: false, to_reject: false, reject: false, cancel: false, confirm: false }, + confirmed: { restart: false, withdraw: true, accept: false, to_accept: false, to_reject: false, reject: false, cancel: true, confirm: false } } states.each do |state| transitions.each do |transition| From 540c982771ea96c4f17e563179139e976519c3ea Mon Sep 17 00:00:00 2001 From: shlok007 Date: Thu, 13 Jul 2017 04:02:13 +0530 Subject: [PATCH 26/74] mention organization in dashboard, splashpage and admin/conference#edit --- app/views/admin/conferences/edit.html.haml | 1 + app/views/admin/conferences/show.html.haml | 2 +- app/views/conferences/show.html.haml | 3 +++ spec/features/conference_spec.rb | 9 +++++++++ spec/features/splashpage_spec.rb | 11 +++++++++++ 5 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/views/admin/conferences/edit.html.haml b/app/views/admin/conferences/edit.html.haml index 2d5df126..49d65235 100644 --- a/app/views/admin/conferences/edit.html.haml +++ b/app/views/admin/conferences/edit.html.haml @@ -9,6 +9,7 @@ = semantic_form_for(@conference, url: admin_conference_path(@conference.short_title), html: {multipart: true}) do |f| = f.input :title, hint: "The full title of the conference, e.g. 'openSUSE Conference 2014'" = f.input :short_title, hint: "A short title, e.g. 'oSC14', to be used in URLs" + = f.input :organization, hint: 'The organization in which this conference belongs', input_html: { disabled: true } = f.input :description, hint: markdown_hint('A description of the conference.'), input_html: { rows: 5, data: { provide: 'markdown-editable' } } = f.input :color, hint: 'The color will be used eg for the dashboard.', input_html: {size: 6, type: 'color'} = f.label 'Conference Logo' diff --git a/app/views/admin/conferences/show.html.haml b/app/views/admin/conferences/show.html.haml index cddae4c2..f270867b 100644 --- a/app/views/admin/conferences/show.html.haml +++ b/app/views/admin/conferences/show.html.haml @@ -1,6 +1,6 @@ %h1 %span.fa.fa-tachometer - Dashboard for #{@conference.title} + Dashboard for #{@conference.title} ( by #{@conference.organization.name} ) %hr .row .col-sm-4.col-xs-4 diff --git a/app/views/conferences/show.html.haml b/app/views/conferences/show.html.haml index fb488e22..b133c26a 100644 --- a/app/views/conferences/show.html.haml +++ b/app/views/conferences/show.html.haml @@ -22,6 +22,9 @@ .col-md-8 %h1 = @conference.title + %h3 + %i + = "( by #{@conference.organization.name} )" %p.lead - if @conference.venue = "#{@conference.venue.city} / #{@conference.venue.country_name}" diff --git a/spec/features/conference_spec.rb b/spec/features/conference_spec.rb index d17ce5fc..5b9f9c54 100644 --- a/spec/features/conference_spec.rb +++ b/spec/features/conference_spec.rb @@ -71,6 +71,15 @@ feature Conference do end describe 'admin' do + let!(:conference) { create(:conference) } + + scenario 'has organization name in edit form', feature: true, js: true do + sign_in user + visit edit_admin_conference_path(conference.short_title) + org_id = find('#conference_organization_id').value + expect(Organization.find(org_id)).to eq conference.organization + end + it_behaves_like 'add and update conference' end end diff --git a/spec/features/splashpage_spec.rb b/spec/features/splashpage_spec.rb index e913d023..ee9b63d5 100644 --- a/spec/features/splashpage_spec.rb +++ b/spec/features/splashpage_spec.rb @@ -61,4 +61,15 @@ feature Splashpage do expect(current_path).to eq(root_path) end end + + context 'public splashpage already created' do + let!(:splashpage) { create(:splashpage, conference: conference, public: true)} + + scenario 'should have organization name', feature: true, js: true do + sign_in participant + visit conference_path(conference.short_title) + + expect(page).to have_text(conference.organization.name) + end + end end From cdc08052b140fc1bca6bb8847f40a4e61a1e169f Mon Sep 17 00:00:00 2001 From: shlok007 Date: Mon, 24 Jul 2017 13:46:09 +0530 Subject: [PATCH 27/74] display organization name in menu bar --- app/views/admin/conferences/edit.html.haml | 1 - app/views/conferences/show.html.haml | 3 --- app/views/layouts/_navigation.html.haml | 5 ++++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/views/admin/conferences/edit.html.haml b/app/views/admin/conferences/edit.html.haml index 49d65235..2d5df126 100644 --- a/app/views/admin/conferences/edit.html.haml +++ b/app/views/admin/conferences/edit.html.haml @@ -9,7 +9,6 @@ = semantic_form_for(@conference, url: admin_conference_path(@conference.short_title), html: {multipart: true}) do |f| = f.input :title, hint: "The full title of the conference, e.g. 'openSUSE Conference 2014'" = f.input :short_title, hint: "A short title, e.g. 'oSC14', to be used in URLs" - = f.input :organization, hint: 'The organization in which this conference belongs', input_html: { disabled: true } = f.input :description, hint: markdown_hint('A description of the conference.'), input_html: { rows: 5, data: { provide: 'markdown-editable' } } = f.input :color, hint: 'The color will be used eg for the dashboard.', input_html: {size: 6, type: 'color'} = f.label 'Conference Logo' diff --git a/app/views/conferences/show.html.haml b/app/views/conferences/show.html.haml index b133c26a..fb488e22 100644 --- a/app/views/conferences/show.html.haml +++ b/app/views/conferences/show.html.haml @@ -22,9 +22,6 @@ .col-md-8 %h1 = @conference.title - %h3 - %i - = "( by #{@conference.organization.name} )" %p.lead - if @conference.venue = "#{@conference.venue.city} / #{@conference.venue.country_name}" diff --git a/app/views/layouts/_navigation.html.haml b/app/views/layouts/_navigation.html.haml index 6683ea23..92be2e89 100644 --- a/app/views/layouts/_navigation.html.haml +++ b/app/views/layouts/_navigation.html.haml @@ -7,7 +7,10 @@ %span.icon-bar %span.icon-bar %span.icon-bar - = link_to (ENV['OSEM_NAME'] || 'OSEM'), root_path, class: 'navbar-brand', title: 'Open Source Event Manager' + - if @conference.nil? || @conference.new_record? + = link_to (ENV['OSEM_NAME'] || 'OSEM'), root_path, class: 'navbar-brand', title: 'Open Source Event Manager' + - else + = link_to (ENV['OSEM_NAME'] || "#{@conference.organization.name} Organization"), organizations_path, class: 'navbar-brand', title: 'Open Source Event Manager' .collapse.navbar-collapse - if content_for :splash_nav %ul.nav.navbar-nav#splash-nav From 17eacaec0dcf154f8d4a11af6b83d0d3bcfbb68e Mon Sep 17 00:00:00 2001 From: shlok007 Date: Tue, 25 Jul 2017 08:16:44 +0530 Subject: [PATCH 28/74] add tests for organization name in conference views remove tests for organization name in conference#edit --- app/views/admin/conferences/show.html.haml | 2 +- app/views/layouts/_navigation.html.haml | 4 ++-- app/views/layouts/application.html.haml | 2 +- spec/features/conference_spec.rb | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/views/admin/conferences/show.html.haml b/app/views/admin/conferences/show.html.haml index f270867b..cddae4c2 100644 --- a/app/views/admin/conferences/show.html.haml +++ b/app/views/admin/conferences/show.html.haml @@ -1,6 +1,6 @@ %h1 %span.fa.fa-tachometer - Dashboard for #{@conference.title} ( by #{@conference.organization.name} ) + Dashboard for #{@conference.title} %hr .row .col-sm-4.col-xs-4 diff --git a/app/views/layouts/_navigation.html.haml b/app/views/layouts/_navigation.html.haml index 92be2e89..dbbfdbc0 100644 --- a/app/views/layouts/_navigation.html.haml +++ b/app/views/layouts/_navigation.html.haml @@ -7,10 +7,10 @@ %span.icon-bar %span.icon-bar %span.icon-bar - - if @conference.nil? || @conference.new_record? + - if conference.nil? || conference.new_record? = link_to (ENV['OSEM_NAME'] || 'OSEM'), root_path, class: 'navbar-brand', title: 'Open Source Event Manager' - else - = link_to (ENV['OSEM_NAME'] || "#{@conference.organization.name} Organization"), organizations_path, class: 'navbar-brand', title: 'Open Source Event Manager' + = link_to (ENV['OSEM_NAME'] || "#{conference.organization.name} Organization"), organizations_path, class: 'navbar-brand', title: 'Open Source Event Manager' .collapse.navbar-collapse - if content_for :splash_nav %ul.nav.navbar-nav#splash-nav diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 09be2523..c1a6ce78 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -21,7 +21,7 @@ = yield(:head) %body - = render 'layouts/navigation' + = render 'layouts/navigation', conference: @conference -# Admin area - if controller.class.name.split("::").first=="Admin" = render 'layouts/admin' diff --git a/spec/features/conference_spec.rb b/spec/features/conference_spec.rb index 5b9f9c54..5ac9f29b 100644 --- a/spec/features/conference_spec.rb +++ b/spec/features/conference_spec.rb @@ -73,11 +73,11 @@ feature Conference do describe 'admin' do let!(:conference) { create(:conference) } - scenario 'has organization name in edit form', feature: true, js: true do + scenario 'has organization name in menu bar for conference views', feature: true, js: true do sign_in user - visit edit_admin_conference_path(conference.short_title) - org_id = find('#conference_organization_id').value - expect(Organization.find(org_id)).to eq conference.organization + visit admin_conference_path(conference.short_title) + + expect(find('.navbar-brand').text).to eq "#{conference.organization.name} Organization" end it_behaves_like 'add and update conference' From 28b55a9d72e8c3b4de5e1271ed4082d31e97fe0a Mon Sep 17 00:00:00 2001 From: shlok007 Date: Wed, 2 Aug 2017 15:00:08 +0530 Subject: [PATCH 29/74] remove OSEM_NAME from navigation --- app/views/layouts/_navigation.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_navigation.html.haml b/app/views/layouts/_navigation.html.haml index dbbfdbc0..193e6b76 100644 --- a/app/views/layouts/_navigation.html.haml +++ b/app/views/layouts/_navigation.html.haml @@ -10,7 +10,7 @@ - if conference.nil? || conference.new_record? = link_to (ENV['OSEM_NAME'] || 'OSEM'), root_path, class: 'navbar-brand', title: 'Open Source Event Manager' - else - = link_to (ENV['OSEM_NAME'] || "#{conference.organization.name} Organization"), organizations_path, class: 'navbar-brand', title: 'Open Source Event Manager' + = link_to "#{conference.organization.name} Organization", organizations_path, class: 'navbar-brand', title: 'Open Source Event Manager' .collapse.navbar-collapse - if content_for :splash_nav %ul.nav.navbar-nav#splash-nav From 83abc9f71f1f857ceab691dfea6b2664baaea847 Mon Sep 17 00:00:00 2001 From: shlok007 Date: Wed, 9 Aug 2017 23:34:58 +0530 Subject: [PATCH 30/74] remove Organization suffix from navbar-brand in navigation --- app/views/layouts/_navigation.html.haml | 2 +- spec/features/conference_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/_navigation.html.haml b/app/views/layouts/_navigation.html.haml index 193e6b76..9750be92 100644 --- a/app/views/layouts/_navigation.html.haml +++ b/app/views/layouts/_navigation.html.haml @@ -10,7 +10,7 @@ - if conference.nil? || conference.new_record? = link_to (ENV['OSEM_NAME'] || 'OSEM'), root_path, class: 'navbar-brand', title: 'Open Source Event Manager' - else - = link_to "#{conference.organization.name} Organization", organizations_path, class: 'navbar-brand', title: 'Open Source Event Manager' + = link_to conference.organization.name, organizations_path, class: 'navbar-brand', title: 'Open Source Event Manager' .collapse.navbar-collapse - if content_for :splash_nav %ul.nav.navbar-nav#splash-nav diff --git a/spec/features/conference_spec.rb b/spec/features/conference_spec.rb index 5ac9f29b..f3205e07 100644 --- a/spec/features/conference_spec.rb +++ b/spec/features/conference_spec.rb @@ -77,7 +77,7 @@ feature Conference do sign_in user visit admin_conference_path(conference.short_title) - expect(find('.navbar-brand').text).to eq "#{conference.organization.name} Organization" + expect(find('.navbar-brand').text).to eq(conference.organization.name) end it_behaves_like 'add and update conference' From f31651ad9c0ce80a3fe41013d01773c9c7cb0307 Mon Sep 17 00:00:00 2001 From: siddhantbajaj Date: Mon, 7 Aug 2017 15:10:06 +0530 Subject: [PATCH 31/74] Added registration-ticket Added registration type tickets that will used in the check-in process --- app/controllers/admin/tickets_controller.rb | 2 +- app/views/admin/tickets/_form.html.haml | 1 + app/views/admin/tickets/index.html.haml | 3 +++ .../20170807092805_add_registration_ticket_to_tickets.rb | 5 +++++ db/schema.rb | 3 ++- 5 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20170807092805_add_registration_ticket_to_tickets.rb diff --git a/app/controllers/admin/tickets_controller.rb b/app/controllers/admin/tickets_controller.rb index c7161409..cf6e0f80 100644 --- a/app/controllers/admin/tickets_controller.rb +++ b/app/controllers/admin/tickets_controller.rb @@ -50,7 +50,7 @@ module Admin private def ticket_params - params.require(:ticket).permit(:conference, :title, :url, :description, :conference_id, :price_cents, :price_currency, :price) + params.require(:ticket).permit(:conference, :title, :url, :description, :conference_id, :price_cents, :price_currency, :price, :registration_ticket) end end end diff --git a/app/views/admin/tickets/_form.html.haml b/app/views/admin/tickets/_form.html.haml index 94a54692..11aff78d 100644 --- a/app/views/admin/tickets/_form.html.haml +++ b/app/views/admin/tickets/_form.html.haml @@ -13,5 +13,6 @@ = f.input :description, input_html: { rows: 5, data: { provide: "markdown-editable" } } = f.input :price = f.input :price_currency, as: :select, class: 'form-control', collection: ['USD', 'EUR', 'GBP', 'INR', 'CNY'], include_blank: false + = f.input :registration_ticket, hint: 'A registration ticket is with which user register for the conference.' %p.text-right = f.action :submit, as: :button, button_html: { class: 'btn btn-primary' } diff --git a/app/views/admin/tickets/index.html.haml b/app/views/admin/tickets/index.html.haml index bb2804bf..b8cdb505 100644 --- a/app/views/admin/tickets/index.html.haml +++ b/app/views/admin/tickets/index.html.haml @@ -14,6 +14,7 @@ %th Price %th Sold %th Turnover + %th Registration Ticket %th Actions %tbody - @conference.tickets.each do |ticket| @@ -27,6 +28,8 @@ = ticket.tickets_sold %td = humanized_money_with_symbol ticket.tickets_turnover + %td + = ticket.registration_ticket? ? 'Yes' : 'No' %td .btn-group = link_to 'Edit', edit_admin_conference_ticket_path(@conference.short_title, ticket.id), diff --git a/db/migrate/20170807092805_add_registration_ticket_to_tickets.rb b/db/migrate/20170807092805_add_registration_ticket_to_tickets.rb new file mode 100644 index 00000000..0ee2410d --- /dev/null +++ b/db/migrate/20170807092805_add_registration_ticket_to_tickets.rb @@ -0,0 +1,5 @@ +class AddRegistrationTicketToTickets < ActiveRecord::Migration + def change + add_column :tickets, :registration_ticket, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index d49dfa03..ce680725 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: 20170721001700) do +ActiveRecord::Schema.define(version: 20170807092805) do create_table "ahoy_events", force: :cascade do |t| t.integer "visit_id" @@ -507,6 +507,7 @@ ActiveRecord::Schema.define(version: 20170721001700) do t.text "description" t.integer "price_cents", default: 0, null: false t.string "price_currency", default: "USD", null: false + t.boolean "registration_ticket", default: false end create_table "tracks", force: :cascade do |t| From bd62df14c3999b7968586d886141bc60557d99a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=20Mar=C3=ADa=20Mart=C3=ADnez=20G=C3=B3mez?= Date: Thu, 20 Jul 2017 14:37:22 +0200 Subject: [PATCH 32/74] Fix vagrant broken after updating to Ruby 2.4 Ruby got updated in https://github.com/openSUSE/osem/pull/1588, But there was a repo missed, so Ruby 2.4 can be installed in our Vagrant machine. Closes https://github.com/openSUSE/osem/issues/1601 Mob-programmed by @mdeniz, @DavidKang and @Ana06. --- bootstrap.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bootstrap.sh b/bootstrap.sh index f0ac49fb..92a8d746 100644 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -2,6 +2,8 @@ pushd /vagrant echo -e "\ninstalling required software packages...\n" +zypper -q ar -f http://download.opensuse.org/repositories/devel:/languages:/ruby/openSUSE_Leap_42.2/devel:languages:ruby.repo +zypper -q --gpg-auto-import-keys --non-interactive ref zypper -q -n install update-alternatives ruby2.4-devel make gcc gcc-c++ \ libxml2-devel libxslt-devel nodejs screen mariadb \ libmysqld-devel sqlite3-devel ImageMagick From 88e31ad902f729412a616b18f50bf2e1f38cac25 Mon Sep 17 00:00:00 2001 From: Wexpo Lyu Date: Wed, 2 Aug 2017 11:24:37 -0500 Subject: [PATCH 33/74] Expose enable_starttls_auto & openssl_verify_mode. And align `ENV` calls to keep up with codes above. My fault for using tabs. which caused a real misalignment. --- config/environments/production.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index d2b43a46..cb3a5d67 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -77,8 +77,9 @@ Osem::Application.configure do user_name: ENV['OSEM_SMTP_USERNAME'], password: ENV['OSEM_SMTP_PASSWORD'], authentication: ENV['OSEM_SMTP_AUTHENTICATION'].try(:to_sym), - domain: ENV['OSEM_SMTP_DOMAIN'], - enable_starttls_auto: true + domain: ENV['OSEM_SMTP_DOMAIN'], + enable_starttls_auto: ENV['OSEM_SMTP_ENABLE_STARTTLS_AUTO'], + openssl_verify_mode: ENV['OSEM_SMTP_OPENSSL_VERIFY_MODE'] } # Set the secret_key_base from the env, if not set by any other means From 33aee342b43d14cf3ade75a0f45ae6018982566b Mon Sep 17 00:00:00 2001 From: Wexpo Lyu Date: Wed, 2 Aug 2017 21:19:13 -0500 Subject: [PATCH 34/74] Add new envs to dotenv.example. --- dotenv.example | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dotenv.example b/dotenv.example index 0c852658..77c28e4c 100644 --- a/dotenv.example +++ b/dotenv.example @@ -58,6 +58,8 @@ OSEM_SMTP_USERNAME="" OSEM_SMTP_PASSWORD="" OSEM_SMTP_AUTHENTICATION="" OSEM_SMTP_DOMAIN="" +OSEM_SMTP_ENABLE_STARTTLS_AUTO="" +OSEM_SMTP_OPENSSL_VERIFY_MODE="" # Enable the usage of the devise ichain plugin OSEM_ICHAIN_ENABLED=false From d616c66745352e9c64e3f248e9271b5555073607 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 12 Jul 2017 14:31:22 +0300 Subject: [PATCH 35/74] Define the track's finite state machine --- .rubocop_todo.yml | 1 + app/models/track.rb | 66 ++++++++++++++++++- spec/features/track_organizer_ability_spec.rb | 4 +- spec/models/admin_ability_spec.rb | 9 +-- 4 files changed, 72 insertions(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8d752446..e7e50f4a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -565,6 +565,7 @@ Style/PercentLiteralDelimiters: - 'app/models/subscription.rb' - 'app/uploaders/picture_uploader.rb' - 'spec/models/program_spec.rb' + - 'app/models/track.rb' # Offense count: 2 # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist. diff --git a/app/models/track.rb b/app/models/track.rb index 2b37d1bb..6228ff02 100644 --- a/app/models/track.rb +++ b/app/models/track.rb @@ -1,4 +1,5 @@ class Track < ActiveRecord::Base + include ActiveRecord::Transitions include RevisionCount resourcify :roles, dependent: :delete_all @@ -18,12 +19,49 @@ class Track < ActiveRecord::Base uniqueness: { scope: :program } - validates :state, presence: true, if: :self_organized? + validates :state, + presence: true, + inclusion: { in: %w(new to_accept accepted confirmed to_reject rejected canceled withdrawn) }, + if: :self_organized? validates :cfp_active, inclusion: { in: [true, false] }, if: :self_organized? before_validation :capitalize_color - after_create :create_organizer_role, if: :self_organized? + state_machine initial: :pending do + state :new + state :to_accept + state :accepted + state :confirmed + state :to_reject + state :rejected + state :canceled + state :withdrawn + + event :restart do + transitions to: :new, from: [:rejected, :withdrawn, :canceled] + end + event :readiness_to_accept do + transitions to: :to_accept, from: [:new] + end + event :accept do + transitions to: :accepted, from: [:new, :to_accept], on_transition: :create_organizer_role + end + event :confirm do + transitions to: :confirmed, from: [:accepted], on_transition: :assign_role_to_submitter + end + event :readiness_to_reject do + transitions to: :to_reject, from: [:new] + end + event :reject do + transitions to: :rejected, from: [:new, :to_reject] + end + event :cancel do + transitions to: :canceled, from: [:to_accept, :to_reject, :accepted, :confirmed], on_transition: :revoke_role_and_cleanup + end + event :withdraw do + transitions to: :withdrawn, from: [:new, :to_accept, :to_reject, :accepted, :confirmed], on_transition: :revoke_role_and_cleanup + end + end def conference program.conference @@ -43,6 +81,30 @@ class Track < ActiveRecord::Base short_name end + def transition_possible?(transition) + self.class.state_machine.events_for(current_state).include?(transition) + end + + # Gives the role of the track_organizer to the submitter + def assign_role_to_submitter + submitter.add_role 'track_organizer', self + end + + # Revokes the track organizer role and removes the track from events that have it set + def revoke_role_and_cleanup + role = Role.find_by(name: 'track_organizer', resource: self) + + if role + role.users.each do |user| + user.remove_role 'track_organizer', self + end + end + + events.each do |event| + event.track = nil + end + end + private def generate_guid diff --git a/spec/features/track_organizer_ability_spec.rb b/spec/features/track_organizer_ability_spec.rb index f79eba05..49e46915 100644 --- a/spec/features/track_organizer_ability_spec.rb +++ b/spec/features/track_organizer_ability_spec.rb @@ -5,10 +5,10 @@ feature 'Has correct abilities' do let(:organization) { create(:organization) } let(:conference) { create(:full_conference, organization: organization) } let(:self_organized_track) { create(:track, :self_organized, program: conference.program) } - let(:role_track_organizer) { Role.find_by(name: 'track_organizer', resource: self_organized_track) } + let(:role_track_organizer) { Role.where(name: 'track_organizer', resource: self_organized_track).first_or_create } let(:user_track_organizer) { create(:user, role_ids: [role_track_organizer.id]) } - context 'when user is info desk' do + context 'when user is track organizer' do before do sign_in user_track_organizer end diff --git a/spec/models/admin_ability_spec.rb b/spec/models/admin_ability_spec.rb index 5398b7d2..16456a19 100644 --- a/spec/models/admin_ability_spec.rb +++ b/spec/models/admin_ability_spec.rb @@ -79,7 +79,7 @@ describe 'User with admin role' do context 'accesses track organizers' do before :each do other_self_organized_track = create(:track, :self_organized) - @other_track_organizer_role = Role.find_by(name: 'track_organizer', resource: other_self_organized_track) + @other_track_organizer_role = Role.where(name: 'track_organizer', resource: other_self_organized_track).first_or_create end it{ should_not be_able_to(:toggle_user, @other_track_organizer_role) } @@ -105,7 +105,7 @@ describe 'User with admin role' do context 'accesses track organizers' do before :each do - @track_organizer_role = Role.find_by(name: 'track_organizer', resource: my_self_organized_track) + @track_organizer_role = Role.where(name: 'track_organizer', resource: my_self_organized_track).first_or_create end if role_name == 'track_organizer' @@ -223,7 +223,7 @@ describe 'User with admin role' do context 'can manage track organizers' do before :each do - @track_organizer_role = Role.find_by(name: 'track_organizer', resource: my_self_organized_track) + @track_organizer_role = Role.where(name: 'track_organizer', resource: my_self_organized_track).first_or_create end it{ should be_able_to(:toggle_user, @track_organizer_role) } @@ -441,7 +441,8 @@ describe 'User with admin role' do end context 'when user has the role track_organizer' do - let(:role) { Role.find_by(name: 'track_organizer', resource: my_self_organized_track) } + + let(:role) { Role.where(name: 'track_organizer', resource: my_self_organized_track).first_or_create } let(:user) { create(:user, role_ids: [role.id]) } let(:new_track) { build(:track, program: my_conference.program) } From fd93b04f169685f446dd9e3a4e2395a80a53f0a6 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 12 Jul 2017 15:56:07 +0300 Subject: [PATCH 36/74] Add room and dates to tracks They are required only for accepted and confirmed self-organized tracks --- .rubocop_todo.yml | 4 ++ app/controllers/admin/tracks_controller.rb | 2 +- app/models/room.rb | 1 + app/models/track.rb | 56 +++++++++++++++++++ app/views/admin/tracks/_form.html.haml | 8 ++- app/views/admin/tracks/index.html.haml | 18 ++++++ ...0712120556_add_room_and_dates_to_tracks.rb | 7 +++ db/schema.rb | 4 ++ spec/models/room_spec.rb | 1 + 9 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20170712120556_add_room_and_dates_to_tracks.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e7e50f4a..487a9fdb 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -317,6 +317,8 @@ Metrics/BlockLength: # Offense count: 23 Metrics/CyclomaticComplexity: Max: 12 + Exclude: + - 'app/models/track.rb' # Offense count: 2353 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. @@ -339,6 +341,8 @@ Metrics/ModuleLength: # Offense count: 15 Metrics/PerceivedComplexity: Max: 16 + Exclude: + - 'app/models/track.rb' # Offense count: 20 Style/AccessorMethodName: diff --git a/app/controllers/admin/tracks_controller.rb b/app/controllers/admin/tracks_controller.rb index c229b9b8..0eed1b2d 100644 --- a/app/controllers/admin/tracks_controller.rb +++ b/app/controllers/admin/tracks_controller.rb @@ -62,7 +62,7 @@ module Admin private def track_params - params.require(:track).permit(:name, :description, :color, :short_name, :cfp_active) + params.require(:track).permit(:name, :description, :color, :short_name, :cfp_active, :start_date, :end_date, :room_id) end end end diff --git a/app/models/room.rb b/app/models/room.rb index f8150f78..8a50551d 100644 --- a/app/models/room.rb +++ b/app/models/room.rb @@ -2,6 +2,7 @@ class Room < ActiveRecord::Base include RevisionCount belongs_to :venue has_many :event_schedules, dependent: :destroy + has_many :tracks has_paper_trail ignore: [:guid], meta: { conference_id: :conference_id } diff --git a/app/models/track.rb b/app/models/track.rb index 6228ff02..f29babde 100644 --- a/app/models/track.rb +++ b/app/models/track.rb @@ -6,6 +6,7 @@ class Track < ActiveRecord::Base belongs_to :program belongs_to :submitter, class_name: 'User' + belongs_to :room has_many :events, dependent: :nullify has_paper_trail only: [:name, :description, :color], meta: { conference_id: :conference_id } @@ -24,6 +25,10 @@ class Track < ActiveRecord::Base inclusion: { in: %w(new to_accept accepted confirmed to_reject rejected canceled withdrawn) }, if: :self_organized? validates :cfp_active, inclusion: { in: [true, false] }, if: :self_organized? + validates :start_date, presence: true, if: :accepted_or_confirmed? + validates :end_date, presence: true, if: :accepted_or_confirmed? + validates :room, presence: true, if: :accepted_or_confirmed? + validate :valid_dates, if: :accepted_or_confirmed? before_validation :capitalize_color @@ -105,6 +110,33 @@ class Track < ActiveRecord::Base end end + ## + # Checks if the track is accepted + # ====Returns + # * +true+ -> If the track's state is 'accepted' + # * +false+ -> If the track's state isn't 'accepted' + def accepted? + state == 'accepted' + end + + ## + # Checks if the track is confirmed + # ====Returns + # * +true+ -> If the track's state is 'confirmed' + # * +false+ -> If the track's state isn't 'confirmed' + def confirmed? + state == 'confirmed' + end + + ## + # Checks if the track is accepted or confirmed + # ====Returns + # * +true+ -> If the track's state is 'accepted' or 'confirmed' + # * +false+ -> If the track's state is neither 'accepted' nor 'confirmed' + def accepted_or_confirmed? + accepted? || confirmed? + end + private def generate_guid @@ -128,4 +160,28 @@ class Track < ActiveRecord::Base def create_organizer_role Role.where(name: 'track_organizer', resource: self).first_or_create(description: 'For the organizers of the Track') end + + def valid_dates + return unless start_date && end_date + + if program && program.conference && program.conference.start_date && (start_date < program.conference.start_date) + errors.add(:start_date, "can't be before the conference start date (#{program.conference.end_date})") + end + + if program && program.conference && program.conference.start_date && (end_date < program.conference.start_date) + errors.add(:end_date, "can't be before the conference start date (#{program.conference.end_date})") + end + + if program && program.conference && program.conference.end_date && (start_date > program.conference.end_date) + errors.add(:start_date, "can't be after the conference end date (#{program.conference.end_date})") + end + + if program && program.conference && program.conference.end_date && (end_date > program.conference.end_date) + errors.add(:end_date, "can't be after the conference end date (#{program.conference.end_date})") + end + + if start_date > end_date + errors.add(:start_date, 'can\'t be after the end_date') + end + end end diff --git a/app/views/admin/tracks/_form.html.haml b/app/views/admin/tracks/_form.html.haml index fe7f014a..cb2e8b01 100644 --- a/app/views/admin/tracks/_form.html.haml +++ b/app/views/admin/tracks/_form.html.haml @@ -8,10 +8,16 @@ Track .row .col-md-12 - = semantic_form_for(@track, url: (@track.new_record? ? admin_conference_program_tracks_path : admin_conference_program_track_path(@conference.short_title, @track))) do |f| + = semantic_form_for(@track, url: (@track.new_record? ? admin_conference_program_tracks_path(@conference.short_title) : admin_conference_program_track_path(@conference.short_title, @track))) do |f| = f.input :name = f.input :short_name, hint: "A short and unique handle for the track, using only letters, numbers, underscores, and dashes. This will be used to identify the track in URLs etc. Example: 'my_awesome_track'", input_html: { required: 'required', pattern: '[a-zA-Z0-9_-]+', title: 'Only letters, numbers, underscores, and dashes.' } = f.input :color, input_html: {size: 6, type: 'color'}, required: true + = f.input :start_date, as: :string, input_html: { id: 'registration-period-start-datepicker', start_date: @conference.start_date, end_date: @conference.end_date, readonly: 'readonly', required: @track.self_organized_and_accepted_or_confirmed? } + = f.input :end_date, as: :string, input_html: { id: 'registration-period-end-datepicker', readonly: 'readonly', required: @track.self_organized_and_accepted_or_confirmed? } + - if @conference.venue + = f.input :room, as: :select, collection: (@conference.venue.rooms).map {|room| ["#{room.name}", room.id]}, include_blank: true, label: 'Room', input_html: { class: 'select-help-toggle', required: @track.self_organized_and_accepted_or_confirmed? } + - else + Please add a venue with rooms, if you want to select a room for the track. = f.input :description, input_html: {rows: 2, data: { provide: 'markdown-editable' } }, hint: markdown_hint - if @track.self_organized? = f.input :cfp_active, label: 'Allow event submitters to select this track for their proposal' diff --git a/app/views/admin/tracks/index.html.haml b/app/views/admin/tracks/index.html.haml index 1cda360a..82167bb8 100644 --- a/app/views/admin/tracks/index.html.haml +++ b/app/views/admin/tracks/index.html.haml @@ -15,6 +15,9 @@ %th Color %th State %th Included in the Cfp + %th Room + %th Start Date + %th End Date %th Actions %tbody - @tracks.each do |track| @@ -52,6 +55,21 @@ off_text: 'No' } - else %i.fa.fa-check + %td + - if track.room + = link_to track.room.name, admin_conference_venue_room_path(@conference.short_title, track.room.id) + - else + N/A + %td + - if track.start_date + = track.start_date.strftime('%A, %B %-d. %Y') + - else + N/A + %td + - if track.end_date + = track.end_date.strftime('%A, %B %-d. %Y') + - else + N/A %td .btn-group{role: "group"} = link_to 'Edit', edit_admin_conference_program_track_path(@conference.short_title, track), diff --git a/db/migrate/20170712120556_add_room_and_dates_to_tracks.rb b/db/migrate/20170712120556_add_room_and_dates_to_tracks.rb new file mode 100644 index 00000000..fb8a064b --- /dev/null +++ b/db/migrate/20170712120556_add_room_and_dates_to_tracks.rb @@ -0,0 +1,7 @@ +class AddRoomAndDatesToTracks < ActiveRecord::Migration + def change + add_reference :tracks, :room, index: true, foreign_key: true + add_column :tracks, :start_date, :date + add_column :tracks, :end_date, :date + end +end diff --git a/db/schema.rb b/db/schema.rb index ce680725..d3e14687 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -522,8 +522,12 @@ ActiveRecord::Schema.define(version: 20170807092805) do t.string "state" t.boolean "cfp_active" t.integer "submitter_id" + t.integer "room_id" + t.date "start_date" + t.date "end_date" end + add_index "tracks", ["room_id"], name: "index_tracks_on_room_id" add_index "tracks", ["submitter_id"], name: "index_tracks_on_submitter_id" create_table "users", force: :cascade do |t| diff --git a/spec/models/room_spec.rb b/spec/models/room_spec.rb index 469fb117..f135f91d 100644 --- a/spec/models/room_spec.rb +++ b/spec/models/room_spec.rb @@ -12,6 +12,7 @@ describe Room do describe 'association' do it { should belong_to(:venue) } it { should have_many(:event_schedules).dependent(:destroy) } + it { should have_many(:tracks) } end describe 'callback' do From 0f154d07c9abcb1bb5a954b79d67092d89e92b08 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Sat, 15 Jul 2017 19:55:45 +0300 Subject: [PATCH 37/74] Mark track state as not null and add default value The regular tracks are marked as 'confirmed' --- app/controllers/admin/tracks_controller.rb | 1 + app/controllers/tracks_controller.rb | 1 - app/models/track.rb | 14 +++++++------- app/views/admin/tracks/index.html.haml | 2 +- ...e_track_state_not_null_and_add_default_value.rb | 14 ++++++++++++++ db/schema.rb | 8 ++++---- spec/controllers/admin/tracks_controller_spec.rb | 4 ++++ spec/factories/tracks.rb | 1 + 8 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 db/migrate/20170715131706_make_track_state_not_null_and_add_default_value.rb diff --git a/app/controllers/admin/tracks_controller.rb b/app/controllers/admin/tracks_controller.rb index 0eed1b2d..2cb6ec2b 100644 --- a/app/controllers/admin/tracks_controller.rb +++ b/app/controllers/admin/tracks_controller.rb @@ -19,6 +19,7 @@ module Admin def create @track = @program.tracks.new(track_params) + @track.state = 'confirmed' if @track.save redirect_to admin_conference_program_tracks_path(conference_id: @conference.short_title), notice: 'Track successfully created.' diff --git a/app/controllers/tracks_controller.rb b/app/controllers/tracks_controller.rb index 529767cd..59deae3c 100644 --- a/app/controllers/tracks_controller.rb +++ b/app/controllers/tracks_controller.rb @@ -18,7 +18,6 @@ class TracksController < ApplicationController def create @track = @program.tracks.new(track_params) @track.submitter = current_user - @track.state = 'new' @track.cfp_active = false if @track.save redirect_to conference_program_tracks_path(conference_id: @conference.short_title), diff --git a/app/models/track.rb b/app/models/track.rb index f29babde..a53471bc 100644 --- a/app/models/track.rb +++ b/app/models/track.rb @@ -25,10 +25,10 @@ class Track < ActiveRecord::Base inclusion: { in: %w(new to_accept accepted confirmed to_reject rejected canceled withdrawn) }, if: :self_organized? validates :cfp_active, inclusion: { in: [true, false] }, if: :self_organized? - validates :start_date, presence: true, if: :accepted_or_confirmed? - validates :end_date, presence: true, if: :accepted_or_confirmed? - validates :room, presence: true, if: :accepted_or_confirmed? - validate :valid_dates, if: :accepted_or_confirmed? + validates :start_date, presence: true, if: :self_organized_and_accepted_or_confirmed? + validates :end_date, presence: true, if: :self_organized_and_accepted_or_confirmed? + validates :room, presence: true, if: :self_organized_and_accepted_or_confirmed? + validate :valid_dates, if: :self_organized_and_accepted_or_confirmed? before_validation :capitalize_color @@ -129,12 +129,12 @@ class Track < ActiveRecord::Base end ## - # Checks if the track is accepted or confirmed + # Checks if a self-organized track is accepted or confirmed # ====Returns # * +true+ -> If the track's state is 'accepted' or 'confirmed' # * +false+ -> If the track's state is neither 'accepted' nor 'confirmed' - def accepted_or_confirmed? - accepted? || confirmed? + def self_organized_and_accepted_or_confirmed? + self_organized? && (accepted? || confirmed?) end private diff --git a/app/views/admin/tracks/index.html.haml b/app/views/admin/tracks/index.html.haml index 82167bb8..4da97a18 100644 --- a/app/views/admin/tracks/index.html.haml +++ b/app/views/admin/tracks/index.html.haml @@ -42,7 +42,7 @@ - if track.self_organized? = track.state - else - N/A + = track.state.humanize %td - if track.self_organized? = check_box_tag "#{@conference.short_title}_#{track.short_name}", track.id, track.cfp_active, diff --git a/db/migrate/20170715131706_make_track_state_not_null_and_add_default_value.rb b/db/migrate/20170715131706_make_track_state_not_null_and_add_default_value.rb new file mode 100644 index 00000000..3d2520cf --- /dev/null +++ b/db/migrate/20170715131706_make_track_state_not_null_and_add_default_value.rb @@ -0,0 +1,14 @@ +class MakeTrackStateNotNullAndAddDefaultValue < ActiveRecord::Migration + class TmpTrack < ActiveRecord::Base + self.table_name = 'tracks' + end + + def change + TmpTrack.where(state: nil).each do |track| + track.state = 'confirmed' + track.save! + end + + change_column :tracks, :state, :string, null: false, default: 'new' + end +end diff --git a/db/schema.rb b/db/schema.rb index d3e14687..aa2f160d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -511,15 +511,15 @@ ActiveRecord::Schema.define(version: 20170807092805) do end create_table "tracks", force: :cascade do |t| - t.string "guid", null: false - t.string "name", null: false + t.string "guid", null: false + t.string "name", null: false t.text "description" t.string "color" t.datetime "created_at" t.datetime "updated_at" t.integer "program_id" - t.string "short_name", null: false - t.string "state" + t.string "short_name", null: false + t.string "state", default: "new", null: false t.boolean "cfp_active" t.integer "submitter_id" t.integer "room_id" diff --git a/spec/controllers/admin/tracks_controller_spec.rb b/spec/controllers/admin/tracks_controller_spec.rb index 2177af2f..ed76fe68 100644 --- a/spec/controllers/admin/tracks_controller_spec.rb +++ b/spec/controllers/admin/tracks_controller_spec.rb @@ -80,6 +80,10 @@ describe Admin::TracksController do it 'creates new track' do expect(Track.find(assigns(:track).id)).to be_a Track end + + it 'the new tracks has the correct attributes' do + expect(assigns(:track).state).to eq 'confirmed' + end end context 'save fails' do diff --git a/spec/factories/tracks.rb b/spec/factories/tracks.rb index 091d8d07..d30a686e 100644 --- a/spec/factories/tracks.rb +++ b/spec/factories/tracks.rb @@ -4,6 +4,7 @@ FactoryGirl.define do description { Faker::Lorem.sentence } color { Faker::Color.hex_color } short_name { SecureRandom.urlsafe_base64(5) } + state 'confirmed' program trait :self_organized do From 2f9eb0421940bffb44837d343e35ef0d0f3fd59d Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 12 Jul 2017 16:35:09 +0300 Subject: [PATCH 38/74] Enable track requests --- .haml-lint_todo.yml | 4 +++ app/helpers/application_helper.rb | 2 +- app/models/ability.rb | 8 +++++ app/models/cfp.rb | 3 +- app/views/admin/cfps/_tracks_cfp.html.haml | 12 ++++++++ .../conferences/_call_for_tracks.html.haml | 30 +++++++++++++++++++ .../conferences/_conference_details.html.haml | 4 +++ app/views/conferences/show.html.haml | 4 +++ app/views/tracks/index.html.haml | 2 +- app/views/tracks/show.html.haml | 2 +- spec/features/cfp_ability_spec.rb | 26 ++++++++++++++-- .../organization_admin_ability_spec.rb | 26 ++++++++++++++-- spec/features/organizer_ability_spec.rb | 26 ++++++++++++++-- spec/models/program_spec.rb | 26 +++++++++------- 14 files changed, 155 insertions(+), 20 deletions(-) create mode 100644 app/views/admin/cfps/_tracks_cfp.html.haml create mode 100644 app/views/conferences/_call_for_tracks.html.haml diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 52d5f5ca..11397297 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -180,6 +180,7 @@ linters: - "app/views/tracks/_form.html.haml" - "app/views/tracks/index.html.haml" - "app/views/tracks/show.html.haml" + - "app/views/conferences/_call_for_tracks.html.haml" # Offense count: 223 InstanceVariables: @@ -242,6 +243,8 @@ linters: - "app/views/schedules/_schedule_tabs.html.haml" - "app/views/admin/cfps/_events_cfp.html.haml" - "app/views/tracks/_form.html.haml" + - "app/views/admin/cfps/_tracks_cfp.html.haml" + - "app/views/conferences/_call_for_tracks.html.haml" # Offense count: 32 IdNames: @@ -261,6 +264,7 @@ linters: - "app/views/admin/users/show.html.haml" - "app/views/users/edit.html.haml" - "app/views/admin/cfps/_events_cfp.html.haml" + - "app/views/admin/cfps/_tracks_cfp.html.haml" # Offense count: 4 UnnecessaryInterpolation: diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 0abf6088..fdef7b4a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -56,7 +56,7 @@ module ApplicationHelper end def tracks(conference) - all = conference.program.tracks.map {|t| t.name} + all = conference.program.tracks.map { |t| t.name if !t.self_organized? || t.confirmed? && t.cfp_active }.compact first = all[0...-1] last = all[-1] ts = '' diff --git a/app/models/ability.rb b/app/models/ability.rb index 0ebabfdc..e5dbd7e4 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -95,6 +95,14 @@ class Ability can :manage, Commercial, commercialable_type: 'Event', commercialable_id: user.events.pluck(:id) can [:destroy], Openid + + can [:new, :create], Track do |track| + track.new_record? && track.program.cfps.for_tracks.try(:open?) + end + + can [:index, :show, :edit, :update], Track do |track| + user == track.submitter + end end # Abilities for users with roles wandering around in non-admin views. diff --git a/app/models/cfp.rb b/app/models/cfp.rb index 97f276c9..44d8281b 100644 --- a/app/models/cfp.rb +++ b/app/models/cfp.rb @@ -1,9 +1,10 @@ # cannot delete program if there are events submitted class Cfp < ActiveRecord::Base - TYPES = %w(events booths).freeze + TYPES = %w(events booths tracks).freeze scope :for_events, (-> { find_by(cfp_type: 'events') }) + scope :for_tracks, (-> { find_by(cfp_type: 'tracks') }) has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } belongs_to :program diff --git a/app/views/admin/cfps/_tracks_cfp.html.haml b/app/views/admin/cfps/_tracks_cfp.html.haml new file mode 100644 index 00000000..e381df96 --- /dev/null +++ b/app/views/admin/cfps/_tracks_cfp.html.haml @@ -0,0 +1,12 @@ +%dt + Start Date: +%dd#start_date + = @cfp.start_date.strftime('%A, %B %-d. %Y') +%dt + End Date: +%dd#end_date + = @cfp.end_date.strftime('%A, %B %-d. %Y') +%dt + Days Left: +%dd + = pluralize(@cfp.remaining_days, 'day') diff --git a/app/views/conferences/_call_for_tracks.html.haml b/app/views/conferences/_call_for_tracks.html.haml new file mode 100644 index 00000000..1abeb956 --- /dev/null +++ b/app/views/conferences/_call_for_tracks.html.haml @@ -0,0 +1,30 @@ += content_for :splash_nav do + %li + %a.smoothscroll{ href: '#callfortracks' } Call For Tracks + +.container + .row + .col-md-12.text-center + %h2 + Call for Tracks + %p.lead + We are ready to accept requests for tracks! + .row + .col-md-6.col-md-offset-3.col-sm-10.col-sm-offset-1 + %p + The submission period for track requests has begun + %em.notranslate + = @conference.program.cfps.for_tracks.start_date.strftime('%A, %B %-d. %Y') + and closes + %em.notranslate + = @conference.program.cfps.for_tracks.end_date.strftime('%A, %B %-d. %Y.') + - if @conference.program.cfps.for_tracks.try(:open?) + That means you have only + %b.notranslate= pluralize(@conference.program.cfps.for_tracks.remaining_days, 'day') + left! + - else + The submission period for track requests is closed. + .row + .col-md-12.text-center + %p.cta-button + = link_to "Submit your request for track", conference_program_tracks_path(@conference.short_title), class: 'btn btn-success btn-lg text-center' diff --git a/app/views/conferences/_conference_details.html.haml b/app/views/conferences/_conference_details.html.haml index d5bafad4..e8ca2449 100644 --- a/app/views/conferences/_conference_details.html.haml +++ b/app/views/conferences/_conference_details.html.haml @@ -30,6 +30,10 @@ = link_to "Register", new_conference_conference_registration_path(conference.short_title), class: "btn btn-default", disabled: cannot?(:new, Registration.new(conference_id: conference.id)) - if cannot?(:new, Registration.new(conference_id: conference.id)) && conference.registration_limit_exceeded? Sorry, no places left + - if !current_user.nil? && current_user.tracks.where(program: conference.program).length > 0 + = link_to "My Track Requests", conference_program_tracks_path(conference.short_title), class: 'btn btn-default' + - elsif can? :new, conference.program.tracks.new + = link_to "Submit Track Request", new_conference_program_track_path(conference.short_title), class: 'btn btn-default' - if !current_user.nil? && current_user.proposal_count(conference) > 0 = link_to "My Proposals", conference_program_proposals_path(conference.short_title), class: 'btn btn-default' - elsif can? :new, conference.program.events.new diff --git a/app/views/conferences/show.html.haml b/app/views/conferences/show.html.haml index fb488e22..edc72726 100644 --- a/app/views/conferences/show.html.haml +++ b/app/views/conferences/show.html.haml @@ -45,6 +45,10 @@ %section#program = render 'schedule_splashpage' + - if @conference.program.cfps.for_tracks.try(:open?) && @conference.splashpage.include_cfp + %section#callfortracks + = render 'call_for_tracks' + - if @conference.program.cfp_open? and @conference.splashpage.include_cfp %section#callforpapers = render 'call_for_paper' diff --git a/app/views/tracks/index.html.haml b/app/views/tracks/index.html.haml index d4670bf4..7e16304f 100644 --- a/app/views/tracks/index.html.haml +++ b/app/views/tracks/index.html.haml @@ -32,7 +32,7 @@ %span.label{style: "background-color: #{track.color}; color: #{ contrast_color(track.color) }"} = track.color %td - = track.state + = track.state.humanize %td = link_to 'Edit', edit_conference_program_track_path(@conference.short_title, track), method: :get, class: 'btn btn-primary' diff --git a/app/views/tracks/show.html.haml b/app/views/tracks/show.html.haml index c08818eb..37c180ae 100644 --- a/app/views/tracks/show.html.haml +++ b/app/views/tracks/show.html.haml @@ -16,7 +16,7 @@ %dt State: %dd - = @track.state + = @track.state.humanize %dt Description %dd diff --git a/spec/features/cfp_ability_spec.rb b/spec/features/cfp_ability_spec.rb index d40a54a4..cd9fa3bd 100644 --- a/spec/features/cfp_ability_spec.rb +++ b/spec/features/cfp_ability_spec.rb @@ -64,29 +64,51 @@ feature 'Has correct abilities' do visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq(new_admin_conference_program_cfp_path(conference.short_title)) - # Both event and booth exists + # Event and booth cfps exist cfb = create(:cfp, cfp_type: 'booths', program: conference.program) visit new_admin_conference_program_cfp_path(conference.short_title) - expect(current_path).to eq root_path + expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) visit edit_admin_conference_program_cfp_path(conference.short_title, conference.program.cfp) expect(current_path).to eq(edit_admin_conference_program_cfp_path(conference.short_title, conference.program.cfp)) + # Event, booth, track cfps exist + cft = create(:cfp, cfp_type: 'tracks', program: conference.program) + visit new_admin_conference_program_cfp_path(conference.short_title) + expect(current_path).to eq root_path + + # Booth and track cfps exist conference.program.cfp.destroy! visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) # Only booth exists + cft.destroy! visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq(new_admin_conference_program_cfp_path(conference.short_title)) visit edit_admin_conference_program_cfp_path(conference.short_title, cfb) expect(current_path). to eq(edit_admin_conference_program_cfp_path(conference.short_title, cfb)) + # No cfp exists cfb.destroy visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq(new_admin_conference_program_cfp_path(conference.short_title)) + # Only Tracks cfp exists + cft = create(:cfp, cfp_type: 'tracks', program: conference.program) + visit new_admin_conference_program_cfp_path(conference.short_title) + expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) + + visit edit_admin_conference_program_cfp_path(conference.short_title, cft) + expect(current_path).to eq edit_admin_conference_program_cfp_path(conference.short_title, cft) + + # Event and track cfps exist + create(:cfp, cfp_type: 'events', program: conference.program) + visit new_admin_conference_program_cfp_path(conference.short_title) + expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) + + cft.destroy! create(:event, program: conference.program) visit edit_admin_conference_program_event_path(conference.short_title, conference.program.events.first) expect(current_path).to eq(edit_admin_conference_program_event_path(conference.short_title, conference.program.events.first)) diff --git a/spec/features/organization_admin_ability_spec.rb b/spec/features/organization_admin_ability_spec.rb index 274d345f..12fd038b 100644 --- a/spec/features/organization_admin_ability_spec.rb +++ b/spec/features/organization_admin_ability_spec.rb @@ -106,29 +106,51 @@ feature 'Has correct abilities' do visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq(new_admin_conference_program_cfp_path(conference.short_title)) - # Both event and booth exists + # Event and booth cfps exist cfb = create(:cfp, cfp_type: 'booths', program: conference.program) visit new_admin_conference_program_cfp_path(conference.short_title) - expect(current_path).to eq root_path + expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) visit edit_admin_conference_program_cfp_path(conference.short_title, conference.program.cfp) expect(current_path).to eq(edit_admin_conference_program_cfp_path(conference.short_title, conference.program.cfp)) + # Event, booth, track cfps exist + cft = create(:cfp, cfp_type: 'tracks', program: conference.program) + visit new_admin_conference_program_cfp_path(conference.short_title) + expect(current_path).to eq root_path + + # Booth and track cfps exist conference.program.cfp.destroy! visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) # Only booth exists + cft.destroy! visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq(new_admin_conference_program_cfp_path(conference.short_title)) visit edit_admin_conference_program_cfp_path(conference.short_title, cfb) expect(current_path). to eq(edit_admin_conference_program_cfp_path(conference.short_title, cfb)) + # No cfp exists cfb.destroy visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq(new_admin_conference_program_cfp_path(conference.short_title)) + # Only Tracks cfp exists + cft = create(:cfp, cfp_type: 'tracks', program: conference.program) + visit new_admin_conference_program_cfp_path(conference.short_title) + expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) + + visit edit_admin_conference_program_cfp_path(conference.short_title, cft) + expect(current_path).to eq edit_admin_conference_program_cfp_path(conference.short_title, cft) + + # Event and track cfps exist + create(:cfp, cfp_type: 'events', program: conference.program) + visit new_admin_conference_program_cfp_path(conference.short_title) + expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) + + cft.destroy! visit admin_conference_program_events_path(conference.short_title) expect(current_path).to eq(admin_conference_program_events_path(conference.short_title)) diff --git a/spec/features/organizer_ability_spec.rb b/spec/features/organizer_ability_spec.rb index 3ed6ee39..ef2b3744 100644 --- a/spec/features/organizer_ability_spec.rb +++ b/spec/features/organizer_ability_spec.rb @@ -112,29 +112,51 @@ feature 'Has correct abilities' do visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq(new_admin_conference_program_cfp_path(conference.short_title)) - # Both event and booth exists + # Event and booth cfps exist cfb = create(:cfp, cfp_type: 'booths', program: conference.program) visit new_admin_conference_program_cfp_path(conference.short_title) - expect(current_path).to eq root_path + expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) visit edit_admin_conference_program_cfp_path(conference.short_title, conference.program.cfp) expect(current_path).to eq(edit_admin_conference_program_cfp_path(conference.short_title, conference.program.cfp)) + # Event, booth, track cfps exist + cft = create(:cfp, cfp_type: 'tracks', program: conference.program) + visit new_admin_conference_program_cfp_path(conference.short_title) + expect(current_path).to eq root_path + + # Booth and track cfps exist conference.program.cfp.destroy! visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) # Only booth exists + cft.destroy! visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq(new_admin_conference_program_cfp_path(conference.short_title)) visit edit_admin_conference_program_cfp_path(conference.short_title, cfb) expect(current_path). to eq(edit_admin_conference_program_cfp_path(conference.short_title, cfb)) + # No cfp exists cfb.destroy visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq(new_admin_conference_program_cfp_path(conference.short_title)) + # Only Tracks cfp exists + cft = create(:cfp, cfp_type: 'tracks', program: conference.program) + visit new_admin_conference_program_cfp_path(conference.short_title) + expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) + + visit edit_admin_conference_program_cfp_path(conference.short_title, cft) + expect(current_path).to eq edit_admin_conference_program_cfp_path(conference.short_title, cft) + + # Event and track cfps exist + create(:cfp, cfp_type: 'events', program: conference.program) + visit new_admin_conference_program_cfp_path(conference.short_title) + expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) + + cft.destroy! visit admin_conference_program_events_path(conference.short_title) expect(current_path).to eq(admin_conference_program_events_path(conference.short_title)) diff --git a/spec/models/program_spec.rb b/spec/models/program_spec.rb index d81e32bc..1c8eabd9 100644 --- a/spec/models/program_spec.rb +++ b/spec/models/program_spec.rb @@ -253,28 +253,34 @@ describe Program do end describe '#remaining_cfp_types' do - it 'returns an array with the types for which a cfp doesn\'t exist, when only the Event type does' do - expect(program.remaining_cfp_types).to eq(Cfp::TYPES) + it 'returns an array without the \'events\' type, when the cfp for events exists' do create(:cfp, cfp_type: 'events', program: program) - expect(program.remaining_cfp_types).to eq(['booths']) + expect(program.remaining_cfp_types).to be_a Array + expect(program.remaining_cfp_types.include?('events')).to eq false end - it 'returns an array with the types for which a cfp doesn\'t exist, when only the Booth type does' do - expect(program.remaining_cfp_types).to eq(Cfp::TYPES) + it 'returns an array without the \'booths\' type, when the cfp for booths exists' do create(:cfp, cfp_type: 'booths', program: program) - expect(program.remaining_cfp_types).to eq(['events']) + expect(program.remaining_cfp_types).to be_a Array + expect(program.remaining_cfp_types.include?('booths')).to eq false end - it 'returns an empty array when all the cfp types exist' do - expect(program.remaining_cfp_types).to eq(Cfp::TYPES) + it 'returns an array without the \'tracks\' type, when the cfp for tracks exists' do + create(:cfp, cfp_type: 'tracks', program: program) + expect(program.remaining_cfp_types).to be_a Array + expect(program.remaining_cfp_types.include?('tracks')).to eq false + end + + it 'returns an empty array when cfps for all the types exist' do create(:cfp, cfp_type: 'events', program: program) create(:cfp, cfp_type: 'booths', program: program) + create(:cfp, cfp_type: 'tracks', program: program) expect(program.remaining_cfp_types).to eq([]) end - it 'returns all the possible cfp types when there is no existed cfp type' do + it 'returns all the possible cfp types when there is no cfp' do expect(program.remaining_cfp_types).to eq(Cfp::TYPES) - expect(program.remaining_cfp_types). to eq(%w[events booths]) + expect(program.remaining_cfp_types). to eq(%w[events booths tracks]) end end end From d9faffc96a04dacdac8472f80c9e3f106e69a2e2 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Fri, 14 Jul 2017 15:57:34 +0300 Subject: [PATCH 39/74] Implement track request acceptance Allow track submitter to request specific dates Redirect to Tracks#edit if a track doesn't have a room or start/end date before accepting it Don't allow the submitter or the track organizers to edit the request after it has been accepted or confirmed Restrict track selection in proposals and move track selection from Proposals form to events helper Mark cfp_active of the tracks table as not null and fill in true if nil --- .haml-lint_todo.yml | 2 + app/controllers/admin/tracks_controller.rb | 46 +++ app/controllers/tracks_controller.rb | 28 +- app/helpers/application_helper.rb | 2 +- app/helpers/events_helper.rb | 8 + app/models/ability.rb | 6 +- app/models/admin_ability.rb | 4 + app/models/event.rb | 8 + app/models/track.rb | 52 ++- .../tracks/_change_state_dropdown.html.haml | 34 ++ app/views/admin/tracks/_form.html.haml | 3 +- app/views/admin/tracks/index.html.haml | 32 +- app/views/proposals/_proposal_form.html.haml | 5 +- app/views/tracks/_form.html.haml | 4 +- app/views/tracks/index.html.haml | 29 +- app/views/tracks/show.html.haml | 17 +- config/routes.rb | 15 +- ...20134353_make_track_cfp_active_not_null.rb | 14 + db/schema.rb | 2 +- .../admin/tracks_controller_spec.rb | 203 ++++++++++- spec/controllers/tracks_controller_spec.rb | 66 +++- spec/factories/tracks.rb | 4 + spec/models/ability_spec.rb | 30 ++ spec/models/admin_ability_spec.rb | 4 +- spec/models/conference_spec.rb | 4 +- spec/models/event_spec.rb | 34 ++ spec/models/track_spec.rb | 315 +++++++++++++++++- 27 files changed, 909 insertions(+), 62 deletions(-) create mode 100644 app/views/admin/tracks/_change_state_dropdown.html.haml create mode 100644 db/migrate/20170720134353_make_track_cfp_active_not_null.rb diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 11397297..89d18c3f 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -181,6 +181,7 @@ linters: - "app/views/tracks/index.html.haml" - "app/views/tracks/show.html.haml" - "app/views/conferences/_call_for_tracks.html.haml" + - "app/views/admin/tracks/_change_state_dropdown.html.haml" # Offense count: 223 InstanceVariables: @@ -245,6 +246,7 @@ linters: - "app/views/tracks/_form.html.haml" - "app/views/admin/cfps/_tracks_cfp.html.haml" - "app/views/conferences/_call_for_tracks.html.haml" + - "app/views/admin/tracks/_change_state_dropdown.html.haml" # Offense count: 32 IdNames: diff --git a/app/controllers/admin/tracks_controller.rb b/app/controllers/admin/tracks_controller.rb index 2cb6ec2b..3d51a677 100644 --- a/app/controllers/admin/tracks_controller.rb +++ b/app/controllers/admin/tracks_controller.rb @@ -20,6 +20,7 @@ module Admin def create @track = @program.tracks.new(track_params) @track.state = 'confirmed' + @track.cfp_active = true if @track.save redirect_to admin_conference_program_tracks_path(conference_id: @conference.short_title), notice: 'Track successfully created.' @@ -60,10 +61,55 @@ module Admin end end + def restart + update_state(:restart, "Review for #{@track.name} started!") + end + + def to_accept + update_state(:to_accept, "Track #{@track.name} marked as a possible acceptance!") + end + + def accept + if @track.room && @track.start_date && @track.end_date + update_state(:accept, "Track #{@track.name} accepted!") + else + flash[:alert] = 'Please make sure that the track has a room and start/end dates before accepting it' + redirect_to edit_admin_conference_program_track_path(@conference.short_title, @track) + end + end + + def confirm + update_state(:confirm, "Track #{@track.name} confirmed!") + end + + def to_reject + update_state(:to_reject, "Track #{@track.name} marked as a possible rejection!") + end + + def reject + update_state(:reject, "Track #{@track.name} rejected!") + end + + def cancel + update_state(:cancel, "Track #{@track.name} canceled!") + end + private def track_params params.require(:track).permit(:name, :description, :color, :short_name, :cfp_active, :start_date, :end_date, :room_id) end + + def update_state(transition, notice) + errors = @track.update_state(transition) + + if errors.blank? + flash[:notice] = notice + else + flash[:error] = errors + end + + redirect_back_or_to(admin_conference_program_tracks_path(conference_id: @conference.short_title)) + end end end diff --git a/app/controllers/tracks_controller.rb b/app/controllers/tracks_controller.rb index 59deae3c..9305950e 100644 --- a/app/controllers/tracks_controller.rb +++ b/app/controllers/tracks_controller.rb @@ -4,7 +4,7 @@ class TracksController < ApplicationController load_and_authorize_resource through: :program, find_by: :short_name def index - @tracks = current_user.tracks.where(program: @program) + @tracks = @tracks.where(submitter: current_user) end def show; end @@ -38,9 +38,33 @@ class TracksController < ApplicationController end end + def restart + update_state(:restart, "Track #{@track.name} re-submitted.") + end + + def confirm + update_state(:confirm, "Track #{@track.name} confirmed.") + end + + def withdraw + update_state(:withdraw, "Track #{@track.name} withdrawn.") + end + private def track_params - params.require(:track).permit(:name, :description, :color, :short_name) + params.require(:track).permit(:name, :description, :color, :short_name, :start_date, :end_date) + end + + def update_state(transition, notice) + errors = @track.update_state(transition) + + if errors.blank? + flash[:notice] = notice + else + flash[:error] = errors + end + + redirect_back_or_to(conference_program_tracks_path(conference_id: @conference.short_title)) end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index fdef7b4a..040927c1 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -56,7 +56,7 @@ module ApplicationHelper end def tracks(conference) - all = conference.program.tracks.map { |t| t.name if !t.self_organized? || t.confirmed? && t.cfp_active }.compact + all = conference.program.tracks.where(state: 'confirmed', cfp_active: true).pluck(:name) first = all[0...-1] last = all[-1] ts = '' diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 012bd930..dbc609ac 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -39,4 +39,12 @@ module EventsHelper content_tag :span, 'REPLACEMENT', class: (['label', 'label-info'] + label_classes) end end + + def track_selector_input(form) + if @program.tracks.any? + form.input :track_id, as: :select, + collection: @program.tracks.where(state: 'confirmed', cfp_active: true).pluck(:name, :id), + include_blank: true + end + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index e5dbd7e4..63ec68dc 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -100,8 +100,10 @@ class Ability track.new_record? && track.program.cfps.for_tracks.try(:open?) end - can [:index, :show, :edit, :update], Track do |track| - user == track.submitter + can [:index, :show, :restart, :confirm, :withdraw], Track, submitter_id: user.id + + can [:edit, :update], Track do |track| + user == track.submitter && !(track.accepted? || track.confirmed?) end end diff --git a/app/models/admin_ability.rb b/app/models/admin_ability.rb index 716aecb0..b0c9d4c9 100644 --- a/app/models/admin_ability.rb +++ b/app/models/admin_ability.rb @@ -275,6 +275,10 @@ class AdminAbility can :manage, Track, id: track_ids_for_track_organizer + cannot [:edit, :update], Track do |track| + track.self_organized_and_accepted_or_confirmed? + end + # Show Roles in the admin sidebar and allow authorization of the index action can [:index, :show], Role do |role| role.resource_type == 'Conference' || role.resource_type == 'Track' diff --git a/app/models/event.rb b/app/models/event.rb index 8436aac9..682eb6c8 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -45,6 +45,7 @@ class Event < ActiveRecord::Base validates :max_attendees, numericality: { only_integer: true, greater_than_or_equal_to: 1, allow_nil: true } validate :max_attendees_no_more_than_room_size + validate :acceptable_track scope :confirmed, -> { where(state: 'confirmed') } scope :canceled, -> { where(state: 'canceled') } @@ -303,4 +304,11 @@ class Event < ActiveRecord::Base def conference_id program.conference_id end + + ## + # Allow only confirmed tracks that belong to the same program and are included in the cfp + def acceptable_track + return unless track && track.program && program + errors.add(:track, 'is invalid') unless track.confirmed? && track.cfp_active && track.program == program + end end diff --git a/app/models/track.rb b/app/models/track.rb index a53471bc..3743cd79 100644 --- a/app/models/track.rb +++ b/app/models/track.rb @@ -22,13 +22,13 @@ class Track < ActiveRecord::Base } validates :state, presence: true, - inclusion: { in: %w(new to_accept accepted confirmed to_reject rejected canceled withdrawn) }, - if: :self_organized? - validates :cfp_active, inclusion: { in: [true, false] }, if: :self_organized? + inclusion: { in: %w(new to_accept accepted confirmed to_reject rejected canceled withdrawn) } + validates :cfp_active, inclusion: { in: [true, false] } validates :start_date, presence: true, if: :self_organized_and_accepted_or_confirmed? validates :end_date, presence: true, if: :self_organized_and_accepted_or_confirmed? validates :room, presence: true, if: :self_organized_and_accepted_or_confirmed? - validate :valid_dates, if: :self_organized_and_accepted_or_confirmed? + validate :valid_dates + validate :valid_room, if: :self_organized_and_accepted_or_confirmed? before_validation :capitalize_color @@ -45,7 +45,7 @@ class Track < ActiveRecord::Base event :restart do transitions to: :new, from: [:rejected, :withdrawn, :canceled] end - event :readiness_to_accept do + event :to_accept do transitions to: :to_accept, from: [:new] end event :accept do @@ -54,7 +54,7 @@ class Track < ActiveRecord::Base event :confirm do transitions to: :confirmed, from: [:accepted], on_transition: :assign_role_to_submitter end - event :readiness_to_reject do + event :to_reject do transitions to: :to_reject, from: [:new] end event :reject do @@ -107,6 +107,7 @@ class Track < ActiveRecord::Base events.each do |event| event.track = nil + event.save! end end @@ -137,6 +138,19 @@ class Track < ActiveRecord::Base self_organized? && (accepted? || confirmed?) end + def update_state(transition) + error = '' + + begin + send(transition) + rescue Transitions::InvalidTransition => e + error += "State update failed. #{e.message} " + end + + error += errors.full_messages.join(', ') unless save + error + end + private def generate_guid @@ -162,26 +176,32 @@ class Track < ActiveRecord::Base end def valid_dates - return unless start_date && end_date - - if program && program.conference && program.conference.start_date && (start_date < program.conference.start_date) - errors.add(:start_date, "can't be before the conference start date (#{program.conference.end_date})") + if start_date && program && program.conference && program.conference.start_date && (start_date < program.conference.start_date) + errors.add(:start_date, "can't be before the conference start date (#{program.conference.start_date})") end - if program && program.conference && program.conference.start_date && (end_date < program.conference.start_date) - errors.add(:end_date, "can't be before the conference start date (#{program.conference.end_date})") + if end_date && program && program.conference && program.conference.start_date && (end_date < program.conference.start_date) + errors.add(:end_date, "can't be before the conference start date (#{program.conference.start_date})") end - if program && program.conference && program.conference.end_date && (start_date > program.conference.end_date) + if start_date && program && program.conference && program.conference.end_date && (start_date > program.conference.end_date) errors.add(:start_date, "can't be after the conference end date (#{program.conference.end_date})") end - if program && program.conference && program.conference.end_date && (end_date > program.conference.end_date) + if end_date && program && program.conference && program.conference.end_date && (end_date > program.conference.end_date) errors.add(:end_date, "can't be after the conference end date (#{program.conference.end_date})") end - if start_date > end_date - errors.add(:start_date, 'can\'t be after the end_date') + if start_date && end_date && (start_date > end_date) + errors.add(:start_date, 'can\'t be after the end date') + end + end + + ## + # Verify that the room is a room of the conference + def valid_room + if room && room.venue && room.venue.conference && program && program.conference && (program.conference != room.venue.conference) + errors.add(:room, "must be a room of #{program.conference.venue.name}") end end end diff --git a/app/views/admin/tracks/_change_state_dropdown.html.haml b/app/views/admin/tracks/_change_state_dropdown.html.haml new file mode 100644 index 00000000..0bbbcd7f --- /dev/null +++ b/app/views/admin/tracks/_change_state_dropdown.html.haml @@ -0,0 +1,34 @@ +- if track.transition_possible? :restart + %li= link_to 'Start review', + restart_admin_conference_program_track_path(@conference.short_title, track), + method: :patch, id: "restart_track_#{track.id}" + +- if track.transition_possible? :to_accept + %li= link_to 'Mark as possible acceptance', + to_accept_admin_conference_program_track_path(@conference.short_title, track), + method: :patch, id: "to_accept_track_#{track.id}" + +- if track.transition_possible? :accept + %li= link_to 'Accept track request', + accept_admin_conference_program_track_path(@conference.short_title, track), + method: :patch, id: "accept_track_#{track.id}" + +- if track.transition_possible? :confirm + %li= link_to 'Confirm track', + confirm_admin_conference_program_track_path(@conference.short_title, track), + method: :patch, id: "confirm_track_#{track.id}" + +- if track.transition_possible? :to_reject + %li= link_to 'Mark as possible rejection', + to_reject_admin_conference_program_track_path(@conference.short_title, track), + method: :patch, id: "to_reject_track_#{track.id}" + +- if track.transition_possible? :reject + %li= link_to 'Reject track request', + reject_admin_conference_program_track_path(@conference.short_title, track), + method: :patch, confirm: 'Are you sure?', id: "reject_track_#{track.id}" + +- if track.transition_possible? :cancel + %li= link_to 'Cancel track request', + cancel_admin_conference_program_track_path(@conference.short_title, track), + method: :patch, id: "cancel_track_#{track.id}" diff --git a/app/views/admin/tracks/_form.html.haml b/app/views/admin/tracks/_form.html.haml index cb2e8b01..eb12561b 100644 --- a/app/views/admin/tracks/_form.html.haml +++ b/app/views/admin/tracks/_form.html.haml @@ -19,6 +19,5 @@ - else Please add a venue with rooms, if you want to select a room for the track. = f.input :description, input_html: {rows: 2, data: { provide: 'markdown-editable' } }, hint: markdown_hint - - if @track.self_organized? - = f.input :cfp_active, label: 'Allow event submitters to select this track for their proposal' + = f.input :cfp_active, label: 'Allow event submitters to select this track for their proposal' = f.action :submit, as: :button, button_html: { class: 'btn btn-primary' } diff --git a/app/views/admin/tracks/index.html.haml b/app/views/admin/tracks/index.html.haml index 4da97a18..0cfe0ff2 100644 --- a/app/views/admin/tracks/index.html.haml +++ b/app/views/admin/tracks/index.html.haml @@ -40,21 +40,24 @@ = track.color %td - if track.self_organized? - = track.state + .btn-group + %button{ type: 'button', class: 'btn btn-link dropdown-toggle', 'data-toggle' => 'dropdown' } + = track.state.humanize + %span.caret + %ul.dropdown-menu{ role: 'menu' } + = render 'change_state_dropdown', track: track - else = track.state.humanize %td - - if track.self_organized? - = check_box_tag "#{@conference.short_title}_#{track.short_name}", track.id, track.cfp_active, - class: 'switch-checkbox', method: :patch, - url: toggle_cfp_inclusion_admin_conference_program_track_path(@conference.short_title, id: track.short_name)+"?included=", - data: { size: 'small', - on_color: 'success', - off_color: 'warning', - on_text: 'Yes', - off_text: 'No' } - - else - %i.fa.fa-check + = check_box_tag "#{@conference.short_title}_#{track.short_name}", track.id, track.cfp_active, + class: 'switch-checkbox', method: :patch, + url: toggle_cfp_inclusion_admin_conference_program_track_path(@conference.short_title, id: track.short_name)+"?included=", + data: { size: 'small', + on_color: 'success', + off_color: 'warning', + on_text: 'Yes', + off_text: 'No' } + %td - if track.room = link_to track.room.name, admin_conference_venue_room_path(@conference.short_title, track.room.id) @@ -72,8 +75,9 @@ N/A %td .btn-group{role: "group"} - = link_to 'Edit', edit_admin_conference_program_track_path(@conference.short_title, track), - method: :get, class: 'btn btn-primary' + - if can? :edit, track + = link_to 'Edit', edit_admin_conference_program_track_path(@conference.short_title, track), + method: :get, class: 'btn btn-primary' - if can? :destroy, track = link_to 'Delete', admin_conference_program_track_path(@conference.short_title, track), method: :delete, class: 'btn btn-danger', diff --git a/app/views/proposals/_proposal_form.html.haml b/app/views/proposals/_proposal_form.html.haml index f31651a1..4b66240c 100644 --- a/app/views/proposals/_proposal_form.html.haml +++ b/app/views/proposals/_proposal_form.html.haml @@ -6,10 +6,7 @@ = speaker_selector_input f - - if @program.tracks.any? - = f.input :track_id, as: :select, - collection: @program.tracks.map {|track| ["#{track.name}", track.id] }, - include_blank: true + = track_selector_input f = f.input :event_type_id, as: :select, collection: @conference.program.event_types.map {|type| ["#{type.title} - #{show_time(type.length)}", type.id, diff --git a/app/views/tracks/_form.html.haml b/app/views/tracks/_form.html.haml index 0e48a851..2a55df5a 100644 --- a/app/views/tracks/_form.html.haml +++ b/app/views/tracks/_form.html.haml @@ -9,9 +9,11 @@ Track .row .col-md-12 - = semantic_form_for(@track, url: (@track.new_record? ? conference_program_tracks_path : conference_program_track_path(@conference.short_title, @track))) do |f| + = semantic_form_for(@track, url: (@track.new_record? ? conference_program_tracks_path(@conference.short_title) : conference_program_track_path(@conference.short_title, @track))) do |f| = f.input :name = f.input :short_name, hint: "A short and unique handle for the track, using only letters, numbers, underscores, and dashes. This will be used to identify the track in URLs etc. Example: 'my_awesome_track'", input_html: { required: 'required', pattern: '[a-zA-Z0-9_-]+', title: 'Only letters, numbers, underscores, and dashes.' } = f.input :color, input_html: {size: 6, type: 'color'}, required: true + = f.input :start_date, as: :string, input_html: { id: 'registration-period-start-datepicker', start_date: @conference.start_date, end_date: @conference.end_date, readonly: 'readonly' } + = f.input :end_date, as: :string, input_html: { id: 'registration-period-end-datepicker', readonly: 'readonly' } = f.input :description, input_html: {rows: 2, data: { provide: 'markdown-editable' } }, required: true, hint: markdown_hint = f.action :submit, as: :button, button_html: { class: 'btn btn-primary' } diff --git a/app/views/tracks/index.html.haml b/app/views/tracks/index.html.haml index 7e16304f..fd1235d4 100644 --- a/app/views/tracks/index.html.haml +++ b/app/views/tracks/index.html.haml @@ -16,6 +16,8 @@ %th Description %th Color %th State + %th Start Date + %th End Date %th Actions %tbody - @tracks.each do |track| @@ -34,10 +36,31 @@ %td = track.state.humanize %td - = link_to 'Edit', edit_conference_program_track_path(@conference.short_title, track), - method: :get, class: 'btn btn-primary' + - if track.start_date + = track.start_date.strftime('%A, %B %-d. %Y') + - else + N/A + %td + - if track.end_date + = track.end_date.strftime('%A, %B %-d. %Y') + - else + N/A + %td + - if track.transition_possible? :confirm + = link_to 'Confirm', confirm_conference_program_track_path(@conference.short_title, track), + method: :patch, class: 'btn btn-mini btn-success', id: "confirm_track_#{track.id}" + - if track.transition_possible? :withdraw + = link_to 'Withdraw', withdraw_conference_program_track_path(@conference.short_title, track), + method: :patch, data: { confirm: 'Are you sure you want to withdraw this track request?' }, + class: 'btn btn-mini btn-warning', id: "withdraw_track_request_#{track.id}" + - if track.transition_possible? :restart + = link_to 'Re-Submit', restart_conference_program_track_path(@conference.short_title, track), + method: :patch, class: 'btn btn-mini btn-success', id: "resubmit_track_request_#{track.id}" + - if can? :edit, track + = link_to 'Edit', edit_conference_program_track_path(@conference.short_title, track), + method: :get, class: 'btn btn-primary' .row .col-md-12 - - if can? :create, @track + - if can? :new, @program.tracks.new = link_to "New Track request", new_conference_program_track_path(@conference.short_title), class: 'btn btn-success pull-right' diff --git a/app/views/tracks/show.html.haml b/app/views/tracks/show.html.haml index 37c180ae..c5bc27e5 100644 --- a/app/views/tracks/show.html.haml +++ b/app/views/tracks/show.html.haml @@ -17,10 +17,25 @@ State: %dd = @track.state.humanize + %dt + Start date: + %dd + - if @track.start_date + = @track.start_date.strftime('%A, %B %-d. %Y') + - else + N/A + %dt + End date: + %dd + - if @track.end_date + = @track.end_date.strftime('%A, %B %-d. %Y') + - else + N/A %dt Description %dd = @track.description .row .col-md-12.text-right - = link_to 'Edit Track request', edit_conference_program_track_path(@conference.short_title, @track), class: 'btn btn-primary' + - if can? :edit, @track + = link_to 'Edit Track request', edit_conference_program_track_path(@conference.short_title, @track), class: 'btn btn-primary' diff --git a/config/routes.rb b/config/routes.rb index be507d35..d4d6d0ee 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -69,6 +69,13 @@ Osem::Application.routes.draw do resources :tracks do member do patch :toggle_cfp_inclusion + patch :restart + patch :to_accept + patch :accept + patch :confirm + patch :to_reject + patch :reject + patch :cancel end end resources :event_types @@ -149,7 +156,13 @@ Osem::Application.routes.draw do patch '/restart' => 'proposals#restart' end end - resources :tracks, except: :destroy + resources :tracks, except: :destroy do + member do + patch :restart + patch :confirm + patch :withdraw + end + end end # TODO: change conference_registrations to singular resource diff --git a/db/migrate/20170720134353_make_track_cfp_active_not_null.rb b/db/migrate/20170720134353_make_track_cfp_active_not_null.rb new file mode 100644 index 00000000..0f75d046 --- /dev/null +++ b/db/migrate/20170720134353_make_track_cfp_active_not_null.rb @@ -0,0 +1,14 @@ +class MakeTrackCfpActiveNotNull < ActiveRecord::Migration + class TmpTrack < ActiveRecord::Base + self.table_name = 'tracks' + end + + def change + TmpTrack.where(cfp_active: nil).each do |track| + track.cfp_active = true + track.save! + end + + change_column_null :tracks, :cfp_active, false + end +end diff --git a/db/schema.rb b/db/schema.rb index aa2f160d..f8cc3ddc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -520,7 +520,7 @@ ActiveRecord::Schema.define(version: 20170807092805) do t.integer "program_id" t.string "short_name", null: false t.string "state", default: "new", null: false - t.boolean "cfp_active" + t.boolean "cfp_active", null: false t.integer "submitter_id" t.integer "room_id" t.date "start_date" diff --git a/spec/controllers/admin/tracks_controller_spec.rb b/spec/controllers/admin/tracks_controller_spec.rb index ed76fe68..70ca48a1 100644 --- a/spec/controllers/admin/tracks_controller_spec.rb +++ b/spec/controllers/admin/tracks_controller_spec.rb @@ -5,7 +5,7 @@ describe Admin::TracksController do let(:conference) { create(:conference) } let!(:track) { create(:track, program: conference.program, color: '#800080') } - let!(:self_organized_track) { create(:track, :self_organized, program: conference.program) } + let!(:self_organized_track) { create(:track, :self_organized, program: conference.program, name: 'My awesome track') } before :each do sign_in(admin) @@ -83,6 +83,7 @@ describe Admin::TracksController do it 'the new tracks has the correct attributes' do expect(assigns(:track).state).to eq 'confirmed' + expect(assigns(:track).cfp_active).to eq true end end @@ -257,4 +258,204 @@ describe Admin::TracksController do end end end + + describe 'PATCH #restart' do + before :each do + self_organized_track.state = 'canceled' + self_organized_track.save! + patch :restart, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Review for My awesome track started!' + end + + it 'changes the track\'s state to new' do + expect(self_organized_track.state).to eq 'new' + end + end + + describe 'PATCH #to_accept' do + before :each do + patch :to_accept, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track marked as a possible acceptance!' + end + + it 'changes the track\'s state to to_accept' do + expect(self_organized_track.state).to eq 'to_accept' + end + end + + describe 'PATCH #accept' do + shared_examples 'fails to accept' do |start_date, end_date, room| + before :each do + self_organized_track.start_date = start_date ? Date.today : nil + self_organized_track.end_date = end_date ? Date.today : nil + if room + conference.venue = create(:venue) + self_organized_track.room = create(:room, venue: conference.venue) + else + self_organized_track.room = nil + end + self_organized_track.save! + + patch :accept, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'redirects to Tracks#edit' do + expect(response).to redirect_to edit_admin_conference_program_track_path(conference.short_title, self_organized_track) + end + + it 'shows message in flash alert' do + expect(flash[:alert]).to eq 'Please make sure that the track has a room and start/end dates before accepting it' + end + end + + context 'has start_date, end_date and room' do + before :each do + self_organized_track.start_date = Date.today + self_organized_track.end_date = Date.today + conference.venue = create(:venue) + self_organized_track.room = create(:room, venue: conference.venue) + self_organized_track.save! + + patch :accept, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track accepted!' + end + + it 'changes the track\'s state to accepted' do + expect(self_organized_track.state).to eq 'accepted' + end + end + + context 'has start_date and end_date' do + it_behaves_like 'fails to accept', true, true, false + end + + context 'has start_date and room' do + it_behaves_like 'fails to accept', true, false, true + end + + context 'has start_date' do + it_behaves_like 'fails to accept', true, false, false + end + + context 'has end_date and room' do + it_behaves_like 'fails to accept', false, true, true + end + + context 'has end_date' do + it_behaves_like 'fails to accept', false, true, false + end + + context 'has room' do + it_behaves_like 'fails to accept', false, false, true + end + + context 'has non of start_date, end_date, room' do + it_behaves_like 'fails to accept', false, false, false + end + end + + describe 'PATCH #confirm' do + before :each do + self_organized_track.state = 'accepted' + self_organized_track.save! + patch :confirm, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track confirmed!' + end + + it 'changes the track\'s state to confirmed' do + expect(self_organized_track.state).to eq 'confirmed' + end + end + + describe 'PATCH #to_reject' do + before :each do + patch :to_reject, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track marked as a possible rejection!' + end + + it 'changes the track\'s state to to_reject' do + expect(self_organized_track.state).to eq 'to_reject' + end + end + + describe 'PATCH #reject' do + before :each do + patch :reject, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track rejected!' + end + + it 'changes the track\'s state to rejected' do + expect(self_organized_track.state).to eq 'rejected' + end + end + + describe 'PATCH #cancel' do + before :each do + self_organized_track.state = 'confirmed' + self_organized_track.save! + patch :cancel, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track canceled!' + end + + it 'changes the track\'s state to canceled' do + expect(self_organized_track.state).to eq 'canceled' + end + end end diff --git a/spec/controllers/tracks_controller_spec.rb b/spec/controllers/tracks_controller_spec.rb index 373435c2..0e3a0553 100644 --- a/spec/controllers/tracks_controller_spec.rb +++ b/spec/controllers/tracks_controller_spec.rb @@ -1,12 +1,11 @@ require 'spec_helper' describe TracksController do - # A regular user should be used when the track requests have been enabled let(:user) { create(:admin) } let(:conference) { create(:conference) } let!(:regular_track) { create(:track, program: conference.program) } - let!(:self_organized_track) { create(:track, :self_organized, program: conference.program, submitter: user, color: '#800080') } + let!(:self_organized_track) { create(:track, :self_organized, program: conference.program, submitter: user, name: 'My awesome track', color: '#800080') } before :each do sign_in(user) @@ -176,4 +175,67 @@ describe TracksController do end end end + + describe 'PATCH #restart' do + before :each do + self_organized_track.state = 'withdrawn' + self_organized_track.save! + patch :restart, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track re-submitted.' + end + + it 'changes the track\'s state to new' do + expect(self_organized_track.state).to eq 'new' + end + end + + describe 'PATCH #confirm' do + before :each do + self_organized_track.state = 'accepted' + self_organized_track.save! + patch :confirm, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track confirmed.' + end + + it 'changes the track\'s state to confirmed' do + expect(self_organized_track.state).to eq 'confirmed' + end + end + + describe 'PATCH #withdraw' do + before :each do + self_organized_track.state = 'confirmed' + self_organized_track.save! + patch :withdraw, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track withdrawn.' + end + + it 'changes the track\'s state to withdrawn' do + expect(self_organized_track.state).to eq 'withdrawn' + end + end end diff --git a/spec/factories/tracks.rb b/spec/factories/tracks.rb index d30a686e..d61c9a1b 100644 --- a/spec/factories/tracks.rb +++ b/spec/factories/tracks.rb @@ -5,12 +5,16 @@ FactoryGirl.define do color { Faker::Color.hex_color } short_name { SecureRandom.urlsafe_base64(5) } state 'confirmed' + cfp_active true program trait :self_organized do association :submitter, factory: :user state 'new' cfp_active false + start_date { Date.today } + end_date { Date.today } + room end end end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 2144cec6..ff3f14cb 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -26,6 +26,7 @@ describe 'User' do let(:program_with_cfp) { create(:program, :with_cfp) } let(:program_without_cfp) { create(:program) } + let(:program_with_call_for_tracks) { create(:cfp, cfp_type: 'tracks').program } let(:conference_with_open_registration) { create(:conference) } let!(:open_registration_period) { create(:registration_period, conference: conference_with_open_registration, start_date: Date.current - 6.days) } let(:conference_with_closed_registration) { create(:conference) } @@ -82,6 +83,10 @@ describe 'User' do let(:user_event_with_cfp) { create(:event, users: [user], program: program_with_cfp) } let(:user_commercial) { create(:commercial, commercialable: user_event_with_cfp) } + let(:user_self_organized_track) { create(:track, :self_organized, submitter: user) } + let(:accepted_user_self_organized_track) { create(:track, :self_organized, submitter: user, state: 'accepted') } + let(:confirmed_user_self_organized_track) { create(:track, :self_organized, submitter: user, state: 'confirmed') } + let(:other_self_organized_track) { create(:track, :self_organized) } it{ should be_able_to(:manage, user) } @@ -114,6 +119,31 @@ describe 'User' do it{ should be_able_to(:create, user_event_with_cfp.commercials.new) } it{ should be_able_to(:manage, user_commercial) } it{ should_not be_able_to(:manage, commercial_event_unconfirmed) } + + it{ should be_able_to(:new, Track.new(program: program_with_call_for_tracks)) } + it{ should be_able_to(:create, Track.new(program: program_with_call_for_tracks)) } + it{ should_not be_able_to(:new, Track.new(program: program_without_cfp)) } + it{ should_not be_able_to(:create, Track.new(program: program_without_cfp)) } + + it{ should be_able_to(:index, user_self_organized_track) } + it{ should be_able_to(:show, user_self_organized_track) } + it{ should be_able_to(:restart, user_self_organized_track) } + it{ should be_able_to(:confirm, user_self_organized_track) } + it{ should be_able_to(:withdraw, user_self_organized_track) } + it{ should_not be_able_to(:index, other_self_organized_track) } + it{ should_not be_able_to(:show, other_self_organized_track) } + it{ should_not be_able_to(:restart, other_self_organized_track) } + it{ should_not be_able_to(:confirm, other_self_organized_track) } + it{ should_not be_able_to(:withdraw, other_self_organized_track) } + + it{ should be_able_to(:edit, user_self_organized_track) } + it{ should be_able_to(:update, user_self_organized_track) } + it{ should_not be_able_to(:edit, accepted_user_self_organized_track) } + it{ should_not be_able_to(:update, accepted_user_self_organized_track) } + it{ should_not be_able_to(:edit, confirmed_user_self_organized_track) } + it{ should_not be_able_to(:update, confirmed_user_self_organized_track) } + it{ should_not be_able_to(:edit, other_self_organized_track) } + it{ should_not be_able_to(:update, other_self_organized_track) } end end end diff --git a/spec/models/admin_ability_spec.rb b/spec/models/admin_ability_spec.rb index 16456a19..126d32de 100644 --- a/spec/models/admin_ability_spec.rb +++ b/spec/models/admin_ability_spec.rb @@ -44,7 +44,7 @@ describe 'User with admin role' do let!(:my_event_schedule) { create(:event_schedule, schedule: my_schedule) } let!(:other_event_schedule) { create(:event_schedule, schedule: other_schedule) } - let!(:my_self_organized_track) { create(:track, :self_organized, program: my_conference.program) } + let!(:my_self_organized_track) { create(:track, :self_organized, program: my_conference.program, state: 'confirmed') } context 'user #is_admin?' do let(:venue) { my_conference.venue } @@ -505,6 +505,8 @@ describe 'User with admin role' do it{ should be_able_to(:show, my_conference.program) } it{ should be_able_to(:update, new_track) } it{ should be_able_to(:manage, my_self_organized_track) } + it{ should_not be_able_to(:edit, my_self_organized_track) } + it{ should_not be_able_to(:update, my_self_organized_track) } it_behaves_like 'user with any role' it_behaves_like 'user with non-organizer role', 'track_organizer' diff --git a/spec/models/conference_spec.rb b/spec/models/conference_spec.rb index 91c06140..26070134 100755 --- a/spec/models/conference_spec.rb +++ b/spec/models/conference_spec.rb @@ -606,8 +606,8 @@ describe Conference do describe 'tracks_distribution' do before do subject.email_settings = create(:email_settings) - @track_one = create(:track, name: 'Track One', color: '#000000') - @track_two = create(:track, name: 'Track Two', color: '#ffffff') + @track_one = create(:track, name: 'Track One', color: '#000000', program: subject.program) + @track_two = create(:track, name: 'Track Two', color: '#ffffff', program: subject.program) end describe '#tracks_distribution' do diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index b723d151..3ada61ca 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -96,6 +96,40 @@ describe Event do end end end + + describe '#acceptable_track' do + context 'is valid' do + it 'when the track belong to the same program, is confirmed and is included in the cfp' do + track = create(:track, state: 'confirmed', cfp_active: true, program: conference.program) + event = build(:event, program: conference.program, track: track) + expect(event.valid?).to eq true + end + end + + context 'is invalid' do + it 'when the track doesn\'t have the same program' do + track = create(:track, state: 'confirmed', cfp_active: true) + event = build(:event, program: conference.program, track: track) + expect(event.valid?).to eq false + expect(event.errors[:track]).to eq ['is invalid'] + end + + it 'when the track is unconfirmed' do + track = create(:track, cfp_active: true, program: conference.program) + allow(track).to receive(:confirmed?).and_return(false) + event = build(:event, program: conference.program, track: track) + expect(event.valid?).to eq false + expect(event.errors[:track]).to eq ['is invalid'] + end + + it 'when the track isn\'t included in the cfp' do + track = create(:track, state: 'confirmed', cfp_active: false, program: conference.program) + event = build(:event, program: conference.program, track: track) + expect(event.valid?).to eq false + expect(event.errors[:track]).to eq ['is invalid'] + end + end + end end describe '#comments_count' do diff --git a/spec/models/track_spec.rb b/spec/models/track_spec.rb index 840641cb..4853f31d 100644 --- a/spec/models/track_spec.rb +++ b/spec/models/track_spec.rb @@ -8,6 +8,7 @@ describe Track do describe 'association' do it { is_expected.to belong_to(:program) } it { is_expected.to belong_to(:submitter).class_name('User') } + it { is_expected.to belong_to(:room) } it { is_expected.to have_many(:events) } end @@ -23,23 +24,99 @@ describe Track do it { is_expected.to allow_value('My_track_name').for(:short_name) } it { is_expected.to_not allow_value('My track name').for(:short_name) } it { is_expected.to validate_uniqueness_of(:short_name).scoped_to(:program_id) } + it { is_expected.to validate_presence_of(:state) } + it { is_expected.to validate_inclusion_of(:state).in_array(%w[new to_accept accepted confirmed to_reject rejected canceled withdrawn]) } + it { is_expected.to validate_inclusion_of(:cfp_active).in_array([true, false]) } - context 'when self-organized' do + context 'when self_organized_and_accepted_or_confirmed? returns true' do before :each do - allow(subject).to receive(:self_organized?).and_return(true) + allow(subject).to receive(:self_organized_and_accepted_or_confirmed?).and_return(true) end - it { is_expected.to validate_presence_of(:state) } - it { is_expected.to validate_inclusion_of(:cfp_active).in_array([true, false]) } + it { is_expected.to validate_presence_of(:start_date) } + it { is_expected.to validate_presence_of(:end_date) } + it { is_expected.to validate_presence_of(:room) } end - context 'when regular' do + context 'when self_organized_and_accepted_or_confirmed? returns false' do before :each do - allow(subject).to receive(:self_organized?).and_return(false) + allow(subject).to receive(:self_organized_and_accepted_or_confirmed?).and_return(false) end - it { is_expected.to_not validate_presence_of(:state) } - it { is_expected.to_not validate_inclusion_of(:cfp_active) } + it { is_expected.to_not validate_presence_of(:start_date) } + it { is_expected.to_not validate_presence_of(:end_date) } + it { is_expected.to_not validate_presence_of(:room) } + end + + describe '#valid_dates' do + before :each do + @conference = create(:conference, start_date: 1.day.ago, end_date: 2.days.from_now) + end + + context 'is valid' do + it 'when the track\'s start date is before it\'s end date and between the conference start/end dates' do + track = build(:track, start_date: Date.today, end_date: Date.tomorrow, program: @conference.program) + expect(track.valid?).to eq true + end + end + + context 'is invalid' do + it 'when the track\'s start date is before the conference\'s start date' do + track = build(:track, start_date: 2.days.ago, end_date: Date.tomorrow, program: @conference.program) + expect(track.valid?).to eq false + expect(track.errors[:start_date]).to eq ["can't be before the conference start date (#{1.day.ago.to_date})"] + end + + it 'when the track\'s end date is before the conference\'s start date' do + track = build(:track, start_date: 3.days.ago, end_date: 2.days.ago, program: @conference.program) + expect(track.valid?).to eq false + expect(track.errors[:end_date]).to eq ["can't be before the conference start date (#{1.day.ago.to_date})"] + end + + it 'when the track\'s start date is after the conference\'s end date' do + track = build(:track, start_date: 3.days.from_now, end_date: 4.days.from_now, program: @conference.program) + expect(track.valid?).to eq false + expect(track.errors[:start_date]).to eq ["can't be after the conference end date (#{2.days.from_now.to_date})"] + end + + it 'when the track\'s end date is after the conference\'s end date' do + track = build(:track, start_date: Date.today, end_date: 3.days.from_now, program: @conference.program) + expect(track.valid?).to eq false + expect(track.errors[:end_date]).to eq ["can't be after the conference end date (#{2.days.from_now.to_date})"] + end + + it 'when the track\'s start date is after it\'s end date' do + track = build(:track, start_date: 1.day.from_now, end_date: 1.day.ago) + expect(track.valid?).to eq false + expect(track.errors[:start_date]).to eq ['can\'t be after the end date'] + end + end + end + + describe '#valid_room' do + before :each do + @conference = create(:conference) + @conference.venue = create(:venue, name: 'The venue') + end + + context 'is valid' do + it 'when the track\'s room belongs to the venue of the conference' do + room = create(:room, venue: @conference.venue) + track = build(:track, :self_organized, state: 'accepted', program: @conference.program, room: room) + expect(track.valid?).to eq true + end + end + + context 'is invalid' do + it 'when the track\'s room doesn\'t belong to the venue of the track\'s conference' do + other_conference = create(:conference) + other_conference.venue = create(:venue) + room = create(:room, venue: other_conference.venue) + track = build(:track, :self_organized, state: 'accepted', program: @conference.program, room: room) + expect(track.valid?).to eq false + expect(track.errors[:room]).to eq ['must be a room of The venue'] + end + end end end @@ -54,4 +131,226 @@ describe Track do expect(track.self_organized?).to eq false end end + + describe '#transition_possible?' do + shared_examples 'transition_possible?' do |state, transition, expected| + it "returns #{expected} for #{transition} event, when the track's state is #{state}}" do + my_self_organized_track = create(:track, :self_organized, state: state) + expect(my_self_organized_track.transition_possible?(transition.to_sym)).to eq expected + end + end + + states = [:new, :to_accept, :accepted, :confirmed, :to_reject, :rejected, :canceled, :withdrawn] + transitions = [:restart, :to_accept, :accept, :confirm, :to_reject, :reject, :cancel, :withdraw] + + states_transitions = { new: { restart: false, to_accept: true, accept: true, confirm: false, to_reject: true, reject: true, cancel: false, withdraw: true }, + to_accept: { restart: false, to_accept: false, accept: true, confirm: false, to_reject: false, reject: false, cancel: true, withdraw: true }, + accepted: { restart: false, to_accept: false, accept: false, confirm: true, to_reject: false, reject: false, cancel: true, withdraw: true }, + confirmed: { restart: false, to_accept: false, accept: false, confirm: false, to_reject: false, reject: false, cancel: true, withdraw: true }, + to_reject: { restart: false, to_accept: false, accept: false, confirm: false, to_reject: false, reject: true, cancel: true, withdraw: true }, + rejected: { restart: true, to_accept: false, accept: false, confirm: false, to_reject: false, reject: false, cancel: false, withdraw: false }, + canceled: { restart: true, to_accept: false, accept: false, confirm: false, to_reject: false, reject: false, cancel: false, withdraw: false }, + withdrawn: { restart: true, to_accept: false, accept: false, confirm: false, to_reject: false, reject: false, cancel: false, withdraw: false } } + + states.each do |state| + transitions.each do |transition| + it_behaves_like 'transition_possible?', state, transition, states_transitions[state.to_sym][transition.to_sym] + end + end + end + + describe '#assign_role_to_submitter' do + before :each do + Role.where(name: 'track_organizer', resource: self_organized_track).first_or_create + @submitter = self_organized_track.submitter + end + + it 'gives the role of the track organizer to the submitter of the track' do + expect(@submitter.has_role?(:track_organizer, self_organized_track)).to eq false + self_organized_track.assign_role_to_submitter + expect(@submitter.has_role?(:track_organizer, self_organized_track)).to eq true + end + + it 'is executed when the track is confirmed' do + self_organized_track.state = 'accepted' + self_organized_track.save! + expect(@submitter.has_role?(:track_organizer, self_organized_track)).to eq false + self_organized_track.confirm + expect(@submitter.has_role?(:track_organizer, self_organized_track)).to eq true + end + end + + describe '#revoke_role_and_cleanup' do + before :each do + Role.where(name: 'track_organizer', resource: self_organized_track).first_or_create + @a_track_organizer = create(:user) + self_organized_track.state = 'confirmed' + self_organized_track.cfp_active = true + self_organized_track.save! + @a_track_organizer.add_role 'track_organizer', self_organized_track + @an_event_of_the_track = create(:event, program: self_organized_track.program, track: self_organized_track) + end + + it 'revokes the role of the track organizer' do + expect(@a_track_organizer.has_role?(:track_organizer, self_organized_track)).to eq true + self_organized_track.revoke_role_and_cleanup + expect(@a_track_organizer.has_role?(:track_organizer, self_organized_track)).to eq false + end + + it 'removes the track from the events that have it set' do + expect(@an_event_of_the_track.track).to eq self_organized_track + self_organized_track.revoke_role_and_cleanup + @an_event_of_the_track.reload + expect(@an_event_of_the_track.track).to eq nil + end + + it 'is executed when the track is canceled' do + self_organized_track.state = 'confirmed' + self_organized_track.save! + self_organized_track.cancel + expect(@a_track_organizer.has_role?(:track_organizer, self_organized_track)).to eq false + @an_event_of_the_track.reload + expect(@an_event_of_the_track.track).to eq nil + end + + it 'is executed when the track is withdrawn' do + self_organized_track.withdraw + expect(@a_track_organizer.has_role?(:track_organizer, self_organized_track)).to eq false + @an_event_of_the_track.reload + expect(@an_event_of_the_track.track).to eq nil + end + end + + describe '#accepted?' do + context 'returns true' do + it 'when the state is "accepted"' do + self_organized_track.state = 'accepted' + self_organized_track.save! + expect(self_organized_track.accepted?).to eq true + end + end + + context 'returns false' do + %w[new to_accept confirmed to_reject rejected canceled withdrawn].each do |state| + it "when the state is \"#{state}\"" do + self_organized_track.state = state + self_organized_track.save! + expect(self_organized_track.accepted?).to eq false + end + end + end + end + + describe '#confirmed?' do + context 'returns true' do + it 'when the state is "confirmed"' do + self_organized_track.state = 'confirmed' + self_organized_track.save! + expect(self_organized_track.confirmed?).to eq true + end + end + + context 'returns false' do + %w[new to_accept accepted to_reject rejected canceled withdrawn].each do |state| + it "when the state is \"#{state}\"" do + self_organized_track.state = state + self_organized_track.save! + expect(self_organized_track.confirmed?).to eq false + end + end + end + end + + # accepted? and confirmed? are mutually exclusive (they can't be both true) + describe '#self_organized_and_accepted_or_confirmed?' do + context 'returns true' do + context 'when self_organized? returns true' do + before :each do + allow(track).to receive(:self_organized?).and_return(true) + end + + context 'accepted? returns true and confirmed? returns false' do + before :each do + allow(track).to receive(:accepted?).and_return(true) + allow(track).to receive(:confirmed?).and_return(false) + end + + it { expect(track.self_organized_and_accepted_or_confirmed?).to eq true } + end + + context 'accepted? returns false and confirmed? returns true' do + before :each do + allow(track).to receive(:accepted?).and_return(false) + allow(track).to receive(:confirmed?).and_return(true) + end + + it { expect(track.self_organized_and_accepted_or_confirmed?).to eq true } + end + end + end + + context 'returns false' do + context 'when self_organized? returns true' do + before :each do + allow(track).to receive(:self_organized?).and_return(true) + end + + context 'accepted? returns false and confirmed? returns false' do + before :each do + allow(track).to receive(:accepted?).and_return(false) + allow(track).to receive(:confirmed?).and_return(false) + end + + it { expect(track.self_organized_and_accepted_or_confirmed?).to eq false } + end + end + + context 'when self_organized? returns false' do + before :each do + allow(track).to receive(:self_organized?).and_return(false) + end + + context 'accepted? returns false and confirmed? returns false' do + before :each do + allow(track).to receive(:accepted?).and_return(false) + allow(track).to receive(:confirmed?).and_return(false) + end + + it { expect(track.self_organized_and_accepted_or_confirmed?).to eq false } + end + + context 'accepted? returns true and confirmed? returns false' do + before :each do + allow(track).to receive(:accepted?).and_return(true) + allow(track).to receive(:confirmed?).and_return(false) + end + + it { expect(track.self_organized_and_accepted_or_confirmed?).to eq false } + end + + context 'accepted? returns false and confirmed? returns true' do + before :each do + allow(track).to receive(:accepted?).and_return(false) + allow(track).to receive(:confirmed?).and_return(true) + end + + it { expect(track.self_organized_and_accepted_or_confirmed?).to eq false } + end + end + end + end + + describe '#create_organizer_role' do + it 'creates the role of the track organizer' do + expect(Role.find_by(name: 'track_organizer', resource: self_organized_track)).to eq nil + self_organized_track.send(:create_organizer_role) + expect(Role.find_by(name: 'track_organizer', resource: self_organized_track).description).to eq 'For the organizers of the Track' + end + + it 'is executed when the track is accepted' do + expect(Role.find_by(name: 'track_organizer', resource: self_organized_track)).to eq nil + self_organized_track.accept + expect(Role.find_by(name: 'track_organizer', resource: self_organized_track).description).to eq 'For the organizers of the Track' + end + end end From f9903eba16e4ff807d484874939971a7d0cddf59 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Sun, 16 Jul 2017 20:29:03 +0300 Subject: [PATCH 40/74] Change Cfp scopes to class methods When no record matches the requested criteria a scope will return all the records In this case, if no record can be found we want the result to be nil Also, make some readability fixes in specs --- app/models/cfp.rb | 21 ++++++++++-- spec/features/cfp_ability_spec.rb | 12 +++---- .../organization_admin_ability_spec.rb | 12 +++---- spec/features/organizer_ability_spec.rb | 12 +++---- spec/models/cfp_spec.rb | 32 +++++++++++++------ 5 files changed, 59 insertions(+), 30 deletions(-) diff --git a/app/models/cfp.rb b/app/models/cfp.rb index 44d8281b..8248cd1a 100644 --- a/app/models/cfp.rb +++ b/app/models/cfp.rb @@ -3,9 +3,6 @@ class Cfp < ActiveRecord::Base TYPES = %w(events booths tracks).freeze - scope :for_events, (-> { find_by(cfp_type: 'events') }) - scope :for_tracks, (-> { find_by(cfp_type: 'tracks') }) - has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } belongs_to :program @@ -79,6 +76,24 @@ class Cfp < ActiveRecord::Base (start_date..end_date).cover?(Date.current) end + ## + # Finds the cfp for events if it exists + # + # ====Returns + # * +Cfp+ -> The cfp with type 'events' + def self.for_events + find_by(cfp_type: 'events') + end + + ## + # Finds the cfp for tracks if it exists + # + # ====Returns + # * +Cfp+ -> The cfp with type 'tracks' + def self.for_tracks + find_by(cfp_type: 'tracks') + end + private def before_end_of_conference diff --git a/spec/features/cfp_ability_spec.rb b/spec/features/cfp_ability_spec.rb index cd9fa3bd..1607401c 100644 --- a/spec/features/cfp_ability_spec.rb +++ b/spec/features/cfp_ability_spec.rb @@ -73,7 +73,7 @@ feature 'Has correct abilities' do expect(current_path).to eq(edit_admin_conference_program_cfp_path(conference.short_title, conference.program.cfp)) # Event, booth, track cfps exist - cft = create(:cfp, cfp_type: 'tracks', program: conference.program) + call_for_tracks = create(:cfp, cfp_type: 'tracks', program: conference.program) visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq root_path @@ -83,7 +83,7 @@ feature 'Has correct abilities' do expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) # Only booth exists - cft.destroy! + call_for_tracks.destroy! visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq(new_admin_conference_program_cfp_path(conference.short_title)) @@ -96,19 +96,19 @@ feature 'Has correct abilities' do expect(current_path).to eq(new_admin_conference_program_cfp_path(conference.short_title)) # Only Tracks cfp exists - cft = create(:cfp, cfp_type: 'tracks', program: conference.program) + call_for_tracks = create(:cfp, cfp_type: 'tracks', program: conference.program) visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) - visit edit_admin_conference_program_cfp_path(conference.short_title, cft) - expect(current_path).to eq edit_admin_conference_program_cfp_path(conference.short_title, cft) + visit edit_admin_conference_program_cfp_path(conference.short_title, call_for_tracks) + expect(current_path).to eq edit_admin_conference_program_cfp_path(conference.short_title, call_for_tracks) # Event and track cfps exist create(:cfp, cfp_type: 'events', program: conference.program) visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) - cft.destroy! + call_for_tracks.destroy! create(:event, program: conference.program) visit edit_admin_conference_program_event_path(conference.short_title, conference.program.events.first) expect(current_path).to eq(edit_admin_conference_program_event_path(conference.short_title, conference.program.events.first)) diff --git a/spec/features/organization_admin_ability_spec.rb b/spec/features/organization_admin_ability_spec.rb index 12fd038b..8bccd2cf 100644 --- a/spec/features/organization_admin_ability_spec.rb +++ b/spec/features/organization_admin_ability_spec.rb @@ -115,7 +115,7 @@ feature 'Has correct abilities' do expect(current_path).to eq(edit_admin_conference_program_cfp_path(conference.short_title, conference.program.cfp)) # Event, booth, track cfps exist - cft = create(:cfp, cfp_type: 'tracks', program: conference.program) + call_for_tracks = create(:cfp, cfp_type: 'tracks', program: conference.program) visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq root_path @@ -125,7 +125,7 @@ feature 'Has correct abilities' do expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) # Only booth exists - cft.destroy! + call_for_tracks.destroy! visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq(new_admin_conference_program_cfp_path(conference.short_title)) @@ -138,19 +138,19 @@ feature 'Has correct abilities' do expect(current_path).to eq(new_admin_conference_program_cfp_path(conference.short_title)) # Only Tracks cfp exists - cft = create(:cfp, cfp_type: 'tracks', program: conference.program) + call_for_tracks = create(:cfp, cfp_type: 'tracks', program: conference.program) visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) - visit edit_admin_conference_program_cfp_path(conference.short_title, cft) - expect(current_path).to eq edit_admin_conference_program_cfp_path(conference.short_title, cft) + visit edit_admin_conference_program_cfp_path(conference.short_title, call_for_tracks) + expect(current_path).to eq edit_admin_conference_program_cfp_path(conference.short_title, call_for_tracks) # Event and track cfps exist create(:cfp, cfp_type: 'events', program: conference.program) visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) - cft.destroy! + call_for_tracks.destroy! visit admin_conference_program_events_path(conference.short_title) expect(current_path).to eq(admin_conference_program_events_path(conference.short_title)) diff --git a/spec/features/organizer_ability_spec.rb b/spec/features/organizer_ability_spec.rb index ef2b3744..5345b58c 100644 --- a/spec/features/organizer_ability_spec.rb +++ b/spec/features/organizer_ability_spec.rb @@ -121,7 +121,7 @@ feature 'Has correct abilities' do expect(current_path).to eq(edit_admin_conference_program_cfp_path(conference.short_title, conference.program.cfp)) # Event, booth, track cfps exist - cft = create(:cfp, cfp_type: 'tracks', program: conference.program) + call_for_tracks = create(:cfp, cfp_type: 'tracks', program: conference.program) visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq root_path @@ -131,7 +131,7 @@ feature 'Has correct abilities' do expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) # Only booth exists - cft.destroy! + call_for_tracks.destroy! visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq(new_admin_conference_program_cfp_path(conference.short_title)) @@ -144,19 +144,19 @@ feature 'Has correct abilities' do expect(current_path).to eq(new_admin_conference_program_cfp_path(conference.short_title)) # Only Tracks cfp exists - cft = create(:cfp, cfp_type: 'tracks', program: conference.program) + call_for_tracks = create(:cfp, cfp_type: 'tracks', program: conference.program) visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) - visit edit_admin_conference_program_cfp_path(conference.short_title, cft) - expect(current_path).to eq edit_admin_conference_program_cfp_path(conference.short_title, cft) + visit edit_admin_conference_program_cfp_path(conference.short_title, call_for_tracks) + expect(current_path).to eq edit_admin_conference_program_cfp_path(conference.short_title, call_for_tracks) # Event and track cfps exist create(:cfp, cfp_type: 'events', program: conference.program) visit new_admin_conference_program_cfp_path(conference.short_title) expect(current_path).to eq new_admin_conference_program_cfp_path(conference.short_title) - cft.destroy! + call_for_tracks.destroy! visit admin_conference_program_events_path(conference.short_title) expect(current_path).to eq(admin_conference_program_events_path(conference.short_title)) diff --git a/spec/models/cfp_spec.rb b/spec/models/cfp_spec.rb index 606f5f9f..95607491 100644 --- a/spec/models/cfp_spec.rb +++ b/spec/models/cfp_spec.rb @@ -5,21 +5,35 @@ describe Cfp do let!(:conference) { create(:conference, end_date: Date.today) } let!(:cfp) { create(:cfp, start_date: Date.today - 2, end_date: Date.today - 1, program_id: conference.program.id) } - describe 'scope' do - describe '#for_events' do - it 'returns the cfp for events' do - expect(conference.program.cfps.for_events).to be_a Cfp - expect(conference.program.cfps.for_events.cfp_type).to eq('events') - end - end - end - describe 'validations' do it { is_expected.to validate_presence_of(:cfp_type) } it { is_expected.to validate_inclusion_of(:cfp_type).in_array(Cfp::TYPES) } it { is_expected.to validate_uniqueness_of(:cfp_type).scoped_to(:program_id).case_insensitive } end + describe '.for_events' do + it 'returns the cfp for events when it exists' do + expect(conference.program.cfps.for_events).to be_a Cfp + expect(conference.program.cfps.for_events.cfp_type).to eq('events') + end + + it 'returns nil when the cfp for events doesn\'t exist' do + conference.program.cfp.destroy + expect(conference.program.cfps.for_events).to eq nil + end + end + + describe '.for_tracks' do + it 'returns the cfp for tracks when it exists' do + call_for_tracks = create(:cfp, cfp_type: 'tracks', program: conference.program, end_date: Date.today) + expect(conference.program.cfps.for_tracks).to eq call_for_tracks + end + + it 'returns nil when the cfp for tracks doesn\'t exist' do + expect(conference.program.cfps.for_tracks).to eq nil + end + end + describe '#before_end_of_conference' do describe 'fails to save cfp' do it 'when cfp end_date is after conference end_date' do From 3d250ecf753c25a9968c642a248777f88876058a Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 19 Jul 2017 18:28:57 +0300 Subject: [PATCH 41/74] UI/UX changes related to tracks Add confirmed and cfp_active scopes to Track Render markdown in track's description Make the views more similar to the proposal/events views Change the sequence of columns in admin/Tracks#index Remove the short_name column from the index views Add button "My Tracks" in the user menu Fix track count in proposals Add details of track in admin/Tracks#show Use tabs to show track details and events --- .haml-lint_todo.yml | 1 + app/controllers/admin/events_controller.rb | 6 +- app/helpers/application_helper.rb | 2 +- app/models/track.rb | 3 + app/views/admin/tracks/index.html.haml | 74 ++++------ app/views/admin/tracks/show.html.haml | 135 ++++++++++++++---- .../_schedule_splashpage.html.haml | 11 +- app/views/layouts/_user_menu.html.haml | 4 + .../proposals/_encouragement_text.html.haml | 2 +- app/views/tracks/_form.html.haml | 2 +- app/views/tracks/index.html.haml | 89 +++++++----- app/views/tracks/show.html.haml | 30 ++-- spec/features/tracks_spec.rb | 2 +- spec/models/track_spec.rb | 40 ++++++ 14 files changed, 269 insertions(+), 132 deletions(-) diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 89d18c3f..b7a18be0 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -182,6 +182,7 @@ linters: - "app/views/tracks/show.html.haml" - "app/views/conferences/_call_for_tracks.html.haml" - "app/views/admin/tracks/_change_state_dropdown.html.haml" + - "app/views/proposals/_encouragement_text.html.haml" # Offense count: 223 InstanceVariables: diff --git a/app/controllers/admin/events_controller.rb b/app/controllers/admin/events_controller.rb index 125d1ef1..1aa2e4bd 100644 --- a/app/controllers/admin/events_controller.rb +++ b/app/controllers/admin/events_controller.rb @@ -17,7 +17,7 @@ module Admin def index @events = @program.events - @tracks = @program.tracks + @tracks = @program.tracks.confirmed.cfp_active @difficulty_levels = @program.difficulty_levels @event_types = @program.event_types @tracks_distribution_confirmed = @conference.tracks_distribution(:confirmed) @@ -43,7 +43,7 @@ module Admin end def show - @tracks = @program.tracks + @tracks = @program.tracks.confirmed.cfp_active @event_types = @program.event_types @comments = @event.root_comments @comment_count = @event.comment_threads.count @@ -58,7 +58,7 @@ module Admin def edit @event_types = @program.event_types - @tracks = Track.all + @tracks = @program.tracks.confirmed.cfp_active @comments = @event.root_comments @comment_count = @event.comment_threads.count @user = @event.submitter diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 040927c1..0c460f26 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -56,7 +56,7 @@ module ApplicationHelper end def tracks(conference) - all = conference.program.tracks.where(state: 'confirmed', cfp_active: true).pluck(:name) + all = conference.program.tracks.confirmed.cfp_active.pluck(:name) first = all[0...-1] last = all[-1] ts = '' diff --git a/app/models/track.rb b/app/models/track.rb index 3743cd79..5fb37549 100644 --- a/app/models/track.rb +++ b/app/models/track.rb @@ -32,6 +32,9 @@ class Track < ActiveRecord::Base before_validation :capitalize_color + scope :confirmed, -> { where(state: 'confirmed') } + scope :cfp_active, -> { where(cfp_active: true) } + state_machine initial: :pending do state :new state :to_accept diff --git a/app/views/admin/tracks/index.html.haml b/app/views/admin/tracks/index.html.haml index 0cfe0ff2..5c47fe11 100644 --- a/app/views/admin/tracks/index.html.haml +++ b/app/views/admin/tracks/index.html.haml @@ -6,39 +6,46 @@ Categorize events in your conference .row .col-md-12 - %table.table.table-hover#tracks + %table.table.table-hover.table-striped.table-bordered.datatable#tracks %thead %th Name - %th Short name %th Description - %th Submitter - %th Color - %th State - %th Included in the Cfp %th Room %th Start Date %th End Date + %th Submitter + %th Included in Cfp + %th State %th Actions %tbody - @tracks.each do |track| %tr - %td - = link_to(admin_conference_program_track_path(@conference.short_title, track)) do - = track.name - %td - = track.short_name + %td{style: "padding: 15px 0px 0px 10px;"} + = link_to admin_conference_program_track_path(@conference.short_title, track), class: 'btn' do + %span.label{style: "background-color: #{track.color}; color: #{ contrast_color(track.color) }"} + = track.name %td %p - = truncate(track.description) + = markdown(truncate(track.description)) %td - - if track.self_organized? - = link_to track.submitter.name, admin_user_path(track.submitter) - - else - N/A + = track.room.try(:name) %td - %span.label{style: "background-color: #{track.color}; color: #{ contrast_color(track.color) }"} - = track.color + = track.start_date.strftime('%A, %B %-d. %Y') if track.start_date %td + = track.end_date.strftime('%A, %B %-d. %Y') if track.end_date + %td + = link_to track.submitter.name, admin_user_path(track.submitter) if track.self_organized? + %td.text-center + = check_box_tag "#{@conference.short_title}_#{track.short_name}", track.id, track.cfp_active, + class: 'switch-checkbox', method: :patch, + url: toggle_cfp_inclusion_admin_conference_program_track_path(@conference.short_title, id: track.short_name)+"?included=", + data: { size: 'small', + on_color: 'success', + off_color: 'warning', + on_text: 'Yes', + off_text: 'No' } + + %td.text-center - if track.self_organized? .btn-group %button{ type: 'button', class: 'btn btn-link dropdown-toggle', 'data-toggle' => 'dropdown' } @@ -48,39 +55,12 @@ = render 'change_state_dropdown', track: track - else = track.state.humanize - %td - = check_box_tag "#{@conference.short_title}_#{track.short_name}", track.id, track.cfp_active, - class: 'switch-checkbox', method: :patch, - url: toggle_cfp_inclusion_admin_conference_program_track_path(@conference.short_title, id: track.short_name)+"?included=", - data: { size: 'small', - on_color: 'success', - off_color: 'warning', - on_text: 'Yes', - off_text: 'No' } - - %td - - if track.room - = link_to track.room.name, admin_conference_venue_room_path(@conference.short_title, track.room.id) - - else - N/A - %td - - if track.start_date - = track.start_date.strftime('%A, %B %-d. %Y') - - else - N/A - %td - - if track.end_date - = track.end_date.strftime('%A, %B %-d. %Y') - - else - N/A %td .btn-group{role: "group"} - if can? :edit, track - = link_to 'Edit', edit_admin_conference_program_track_path(@conference.short_title, track), - method: :get, class: 'btn btn-primary' + = link_to 'Edit', edit_admin_conference_program_track_path(@conference.short_title, track), class: 'btn btn-primary' - if can? :destroy, track - = link_to 'Delete', admin_conference_program_track_path(@conference.short_title, track), - method: :delete, class: 'btn btn-danger', + = link_to 'Delete', admin_conference_program_track_path(@conference.short_title, track), method: :delete, class: 'btn btn-danger', data: { confirm: "Do you really want to delete #{track.name}? Attention: This track will be removed from all Events that have it set" } .row .col-md-12.text-right diff --git a/app/views/admin/tracks/show.html.haml b/app/views/admin/tracks/show.html.haml index 3acb8471..5e45d3a5 100644 --- a/app/views/admin/tracks/show.html.haml +++ b/app/views/admin/tracks/show.html.haml @@ -4,27 +4,114 @@ %h1 = @track.name Track - %p.text-muted - Events in this track -.row - .col-md-12 - %table.table.table-hover.datatable - %thead - %th Title - %th Type - %th Submitter - %th State - %th Time - %tbody - - @track.events.each_with_index do |event| - %tr - %td - =link_to event.title, admin_conference_program_event_path(@conference.short_title, event) - %td - = event.event_type.title - %td - =link_to event.submitter.name, admin_user_path(event.submitter) - %td - = event.state - %td - = event.time + +.tabbable + %ul.nav.nav-tabs + %li.active + = link_to 'Details', '#details', 'data-toggle' => 'tab' + %li + = link_to 'Events', '#events', 'data-toggle' => 'tab' + + .tab-content + .tab-pane.active#details + .row + .col-md-12 + .btn-group.pull-right + - if can? :edit, @track + = link_to 'Edit', edit_admin_conference_program_track_path(@conference.short_title, @track), + method: :get, class: 'btn btn-primary' + - if can? :destroy, @track + = link_to 'Delete', admin_conference_program_track_path(@conference.short_title, @track), + method: :delete, class: 'btn btn-danger', + data: { confirm: "Do you really want to delete #{@track.name}? Attention: This track will be removed from all Events that have it set" } + .row + .col-md-12 + %table.table + %tr + %td.col-md-2 + %b Color + %td + %span.label{ style: "background-color: #{@track.color}; color: #{ contrast_color(@track.color) }" } + = @track.color + %tr + %td + %b Room + %td + = @track.room.try(:name) + %tr + %td + %b Start date + %td + = @track.start_date.strftime('%A, %B %-d. %Y') if @track.start_date + %tr + %td + %b End date + %td + = @track.end_date.strftime('%A, %B %-d. %Y') if @track.end_date + - if @track.self_organized? + %tr + %td + %b Submitter + %td + = link_to @track.submitter.name, admin_user_path(@track.submitter) + - if @track.confirmed? + %tr + %td + %b Organizers + %td + - Role.find_by(name: 'track_organizer', resource: @track).users.each do |organizer| + %div + = link_to organizer.name, admin_user_path(organizer) + %tr + %td + %b Included in the Cfp? + %td + = check_box_tag "#{@conference.short_title}_#{@track.short_name}", @track.id, @track.cfp_active, + class: 'switch-checkbox', method: :patch, + url: toggle_cfp_inclusion_admin_conference_program_track_path(@conference.short_title, id: @track.short_name)+"?included=", + data: { size: 'small', + on_color: 'success', + off_color: 'warning', + on_text: 'Yes', + off_text: 'No' } + %tr + %td + %b State + %td + - if @track.self_organized? + .btn-group + %button{ type: 'button', class: 'btn btn-link dropdown-toggle', 'data-toggle' => 'dropdown' } + = @track.state.humanize + %span.caret + %ul.dropdown-menu{ role: 'menu' } + = render 'change_state_dropdown', track: @track + - else + = @track.state.humanize + %tr + %td + %b Description + %td + = markdown(@track.description) + + .tab-pane#events + .col-md-12 + %table.table.table-hover.datatable + %thead + %th Title + %th Type + %th Submitter + %th State + %th Time + %tbody + - @track.events.each_with_index do |event| + %tr + %td + =link_to event.title, admin_conference_program_event_path(@conference.short_title, event) + %td + = event.event_type.title + %td + =link_to event.submitter.name, admin_user_path(event.submitter) + %td + = event.state + %td + = event.time diff --git a/app/views/conferences/_schedule_splashpage.html.haml b/app/views/conferences/_schedule_splashpage.html.haml index 67bd52e4..defd3e4d 100644 --- a/app/views/conferences/_schedule_splashpage.html.haml +++ b/app/views/conferences/_schedule_splashpage.html.haml @@ -10,13 +10,22 @@ - if @conference.splashpage and @conference.program.tracks.any? and @conference.splashpage.include_tracks See rock-star speakers cover the topics of - if @conference.splashpage and @conference.splashpage.include_tracks - - @conference.program.tracks.each_slice(3) do |slice| + - @conference.program.tracks.confirmed.cfp_active.each_slice(3) do |slice| .row.row-centered - slice.each do |track| .col-md-4.col-sm-4.col-centered.col-top.track %h4.text-center = track.name = markdown(track.description) + - if track.start_date + %br + From: #{track.start_date.strftime('%A, %B %-d. %Y')} + - if track.end_date + %br + To: #{track.end_date.strftime('%A, %B %-d. %Y')} + - if track.room + %br + In: #{track.room.name} - if @conference.program and @conference.program.schedule_public .row diff --git a/app/views/layouts/_user_menu.html.haml b/app/views/layouts/_user_menu.html.haml index 38a96b08..fe1e9a35 100644 --- a/app/views/layouts/_user_menu.html.haml +++ b/app/views/layouts/_user_menu.html.haml @@ -12,6 +12,10 @@ = link_to(conference_program_proposals_path(@conference.short_title)) do %span.fa.fa-comment My Submissions + %li + = link_to(conference_program_tracks_path(@conference.short_title)) do + %span.fa.fa-road + My Tracks %li - if ENV['OSEM_ICHAIN_ENABLED'] == 'true' = link_to(destroy_user_ichain_session_path, method: 'delete') do diff --git a/app/views/proposals/_encouragement_text.html.haml b/app/views/proposals/_encouragement_text.html.haml index a0f513cd..c6d574d6 100644 --- a/app/views/proposals/_encouragement_text.html.haml +++ b/app/views/proposals/_encouragement_text.html.haml @@ -4,7 +4,7 @@ = "#{event_types(@conference)}." - if @program.tracks.any? Proposals should fit in one of the - = "#{pluralize(@program.tracks.count, 'track')}:" + = "#{pluralize(@program.tracks.confirmed.cfp_active.count, 'track')}:" = "#{tracks(@conference)}." - if @program.cfp_open? The submission period has begun diff --git a/app/views/tracks/_form.html.haml b/app/views/tracks/_form.html.haml index 2a55df5a..31e9deaf 100644 --- a/app/views/tracks/_form.html.haml +++ b/app/views/tracks/_form.html.haml @@ -15,5 +15,5 @@ = f.input :color, input_html: {size: 6, type: 'color'}, required: true = f.input :start_date, as: :string, input_html: { id: 'registration-period-start-datepicker', start_date: @conference.start_date, end_date: @conference.end_date, readonly: 'readonly' } = f.input :end_date, as: :string, input_html: { id: 'registration-period-end-datepicker', readonly: 'readonly' } - = f.input :description, input_html: {rows: 2, data: { provide: 'markdown-editable' } }, required: true, hint: markdown_hint + = f.input :description, input_html: {rows: 2, data: { provide: 'markdown-editable' } }, required: true, hint: "This will be public #{markdown_hint}".html_safe = f.action :submit, as: :button, button_html: { class: 'btn btn-primary' } diff --git a/app/views/tracks/index.html.haml b/app/views/tracks/index.html.haml index fd1235d4..31a9fe97 100644 --- a/app/views/tracks/index.html.haml +++ b/app/views/tracks/index.html.haml @@ -6,46 +6,60 @@ %span.notranslate = @conference.title + .row + .col-md-12 + %p.text-right + = link_to '#status-help', class: 'btn btn-default', "data-toggle"=>"collapse" do + Help? + .collapse#status-help + %p + %strong + What happens next with my track request? + %p + If you submit a track request, the conference organizers will review it and either accept or reject it. + %br + If your track request is accepted, the conference organizers expect you to confirm that you will be able to hold it. + Then you will gain the Track organizer role. + %br + If your track request is rejected, you can either live with that or adapt it and resubmit it for review again. + %br + If something changes and you can't organize the track any more, you should withdraw it. + - if @tracks.any? .row .col-md-12 - %table.table.table-hover#tracks - %thead - %th Name - %th Short name - %th Description - %th Color - %th State - %th Start Date - %th End Date - %th Actions - %tbody - - @tracks.each do |track| - %tr - %td - = link_to(conference_program_track_path(@conference.short_title, track)) do - = track.name - %td - = track.short_name - %td - %p - = truncate(track.description) - %td + %table.table.table-striped#tracks + - @tracks.each do |track| + %tr + %td{style: "padding:15px 0px 0px 8px;"} + - if %w(new to_accept to_reject).include? track.state + %span{ title: 'In review', class: 'fa fa-eye' } + - elsif track.state == 'accepted' + %span{ title: 'Accepted', class: 'fa fa-check text-muted' } + - elsif track.state == 'confirmed' + %spam{ title: 'Confirmed', class: 'fa fa-check text-success' } + - elsif %w(rejected withdrawn canceled).include? track.state + %span{ title: track.state.humanize, class: 'fa fa-ban'} + %td{style: "padding: 15px 0px 0px 0px;"} + = link_to conference_program_track_path(@conference.short_title, track), class: 'btn' do %span.label{style: "background-color: #{track.color}; color: #{ contrast_color(track.color) }"} - = track.color - %td - = track.state.humanize - %td - - if track.start_date - = track.start_date.strftime('%A, %B %-d. %Y') - - else - N/A - %td - - if track.end_date - = track.end_date.strftime('%A, %B %-d. %Y') - - else - N/A - %td + = track.name + %td + = markdown(truncate(track.description)) + %td + - if track.start_date + From: + = track.start_date.strftime('%A, %B %-d. %Y') + %td + - if track.end_date + To: + = track.end_date.strftime('%A, %B %-d. %Y') + %td + - if track.room + In: + = track.room.name + %td + .pull-right - if track.transition_possible? :confirm = link_to 'Confirm', confirm_conference_program_track_path(@conference.short_title, track), method: :patch, class: 'btn btn-mini btn-success', id: "confirm_track_#{track.id}" @@ -57,8 +71,7 @@ = link_to 'Re-Submit', restart_conference_program_track_path(@conference.short_title, track), method: :patch, class: 'btn btn-mini btn-success', id: "resubmit_track_request_#{track.id}" - if can? :edit, track - = link_to 'Edit', edit_conference_program_track_path(@conference.short_title, track), - method: :get, class: 'btn btn-primary' + = link_to 'Edit', edit_conference_program_track_path(@conference.short_title, track), class: 'btn btn-default' .row .col-md-12 diff --git a/app/views/tracks/show.html.haml b/app/views/tracks/show.html.haml index c5bc27e5..db437878 100644 --- a/app/views/tracks/show.html.haml +++ b/app/views/tracks/show.html.haml @@ -2,9 +2,12 @@ .row .col-md-12 .page-header - %h1 + %h2 = @track.name Track + .btn-group.pull-right + - if can? :edit, @track + = link_to 'Edit Track request', edit_conference_program_track_path(@conference.short_title, @track), class: 'btn btn-primary' .row .col-md-8 %dl.dl-horizontal @@ -16,26 +19,23 @@ %dt State: %dd - = @track.state.humanize + - if %w(new to_accept to_reject).include? @track.state + New + - else + = @track.state.humanize %dt Start date: %dd - - if @track.start_date - = @track.start_date.strftime('%A, %B %-d. %Y') - - else - N/A + = @track.start_date.strftime('%A, %B %-d. %Y') if @track.start_date %dt End date: %dd - - if @track.end_date - = @track.end_date.strftime('%A, %B %-d. %Y') - - else - N/A + = @track.end_date.strftime('%A, %B %-d. %Y') if @track.end_date + %dt + Room: + %dd + = @track.room.try(:name) %dt Description %dd - = @track.description - .row - .col-md-12.text-right - - if can? :edit, @track - = link_to 'Edit Track request', edit_conference_program_track_path(@conference.short_title, @track), class: 'btn btn-primary' + = markdown(@track.description) diff --git a/spec/features/tracks_spec.rb b/spec/features/tracks_spec.rb index 3538a6d7..91e90ee9 100644 --- a/spec/features/tracks_spec.rb +++ b/spec/features/tracks_spec.rb @@ -39,7 +39,7 @@ feature Track do within('table#tracks') do expect(page.has_content?(track.name)).to be false expect(page.has_content?(track.description)).to be false - expect(page.assert_selector('tr', count: 1)).to be true + expect(page.has_content?('No data available in table')).to eq true end end diff --git a/spec/models/track_spec.rb b/spec/models/track_spec.rb index 4853f31d..158c6140 100644 --- a/spec/models/track_spec.rb +++ b/spec/models/track_spec.rb @@ -120,6 +120,46 @@ describe Track do end end + describe 'scope' do + describe '#confirmed' do + before :each do + @program = create(:program) + end + + context 'includes' do + it 'when track is confirmed' do + confirmed_track = create(:track, state: 'confirmed', program: @program) + expect(@program.tracks.confirmed.include?(confirmed_track)).to eq true + end + end + + context 'excludes' do + %w[new to_accept accepted to_reject rejected canceled withdrawn].each do |state| + it "when track is #{state.humanize}" do + unconfirmed_track = create(:track, state: state, program: @program) + expect(@program.tracks.confirmed.include?(unconfirmed_track)).to eq false + end + end + end + end + + describe '#cfp_active' do + before :each do + @program = create(:program) + @cfp_active_track = create(:track, cfp_active: true, program: @program) + @non_cfp_active_track = create(:track, cfp_active: false, program: @program) + end + + it 'include tracks with the cfp_active flag enabled' do + expect(@program.tracks.cfp_active.include?(@cfp_active_track)).to eq true + end + + it 'excludes tracks with the cfp_active flag disabled' do + expect(@program.tracks.cfp_active.include?(@non_cfp_active_track)).to eq false + end + end + end + describe '#self_organized?' do it 'returns true when it has a submitter' do expect(self_organized_track.submitter).to be_a User From 2ff4c6d6b25078ac76085f47091104da3dedd5af Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 26 Jul 2017 14:54:10 +0300 Subject: [PATCH 42/74] Add relevance field for track requests The requester can now provide more info about the track and himself --- app/controllers/tracks_controller.rb | 2 +- app/models/track.rb | 1 + app/views/admin/tracks/show.html.haml | 6 ++++++ app/views/tracks/_form.html.haml | 1 + app/views/tracks/show.html.haml | 4 ++++ .../20170726065629_add_relevance_to_tracks.rb | 5 +++++ db/schema.rb | 1 + spec/controllers/tracks_controller_spec.rb | 8 ++++---- spec/factories/tracks.rb | 1 + spec/models/track_spec.rb | 16 ++++++++++++++++ 10 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20170726065629_add_relevance_to_tracks.rb diff --git a/app/controllers/tracks_controller.rb b/app/controllers/tracks_controller.rb index 9305950e..6b584d22 100644 --- a/app/controllers/tracks_controller.rb +++ b/app/controllers/tracks_controller.rb @@ -53,7 +53,7 @@ class TracksController < ApplicationController private def track_params - params.require(:track).permit(:name, :description, :color, :short_name, :start_date, :end_date) + params.require(:track).permit(:name, :description, :color, :short_name, :start_date, :end_date, :relevance) end def update_state(transition, notice) diff --git a/app/models/track.rb b/app/models/track.rb index 5fb37549..2b09b654 100644 --- a/app/models/track.rb +++ b/app/models/track.rb @@ -27,6 +27,7 @@ class Track < ActiveRecord::Base validates :start_date, presence: true, if: :self_organized_and_accepted_or_confirmed? validates :end_date, presence: true, if: :self_organized_and_accepted_or_confirmed? validates :room, presence: true, if: :self_organized_and_accepted_or_confirmed? + validates :relevance, presence: true, if: :self_organized? validate :valid_dates validate :valid_room, if: :self_organized_and_accepted_or_confirmed? diff --git a/app/views/admin/tracks/show.html.haml b/app/views/admin/tracks/show.html.haml index 5e45d3a5..a8c13d9d 100644 --- a/app/views/admin/tracks/show.html.haml +++ b/app/views/admin/tracks/show.html.haml @@ -92,6 +92,12 @@ %b Description %td = markdown(@track.description) + - if @track.self_organized? + %tr + %td + %b Relevance + %td + = markdown(@track.relevance) .tab-pane#events .col-md-12 diff --git a/app/views/tracks/_form.html.haml b/app/views/tracks/_form.html.haml index 31e9deaf..ab693dc5 100644 --- a/app/views/tracks/_form.html.haml +++ b/app/views/tracks/_form.html.haml @@ -16,4 +16,5 @@ = f.input :start_date, as: :string, input_html: { id: 'registration-period-start-datepicker', start_date: @conference.start_date, end_date: @conference.end_date, readonly: 'readonly' } = f.input :end_date, as: :string, input_html: { id: 'registration-period-end-datepicker', readonly: 'readonly' } = f.input :description, input_html: {rows: 2, data: { provide: 'markdown-editable' } }, required: true, hint: "This will be public #{markdown_hint}".html_safe + = f.input :relevance, input_html: {rows: 5, data: { provide: 'markdown-editable' } }, required: true, hint: "Please explain here how this track relates to the conference, how you are related to it's content and why we should accept it. #{markdown_hint}".html_safe = f.action :submit, as: :button, button_html: { class: 'btn btn-primary' } diff --git a/app/views/tracks/show.html.haml b/app/views/tracks/show.html.haml index db437878..6014f23e 100644 --- a/app/views/tracks/show.html.haml +++ b/app/views/tracks/show.html.haml @@ -39,3 +39,7 @@ Description %dd = markdown(@track.description) + %dt + Relevance + %dd + = markdown(@track.relevance) diff --git a/db/migrate/20170726065629_add_relevance_to_tracks.rb b/db/migrate/20170726065629_add_relevance_to_tracks.rb new file mode 100644 index 00000000..215200c1 --- /dev/null +++ b/db/migrate/20170726065629_add_relevance_to_tracks.rb @@ -0,0 +1,5 @@ +class AddRelevanceToTracks < ActiveRecord::Migration + def change + add_column :tracks, :relevance, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index f8cc3ddc..9e6123a3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -525,6 +525,7 @@ ActiveRecord::Schema.define(version: 20170807092805) do t.integer "room_id" t.date "start_date" t.date "end_date" + t.text "relevance" end add_index "tracks", ["room_id"], name: "index_tracks_on_room_id" diff --git a/spec/controllers/tracks_controller_spec.rb b/spec/controllers/tracks_controller_spec.rb index 0e3a0553..a555dba8 100644 --- a/spec/controllers/tracks_controller_spec.rb +++ b/spec/controllers/tracks_controller_spec.rb @@ -60,7 +60,7 @@ describe TracksController do describe 'POST #create' do context 'saves successfuly' do before :each do - post :create, track: attributes_for(:track, short_name: 'my_track'), conference_id: conference.short_title + post :create, track: attributes_for(:track, :self_organized, short_name: 'my_track'), conference_id: conference.short_title end it 'redirects to tracks index path' do @@ -86,7 +86,7 @@ describe TracksController do context 'save fails' do before :each do allow_any_instance_of(Track).to receive(:save).and_return(false) - post :create, track: attributes_for(:track, short_name: 'my_track'), conference_id: conference.short_title + post :create, track: attributes_for(:track, :self_organized, short_name: 'my_track'), conference_id: conference.short_title end it 'assigns a new track with the correct conference' do @@ -126,7 +126,7 @@ describe TracksController do describe 'PATCH #update' do context 'updates successfully' do before :each do - patch :update, track: attributes_for(:track, color: '#FF0000'), + patch :update, track: attributes_for(:track, :self_organized, color: '#FF0000'), conference_id: conference.short_title, id: self_organized_track.short_name end @@ -152,7 +152,7 @@ describe TracksController do context 'update fails' do before :each do allow_any_instance_of(Track).to receive(:save).and_return(false) - patch :update, track: attributes_for(:track, color: '#FF0000'), + patch :update, track: attributes_for(:track, :self_organized, color: '#FF0000'), conference_id: conference.short_title, id: self_organized_track.short_name end diff --git a/spec/factories/tracks.rb b/spec/factories/tracks.rb index d61c9a1b..3c0042f2 100644 --- a/spec/factories/tracks.rb +++ b/spec/factories/tracks.rb @@ -15,6 +15,7 @@ FactoryGirl.define do start_date { Date.today } end_date { Date.today } room + relevance { Faker::Hipster.paragraph(2) } end end end diff --git a/spec/models/track_spec.rb b/spec/models/track_spec.rb index 158c6140..ed058176 100644 --- a/spec/models/track_spec.rb +++ b/spec/models/track_spec.rb @@ -48,6 +48,22 @@ describe Track do it { is_expected.to_not validate_presence_of(:room) } end + context 'when self_organized? returns true' do + before :each do + allow(subject).to receive(:self_organized?).and_return(true) + end + + it { is_expected.to validate_presence_of(:relevance) } + end + + context 'when self_organized? returns false' do + before :each do + allow(subject).to receive(:self_organized?).and_return(false) + end + + it { is_expected.to_not validate_presence_of(:relevance) } + end + describe '#valid_dates' do before :each do @conference = create(:conference, start_date: 1.day.ago, end_date: 2.days.from_now) From 5f1ed7ce85122305cec73b4fd73db6ea1afc1ba4 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Fri, 28 Jul 2017 02:05:08 +0300 Subject: [PATCH 43/74] Handle Tracks as resource_type in User#get_roles --- app/models/user.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index f3f03ab7..793db609 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -165,7 +165,11 @@ class User < ActiveRecord::Base def get_roles result = {} roles.each do |role| - resource = Conference.find(role.resource_id).short_title + resource = if role.resource_type == 'Conference' + Conference.find(role.resource_id).short_title + elsif role.resource_type == 'Track' + Track.find(role.resource_id).name + end if result[role.name].nil? result[role.name] = [resource] else From b717018b310dea45d2e02a1f69ae9f17912b538a Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Tue, 8 Aug 2017 12:41:37 +0300 Subject: [PATCH 44/74] Track related fixes Make the message in admin/Tracks form more visible by making it bold and adding links to venue and rooms Make papertrail track changes for all the track's attributes Add validation to require presence of description for self-organized tracks Add ID column to admin/Tracks#index Make the cfp inclusion column sortable Show success/error flash messages after toggling cfp inclusion --- app/assets/javascripts/osem-datatables.js | 24 +++--- app/controllers/admin/tracks_controller.rb | 11 ++- app/models/track.rb | 3 +- app/views/admin/tracks/_form.html.haml | 9 +- app/views/admin/tracks/index.html.haml | 6 +- app/views/admin/tracks/show.html.haml | 1 + .../admin/tracks/toggle_cfp_inclusion.js.erb | 8 ++ .../admin/tracks_controller_spec.rb | 82 ++++++++++++++++--- spec/models/track_spec.rb | 2 + 9 files changed, 114 insertions(+), 32 deletions(-) create mode 100644 app/views/admin/tracks/toggle_cfp_inclusion.js.erb diff --git a/app/assets/javascripts/osem-datatables.js b/app/assets/javascripts/osem-datatables.js index f950159e..a6f85c5d 100644 --- a/app/assets/javascripts/osem-datatables.js +++ b/app/assets/javascripts/osem-datatables.js @@ -1,18 +1,14 @@ $(function () { - $(document).ready(function() { - $('.datatable').DataTable({ - // ajax: ..., - stateSave: true, - autoWidth: false, - pagingType: 'full_numbers', - "lengthMenu": [[25, 50, 100, -1], [25, 50, 100, "All"]] - }); + $('.datatable').DataTable({ + // ajax: ..., + stateSave: true, + autoWidth: false, + pagingType: 'full_numbers', + "lengthMenu": [[25, 50, 100, -1], [25, 50, 100, "All"]], + }); - $('#versionstable').DataTable({ - pagingType: 'full_numbers', - order: [[ 0, 'desc' ]] - }); + $('#versionstable').DataTable({ + pagingType: 'full_numbers', + order: [[ 0, 'desc' ]] }); }); - - diff --git a/app/controllers/admin/tracks_controller.rb b/app/controllers/admin/tracks_controller.rb index 3d51a677..0f3940da 100644 --- a/app/controllers/admin/tracks_controller.rb +++ b/app/controllers/admin/tracks_controller.rb @@ -4,6 +4,9 @@ module Admin load_and_authorize_resource :program, through: :conference, singleton: true load_and_authorize_resource through: :program, find_by: :short_name + # Show flash message with ajax calls + after_action :prepare_unobtrusive_flash, only: :toggle_cfp_inclusion + def index; end def show @@ -55,9 +58,13 @@ module Admin def toggle_cfp_inclusion @track.cfp_active = !@track.cfp_active if @track.save - head :ok + flash[:notice] = "Successfully changed cfp inclusion of #{@track.name} to #{@track.cfp_active}" else - head :unprocessable_entity + flash[:error] = "Failed to toggle cfp inclusion of #{@track.name} to #{@track.cfp_active}" + end + + respond_to do |format| + format.js end end diff --git a/app/models/track.rb b/app/models/track.rb index 2b09b654..c624cab8 100644 --- a/app/models/track.rb +++ b/app/models/track.rb @@ -9,7 +9,7 @@ class Track < ActiveRecord::Base belongs_to :room has_many :events, dependent: :nullify - has_paper_trail only: [:name, :description, :color], meta: { conference_id: :conference_id } + has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } before_create :generate_guid validates :name, presence: true @@ -28,6 +28,7 @@ class Track < ActiveRecord::Base validates :end_date, presence: true, if: :self_organized_and_accepted_or_confirmed? validates :room, presence: true, if: :self_organized_and_accepted_or_confirmed? validates :relevance, presence: true, if: :self_organized? + validates :description, presence: true, if: :self_organized? validate :valid_dates validate :valid_room, if: :self_organized_and_accepted_or_confirmed? diff --git a/app/views/admin/tracks/_form.html.haml b/app/views/admin/tracks/_form.html.haml index eb12561b..9109e4f5 100644 --- a/app/views/admin/tracks/_form.html.haml +++ b/app/views/admin/tracks/_form.html.haml @@ -14,10 +14,15 @@ = f.input :color, input_html: {size: 6, type: 'color'}, required: true = f.input :start_date, as: :string, input_html: { id: 'registration-period-start-datepicker', start_date: @conference.start_date, end_date: @conference.end_date, readonly: 'readonly', required: @track.self_organized_and_accepted_or_confirmed? } = f.input :end_date, as: :string, input_html: { id: 'registration-period-end-datepicker', readonly: 'readonly', required: @track.self_organized_and_accepted_or_confirmed? } - - if @conference.venue + - if @conference.venue.try(:rooms) = f.input :room, as: :select, collection: (@conference.venue.rooms).map {|room| ["#{room.name}", room.id]}, include_blank: true, label: 'Room', input_html: { class: 'select-help-toggle', required: @track.self_organized_and_accepted_or_confirmed? } - else - Please add a venue with rooms, if you want to select a room for the track. + %b + Please add a + = link_to 'venue', admin_conference_venue_path(@conference.short_title) + with + = link_to 'rooms', admin_conference_venue_rooms_path(@conference.short_title) + , if you want to select a room for the track. = f.input :description, input_html: {rows: 2, data: { provide: 'markdown-editable' } }, hint: markdown_hint = f.input :cfp_active, label: 'Allow event submitters to select this track for their proposal' = f.action :submit, as: :button, button_html: { class: 'btn btn-primary' } diff --git a/app/views/admin/tracks/index.html.haml b/app/views/admin/tracks/index.html.haml index 5c47fe11..77a7cf02 100644 --- a/app/views/admin/tracks/index.html.haml +++ b/app/views/admin/tracks/index.html.haml @@ -1,3 +1,4 @@ +.unobtrusive-flash-container .row .col-md-12 .page-header @@ -8,6 +9,7 @@ .col-md-12 %table.table.table-hover.table-striped.table-bordered.datatable#tracks %thead + %th ID %th Name %th Description %th Room @@ -20,6 +22,8 @@ %tbody - @tracks.each do |track| %tr + %td + = track.id %td{style: "padding: 15px 0px 0px 10px;"} = link_to admin_conference_program_track_path(@conference.short_title, track), class: 'btn' do %span.label{style: "background-color: #{track.color}; color: #{ contrast_color(track.color) }"} @@ -35,7 +39,7 @@ = track.end_date.strftime('%A, %B %-d. %Y') if track.end_date %td = link_to track.submitter.name, admin_user_path(track.submitter) if track.self_organized? - %td.text-center + %td.text-center{ 'id' => "cfp_switch_#{track.id}", 'data-order' => track.cfp_active.to_s } = check_box_tag "#{@conference.short_title}_#{track.short_name}", track.id, track.cfp_active, class: 'switch-checkbox', method: :patch, url: toggle_cfp_inclusion_admin_conference_program_track_path(@conference.short_title, id: track.short_name)+"?included=", diff --git a/app/views/admin/tracks/show.html.haml b/app/views/admin/tracks/show.html.haml index a8c13d9d..78f8830e 100644 --- a/app/views/admin/tracks/show.html.haml +++ b/app/views/admin/tracks/show.html.haml @@ -1,3 +1,4 @@ +.unobtrusive-flash-container .row .col-md-12 .page-header diff --git a/app/views/admin/tracks/toggle_cfp_inclusion.js.erb b/app/views/admin/tracks/toggle_cfp_inclusion.js.erb new file mode 100644 index 00000000..6ee83003 --- /dev/null +++ b/app/views/admin/tracks/toggle_cfp_inclusion.js.erb @@ -0,0 +1,8 @@ +$('.alert').remove(); + +track_id = <%= @track.id %>; +track_cfp_td = $('#cfp_switch_' + track_id); +track_cfp_value = <%= @track.cfp_active %>; + +track_cfp_td.attr('data-order', track_cfp_value); +$('#tracks').DataTable().cell(track_cfp_td).invalidate(); diff --git a/spec/controllers/admin/tracks_controller_spec.rb b/spec/controllers/admin/tracks_controller_spec.rb index 70ca48a1..795906aa 100644 --- a/spec/controllers/admin/tracks_controller_spec.rb +++ b/spec/controllers/admin/tracks_controller_spec.rb @@ -228,16 +228,45 @@ describe Admin::TracksController do before :each do self_organized_track.cfp_active = false self_organized_track.save! - patch :toggle_cfp_inclusion, conference_id: conference.short_title, id: self_organized_track.short_name - self_organized_track.reload end - it 'assigns the correct track' do - expect(assigns(:track)).to eq self_organized_track + context 'toggles successfully' do + before :each do + patch :toggle_cfp_inclusion, conference_id: conference.short_title, id: self_organized_track.short_name, format: :js + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows success message in flash notice' do + expect(flash[:notice]).to match('Successfully changed cfp inclusion of My awesome track to true') + end + + it 'becomes true' do + expect(self_organized_track.cfp_active).to eq true + end end - it 'becomes true' do - expect(self_organized_track.cfp_active).to eq true + context 'save fails' do + before :each do + allow_any_instance_of(Track).to receive(:save).and_return(false) + patch :toggle_cfp_inclusion, conference_id: conference.short_title, id: self_organized_track.short_name, format: :js + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows error message in flash notice' do + expect(flash[:error]).to match('Failed to toggle cfp inclusion of My awesome track to true') + end + + it 'stays false' do + expect(self_organized_track.cfp_active).to eq false + end end end @@ -245,16 +274,45 @@ describe Admin::TracksController do before :each do self_organized_track.cfp_active = true self_organized_track.save! - patch :toggle_cfp_inclusion, conference_id: conference.short_title, id: self_organized_track.short_name - self_organized_track.reload end - it 'assigns the correct track' do - expect(assigns(:track)).to eq self_organized_track + context 'toggles successfully' do + before :each do + patch :toggle_cfp_inclusion, conference_id: conference.short_title, id: self_organized_track.short_name, format: :js + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows success message in flash notice' do + expect(flash[:notice]).to match('Successfully changed cfp inclusion of My awesome track to false') + end + + it 'becomes false' do + expect(self_organized_track.cfp_active).to eq false + end end - it 'becomes false' do - expect(self_organized_track.cfp_active).to eq false + context 'save fails' do + before :each do + allow_any_instance_of(Track).to receive(:save).and_return(false) + patch :toggle_cfp_inclusion, conference_id: conference.short_title, id: self_organized_track.short_name, format: :js + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows error message in flash notice' do + expect(flash[:error]).to match('Failed to toggle cfp inclusion of My awesome track to false') + end + + it 'stays true' do + expect(self_organized_track.cfp_active).to eq true + end end end end diff --git a/spec/models/track_spec.rb b/spec/models/track_spec.rb index ed058176..1ad54839 100644 --- a/spec/models/track_spec.rb +++ b/spec/models/track_spec.rb @@ -54,6 +54,7 @@ describe Track do end it { is_expected.to validate_presence_of(:relevance) } + it { is_expected.to validate_presence_of(:description) } end context 'when self_organized? returns false' do @@ -62,6 +63,7 @@ describe Track do end it { is_expected.to_not validate_presence_of(:relevance) } + it { is_expected.to_not validate_presence_of(:description) } end describe '#valid_dates' do From b4be83e3a65497354044898e3548c093f6809b58 Mon Sep 17 00:00:00 2001 From: divyanshumehta Date: Wed, 9 Aug 2017 13:26:38 +0530 Subject: [PATCH 45/74] Removed delete action from abiltiy.rb and other places There was no delete action defined for proposals, but it was there in ability.rb So the ability was deleted. Fixes #1608 --- app/models/ability.rb | 2 +- app/models/admin_ability.rb | 2 +- spec/models/ability_spec.rb | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 63ec68dc..6713127c 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -87,7 +87,7 @@ class Ability event.program.cfp_open? && event.new_record? end - can [:update, :show, :delete, :index], Event do |event| + can [:update, :show, :index], Event do |event| event.users.include?(user) end diff --git a/app/models/admin_ability.rb b/app/models/admin_ability.rb index b0c9d4c9..46038d5f 100644 --- a/app/models/admin_ability.rb +++ b/app/models/admin_ability.rb @@ -39,7 +39,7 @@ class AdminAbility event.program.cfp_open? && event.new_record? end - can [:update, :show, :delete, :index], Event do |event| + can [:update, :show, :index], Event do |event| event.users.include?(user) end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index ff3f14cb..7936ddbb 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -106,7 +106,6 @@ describe 'User' do it{ should be_able_to(:update, user_event_with_cfp) } it{ should be_able_to(:show, user_event_with_cfp) } - it{ should be_able_to(:delete, user_event_with_cfp) } it{ should_not be_able_to(:new, Event.new(program: program_without_cfp)) } it{ should_not be_able_to(:create, Event.new(program: program_without_cfp)) } # TODO: At moment it's not possible to manually add someone else as event_user From 5f9c0065c68174b110f84531cb8fedf0bd27a74b Mon Sep 17 00:00:00 2001 From: damien clochard Date: Sat, 12 Aug 2017 10:45:12 +0200 Subject: [PATCH 46/74] FIX : links --- TRANSLATION.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TRANSLATION.md b/TRANSLATION.md index 5014159f..3923584c 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -1,5 +1,5 @@ # Translation -We are using [Transifex] (https://www.transifex.com/opensuse-community/osem/) to manage our translations. +We are using [Transifex](https://www.transifex.com/opensuse-community/osem/) to manage our translations. We are also using the _Live_ feature of Transifex. That means that all strings from OSEM instances are automatically collected and are available for translating. >Automated collection only works if you use the API key (See at the end of this file) @@ -7,7 +7,7 @@ We are also using the _Live_ feature of Transifex. That means that all strings f ## 1. Translate * Start translating: - 1. Navigate to the [project's page] (https://www.transifex.com/opensuse-community/osem) + 1. Navigate to the [project's page](https://www.transifex.com/opensuse-community/osem) 2. Click **translation** button 3. Select **language** 4. Select **resource** (events.opensuse.org) @@ -27,7 +27,7 @@ Eg. Assuming you have signed up yourself as a translator for OSEM, to translate * How to request to publish translated strings - After you make sure that you have properly reviewed the newly translated strings (they need to be marked as **reviewed** otherwise they won't go live), you can open a [new issue] (https://github.com/openSUSE/osem/issues/new) with the following information: + After you make sure that you have properly reviewed the newly translated strings (they need to be marked as **reviewed** otherwise they won't go live), you can open a [new issue](https://github.com/openSUSE/osem/issues/new) with the following information: Title: [Transifex] Publish translation for EN From 7107f9a35f12838bf9d1279c4ac54c9a167d5cbd Mon Sep 17 00:00:00 2001 From: nasia Date: Wed, 2 Aug 2017 20:10:47 +0300 Subject: [PATCH 47/74] Add emails for booth's acceptance and rejection --- .haml-lint_todo.yml | 2 ++ .rubocop_todo.yml | 1 + app/controllers/admin/booths_controller.rb | 24 +++++++++++++++++-- app/controllers/admin/emails_controller.rb | 4 +++- app/mailers/mailbot.rb | 18 ++++++++++++++ app/models/email_settings.rb | 11 ++++++++- .../booths/_change_state_dropdown.html.haml | 18 ++++++++++---- app/views/admin/emails/_help.html.haml | 8 ++++++- app/views/admin/emails/index.html.haml | 22 +++++++++++++++++ ...0731161207_add_booths_to_email_settings.rb | 10 ++++++++ db/schema.rb | 6 +++++ 11 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 db/migrate/20170731161207_add_booths_to_email_settings.rb diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index b7a18be0..4ec5de3c 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -303,6 +303,7 @@ linters: SpaceInsideHashAttributes: exclude: - "app/views/admin/conferences/_todo_list.html.haml" + - "app/views/admin/emails/index.html.haml" - "app/views/admin/questions/_form.html.haml" - "app/views/admin/questions/index.html.haml" - "app/views/admin/registrations/index.html.haml" @@ -403,6 +404,7 @@ linters: # Offense count: 14 ConsecutiveSilentScripts: exclude: + - "app/views/admin/booths/_change_state_dropdown.html.haml" - "app/views/admin/events/index.html.haml" - "app/views/admin/schedules/_day_tab.html.haml" - "app/views/admin/schedules/_event.html.haml" diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 487a9fdb..38bf269f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -450,6 +450,7 @@ Style/IfUnlessModifier: - 'app/helpers/application_helper.rb' - 'app/models/commercial.rb' - 'app/models/conference.rb' + - 'app/models/email_settings.rb' - 'app/models/ticket_purchase.rb' - 'app/models/user.rb' - 'db/migrate/20151031092713_change_conference_id_to_venue_id_in_rooms.rb' diff --git a/app/controllers/admin/booths_controller.rb b/app/controllers/admin/booths_controller.rb index e5b55552..a200e42e 100644 --- a/app/controllers/admin/booths_controller.rb +++ b/app/controllers/admin/booths_controller.rb @@ -47,7 +47,18 @@ module Admin end def accept - update_state(:accept, 'Booth accepted!') + @booth.accept! + + if @booth.save + if @conference.email_settings.send_on_booths_acceptance + Mailbot.conference_booths_acceptance_mail(@booth).deliver + end + redirect_to admin_conference_booths_path(conference_id: @conference.short_title), + notice: 'Booth successfully accepted!' + else + redirect_to admin_conference_booths_path(conference_id: @conference.short_title) + flash[:error] = "Booth could not be accepted. #{@booth.errors.full_messages.to_sentence}." + end end def to_accept @@ -59,7 +70,16 @@ module Admin end def reject - update_state(:reject, 'Booth rejected') + @booth.reject! + + if @booth.save + Mailbot.conference_booths_rejection_mail(@booth).deliver + redirect_to admin_conference_booths_path(conference_id: @conference.short_title), + notice: 'Booth successfully rejected.' + else + redirect_to admin_conference_booths_path(conference_id: @conference.short_title) + flash[:error] = "Booth could not be rejected. #{@booth.errors.full_messages.to_sentence}." + end end def restart diff --git a/app/controllers/admin/emails_controller.rb b/app/controllers/admin/emails_controller.rb index adff714a..a4d6fe04 100644 --- a/app/controllers/admin/emails_controller.rb +++ b/app/controllers/admin/emails_controller.rb @@ -30,7 +30,9 @@ module Admin :send_on_conference_registration_dates_updated, :conference_registration_dates_updated_subject, :conference_registration_dates_updated_body, :send_on_venue_updated, :venue_updated_subject, :venue_updated_body, :send_on_cfp_dates_updated, :cfp_dates_updated_subject, :cfp_dates_updated_body, - :send_on_program_schedule_public, :program_schedule_public_subject, :program_schedule_public_body) + :send_on_program_schedule_public, :program_schedule_public_subject, :program_schedule_public_body, + :send_on_booths_acceptance, :booths_acceptance_subject, :booths_acceptance_body, + :send_on_booths_rejection, :booths_rejection_subject, :booths_rejection_body) end end end diff --git a/app/mailers/mailbot.rb b/app/mailers/mailbot.rb index 837e2c1e..63f95a40 100644 --- a/app/mailers/mailbot.rb +++ b/app/mailers/mailbot.rb @@ -97,6 +97,24 @@ class Mailbot < ActionMailer::Base conference.email_settings.cfp_dates_updated_body)) end + def conference_booths_acceptance_mail(booth) + conference = booth.conference + + mail(to: booth.submitter.email, + from: conference.contact.email, + subject: conference.email_settings.booths_acceptance_subject, + body: conference.email_settings.generate_booth_mail(booth, conference.email_settings.booths_acceptance_body)) + end + + def conference_booths_rejection_mail(booth) + conference = booth.conference + + mail(to: booth.submitter.email, + from: conference.contact.email, + subject: conference.email_settings.booths_rejection_subject, + body: conference.email_settings.generate_booth_mail(booth, conference.email_settings.booths_rejection_body)) + end + def event_comment_mail(comment, user) @comment = comment @event = @comment.commentable diff --git a/app/models/email_settings.rb b/app/models/email_settings.rb index 6d6c4478..6210cf9d 100644 --- a/app/models/email_settings.rb +++ b/app/models/email_settings.rb @@ -3,7 +3,7 @@ class EmailSettings < ActiveRecord::Base has_paper_trail on: [:update], ignore: [:updated_at], meta: { conference_id: :conference_id } - def get_values(conference, user, event = nil) + def get_values(conference, user, event = nil, booth = nil) h = { 'email' => user.email, 'name' => user.name, @@ -45,6 +45,10 @@ class EmailSettings < ActiveRecord::Base h['proposalslink'] = Rails.application.routes.url_helpers.conference_program_proposals_url( conference.short_title, host: (ENV['OSEM_HOSTNAME'] || 'localhost:3000')) end + + if booth + h['booth_title'] = booth.title + end h end @@ -58,6 +62,11 @@ class EmailSettings < ActiveRecord::Base parse_template(conf_update_template, values) end + def generate_booth_mail(booth, booth_template) + values = get_values(booth.conference, booth.submitter, nil, booth) + parse_template(booth_template, values) + end + private def parse_template(text, values) diff --git a/app/views/admin/booths/_change_state_dropdown.html.haml b/app/views/admin/booths/_change_state_dropdown.html.haml index c0418d67..8178a790 100644 --- a/app/views/admin/booths/_change_state_dropdown.html.haml +++ b/app/views/admin/booths/_change_state_dropdown.html.haml @@ -1,17 +1,25 @@ - if booth.transition_possible? :accept - %li= link_to 'Accept booth', + - if @conference.email_settings.send_on_booths_acceptance + - link = 'Accept with email' + - else + - link = 'Accept booth' + %li= link_to link, accept_admin_conference_booth_path(@conference.short_title, booth), - method: :patch, id: "accept_booth_#{booth.id}" + method: :patch ,id: "accept_booth_#{booth.id}" - if booth.transition_possible? :reject - %li= link_to 'Reject booth', + - if @conference.email_settings.send_on_booths_rejection + - link = 'Reject with email' + - else + - link = 'Reject' + %li= link_to link, reject_admin_conference_booth_path(@conference.short_title, booth), - method: :patch, confirm: 'Are you sure?', id: "reject_booth_#{booth.id}" + method: :patch, id: "reject_booth_#{booth.id}" - if booth.transition_possible? :to_reject %li= link_to 'To reject booth', to_reject_admin_conference_booth_path(@conference.short_title, booth), - method: :patch, confirm: 'Are you sure?', id: "to_reject_booth_#{booth.id}" + method: :patch, id: "to_reject_booth_#{booth.id}" - if booth.transition_possible? :restart %li= link_to 'Start review', diff --git a/app/views/admin/emails/_help.html.haml b/app/views/admin/emails/_help.html.haml index 535cd097..371bac7b 100644 --- a/app/views/admin/emails/_help.html.haml +++ b/app/views/admin/emails/_help.html.haml @@ -55,4 +55,10 @@ %tr %td {conference_splash_link} %td The link to conference splash page - + - if @conference.booths + %tr + %td {submitter_name} + %td Submitter's name + %tr + %td {booth_title} + %td Booth's title diff --git a/app/views/admin/emails/index.html.haml b/app/views/admin/emails/index.html.haml index 4b380b86..99d02130 100644 --- a/app/views/admin/emails/index.html.haml +++ b/app/views/admin/emails/index.html.haml @@ -14,6 +14,8 @@ %a{ 'aria-controls' => 'notifications', 'data-toggle' => 'tab', href: '#notifications', role: 'tab' } Update Notifications %li{ role: 'presentation' } %a{ 'aria-controls' => 'cfp', 'data-toggle' => 'tab', href: '#cfp', role: 'tab' } Call for Papers + %li{ role: 'presentation' } + %a{ 'aria-controls' => 'booths', 'data-toggle' => 'tab', href: '#booth', role: 'tab' } Booth / Tab panes .tab-content #onboarding.tab-pane.active{ role: 'tabpanel' } @@ -100,6 +102,26 @@ 'data-body-text' => "Dear {name},\n\nThe Conference Call for Papers Details of {conference} has changed.\nNew Dates : {cfp_start_date} - {cfp_end_date}.\n Link to Schedule {schedule_link} \n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_cfp_help' } Show Help = render partial: 'help', locals: {id: 'updated_cfp_help', show_event_variables: false} + #booth.tab-pane{ role: 'tabpanel' } + = f.input :send_on_booths_acceptance + = f.input :booths_acceptance_subject + = f.input :booths_acceptance_body, input_html: { rows:10, cols: 20 } + %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_booths_acceptance_subject', + 'data-subject-text' => 'Your booth has been accepted!', + 'data-body-input-id' => 'email_settings_booths_acceptance_body', + 'data-body-text' => "Dear {name},\n\nWe are really pleased to inform you that your booth request {booth_title} has been accepted for the conference {conference}.\nPlease click the confirm button to let us know you can make it as soon as possible!\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team"} Load Template + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'booth_acceptance_help' } Show help + = render partial: 'help', locals: {id: 'booth_acceptance_help', show_event_variables: false} + = f.input :send_on_booths_rejection + = f.input :booths_rejection_subject + = f.input :booths_rejection_body, input_html: { rows:10, cols:20 } + %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_booths_rejection_subject', + 'data-subject-text' => 'Your booth request has been rejected', + 'data-body-input-id' => 'email_settings_booths_rejection_body', + 'data-body-text' => "Dear {name},\n\nThank you for your booth request {booth_title} for the conference {conference}.\n\nUnfortunately, we are sorry to inform you that your request has been rejected.\n\n\nBest wishes\n\n{conference} Team" } Load Template + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'booth_rejection_help' } Show help + = render partial: 'help', locals: {id: 'booth_rejection_help', show_event_variables: false} + .row .col-md-12 = f.action :submit, as: :button, button_html: { class: 'btn btn-primary' } diff --git a/db/migrate/20170731161207_add_booths_to_email_settings.rb b/db/migrate/20170731161207_add_booths_to_email_settings.rb new file mode 100644 index 00000000..0b3a7a87 --- /dev/null +++ b/db/migrate/20170731161207_add_booths_to_email_settings.rb @@ -0,0 +1,10 @@ +class AddBoothsToEmailSettings < ActiveRecord::Migration + def change + add_column :email_settings, :send_on_booths_acceptance, :boolean, default: false + add_column :email_settings, :booths_acceptance_subject, :string + add_column :email_settings, :booths_acceptance_body, :text + add_column :email_settings, :send_on_booths_rejection, :boolean, default: false + add_column :email_settings, :booths_rejection_subject, :string + add_column :email_settings, :booths_rejection_body, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 9e6123a3..6cc32e82 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -205,6 +205,12 @@ ActiveRecord::Schema.define(version: 20170807092805) do t.string "cfp_dates_updated_subject" t.text "program_schedule_public_body" t.text "cfp_dates_updated_body" + t.boolean "send_on_booths_acceptance", default: false + t.string "booths_acceptance_subject" + t.text "booths_acceptance_body" + t.boolean "send_on_booths_rejection", default: false + t.string "booths_rejection_subject" + t.text "booths_rejection_body" end create_table "event_schedules", force: :cascade do |t| From 1391668c0f4536f6685b0b8ee4ce655a7ac58dc4 Mon Sep 17 00:00:00 2001 From: siddhantbajaj Date: Tue, 18 Jul 2017 02:57:46 +0530 Subject: [PATCH 48/74] Add routes and controller to scan Qr code Added TicketScanning controller and routes to scan qr code on PhysicalTickets. Added test for the same --- .../admin/ticket_scannings_controller.rb | 16 +++++ app/models/admin_ability.rb | 8 +++ config/routes.rb | 1 + .../admin/ticket_scannings_controller_spec.rb | 60 +++++++++++++++++++ 4 files changed, 85 insertions(+) create mode 100644 app/controllers/admin/ticket_scannings_controller.rb create mode 100644 spec/controllers/admin/ticket_scannings_controller_spec.rb diff --git a/app/controllers/admin/ticket_scannings_controller.rb b/app/controllers/admin/ticket_scannings_controller.rb new file mode 100644 index 00000000..488f28a1 --- /dev/null +++ b/app/controllers/admin/ticket_scannings_controller.rb @@ -0,0 +1,16 @@ +module Admin + class TicketScanningsController < Admin::BaseController + before_action :authenticate_user! + load_resource :physical_ticket, find_by: :token + # We authorize manually in these actions + skip_authorize_resource only: [:create] + + def create + @ticket_scanning = TicketScanning.new(physical_ticket: @physical_ticket) + authorize! :create, @ticket_scanning + @ticket_scanning.save + redirect_to conferences_path, + notice: "Ticket with token #{@physical_ticket.token} successfully scanned." + end + end +end diff --git a/app/models/admin_ability.rb b/app/models/admin_ability.rb index 4244d049..256262f4 100644 --- a/app/models/admin_ability.rb +++ b/app/models/admin_ability.rb @@ -145,6 +145,10 @@ class AdminAbility can :manage, Sponsor, conference_id: conf_ids can :manage, SponsorshipLevel, conference_id: conf_ids can :manage, Ticket, conference_id: conf_ids + can :create, TicketScanning do |ticket_scanning| + conf_id = ticket_scanning.physical_ticket.ticket_purchase.conference_id + conf_ids.include? conf_id + end can :index, Comment, commentable_type: 'Event', commentable_id: Event.where(program_id: Program.where(conference_id: conf_ids).pluck(:id)).pluck(:id) @@ -220,6 +224,10 @@ class AdminAbility can :manage, Question do |question| !(question.conferences.pluck(:id) & conf_ids_for_info_desk).empty? end + can :create, TicketScanning do |ticket_scanning| + conf_id = ticket_scanning.physical_ticket.ticket_purchase.conference_id + conf_ids_for_info_desk.include? conf_id + end # Abilities for Role (Conference resource) can [:index, :show], Role do |role| diff --git a/config/routes.rb b/config/routes.rb index 5a17d314..98211087 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -26,6 +26,7 @@ Osem::Application.routes.draw do patch :toggle_confirmation end end + resource :ticket_scanning, only: [:create] resources :comments, only: [:index] resources :conferences do resource :contact, except: [:index, :new, :create, :show, :destroy] diff --git a/spec/controllers/admin/ticket_scannings_controller_spec.rb b/spec/controllers/admin/ticket_scannings_controller_spec.rb new file mode 100644 index 00000000..245bd885 --- /dev/null +++ b/spec/controllers/admin/ticket_scannings_controller_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' + +describe Admin::TicketScanningsController do + let(:admin) { create(:admin) } + let(:conference) { create(:conference) } + let(:user) { create(:user) } + let(:paid_ticket_purchase) { create(:ticket_purchase, conference: conference, user: user) } + let(:physical_ticket) { create(:physical_ticket, ticket_purchase: paid_ticket_purchase) } + + context 'logged in as user with no role' do + before :each do + sign_in user + end + describe 'POST #create' do + it 'does not create new ticket scanning' do + expected = expect do + post :create, physical_ticket_id: physical_ticket.token + end + expected.to_not change(TicketScanning, :count) + end + + it 'redirects to root' do + post :create, physical_ticket_id: physical_ticket.token + expect(flash[:alert]).to eq('You are not authorized to access this page.') + expect(response).to redirect_to(root_path) + end + end + end + + context 'logged in as admin' do + before :each do + sign_in admin + end + describe 'POST #create' do + context 'with valid physical_ticket' do + it 'creates new ticket scanning' do + expected = expect do + post :create, physical_ticket_id: physical_ticket.token + end + expected.to change { TicketScanning.count }.by(1) + end + + it 'redirects to index' do + post :create, physical_ticket_id: physical_ticket.token + expect(flash[:notice]).to eq("Ticket with token #{physical_ticket.token} successfully scanned.") + expect(response).to redirect_to(conferences_path) + end + end + + context 'with Invalid physical_ticket' do + it 'raises exception' do + expected = expect do + post :create, physical_ticket_id: 'XXXX' + end + expected.to raise_exception(ActiveRecord::RecordNotFound) + end + end + end + end +end From 8b5ebb3dd7e283129d78f4d67cd1982f558d2a0a Mon Sep 17 00:00:00 2001 From: nasia Date: Thu, 27 Jul 2017 11:22:14 +0300 Subject: [PATCH 49/74] Add confirmed booths to splashpage --- .haml-lint_todo.yml | 1 + .../admin/splashpages_controller.rb | 3 ++- app/models/booth.rb | 2 ++ app/views/admin/splashpages/_form.html.haml | 1 + app/views/admin/splashpages/show.html.haml | 7 ++++++ app/views/conferences/_booths.html.haml | 24 +++++++++++++++++++ .../conferences/_conference_details.html.haml | 4 ++++ app/views/conferences/show.html.haml | 4 ++++ ...81731_add_include_booths_to_splashpages.rb | 5 ++++ db/schema.rb | 1 + 10 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 app/views/conferences/_booths.html.haml create mode 100644 db/migrate/20170727081731_add_include_booths_to_splashpages.rb diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 4ec5de3c..a456dcd2 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -118,6 +118,7 @@ linters: - "app/views/conference_registrations/_registration_info.html.haml" - "app/views/conference_registrations/_volunteer.html.haml" - "app/views/conference_registrations/show.html.haml" + - "app/views/conferences/_booths.html.haml" - "app/views/conferences/_call_for_paper.html.haml" - "app/views/conferences/_conference_details.html.haml" - "app/views/conferences/_gallery.html.haml" diff --git a/app/controllers/admin/splashpages_controller.rb b/app/controllers/admin/splashpages_controller.rb index 2745cc8b..5f04a67b 100644 --- a/app/controllers/admin/splashpages_controller.rb +++ b/app/controllers/admin/splashpages_controller.rb @@ -47,7 +47,8 @@ module Admin :include_tracks, :include_program, :include_cfp, :include_venue, :include_registrations, :include_tickets, :include_lodgings, - :include_sponsors, :include_social_media) + :include_sponsors, :include_social_media, + :include_booths) end end end diff --git a/app/models/booth.rb b/app/models/booth.rb index 853bca9e..d7cc3eb2 100644 --- a/app/models/booth.rb +++ b/app/models/booth.rb @@ -25,6 +25,8 @@ class Booth < ActiveRecord::Base :submitter_relationship, presence: true + scope :confirmed, -> { where(state: 'confirmed') } + mount_uploader :picture, PictureUploader, mount_on: :logo_link state_machine initial: :new do diff --git a/app/views/admin/splashpages/_form.html.haml b/app/views/admin/splashpages/_form.html.haml index f945b2c9..c1897745 100644 --- a/app/views/admin/splashpages/_form.html.haml +++ b/app/views/admin/splashpages/_form.html.haml @@ -14,6 +14,7 @@ = f.input :include_tickets, label: 'Display tickets', input_html: { checked: params[:action] == 'new' || @splashpage.try(:include_tickets) } = f.input :include_lodgings, label: 'Display the lodgings', input_html: { checked: params[:action] == 'new' || @splashpage.try(:include_lodgings) } = f.input :include_sponsors, label: 'Display sponsors', input_html: { checked: params[:action] == 'new' || @splashpage.try(:include_sponsors) } + = f.input :include_booths, label: 'Display confirmed booths', input_html: { checked: params[:action] == 'new' || @splashpage.try(:include_booths) } = f.input :include_social_media, label: 'Display social media', input_html: { checked: params[:action] == 'new' || @splashpage.try(:include_social_media) } = f.inputs name: 'Access' do = f.input :public, label: 'Make splash page public?' diff --git a/app/views/admin/splashpages/show.html.haml b/app/views/admin/splashpages/show.html.haml index b4e5f24c..c185b119 100644 --- a/app/views/admin/splashpages/show.html.haml +++ b/app/views/admin/splashpages/show.html.haml @@ -66,6 +66,13 @@ Yes - else No + %dt + Include Booths + %dd + - if @splashpage.include_booths + Yes + - else + No %dt Include Social Media: %dd diff --git a/app/views/conferences/_booths.html.haml b/app/views/conferences/_booths.html.haml new file mode 100644 index 00000000..b40af9b8 --- /dev/null +++ b/app/views/conferences/_booths.html.haml @@ -0,0 +1,24 @@ += content_for :splash_nav do + %li + %a.smoothscroll{ href: '#booths' } Booths + +.container + .row + .col-md-12.text-center + %h2 Booths + - @conference.booths.confirmed.each_slice(3).with_index do |slice, index_for_row| + .row.row-centered + - slice.each.with_index do |booth, index_for_column| + .col-md-4.col-sm-4.col-xs-10.col-centered.col-top + .thumbnail + - if booth.logo_link + = link_to booth.website_url, class: 'thumbnail' do + = image_tag booth.picture.large.url + .caption + %h3.text-center + = booth.title + %p.text-center.text-muted + = link_to "#show_descrition_#{index_for_row}_#{index_for_column}", "data-toggle"=>"collapse" do + learn more + .collapse{ id: "show_descrition_#{index_for_row}_#{index_for_column}" } + = markdown(booth.description) diff --git a/app/views/conferences/_conference_details.html.haml b/app/views/conferences/_conference_details.html.haml index e8ca2449..f4ec7101 100644 --- a/app/views/conferences/_conference_details.html.haml +++ b/app/views/conferences/_conference_details.html.haml @@ -38,6 +38,10 @@ = link_to "My Proposals", conference_program_proposals_path(conference.short_title), class: 'btn btn-default' - elsif can? :new, conference.program.events.new = link_to "Submit Proposal", new_conference_program_proposal_path(conference.short_title), class: 'btn btn-default' + - if current_user && current_user.booths.where(conference_id: conference.id).count > 0 + = link_to 'My Booth Requests', conference_booths_path(conference.short_title), class: 'btn btn-default' + - elsif can? :new, conference.booths.new + = link_to 'Request Booth', new_conference_booth_path(conference.short_title), class: 'btn btn-default' - if current_user.nil? || !current_user.subscribed?(conference) = link_to 'Subscribe', conference_subscriptions_path(conference.short_title), method: :post, class: 'btn btn-default' - else diff --git a/app/views/conferences/show.html.haml b/app/views/conferences/show.html.haml index edc72726..afda1516 100644 --- a/app/views/conferences/show.html.haml +++ b/app/views/conferences/show.html.haml @@ -65,6 +65,10 @@ %section#tickets = render 'tickets' + - if @conference.booths.confirmed.any? and @conference.splashpage.include_booths + %section#booths + = render 'booths' + - if @conference.sponsors.any? and @conference.splashpage.include_sponsors %section#sponsors = render 'sponsors' diff --git a/db/migrate/20170727081731_add_include_booths_to_splashpages.rb b/db/migrate/20170727081731_add_include_booths_to_splashpages.rb new file mode 100644 index 00000000..da11ea8d --- /dev/null +++ b/db/migrate/20170727081731_add_include_booths_to_splashpages.rb @@ -0,0 +1,5 @@ +class AddIncludeBoothsToSplashpages < ActiveRecord::Migration + def change + add_column :splashpages, :include_booths, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 6cc32e82..6c40c572 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -451,6 +451,7 @@ ActiveRecord::Schema.define(version: 20170807092805) do t.datetime "created_at" t.datetime "updated_at" t.boolean "include_cfp", default: false + t.boolean "include_booths" end create_table "sponsors", force: :cascade do |t| From 703813248dfef66f12b2285b6ea50a664b5f160d Mon Sep 17 00:00:00 2001 From: nasia Date: Wed, 16 Aug 2017 11:42:57 +0300 Subject: [PATCH 50/74] Request a booth only when cfp is open --- app/models/ability.rb | 4 +++- app/models/cfp.rb | 9 +++++++++ spec/controllers/booths_controller_spec.rb | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 6713127c..18f9d4c4 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -75,7 +75,9 @@ class Ability can [:new, :create], Payment, user_id: user.id can [:index, :show], PhysicalTicket, user: user - can [:new, :create], Booth + can [:new, :create], Booth do |booth| + booth.new_record? && booth.conference.program.cfps.for_booths.try(:open?) + end can [:edit, :update, :index, :show], Booth do |booth| booth.users.include?(user) diff --git a/app/models/cfp.rb b/app/models/cfp.rb index 8248cd1a..17b37809 100644 --- a/app/models/cfp.rb +++ b/app/models/cfp.rb @@ -94,6 +94,15 @@ class Cfp < ActiveRecord::Base find_by(cfp_type: 'tracks') end + ## + # Finds the cfp for booths if it exists + # + # ====Returns + # * +Cfp+ -> The cfp with type 'booths' + def self.for_booths + find_by(cfp_type: 'booths') + end + private def before_end_of_conference diff --git a/spec/controllers/booths_controller_spec.rb b/spec/controllers/booths_controller_spec.rb index 243bc6e2..56f3b9d9 100644 --- a/spec/controllers/booths_controller_spec.rb +++ b/spec/controllers/booths_controller_spec.rb @@ -8,6 +8,7 @@ describe BoothsController do context 'user is signed in with submitter role' do before :each do + create(:cfp, program: conference.program, cfp_type: 'booths') sign_in booth.submitter end From e73218b5ca009ee7a159d55b99f09db320ec6951 Mon Sep 17 00:00:00 2001 From: nasia Date: Fri, 28 Jul 2017 21:46:48 +0300 Subject: [PATCH 51/74] Add booth limit --- .haml-lint_todo.yml | 2 ++ app/controllers/admin/booths_controller.rb | 23 +++++++++++-------- .../admin/conferences_controller.rb | 2 +- app/models/booth.rb | 1 + app/models/conference.rb | 9 ++++++++ .../booths/_change_state_dropdown.html.haml | 16 +++++++------ app/views/admin/booths/index.html.haml | 22 ++++++++++++++++++ app/views/admin/conferences/edit.html.haml | 3 +++ ...28182033_add_booth_limit_to_conferences.rb | 5 ++++ db/schema.rb | 1 + 10 files changed, 66 insertions(+), 18 deletions(-) create mode 100644 db/migrate/20170728182033_add_booth_limit_to_conferences.rb diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index a456dcd2..2e4c2995 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -11,6 +11,8 @@ linters: # Offense count: 945 LineLength: exclude: + - "app/views/admin/booths/_change_state_dropdown.html.haml" + - "app/views/admin/booths/_form.html.haml" - "app/views/admin/booths/index.html.haml" - "app/views/admin/booths/show.html.haml" - "app/views/admin/campaigns/_form.html.haml" diff --git a/app/controllers/admin/booths_controller.rb b/app/controllers/admin/booths_controller.rb index a200e42e..26bacc8f 100644 --- a/app/controllers/admin/booths_controller.rb +++ b/app/controllers/admin/booths_controller.rb @@ -47,18 +47,21 @@ module Admin end def accept - @booth.accept! - if @booth.save - if @conference.email_settings.send_on_booths_acceptance - Mailbot.conference_booths_acceptance_mail(@booth).deliver + if can? :accept, @booth + @booth.accept! + + if @booth.save + if @conference.email_settings.send_on_booths_acceptance + Mailbot.conference_booths_acceptance_mail(@booth).deliver + end + redirect_to admin_conference_booths_path(conference_id: @conference.short_title), + notice: 'Booth successfully accepted!' + else + redirect_to admin_conference_booths_path(conference_id: @conference.short_title) + flash[:error] = "Booth could not be accepted. #{@booth.errors.full_messages.to_sentence}." end - redirect_to admin_conference_booths_path(conference_id: @conference.short_title), - notice: 'Booth successfully accepted!' - else - redirect_to admin_conference_booths_path(conference_id: @conference.short_title) - flash[:error] = "Booth could not be accepted. #{@booth.errors.full_messages.to_sentence}." - end + end end def to_accept diff --git a/app/controllers/admin/conferences_controller.rb b/app/controllers/admin/conferences_controller.rb index 6716a253..3905012c 100644 --- a/app/controllers/admin/conferences_controller.rb +++ b/app/controllers/admin/conferences_controller.rb @@ -211,7 +211,7 @@ module Admin :vpositions_attributes, :use_volunteers, :color, :sponsorship_levels_attributes, :sponsors_attributes, :targets, :targets_attributes, - :campaigns, :campaigns_attributes, :registration_limit, :organization_id, :ticket_layout) + :campaigns, :campaigns_attributes, :registration_limit, :organization_id, :ticket_layout, :booth_limit) end end end diff --git a/app/models/booth.rb b/app/models/booth.rb index d7cc3eb2..4e42d839 100644 --- a/app/models/booth.rb +++ b/app/models/booth.rb @@ -25,6 +25,7 @@ class Booth < ActiveRecord::Base :submitter_relationship, presence: true + scope :accepted, -> { where(state: 'accepted') } scope :confirmed, -> { where(state: 'confirmed') } mount_uploader :picture, PictureUploader, mount_on: :logo_link diff --git a/app/models/conference.rb b/app/models/conference.rb index b528d755..729db645 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -738,6 +738,15 @@ class Conference < ActiveRecord::Base (start_hour..(end_hour - 1)).cover?(current_hour) ? current_hour - start_hour : 0 end + ## + # + # ====Returns + # * +True+ -> if accepted booths are equal to the booth limit + # * +False+ -> Accepted booths have not reached the booth limit + def maximum_accepted_booths? + booth_limit > 0 && booths.accepted.count + booths.confirmed.count >= booth_limit + end + ## # Return the current conference object to be used in RevisionCount # diff --git a/app/views/admin/booths/_change_state_dropdown.html.haml b/app/views/admin/booths/_change_state_dropdown.html.haml index 8178a790..85f8b971 100644 --- a/app/views/admin/booths/_change_state_dropdown.html.haml +++ b/app/views/admin/booths/_change_state_dropdown.html.haml @@ -1,11 +1,13 @@ - if booth.transition_possible? :accept - - if @conference.email_settings.send_on_booths_acceptance - - link = 'Accept with email' - - else - - link = 'Accept booth' - %li= link_to link, - accept_admin_conference_booth_path(@conference.short_title, booth), - method: :patch ,id: "accept_booth_#{booth.id}" + - if can? :accept, @booth + - if @conference.email_settings.send_on_booths_acceptance + - link = 'Accept with email' + - else + - link = 'Accept booth' + %li= link_to link, + accept_admin_conference_booth_path(@conference.short_title, booth), + method: :patch ,id: "accept_booth_#{booth.id}", + data: (@conference.booth_limit > 0 ? { confirm: 'You are able to accept '+ pluralize(@conference.booth_limit - @conference.booths.accepted.count, 'more booth') + " (booth limit set to #{@conference.booth_limit}). Are you sure you want to accept this one?" } : nil ) - if booth.transition_possible? :reject - if @conference.email_settings.send_on_booths_rejection diff --git a/app/views/admin/booths/index.html.haml b/app/views/admin/booths/index.html.haml index 27f505ad..a7ee53cd 100644 --- a/app/views/admin/booths/index.html.haml +++ b/app/views/admin/booths/index.html.haml @@ -9,8 +9,30 @@ = link_to 'Add Booth', new_admin_conference_booth_path(@conference.short_title), class: 'button btn btn-primary' %p.text-muted All the booth requests + + .row .col-md-12 + %h4 + - if @conference.booth_limit == 0 + %p + Set the + = link_to 'Booth limit', edit_admin_conference_path(@conference.short_title) + to make sure you are not accepting more booths than you can accommodate. + - elsif !@conference.maximum_accepted_booths? + %p + You cannot accept more than + %b + = pluralize(@conference.booth_limit, 'booth') + ( + = pluralize(@conference.booths.accepted.count + @conference.booths.confirmed.count, 'accepted booth') + so far) + - else + %p + You have reached the maximum number of accepted booths. + ( + = link_to "#{@conference.booth_limit} booths", edit_admin_conference_path(@conference.short_title) + ) .margin-booth-table %table.table.table-striped.table-bordered.table-hover.datatable %thead diff --git a/app/views/admin/conferences/edit.html.haml b/app/views/admin/conferences/edit.html.haml index 2d5df126..26ae198f 100644 --- a/app/views/admin/conferences/edit.html.haml +++ b/app/views/admin/conferences/edit.html.haml @@ -26,4 +26,7 @@ = f.input :end_hour, input_html: {size: 2, type: 'number', min: 1, max: 24} = f.inputs name: 'Registrations' do = f.input :registration_limit, as: :number, in: 0..9999, hint: 'Limit the number of registrations to the conference (0 no limit). Please note that the registration limit doesn\'t apply to speakers of confirmed events (they will still be able to register even if it has been reached). You currently have ' + pluralize(@conference.registrations.count, 'registration') + = f.inputs name: 'Booths' do + = f.input :booth_limit, as: :number, in: 0..9999, + hint: 'Booth limit is the maximum number of booths that you can accept for this conference. By setting this number (0 no limit) you can be sure that you are not going to accept more booths than the conference can accommodate. You currently have ' + pluralize(@conference.booths.accepted.count, 'accepted booth') +'.' = f.action :submit, as: :button, button_html: {class: 'btn btn-primary'} diff --git a/db/migrate/20170728182033_add_booth_limit_to_conferences.rb b/db/migrate/20170728182033_add_booth_limit_to_conferences.rb new file mode 100644 index 00000000..60643784 --- /dev/null +++ b/db/migrate/20170728182033_add_booth_limit_to_conferences.rb @@ -0,0 +1,5 @@ +class AddBoothLimitToConferences < ActiveRecord::Migration + def change + add_column :conferences, :booth_limit, :integer, default: 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index 6c40c572..83499b33 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -127,6 +127,7 @@ ActiveRecord::Schema.define(version: 20170807092805) do t.integer "end_hour", default: 20 t.integer "organization_id" t.integer "ticket_layout", default: 0 + t.integer "booth_limit", default: 0 end add_index "conferences", ["organization_id"], name: "index_conferences_on_organization_id" From 52cde784fa7c8f1ddb4cae926d744bdd8a19ddfe Mon Sep 17 00:00:00 2001 From: nasia Date: Tue, 15 Aug 2017 18:02:51 +0300 Subject: [PATCH 52/74] Add :accept booth to ability --- .rubocop_todo.yml | 3 + app/controllers/admin/booths_controller.rb | 23 +++-- app/models/admin_ability.rb | 5 ++ .../booths/_change_state_dropdown.html.haml | 11 ++- app/views/admin/booths/index.html.haml | 84 +++++++++---------- app/views/admin/emails/index.html.haml | 2 +- 6 files changed, 68 insertions(+), 60 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 38bf269f..a91f2ae9 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -330,6 +330,8 @@ Metrics/LineLength: # Configuration parameters: CountComments. Metrics/MethodLength: Max: 56 + Exclude: + - 'app/models/admin_ability.rb' # Offense count: 3 # Configuration parameters: CountComments. @@ -443,6 +445,7 @@ Style/HashSyntax: # Configuration parameters: MaxLineLength. Style/IfUnlessModifier: Exclude: + - 'app/controllers/admin/booths_controller.rb' - 'app/controllers/admin/events_controller.rb' - 'app/controllers/api/v1/events_controller.rb' - 'app/controllers/conference_registrations_controller.rb' diff --git a/app/controllers/admin/booths_controller.rb b/app/controllers/admin/booths_controller.rb index 26bacc8f..a200e42e 100644 --- a/app/controllers/admin/booths_controller.rb +++ b/app/controllers/admin/booths_controller.rb @@ -47,21 +47,18 @@ module Admin end def accept + @booth.accept! - if can? :accept, @booth - @booth.accept! - - if @booth.save - if @conference.email_settings.send_on_booths_acceptance - Mailbot.conference_booths_acceptance_mail(@booth).deliver - end - redirect_to admin_conference_booths_path(conference_id: @conference.short_title), - notice: 'Booth successfully accepted!' - else - redirect_to admin_conference_booths_path(conference_id: @conference.short_title) - flash[:error] = "Booth could not be accepted. #{@booth.errors.full_messages.to_sentence}." + if @booth.save + if @conference.email_settings.send_on_booths_acceptance + Mailbot.conference_booths_acceptance_mail(@booth).deliver end - end + redirect_to admin_conference_booths_path(conference_id: @conference.short_title), + notice: 'Booth successfully accepted!' + else + redirect_to admin_conference_booths_path(conference_id: @conference.short_title) + flash[:error] = "Booth could not be accepted. #{@booth.errors.full_messages.to_sentence}." + end end def to_accept diff --git a/app/models/admin_ability.rb b/app/models/admin_ability.rb index 466380cc..f742b775 100644 --- a/app/models/admin_ability.rb +++ b/app/models/admin_ability.rb @@ -74,6 +74,11 @@ class AdminAbility cannot :destroy, Track do |track| track.self_organized? end + # Can't accept a booth when booth_limit is reached + cannot :accept, Booth do |booth| + conference = booth.conference + conference.maximum_accepted_booths? + end end # Abilities for signed in users with roles diff --git a/app/views/admin/booths/_change_state_dropdown.html.haml b/app/views/admin/booths/_change_state_dropdown.html.haml index 85f8b971..12b982df 100644 --- a/app/views/admin/booths/_change_state_dropdown.html.haml +++ b/app/views/admin/booths/_change_state_dropdown.html.haml @@ -1,13 +1,17 @@ + - if booth.transition_possible? :accept - - if can? :accept, @booth + - if can? :accept, booth + - if @conference.booth_limit > 0 + - confirm_message = ' You are able to accept '+ pluralize(@conference.booth_limit - (@conference.booths.accepted.count + @conference.booths.confirmed.count), 'more booth') + " (booth limit set to #{@conference.booth_limit}). Are you sure you want to accept this one?" - if @conference.email_settings.send_on_booths_acceptance - link = 'Accept with email' + - confirm_message = "By accepting this booth, an email will be sent informing the submitter for the acceptance. You may change the state to \'To accept\' until you are completely sure." + confirm_message - else - link = 'Accept booth' %li= link_to link, accept_admin_conference_booth_path(@conference.short_title, booth), method: :patch ,id: "accept_booth_#{booth.id}", - data: (@conference.booth_limit > 0 ? { confirm: 'You are able to accept '+ pluralize(@conference.booth_limit - @conference.booths.accepted.count, 'more booth') + " (booth limit set to #{@conference.booth_limit}). Are you sure you want to accept this one?" } : nil ) + data: (confirm_message ? { confirm: confirm_message } : nil) - if booth.transition_possible? :reject - if @conference.email_settings.send_on_booths_rejection @@ -16,7 +20,8 @@ - link = 'Reject' %li= link_to link, reject_admin_conference_booth_path(@conference.short_title, booth), - method: :patch, id: "reject_booth_#{booth.id}" + method: :patch, id: "reject_booth_#{booth.id}", + data: (@conference.email_settings.send_on_booths_rejection ? { confirm: 'By rejecting this booth, an email will be sent informing the submitter about the rejection. You may change the state to \'To reject\' until you are completely sure.'} : nil) - if booth.transition_possible? :to_reject %li= link_to 'To reject booth', diff --git a/app/views/admin/booths/index.html.haml b/app/views/admin/booths/index.html.haml index a7ee53cd..fc3f3ba3 100644 --- a/app/views/admin/booths/index.html.haml +++ b/app/views/admin/booths/index.html.haml @@ -10,7 +10,6 @@ %p.text-muted All the booth requests - .row .col-md-12 %h4 @@ -33,46 +32,45 @@ ( = link_to "#{@conference.booth_limit} booths", edit_admin_conference_path(@conference.short_title) ) - .margin-booth-table - %table.table.table-striped.table-bordered.table-hover.datatable - %thead - %th - %b ID - %th - %b Logo - %th - %b Title - %th - %b Submitter - %th - %b Responsibles - %th - %b State - %th - %b Actions - - @booths.each do |booth| - %tr + %table.table.table-striped.table-bordered.table-hover.datatable + %thead + %th + %b ID + %th + %b Logo + %th + %b Title + %th + %b Submitter + %th + %b Responsibles + %th + %b State + %th + %b Actions + - @booths.each do |booth| + %tr + %td + = booth.id + %td + - if booth.logo_link + = image_tag(booth.picture.thumb.url, width: '20%') + %td + = link_to booth.title, admin_conference_booth_path(@conference.short_title, booth) + %td + = link_to booth.submitter.name, admin_user_path(booth.submitter) if booth.submitter + %td + .responsibles + - booth.responsibles.each_with_index do |responsible, i| + = link_to responsible.name, admin_user_path(responsible) + = ", " unless i == booth.responsibles.length - 1 + %td + .btn-group + %button{ type: 'button', class: 'btn btn-link dropdown-toggle', 'data-toggle' => 'dropdown' } + = booth.state.humanize + %span.caret + %ul.dropdown-menu{ role: 'menu' } + = render 'change_state_dropdown', booth: booth %td - = booth.id - %td - - if booth.logo_link - = image_tag(booth.picture.thumb.url, width: '20%') - %td - = link_to booth.title, admin_conference_booth_path(@conference.short_title, booth) - %td - = link_to booth.submitter.name, admin_user_path(booth.submitter) if booth.submitter - %td - .responsibles - - booth.responsibles.each_with_index do |responsible, i| - = link_to responsible.name, admin_user_path(responsible) - = ", " unless i == booth.responsibles.length - 1 - %td - .btn-group - %button{ type: 'button', class: 'btn btn-link dropdown-toggle', 'data-toggle' => 'dropdown' } - = booth.state.humanize - %span.caret - %ul.dropdown-menu{ role: 'menu' } - = render 'change_state_dropdown', booth: booth - %td - = link_to 'Edit', edit_admin_conference_booth_path(@conference.short_title, booth.id), - class: 'btn btn-primary' + = link_to 'Edit', edit_admin_conference_booth_path(@conference.short_title, booth.id), + class: 'btn btn-primary' diff --git a/app/views/admin/emails/index.html.haml b/app/views/admin/emails/index.html.haml index 99d02130..83ebe210 100644 --- a/app/views/admin/emails/index.html.haml +++ b/app/views/admin/emails/index.html.haml @@ -109,7 +109,7 @@ %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_booths_acceptance_subject', 'data-subject-text' => 'Your booth has been accepted!', 'data-body-input-id' => 'email_settings_booths_acceptance_body', - 'data-body-text' => "Dear {name},\n\nWe are really pleased to inform you that your booth request {booth_title} has been accepted for the conference {conference}.\nPlease click the confirm button to let us know you can make it as soon as possible!\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team"} Load Template + 'data-body-text' => "Dear {name},\n\nWe are pleased to inform you that your booth request {booth_title} has been accepted for the conference {conference}.\nPlease click the confirm button to let us know you can make it as soon as possible!\n\nFeel free to contact us with any questions or concerns.\n\nWe are looking forward to seeing you there.\n\nBest wishes\n\n{conference} Team"} Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'booth_acceptance_help' } Show help = render partial: 'help', locals: {id: 'booth_acceptance_help', show_event_variables: false} = f.input :send_on_booths_rejection From d0a961099261fbb6ed0569957caf5e87eddcdb66 Mon Sep 17 00:00:00 2001 From: nikhilgupta1211 Date: Sun, 20 Aug 2017 16:27:35 +0530 Subject: [PATCH 53/74] Made Sidebar collapsible for small screens Added a hamburger button in _admin_html.haml for navbar collapse Fixes #853 --- app/assets/stylesheets/osem.css.scss | 6 ++++++ app/views/layouts/_admin_sidebar.html.haml | 2 +- app/views/layouts/_navigation.html.haml | 10 ++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/osem.css.scss b/app/assets/stylesheets/osem.css.scss index 5a331436..775418b3 100644 --- a/app/assets/stylesheets/osem.css.scss +++ b/app/assets/stylesheets/osem.css.scss @@ -94,3 +94,9 @@ p.comment-body { .box{ height: 230px; } + +/* sidebar hamburger btn */ +.side-nav-btn{ + margin-left: 10px; + float: left; +} diff --git a/app/views/layouts/_admin_sidebar.html.haml b/app/views/layouts/_admin_sidebar.html.haml index 1c7bac9a..28ef62e2 100644 --- a/app/views/layouts/_admin_sidebar.html.haml +++ b/app/views/layouts/_admin_sidebar.html.haml @@ -1,4 +1,4 @@ -%ul.nav.nav-stacked.nav-pills.mySidebar +%ul.nav.nav-stacked.nav-pills.mySidebar.collapse.navbar-collapse#side-nav .btn-group %button{type:'button', class: 'btn btn-default btn-link dropdown-toggle', 'data-toggle'=>'dropdown'} %span.fa.fa-cog diff --git a/app/views/layouts/_navigation.html.haml b/app/views/layouts/_navigation.html.haml index 9750be92..9995123b 100644 --- a/app/views/layouts/_navigation.html.haml +++ b/app/views/layouts/_navigation.html.haml @@ -1,7 +1,13 @@ .navbar.navbar-default.navbar-fixed-top.nav-osem{role: 'navigation'} .container .navbar-header - %button{"data-target"=>".navbar-collapse", "data-toggle"=>"collapse", class: 'navbar-toggle', type: 'button'} + - if @conference && @conference.short_title.present? + %button{ "data-target"=>"#side-nav", "data-toggle"=>"collapse", class: 'navbar-toggle side-nav-btn', type: 'button' } + %span.sr-only Toggle navigation + %span.icon-bar + %span.icon-bar + %span.icon-bar + %button{"data-target"=>"#main-nav", "data-toggle"=>"collapse", class: 'navbar-toggle', type: 'button'} %span.sr-only Toggle navigation %span.icon-bar @@ -11,7 +17,7 @@ = link_to (ENV['OSEM_NAME'] || 'OSEM'), root_path, class: 'navbar-brand', title: 'Open Source Event Manager' - else = link_to conference.organization.name, organizations_path, class: 'navbar-brand', title: 'Open Source Event Manager' - .collapse.navbar-collapse + .collapse.navbar-collapse#main-nav - if content_for :splash_nav %ul.nav.navbar-nav#splash-nav = content_for :splash_nav From 4b735cff69f9ac8f316d25d095c079d971024eb1 Mon Sep 17 00:00:00 2001 From: siddhantbajaj Date: Thu, 27 Jul 2017 17:25:17 +0530 Subject: [PATCH 54/74] Added qr code Added qr code on ticket pdf and ticket show page. --- app/assets/stylesheets/osem.css.scss | 4 ++++ app/controllers/physical_ticket_controller.rb | 1 + app/pdfs/ticket_pdf.rb | 6 +++++- app/views/physical_ticket/show.html.haml | 1 + 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/osem.css.scss b/app/assets/stylesheets/osem.css.scss index 775418b3..79768c5d 100644 --- a/app/assets/stylesheets/osem.css.scss +++ b/app/assets/stylesheets/osem.css.scss @@ -99,4 +99,8 @@ p.comment-body { .side-nav-btn{ margin-left: 10px; float: left; + } + +.qr-image{ + margin-left: 120px; } diff --git a/app/controllers/physical_ticket_controller.rb b/app/controllers/physical_ticket_controller.rb index fb25ae63..af02155c 100644 --- a/app/controllers/physical_ticket_controller.rb +++ b/app/controllers/physical_ticket_controller.rb @@ -13,6 +13,7 @@ class PhysicalTicketController < ApplicationController @file_name = "ticket_for_#{@conference.short_title}" @user = @physical_ticket.user @ticket_layout = @conference.ticket_layout.to_sym + @qrcode_image = RQRCode::QRCode.new(@physical_ticket.token).as_png(size: 180, border_modules: 0) respond_to do |format| format.html format.pdf do diff --git a/app/pdfs/ticket_pdf.rb b/app/pdfs/ticket_pdf.rb index 1c26ec77..ecb4cd2c 100644 --- a/app/pdfs/ticket_pdf.rb +++ b/app/pdfs/ticket_pdf.rb @@ -77,5 +77,9 @@ class TicketPdf < Prawn::Document move_up 180 end - def draw_fourth_square; end + def draw_fourth_square + x = @mid_horizontal + (@right - @mid_horizontal - 180) / 2 + y = cursor - (bounds.top - @mid_vertical - 180) / 2 + print_qr_code(@physical_ticket.token, pos: [x, y], extent: 180, stroke: false) + end end diff --git a/app/views/physical_ticket/show.html.haml b/app/views/physical_ticket/show.html.haml index 067f0a9b..40513ae8 100644 --- a/app/views/physical_ticket/show.html.haml +++ b/app/views/physical_ticket/show.html.haml @@ -67,6 +67,7 @@ = @physical_ticket.ticket_purchase.id %br .col-md-5.col-md-offset-2.box.well + = image_tag(@qrcode_image.to_data_url, class: 'img-responsive qr-image') .row .col-md-12 %p.text-left From 157c270497356ad17e8c18a0946025aba9d5c108 Mon Sep 17 00:00:00 2001 From: siddhantbajaj Date: Sat, 12 Aug 2017 01:27:16 +0530 Subject: [PATCH 55/74] Registration tickets to be set for registration period Admin must create at least one registration ticket before creating registration period. --- app/models/admin_ability.rb | 5 ++++- app/models/conference.rb | 6 +++++- app/views/admin/registration_periods/show.html.haml | 11 +++++++++-- .../admin/registration_periods_controller_spec.rb | 2 +- spec/factories/tickets.rb | 3 +++ spec/features/organization_admin_ability_spec.rb | 1 + spec/features/organizer_ability_spec.rb | 1 + spec/features/registration_periods_spec.rb | 1 + spec/models/admin_ability_spec.rb | 1 + spec/models/registration_period_spec.rb | 1 + 10 files changed, 27 insertions(+), 5 deletions(-) diff --git a/app/models/admin_ability.rb b/app/models/admin_ability.rb index f742b775..39183543 100644 --- a/app/models/admin_ability.rb +++ b/app/models/admin_ability.rb @@ -123,7 +123,10 @@ class AdminAbility can :manage, Commercial, commercialable_type: 'Conference', commercialable_id: conf_ids can :manage, Registration, conference_id: conf_ids - can :manage, RegistrationPeriod, conference_id: conf_ids + can :manage, RegistrationPeriod do |registration_period| + conference = registration_period.conference + conf_ids.include?(conference.id) && conference.tickets.for_registration.any? + end can :manage, Booth, conference_id: conf_ids can :manage, Question, conference_id: conf_ids can :manage, Question do |question| diff --git a/app/models/conference.rb b/app/models/conference.rb index 729db645..919bf7e5 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -26,7 +26,11 @@ class Conference < ActiveRecord::Base has_many :ticket_purchases, dependent: :destroy has_many :payments, dependent: :destroy has_many :supporters, through: :ticket_purchases, source: :user - has_many :tickets, dependent: :destroy + has_many :tickets, dependent: :destroy do + def for_registration + where(registration_ticket: true) + end + end has_many :resources, dependent: :destroy has_many :booths, dependent: :destroy diff --git a/app/views/admin/registration_periods/show.html.haml b/app/views/admin/registration_periods/show.html.haml index 057a7a37..e923d089 100644 --- a/app/views/admin/registration_periods/show.html.haml +++ b/app/views/admin/registration_periods/show.html.haml @@ -25,5 +25,12 @@ = link_to 'Delete', admin_conference_registration_period_path, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger' - else - - if can? :create, @conference.build_registration_period - = link_to 'New Registration Period', new_admin_conference_registration_period_path, class: 'btn btn-primary' + - unless @conference.tickets.for_registration.empty? + - if can? :create, @conference.build_registration_period + = link_to 'New Registration Period', new_admin_conference_registration_period_path, class: 'btn btn-primary' + - else + .h3.text-left + No Registration Tickets! + %small + = link_to 'Create registration tickets', new_admin_conference_ticket_path + before creating the registration period. diff --git a/spec/controllers/admin/registration_periods_controller_spec.rb b/spec/controllers/admin/registration_periods_controller_spec.rb index 6177fde7..fd98ce77 100644 --- a/spec/controllers/admin/registration_periods_controller_spec.rb +++ b/spec/controllers/admin/registration_periods_controller_spec.rb @@ -5,7 +5,7 @@ describe Admin::RegistrationPeriodsController do # It is necessary to use bang version of let to build roles before user let(:conference) { create(:conference) } let!(:organizer_role) { Role.find_by(name: 'organizer', resource: conference) } - + let!(:registration_ticket) { create(:registration_ticket, conference: conference) } let(:organizer) { create(:user, role_ids: organizer_role.id) } let(:organizer2) { create(:user, email: 'organizer2@email.osem', role_ids: organizer_role.id) } let(:participant) { create(:user) } diff --git a/spec/factories/tickets.rb b/spec/factories/tickets.rb index 88532b86..c4bb1539 100644 --- a/spec/factories/tickets.rb +++ b/spec/factories/tickets.rb @@ -3,5 +3,8 @@ FactoryGirl.define do title { "#{Faker::Hipster.word} Ticket" } price_cents 1000 price_currency 'USD' + factory :registration_ticket do + registration_ticket true + end end end diff --git a/spec/features/organization_admin_ability_spec.rb b/spec/features/organization_admin_ability_spec.rb index 8bccd2cf..d84a7c01 100644 --- a/spec/features/organization_admin_ability_spec.rb +++ b/spec/features/organization_admin_ability_spec.rb @@ -5,6 +5,7 @@ feature 'Has correct abilities' do let(:conference) { create(:full_conference, organization: organization) } let(:role_organization_admin) { Role.find_by(name: 'organization_admin', resource: organization) } let(:user_organization_admin) { create(:user, role_ids: [role_organization_admin.id]) } + let!(:registration_ticket) { create(:registration_ticket, conference: conference) } context 'when user is organization_admin' do before do diff --git a/spec/features/organizer_ability_spec.rb b/spec/features/organizer_ability_spec.rb index 5345b58c..0127ea2f 100644 --- a/spec/features/organizer_ability_spec.rb +++ b/spec/features/organizer_ability_spec.rb @@ -8,6 +8,7 @@ feature 'Has correct abilities' do let(:role_organizer_conf) { Role.find_by(name: 'organizer', resource: conference) } let(:role_organizer_other_conf) { Role.find_by(name: 'organizer', resource: other_conference) } let(:user_organizer) { create(:user, role_ids: [role_organizer_conf.id, role_organizer_other_conf.id]) } + let!(:registration_ticket) { create(:registration_ticket, conference: conference) } context 'when user is organizer' do before do diff --git a/spec/features/registration_periods_spec.rb b/spec/features/registration_periods_spec.rb index c6cc992e..5c1c90b5 100644 --- a/spec/features/registration_periods_spec.rb +++ b/spec/features/registration_periods_spec.rb @@ -6,6 +6,7 @@ feature RegistrationPeriod do let!(:conference) { create(:conference) } let!(:organizer_role) { Role.find_by(name: 'organizer', resource: conference) } let!(:organizer) { create(:user, email: 'admin@example.com', role_ids: [organizer_role.id]) } + let!(:registration_ticket) { create(:registration_ticket, conference: conference) } shared_examples 'successfully' do scenario 'create and update registration period', js: true do diff --git a/spec/models/admin_ability_spec.rb b/spec/models/admin_ability_spec.rb index 126d32de..805dd621 100644 --- a/spec/models/admin_ability_spec.rb +++ b/spec/models/admin_ability_spec.rb @@ -11,6 +11,7 @@ describe 'User with admin role' do let!(:organization) { create(:organization) } let!(:my_conference) { create(:full_conference, organization: organization) } + let!(:registration_ticket) { create(:registration_ticket, conference: my_conference) } let(:my_venue) { my_conference.venue || create(:venue, conference: my_conference) } let(:my_registration) { create(:registration, conference: my_conference, user: admin) } diff --git a/spec/models/registration_period_spec.rb b/spec/models/registration_period_spec.rb index ef28bab1..52fb3755 100644 --- a/spec/models/registration_period_spec.rb +++ b/spec/models/registration_period_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe RegistrationPeriod do let!(:conference) { create(:conference, start_date: Date.today, end_date: Date.today + 6) } + let!(:registration_ticket) { create(:registration_ticket, conference: conference) } let!(:registration_period) { create(:registration_period, start_date: Date.today - 2, end_date: Date.today - 1, conference: conference) } describe 'validations' do From db8fdb3d387ad70671f6b262f5f1d301990dfce7 Mon Sep 17 00:00:00 2001 From: shlok007 Date: Tue, 22 Aug 2017 17:24:50 +0530 Subject: [PATCH 56/74] add permissions and actions to assign and unassign organization admin role --- .../admin/organizations_controller.rb | 42 +++++++++++++++++++ app/models/admin_ability.rb | 8 ++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/app/controllers/admin/organizations_controller.rb b/app/controllers/admin/organizations_controller.rb index 087bf7f8..d125c5cf 100644 --- a/app/controllers/admin/organizations_controller.rb +++ b/app/controllers/admin/organizations_controller.rb @@ -1,6 +1,7 @@ module Admin class OrganizationsController < Admin::BaseController load_and_authorize_resource :organization + before_action :verify_user, only: [:assign_org_admins, :unassign_org_admins] def index @organizations = Organization.all @@ -43,8 +44,49 @@ module Admin end end + def assign_org_admins + if @user.has_role? 'organization_admin', @organization + flash[:error] = "User #{@user.email} already has the role organization admin" + elsif @user.add_role 'organization_admin', @organization + flash[:notice] = "Successfully added role organization admin to user #{@user.email}" + else + flash[:error] = "Coud not add role organization admin to #{@user.email}" + end + + redirect_to admins_admin_organization_path(@organization) + end + + def unassign_org_admins + if @user.remove_role 'organization_admin', @organization + flash[:notice] = "Successfully removed role organization admin from user #{@user.email}" + else + flash[:error] = "Could not remove role organization admin from user #{@user.email}" + end + + redirect_to admins_admin_organization_path(@organization) + end + + def admins + @role = @organization.roles.first + @users = @role.users + render 'show_org_admins' + end + private + def user_params + params.require(:user).permit(:email) + end + + def verify_user + @user = User.find_by(email: user_params[:email]) + unless @user + redirect_to admins_admin_organization_path(@organization), + error: 'Could not find user. Please provide a valid email!' + return + end + end + def organization_params params.require(:organization).permit(:name, :description, :picture) end diff --git a/app/models/admin_ability.rb b/app/models/admin_ability.rb index 39183543..cfe17d1d 100644 --- a/app/models/admin_ability.rb +++ b/app/models/admin_ability.rb @@ -28,7 +28,7 @@ class AdminAbility conference.registration_open? && !conference.registration_limit_exceeded? || conference.program.speakers.confirmed.include?(user) end - can :index, Organization + can [:index, :admins], Organization can :index, Ticket can :manage, TicketPurchase, user_id: user.id can [:new, :create], Payment, user_id: user.id @@ -96,13 +96,11 @@ class AdminAbility org_ids_for_organization_admin = Organization.with_role(:organization_admin, user).pluck(:id) conf_ids_for_organization_admin = Conference.where(organization_id: org_ids_for_organization_admin).pluck(:id) - can [:read, :update, :destroy], Organization, id: org_ids_for_organization_admin + can [:read, :update, :destroy, :assign_org_admins, :unassign_org_admins, :admins], Organization, id: org_ids_for_organization_admin can :new, Conference can :manage, Conference, organization_id: org_ids_for_organization_admin can [:index, :show], Role - can [:edit, :update], Role do |role| - role.resource_type == 'Organization' && (org_ids_for_organization_admin.include? role.resource_id) - end + signed_in_with_organizer_role(user, conf_ids_for_organization_admin) end From a91c67be0afa368e5ed0d5a262603ca84cbb7b30 Mon Sep 17 00:00:00 2001 From: shlok007 Date: Tue, 22 Aug 2017 17:25:34 +0530 Subject: [PATCH 57/74] add views for organization admin role --- .haml-lint_todo.yml | 1 + .../_users_with_org_admin_role.haml | 25 +++++++++++++++++++ app/views/admin/organizations/index.html.haml | 2 ++ .../admin/organizations/show_org_admins.haml | 24 ++++++++++++++++++ config/routes.rb | 8 +++++- 5 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 app/views/admin/organizations/_users_with_org_admin_role.haml create mode 100644 app/views/admin/organizations/show_org_admins.haml diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 2e4c2995..25ccb622 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -78,6 +78,7 @@ linters: - "app/views/admin/resources/show.html.haml" - "app/views/admin/roles/_form.html.haml" - "app/views/admin/roles/_users.html.haml" + - "app/views/admin/roles/_users_with_org_admin_role.haml" - "app/views/admin/roles/index.html.haml" - "app/views/admin/roles/show.html.haml" - "app/views/admin/rooms/_form.html.haml" diff --git a/app/views/admin/organizations/_users_with_org_admin_role.haml b/app/views/admin/organizations/_users_with_org_admin_role.haml new file mode 100644 index 00000000..e107868c --- /dev/null +++ b/app/views/admin/organizations/_users_with_org_admin_role.haml @@ -0,0 +1,25 @@ +.page-header + %h3 Users (#{users.length}) +- if users.present? + %table.table.table-striped.table-bordered.table-hover.datatable#users + %thead + %th Name + %th Email + - if ( can? :unassign_org_admins, organization ) + %th + Actions + %tbody + - users.each do |user| + %tr + %td= user.name + %td= user.email + - if ( can? :unassign_org_admins, organization ) + %td + = link_to 'Remove from organization admin', + unassign_org_admins_admin_organization_path(organization.id, + role.name, + user: {email: user.email}), + method: :delete, + class: 'btn btn-danger' +- else + %h5 No users found! diff --git a/app/views/admin/organizations/index.html.haml b/app/views/admin/organizations/index.html.haml index aa682c57..baf9bc80 100644 --- a/app/views/admin/organizations/index.html.haml +++ b/app/views/admin/organizations/index.html.haml @@ -25,6 +25,8 @@ = organization.conferences.past.count %td .btn-group + = link_to 'Admins', admins_admin_organization_path(organization), + method: :get, class: 'btn btn-success' = link_to 'Edit', edit_admin_organization_path(organization), method: :get, class: 'btn btn-primary' = link_to 'Delete', admin_organization_path(organization), diff --git a/app/views/admin/organizations/show_org_admins.haml b/app/views/admin/organizations/show_org_admins.haml new file mode 100644 index 00000000..7fe1f943 --- /dev/null +++ b/app/views/admin/organizations/show_org_admins.haml @@ -0,0 +1,24 @@ +.row + .col-md-12 + .page-header + %h2 + Organization admins for #{@organization.name} + .text-muted + = @role.description + +.row.col-md-3 + - if ( can? :assign_org_admins, @organization ) + = semantic_form_for :user, + url: assign_org_admins_admin_organization_path(@organization, + @role.name), method: :post do |u| + + = u.label 'Add user by email: ' + .input-group + = u.input :email, label: false, placeholder: "User's email" + .input-group-btn + = u.submit 'Add', id: 'user-add', class: 'btn btn-primary' + +.row + .col-md-12 + = render partial: 'users_with_org_admin_role', + locals: { users: @users, organization: @organization, role: @role } diff --git a/config/routes.rb b/config/routes.rb index 523a076d..3b955953 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,7 +20,13 @@ Osem::Application.routes.draw do end namespace :admin do - resources :organizations + resources :organizations do + member do + get :admins + post :assign_org_admins + delete :unassign_org_admins + end + end resources :users do member do patch :toggle_confirmation From c596a9fda7a541f769d12d402eeb7f88201e8b38 Mon Sep 17 00:00:00 2001 From: shlok007 Date: Tue, 22 Aug 2017 17:26:11 +0530 Subject: [PATCH 58/74] add tests to assign and unassign organization admin role --- .../admin/organizations_controller_spec.rb | 27 +++++++++++ .../admin/roles_controller_spec.rb | 1 - spec/features/roles_spec.rb | 45 +++++++++++++++++++ spec/models/admin_ability_spec.rb | 18 +++++++- 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/spec/controllers/admin/organizations_controller_spec.rb b/spec/controllers/admin/organizations_controller_spec.rb index abe5005b..801cce83 100644 --- a/spec/controllers/admin/organizations_controller_spec.rb +++ b/spec/controllers/admin/organizations_controller_spec.rb @@ -167,5 +167,32 @@ describe Admin::OrganizationsController do end end end + + describe 'POST #assign_org_admins' do + let(:org_admin_role) { Role.find_by(name: 'organization_admin', resource: organization) } + + before do + post :assign_org_admins, id: organization.id, + user: { email: user.email } + end + + it 'assigns organization_admin role' do + expect(user.roles).to eq [org_admin_role] + end + end + + describe 'DELETE #unassign_org_admins' do + let(:org_admin_role) { Role.find_by(name: 'organization_admin', resource: organization) } + let!(:org_admin_user) { create(:user, role_ids: [org_admin_role.id]) } + + before do + delete :unassign_org_admins, id: organization.id, + user: { email: org_admin_user.email } + end + + it 'unassigns organization_admin role' do + expect(org_admin_user.reload.roles).to eq [] + end + end end end diff --git a/spec/controllers/admin/roles_controller_spec.rb b/spec/controllers/admin/roles_controller_spec.rb index d5cdc67e..dcc81313 100644 --- a/spec/controllers/admin/roles_controller_spec.rb +++ b/spec/controllers/admin/roles_controller_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Admin::RolesController do - let(:conference) { create(:conference) } let(:organizer_role) { Role.find_by(name: 'organizer', resource: conference) } let(:cfp_role) { Role.find_by(name: 'cfp', resource: conference) } diff --git a/spec/features/roles_spec.rb b/spec/features/roles_spec.rb index ebdeacaf..ba9e5d4c 100644 --- a/spec/features/roles_spec.rb +++ b/spec/features/roles_spec.rb @@ -98,6 +98,51 @@ feature Role do end end + context 'organization_admin' do + let!(:organization) { create(:organization) } + let!(:org_admin_role) { Role.find_by(name: 'organization_admin', resource: organization) } + let!(:organization_admin) { create(:user, role_ids: [org_admin_role.id]) } + let(:user_with_no_role) { create :user } + let!(:other_organization) { create(:organization) } + + before do + sign_in organization_admin + visit admin_organizations_path + end + + context 'for the organization it belongs to' do + scenario 'successfully adds role organization_admin' do + click_link('Admins', href: admins_admin_organization_path(organization.id)) + + fill_in 'user_email', with: user_with_no_role.email + click_button 'Add' + user_with_no_role.reload + + expect(user_with_no_role.has_role?('organization_admin', organization)).to eq true + end + + scenario 'successfully removes role organization_admin' do + click_link('Admins', href: admins_admin_organization_path(organization.id)) + + first('tr').find('.btn-danger').click + expect(organization_admin.has_role?('organization_admin', organization)).to eq false + end + end + + context 'for the organizations it does not belong to' do + scenario 'does not successfully add role organization_admin' do + click_link('Admins', href: admins_admin_organization_path(other_organization.id)) + + expect(page.has_field?('user_email')).to eq false + end + + scenario 'does not successfully removes role organization_admin' do + click_link('Admins', href: admins_admin_organization_path(other_organization.id)) + expect(page.has_css?('.btn-danger')).to eq false + end + end + end + context 'organizer' do Role.all.each.map(&:name).each do |role| it_behaves_like 'successfully', role, 'organizer' diff --git a/spec/models/admin_ability_spec.rb b/spec/models/admin_ability_spec.rb index 805dd621..f148b2ae 100644 --- a/spec/models/admin_ability_spec.rb +++ b/spec/models/admin_ability_spec.rb @@ -63,7 +63,7 @@ describe 'User with admin role' do it{ should_not be_able_to(:update, Role.find_by(name: 'organization_admin', resource: other_organization)) } it{ should_not be_able_to(:edit, Role.find_by(name: 'organization_admin', resource: other_organization)) } - it{ should_not be_able_to(:show, Role.find_by(name: 'organization_admin', resource: other_organization)) } + it{ should be_able_to(:admins, organization) } it{ should_not be_able_to(:new, User.new) } it{ should_not be_able_to(:create, User.new) } @@ -127,6 +127,8 @@ describe 'User with admin role' do let(:other_organization) { create(:organization) } let(:other_conference) { create(:conference, organization: other_organization) } + it{ should be_able_to(:assign_org_admins, organization) } + it{ should be_able_to(:unassign_org_admins, organization) } it{ should be_able_to(:manage, my_conference) } it{ should be_able_to(:read, organization) } it{ should be_able_to(:update, organization) } @@ -137,6 +139,8 @@ describe 'User with admin role' do it{ should_not be_able_to(:create, Conference.new(organization_id: other_organization.id)) } it{ should_not be_able_to(:new, Organization.new) } it{ should_not be_able_to(:create, Organization.new) } + + it_behaves_like 'user with any role' end context 'when user has the role organizer' do @@ -214,6 +218,9 @@ describe 'User with admin role' do it{ should be_able_to(:manage, resource) } + it{ should_not be_able_to(:assign_org_admins, organization) } + it{ should_not be_able_to(:unassign_org_admins, organization) } + %w[organizer cfp info_desk volunteers_coordinator].each do |role| it{ should be_able_to(:toggle_user, Role.find_by(name: role, resource: my_conference)) } it{ should be_able_to(:edit, Role.find_by(name: role, resource: my_conference)) } @@ -299,6 +306,8 @@ describe 'User with admin role' do it{ should be_able_to(:index, resource) } it{ should be_able_to(:show, resource) } it{ should be_able_to(:update, resource) } + it{ should_not be_able_to(:assign_org_admins, organization) } + it{ should_not be_able_to(:unassign_org_admins, organization) } it_behaves_like 'user with any role' it_behaves_like 'user with non-organizer role', 'cfp' @@ -366,6 +375,8 @@ describe 'User with admin role' do it{ should be_able_to(:index, resource) } it{ should be_able_to(:show, resource) } it{ should be_able_to(:update, resource) } + it{ should_not be_able_to(:assign_org_admins, organization) } + it{ should_not be_able_to(:unassign_org_admins, organization) } it_behaves_like 'user with any role' it_behaves_like 'user with non-organizer role', 'info_desk' @@ -433,6 +444,8 @@ describe 'User with admin role' do it{ should be_able_to(:index, resource) } it{ should be_able_to(:show, resource) } it{ should be_able_to(:update, resource) } + it{ should_not be_able_to(:assign_org_admins, organization) } + it{ should_not be_able_to(:unassign_org_admins, organization) } it 'should be_able to :manage Vposition' it 'should be_able to :manage Vday' @@ -509,6 +522,9 @@ describe 'User with admin role' do it{ should_not be_able_to(:edit, my_self_organized_track) } it{ should_not be_able_to(:update, my_self_organized_track) } + it{ should_not be_able_to(:assign_org_admins, organization) } + it{ should_not be_able_to(:unassign_org_admins, organization) } + it_behaves_like 'user with any role' it_behaves_like 'user with non-organizer role', 'track_organizer' end From 48accd02aefbbfa8eadfb47523a1d23e28332a0a Mon Sep 17 00:00:00 2001 From: rahul Date: Tue, 22 Aug 2017 21:41:13 +0530 Subject: [PATCH 59/74] Fix warning in rspec --- spec/models/registration_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/registration_spec.rb b/spec/models/registration_spec.rb index 25ec9888..e7824c08 100644 --- a/spec/models/registration_spec.rb +++ b/spec/models/registration_spec.rb @@ -20,7 +20,7 @@ describe 'Registration' do describe 'registration_limit_not_exceed' do it 'is not valid when limit exceeded' do conference.registration_limit = 1 - expect { create(:registration, conference: conference, user: user) }.to raise_error + expect { create(:registration, conference: conference, user: user) }.to raise_error('Validation failed: User already Registered!, Registration limit exceeded') expect(user.registrations.size).to be 1 end end From 1a5aa14e2cb825ed79af4567cce5a2bf2790211b Mon Sep 17 00:00:00 2001 From: shlok007 Date: Sun, 13 Aug 2017 02:31:46 +0530 Subject: [PATCH 60/74] Mention organization name while creating a conference --- app/controllers/admin/conferences_controller.rb | 3 ++- app/views/admin/conferences/new.html.haml | 1 + spec/features/conference_spec.rb | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/controllers/admin/conferences_controller.rb b/app/controllers/admin/conferences_controller.rb index 3905012c..d8c101d1 100644 --- a/app/controllers/admin/conferences_controller.rb +++ b/app/controllers/admin/conferences_controller.rb @@ -72,11 +72,12 @@ module Admin def new @conference = Conference.new + @organizations = Organization.accessible_by(current_ability, :update).pluck(:name, :id) end def create @conference = Conference.new(conference_params) - @conference.organization = Organization.find_or_create_by(name: 'organization') + if @conference.save # user that creates the conference becomes organizer of that conference current_user.add_role :organizer, @conference diff --git a/app/views/admin/conferences/new.html.haml b/app/views/admin/conferences/new.html.haml index 014758de..9e115457 100644 --- a/app/views/admin/conferences/new.html.haml +++ b/app/views/admin/conferences/new.html.haml @@ -2,6 +2,7 @@ .col-md-8 = semantic_form_for(@conference, url: admin_conferences_path) do |f| = f.inputs 'Basic Information' do + = f.input :organization, as: :select, collection: @organizations = f.input :title, hint: "The name of your conference as it shall appear throughout the site. Example: 'OpenSUSE Conference 2013'", input_html: { required: 'required' } = f.input :short_title, hint: "A short and unique handle for your conference, using only letters, numbers, underscores, and dashes. This will be used to identify your conference in URLs etc. Example: 'froscon2011'", diff --git a/spec/features/conference_spec.rb b/spec/features/conference_spec.rb index f3205e07..0dc4e228 100644 --- a/spec/features/conference_spec.rb +++ b/spec/features/conference_spec.rb @@ -2,13 +2,15 @@ require 'spec_helper' feature Conference do let!(:user) { create(:admin) } - + let!(:organization) { create(:organization) } shared_examples 'add and update conference' do scenario 'adds a new conference', feature: true, js: true do expected_count = Conference.count + 1 sign_in user visit new_admin_conference_path + + select organization.name, from: 'conference_organization_id' fill_in 'conference_title', with: 'Example Con' fill_in 'conference_short_title', with: 'ExCon' @@ -27,7 +29,7 @@ feature Conference do expect(flash) .to eq('Conference was successfully created.') expect(Conference.count).to eq(expected_count) - + expect(Conference.last.organization).to eq(organization) expect(user.has_role? :organizer, Conference.last).to eq(true) end From 0ac5d18ef27e5658aa290e1ec80f3d03fb0f90a5 Mon Sep 17 00:00:00 2001 From: shlok007 Date: Sat, 22 Jul 2017 12:34:48 +0530 Subject: [PATCH 61/74] route to conference#show for custom domain --- app/controllers/conferences_controller.rb | 9 ++++++++- config/initializers/domain_constraint.rb | 6 ++++++ config/routes.rb | 4 ++++ ...0170721184810_add_custom_domain_to_conferences.rb | 5 +++++ db/schema.rb | 12 +++++++----- 5 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 config/initializers/domain_constraint.rb create mode 100644 db/migrate/20170721184810_add_custom_domain_to_conferences.rb diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb index 53e5bd6d..15a59a0c 100644 --- a/app/controllers/conferences_controller.rb +++ b/app/controllers/conferences_controller.rb @@ -9,10 +9,17 @@ class ConferencesController < ApplicationController @antiquated = @conferences - @current end - def show; end + def show + # have to change "localhost" to ENV['OSEM_HOSTNAME'] in production + check_custom_domain if request.host != 'localhost' + end private + def check_custom_domain + @conference = @conference.custom_domain.present? ? Conference.find_by(custom_domain: request.domain) : @conference + end + def respond_to_options respond_to do |format| format.html { head :ok } diff --git a/config/initializers/domain_constraint.rb b/config/initializers/domain_constraint.rb new file mode 100644 index 00000000..67e19a75 --- /dev/null +++ b/config/initializers/domain_constraint.rb @@ -0,0 +1,6 @@ +class DomainConstraint + def self.matches?(request) + @domains = Conference.pluck(:custom_domain).compact + @domains.include?(request.domain) + end +end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 3b955953..c4a5f6ce 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,9 @@ Osem::Application.routes.draw do + constraints DomainConstraint do + get '/', to: 'conferences#show' + end + if ENV['OSEM_ICHAIN_ENABLED'] == 'true' devise_for :users, controllers: { registrations: :registrations } else diff --git a/db/migrate/20170721184810_add_custom_domain_to_conferences.rb b/db/migrate/20170721184810_add_custom_domain_to_conferences.rb new file mode 100644 index 00000000..d5aef4fd --- /dev/null +++ b/db/migrate/20170721184810_add_custom_domain_to_conferences.rb @@ -0,0 +1,5 @@ +class AddCustomDomainToConferences < ActiveRecord::Migration + def change + add_column :conferences, :custom_domain, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 83499b33..d9949b1e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,10 +11,10 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170807092805) do +ActiveRecord::Schema.define(version: 20170816203325) do create_table "ahoy_events", force: :cascade do |t| - t.integer "visit_id" + t.uuid "visit_id", limit: 16 t.integer "user_id" t.string "name" t.text "properties" @@ -127,6 +127,7 @@ ActiveRecord::Schema.define(version: 20170807092805) do t.integer "end_hour", default: 20 t.integer "organization_id" t.integer "ticket_layout", default: 0 + t.string "custom_domain" t.integer "booth_limit", default: 0 end @@ -511,10 +512,10 @@ ActiveRecord::Schema.define(version: 20170807092805) do create_table "tickets", force: :cascade do |t| t.integer "conference_id" - t.string "title", null: false + t.string "title", null: false t.text "description" - t.integer "price_cents", default: 0, null: false - t.string "price_currency", default: "USD", null: false + t.integer "price_cents", default: 0, null: false + t.string "price_currency", default: "USD", null: false t.boolean "registration_ticket", default: false end @@ -572,6 +573,7 @@ ActiveRecord::Schema.define(version: 20170807092805) do t.boolean "is_admin", default: false t.string "username" t.boolean "is_disabled", default: false + t.string "token" end add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true From f4775f8e6ab31a05d83fafdb6eba7e025c7db826 Mon Sep 17 00:00:00 2001 From: shlok007 Date: Tue, 25 Jul 2017 07:42:50 +0530 Subject: [PATCH 62/74] fix access denied error for program in custom domains --- app/controllers/conferences_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb index 15a59a0c..6e5dd5dd 100644 --- a/app/controllers/conferences_controller.rb +++ b/app/controllers/conferences_controller.rb @@ -2,7 +2,6 @@ class ConferencesController < ApplicationController protect_from_forgery with: :null_session before_action :respond_to_options load_and_authorize_resource find_by: :short_title - load_resource :program, through: :conference, singleton: true, except: :index def index @current = Conference.where('end_date >= ?', Date.current).reorder(start_date: :asc) @@ -10,14 +9,15 @@ class ConferencesController < ApplicationController end def show - # have to change "localhost" to ENV['OSEM_HOSTNAME'] in production + # have to change "localhost" to ENV['OSEM_HOSTNAME'] in production check_custom_domain if request.host != 'localhost' + @program = @conference.program end private def check_custom_domain - @conference = @conference.custom_domain.present? ? Conference.find_by(custom_domain: request.domain) : @conference + @conference = @conference.nil? ? Conference.find_by(custom_domain: request.domain) : @conference end def respond_to_options From 66b48be968a0cb9d6519278fcba5c55117127c03 Mon Sep 17 00:00:00 2001 From: shlok007 Date: Wed, 26 Jul 2017 09:04:07 +0530 Subject: [PATCH 63/74] controller tests for custom domain --- spec/controllers/conferences_controller_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/controllers/conferences_controller_spec.rb b/spec/controllers/conferences_controller_spec.rb index 6479eb34..dffae77a 100644 --- a/spec/controllers/conferences_controller_spec.rb +++ b/spec/controllers/conferences_controller_spec.rb @@ -23,6 +23,15 @@ describe ConferencesController do get :show, id: conference.short_title expect(response).to render_template :show end + + it 'assigns correct conference from a custom domain' do + conference.update_attribute(:custom_domain, 'lvh.me') + @request.host = 'lvh.me' + + get :show + expect(response).to render_template :show + expect(assigns(:conference)).to eq conference + end end end From 835859e350ab41d4a93b077412c84cf2b0c37010 Mon Sep 17 00:00:00 2001 From: shlok007 Date: Wed, 2 Aug 2017 20:16:50 +0530 Subject: [PATCH 64/74] refactored DomainConstraint and conference_controller_spec use params id instead of OSEM_HOSTNAME to load conference --- app/controllers/conferences_controller.rb | 14 +++++++++----- config/initializers/domain_constraint.rb | 6 +++--- spec/controllers/conferences_controller_spec.rb | 7 ++++++- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb index 6e5dd5dd..f141529d 100644 --- a/app/controllers/conferences_controller.rb +++ b/app/controllers/conferences_controller.rb @@ -1,7 +1,7 @@ class ConferencesController < ApplicationController protect_from_forgery with: :null_session before_action :respond_to_options - load_and_authorize_resource find_by: :short_title + load_and_authorize_resource find_by: :short_title, except: :show def index @current = Conference.where('end_date >= ?', Date.current).reorder(start_date: :asc) @@ -9,15 +9,19 @@ class ConferencesController < ApplicationController end def show - # have to change "localhost" to ENV['OSEM_HOSTNAME'] in production - check_custom_domain if request.host != 'localhost' + @conference = if params[:id] + Conference.find_by_short_title(params[:id]) + else + load_conference_by_domain + end + authorize! :show, @conference @program = @conference.program end private - def check_custom_domain - @conference = @conference.nil? ? Conference.find_by(custom_domain: request.domain) : @conference + def load_conference_by_domain + Conference.find_by(custom_domain: request.domain) end def respond_to_options diff --git a/config/initializers/domain_constraint.rb b/config/initializers/domain_constraint.rb index 67e19a75..5b35e5c7 100644 --- a/config/initializers/domain_constraint.rb +++ b/config/initializers/domain_constraint.rb @@ -1,6 +1,6 @@ class DomainConstraint def self.matches?(request) - @domains = Conference.pluck(:custom_domain).compact - @domains.include?(request.domain) + domains = Conference.where.not(custom_domain: nil).pluck(:custom_domain) + domains.include?(request.domain) end -end \ No newline at end of file +end diff --git a/spec/controllers/conferences_controller_spec.rb b/spec/controllers/conferences_controller_spec.rb index dffae77a..cdbebf9f 100644 --- a/spec/controllers/conferences_controller_spec.rb +++ b/spec/controllers/conferences_controller_spec.rb @@ -23,12 +23,17 @@ describe ConferencesController do get :show, id: conference.short_title expect(response).to render_template :show end + end - it 'assigns correct conference from a custom domain' do + context 'accessing conference via custom domain' do + before do conference.update_attribute(:custom_domain, 'lvh.me') @request.host = 'lvh.me' + end + it 'assigns correct conference' do get :show + expect(response).to render_template :show expect(assigns(:conference)).to eq conference end From 834e9d96e4ac60a3c28455722d05ad2c6f2f6fda Mon Sep 17 00:00:00 2001 From: nasia Date: Fri, 11 Aug 2017 15:14:22 +0300 Subject: [PATCH 65/74] Add booths to admin sidebar --- app/controllers/admin/booths_controller.rb | 4 ++++ app/views/admin/booths/_change_state_dropdown.html.haml | 5 +++++ app/views/layouts/_admin_sidebar.html.haml | 6 +++++- config/routes.rb | 1 + 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/controllers/admin/booths_controller.rb b/app/controllers/admin/booths_controller.rb index a200e42e..a65de090 100644 --- a/app/controllers/admin/booths_controller.rb +++ b/app/controllers/admin/booths_controller.rb @@ -90,6 +90,10 @@ module Admin update_state(:cancel, 'Booth is canceled') end + def confirm + update_state(:confirm, 'Booth successfully confirmed') + end + private def update_state(transition, notice) diff --git a/app/views/admin/booths/_change_state_dropdown.html.haml b/app/views/admin/booths/_change_state_dropdown.html.haml index 12b982df..338e43f7 100644 --- a/app/views/admin/booths/_change_state_dropdown.html.haml +++ b/app/views/admin/booths/_change_state_dropdown.html.haml @@ -42,3 +42,8 @@ %li= link_to 'Cancel booth', cancel_admin_conference_booth_path(@conference.short_title, booth), method: :patch, id: "cancel_booth_#{booth.id}" + +- if booth.transition_possible? :confirm + %li= link_to 'Confirm booth', + confirm_admin_conference_booth_path(@conference.short_title, booth), + method: :patch, id: "confirm_booth_#{booth.id}" diff --git a/app/views/layouts/_admin_sidebar.html.haml b/app/views/layouts/_admin_sidebar.html.haml index 28ef62e2..1f8bf6b5 100644 --- a/app/views/layouts/_admin_sidebar.html.haml +++ b/app/views/layouts/_admin_sidebar.html.haml @@ -116,7 +116,11 @@ - if can? :update, @conference.tickets.build %li{class: active_nav_li(admin_conference_tickets_path(@conference.short_title)) } = link_to 'Tickets', admin_conference_tickets_path(@conference.short_title) - + - if can? :manage, @conference.booths + %li + = link_to admin_conference_booths_path(@conference.short_title) do + %span.fa.fa-shopping-bag + Booths - if (can? :manage, @conference.targets.build) || (can? :manage, @conference.campaigns.build) %li %a diff --git a/config/routes.rb b/config/routes.rb index c4a5f6ce..1e26807c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -57,6 +57,7 @@ Osem::Application.routes.draw do patch :reset patch :to_reject patch :cancel + patch :confirm end end From bc45531efda66c367e88e674fae6eb4576d46e9d Mon Sep 17 00:00:00 2001 From: nasia Date: Tue, 15 Aug 2017 15:46:33 +0300 Subject: [PATCH 66/74] Add My Booth Requests to user menu --- .haml-lint_todo.yml | 1 + app/views/layouts/_admin_sidebar.html.haml | 2 +- app/views/layouts/_user_menu.html.haml | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 25ccb622..49feab3d 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -153,6 +153,7 @@ linters: - "app/views/layouts/_admin_sidebar_index.html.haml" - "app/views/layouts/_messages.html.haml" - "app/views/layouts/_navigation.html.haml" + - "app/views/layouts/_user_menu.html.haml" - "app/views/layouts/application.html.haml" - "app/views/organizations/index.html.haml" - "app/views/payments/_payment.html.haml" diff --git a/app/views/layouts/_admin_sidebar.html.haml b/app/views/layouts/_admin_sidebar.html.haml index 1f8bf6b5..e8f1ccd9 100644 --- a/app/views/layouts/_admin_sidebar.html.haml +++ b/app/views/layouts/_admin_sidebar.html.haml @@ -116,7 +116,7 @@ - if can? :update, @conference.tickets.build %li{class: active_nav_li(admin_conference_tickets_path(@conference.short_title)) } = link_to 'Tickets', admin_conference_tickets_path(@conference.short_title) - - if can? :manage, @conference.booths + - if can? :manage, @conference.booths.build %li = link_to admin_conference_booths_path(@conference.short_title) do %span.fa.fa-shopping-bag diff --git a/app/views/layouts/_user_menu.html.haml b/app/views/layouts/_user_menu.html.haml index fe1e9a35..bad4c16d 100644 --- a/app/views/layouts/_user_menu.html.haml +++ b/app/views/layouts/_user_menu.html.haml @@ -16,6 +16,11 @@ = link_to(conference_program_tracks_path(@conference.short_title)) do %span.fa.fa-road My Tracks +-if @conference && @conference.program && (@conference.program.cfps.for_booths.try(:open?) || current_user.booths.where(conference_id: @conference.id).count > 0) + %li + = link_to (conference_booths_path(@conference.short_title)) do + %span.fa.fa-shopping-bag + My Booth Requests %li - if ENV['OSEM_ICHAIN_ENABLED'] == 'true' = link_to(destroy_user_ichain_session_path, method: 'delete') do From 7af712f49211fd12b1fb55b555b16d1628429148 Mon Sep 17 00:00:00 2001 From: divyanshumehta Date: Wed, 10 May 2017 16:24:20 +0530 Subject: [PATCH 67/74] Made lodging cards of same height w.r.t. to its row. Fixes #1456. --- app/views/conferences/_lodging.html.haml | 34 +++++++++++++----------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/app/views/conferences/_lodging.html.haml b/app/views/conferences/_lodging.html.haml index e94286a4..561e2421 100644 --- a/app/views/conferences/_lodging.html.haml +++ b/app/views/conferences/_lodging.html.haml @@ -12,22 +12,26 @@ = @conference.venue.city %p.lead We recommend these affordable lodging accommodations for your visit. - - @conference.lodgings.each_slice(3) do |slice| - .row.row-centered - - slice.each do |lodging| - .col-md-4.col-sm-4.ticket.col-centered.col-top - .thumbnail - - unless lodging.picture? - %p.text-center - %i.fa.fa-home.fa-5x + + .row.row-centered{ style:"display: flex; flex-wrap: wrap" } + - @conference.lodgings.each do |lodging| + .col-md-4.col-sm-4.col-centered.col-top{ style:"display:flex;" } + .thumbnail + - if lodging.picture? + -if lodging.website_link.present? + = link_to(lodging.website_link, class: 'thumbnail') do + = image_tag lodging.picture.large.url, class: 'img-responsive img-lodging' - else + = image_tag lodging.picture.large.url, class: 'img-responsive img-lodging' + - else + %p.text-center -if lodging.website_link.present? = link_to(lodging.website_link, class: 'thumbnail') do - = image_tag lodging.picture.large.url, class: 'img-responsive img-lodging' + %i.fa.fa-home.fa-5x - else - = image_tag lodging.picture.large.url, class: 'img-responsive img-lodging' - .caption - %h3.text-center - = lodging.name - -if lodging.description.present? - = markdown(lodging.description) + %i.fa.fa-home.fa-5x + .caption + %h3.text-center + = lodging.name + -if lodging.description.present? + = markdown(lodging.description) From 68788ce9fc71ecdbebaea9351612e6a5995f5d04 Mon Sep 17 00:00:00 2001 From: Nishanth Vijayan Date: Sat, 6 Aug 2016 17:22:53 +0530 Subject: [PATCH 68/74] Show conference changelog Use load_and_authorize_resource in versions controlller Add conference specifc route to revision history page Users with role can view revision_history only for the versions they have access to Handle versions where conference_id is not set (records before papertrail was introduced) --- app/controllers/admin/versions_controller.rb | 17 ++- app/helpers/application_helper.rb | 19 --- app/helpers/versions_helper.rb | 48 +++++++- app/models/admin_ability.rb | 13 +- .../versions/_object_desc_and_link.html.haml | 114 +++++++++++------- app/views/admin/versions/index.html.haml | 10 ++ app/views/layouts/_admin_sidebar.html.haml | 7 ++ .../layouts/_admin_sidebar_index.html.haml | 2 +- app/views/layouts/_user_menu.html.haml | 2 +- config/routes.rb | 2 + lib/tasks/version.rake | 24 +++- .../admin/versions_controller_spec.rb | 9 ++ spec/features/versions_spec.rb | 114 ++++++++++-------- 13 files changed, 240 insertions(+), 141 deletions(-) diff --git a/app/controllers/admin/versions_controller.rb b/app/controllers/admin/versions_controller.rb index 07108d38..8048595f 100644 --- a/app/controllers/admin/versions_controller.rb +++ b/app/controllers/admin/versions_controller.rb @@ -1,17 +1,17 @@ module Admin class VersionsController < Admin::BaseController - skip_authorization_check + load_resource :conference, find_by: :short_title + load_and_authorize_resource class: PaperTrail::Version def index - authorize! :index, PaperTrail::Version.new(item_type: 'User') - conf_ids_for_organizer = current_user.is_admin? ? Conference.pluck(:id) : Conference.with_role(:organizer, current_user).pluck(:id) - @versions = PaperTrail::Version.where(["conference_id IN (?) OR item_type = 'User'", conf_ids_for_organizer]) + @conf_ids_with_role = current_user.is_admin? ? Conference.pluck(:short_title) : Conference.with_role([:organizer, :cfp, :info_desk], current_user).pluck(:short_title) + + return if @conference.blank? + authorize! :index, PaperTrail::Version.new(conference_id: @conference.id) + @versions = @versions.where(conference_id: @conference.id) end def revert_attribute - @version = PaperTrail::Version.find(params[:id]) - authorize! :revert_attribute, @version - if params[:attribute] && @version.changeset.reject{ |_, values| values[0].blank? && values[1].blank? }.keys.include?(params[:attribute]) if @version.item[params[:attribute]] == @version.changeset[params[:attribute]][0] flash[:error] = 'The item is already in the state that you are trying to revert it back to' @@ -33,9 +33,6 @@ module Admin end def revert_object - @version = PaperTrail::Version.find(params[:id]) - authorize! :revert_object, @version - if @version.event != 'create' if @version.reify.save flash[:notice] = 'The selected change was successfully reverted' diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 0c460f26..e55656ea 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -98,25 +98,6 @@ module ApplicationHelper .reverse.sub(',', ' dna ').reverse end - # Recieves a model_name and id - # Returns nil if model_name is invalid - # Returns object in its current state if its alive - # Otherwise Returns object state just before deletion - def current_or_last_object_state(model_name, id) - return nil unless id.present? && model_name.present? - begin - object = model_name.constantize.find_by(id: id) - rescue NameError - return nil - end - - if object.nil? - object_last_version = PaperTrail::Version.where(item_type: model_name, item_id: id).last - object = object_last_version.reify if object_last_version - end - object - end - def normalize_array_length(hashmap, length) hashmap.each do |_, value| if value.length < length diff --git a/app/helpers/versions_helper.rb b/app/helpers/versions_helper.rb index e919eb79..10ccef74 100644 --- a/app/helpers/versions_helper.rb +++ b/app/helpers/versions_helper.rb @@ -2,8 +2,52 @@ module VersionsHelper ## # Groups functions related to change description ## - def link_if_alive(version, link_text, link_url) - version.item ? link_to(link_text, link_url) : link_text + def link_if_alive(version, link_text, link_url, conference) + version.item && conference ? link_to(link_text, link_url) : "#{link_text} with ID #{version.item_id}" + end + + def link_to_conference(conference_id) + return 'deleted conference' if conference_id.nil? + + conference = Conference.find_by(id: conference_id) + if conference + link_to conference.short_title, + edit_admin_conference_path(conference.short_title) + else + short_title = current_or_last_object_state('Conference', conference_id).try(:short_title) || '' + " #{short_title} with ID #{conference_id}" + end + end + + def link_to_user(user_id) + return 'Someone (probably via the console)' unless user_id + + user = User.find_by(id: user_id) + if user + link_to user.name, admin_user_path(id: user_id) + else + name = current_or_last_object_state('User', user_id).try(:name) + "#{name ? name : 'Unknown user'} with ID #{user_id}" + end + end + + # Recieves a model_name and id + # Returns nil if model_name is invalid + # Returns object in its current state if its alive + # Otherwise Returns object state just before deletion + def current_or_last_object_state(model_name, id) + return nil unless id.present? && model_name.present? + begin + object = model_name.constantize.find_by(id: id) + rescue NameError + return nil + end + + if object.nil? + object_last_version = PaperTrail::Version.where(item_type: model_name, item_id: id).last + object = object_last_version.reify if object_last_version + end + object end def subscription_change_description(version) diff --git a/app/models/admin_ability.rb b/app/models/admin_ability.rb index cfe17d1d..5c08a073 100644 --- a/app/models/admin_ability.rb +++ b/app/models/admin_ability.rb @@ -167,9 +167,8 @@ class AdminAbility role.resource_type == 'Track' && (track_ids.include? role.resource_id) end - can [:index, :revert_object, :revert_attribute], PaperTrail::Version do |version| - version.item_type == 'User' || (conf_ids.include? version.conference_id) - end + can [:index, :revert_object, :revert_attribute], PaperTrail::Version, item_type: 'User' + can [:index, :revert_object, :revert_attribute], PaperTrail::Version, conference_id: conf_ids end def signed_in_with_cfp_role(user) @@ -208,9 +207,10 @@ class AdminAbility (Conference.with_role(:cfp, user).pluck(:id).include? role.resource_id) end - can [:index, :revert_object, :revert_attribute], PaperTrail::Version, item_type: 'Event', conference_id: conf_ids_for_cfp - can [:index, :revert_object, :revert_attribute], PaperTrail::Version, item_type: 'Vote', conference_id: conf_ids_for_cfp - can [:index, :revert_object, :revert_attribute], PaperTrail::Version do |version| + can [:index, :revert_object, :revert_attribute], PaperTrail::Version, + item_type: %w(Event EventType Track DifficultyLevel EmailSettings Room Cfp Program Comment), conference_id: conf_ids_for_cfp + can [:index, :revert_object, :revert_attribute], PaperTrail::Version, + ["item_type = 'Commercial' AND conference_id IN (?) AND (object LIKE '%Event%' OR object_changes LIKE '%Event%')", conf_ids_for_cfp] do |version| version.item_type == 'Commercial' && conf_ids_for_cfp.include?(version.conference_id) && (version.object.to_s.include?('Event') || version.object_changes.to_s.include?('Event')) end @@ -244,6 +244,7 @@ class AdminAbility role.resource_type == 'Conference' && role.name == 'info_desk' && (Conference.with_role(:info_desk, user).pluck(:id).include? role.resource_id) end + can [:index, :revert_object, :revert_attribute], PaperTrail::Version, item_type: 'Registration', conference_id: conf_ids_for_info_desk end def signed_in_with_volunteers_coordinator_role(user) diff --git a/app/views/admin/versions/_object_desc_and_link.html.haml b/app/views/admin/versions/_object_desc_and_link.html.haml index d29e5f9b..c5b5e84e 100644 --- a/app/views/admin/versions/_object_desc_and_link.html.haml +++ b/app/views/admin/versions/_object_desc_and_link.html.haml @@ -1,84 +1,92 @@ +- conference = Conference.find_by(id: version.conference_id) +- conference_short_title = conference.try(:short_title) || current_or_last_object_state(version.item_type, version.item_id).try(:conference).try(:short_title) || '' + - case version.item_type - when 'UsersRole' - users_role = current_or_last_object_state(version.item_type, version.item_id) - - user = current_or_last_object_state('User', users_role.user_id) = 'role' - = link_to users_role.role.name, admin_conference_role_path(conference_id: Conference.find(version.conference_id).short_title, id: users_role.role.name) + = link_to users_role.role.name, admin_conference_role_path(conference.short_title, users_role.role.name) = version.event == 'create' ? 'to' : 'from' = 'user' - - = link_to (user.try(:name) || 'deleted user'), admin_user_path(id: users_role.user.id) + = link_to_user(users_role.user_id) - when 'Subscription', 'Registration' = 'conference' - = link_to Conference.find(version.conference_id).title, - admin_conference_registrations_path(conference_id: Conference.find(version.conference_id).short_title) + = link_to_conference(version.conference_id) - when 'Commercial' - commercial = current_or_last_object_state(version.item_type, version.item_id) - commercialable = current_or_last_object_state(commercial.commercialable_type, commercial.commercialable_id) - - commercialable_last_version = PaperTrail::Version.where(item_type: commercial.commercialable_type, item_id: commercial.commercialable_id).last - case commercial.commercialable_type - when 'Event' - commercial in - - if commercialable_last_version.item - event + = 'commercial in event' + - if commercialable && conference = link_to commercialable.title, - admin_conference_program_event_path(conference_id: Conference.find(version.conference_id).short_title, id: commercialable.id) + admin_conference_program_event_path(conference_id: conference.short_title, + id: commercialable.id) - else = commercialable.title + = "with ID #{commercialable.id}" - when 'Venue' - commercial in venue - - if commercialable_last_version.item + = 'commercial in venue' + - if commercialable && conference = link_to commercialable.name, - edit_admin_conference_venue_path(conference_id: Conference.find(version.conference_id).short_title, - id: commercialable.id, anchor: 'commercials-content') + edit_admin_conference_venue_path(conference_id: conference_short_title, + id: commercialable.id, anchor: 'commercials-content') - else = commercialable.name + = "with ID #{commercialable.id}" - when 'Conference' - = link_to 'commercial', - admin_conference_commercials_path(conference_id: Conference.find(version.conference_id).short_title) + = 'commercial in conference' + - if commercialable + = link_to commercialable.short_title, + admin_conference_commercials_path(conference_id: commercialable.short_title) + - else + = commercialable.short_title + = "with ID #{commercialable.id}" - when 'EventsRegistration', 'Comment', 'Vote', 'Event' = 'event' - object = current_or_last_object_state(version.item_type, version.item_id) - event_id = object.try(:event_id) || object.try(:commentable_id) || object.id = link_to (current_or_last_object_state('Event', event_id).try(:title) || 'deleted event'), - admin_conference_program_event_path(conference_id: Conference.find(version.conference_id).short_title, id: event_id) + admin_conference_program_event_path(conference_id: conference_short_title, id: event_id) - when 'Target' = 'target' - target = current_or_last_object_state(version.item_type, version.item_id) - = link_if_alive version, target.to_s, admin_conference_targets_path(conference_id: Conference.find(version.conference_id).short_title) + = link_if_alive version, target.to_s, admin_conference_targets_path(conference_id: conference_short_title), conference - when 'EventSchedule' - event_schedule = current_or_last_object_state(version.item_type, version.item_id) event - = link_to (current_or_last_object_state('Event', event_schedule.event_id).try(:title) || 'deleted event'), - admin_conference_program_event_path(conference_id: Conference.find(version.conference_id).short_title, id: event_schedule.event_id) + = link_to (current_or_last_object_state('Event', event_schedule.event_id).try(:title) || 'deleted'), + admin_conference_program_event_path(conference_id: conference_short_title, id: event_schedule.event_id) in = link_to "Schedule #{event_schedule.schedule_id}", - admin_conference_schedule_path(conference_id: Conference.find(version.conference_id).short_title, id: event_schedule.schedule_id) + admin_conference_schedule_path(conference_id: conference_short_title, id: event_schedule.schedule_id) - when 'Schedule' = link_if_alive version, "Schedule #{version.item_id}", - admin_conference_schedule_path(conference_id: Conference.find(version.conference_id).short_title, id: version.item_id) + admin_conference_schedule_path(conference_id: conference_short_title, id: version.item_id), + conference - when 'Conference' = 'conference' - = link_to Conference.find(version.conference_id).title, - edit_admin_conference_path(id: Conference.find(version.conference_id).short_title) + = link_to_conference(version.item_id) - when 'RegistrationPeriod' = link_if_alive version, 'registration period', - admin_conference_registration_period_path(conference_id: Conference.find(version.conference_id).short_title) + admin_conference_registration_period_path(conference_id: conference_short_title), + conference - when 'Contact' = link_if_alive version, 'contact details', - edit_admin_conference_contact_path(conference_id: Conference.find(version.conference_id).short_title) + edit_admin_conference_contact_path(conference_id: conference_short_title), + conference - when 'Booth' = 'booth' @@ -88,94 +96,108 @@ - when 'Program' = link_if_alive version, 'program', - admin_conference_program_path(conference_id: Conference.find(version.conference_id).short_title) + admin_conference_program_path(conference_id: conference_short_title), + conference - when 'Cfp' = 'cfp for' - cfp = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, cfp.cfp_type, - admin_conference_program_cfp_path(conference_id: Conference.find(version.conference_id).short_title, id: version.item_id) + admin_conference_program_cfp_path(conference_id: conference_short_title, id: version.item_id), + conference - when 'Track' = 'track' - track = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, track.name, - admin_conference_program_track_path(conference_id: Conference.find(version.conference_id).short_title, id: track.try(:short_name)) + admin_conference_program_track_path(conference_id: conference_short_title, id: track.try(:short_name)), + conference - when 'EventType' = 'event type' - event_type = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, event_type.title, - admin_conference_program_event_types_path(conference_id: Conference.find(version.conference_id).short_title) + admin_conference_program_event_types_path(conference_id: conference_short_title), + conference - when 'Role' = 'role' - role = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, role.name, - admin_conference_role_path(conference_id: Conference.find(version.conference_id).short_title, id: role.name) + admin_conference_role_path(conference_id: conference_short_title, id: role.name), + conference - when 'Venue' = 'venue' - venue = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, venue.name, - admin_conference_venue_path(conference_id: Conference.find(version.conference_id).short_title) + admin_conference_venue_path(conference_id: conference_short_title), + conference - when 'Lodging' = 'lodging' - lodging = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, lodging.name, - admin_conference_lodgings_path(conference_id: Conference.find(version.conference_id).short_title) + admin_conference_lodgings_path(conference_id: conference_short_title), + conference - when 'Room' = 'room' - room = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, room.name, - admin_conference_venue_rooms_path(conference_id: Conference.find(version.conference_id).short_title) + admin_conference_venue_rooms_path(conference_id: conference_short_title), + conference - when 'Sponsor' = 'sponsor' - sponsor = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, sponsor.name, - admin_conference_sponsors_path(conference_id: Conference.find(version.conference_id).short_title) + admin_conference_sponsors_path(conference_id: conference_short_title), + conference - when 'SponsorshipLevel' = 'sponsorship level' - sponsorship_level = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, sponsorship_level.title, - admin_conference_sponsorship_levels_path(conference_id: Conference.find(version.conference_id).short_title) + admin_conference_sponsorship_levels_path(conference_id: conference_short_title), + conference - when 'Ticket' = 'ticket' - ticket = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, ticket.title, - admin_conference_ticket_path(conference_id: Conference.find(version.conference_id).short_title, id: version.item_id) + admin_conference_ticket_path(conference_id: conference_short_title, id: version.item_id), + conference - when 'Campaign' = 'campaign' - campaign = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, campaign.name, - admin_conference_campaigns_path(conference_id: Conference.find(version.conference_id).short_title) + admin_conference_campaigns_path(conference_id: conference_short_title), + conference - when 'DifficultyLevel' = 'difficulty level' - difficulty_level = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, difficulty_level.title, - admin_conference_program_difficulty_level_path(conference_id: Conference.find(version.conference_id).short_title, id: version.item_id) + admin_conference_program_difficulty_level_path(conference_id: conference_short_title, id: version.item_id), + conference - when 'Splashpage' = link_if_alive version, 'splashpage', - admin_conference_splashpage_path(conference_id: Conference.find(version.conference_id).short_title) + admin_conference_splashpage_path(conference_id: conference_short_title), + conference - when 'EmailSettings' = link_if_alive version, 'email settings', - admin_conference_emails_path(conference_id: Conference.find(version.conference_id).short_title) + admin_conference_emails_path(conference_id: conference_short_title), + conference - when 'User' - if version.event == 'update' = 'user' - = link_to (current_or_last_object_state('User', version.item_id).try(:name) || 'deleted user'), admin_user_path(id: version.item_id) + = link_to_user(version.item_id) - unless %w(Conference Subscription Registration User).include?(version.item_type) - = "in conference" - = link_to Conference.find(version.conference_id).short_title, - edit_admin_conference_path(id: Conference.find(version.conference_id).short_title) + = 'in conference' + = link_to_conference(version.conference_id) diff --git a/app/views/admin/versions/index.html.haml b/app/views/admin/versions/index.html.haml index 69af134b..3a5113b1 100644 --- a/app/views/admin/versions/index.html.haml +++ b/app/views/admin/versions/index.html.haml @@ -1,6 +1,16 @@ .row .col-md-12 .page-header + + .dropdown.pull-right + %button.btn.btn-success.dropdown-toggle{ 'data-toggle' => 'dropdown', type: 'button' } + = @conference.nil? ? 'All Conferences' : @conference.short_title + %span.caret + %ul.dropdown-menu + %li= link_to 'All Conferences & Users', admin_revision_history_path + - @conf_ids_with_role.each do |conference_short_title| + %li= link_to conference_short_title, admin_conference_revision_history_path(conference_id: conference_short_title) + %h1 Revision History %p.text-muted Log of changes made to conferences and associated resources diff --git a/app/views/layouts/_admin_sidebar.html.haml b/app/views/layouts/_admin_sidebar.html.haml index e8f1ccd9..4385d307 100644 --- a/app/views/layouts/_admin_sidebar.html.haml +++ b/app/views/layouts/_admin_sidebar.html.haml @@ -143,8 +143,15 @@ = link_to(admin_conference_roles_path(@conference.short_title)) do %span.fa.fa-group Roles + - if can? :index, @conference.resources.new %li = link_to admin_conference_resources_path(@conference.short_title) do %span.fa.fa-pencil-square Resources + + - if can?(:index, PaperTrail::Version.new(conference_id: @conference.id, item_type: 'Event')) || can?(:index, PaperTrail::Version.new(conference_id: @conference.id, item_type: 'Registration')) + %li{:class=> active_nav_li(admin_conference_revision_history_path(@conference.short_title))} + = link_to(admin_conference_revision_history_path(@conference.short_title)) do + %span.fa.fa-history + Revision History diff --git a/app/views/layouts/_admin_sidebar_index.html.haml b/app/views/layouts/_admin_sidebar_index.html.haml index 01356f42..9a3d19f3 100644 --- a/app/views/layouts/_admin_sidebar_index.html.haml +++ b/app/views/layouts/_admin_sidebar_index.html.haml @@ -27,7 +27,7 @@ = link_to(admin_users_path) do %span.fa.fa-user Users - - if can? :index, PaperTrail::Version.new(item_type: 'User') + - if can? :index, PaperTrail::Version %li = link_to(admin_revision_history_path) do %span.fa.fa-history diff --git a/app/views/layouts/_user_menu.html.haml b/app/views/layouts/_user_menu.html.haml index bad4c16d..10d4c386 100644 --- a/app/views/layouts/_user_menu.html.haml +++ b/app/views/layouts/_user_menu.html.haml @@ -53,7 +53,7 @@ = link_to(admin_users_path) do %span.fa.fa-user Users -- if can? :index, PaperTrail::Version.new(item_type: 'User') +- if can? :index, PaperTrail::Version %li = link_to(admin_revision_history_path) do %span.fa.fa-history diff --git a/config/routes.rb b/config/routes.rb index 1e26807c..ec55b8a3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -141,6 +141,8 @@ Osem::Application.routes.draw do patch :update_conference end end + + get '/revision_history' => 'versions#index' end get '/revision_history' => 'versions#index' diff --git a/lib/tasks/version.rake b/lib/tasks/version.rake index e4005c15..a25653c6 100644 --- a/lib/tasks/version.rake +++ b/lib/tasks/version.rake @@ -1,7 +1,7 @@ namespace :data do desc 'Sets conference_id in all pre-existing PaperTrail::Version objects' task set_conference_in_versions: :environment do - + ids_with_failure = [] PaperTrail::Version.where(conference_id: nil, item_type: %w[Conference Event]).each do |version| # All pre-existing versions are either of Conference or Event if version.item_type == 'Conference' @@ -9,13 +9,25 @@ namespace :data do elsif version.item_type == 'Event' event = (version.item || version.reify || version.next.reify) - if event.try(:program) - version.update_attributes(conference_id: event.program.conference_id) - else - puts "Setting conference_id value failed for PaperTrail::Version object with ID=#{version.id}" - end + + conference_id = if event.try(:program) + event.program.conference_id + # Event had attribute conference_id before it was replaced with program_id + elsif version.changeset[:conference_id] + version.changeset[:conference_id].second + elsif version.changeset[:program_id] + version.changeset[:program_id].second + elsif version.object && (object = YAML.safe_load(version.object)) + object['conference_id'] + else + ids_with_failure << version.id + puts "Setting conference_id value failed for PaperTrail::Version object with ID=#{version.id}" + nil + end + version.update_attributes(conference_id: conference_id) end end puts 'All done!' + puts "IDs with failures: #{ids_with_failure}" if ids_with_failure.any? end end diff --git a/spec/controllers/admin/versions_controller_spec.rb b/spec/controllers/admin/versions_controller_spec.rb index 4694837d..f1400e3b 100644 --- a/spec/controllers/admin/versions_controller_spec.rb +++ b/spec/controllers/admin/versions_controller_spec.rb @@ -97,5 +97,14 @@ describe Admin::VersionsController do expect(flash[:error]).to match('Revert failed. Attribute missing or invalid') end end + + describe 'GET #index' do + it 'raises error if user is not an organizer of specified conference' do + user = create(:user) + sign_in user + get :index, conference_id: conference.short_title + expect(flash[:alert]).to match('You are not authorized to access this area.') + end + end end end diff --git a/spec/features/versions_spec.rb b/spec/features/versions_spec.rb index 11afa1d2..2f1f7f8e 100644 --- a/spec/features/versions_spec.rb +++ b/spec/features/versions_spec.rb @@ -35,23 +35,25 @@ feature 'Version' do scenario 'display changes in cfp', feature: true, versioning: true, js: true do cfp = create(:cfp, program: conference.program) cfp.update_attributes(start_date: (Date.today + 1).strftime('%d/%m/%Y'), end_date: (Date.today + 3).strftime('%d/%m/%Y')) + cfp_id = cfp.id cfp.destroy visit admin_revision_history_path - expect(page).to have_text("Someone (probably via the console) created new cfp for events in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) updated start date and end date of cfp for events in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) deleted cfp for events in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) created new cfp for events with ID #{cfp_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) updated start date and end date of cfp for events with ID #{cfp_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) deleted cfp for events with ID #{cfp_id} in conference #{conference.short_title}") end scenario 'display changes in registration_period', feature: true, versioning: true, js: true do registration_period = create(:registration_period, conference: conference) registration_period.update_attributes(start_date: (Date.today + 1).strftime('%d/%m/%Y'), end_date: (Date.today + 3).strftime('%d/%m/%Y')) + registration_period_id = registration_period.id registration_period.destroy visit admin_revision_history_path - expect(page).to have_text("Someone (probably via the console) created new registration period in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) updated start date and end date of registration period in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) deleted registration period in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) created new registration period with ID #{registration_period_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) updated start date and end date of registration period with ID #{registration_period_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) deleted registration period with ID #{registration_period_id} in conference #{conference.short_title}") end scenario 'display changes in conference', feature: true, versioning: true, js: true do @@ -61,32 +63,34 @@ feature 'Version' do visit admin_revision_history_path select '100', from: 'versionstable_length' - expect(page).to have_text('Someone (probably via the console) created new conference New Con') + expect(page).to have_text('Someone (probably via the console) created new conference NewCon') expect(page).to have_text('Someone (probably via the console) created new event type Talk in conference NewCon') expect(page).to have_text('Someone (probably via the console) created new event type Workshop in conference NewCon') - expect(page).to have_text('Someone (probably via the console) updated title and short title of conference New Con') + expect(page).to have_text('Someone (probably via the console) updated title and short title of conference NewCon') end scenario 'display changes in event_type', feature: true, versioning: true, js: true do event_type = create(:event_type, program: conference.program, name: 'Discussion') event_type.update_attributes(length: 90, maximum_abstract_length: 10000) + event_type_id = event_type.id event_type.destroy visit admin_revision_history_path - expect(page).to have_text("Someone (probably via the console) created new event type Discussion in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) updated length and maximum abstract length of event type Discussion in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) deleted event type Discussion in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) created new event type Discussion with ID #{event_type_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) updated length and maximum abstract length of event type Discussion with ID #{event_type_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) deleted event type Discussion with ID #{event_type_id} in conference #{conference.short_title}") end scenario 'display changes in lodging', feature: true, versioning: true, js: true do lodging = create(:lodging, conference: conference, name: 'Hotel XYZ') lodging.update_attributes(description: 'Nice view,close to venue', website_link: 'http://www.example.com') + lodging_id = lodging.id lodging.destroy visit admin_revision_history_path - expect(page).to have_text("Someone (probably via the console) created new lodging Hotel XYZ in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) updated description and website link of lodging Hotel XYZ in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) deleted lodging Hotel XYZ in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) created new lodging Hotel XYZ with ID #{lodging_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) updated description and website link of lodging Hotel XYZ with ID #{lodging_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) deleted lodging Hotel XYZ with ID #{lodging_id} in conference #{conference.short_title}") end scenario 'display changes in role', feature: true, versioning: true, js: true do @@ -102,12 +106,13 @@ feature 'Version' do venue = create(:venue, conference: conference) room = create(:room, venue: venue, name: 'Auditorium') room.update_attributes(size: 120) + room_id = room.id room.destroy visit admin_revision_history_path - expect(page).to have_text("Someone (probably via the console) created new room Auditorium in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) updated size of room Auditorium in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) deleted room Auditorium in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) created new room Auditorium with ID #{room_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) updated size of room Auditorium with ID #{room_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) deleted room Auditorium with ID #{room_id} in conference #{conference.short_title}") end scenario 'display changes in sponsor', feature: true, versioning: true, js: true do @@ -115,55 +120,60 @@ feature 'Version' do sponsor = create(:sponsor, conference: conference, name: 'SUSE', sponsorship_level: conference.sponsorship_levels.first) sponsor.update_attributes(website_url: 'https://www.suse.com/company/history', sponsorship_level: conference.sponsorship_levels.second) sponsor.destroy + sponsor_id = sponsor.id visit admin_revision_history_path - expect(page).to have_text("Someone (probably via the console) created new sponsor SUSE in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) updated website url and sponsorship level of sponsor SUSE in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) deleted sponsor SUSE in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) created new sponsor SUSE with ID #{sponsor_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) updated website url and sponsorship level of sponsor SUSE with ID #{sponsor_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) deleted sponsor SUSE with ID #{sponsor_id} in conference #{conference.short_title}") end scenario 'display changes in sponsorship_level', feature: true, versioning: true, js: true do sponsorship_level = create(:sponsorship_level, conference: conference) sponsorship_level.update_attributes(title: 'Gold') + sponsorship_level_id = sponsorship_level.id sponsorship_level.destroy visit admin_revision_history_path - expect(page).to have_text("Someone (probably via the console) created new sponsorship level Gold in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) updated title of sponsorship level Gold in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) deleted sponsorship level Gold in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) created new sponsorship level Gold with ID #{sponsorship_level_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) updated title of sponsorship level Gold with ID #{sponsorship_level_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) deleted sponsorship level Gold with ID #{sponsorship_level_id} in conference #{conference.short_title}") end scenario 'display changes in ticket', feature: true, versioning: true, js: true do ticket = create(:ticket, conference: conference, title: 'Gold') ticket.update_attributes(price: 50, description: 'Premium Ticket') + ticket_id = ticket.id ticket.destroy visit admin_revision_history_path - expect(page).to have_text("Someone (probably via the console) created new ticket Gold in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) updated price cents and description of ticket Gold in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) deleted ticket Gold in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) created new ticket Gold with ID #{ticket_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) updated price cents and description of ticket Gold with ID #{ticket_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) deleted ticket Gold with ID #{ticket_id} in conference #{conference.short_title}") end scenario 'display changes in track', feature: true, versioning: true, js: true do track = create(:track, program: conference.program, name: 'Distribution') track.update_attributes(description: 'Events about Linux distributions') + track_id = track.id track.destroy visit admin_revision_history_path - expect(page).to have_text("Someone (probably via the console) created new track Distribution in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) updated description of track Distribution in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) deleted track Distribution in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) created new track Distribution with ID #{track_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) updated description of track Distribution with ID #{track_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) deleted track Distribution with ID #{track_id} in conference #{conference.short_title}") end scenario 'display changes in venue', feature: true, versioning: true, js: true do venue = create(:venue, conference: conference, name: 'Example University') venue.update_attributes(website: 'www.example.com new', description: 'Just another beautiful venue') + venue_id = venue.id venue.destroy visit admin_revision_history_path - expect(page).to have_text("Someone (probably via the console) created new venue Example University in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) updated website and description of venue Example University in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) deleted venue Example University in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) created new venue Example University with ID #{venue_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) updated website and description of venue Example University with ID #{venue_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) deleted venue Example University with ID #{venue_id} in conference #{conference.short_title}") end scenario 'display changes in event', feature: true, versioning: true, js: true do @@ -209,12 +219,13 @@ feature 'Version' do scenario 'display changes in difficulty levels', feature: true, versioning: true, js: true do difficulty_level = create(:difficulty_level, program: conference.program, title: 'Expert') difficulty_level.update_attributes(description: 'Only for Experts') + difficulty_level_id = difficulty_level.id difficulty_level.destroy visit admin_revision_history_path - expect(page).to have_text("Someone (probably via the console) created new difficulty level Expert in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) updated description of difficulty level Expert in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) deleted difficulty level Expert in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) created new difficulty level Expert with ID #{difficulty_level_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) updated description of difficulty level Expert with ID #{difficulty_level_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) deleted difficulty level Expert with ID #{difficulty_level_id} in conference #{conference.short_title}") end scenario 'display changes in splashpages', feature: true, versioning: true, js: true do @@ -232,13 +243,14 @@ feature 'Version' do uncheck('Display social media') check('Make splash page public?') click_button 'Save Splashpage' + splashpage_id = conference.splashpage.id click_link 'Delete' visit admin_revision_history_path - expect(page).to have_text("#{organizer.name} created new splashpage in conference #{conference.short_title}") + expect(page).to have_text("#{organizer.name} created new splashpage with ID #{splashpage_id} in conference #{conference.short_title}") expect(page).to have_text("#{organizer.name} updated public, include program, include cfp, include venue, include tickets, include lodgings, - include sponsors and include social media of splashpage in conference #{conference.short_title}") - expect(page).to have_text("#{organizer.name} deleted splashpage in conference #{conference.short_title}") + include sponsors and include social media of splashpage with ID #{splashpage_id} in conference #{conference.short_title}") + expect(page).to have_text("#{organizer.name} deleted splashpage with ID #{splashpage_id} in conference #{conference.short_title}") end scenario 'displays users subscribe/unsubscribe to conferences', feature: true, versioning: true, js: true do @@ -249,10 +261,10 @@ feature 'Version' do PaperTrail::Version.last.item.destroy! visit admin_revision_history_path - expect(page).to have_text("#{organizer.name} subscribed to conference #{conference.title}") - expect(page).to have_text("#{organizer.name} unsubscribed from conference #{conference.title}") - expect(page).to have_text("Someone (probably via the console) subscribed #{organizer.name} to conference #{conference.title}") - expect(page).to have_text("Someone (probably via the console) unsubscribed #{organizer.name} from conference #{conference.title}") + expect(page).to have_text("#{organizer.name} subscribed to conference #{conference.short_title}") + expect(page).to have_text("#{organizer.name} unsubscribed from conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) subscribed #{organizer.name} to conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) unsubscribed #{organizer.name} from conference #{conference.short_title}") end scenario 'display changes in conference commercials', feature: true, versioning: true, js: true do @@ -312,8 +324,8 @@ feature 'Version' do Registration.last.destroy visit admin_revision_history_path - expect(page).to have_text("Someone (probably via the console) registered #{organizer.name} to conference #{conference.title}") - expect(page).to have_text("Someone (probably via the console) unregistered #{organizer.name} from conference #{conference.title}") + expect(page).to have_text("Someone (probably via the console) registered #{organizer.name} to conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) unregistered #{organizer.name} from conference #{conference.short_title}") end scenario 'display changes in event registration', feature: true, versioning: true, js: true do @@ -335,12 +347,13 @@ feature 'Version' do scenario 'display changes in target', feature: true, versioning: true, js: true do target = create(:target, conference: conference) target.update_attributes(due_date: Date.today, target_count: 1000) + target_id = target.id target.destroy visit admin_revision_history_path - expect(page).to have_text("Someone (probably via the console) created new target 1000 Submissions by #{Date.today} in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) updated due date and target count of target 1000 Submissions by #{Date.today} in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) deleted target 1000 Submissions by #{Date.today} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) created new target 1000 Submissions by #{Date.today} with ID #{target_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) updated due date and target count of target 1000 Submissions by #{Date.today} with ID #{target_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) deleted target 1000 Submissions by #{Date.today} with ID #{target_id} in conference #{conference.short_title}") end scenario 'display changes in comment', feature: true, versioning: true, js: true do @@ -376,12 +389,13 @@ feature 'Version' do scenario 'display changes in campaign', feature: true, versioning: true, js: true do campaign = create(:campaign, conference: conference, name: 'Test Campaign', utm_campaign: 'campaign') campaign.update_attributes(utm_source: 'source', utm_medium: 'medium', utm_term: 'term', utm_content: 'content') + campaign_id = campaign.id campaign.destroy visit admin_revision_history_path - expect(page).to have_text("Someone (probably via the console) created new campaign Test Campaign in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) updated utm source, utm medium, utm term and utm content of campaign Test Campaign in conference #{conference.short_title}") - expect(page).to have_text("Someone (probably via the console) deleted campaign Test Campaign in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) created new campaign Test Campaign with ID #{campaign_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) updated utm source, utm medium, utm term and utm content of campaign Test Campaign with ID #{campaign_id} in conference #{conference.short_title}") + expect(page).to have_text("Someone (probably via the console) deleted campaign Test Campaign with ID #{campaign_id} in conference #{conference.short_title}") end scenario 'display password reset requests', feature: true, versioning: true, js: true do From 16f294f3e83af0b7a6cdda65c816ed2a14836a45 Mon Sep 17 00:00:00 2001 From: shlok007 Date: Wed, 23 Aug 2017 19:53:35 +0530 Subject: [PATCH 69/74] minor inprovements and included organizations in changelog --- app/controllers/admin/versions_controller.rb | 12 +- app/helpers/paths_helper.rb | 8 - app/helpers/versions_helper.rb | 31 ++-- app/models/admin_ability.rb | 2 +- app/models/conference.rb | 4 + app/models/organization.rb | 2 + .../versions/_object_desc_and_link.html.haml | 140 +++++++++++------- app/views/admin/versions/index.html.haml | 2 +- .../admin/versions_controller_spec.rb | 50 ++++++- spec/features/cfp_ability_spec.rb | 2 +- spec/features/info_desk_ability_spec.rb | 2 +- spec/features/versions_spec.rb | 34 ++++- 12 files changed, 199 insertions(+), 90 deletions(-) diff --git a/app/controllers/admin/versions_controller.rb b/app/controllers/admin/versions_controller.rb index 8048595f..5056f3ff 100644 --- a/app/controllers/admin/versions_controller.rb +++ b/app/controllers/admin/versions_controller.rb @@ -1,14 +1,18 @@ module Admin class VersionsController < Admin::BaseController - load_resource :conference, find_by: :short_title + load_resource :conference, find_by: :short_title, only: :index load_and_authorize_resource class: PaperTrail::Version def index - @conf_ids_with_role = current_user.is_admin? ? Conference.pluck(:short_title) : Conference.with_role([:organizer, :cfp, :info_desk], current_user).pluck(:short_title) + @conferences_with_role = current_user.is_admin? ? Conference.pluck(:short_title) : Conference.with_role([:organizer, :cfp, :info_desk], current_user).pluck(:short_title) + + if current_user.has_role? :organization_admin, :any + @conferences_with_role = Organization.with_role('organization_admin', current_user).map { |org| org.conferences.pluck :short_title }.flatten + end + @conferences_with_role.uniq! return if @conference.blank? - authorize! :index, PaperTrail::Version.new(conference_id: @conference.id) - @versions = @versions.where(conference_id: @conference.id) + @versions = PaperTrail::Version.where(conference_id: @conference.id).accessible_by(current_ability) end def revert_attribute diff --git a/app/helpers/paths_helper.rb b/app/helpers/paths_helper.rb index 97de8bca..df01ab2f 100644 --- a/app/helpers/paths_helper.rb +++ b/app/helpers/paths_helper.rb @@ -2,14 +2,6 @@ module PathsHelper ## # Includes functions related to links or redirects ## - def link_to_user(user_id) - user = User.find_by(id: user_id) - if user - link_to user.name, admin_user_path(id: user_id) - else - 'Someone (probably via the console)' - end - end def active_nav_li(link) if current_page?(link) diff --git a/app/helpers/versions_helper.rb b/app/helpers/versions_helper.rb index 10ccef74..5788fe9d 100644 --- a/app/helpers/versions_helper.rb +++ b/app/helpers/versions_helper.rb @@ -6,6 +6,14 @@ module VersionsHelper version.item && conference ? link_to(link_text, link_url) : "#{link_text} with ID #{version.item_id}" end + def link_to_organization(organization_id) + return 'deleted organization' unless organization_id + + org = Organization.find_by(id: organization_id) + return current_or_last_object_state('Organization', organization_id).try(:name) unless org + org.name.to_s + end + def link_to_conference(conference_id) return 'deleted conference' if conference_id.nil? @@ -26,12 +34,12 @@ module VersionsHelper if user link_to user.name, admin_user_path(id: user_id) else - name = current_or_last_object_state('User', user_id).try(:name) + name = current_or_last_object_state('User', user_id).try(:name) || PaperTrail::Version.where(item_type: 'User', item_id: user_id).last.changeset['name'].second if PaperTrail::Version.where(item_type: 'User', item_id: user_id).any? "#{name ? name : 'Unknown user'} with ID #{user_id}" end end - # Recieves a model_name and id + # Receives a model_name and id # Returns nil if model_name is invalid # Returns object in its current state if its alive # Otherwise Returns object state just before deletion @@ -51,30 +59,31 @@ module VersionsHelper end def subscription_change_description(version) - user = current_or_last_object_state(version.item_type, version.item_id).user - user_name = user.name unless user.id.to_s == version.whodunnit + user_id = current_or_last_object_state(version.item_type, version.item_id).user_id + user_name = User.find_by(id: user_id).try(:name) || current_or_last_object_state('User', user_id).try(:name) || PaperTrail::Version.where(item_type: 'User', item_id: user_id).last.changeset[:name].second unless user_id.to_s == version.whodunnit version.event == 'create' ? "subscribed #{user_name} to" : "unsubscribed #{user_name} from" end def registration_change_description(version) if version.item_type == 'Registration' - user = current_or_last_object_state(version.item_type, version.item_id).user + user_id = current_or_last_object_state(version.item_type, version.item_id).user_id elsif version.item_type == 'EventsRegistration' registration_id = current_or_last_object_state(version.item_type, version.item_id).registration_id - user = current_or_last_object_state('Registration', registration_id).user + user_id = current_or_last_object_state('Registration', registration_id).user_id end + user_name = User.find_by(id: user_id).try(:name) || current_or_last_object_state('User', user_id).try(:name) || PaperTrail::Version.where(item_type: 'User', item_id: user_id).last.changeset[:name].second - if user.id.to_s == version.whodunnit + if user_id.to_s == version.whodunnit case version.event when 'create' then 'registered to' when 'update' then "updated #{updated_attributes(version)} of the registration for" - when 'destroy' then 'unregistered from' + when 'destroy' then 'unregistered from' end else case version.event - when 'create' then "registered #{user.name} to" - when 'update' then "updated #{updated_attributes(version)} of #{user.name}'s registration for" - when 'destroy' then "unregistered #{user.name} from" + when 'create' then "registered #{user_name} to" + when 'update' then "updated #{updated_attributes(version)} of #{user_name}'s registration for" + when 'destroy' then "unregistered #{user_name} from" end end end diff --git a/app/models/admin_ability.rb b/app/models/admin_ability.rb index 5c08a073..7fd805d1 100644 --- a/app/models/admin_ability.rb +++ b/app/models/admin_ability.rb @@ -208,7 +208,7 @@ class AdminAbility end can [:index, :revert_object, :revert_attribute], PaperTrail::Version, - item_type: %w(Event EventType Track DifficultyLevel EmailSettings Room Cfp Program Comment), conference_id: conf_ids_for_cfp + item_type: %w[Event EventType Track DifficultyLevel EmailSettings Room Cfp Program Comment], conference_id: conf_ids_for_cfp can [:index, :revert_object, :revert_attribute], PaperTrail::Version, ["item_type = 'Commercial' AND conference_id IN (?) AND (object LIKE '%Event%' OR object_changes LIKE '%Event%')", conf_ids_for_cfp] do |version| version.item_type == 'Commercial' && conf_ids_for_cfp.include?(version.conference_id) && diff --git a/app/models/conference.rb b/app/models/conference.rb index 919bf7e5..034e755b 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -760,6 +760,10 @@ class Conference < ActiveRecord::Base self end + def to_param + short_title + end + private # Returns a different html colour for every i and consecutive colors are diff --git a/app/models/organization.rb b/app/models/organization.rb index de3d4f94..0354df5b 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -1,6 +1,8 @@ class Organization < ActiveRecord::Base resourcify :roles, dependent: :delete_all + has_paper_trail + has_many :conferences, dependent: :destroy after_create :create_roles diff --git a/app/views/admin/versions/_object_desc_and_link.html.haml b/app/views/admin/versions/_object_desc_and_link.html.haml index c5b5e84e..12f9dbbc 100644 --- a/app/views/admin/versions/_object_desc_and_link.html.haml +++ b/app/views/admin/versions/_object_desc_and_link.html.haml @@ -1,17 +1,32 @@ -- conference = Conference.find_by(id: version.conference_id) -- conference_short_title = conference.try(:short_title) || current_or_last_object_state(version.item_type, version.item_id).try(:conference).try(:short_title) || '' +- unless version.item_type == 'Role' || version.item_type == 'UsersRole' + - conference = Conference.find_by(id: version.conference_id) + - conference_short_title = conference.try(:short_title) || current_or_last_object_state('Conference', version.conference_id).try(:short_title) || ' ' - case version.item_type +- when 'Organization' + organization + = link_to_organization(version.item_id) + - when 'UsersRole' - users_role = current_or_last_object_state(version.item_type, version.item_id) - = 'role' - = link_to users_role.role.name, admin_conference_role_path(conference.short_title, users_role.role.name) + - role = Role.find_by(id: users_role.role_id) if users_role + role + - if role.name == 'organization_admin' + -# organization_admin belongs to organization and not conferences + - organization = Organization.find(version.conference_id) + = link_if_alive version, role.name, + admins_admin_organization_path(organization), organization + - else + - conference = Conference.find_by(id: version.conference_id) + - conference_short_title = conference.try(:short_title) || current_or_last_object_state('Conference', version.conference_id).try(:short_title) || ' ' + = link_if_alive version, role.try(:name), admin_conference_role_path(role.try(:name) || ' ', conference_short_title), conference + = version.event == 'create' ? 'to' : 'from' - = 'user' + user = link_to_user(users_role.user_id) - when 'Subscription', 'Registration' - = 'conference' + conference = link_to_conference(version.conference_id) - when 'Commercial' @@ -20,72 +35,71 @@ - case commercial.commercialable_type - when 'Event' - = 'commercial in event' + commercial in event - if commercialable && conference = link_to commercialable.title, - admin_conference_program_event_path(conference_id: conference.short_title, - id: commercialable.id) + admin_conference_program_event_path(conference, commercialable.id) - else = commercialable.title = "with ID #{commercialable.id}" - when 'Venue' - = 'commercial in venue' + commercial in venue - if commercialable && conference = link_to commercialable.name, - edit_admin_conference_venue_path(conference_id: conference_short_title, - id: commercialable.id, anchor: 'commercials-content') + edit_admin_conference_venue_path(conference_short_title, + commercialable.id, anchor: 'commercials-content') - else = commercialable.name = "with ID #{commercialable.id}" - when 'Conference' - = 'commercial in conference' + commercial in conference - if commercialable = link_to commercialable.short_title, - admin_conference_commercials_path(conference_id: commercialable.short_title) + admin_conference_commercials_path(commercialable.short_title) - else = commercialable.short_title = "with ID #{commercialable.id}" - when 'EventsRegistration', 'Comment', 'Vote', 'Event' - = 'event' + event - object = current_or_last_object_state(version.item_type, version.item_id) - event_id = object.try(:event_id) || object.try(:commentable_id) || object.id = link_to (current_or_last_object_state('Event', event_id).try(:title) || 'deleted event'), - admin_conference_program_event_path(conference_id: conference_short_title, id: event_id) + admin_conference_program_event_path(conference_short_title, event_id) - when 'Target' - = 'target' + target - target = current_or_last_object_state(version.item_type, version.item_id) - = link_if_alive version, target.to_s, admin_conference_targets_path(conference_id: conference_short_title), conference + = link_if_alive version, target.to_s, admin_conference_targets_path(conference_short_title), conference - when 'EventSchedule' - event_schedule = current_or_last_object_state(version.item_type, version.item_id) event = link_to (current_or_last_object_state('Event', event_schedule.event_id).try(:title) || 'deleted'), - admin_conference_program_event_path(conference_id: conference_short_title, id: event_schedule.event_id) + admin_conference_program_event_path(conference_short_title, event_schedule.event_id) in = link_to "Schedule #{event_schedule.schedule_id}", - admin_conference_schedule_path(conference_id: conference_short_title, id: event_schedule.schedule_id) + admin_conference_schedule_path(conference_short_title, event_schedule.schedule_id) - when 'Schedule' = link_if_alive version, "Schedule #{version.item_id}", - admin_conference_schedule_path(conference_id: conference_short_title, id: version.item_id), + admin_conference_schedule_path(conference_short_title, version.item_id), conference - when 'Conference' - = 'conference' + conference = link_to_conference(version.item_id) - when 'RegistrationPeriod' = link_if_alive version, 'registration period', - admin_conference_registration_period_path(conference_id: conference_short_title), + admin_conference_registration_period_path(conference_short_title), conference - when 'Contact' = link_if_alive version, 'contact details', - edit_admin_conference_contact_path(conference_id: conference_short_title), + edit_admin_conference_contact_path(conference_short_title), conference - when 'Booth' @@ -96,108 +110,120 @@ - when 'Program' = link_if_alive version, 'program', - admin_conference_program_path(conference_id: conference_short_title), + admin_conference_program_path(conference_short_title), conference - when 'Cfp' - = 'cfp for' + cfp for - cfp = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, cfp.cfp_type, - admin_conference_program_cfp_path(conference_id: conference_short_title, id: version.item_id), + admin_conference_program_cfp_path(conference_short_title, version.item_id), conference - when 'Track' - = 'track' + track - track = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, track.name, - admin_conference_program_track_path(conference_id: conference_short_title, id: track.try(:short_name)), + admin_conference_program_track_path(conference_short_title, track.try(:short_name)), conference - when 'EventType' - = 'event type' + event type - event_type = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, event_type.title, - admin_conference_program_event_types_path(conference_id: conference_short_title), + admin_conference_program_event_types_path(conference_short_title), conference - when 'Role' - = 'role' + role - role = current_or_last_object_state(version.item_type, version.item_id) - = link_if_alive version, role.name, - admin_conference_role_path(conference_id: conference_short_title, id: role.name), - conference + - role_name = role.try(:name) || PaperTrail::Version.where(item_type: 'Role', item_id: version.item_id).last.changeset[:name].second + - if role_name == 'organization_admin' + -# organization_admin belongs to organization and not conferences + - organization = Organization.find(version.conference_id) + = link_if_alive version, role_name, + admins_admin_organization_path(organization), organization + - else + - conference = Conference.find_by(id: version.conference_id) + - conference_short_title = conference.try(:short_title) || current_or_last_object_state('Conference', version.conference_id).try(:short_title) || ' ' + = link_if_alive version, role_name, + admin_conference_role_path(conference_short_title, role_name), conference - when 'Venue' - = 'venue' + venue - venue = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, venue.name, - admin_conference_venue_path(conference_id: conference_short_title), + admin_conference_venue_path(conference_short_title), conference - when 'Lodging' - = 'lodging' + lodging - lodging = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, lodging.name, - admin_conference_lodgings_path(conference_id: conference_short_title), + admin_conference_lodgings_path(conference_short_title), conference - when 'Room' - = 'room' + room - room = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, room.name, - admin_conference_venue_rooms_path(conference_id: conference_short_title), + admin_conference_venue_rooms_path(conference_short_title), conference - when 'Sponsor' - = 'sponsor' + sponsor - sponsor = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, sponsor.name, - admin_conference_sponsors_path(conference_id: conference_short_title), + admin_conference_sponsors_path(conference_short_title), conference - when 'SponsorshipLevel' - = 'sponsorship level' + sponsorship level - sponsorship_level = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, sponsorship_level.title, - admin_conference_sponsorship_levels_path(conference_id: conference_short_title), + admin_conference_sponsorship_levels_path(conference_short_title), conference - when 'Ticket' - = 'ticket' + ticket - ticket = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, ticket.title, - admin_conference_ticket_path(conference_id: conference_short_title, id: version.item_id), + admin_conference_ticket_path(conference_short_title, version.item_id), conference - when 'Campaign' - = 'campaign' + campaign - campaign = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, campaign.name, - admin_conference_campaigns_path(conference_id: conference_short_title), + admin_conference_campaigns_path(conference_short_title), conference - when 'DifficultyLevel' - = 'difficulty level' + difficulty level - difficulty_level = current_or_last_object_state(version.item_type, version.item_id) = link_if_alive version, difficulty_level.title, - admin_conference_program_difficulty_level_path(conference_id: conference_short_title, id: version.item_id), + admin_conference_program_difficulty_level_path(conference_short_title, version.item_id), conference - when 'Splashpage' = link_if_alive version, 'splashpage', - admin_conference_splashpage_path(conference_id: conference_short_title), + admin_conference_splashpage_path(conference_short_title), conference - when 'EmailSettings' = link_if_alive version, 'email settings', - admin_conference_emails_path(conference_id: conference_short_title), + admin_conference_emails_path(conference_short_title), conference - when 'User' - if version.event == 'update' - = 'user' + user = link_to_user(version.item_id) -- unless %w(Conference Subscription Registration User).include?(version.item_type) - = 'in conference' - = link_to_conference(version.conference_id) +- unless %w(Conference Subscription Registration User Organization).include?(version.item_type) + - if (version.item_type == 'Role' && role_name == 'organization_admin') || (version.item_type == 'UsersRole' && role.name == 'organization_admin') + in organization + = link_to_organization(version.conference_id) + - else + in conference + = link_to_conference(version.conference_id) diff --git a/app/views/admin/versions/index.html.haml b/app/views/admin/versions/index.html.haml index 3a5113b1..963e0a57 100644 --- a/app/views/admin/versions/index.html.haml +++ b/app/views/admin/versions/index.html.haml @@ -8,7 +8,7 @@ %span.caret %ul.dropdown-menu %li= link_to 'All Conferences & Users', admin_revision_history_path - - @conf_ids_with_role.each do |conference_short_title| + - @conferences_with_role.each do |conference_short_title| %li= link_to conference_short_title, admin_conference_revision_history_path(conference_id: conference_short_title) %h1 Revision History diff --git a/spec/controllers/admin/versions_controller_spec.rb b/spec/controllers/admin/versions_controller_spec.rb index f1400e3b..f04bc33d 100644 --- a/spec/controllers/admin/versions_controller_spec.rb +++ b/spec/controllers/admin/versions_controller_spec.rb @@ -4,6 +4,9 @@ describe Admin::VersionsController do let!(:conference) { create(:conference, short_title: 'exampletitle', description: 'Example Description') } let(:admin) { create(:admin) } + let(:role_organizer) { conference.roles.find_by(name: 'organizer') } + let(:role_cfp) { conference.roles.find_by(name: 'cfp') } + let(:role_info_desk) { conference.roles.find_by(name: 'info_desk') } with_versioning do describe 'GET #revert' do @@ -99,11 +102,54 @@ describe Admin::VersionsController do end describe 'GET #index' do - it 'raises error if user is not an organizer of specified conference' do + it 'raises error if user is not of any role' do user = create(:user) sign_in user get :index, conference_id: conference.short_title - expect(flash[:alert]).to match('You are not authorized to access this area.') + expect(flash[:alert]).to match('You are not authorized to access this page.') + end + + context 'with conference' do + before :each do + @user = create(:user) + + conference.update_attributes(short_title: 'testtitle', description: 'Some random text') + @version_organizer = PaperTrail::Version.last + create(:cfp, program: conference.program) + @version_cfp = PaperTrail::Version.last + registration = create(:registration, conference: conference) + registration.update_attributes(attended: true) + @version_info_desk = PaperTrail::Version.last + end + + it 'when user has role cfp' do + @user.roles = [role_cfp] + sign_in @user + get :index, conference_id: conference.short_title + + expect(assigns(:versions).include?(@version_cfp)).to eq true + expect(assigns(:versions).include?(@version_organizer)).to eq false + end + + it 'when user has role info_desk' do + @user.roles = [role_info_desk] + sign_in @user + get :index, conference_id: conference.short_title + + expect(assigns(:versions).include?(@version_info_desk)).to eq true + expect(assigns(:versions).include?(@version_organizer)).to eq false + expect(assigns(:versions).include?(@version_cfp)).to eq false + end + + it 'when user has role organizer' do + @user.roles = [role_organizer] + sign_in @user + get :index, conference_id: conference.short_title + + expect(assigns(:versions).include?(@version_organizer)).to eq true + expect(assigns(:versions).include?(@version_cfp)).to eq true + expect(assigns(:versions).include?(@version_info_desk)).to eq true + end end end end diff --git a/spec/features/cfp_ability_spec.rb b/spec/features/cfp_ability_spec.rb index 1607401c..7229ab8d 100644 --- a/spec/features/cfp_ability_spec.rb +++ b/spec/features/cfp_ability_spec.rb @@ -298,7 +298,7 @@ feature 'Has correct abilities' do expect(current_path).to eq(root_path) visit admin_revision_history_path - expect(current_path).to eq(root_path) + expect(current_path).to eq(admin_revision_history_path) end end end diff --git a/spec/features/info_desk_ability_spec.rb b/spec/features/info_desk_ability_spec.rb index c005f9de..f4ef4fce 100644 --- a/spec/features/info_desk_ability_spec.rb +++ b/spec/features/info_desk_ability_spec.rb @@ -160,7 +160,7 @@ feature 'Has correct abilities' do expect(current_path).to eq(edit_admin_conference_resource_path(conference.short_title, conference.resources.first)) visit admin_revision_history_path - expect(current_path).to eq(root_path) + expect(current_path).to eq(admin_revision_history_path) visit admin_conference_path(conference.short_title) expect(current_path).to eq(admin_conference_path(conference.short_title)) diff --git a/spec/features/versions_spec.rb b/spec/features/versions_spec.rb index 2f1f7f8e..0db2d1f9 100644 --- a/spec/features/versions_spec.rb +++ b/spec/features/versions_spec.rb @@ -93,7 +93,7 @@ feature 'Version' do expect(page).to have_text("Someone (probably via the console) deleted lodging Hotel XYZ with ID #{lodging_id} in conference #{conference.short_title}") end - scenario 'display changes in role', feature: true, versioning: true, js: true do + scenario 'display changes in conference role', feature: true, versioning: true, js: true do visit edit_admin_conference_role_path(conference.short_title, 'cfp') fill_in 'role_description', with: 'For the members of the call for papers team' click_button 'Update Role' @@ -301,14 +301,40 @@ feature 'Version' do expect(page).to have_no_text('Someone (probably via the console) created new commercial') end - scenario 'display changes in users_role', feature: true, versioning: true, js: true do + scenario 'display changes in organization', feature: true, versioning: true, js: true do + admin = create(:admin) + sign_in admin + + visit new_admin_organization_path + fill_in 'organization_name', with: 'New org' + click_button 'Create Organization' + + visit admin_revision_history_path + expect(page).to have_text('created new organization New org') + end + + scenario 'display changes in users_role for organization role', feature: true, versioning: true, js: true do user = create(:user) + role = Role.find_by(resource_id: conference.organization.id, resource_type: 'Organization') + user.add_role :organization_admin, conference.organization + user_role = UsersRole.find_by(user_id: user.id, role_id: role.id) + user.remove_role :organization_admin, conference.organization + + visit admin_revision_history_path + expect(page).to have_text("added role organization_admin with ID #{user_role.id} to user #{user.name} in organization #{conference.organization.name}") + expect(page).to have_text("removed role organization_admin with ID #{user_role.id} from user #{user.name} in organization #{conference.organization.name}") + end + + scenario 'display changes in users_role for conference role', feature: true, versioning: true, js: true do + user = create(:user) + role = Role.find_by(name: 'cfp', resource_id: conference.id, resource_type: 'Conference') user.add_role :cfp, conference + user_role = UsersRole.find_by(user_id: user.id, role_id: role.id) user.remove_role :cfp, conference visit admin_revision_history_path - expect(page).to have_text("added role cfp to user #{user.name} in conference #{conference.short_title}") - expect(page).to have_text("removed role cfp from user #{user.name} in conference #{conference.short_title}") + expect(page).to have_text("added role cfp with ID #{user_role.id} to user #{user.name} in conference #{conference.short_title}") + expect(page).to have_text("removed role cfp with ID #{user_role.id} from user #{user.name} in conference #{conference.short_title}") end scenario 'display changes in email settings', feature: true, versioning: true, js: true do From 7067803cb9afb1850d2adb78b6f8b10d073f625c Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Tue, 8 Aug 2017 14:18:42 +0300 Subject: [PATCH 70/74] Validate that tracks don't overlap Also, add scope for accepted tracks --- app/models/track.rb | 18 ++++++++ spec/models/track_spec.rb | 88 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/app/models/track.rb b/app/models/track.rb index c624cab8..f2b1ac95 100644 --- a/app/models/track.rb +++ b/app/models/track.rb @@ -31,9 +31,11 @@ class Track < ActiveRecord::Base validates :description, presence: true, if: :self_organized? validate :valid_dates validate :valid_room, if: :self_organized_and_accepted_or_confirmed? + validate :overlapping before_validation :capitalize_color + scope :accepted, -> { where(state: 'accepted') } scope :confirmed, -> { where(state: 'confirmed') } scope :cfp_active, -> { where(cfp_active: true) } @@ -209,4 +211,20 @@ class Track < ActiveRecord::Base errors.add(:room, "must be a room of #{program.conference.venue.name}") end end + + ## + # Check that there is no other track in the same room with overlapping dates + def overlapping + return unless start_date && end_date && room && program.try(:tracks) + (program.tracks.accepted + program.tracks.confirmed - [self]).each do |other_track| + if other_track.room == room && + other_track.start_date && other_track.end_date && + (other_track.start_date <= start_date && other_track.end_date >= start_date || + other_track.start_date <= end_date && other_track.end_date >= end_date || + start_date <= other_track.start_date && other_track.end_date <= end_date) + errors.add(:track, 'has overlapping dates with a confirmed or accepted track in the same room') + break + end + end + end end diff --git a/spec/models/track_spec.rb b/spec/models/track_spec.rb index 1ad54839..f9c53653 100644 --- a/spec/models/track_spec.rb +++ b/spec/models/track_spec.rb @@ -136,9 +136,97 @@ describe Track do end end end + + describe '#overlapping' do + before :each do + @conference = create(:conference, start_date: Date.current - 1.day, end_date: Date.current + 2.days) + @conference.venue = create(:venue) + @room = create(:room, venue: @conference.venue) + end + + context 'is valid' do + it 'when the tracks are in different rooms' do + other_room = create(:room, venue: @conference.venue) + create(:track, :self_organized, state: 'confirmed', program: @conference.program, room: other_room, start_date: Date.current, end_date: Date.current) + track = build(:track, :self_organized, program: @conference.program, room: @room, start_date: Date.current, end_date: Date.current) + expect(track.valid?).to eq true + end + + it 'when it ends before the other tracks' do + create(:track, :self_organized, state: 'confirmed', program: @conference.program, room: @room, start_date: Date.current, end_date: Date.current) + track = build(:track, :self_organized, program: @conference.program, room: @room, start_date: Date.current - 1.day, end_date: Date.current - 1.day) + expect(track.valid?).to eq true + end + + it 'when it starts after the other tracks' do + create(:track, :self_organized, state: 'confirmed', program: @conference.program, room: @room, start_date: Date.current, end_date: Date.current) + track = build(:track, :self_organized, program: @conference.program, room: @room, start_date: Date.current + 1.day, end_date: Date.current + 1.day) + expect(track.valid?).to eq true + end + end + + context 'is invalid' do + it 'when it starts or ends with another track in the same room' do + create(:track, :self_organized, state: 'confirmed', program: @conference.program, room: @room, start_date: Date.current, end_date: Date.current) + track = build(:track, :self_organized, program: @conference.program, room: @room, start_date: Date.current, end_date: Date.current) + expect(track.valid?).to eq false + expect(track.errors[:track]).to eq ['has overlapping dates with a confirmed or accepted track in the same room'] + end + + it 'when it starts before another track and ends after the other starts and before it ends' do + create(:track, :self_organized, state: 'confirmed', program: @conference.program, room: @room, start_date: Date.current, end_date: Date.current + 2.days) + track = build(:track, :self_organized, program: @conference.program, room: @room, start_date: Date.current - 1.day, end_date: Date.current + 1.day) + expect(track.valid?).to eq false + expect(track.errors[:track]).to eq ['has overlapping dates with a confirmed or accepted track in the same room'] + end + + it 'when it starts after another track and before it ends and ends after the other' do + create(:track, :self_organized, state: 'confirmed', program: @conference.program, room: @room, start_date: Date.current, end_date: Date.current + 2.days) + track = build(:track, :self_organized, program: @conference.program, room: @room, start_date: Date.current + 1.day, end_date: Date.current + 3.days) + expect(track.valid?).to eq false + expect(track.errors[:track]).to eq ['has overlapping dates with a confirmed or accepted track in the same room'] + end + + it 'when it starts after another track and ends before the other' do + create(:track, :self_organized, state: 'confirmed', program: @conference.program, room: @room, start_date: Date.current, end_date: Date.current + 2.days) + track = build(:track, :self_organized, program: @conference.program, room: @room, start_date: Date.current + 1.day, end_date: Date.current + 1.day) + expect(track.valid?).to eq false + expect(track.errors[:track]).to eq ['has overlapping dates with a confirmed or accepted track in the same room'] + end + + it 'when it starts before another track and ends after the other' do + create(:track, :self_organized, state: 'confirmed', program: @conference.program, room: @room, start_date: Date.current, end_date: Date.current) + track = build(:track, :self_organized, program: @conference.program, room: @room, start_date: Date.current - 1.day, end_date: Date.current + 1.day) + expect(track.valid?).to eq false + expect(track.errors[:track]).to eq ['has overlapping dates with a confirmed or accepted track in the same room'] + end + end + end end describe 'scope' do + describe '#accepted' do + before :each do + @program = create(:program) + end + + context 'includes' do + it 'when track is accepted' do + accepted_track = create(:track, state: 'accepted', program: @program) + expect(@program.tracks.accepted.include?(accepted_track)).to eq true + end + end + + context 'excludes' do + %w[new to_accept confirmed to_reject rejected canceled withdrawn].each do |state| + it "when track is #{state.humanize}" do + not_accepted_track = create(:track, state: state, program: @program) + expect(@program.tracks.accepted.include?(not_accepted_track)).to eq false + end + end + end + end + describe '#confirmed' do before :each do @program = create(:program) From c4eec6a3de27041162db476bed3f3b667013b59b Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Tue, 8 Aug 2017 16:18:11 +0300 Subject: [PATCH 71/74] Validate room and start_time for EventSchedule Don't allow an event to be scheduled outside of it's track's room and time slot --- app/models/event_schedule.rb | 24 ++++++++++++ spec/models/event_schedule_spec.rb | 63 ++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/app/models/event_schedule.rb b/app/models/event_schedule.rb index 82f1e55a..a7cc1ed1 100644 --- a/app/models/event_schedule.rb +++ b/app/models/event_schedule.rb @@ -12,6 +12,8 @@ class EventSchedule < ActiveRecord::Base validates :event, uniqueness: { scope: :schedule } validate :start_after_end_hour validate :start_before_start_hour + validate :room_of_track + validate :during_track scope :confirmed, -> { joins(:event).where('state = ?', 'confirmed') } scope :canceled, -> { joins(:event).where('state = ?', 'canceled') } @@ -52,4 +54,26 @@ class EventSchedule < ActiveRecord::Base def conference_id schedule.program.conference_id end + + ## + # Validates that the event is scheduled in the same room as it's track + # + def room_of_track + if event && event.track.try(:room) && event.track.room != room + errors.add(:room, "must be the same as the track's room (#{event.track.room.name})") + end + end + + ## + # Validates that the event is scheduled within it's track's time slot + # + def during_track + if event && event.track.try(:start_date) && event.track.start_date > start_time + errors.add(:start_time, "can't be before the track's start date (#{event.track.start_date})") + end + + if event && event.track.try(:end_date) && event.track.end_date + 1.day < end_time + errors.add(:end_time, "can't be after the track's end date (#{event.track.end_date})") + end + end end diff --git a/spec/models/event_schedule_spec.rb b/spec/models/event_schedule_spec.rb index 6d1325b7..8e22430b 100644 --- a/spec/models/event_schedule_spec.rb +++ b/spec/models/event_schedule_spec.rb @@ -45,5 +45,68 @@ describe EventSchedule do end end end + + describe '#room_of_track' do + before :each do + conference = create(:conference) + conference.venue = create(:venue) + @room = create(:room, venue: conference.venue) + @track = create(:track, program: conference.program, room: @room) + @event = create(:event, program: conference.program, track: @track) + end + + context 'is valid' do + it 'when scheduled in the track\'s room' do + event_schedule = build(:event_schedule, event: @event, room: @room) + expect(event_schedule.valid?).to eq true + end + + it 'when the track doesn\'t have a room' do + @track.room = nil + @track.save! + event_schedule = build(:event_schedule, event: @event) + expect(event_schedule.valid?).to eq true + end + end + + context 'is invalid' do + it 'when scheduled in different room than the track\'s' do + event_schedule = build(:event_schedule, event: @event) + expect(event_schedule.valid?).to eq false + expect(event_schedule.errors[:room]).to eq ["must be the same as the track's room (#{@room.name})"] + end + end + end + + describe '#during_track' do + before :each do + conference = create(:conference, start_date: Date.current - 1.day, start_hour: 0, end_hour: 24) + conference.venue = create(:venue) + @room = create(:room, venue: conference.venue) + @track = create(:track, program: conference.program, room: @room, start_date: Date.current, end_date: Date.current) + @event = create(:event, program: conference.program, track: @track) + end + + context 'is valid' do + it 'when scheduled during the track\'s time slot' do + event_schedule = build(:event_schedule, event: @event, room: @room, start_time: Date.current + 3.hours) + expect(event_schedule.valid?).to eq true + end + end + + context 'is invalid' do + it 'when scheduled before the track\'s start date' do + event_schedule = build(:event_schedule, event: @event, room: @room, start_time: Date.current - 1.hour) + expect(event_schedule.valid?).to eq false + expect(event_schedule.errors[:start_time]).to eq ["can't be before the track's start date (#{@track.start_date})"] + end + + it 'when event ends after the track\'s end date' do + event_schedule = build(:event_schedule, event: @event, room: @room, start_time: Date.current + 1.day - 10.minutes) + expect(event_schedule.valid?).to eq false + expect(event_schedule.errors[:end_time]).to eq ["can't be after the track's end date (#{@track.end_date})"] + end + end + end end end From a87c6e55f7e9a0d073a98fd899567d273d2f46b7 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 9 Aug 2017 12:05:11 +0300 Subject: [PATCH 72/74] Remove unnecessary ability from adminAbilities CanCanCan can load @events and @event in EventsController by itself --- app/controllers/admin/events_controller.rb | 13 ------------- app/controllers/admin/reports_controller.rb | 4 +++- app/models/admin_ability.rb | 4 ---- spec/features/track_organizer_ability_spec.rb | 2 +- 4 files changed, 4 insertions(+), 19 deletions(-) diff --git a/app/controllers/admin/events_controller.rb b/app/controllers/admin/events_controller.rb index 1aa2e4bd..c93c3892 100644 --- a/app/controllers/admin/events_controller.rb +++ b/app/controllers/admin/events_controller.rb @@ -5,8 +5,6 @@ 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, :new] - # FIXME: The timezome should only be applied on output, otherwise # you get lost in timezone conversions... # around_filter :set_timezone_for_this_request @@ -16,7 +14,6 @@ module Admin end def index - @events = @program.events @tracks = @program.tracks.confirmed.cfp_active @difficulty_levels = @program.difficulty_levels @event_types = @program.event_types @@ -187,16 +184,6 @@ module Admin params.require(:comment).permit(:commentable, :body, :user_id) end - def get_event - @event = @conference.program.events.find(params[:id]) - unless @event - redirect_to admin_conference_program_events_path(conference_id: @conference.short_title), - error: 'Error! Could not find event!' - return - end - @event - end - def update_state(transition, notice, mail = false, subject = false, send_mail = false) alert = @event.update_state(transition, mail, subject, send_mail, params[:send_mail].blank?) diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index c6d4003c..b01a4bdc 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -2,9 +2,11 @@ module Admin class ReportsController < Admin::BaseController load_and_authorize_resource :conference, find_by: :short_title load_and_authorize_resource :program, through: :conference, singleton: true + # For some reason this doesn't work, so a workaround is used + # load_and_authorize_resource :event, through: :program def index - @events = @program.events + @events = Event.accessible_by(current_ability).where(program: @program) @events_commercials = Commercial.where(commercialable_type: 'Event', commercialable_id: @events.pluck(:id)) @events_missing_commercial = @events.where.not(id: @events_commercials.pluck(:commercialable_id)) @events_with_requirements = @events.where.not(description: ['', nil]) diff --git a/app/models/admin_ability.rb b/app/models/admin_ability.rb index 7fd805d1..e8b8d098 100644 --- a/app/models/admin_ability.rb +++ b/app/models/admin_ability.rb @@ -39,10 +39,6 @@ class AdminAbility event.program.cfp_open? && event.new_record? end - can [:update, :show, :index], Event do |event| - event.users.include?(user) - end - # can manage the commercials of their own events can :manage, Commercial, commercialable_type: 'Event', commercialable_id: user.events.pluck(:id) diff --git a/spec/features/track_organizer_ability_spec.rb b/spec/features/track_organizer_ability_spec.rb index 49e46915..91c0b55a 100644 --- a/spec/features/track_organizer_ability_spec.rb +++ b/spec/features/track_organizer_ability_spec.rb @@ -101,7 +101,7 @@ feature 'Has correct abilities' do expect(current_path).to eq root_path visit admin_conference_program_events_path(conference.short_title) - expect(current_path).to eq admin_conference_program_events_path(conference.short_title) + expect(current_path).to eq root_path create(:event, program: conference.program) visit edit_admin_conference_program_event_path(conference.short_title, conference.program.events.first) From af7efdb6fe888fa479b24b971a1e4ad92817a560 Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 9 Aug 2017 14:53:27 +0300 Subject: [PATCH 73/74] Allow the track organizer to manage events He/She can manage the events of his/her tracks and their commercials --- app/controllers/admin/events_controller.rb | 11 ++++++++--- app/models/admin_ability.rb | 9 +++++++++ spec/features/track_organizer_ability_spec.rb | 14 +++++++++----- spec/models/admin_ability_spec.rb | 9 ++++++++- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/app/controllers/admin/events_controller.rb b/app/controllers/admin/events_controller.rb index c93c3892..2d7af60e 100644 --- a/app/controllers/admin/events_controller.rb +++ b/app/controllers/admin/events_controller.rb @@ -4,6 +4,10 @@ module Admin load_and_authorize_resource :program, through: :conference, singleton: true load_and_authorize_resource :event, through: :program load_and_authorize_resource :events_registration, only: :toggle_attendance + # For some reason this doesn't work, so a workaround is used + # load_and_authorize_resource :track, through: :program, only: [:index, :show, :edit] + + before_action :get_tracks, only: [:index, :show, :edit] # FIXME: The timezome should only be applied on output, otherwise # you get lost in timezone conversions... @@ -14,7 +18,6 @@ module Admin end def index - @tracks = @program.tracks.confirmed.cfp_active @difficulty_levels = @program.difficulty_levels @event_types = @program.event_types @tracks_distribution_confirmed = @conference.tracks_distribution(:confirmed) @@ -40,7 +43,6 @@ module Admin end def show - @tracks = @program.tracks.confirmed.cfp_active @event_types = @program.event_types @comments = @event.root_comments @comment_count = @event.comment_threads.count @@ -55,7 +57,6 @@ module Admin def edit @event_types = @program.event_types - @tracks = @program.tracks.confirmed.cfp_active @comments = @event.root_comments @comment_count = @event.comment_threads.count @user = @event.submitter @@ -195,5 +196,9 @@ module Admin return redirect_back_or_to(admin_conference_program_events_path(conference_id: @conference.short_title)) && return end end + + def get_tracks + @tracks = Track.accessible_by(current_ability).where(program: @program).confirmed.cfp_active + end end end diff --git a/app/models/admin_ability.rb b/app/models/admin_ability.rb index e8b8d098..af0f80eb 100644 --- a/app/models/admin_ability.rb +++ b/app/models/admin_ability.rb @@ -298,5 +298,14 @@ class AdminAbility can :toggle_user, Role do |role| role.resource_type == 'Track' && track_ids_for_track_organizer.include?(role.resource_id) end + + # Show Events in the admin sidebar + can :update, Event do |event| + event.new_record? && conf_ids_for_track_organizer.include?(event.program.conference_id) + end + + can :manage, Event, track_id: track_ids_for_track_organizer + can :manage, Commercial, commercialable_type: 'Event', + commercialable_id: Event.where(track_id: track_ids_for_track_organizer).pluck(:id) end end diff --git a/spec/features/track_organizer_ability_spec.rb b/spec/features/track_organizer_ability_spec.rb index 91c0b55a..69b4d3e5 100644 --- a/spec/features/track_organizer_ability_spec.rb +++ b/spec/features/track_organizer_ability_spec.rb @@ -4,7 +4,7 @@ feature 'Has correct abilities' do let(:organization) { create(:organization) } let(:conference) { create(:full_conference, organization: organization) } - let(:self_organized_track) { create(:track, :self_organized, program: conference.program) } + let(:self_organized_track) { create(:track, :self_organized, program: conference.program, state: 'confirmed', cfp_active: true) } let(:role_track_organizer) { Role.where(name: 'track_organizer', resource: self_organized_track).first_or_create } let(:user_track_organizer) { create(:user, role_ids: [role_track_organizer.id]) } @@ -28,12 +28,12 @@ feature 'Has correct abilities' do expect(page).to_not have_link('Lodgings', href: "/admin/conferences/#{conference.short_title}/lodgings") expect(page).to have_link('Program', href: "/admin/conferences/#{conference.short_title}/program") expect(page).to_not have_link('Call for Papers', href: "/admin/conferences/#{conference.short_title}/program/cfps") - expect(page).to_not have_link('Events', href: "/admin/conferences/#{conference.short_title}/program/events") + expect(page).to have_link('Events', href: "/admin/conferences/#{conference.short_title}/program/events") expect(page).to have_link('Tracks', href: "/admin/conferences/#{conference.short_title}/program/tracks") expect(page).to_not have_link('Event Types', href: "/admin/conferences/#{conference.short_title}/program/event_types") expect(page).to_not have_link('Difficulty Levels', href: "/admin/conferences/#{conference.short_title}/program/difficulty_levels") expect(page).to_not have_link('Schedules', href: "/admin/conferences/#{conference.short_title}/schedules") - expect(page).to_not have_link('Reports', href: "/admin/conferences/#{conference.short_title}/program/reports") + expect(page).to have_link('Reports', href: "/admin/conferences/#{conference.short_title}/program/reports") expect(page).to_not have_link('Registrations', href: "/admin/conferences/#{conference.short_title}/registrations") expect(page).to_not have_link('Registration Period', href: "/admin/conferences/#{conference.short_title}/registration_period") expect(page).to_not have_link('Questions', href: "/admin/conferences/#{conference.short_title}/questions") @@ -101,12 +101,16 @@ feature 'Has correct abilities' do expect(current_path).to eq root_path visit admin_conference_program_events_path(conference.short_title) - expect(current_path).to eq root_path + expect(current_path).to eq admin_conference_program_events_path(conference.short_title) create(:event, program: conference.program) visit edit_admin_conference_program_event_path(conference.short_title, conference.program.events.first) expect(current_path).to eq root_path + self_organized_track_event = create(:event, program: conference.program, track: self_organized_track) + visit edit_admin_conference_program_event_path(conference.short_title, self_organized_track_event) + expect(current_path).to eq(edit_admin_conference_program_event_path(conference.short_title, self_organized_track_event)) + visit admin_conference_program_event_types_path(conference.short_title) expect(current_path).to eq root_path @@ -219,7 +223,7 @@ feature 'Has correct abilities' do expect(current_path).to eq admin_conference_program_track_path(conference.short_title, self_organized_track) visit edit_admin_conference_program_track_path(conference.short_title, self_organized_track) - expect(current_path).to eq edit_admin_conference_program_track_path(conference.short_title, self_organized_track) + expect(current_path).to eq root_path visit admin_conference_roles_path(conference.short_title) expect(current_path).to eq admin_conference_roles_path(conference.short_title) diff --git a/spec/models/admin_ability_spec.rb b/spec/models/admin_ability_spec.rb index f148b2ae..126668fa 100644 --- a/spec/models/admin_ability_spec.rb +++ b/spec/models/admin_ability_spec.rb @@ -45,7 +45,7 @@ describe 'User with admin role' do let!(:my_event_schedule) { create(:event_schedule, schedule: my_schedule) } let!(:other_event_schedule) { create(:event_schedule, schedule: other_schedule) } - let!(:my_self_organized_track) { create(:track, :self_organized, program: my_conference.program, state: 'confirmed') } + let!(:my_self_organized_track) { create(:track, :self_organized, program: my_conference.program, state: 'confirmed', cfp_active: true) } context 'user #is_admin?' do let(:venue) { my_conference.venue } @@ -459,6 +459,9 @@ describe 'User with admin role' do let(:role) { Role.where(name: 'track_organizer', resource: my_self_organized_track).first_or_create } let(:user) { create(:user, role_ids: [role.id]) } let(:new_track) { build(:track, program: my_conference.program) } + let(:new_event) { build(:event, program: my_conference.program) } + let(:my_self_organized_track_event) { create(:event, program: my_conference.program, track: my_self_organized_track) } + let(:my_self_organized_track_event_commercial) { create(:commercial, commercialable: my_self_organized_track_event) } it{ should_not be_able_to(:new, Conference.new) } it{ should_not be_able_to(:create, Conference.new) } @@ -525,6 +528,10 @@ describe 'User with admin role' do it{ should_not be_able_to(:assign_org_admins, organization) } it{ should_not be_able_to(:unassign_org_admins, organization) } + it{ should be_able_to(:update, new_event) } + it{ should be_able_to(:manage, my_self_organized_track_event) } + it{ should be_able_to(:manage, my_self_organized_track_event_commercial) } + it_behaves_like 'user with any role' it_behaves_like 'user with non-organizer role', 'track_organizer' end From 7eea9302694943858d81e450e14e188e6832f7bd Mon Sep 17 00:00:00 2001 From: AEtherC0r3 Date: Wed, 9 Aug 2017 18:12:20 +0300 Subject: [PATCH 74/74] Implement track scheduling Add track association to schedule Show schedules in admin sidebar to track organizers Allow track organizers to manage the schedules of their tracks Don't allow self-organized track events to be dragged or unscheduled in a conference schedule Make scheduled events of self-organized tracks appear semitransparent in conference schedules Make the rooms of confirmed self_organized tracks appear semitransparent and don't allow events to be scheduled to it in the conference schedules during the dates of its track Create admin/SchedulesController#new action Add a button in admin/Schedules#index to create schedules for tracks Add self_organized scope to Track Modify Schedules#show to handle track schedules and show a unified schedule Allow track organizers to create new schedules for their tracks Correctly identify scheduled and unscheduled events in Schedules#events Fix Event#room and Event#time for when the event is scheduled in a track schedule Modify Program#selected_event_schedules to include the event_schedules of selected track schedules Modify Track#revoke_role_and_cleanup to destroy the track's schedules and revert its events' state to new Add tabs for conference and track schedules in admin/Schedules#index Add button to Create/Show a tracks schedule in Tracks#index and #show Fix concurrent_events in application_helper because of changes in Program#selected_event_schedules Do not take into account cfp_active in Event#valid_track Modify EventsController#get_tracks accordingly Enforce cfp_active of track to be enabled for proposals in ProposalsController#create and #update Add support for multiple schedules per track Add selected_schedule_id to Track Load EventSchedules of selected track schedules for conference schedules in admin/SchedulesController#show Modify SchedulesController#show to take into account only the selected track schedules Create Event#selected_schedule_id and use it in Event#scheduled? and Event#time Validate that an EventSchedule for an event of a self-organized track belongs to one of the track's schedules Add 'Manage' button in Tracks#index, #show that sends you to the admin side of things Add admin/TracksController#update_selected_schedule to update the selected_schedule_id of tracks --- .haml-lint_todo.yml | 2 + .rubocop.yml | 1 + app/assets/javascripts/osem-schedule.js | 7 +- app/assets/stylesheets/osem-schedule.css.scss | 4 + app/controllers/admin/events_controller.rb | 2 +- app/controllers/admin/schedules_controller.rb | 35 ++++- app/controllers/admin/tracks_controller.rb | 12 ++ app/controllers/proposals_controller.rb | 13 ++ app/controllers/schedules_controller.rb | 13 +- app/helpers/application_helper.rb | 2 +- app/models/admin_ability.rb | 13 ++ app/models/event.rb | 32 ++++- app/models/event_schedule.rb | 38 ++++-- app/models/program.rb | 6 +- app/models/schedule.rb | 1 + app/models/track.rb | 14 +- app/views/admin/schedules/_day_tab.html.haml | 4 +- app/views/admin/schedules/_event.html.haml | 3 +- app/views/admin/schedules/_form.html.haml | 10 ++ app/views/admin/schedules/index.html.haml | 121 ++++++++++++------ app/views/admin/schedules/show.html.haml | 11 +- app/views/admin/tracks/index.html.haml | 10 ++ app/views/admin/tracks/show.html.haml | 10 ++ app/views/proposals/show.html.haml | 2 +- app/views/schedules/_carousel.html.haml | 2 +- app/views/tracks/index.html.haml | 2 + app/views/tracks/show.html.haml | 2 + config/routes.rb | 3 +- ...9120927_add_track_reference_to_schedule.rb | 5 + ...4174637_add_selected_schedule_to_tracks.rb | 6 + db/schema.rb | 14 +- spec/features/track_organizer_ability_spec.rb | 10 +- spec/models/admin_ability_spec.rb | 11 +- spec/models/event_schedule_spec.rb | 36 +++++- spec/models/event_spec.rb | 43 +++++-- spec/models/schedule_spec.rb | 1 + spec/models/track_spec.rb | 60 +++++++-- 37 files changed, 443 insertions(+), 118 deletions(-) create mode 100644 app/views/admin/schedules/_form.html.haml create mode 100644 db/migrate/20170809120927_add_track_reference_to_schedule.rb create mode 100644 db/migrate/20170814174637_add_selected_schedule_to_tracks.rb diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 49feab3d..b79ddc17 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -188,6 +188,7 @@ linters: - "app/views/conferences/_call_for_tracks.html.haml" - "app/views/admin/tracks/_change_state_dropdown.html.haml" - "app/views/proposals/_encouragement_text.html.haml" + - "app/views/admin/schedules/_form.html.haml" # Offense count: 223 InstanceVariables: @@ -253,6 +254,7 @@ linters: - "app/views/admin/cfps/_tracks_cfp.html.haml" - "app/views/conferences/_call_for_tracks.html.haml" - "app/views/admin/tracks/_change_state_dropdown.html.haml" + - "app/views/admin/schedules/_form.html.haml" # Offense count: 32 IdNames: diff --git a/.rubocop.yml b/.rubocop.yml index 5d24032d..9ab6f6b3 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -31,3 +31,4 @@ Metrics/BlockLength: - 'spec/models/conference_spec.rb' - 'spec/features/ability_spec.rb' - 'spec/models/ability_spec.rb' + - 'spec/models/admin_ability_spec.rb' diff --git a/app/assets/javascripts/osem-schedule.js b/app/assets/javascripts/osem-schedule.js index baae1933..39e33922 100644 --- a/app/assets/javascripts/osem-schedule.js +++ b/app/assets/javascripts/osem-schedule.js @@ -78,11 +78,12 @@ var Schedule = { }; $(document).ready( function() { - // hide the remove button for unscheduled events + // hide the remove button for unscheduled and non schedulable events $('.unscheduled-events .schedule-event-delete-button').hide(); + $('.non_schedulable .schedule-event-delete-button').hide(); // set events as draggable - $('.schedule-event').draggable({ + $('.schedule-event').not('.non_schedulable').draggable({ snap: '.schedule-room-slot', revertDuration: 200, revert: function (event, ui) { @@ -99,7 +100,7 @@ $(document).ready( function() { }); // set room cells as droppable - $('.schedule-room-slot').droppable({ + $('.schedule-room-slot').not('.non_schedulable .schedule-room-slot').droppable({ accept: '.schedule-event', tolerance: "pointer", drop: function(event, ui) { diff --git a/app/assets/stylesheets/osem-schedule.css.scss b/app/assets/stylesheets/osem-schedule.css.scss index 21fda6f3..75a71767 100644 --- a/app/assets/stylesheets/osem-schedule.css.scss +++ b/app/assets/stylesheets/osem-schedule.css.scss @@ -267,6 +267,10 @@ td.no-padding{ font-size: 7px; } +.non_schedulable{ + opacity: 0.5; +} + /* Small devices (tablets, 768px and up) */ @media (min-width: 768px) { .room, .event-title{ diff --git a/app/controllers/admin/events_controller.rb b/app/controllers/admin/events_controller.rb index 2d7af60e..bb23dac9 100644 --- a/app/controllers/admin/events_controller.rb +++ b/app/controllers/admin/events_controller.rb @@ -198,7 +198,7 @@ module Admin end def get_tracks - @tracks = Track.accessible_by(current_ability).where(program: @program).confirmed.cfp_active + @tracks = Track.accessible_by(current_ability).where(program: @program).confirmed end end end diff --git a/app/controllers/admin/schedules_controller.rb b/app/controllers/admin/schedules_controller.rb index d3c7cea3..00ef9a1d 100644 --- a/app/controllers/admin/schedules_controller.rb +++ b/app/controllers/admin/schedules_controller.rb @@ -4,14 +4,21 @@ module Admin # the schedule of a conference, which should not be accessed in the first place load_and_authorize_resource :conference, find_by: :short_title load_and_authorize_resource :program, through: :conference, singleton: true - load_and_authorize_resource :schedule, through: :program + load_and_authorize_resource :schedule, through: :program, except: [:new, :create] load_resource :event_schedules, through: :schedule load_resource :selected_schedule, through: :program, singleton: true load_resource :venue, through: :conference, singleton: true def index; end + def new + @schedule = @program.schedules.build(track: @program.tracks.new) + authorize! :new, @schedule + end + def create + @schedule = @program.schedules.new(schedule_params) + authorize! :create, @schedule if @schedule.save redirect_to admin_conference_schedule_path(@conference.short_title, @schedule.id), notice: 'Schedule was successfully created.' @@ -23,9 +30,23 @@ module Admin def show @event_schedules = @schedule.event_schedules - @unscheduled_events = @program.events.confirmed - @schedule.events - @dates = @conference.start_date..@conference.end_date - @rooms = @conference.venue.rooms if @conference.venue + + if @schedule.track + track = @schedule.track + @unscheduled_events = track.events.confirmed - @schedule.events + @dates = track.start_date..track.end_date + @rooms = [track.room] + else + @program.tracks.self_organized.confirmed.each do |t| + @event_schedules += t.selected_schedule.event_schedules if t.selected_schedule + end + self_organized_tracks_events = @program.tracks.self_organized.confirmed.map do |t| + t.events.confirmed + end + @unscheduled_events = @program.events.confirmed - @schedule.events - self_organized_tracks_events.flatten.compact + @dates = @conference.start_date..@conference.end_date + @rooms = @conference.venue.rooms if @conference.venue + end end def destroy @@ -37,5 +58,11 @@ module Admin error: "Schedule couldn't be deleted. #{@schedule.errors.full_messages.join('. ')}." end end + + private + + def schedule_params + params.require(:schedule).permit(:track_id) if params[:schedule] + end end end diff --git a/app/controllers/admin/tracks_controller.rb b/app/controllers/admin/tracks_controller.rb index 0f3940da..03f26448 100644 --- a/app/controllers/admin/tracks_controller.rb +++ b/app/controllers/admin/tracks_controller.rb @@ -101,6 +101,18 @@ module Admin update_state(:cancel, "Track #{@track.name} canceled!") end + def update_selected_schedule + if @track.update_attributes(params.require(:track).permit(:selected_schedule_id)) + respond_to do |format| + format.js { render json: {} } + end + else + respond_to do |format| + format.js { render json: { errors: "The selected schedule couldn't been updated #{@track.errors.to_a.join('. ')}" }, status: 422 } + end + end + end + private def track_params diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index baf86f3c..b75f648e 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -49,6 +49,13 @@ class ProposalsController < ApplicationController # by default. @event.speakers = [current_user] @event.submitter = current_user + + if Track.find_by(id: params[:event][:track_id]).try(:cfp_active) == false + flash.now[:error] = 'You have selected a track that doesn\'t accept proposals' + render action: 'new' + return + end + if @event.save ahoy.track 'Event submission', title: 'New submission' redirect_to conference_program_proposals_path(@conference.short_title), notice: 'Proposal was successfully submitted.' @@ -61,6 +68,12 @@ class ProposalsController < ApplicationController def update @url = conference_program_proposal_path(@conference.short_title, params[:id]) + if Track.find_by(id: params[:event][:track_id]).try(:cfp_active) == false + flash.now[:error] = 'You have selected a track that doesn\'t accept proposals' + render action: 'edit' + return + end + if @event.update(event_params) redirect_to conference_program_proposals_path(conference_id: @conference.short_title), notice: 'Proposal was successfully updated.' diff --git a/app/controllers/schedules_controller.rb b/app/controllers/schedules_controller.rb index 5fdbeecd..eeb272e0 100644 --- a/app/controllers/schedules_controller.rb +++ b/app/controllers/schedules_controller.rb @@ -24,6 +24,13 @@ class SchedulesController < ApplicationController return unless @current_day # the schedule takes you to the current time if it is beetween the start and the end time. @hour_column = @conference.hours_from_start_time(@conf_start, @conference.end_hour) + + # Ids of the schedules of confrmed self_organized tracks along with the selected_schedule_id + @selected_schedules_ids = [@conference.program.selected_schedule_id] + @conference.program.tracks.self_organized.confirmed.each do |track| + @selected_schedules_ids << track.selected_schedule_id + end + @selected_schedules_ids.compact! end def events @@ -32,11 +39,7 @@ class SchedulesController < ApplicationController @events_schedules = @program.selected_event_schedules @events_schedules = [] unless @events_schedules - @unscheduled_events = if @program.selected_schedule - @program.events.confirmed - @program.selected_schedule.events - else - @program.events.confirmed - end + @unscheduled_events = @program.events.confirmed - @events_schedules.map(&:event) day = @conference.current_conference_day @tag = day.strftime('%Y-%m-%d') if day diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e55656ea..e4a0e230 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -117,7 +117,7 @@ module ApplicationHelper def concurrent_events(event) return nil unless event.scheduled? && event.program.selected_event_schedules - event_schedule = event.program.selected_event_schedules.find_by(event: event) + event_schedule = event.program.selected_event_schedules.find { |es| es.event == event } other_event_schedules = event.program.selected_event_schedules.reject { |other_event_schedule| other_event_schedule == event_schedule } concurrent_events = [] diff --git a/app/models/admin_ability.rb b/app/models/admin_ability.rb index af0f80eb..cc318da2 100644 --- a/app/models/admin_ability.rb +++ b/app/models/admin_ability.rb @@ -307,5 +307,18 @@ class AdminAbility can :manage, Event, track_id: track_ids_for_track_organizer can :manage, Commercial, commercialable_type: 'Event', commercialable_id: Event.where(track_id: track_ids_for_track_organizer).pluck(:id) + + # Show Scheduless in the admin sidebar + can :update, Schedule do |schedule| + schedule.new_record? && conf_ids_for_track_organizer.include?(schedule.program.conference_id) + end + + # Show new track schedule button + can :new, Schedule do |schedule| + schedule.new_record? && conf_ids_for_track_organizer.include?(schedule.program.conference_id) && schedule.track.try(:new_record?) + end + + can :manage, Schedule, track_id: track_ids_for_track_organizer + can :manage, EventSchedule, schedule: { track_id: track_ids_for_track_organizer } end end diff --git a/app/models/event.rb b/app/models/event.rb index 682eb6c8..c6de2d45 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -45,7 +45,7 @@ class Event < ActiveRecord::Base validates :max_attendees, numericality: { only_integer: true, greater_than_or_equal_to: 1, allow_nil: true } validate :max_attendees_no_more_than_room_size - validate :acceptable_track + validate :valid_track scope :confirmed, -> { where(state: 'confirmed') } scope :canceled, -> { where(state: 'canceled') } @@ -85,7 +85,7 @@ class Event < ActiveRecord::Base # ====Returns # * +true+ or +false+ def scheduled? - event_schedules.find_by(schedule_id: program.selected_schedule_id).present? + event_schedules.find_by(schedule_id: selected_schedule_id).present? end def registration_possible? @@ -243,14 +243,18 @@ class Event < ActiveRecord::Base def room # We use try(:selected_schedule_id) because this function is used for # validations so program could not be present there - event_schedules.find_by(schedule_id: program.try(:selected_schedule_id)).try(:room) + if track.try(:self_organized?) + track.room + else + event_schedules.find_by(schedule_id: program.try(:selected_schedule_id)).try(:room) + end end ## # Returns the start time at which this event is scheduled # def time - event_schedules.find_by(schedule_id: program.selected_schedule_id).try(:start_time) + event_schedules.find_by(schedule_id: selected_schedule_id).try(:start_time) end def conference @@ -306,9 +310,23 @@ class Event < ActiveRecord::Base end ## - # Allow only confirmed tracks that belong to the same program and are included in the cfp - def acceptable_track + # Allow only confirmed tracks that belong to the same program as the event + # + def valid_track return unless track && track.program && program - errors.add(:track, 'is invalid') unless track.confirmed? && track.cfp_active && track.program == program + errors.add(:track, 'is invalid') unless track.confirmed? && track.program == program + end + + ## + # Return the id of the selected schedule + # + # ====Returns + # * +Integer+ -> selected_schedule_id of self-organized track or program + def selected_schedule_id + if track.try(:self_organized?) + track.selected_schedule_id + else + program.selected_schedule_id + end end end diff --git a/app/models/event_schedule.rb b/app/models/event_schedule.rb index a7cc1ed1..57edbec0 100644 --- a/app/models/event_schedule.rb +++ b/app/models/event_schedule.rb @@ -12,8 +12,9 @@ class EventSchedule < ActiveRecord::Base validates :event, uniqueness: { scope: :schedule } validate :start_after_end_hour validate :start_before_start_hour - validate :room_of_track + validate :same_room_as_track validate :during_track + validate :valid_schedule scope :confirmed, -> { joins(:event).where('state = ?', 'confirmed') } scope :canceled, -> { joins(:event).where('state = ?', 'canceled') } @@ -56,24 +57,33 @@ class EventSchedule < ActiveRecord::Base end ## - # Validates that the event is scheduled in the same room as it's track + # Validates that the event is scheduled in the same room as its track # - def room_of_track - if event && event.track.try(:room) && event.track.room != room - errors.add(:room, "must be the same as the track's room (#{event.track.room.name})") + def same_room_as_track + return unless event.try(:track).try(:room) + errors.add(:room, "must be the same as the track's room (#{event.track.room.name})") unless event.track.room == room + end + + ## + # Validates that the event is scheduled within its track's time slot + # + def during_track + return unless event.try(:track) && start_time + + if event.track.try(:start_date) && event.track.start_date > start_time + errors.add(:start_time, "can't be before the track's start date (#{event.track.start_date})") + end + + if event.track.try(:end_date) && event.track.end_date + 1.day < end_time + errors.add(:end_time, "can't be after the track's end date (#{event.track.end_date})") end end ## - # Validates that the event is scheduled within it's track's time slot + # Validates that the event is scheduled in its self-organized tracks's schedules # - def during_track - if event && event.track.try(:start_date) && event.track.start_date > start_time - errors.add(:start_time, "can't be before the track's start date (#{event.track.start_date})") - end - - if event && event.track.try(:end_date) && event.track.end_date + 1.day < end_time - errors.add(:end_time, "can't be after the track's end date (#{event.track.end_date})") - end + def valid_schedule + return unless event.try(:track).try(:self_organized?) && schedule + errors.add(:schedule, "must be one of #{event.track.name} track's schedules") unless event.track.schedules.include?(schedule) end end diff --git a/app/models/program.rb b/app/models/program.rb index 49894cbd..612d8154 100644 --- a/app/models/program.rb +++ b/app/models/program.rb @@ -71,7 +71,11 @@ class Program < ActiveRecord::Base # Returns all event_schedules for the selected schedule ordered by start_time def selected_event_schedules - selected_schedule.event_schedules.order(start_time: :asc) if selected_schedule + event_schedules = selected_schedule.event_schedules.order(start_time: :asc) if selected_schedule + tracks.self_organized.confirmed.order(start_date: :asc).each do |track| + event_schedules += track.selected_schedule.event_schedules.order(start_time: :asc) if track.selected_schedule + end + event_schedules.sort_by(&:start_time) if event_schedules end ## diff --git a/app/models/schedule.rb b/app/models/schedule.rb index b14a978c..082d0af0 100644 --- a/app/models/schedule.rb +++ b/app/models/schedule.rb @@ -1,5 +1,6 @@ class Schedule < ActiveRecord::Base belongs_to :program + belongs_to :track has_many :event_schedules, dependent: :destroy has_many :events, through: :event_schedules diff --git a/app/models/track.rb b/app/models/track.rb index f2b1ac95..c3d02950 100644 --- a/app/models/track.rb +++ b/app/models/track.rb @@ -7,7 +7,9 @@ class Track < ActiveRecord::Base belongs_to :program belongs_to :submitter, class_name: 'User' belongs_to :room + belongs_to :selected_schedule, class_name: 'Schedule' has_many :events, dependent: :nullify + has_many :schedules has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } @@ -38,6 +40,7 @@ class Track < ActiveRecord::Base scope :accepted, -> { where(state: 'accepted') } scope :confirmed, -> { where(state: 'confirmed') } scope :cfp_active, -> { where(cfp_active: true) } + scope :self_organized, -> { where.not(submitter: nil) } state_machine initial: :pending do state :new @@ -102,7 +105,10 @@ class Track < ActiveRecord::Base submitter.add_role 'track_organizer', self end - # Revokes the track organizer role and removes the track from events that have it set + ## + # Revokes the track organizer role, destroys the track's schedule, removes the + # track from events that have it set and reverts their state to new + # def revoke_role_and_cleanup role = Role.find_by(name: 'track_organizer', resource: self) @@ -112,8 +118,14 @@ class Track < ActiveRecord::Base end end + self.selected_schedule_id = nil + save! + + schedules.each(&:destroy!) + events.each do |event| event.track = nil + event.state = 'new' event.save! end end diff --git a/app/views/admin/schedules/_day_tab.html.haml b/app/views/admin/schedules/_day_tab.html.haml index 7bfe3ec6..8a1dff60 100644 --- a/app/views/admin/schedules/_day_tab.html.haml +++ b/app/views/admin/schedules/_day_tab.html.haml @@ -5,7 +5,9 @@ - date_event_schedules = @event_schedules.select{ |e| e.start_time.to_date.eql? date } .row - @rooms.each do |room| - .col-md-2.col-xs-6 + - non_schedulable = room.tracks.self_organized.confirmed.any? do |track| + - !track.schedules.include?(@schedule) && (track.start_date..track.end_date).include?(date) + .col-md-2.col-xs-6{ class: ('non_schedulable' if non_schedulable) } .room-name - room_date_event_schedules = date_event_schedules.select{ |e| e.room == room } = room.name diff --git a/app/views/admin/schedules/_event.html.haml b/app/views/admin/schedules/_event.html.haml index 3a13323b..ff6fd0c3 100644 --- a/app/views/admin/schedules/_event.html.haml +++ b/app/views/admin/schedules/_event.html.haml @@ -7,12 +7,13 @@ / subtracting the padding before calculate the number of lines - lines = (height - 7) / 23 - color = event.track.try(:color).present? ? event.track.try(:color) : 'FFFFFF' +- non_schedulable = event_schedule_id && (EventSchedule.find(event_schedule_id).schedule != @schedule) .schedule-event{ style: "height: #{height}px; background-color: #{color}; color: #{contrast_color(color)}", | id: "event-#{event.id}", | event_id: event.id, | length: cells_length, | event_schedule_id: event_schedule_id, | - class: ('compact' if compact_grid) } + class: "#{'compact' if compact_grid} #{'non_schedulable' if non_schedulable}" } .schedule-event-text{ style: "-webkit-line-clamp: #{lines}; height: #{lines * 23}px;"} %span.schedule-event-delete-button{ onclick: "Schedule.remove(\'event-#{event.id}\');" } X = event.title diff --git a/app/views/admin/schedules/_form.html.haml b/app/views/admin/schedules/_form.html.haml new file mode 100644 index 00000000..8c6c091d --- /dev/null +++ b/app/views/admin/schedules/_form.html.haml @@ -0,0 +1,10 @@ +.row + .col-md-12 + .page-header + %h1 + New Track Schedule +.row + .col-md-12 + = semantic_form_for @schedule, url: admin_conference_schedules_path(@conference.short_title) do |f| + = f.input :track, collection: Track.accessible_by(current_ability).where(program: @program).self_organized.confirmed.pluck(:name, :id), include_blank: false + = f.action :submit, as: :button, button_html: { class: 'btn btn-primary' } diff --git a/app/views/admin/schedules/index.html.haml b/app/views/admin/schedules/index.html.haml index acfc79a8..29c1910e 100644 --- a/app/views/admin/schedules/index.html.haml +++ b/app/views/admin/schedules/index.html.haml @@ -4,42 +4,85 @@ %h1 Schedules %p.text-muted The schedules for your conference -.row - .col-md-12 - %table.table.table-hover#event_types - %thead - %th Schedule - %th Selected - %th Actions - %tbody - - @schedules.each do |schedule| - %tr - %td - Schedule - = schedule.id - %td - = selected_scheduled?(schedule) - %td - .btn-group{role: "group"} - = link_to 'Show', admin_conference_schedule_path(@conference.short_title, schedule.id), - method: :get, class: 'btn btn-primary' - = link_to 'Delete', admin_conference_schedule_path(@conference.short_title, schedule.id), - method: :delete, class: 'btn btn-danger', data: { confirm: "Do you really want to delete Schedule #{schedule.id}?" } -.row - .col-md-12 - - if @venue.try(:rooms).present? - .text-right - = link_to 'Add Schedule', admin_conference_schedules_path(@conference.short_title), - method: :post, class: 'btn btn-primary' - - elsif @venue - .h3 - No Rooms! - %small - = link_to 'Create rooms', admin_conference_venue_rooms_path - before creating the schedule. - - else - .h3 - No Venue! - %small - = link_to 'Create a venue with rooms', new_admin_conference_venue_path - before creating the schedule. +.tabbable + %ul.nav.nav-tabs + %li.active + = link_to 'Conference schedules', '#conference', 'data-toggle' => 'tab' + %li + = link_to 'Track schedules', '#tracks', 'data-toggle' => 'tab' + .tab-content + .tab-pane.active#conference + .row + .col-md-12 + %table.table.table-hover + %thead + %th Schedule + %th Selected + %th Actions + %tbody + - @schedules.where(track: nil).each do |schedule| + %tr + %td + Schedule + = schedule.id + %td + = selected_scheduled?(schedule) + %td + .btn-group{role: "group"} + - if can? :show, schedule + = link_to 'Show', admin_conference_schedule_path(@conference.short_title, schedule), + method: :get, class: 'btn btn-primary' + - if can? :destroy, schedule + = link_to 'Delete', admin_conference_schedule_path(@conference.short_title, schedule), + method: :delete, class: 'btn btn-danger', data: { confirm: "Do you really want to delete Schedule #{schedule.id}?" } + .row + .col-md-12 + - if @venue.try(:rooms).present? + .text-right + - if can? :create, @program.schedules.new + = link_to 'Add Schedule', admin_conference_schedules_path(@conference.short_title), + method: :post, class: 'btn btn-primary' + - elsif @venue + .h3 + No Rooms! + %small + = link_to 'Create rooms', admin_conference_venue_rooms_path + before creating the schedule. + - else + .h3 + No Venue! + %small + = link_to 'Create a venue with rooms', new_admin_conference_venue_path + before creating the schedule. + .tab-pane#tracks + .row + .col-md-12 + %table.table.table-hover + %thead + %th Schedule + %th Track + %th Selected + %th Actions + %tbody + - @schedules.where.not(track: nil).each do |schedule| + %tr + %td + Schedule + = schedule.id + %td + - track = schedule.track + = link_to track.name, admin_conference_program_track_path(@conference.short_title, track) + %td + = schedule == schedule.track.selected_schedule ? 'Yes' : 'No' + %td + .btn-group{role: "group"} + - if can? :show, schedule + = link_to 'Show', admin_conference_schedule_path(@conference.short_title, schedule), class: 'btn btn-primary' + - if can? :destroy, schedule + = link_to 'Delete', admin_conference_schedule_path(@conference.short_title, schedule), + method: :delete, class: 'btn btn-danger', data: { confirm: "Do you really want to delete Schedule #{schedule.id}?" } + .row + .col-md-12 + .text-right + - if can? :new, @program.schedules.build(track: @program.tracks.new) + = link_to 'Add Track Schedule', new_admin_conference_schedule_path(@conference.short_title), class: 'btn btn-primary' diff --git a/app/views/admin/schedules/show.html.haml b/app/views/admin/schedules/show.html.haml index 9b2d3571..9009fcbb 100644 --- a/app/views/admin/schedules/show.html.haml +++ b/app/views/admin/schedules/show.html.haml @@ -11,8 +11,15 @@ .row .col-md-2 Selected schedule - = check_box_tag @conference.short_title, @schedule.id, (@schedule.id == @selected_schedule.try(:id)), - method: :patch, url: (admin_conference_program_path(@conference.short_title) + '?[program][selected_schedule_id]='), + :ruby + if @schedule.track + value = @schedule == @schedule.track.selected_schedule + url = update_selected_schedule_admin_conference_program_track_path(@conference.short_title, @schedule.track) + '?[track][selected_schedule_id]=' + else + value = @schedule.id == @selected_schedule.try(:id) + url = admin_conference_program_path(@conference.short_title) + '?[program][selected_schedule_id]=' + end + = check_box_tag @conference.short_title, @schedule.id, value, method: :patch, url: url, class: 'switch-checkbox-schedule', data: { size: 'small', off_color: 'warning', on_text: 'Yes', diff --git a/app/views/admin/tracks/index.html.haml b/app/views/admin/tracks/index.html.haml index 77a7cf02..48c0d1b3 100644 --- a/app/views/admin/tracks/index.html.haml +++ b/app/views/admin/tracks/index.html.haml @@ -63,9 +63,19 @@ .btn-group{role: "group"} - if can? :edit, track = link_to 'Edit', edit_admin_conference_program_track_path(@conference.short_title, track), class: 'btn btn-primary' + - special_style = true - if can? :destroy, track = link_to 'Delete', admin_conference_program_track_path(@conference.short_title, track), method: :delete, class: 'btn btn-danger', data: { confirm: "Do you really want to delete #{track.name}? Attention: This track will be removed from all Events that have it set" } + - if track.self_organized? + - if track.selected_schedule + - if can? :show, track.selected_schedule + = link_to 'Show Schedule', admin_conference_schedule_path(@conference.short_title, track.selected_schedule), + class: 'btn btn-default' + - elsif can? :create, @program.schedules.build(track: track) + = button_to 'Create Schedule', admin_conference_schedules_path(@conference.short_title), + form: { class: 'btn', style: 'padding: 0px 0px; margin-top: -1px;' }, class: 'btn btn-default', + style: ('border-top-left-radius: 0; border-bottom-left-radius: 0;' if special_style), params: { 'schedule[track_id]' => track.id } .row .col-md-12.text-right = link_to 'New Track', new_admin_conference_program_track_path(@conference.short_title), class: 'btn btn-success' diff --git a/app/views/admin/tracks/show.html.haml b/app/views/admin/tracks/show.html.haml index 78f8830e..b8c9773e 100644 --- a/app/views/admin/tracks/show.html.haml +++ b/app/views/admin/tracks/show.html.haml @@ -21,10 +21,20 @@ - if can? :edit, @track = link_to 'Edit', edit_admin_conference_program_track_path(@conference.short_title, @track), method: :get, class: 'btn btn-primary' + - special_style = true - if can? :destroy, @track = link_to 'Delete', admin_conference_program_track_path(@conference.short_title, @track), method: :delete, class: 'btn btn-danger', data: { confirm: "Do you really want to delete #{@track.name}? Attention: This track will be removed from all Events that have it set" } + - if @track.self_organized? + - if @track.selected_schedule + - if can? :show, @track.selected_schedule + = link_to 'Show Schedule', admin_conference_schedule_path(@conference.short_title, @track.selected_schedule), + class: 'btn btn-default' + - elsif can? :create, @program.schedules.build(track: @track) + = button_to 'Create Schedule', admin_conference_schedules_path(@conference.short_title), + form: { class: 'btn', style: 'padding: 0px 0px; margin-top: -1px;' }, class: 'btn btn-default', + style: ('border-top-left-radius: 0; border-bottom-left-radius: 0;' if special_style), params: { 'schedule[track_id]' => @track.id } .row .col-md-12 %table.table diff --git a/app/views/proposals/show.html.haml b/app/views/proposals/show.html.haml index 2d38d3a8..c181e283 100644 --- a/app/views/proposals/show.html.haml +++ b/app/views/proposals/show.html.haml @@ -114,7 +114,7 @@ %dl %dt Start Time: %dd - = event.program.selected_event_schedules.find_by(event: event).start_time.strftime("%Y %B %e %H:%M") + = event.program.selected_event_schedules.find { |es| es.event == event }.start_time.strftime("%Y %B %e %H:%M") %br %dt Room: %dd diff --git a/app/views/schedules/_carousel.html.haml b/app/views/schedules/_carousel.html.haml index ce28e909..dde7e72c 100644 --- a/app/views/schedules/_carousel.html.haml +++ b/app/views/schedules/_carousel.html.haml @@ -29,7 +29,7 @@ %td.room{ style: "height: #{ td_height(@rooms) }px;" } .room.elipsis.break-words{ style: "-webkit-line-clamp: #{ room_lines(@rooms) }; height: #{ room_height(@rooms) }px;" } = room.name - - event_schedules = room.event_schedules.select{ |e| (e.schedule_id == @conference.program.selected_schedule.id) && (e.end_time > start_time) && (e.start_time <= (start_time + hrs_per_slide.hour)) } + - event_schedules = room.event_schedules.select{ |e| @selected_schedules_ids.include?(e.schedule_id) && (e.end_time > start_time) && (e.start_time <= (start_time + hrs_per_slide.hour)) } - (1..intervals).each do |i| - if span > 1 - span -= 1 diff --git a/app/views/tracks/index.html.haml b/app/views/tracks/index.html.haml index 31a9fe97..d17ee766 100644 --- a/app/views/tracks/index.html.haml +++ b/app/views/tracks/index.html.haml @@ -72,6 +72,8 @@ method: :patch, class: 'btn btn-mini btn-success', id: "resubmit_track_request_#{track.id}" - if can? :edit, track = link_to 'Edit', edit_conference_program_track_path(@conference.short_title, track), class: 'btn btn-default' + - if current_user.has_role? :track_organizer, track + = link_to 'Manage', admin_conference_program_track_path(@conference.short_title, track), class: 'btn btn-default' .row .col-md-12 diff --git a/app/views/tracks/show.html.haml b/app/views/tracks/show.html.haml index 6014f23e..c81d60ec 100644 --- a/app/views/tracks/show.html.haml +++ b/app/views/tracks/show.html.haml @@ -8,6 +8,8 @@ .btn-group.pull-right - if can? :edit, @track = link_to 'Edit Track request', edit_conference_program_track_path(@conference.short_title, @track), class: 'btn btn-primary' + - if current_user.has_role? :track_organizer, @track + = link_to 'Manage', admin_conference_program_track_path(@conference.short_title, @track), class: 'btn btn-default' .row .col-md-8 %dl.dl-horizontal diff --git a/config/routes.rb b/config/routes.rb index ec55b8a3..0e1cad2e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -40,7 +40,7 @@ Osem::Application.routes.draw do resources :comments, only: [:index] resources :conferences do resource :contact, except: [:index, :new, :create, :show, :destroy] - resources :schedules, only: [:index, :create, :show, :update, :destroy] + resources :schedules, except: [:edit, :update] resources :event_schedules, only: [:create, :update, :destroy] get 'commercials/render_commercial' => 'commercials#render_commercial' resources :commercials, only: [:index, :create, :update, :destroy] @@ -88,6 +88,7 @@ Osem::Application.routes.draw do patch :to_reject patch :reject patch :cancel + patch :update_selected_schedule end end resources :event_types diff --git a/db/migrate/20170809120927_add_track_reference_to_schedule.rb b/db/migrate/20170809120927_add_track_reference_to_schedule.rb new file mode 100644 index 00000000..2c2d7f1c --- /dev/null +++ b/db/migrate/20170809120927_add_track_reference_to_schedule.rb @@ -0,0 +1,5 @@ +class AddTrackReferenceToSchedule < ActiveRecord::Migration + def change + add_reference :schedules, :track, index: true, foreign_key: true + end +end diff --git a/db/migrate/20170814174637_add_selected_schedule_to_tracks.rb b/db/migrate/20170814174637_add_selected_schedule_to_tracks.rb new file mode 100644 index 00000000..05b7d417 --- /dev/null +++ b/db/migrate/20170814174637_add_selected_schedule_to_tracks.rb @@ -0,0 +1,6 @@ +class AddSelectedScheduleToTracks < ActiveRecord::Migration + def change + add_column :tracks, :selected_schedule_id, :integer + add_index :tracks, :selected_schedule_id + end +end diff --git a/db/schema.rb b/db/schema.rb index d9949b1e..82f5923d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -431,9 +431,11 @@ ActiveRecord::Schema.define(version: 20170816203325) do t.integer "program_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "track_id" end add_index "schedules", ["program_id"], name: "index_schedules_on_program_id" + add_index "schedules", ["track_id"], name: "index_schedules_on_track_id" create_table "splashpages", force: :cascade do |t| t.integer "conference_id" @@ -520,24 +522,26 @@ ActiveRecord::Schema.define(version: 20170816203325) do end create_table "tracks", force: :cascade do |t| - t.string "guid", null: false - t.string "name", null: false + t.string "guid", null: false + t.string "name", null: false t.text "description" t.string "color" t.datetime "created_at" t.datetime "updated_at" t.integer "program_id" - t.string "short_name", null: false - t.string "state", default: "new", null: false - t.boolean "cfp_active", null: false + t.string "short_name", null: false + t.string "state", default: "new", null: false + t.boolean "cfp_active", null: false t.integer "submitter_id" t.integer "room_id" t.date "start_date" t.date "end_date" t.text "relevance" + t.integer "selected_schedule_id" end add_index "tracks", ["room_id"], name: "index_tracks_on_room_id" + add_index "tracks", ["selected_schedule_id"], name: "index_tracks_on_selected_schedule_id" add_index "tracks", ["submitter_id"], name: "index_tracks_on_submitter_id" create_table "users", force: :cascade do |t| diff --git a/spec/features/track_organizer_ability_spec.rb b/spec/features/track_organizer_ability_spec.rb index 69b4d3e5..67e0c8de 100644 --- a/spec/features/track_organizer_ability_spec.rb +++ b/spec/features/track_organizer_ability_spec.rb @@ -4,7 +4,7 @@ feature 'Has correct abilities' do let(:organization) { create(:organization) } let(:conference) { create(:full_conference, organization: organization) } - let(:self_organized_track) { create(:track, :self_organized, program: conference.program, state: 'confirmed', cfp_active: true) } + let(:self_organized_track) { create(:track, :self_organized, program: conference.program, state: 'confirmed') } let(:role_track_organizer) { Role.where(name: 'track_organizer', resource: self_organized_track).first_or_create } let(:user_track_organizer) { create(:user, role_ids: [role_track_organizer.id]) } @@ -32,7 +32,7 @@ feature 'Has correct abilities' do expect(page).to have_link('Tracks', href: "/admin/conferences/#{conference.short_title}/program/tracks") expect(page).to_not have_link('Event Types', href: "/admin/conferences/#{conference.short_title}/program/event_types") expect(page).to_not have_link('Difficulty Levels', href: "/admin/conferences/#{conference.short_title}/program/difficulty_levels") - expect(page).to_not have_link('Schedules', href: "/admin/conferences/#{conference.short_title}/schedules") + expect(page).to have_link('Schedules', href: "/admin/conferences/#{conference.short_title}/schedules") expect(page).to have_link('Reports', href: "/admin/conferences/#{conference.short_title}/program/reports") expect(page).to_not have_link('Registrations', href: "/admin/conferences/#{conference.short_title}/registrations") expect(page).to_not have_link('Registration Period', href: "/admin/conferences/#{conference.short_title}/registration_period") @@ -130,12 +130,16 @@ feature 'Has correct abilities' do expect(current_path).to eq root_path visit admin_conference_schedules_path(conference.short_title) - expect(current_path).to eq root_path + expect(current_path).to eq admin_conference_schedules_path(conference.short_title) create(:schedule, program: conference.program) visit admin_conference_schedule_path(conference.short_title, conference.program.schedules.first) expect(current_path).to eq root_path + self_organized_track_schedule = create(:schedule, program: conference.program, track: self_organized_track) + visit admin_conference_schedule_path(conference.short_title, self_organized_track_schedule) + expect(current_path).to eq admin_conference_schedule_path(conference.short_title, self_organized_track_schedule) + visit admin_conference_program_reports_path(conference.short_title) expect(current_path).to eq admin_conference_program_reports_path(conference.short_title) diff --git a/spec/models/admin_ability_spec.rb b/spec/models/admin_ability_spec.rb index 126668fa..344f63ba 100644 --- a/spec/models/admin_ability_spec.rb +++ b/spec/models/admin_ability_spec.rb @@ -45,7 +45,7 @@ describe 'User with admin role' do let!(:my_event_schedule) { create(:event_schedule, schedule: my_schedule) } let!(:other_event_schedule) { create(:event_schedule, schedule: other_schedule) } - let!(:my_self_organized_track) { create(:track, :self_organized, program: my_conference.program, state: 'confirmed', cfp_active: true) } + let!(:my_self_organized_track) { create(:track, :self_organized, program: my_conference.program, state: 'confirmed') } context 'user #is_admin?' do let(:venue) { my_conference.venue } @@ -460,8 +460,12 @@ describe 'User with admin role' do let(:user) { create(:user, role_ids: [role.id]) } let(:new_track) { build(:track, program: my_conference.program) } let(:new_event) { build(:event, program: my_conference.program) } + let(:new_schedule) { build(:schedule, program: my_conference.program) } + let(:new_track_schedule) { build(:schedule, program: my_conference.program, track: new_track) } let(:my_self_organized_track_event) { create(:event, program: my_conference.program, track: my_self_organized_track) } let(:my_self_organized_track_event_commercial) { create(:commercial, commercialable: my_self_organized_track_event) } + let(:my_self_organized_track_schedule) { create(:schedule, program: my_conference.program, track: my_self_organized_track) } + let(:my_self_organized_track_event_schedule) { create(:event_schedule, event: my_self_organized_track_event, schedule: my_self_organized_track_schedule, room: my_self_organized_track.room) } it{ should_not be_able_to(:new, Conference.new) } it{ should_not be_able_to(:create, Conference.new) } @@ -532,6 +536,11 @@ describe 'User with admin role' do it{ should be_able_to(:manage, my_self_organized_track_event) } it{ should be_able_to(:manage, my_self_organized_track_event_commercial) } + it{ should be_able_to(:update, new_schedule) } + it{ should be_able_to(:new, new_track_schedule) } + it{ should be_able_to(:manage, my_self_organized_track_schedule) } + it{ should be_able_to(:manage, my_self_organized_track_event_schedule) } + it_behaves_like 'user with any role' it_behaves_like 'user with non-organizer role', 'track_organizer' end diff --git a/spec/models/event_schedule_spec.rb b/spec/models/event_schedule_spec.rb index 8e22430b..91db4b64 100644 --- a/spec/models/event_schedule_spec.rb +++ b/spec/models/event_schedule_spec.rb @@ -46,7 +46,7 @@ describe EventSchedule do end end - describe '#room_of_track' do + describe '#same_room_as_track' do before :each do conference = create(:conference) conference.venue = create(:venue) @@ -108,5 +108,39 @@ describe EventSchedule do end end end + + describe '#valid_schedule' do + before :each do + conference.venue = create(:venue) + @room = create(:room, venue: conference.venue) + track = create(:track, :self_organized, program: conference.program, room: @room, state: 'confirmed', name: 'My awesome track') + @event = create(:event, program: conference.program, track: track) + end + + context 'is valid' do + it 'when the event belongs to a self-organized track and is scheduled in one of its track\'s schedules' do + schedule = create(:schedule, program: conference.program, track: @event.track) + event_schedule = build(:event_schedule, event: @event, room: @room, schedule: schedule) + expect(event_schedule.valid?).to eq true + expect(event_schedule.errors[:schedule]).to eq [] + end + + it 'when the event doesn\'t belong to a self-organized track' do + @event.track = nil + @event.save! + event_schedule = build(:event_schedule, event: @event, room: @room) + expect(event_schedule.valid?).to eq true + expect(event_schedule.errors[:schedule]).to eq [] + end + end + + context 'is invalid' do + it 'when the event belongs to a self_organized track but isn\'t scheduled in one of its schedules' do + event_schedule = build(:event_schedule, event: @event, room: @room) + expect(event_schedule.valid?).to eq false + expect(event_schedule.errors[:schedule]).to eq ['must be one of My awesome track track\'s schedules'] + end + end + end end end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 3ada61ca..0a84aedd 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -97,10 +97,10 @@ describe Event do end end - describe '#acceptable_track' do + describe '#valid_track' do context 'is valid' do - it 'when the track belong to the same program, is confirmed and is included in the cfp' do - track = create(:track, state: 'confirmed', cfp_active: true, program: conference.program) + it 'when the track belongs to the same program and is confirmed' do + track = create(:track, state: 'confirmed', program: conference.program) event = build(:event, program: conference.program, track: track) expect(event.valid?).to eq true end @@ -108,26 +108,19 @@ describe Event do context 'is invalid' do it 'when the track doesn\'t have the same program' do - track = create(:track, state: 'confirmed', cfp_active: true) + track = create(:track, state: 'confirmed') event = build(:event, program: conference.program, track: track) expect(event.valid?).to eq false expect(event.errors[:track]).to eq ['is invalid'] end it 'when the track is unconfirmed' do - track = create(:track, cfp_active: true, program: conference.program) + track = create(:track, program: conference.program) allow(track).to receive(:confirmed?).and_return(false) event = build(:event, program: conference.program, track: track) expect(event.valid?).to eq false expect(event.errors[:track]).to eq ['is invalid'] end - - it 'when the track isn\'t included in the cfp' do - track = create(:track, state: 'confirmed', cfp_active: false, program: conference.program) - event = build(:event, program: conference.program, track: track) - expect(event.valid?).to eq false - expect(event.errors[:track]).to eq ['is invalid'] - end end end end @@ -382,4 +375,30 @@ describe Event do expect(other_event.week).to eq 48 end end + + describe '#selected_schedule_id' do + before :each do + conference.program.selected_schedule = create(:schedule, program: conference.program) + end + + context 'returns the program\'s selected_schedule_id' do + it 'when it doesn\'t have a track' do + create(:event_schedule, event: event, schedule: conference.program.selected_schedule) + expect(event.send(:selected_schedule_id)).to eq conference.program.selected_schedule_id + end + + it 'when it belongs to a regular track' do + event.track = create(:track, program: conference.program) + expect(event.send(:selected_schedule_id)).to eq conference.program.selected_schedule_id + end + end + + context 'returns the track\'s selected_schedule_id' do + it 'when it belongs to a self-organized track' do + event.track = create(:track, :self_organized, program: conference.program, state: 'confirmed') + event.track.selected_schedule = create(:schedule, program: conference.program, track: event.track) + expect(event.send(:selected_schedule_id)).to eq event.track.selected_schedule_id + end + end + end end diff --git a/spec/models/schedule_spec.rb b/spec/models/schedule_spec.rb index 6d1f7346..2dc1b826 100644 --- a/spec/models/schedule_spec.rb +++ b/spec/models/schedule_spec.rb @@ -4,6 +4,7 @@ describe Schedule do describe 'association' do it { should belong_to(:program) } + it { should belong_to(:track) } it { should have_many(:event_schedules).dependent(:destroy) } it { should have_many(:events).through(:event_schedules) } end diff --git a/spec/models/track_spec.rb b/spec/models/track_spec.rb index f9c53653..d356a2c7 100644 --- a/spec/models/track_spec.rb +++ b/spec/models/track_spec.rb @@ -9,7 +9,9 @@ describe Track do it { is_expected.to belong_to(:program) } it { is_expected.to belong_to(:submitter).class_name('User') } it { is_expected.to belong_to(:room) } + it { is_expected.to belong_to(:selected_schedule).class_name('Schedule') } it { is_expected.to have_many(:events) } + it { is_expected.to have_many(:schedules) } end describe 'validation' do @@ -145,20 +147,20 @@ describe Track do end context 'is valid' do - it 'when the tracks are in different rooms' do + it 'when the tracks are in different rooms at the same time' do other_room = create(:room, venue: @conference.venue) create(:track, :self_organized, state: 'confirmed', program: @conference.program, room: other_room, start_date: Date.current, end_date: Date.current) track = build(:track, :self_organized, program: @conference.program, room: @room, start_date: Date.current, end_date: Date.current) expect(track.valid?).to eq true end - it 'when it ends before the other tracks' do + it 'when it ends before the other tracks in the same room' do create(:track, :self_organized, state: 'confirmed', program: @conference.program, room: @room, start_date: Date.current, end_date: Date.current) track = build(:track, :self_organized, program: @conference.program, room: @room, start_date: Date.current - 1.day, end_date: Date.current - 1.day) expect(track.valid?).to eq true end - it 'when it starts after the other tracks' do + it 'when it starts after the other tracks in the same room' do create(:track, :self_organized, state: 'confirmed', program: @conference.program, room: @room, start_date: Date.current, end_date: Date.current) track = build(:track, :self_organized, program: @conference.program, room: @room, start_date: Date.current + 1.day, end_date: Date.current + 1.day) expect(track.valid?).to eq true @@ -166,7 +168,7 @@ describe Track do end context 'is invalid' do - it 'when it starts or ends with another track in the same room' do + it 'when it starts and/or ends with another track in the same room' do create(:track, :self_organized, state: 'confirmed', program: @conference.program, room: @room, start_date: Date.current, end_date: Date.current) track = build(:track, :self_organized, program: @conference.program, room: @room, start_date: Date.current, end_date: Date.current) expect(track.valid?).to eq false @@ -264,6 +266,24 @@ describe Track do expect(@program.tracks.cfp_active.include?(@non_cfp_active_track)).to eq false end end + + describe '#self_organized' do + before :each do + @program = create(:program) + track.program = @program + track.save! + self_organized_track.program = @program + self_organized_track.save! + end + + it 'includes self-organized tracks' do + expect(@program.tracks.self_organized.include?(self_organized_track)).to eq true + end + + it 'excludes regular tracks' do + expect(@program.tracks.self_organized.include?(track)).to eq false + end + end end describe '#self_organized?' do @@ -334,7 +354,8 @@ describe Track do self_organized_track.cfp_active = true self_organized_track.save! @a_track_organizer.add_role 'track_organizer', self_organized_track - @an_event_of_the_track = create(:event, program: self_organized_track.program, track: self_organized_track) + @event_of_self_organized_track = create(:event, program: self_organized_track.program, track: self_organized_track, state: 'confirmed') + @schedule_of_self_organized_track = create(:schedule, program: self_organized_track.program, track: self_organized_track) end it 'revokes the role of the track organizer' do @@ -343,11 +364,24 @@ describe Track do expect(@a_track_organizer.has_role?(:track_organizer, self_organized_track)).to eq false end - it 'removes the track from the events that have it set' do - expect(@an_event_of_the_track.track).to eq self_organized_track + it 'destroys the track\'s schedules' do + expect(Schedule.find(@schedule_of_self_organized_track.id)).to eq @schedule_of_self_organized_track self_organized_track.revoke_role_and_cleanup - @an_event_of_the_track.reload - expect(@an_event_of_the_track.track).to eq nil + expect(Schedule.find_by(id: @schedule_of_self_organized_track.id)).to eq nil + end + + it 'removes the track from the events that have it set' do + expect(@event_of_self_organized_track.track).to eq self_organized_track + self_organized_track.revoke_role_and_cleanup + @event_of_self_organized_track.reload + expect(@event_of_self_organized_track.track).to eq nil + end + + it 'sets the state of the track\'s events to new' do + expect(@event_of_self_organized_track.state).to eq 'confirmed' + self_organized_track.revoke_role_and_cleanup + @event_of_self_organized_track.reload + expect(@event_of_self_organized_track.state).to eq 'new' end it 'is executed when the track is canceled' do @@ -355,15 +389,15 @@ describe Track do self_organized_track.save! self_organized_track.cancel expect(@a_track_organizer.has_role?(:track_organizer, self_organized_track)).to eq false - @an_event_of_the_track.reload - expect(@an_event_of_the_track.track).to eq nil + @event_of_self_organized_track.reload + expect(@event_of_self_organized_track.track).to eq nil end it 'is executed when the track is withdrawn' do self_organized_track.withdraw expect(@a_track_organizer.has_role?(:track_organizer, self_organized_track)).to eq false - @an_event_of_the_track.reload - expect(@an_event_of_the_track.track).to eq nil + @event_of_self_organized_track.reload + expect(@event_of_self_organized_track.track).to eq nil end end