Introduce Booths for admin

This commit is contained in:
nasia 2017-05-17 13:31:27 +03:00 committed by Stella Rouzi
parent 29aee82a14
commit f7c0b64eb5
22 changed files with 534 additions and 1 deletions

View file

@ -11,6 +11,9 @@ 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"
- "app/views/admin/campaigns/index.html.haml"
- "app/views/admin/cfps/_form.html.haml"
@ -179,6 +182,7 @@ linters:
# Offense count: 223
InstanceVariables:
exclude:
- "app/views/admin/booths/_change_state_dropdown.html.haml"
- "app/views/admin/campaigns/_form.html.haml"
- "app/views/admin/cfps/_booths_cfp.html.haml"
- "app/views/admin/cfps/_form.html.haml"

View file

@ -0,0 +1,94 @@
module Admin
class BoothsController < Admin::BaseController
load_and_authorize_resource :conference, find_by: :short_title
load_and_authorize_resource through: :conference
def index; end
def show; end
def new; end
def create
@booth = @conference.booths.new(booth_params)
@booth.submitter = current_user
if @booth.save
redirect_to admin_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; end
def update
@booth.update_attributes(booth_params)
if @booth.save
redirect_to admin_conference_booths_path,
notice: "Successfully updated booth for #{@booth.title}."
else
flash[:error] = "An error prohibited the Booth for #{@booth.title} "\
"#{@booth.errors.full_messages.join('. ')}."
render :edit
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
def to_accept
update_state(:to_accept, 'Booth to accept')
end
def to_reject
update_state(:to_reject, 'Booth to reject')
end
def reject
update_state(:reject, 'Booth rejected')
end
def restart
update_state(:restart, 'Booth is submitted')
end
def cancel
update_state(:cancel, 'Booth is canceled')
end
private
def update_state(transition, notice)
alert = @booth.update_state(transition, notice)
if alert.blank?
flash[:notice] = notice
redirect_back_or_to(admin_conference_booths_path(conference_id: @conference.short_title)) && return
else
flash[:error] = alert
return redirect_back_or_to(admin_conference_booths_path(conference_id: @conference.short_title)) && return
end
end
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
end

View file

@ -162,6 +162,13 @@ module ApplicationHelper
include_blank: false, label: 'Speakers', input_html: { class: 'select-help-toggle', multiple: 'true' }
end
def responsibles_selector_input(form)
users = User.active.pluck(:id, :name, :username, :email).map { |user| [user[0], user[1].blank? ? user[2] : user[1], user[2], user[3]] }.sort_by { |user| user[1].downcase }
form.input :responsibles, as: :select,
collection: options_for_select(users.map {|user| ["#{user[1]} (#{user[2]}) #{user[3]}", user[0]]}, @booth.responsibles.map(&:id)),
include_blank: false, label: 'Responsibles', input_html: { class: 'select-help-toggle', multiple: 'true' }
end
def event_types(conference)
conference.program.event_types.map { |et| et.title.pluralize }.to_sentence
end

View file

@ -120,6 +120,7 @@ class AdminAbility
commercialable_id: conf_ids
can :manage, Registration, conference_id: conf_ids
can :manage, RegistrationPeriod, conference_id: conf_ids
can :manage, Booth, conference_id: conf_ids
can :manage, Question, conference_id: conf_ids
can :manage, Question do |question|
!(question.conferences.pluck(:id) & conf_ids).empty?
@ -170,6 +171,7 @@ class AdminAbility
conf_ids_for_cfp.include?(conf.id)
end
can [:index, :show, :update], Resource, conference_id: conf_ids_for_cfp
can :manage, Booth, conference_id: conf_ids_for_cfp
can :manage, Event, program: { conference_id: conf_ids_for_cfp }
can :manage, EventType, program: { conference_id: conf_ids_for_cfp }
can :manage, Track, program: { conference_id: conf_ids_for_cfp }

75
app/models/booth.rb Normal file
View file

@ -0,0 +1,75 @@
class Booth < ActiveRecord::Base
include ActiveRecord::Transitions
belongs_to :conference
has_many :booth_requests, dependent: :destroy
has_many :users, through: :booth_requests
has_one :submitter_booth_user, -> { where(role: 'submitter') }, class_name: 'BoothRequest'
has_one :submitter, through: :submitter_booth_user, source: :user
has_many :responsibles_booth_user, -> { where(role: 'responsible') }, class_name: 'BoothRequest'
has_many :responsibles, through: :responsibles_booth_user, source: :user
validates :title,
uniqueness: { case_sensitive: false },
presence: true
validates :description,
:reasoning,
:state,
:responsibles,
:conference_id,
:website_url,
:submitter_relationship,
presence: true
mount_uploader :picture, PictureUploader, mount_on: :logo_link
state_machine initial: :new do
state :new
state :withdrawn
state :to_accept
state :accepted
state :to_reject
state :rejected
state :canceled
event :restart do
transitions to: :new, from: [:withdrawn, :to_accept, :to_reject, :canceled]
end
event :withdraw do
transitions to: :withdrawn, from: [:new, :to_accept, :accepted, :to_reject, :rejected]
end
event :to_accept do
transitions to: :to_accept, from: [:new, :to_reject]
end
event :to_reject do
transitions to: :to_reject, from: [:new, :to_accept]
end
event :accept do
transitions to: :accepted, from: [:new, :to_accept]
end
event :reject do
transitions to: :rejected, from: [:new, :to_reject]
end
event :cancel do
transitions to: :canceled, from: [:accepted, :rejected]
end
end
def transition_possible?(transition)
self.class.state_machine.events_for(current_state).include?(transition)
end
def update_state(transition, _notice)
alert = ''
begin
send(transition)
save
rescue Transitions::InvalidTransition => e
alert = "Update state failed. #{e.message}"
end
alert
end
end

