Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rosters #43

Merged
merged 55 commits into from
Apr 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
76d5b96
Update seed file and Gemfile, and write rotation model
dfaulken Apr 3, 2017
d24b339
Merge branch 'master' of github.com:umts/screaming-dinosaur into rota…
dfaulken Apr 3, 2017
5bb0716
Add needed columns
dfaulken Apr 3, 2017
533e9a8
Passing specs
dfaulken Apr 3, 2017
2b43bad
Developer login and index work
dfaulken Apr 3, 2017
a49faab
Rotation edit page works
dfaulken Apr 3, 2017
6a03af1
Fixed overlap validation
dfaulken Apr 3, 2017
acee8bb
User edit works
dfaulken Apr 3, 2017
74bad9c
Remove fallback validation from user
dfaulken Apr 3, 2017
759daf3
New user works
dfaulken Apr 3, 2017
b8ddc2f
Basic rotations index
dfaulken Apr 3, 2017
f172150
Rotations edit works
dfaulken Apr 3, 2017
44a96c1
Initial login logic
dfaulken Apr 4, 2017
5d76162
Show links for each rotation a person is in
dfaulken Apr 4, 2017
9d0bc49
Just display calendar for people in only one rotation
dfaulken Apr 4, 2017
0d67bc6
Adding users to multiple rotations
dfaulken Apr 4, 2017
4e95b50
Generating rotations works
dfaulken Apr 4, 2017
24a2a57
Remove old method
dfaulken Apr 4, 2017
71c15c1
Twilio actions are scoped per-rotation
dfaulken Apr 4, 2017
43acf34
No special user validations anymore
dfaulken Apr 4, 2017
4250a08
Remove tests for disused model method
dfaulken Apr 4, 2017
1226cf3
Move specs from Assignment to Rotation
dfaulken Apr 4, 2017
f09b62c
Fix final failing assignment model test
dfaulken Apr 4, 2017
8bed699
Complete assignment model tests
dfaulken Apr 4, 2017
78571c1
Make sure Assignment#current is scoped to the target assignments' rot…
dfaulken Apr 4, 2017
79e0595
Finish porting the previous rotation generation specs
dfaulken Apr 4, 2017
41e3183
Finish rotation model specs
dfaulken Apr 4, 2017
c14aa68
Test SessionsController
dfaulken Apr 4, 2017
bf14c3f
Test Twilio controller
dfaulken Apr 4, 2017
4e0a564
Current coverage
dfaulken Apr 4, 2017
2fe4d2b
Fill out the remaining Rotation CRUD routes
dfaulken Apr 4, 2017
ce506b4
Fix reference to disused model method
dfaulken Apr 4, 2017
ccd0e4f
Link to manage rotations
dfaulken Apr 4, 2017
2799f8d
Validate for rotation presence
dfaulken Apr 4, 2017
4fa0e3a
Reorganize 'navbar'
dfaulken Apr 4, 2017
1129106
Fix making new assignments and validation
dfaulken Apr 5, 2017
2dbbf08
Extract links into a partial
dfaulken Apr 5, 2017
8ef75d7
Some links CSS, and show current rotation more obviously
dfaulken Apr 5, 2017
a9cd43b
The end of the styling tweaks
dfaulken Apr 5, 2017
95968d0
use correct path
Anbranin Apr 12, 2017
e40a8ec
fix tests
Anbranin Apr 12, 2017
852a130
change factories to reflect correct attributes
Anbranin Apr 12, 2017
883275f
find rotation before every controller action
Anbranin Apr 12, 2017
622fbbc
called already before all controller actions
Anbranin Apr 12, 2017
9a027e2
do not override application controller method
Anbranin Apr 12, 2017
39a8e03
called already before all controller actions
Anbranin Apr 12, 2017
610d15a
fixed specs
Anbranin Apr 12, 2017
383a843
change spec to be passing
Anbranin Apr 12, 2017
f2ea6df
started new spec file
Anbranin Apr 12, 2017
dd46bf2
rename rotation to roster
Anbranin Apr 14, 2017
ea9975f
testing rosters
Anbranin Apr 14, 2017
23f7b86
bring test coverage up to 100
Anbranin Apr 14, 2017
4684d59
write method to create user with specific roster
Anbranin Apr 14, 2017
2280b51
rubocop failures
Anbranin Apr 14, 2017
f0603cf
rubocop tweaking
Anbranin Apr 14, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
require: umts-custom-cops
inherit_from: .rubocop-no-umts-cops.yml

HasAndBelongsToMany:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I like to add comments explaining why we're disagreeing with Rubocop.

Enabled: false
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ Other candidates for the repo name included:
+ nervous-neutron

Rails 4 app for management of Transportation IT on-call schedule and interaction with Twilio.

## Development

When seeding, skip creating assignments with `SKIP_ASSIGNMENTS=true rake db:reset` etc.
16 changes: 0 additions & 16 deletions app/assets/stylesheets/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,6 @@ body{
border: 3px solid limegreen;
}