View file

@ -0,0 +1,9 @@
class BoothRequest < ActiveRecord::Base
belongs_to :booth
belongs_to :user
validates :role,
presence: true
ROLES = %w[submitter responsible].freeze
end

View file

@ -28,6 +28,7 @@ class Conference < ActiveRecord::Base
has_many :supporters, through: :ticket_purchases, source: :user
has_many :tickets, dependent: :destroy
has_many :resources, dependent: :destroy
has_many :booths, dependent: :destroy
has_many :lodgings, dependent: :destroy
has_many :registrations, dependent: :destroy

View file

@ -56,6 +56,9 @@ class User < ActiveRecord::Base
has_many :voted_events, through: :votes, source: :events
has_many :subscriptions, dependent: :destroy
has_many :tracks, foreign_key: 'submitter_id'
has_many :booth_requests
has_many :booth_requests, dependent: :destroy
has_many :booths, through: :booth_requests
accepts_nested_attributes_for :roles
scope :admin, -> { where(is_admin: true) }

View file

@ -0,0 +1,29 @@
- if booth.transition_possible? :accept
%li= link_to 'Accept booth',
accept_admin_conference_booth_path(@conference.short_title, booth),
method: :patch, id: "accept_booth_#{booth.id}"
- if booth.transition_possible? :reject
%li= link_to 'Reject booth',
reject_admin_conference_booth_path(@conference.short_title, booth),
method: :patch, confirm: 'Are you sure?', 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}"
- if booth.transition_possible? :restart
%li= link_to 'Start review',
restart_admin_conference_booth_path(@conference.short_title, booth),
method: :patch, id: "restart_booth_#{booth.id}"
- if booth.transition_possible? :to_accept
%li= link_to 'To accept booth',
to_accept_admin_conference_booth_path(@conference.short_title, booth),
method: :patch, id: "to_accept_booth_#{booth.id}"
- if booth.transition_possible? :cancel
%li= link_to 'Cancel booth',
cancel_admin_conference_booth_path(@conference.short_title, booth),
method: :patch, id: "cancel_booth_#{booth.id}"

View file

@ -0,0 +1,33 @@
.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, 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: 'Your personal relationship with the organization you are applying for',
hint: 'e.g. employee, member of the open source community 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
} )
});

View file

@ -0,0 +1,5 @@
%h1
Editing
= @booth.title
= render 'form'

View file

@ -0,0 +1,58 @@
.row
.col-md-12
.page-header
%h1
Booths
= "(#{@booths.length})" if @booths.any?
.pull-right
- if can? :create, Booth
= 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
.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
%td
= booth.id
%td
= 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 do |responsible|
= link_to responsible.name, admin_user_path(responsible)
%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
.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?"}

View file

@ -0,0 +1,3 @@
%h1 New booth
= render 'form'

View file

@ -0,0 +1,57 @@
.row
.col-md-12
%h3
= 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, admin_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 do |responsibles|
.responsibles
= link_to responsibles.name, admin_user_path(responsibles)
(
= responsibles.email
)
%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

View file

@ -37,6 +37,19 @@ Osem::Application.routes.draw do
get '/volunteers' => 'volunteers#index', as: 'volunteers_info'
patch '/volunteers' => 'volunteers#update', as: 'volunteers_update'
resources :booths do
member do
patch :accept
patch :restart
patch :withdrawn
patch :to_accept
patch :reject
patch :reset
patch :to_reject
patch :cancel
end
end
resources :registrations, except: [:create, :new] do
member do
patch :toggle_attendance

View file

@ -0,0 +1,16 @@
class CreateBooths < ActiveRecord::Migration
def change
create_table :booths do |t|
t.string :title
t.text :description
t.text :reasoning
t.string :state
t.string :logo_link
t.string :website_url
t.text :submitter_relationship
t.references :conference
t.timestamps null: false
end
end
end

View file