#current_user_name{
color: lightgrey;
float: right;
margin-right: 2em;
text-align: right;
}
#links{
float: right;
padding: 0 2em 0 0;
margin: 1em;
}
#links a{
font-size: 16px;
margin-right: 5px;
}

.left{
float: left;
}
Expand Down
28 changes: 28 additions & 0 deletions app/assets/stylesheets/layouts/links.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#links{
background: #881c1c;
color: white;
float: right;
font-size: 18px;
padding: 0.5em 1em;
.group{
padding: 5px 0;
&.session{
padding-bottom: 20px;
#logout{
float: right;
margin-right: 2em;
text-align: right;
}
}
a{
color: #ddd;
padding-right: 1em;
&.current{
background: #d94545;
border-radius: 6px;
color: white;
padding: 5px 10px;
}
}
}
}
11 changes: 10 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
class ApplicationController < ActionController::Base
attr_accessor :current_user
before_action :set_current_user
before_action :set_current_user, :find_roster
protect_from_forgery with: :exception

# If there's one specified, go to that.
# Otherwise, go to the first roster of which the current user is a member.
# Finally, just go to the first roster (if they're a member of none).
def find_roster
@roster = Roster.find_by(id: params[:roster_id])
@roster ||= @current_user.rosters.first
@roster ||= Roster.first
end

def set_current_user
if session.key? :user_id
@current_user = User.find_by id: session[:user_id]
Expand Down
29 changes: 18 additions & 11 deletions app/controllers/assignments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ class AssignmentsController < ApplicationController

def create
assignment_params = params.require(:assignment)
.permit :start_date, :end_date, :user_id
.permit :start_date, :end_date,
:user_id, :roster_id
assignment = Assignment.new assignment_params
if assignment.save
flash[:message] = 'Assignment has been created.'
redirect_to assignments_path(date: assignment.start_date)
redirect_to roster_assignments_path(@roster, date: assignment.start_date)
else
flash[:errors] = assignment.errors.full_messages
redirect_to :back
Expand All @@ -17,23 +18,25 @@ def create
def destroy
@assignment.destroy
flash[:message] = 'Assignment has been deleted.'
redirect_to assignments_path
redirect_to roster_assignments_path(@roster)
end

def edit
@users = User.order :last_name
@users = @roster.users.order :last_name
end

def generate_rotation
start_date = Date.parse(params.require :start_date)
end_date = Date.parse(params.require :end_date)
user_ids = params.require :user_ids
start_user = params.require :starting_user_id
Assignment.generate_rotation user_ids, start_date, end_date, start_user
@roster.generate_assignments user_ids, start_date, end_date, start_user
flash[:message] = 'Rotation has been generated.'
redirect_to assignments_path date: start_date
redirect_to roster_assignments_path(@roster, date: start_date)
end

# rubocop:disable Metrics/AbcSize, MethodLength

def index
@month_date = if params[:date].present?
Date.parse params[:date]
Expand All @@ -42,16 +45,20 @@ def index
start_date = @month_date.beginning_of_week(:sunday)
end_date = @month_date.end_of_month.end_of_week(:sunday)
@weeks = (start_date..end_date).each_slice(7)
@assignments = @current_user.assignments.upcoming.order :start_date
@current_assignment = Assignment.current
@assignments = @current_user.assignments.in(@roster)
.upcoming
.order :start_date
@current_assignment = @roster.assignments.current
@switchover_hour = CONFIG[:switchover_hour]
@fallback_user = User.fallback
@fallback_user = @roster.fallback_user
end

# rubocop:enable Metrics/AbcSize, MethodLength

def new
@start_date = Date.parse(params.require :date)
@end_date = @start_date + 6.days
@users = User.order :last_name
@users = @roster.users.order :last_name
end

def rotation_generator
Expand All @@ -64,7 +71,7 @@ def update
.permit :start_date, :end_date, :user_id
if @assignment.update assignment_params
flash[:message] = 'Assignment has been updated.'
redirect_to assignments_path(date: @assignment.start_date)
redirect_to roster_assignments_path(@roster, date: @assignment.start_date)
else
flash[:errors] = @assignment.errors.full_messages
redirect_to :back
Expand Down
49 changes: 49 additions & 0 deletions app/controllers/rosters_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
class RostersController < ApplicationController
before_action :find_roster, only: [:destroy, :edit, :update]

def create
roster_params = params.require(:roster).permit(:name)
roster = Roster.new roster_params
if roster.save
flash[:message] = 'Roster has been created.'
redirect_to rosters_path
else
flash[:errors] = roster.errors.full_messages
redirect_to :back
end
end

def destroy
@roster.destroy
flash[:message] = 'Roster and any assignments have been deleted.'
redirect_to rosters_path
end

def edit
@users = @roster.users
end