@ -0,0 +1,11 @@
class CreateBoothRequests < ActiveRecord::Migration
def change
create_table :booth_requests do |t|
t.references :booth, index: true, foreign_key: true
t.references :user, index: true, foreign_key: true
t.string :role
t.timestamps null: false
end
end
end

View file

@ -14,7 +14,7 @@
ActiveRecord::Schema.define(version: 20170711102511) do
create_table "ahoy_events", force: :cascade do |t|
t.uuid "visit_id", limit: 16
t.integer "visit_id"
t.integer "user_id"
t.string "name"
t.text "properties"
@ -31,6 +31,30 @@ ActiveRecord::Schema.define(version: 20170711102511) do
t.datetime "updated_at"
end
create_table "booth_requests", force: :cascade do |t|
t.integer "booth_id"
t.integer "user_id"
t.string "role"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "booth_requests", ["booth_id"], name: "index_booth_requests_on_booth_id"
add_index "booth_requests", ["user_id"], name: "index_booth_requests_on_user_id"
create_table "booths", force: :cascade do |t|
t.string "title"
t.text "description"
t.text "reasoning"
t.string "state"
t.string "logo_link"
t.string "website_url"
t.text "submitter_relationship"
t.integer "conference_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "campaigns", force: :cascade do |t|
t.integer "conference_id"
t.string "name"

15
spec/factories/booths.rb Normal file
View file

@ -0,0 +1,15 @@
FactoryGirl.define do
factory :booth do
title { Faker::Hipster.sentence }
description { Faker::Lorem.paragraph }
reasoning { Faker::Lorem.paragraph }
website_url { Faker::Internet.url }
submitter_relationship { Faker::Lorem.paragraph }
conference
after(:build) do |booth|
booth.responsibles << create(:user)
end
end
end

View file

@ -249,6 +249,16 @@ feature 'Has correct abilities' do
visit admin_conference_roles_path(conference.short_title)
expect(current_path).to eq(admin_conference_roles_path(conference.short_title))
visit admin_conference_booths_path(conference.short_title)
expect(current_path).to eq(admin_conference_booths_path(conference.short_title))
visit new_admin_conference_booth_path(conference.short_title)
expect(current_path).to eq(new_admin_conference_booth_path(conference.short_title))
create(:booth, conference: conference)
visit edit_admin_conference_booth_path(conference.short_title, conference.booths.first)
expect(current_path).to eq(edit_admin_conference_booth_path(conference.short_title, conference.booths.first))
visit admin_conference_resources_path(conference.short_title)
expect(current_path).to eq(admin_conference_resources_path(conference.short_title))

View file

@ -237,6 +237,16 @@ feature 'Has correct abilities' do
visit edit_admin_conference_target_path(conference.short_title, conference.targets.first)
expect(current_path).to eq(edit_admin_conference_target_path(conference.short_title, conference.targets.first))
visit admin_conference_booths_path(conference.short_title)
expect(current_path).to eq(admin_conference_booths_path(conference.short_title))
visit new_admin_conference_booth_path(conference.short_title)
expect(current_path).to eq(new_admin_conference_booth_path(conference.short_title))
create(:booth, conference: conference)
visit edit_admin_conference_booth_path(conference.short_title, conference.booths.first)
expect(current_path).to eq(edit_admin_conference_booth_path(conference.short_title, conference.booths.first))
visit admin_conference_program_tracks_path(conference.short_title)
expect(current_path).to eq(admin_conference_program_tracks_path(conference.short_title))

54
spec/models/booth_spec.rb Normal file
View file

@ -0,0 +1,54 @@
require 'spec_helper'
describe 'Booth' do
subject { create(:booth) }
let!(:conference) { create(:conference) }
describe 'validation' do
it 'has a valid factory' do
expect(build(:booth)).to be_valid
end
it { is_expected.to validate_presence_of(:reasoning) }
it { is_expected.to validate_presence_of(:description) }
it { is_expected.to validate_presence_of(:responsibles) }
it { is_expected.to validate_presence_of(:submitter_relationship) }
it { is_expected.to validate_presence_of(:website_url) }
it 'is not valid without a title' do
is_expected.to validate_presence_of(:title)
end
end
describe 'association' do
it { is_expected.to belong_to(:conference) }
it { is_expected.to have_many(:booth_requests) }
end
describe '#transition_possible?(transition)' do
shared_examples 'transition_possible?(transition)' do |state, transition, expected|
it "returns #{expected} for #{transition} transition, when the booth is #{state}}" do
my_booth = create(:booth, state: state)
expect(my_booth.transition_possible?(transition.to_sym)).to eq expected
end
end
states = [:new, :withdrawn, :to_accept, :accepted, :to_reject, :rejected, :canceled]
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: false },
to_reject: { restart: true, withdraw: true, accept: false, to_accept: true, to_reject: false, reject: true, cancel: false },
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.each do |state|
transitions.each do |transition|
it_behaves_like 'transition_possible?(transition)', state, transition, states_transitions[state.to_sym][transition.to_sym]
end
end
end
end