def index
@rosters = Roster.all
end

def new
end

def update
roster_params = params.require(:roster).permit!
if @roster.update roster_params
flash[:message] = 'Roster has been updated.'
redirect_to rosters_path
else
flash[:errors] = @roster.errors.full_messages
redirect_to :back
end
end

private

def find_roster
@roster = Roster.find(params.require :id)
end
end
6 changes: 3 additions & 3 deletions app/controllers/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class SessionsController < ApplicationController
layout false
skip_before_action :set_current_user
skip_before_action :set_current_user, :find_roster

def destroy
session.clear
Expand All @@ -12,10 +12,10 @@ def destroy

def dev_login # route not defined in production
if request.get?
@users = User.all
@rosters = Roster.includes(:users)
elsif request.post?
session[:user_id] = params[:user_id]
redirect_to assignments_path
redirect_to roster_assignments_path(roster_id: params[:roster_id])
end
end

Expand Down
7 changes: 2 additions & 5 deletions app/controllers/twilio_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
class TwilioController < ApplicationController
before_action :find_roster
before_action :set_on_call_user
skip_before_action :set_current_user
layout false
Expand All @@ -19,10 +20,6 @@ def text
private

def set_on_call_user
assignment = Assignment.current
@user = if assignment.present?
assignment.user
else User.fallback
end
@user = @roster.on_call_user
end
end
26 changes: 14 additions & 12 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ class UsersController < ApplicationController
def create
user_params = params.require(:user).permit!
user = User.new user_params
user.rosters << @roster
if user.save
flash[:message] = 'User has been created.'
redirect_to users_path
redirect_to roster_users_path(@roster)
else
flash[:errors] = user.errors.full_messages
redirect_to :back
Expand All @@ -16,25 +17,19 @@ def create
def destroy
@user.destroy
flash[:message] = 'User has been deleted.'
redirect_to users_path
end

def edit
redirect_to roster_users_path
end

def index
@users = User.all
@no_fallback = User.fallback.nil?
end

def new
@users = @roster.users
@fallback = @roster.fallback_user
end

def update
user_params = params.require(:user).permit!
if @user.update user_params
if @user.update parse_roster_ids(user_params)
flash[:message] = 'User has been updated.'
redirect_to users_path
redirect_to roster_users_path(@roster)
else
flash[:errors] = @user.errors.full_messages
redirect_to :back
Expand All @@ -46,4 +41,11 @@ def update
def find_user
@user = User.find(params.require :id)
end

def parse_roster_ids(attrs)
attrs[:rosters] = attrs[:rosters].map do |roster_id|
Roster.find_by id: roster_id
end.compact
attrs
end
end
49 changes: 29 additions & 20 deletions app/models/assignment.rb
Original file line number Diff line number Diff line change
@@ -1,40 +1,34 @@
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :roster

validates :user, :start_date, :end_date,
validates :user, :start_date, :end_date, :roster,
presence: true
validate :overlaps_any?
validate :user_in_roster?

class << self
# The current assignment - this method accounts for the 5pm switchover hour.
# This should be called while scoped to a particular roster.
def current
if Time.zone.now.hour < CONFIG.fetch(:switchover_hour)
on Date.yesterday
else on Date.today
end
end

# Generates a weekly rotation over a date range
# switching on the weekday of the start date
# starting with the user whose ID is in start_user_id
def generate_rotation(user_ids, start_date, end_date, start_user_id)
assignments = []
user_ids.rotate! user_ids.index(start_user_id)
(start_date..end_date).each_slice(7).with_index do |week, i|
assignments << create(
start_date: week.first,
end_date: week.last,
user_id: user_ids[i % user_ids.size]
)
end
assignments
def in(roster)
where roster: roster
end

# Returns the day AFTER the last assignment ends.
# If there is no last assignment, returns nil.
# If there is no last assignment, returns the upcoming Friday.
def next_rotation_start_date
last = order(:end_date).last
last.end_date + 1.day if last.present?
if last.present?
last.end_date + 1.day
else 1.week.since.beginning_of_week(:friday).to_date
end
end

# returns the assignment which takes place on a particular date
Expand All @@ -56,12 +50,27 @@ def upcoming

private

# rubocop:disable MethodLength
def overlaps_any?
overlapping_assignments = Assignment.where("
start_date <= ? AND end_date >= ? AND id != ?
", end_date, start_date, id)
if new_record?
overlapping_assignments = roster.assignments.where("
start_date < ? AND end_date >= ?
", end_date, start_date)
else
overlapping_assignments = roster.assignments.where("
start_date < ? AND end_date >= ? AND id != ?
", end_date, start_date, id)
end
return if overlapping_assignments.blank?
errors.add :base,
'Overlaps with another assignment'
end

# rubocop:enable MethodLength

def user_in_roster?
unless roster.users.include? user
errors.add :base, 'User is not in this roster'
end
end
end
Loading