updating view access for multi departments
This commit is contained in:
commit
b6cc08ecf9
1
Gemfile
1
Gemfile
@ -54,6 +54,7 @@ group :development, :test do
|
||||
gem 'byebug', platform: :mri
|
||||
gem 'pry-byebug'
|
||||
gem 'pry-rails'
|
||||
gem 'table_print'
|
||||
gem 'faker'
|
||||
|
||||
gem 'brakeman'
|
||||
|
@ -280,6 +280,7 @@ GEM
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
table_print (1.5.6)
|
||||
thor (0.19.4)
|
||||
thread_safe (0.3.5)
|
||||
tilt (2.0.5)
|
||||
@ -348,6 +349,7 @@ DEPENDENCIES
|
||||
simplecov
|
||||
spring
|
||||
spring-watcher-listen (~> 2.0.0)
|
||||
table_print
|
||||
turbolinks (~> 5)
|
||||
tzinfo-data
|
||||
uglifier (>= 1.3.0)
|
||||
|
@ -1,18 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
module Admin
|
||||
class ResultController < AdminController
|
||||
# TODO: change context from Candidate to Quiz
|
||||
# bypass pundit lockdowns until completed
|
||||
after_action :skip_policy_scope
|
||||
# TODO: bypass pundit authorization until a result wrapper class if sorted
|
||||
after_action :skip_authorization
|
||||
#
|
||||
# needed for :view
|
||||
|
||||
# TODO: Limit results to the quizzes current_user has access to
|
||||
def index
|
||||
sort_case = "(case when review_status = 0 then '' else name end)"
|
||||
sort_with_case = sort_column == 'name' ? sort_case : sort_column
|
||||
@candidates = Candidate.where(completed: true)
|
||||
.includes(:recruiter)
|
||||
@candidates = policy_scope(:result).includes(:recruiter)
|
||||
.order("#{sort_with_case} #{sort_direction}")
|
||||
.page(params[:page])
|
||||
end
|
||||
|
@ -45,6 +45,7 @@ class CandidateController < ApplicationController
|
||||
end
|
||||
|
||||
def send_to_oops
|
||||
redirect_to welcome_path and return if current_candidate && current_candidate.stale?
|
||||
redirect_to oops_path if current_candidate
|
||||
end
|
||||
end
|
||||
|
@ -48,6 +48,17 @@ class Candidate < ApplicationRecord
|
||||
answers.where(submitted: true)
|
||||
end
|
||||
|
||||
def last_answered_at
|
||||
return Time.current unless submitted_answers.count.positive?
|
||||
submitted_answers.order(updated_at: :desc).first.updated_at
|
||||
end
|
||||
|
||||
def stale?
|
||||
return true unless answers.count.positive?
|
||||
minutes_since_answered = (Time.current.minus_with_coercion(last_answered_at) / 60).round
|
||||
minutes_since_answered > 45
|
||||
end
|
||||
|
||||
def answered_questions
|
||||
answers.where.not(answer: nil)
|
||||
.where("answers.answer not like '%later:%'")
|
||||
|
@ -25,10 +25,10 @@ class QuizPolicy < ApplicationPolicy
|
||||
|
||||
class Scope < Scope
|
||||
def resolve
|
||||
if user.reviewer?
|
||||
scope.joins(:reviewers).where('reviewer_to_quizzes.user_id = ?', user.id)
|
||||
else
|
||||
if user.acts_as_recruiter?
|
||||
scope
|
||||
else
|
||||
scope.joins(:reviewers).where('reviewer_to_quizzes.user_id = ?', user.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
41
app/policies/result_policy.rb
Normal file
41
app/policies/result_policy.rb
Normal file
@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
class ResultPolicy < Struct.new(:user, :result)
|
||||
# Result Access Policy
|
||||
#
|
||||
# Only Admins and Recruiters can view all results
|
||||
# Managers and Reviewers can view any completed quiz they are linked to
|
||||
|
||||
attr_reader :user, :record
|
||||
|
||||
def initialize(user, record)
|
||||
raise Pundit::NotAuthorizedError, "Must be logged in." unless user
|
||||
@user = user
|
||||
@record = record
|
||||
end
|
||||
|
||||
def index?
|
||||
true
|
||||
end
|
||||
|
||||
# def view?
|
||||
# return true if user.acts_as_recruiter?
|
||||
# user.reviewees.include? record
|
||||
# end
|
||||
|
||||
class Scope
|
||||
attr_reader :user, :scope
|
||||
|
||||
def initialize(user, scope)
|
||||
@user = user
|
||||
@scope = scope
|
||||
end
|
||||
|
||||
def resolve
|
||||
if user.acts_as_recruiter?
|
||||
Candidate.where(completed: true)
|
||||
else
|
||||
user.reviewees.where(completed: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -8,13 +8,13 @@
|
||||
|
||||
User.create(
|
||||
name: 'admin',
|
||||
email: 'pdr.admin@mailinator.com',
|
||||
email: 'pda.admin@mailinator.com',
|
||||
password_digest: BCrypt::Password.create("this is the admin password"),
|
||||
role: 'admin'
|
||||
)
|
||||
|
||||
Quiz.create(
|
||||
name: 'PDR Standard FED Screening',
|
||||
name: 'PDA Standard FED Screening',
|
||||
unit: 'FED',
|
||||
dept: 'PDR'
|
||||
dept: 'PDA'
|
||||
)
|
||||
|
@ -47,7 +47,7 @@ module Admin
|
||||
|
||||
test "recruiter should auth to dashboard" do
|
||||
post admin_auth_url, params: { auth:
|
||||
{ email: 'pdr.recruiter@mailinator.com', password: 'password' } }
|
||||
{ email: 'pda.recruiter@mailinator.com', password: 'password' } }
|
||||
assert_redirected_to admin_url
|
||||
end
|
||||
|
||||
|
@ -25,7 +25,7 @@ module Admin
|
||||
auth_recruiter
|
||||
get admin_candidates_url
|
||||
assert_response :success
|
||||
assert_select "a[href='#{admin_edit_candidate_path(candidates(:martha))}']"
|
||||
assert_select "a[href='#{admin_edit_candidate_path(candidates(:gillian))}']"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
88
test/fixtures/answers.yml
vendored
88
test/fixtures/answers.yml
vendored
@ -698,8 +698,8 @@ wade10:
|
||||
|
||||
jorge1:
|
||||
candidate: jorge
|
||||
question: Cras justo odio, dapibus ac facilisis in, egestas eget quam. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit.
|
||||
answer: option 3
|
||||
question: fed1
|
||||
answer: Cras justo odio, dapibus ac facilisis in, egestas eget quam. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 22.minutes %>
|
||||
@ -890,3 +890,87 @@ elsie10:
|
||||
created_at: <%= DateTime.now() - 36.hours - 40.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 20.minutes %>
|
||||
|
||||
|
||||
##########################
|
||||
############# Studio Quiz
|
||||
ethan1:
|
||||
candidate: ethan
|
||||
question: studio1
|
||||
answer: Cras justo odio, dapibus ac facilisis in, egestas eget quam. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 22.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 22.minutes %>
|
||||
|
||||
ethan2:
|
||||
candidate: ethan
|
||||
question: studio2
|
||||
answer: Vestibulum id ligula porta felis euismod semper.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 24.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 4.minutes %>
|
||||
|
||||
ethan3:
|
||||
candidate: ethan
|
||||
question: studio3
|
||||
answer: Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nulla vitae elit libero, a pharetra augue.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 26.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 6.minutes %>
|
||||
|
||||
adele1:
|
||||
candidate: adele
|
||||
question: studio1
|
||||
answer: Cras justo odio, dapibus ac facilisis in, egestas eget quam. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 16.hours - 22.minutes %>
|
||||
updated_at: <%= DateTime.now() - 16.hours - 22.minutes %>
|
||||
|
||||
adele2:
|
||||
candidate: adele
|
||||
question: studio2
|
||||
answer: Vestibulum id ligula porta felis euismod semper.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 16.hours - 24.minutes %>
|
||||
updated_at: <%= DateTime.now() - 16.hours - 4.minutes %>
|
||||
|
||||
adele3:
|
||||
candidate: adele
|
||||
question: studio3
|
||||
answer: Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nulla vitae elit libero, a pharetra augue.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 16.hours - 26.minutes %>
|
||||
updated_at: <%= DateTime.now() - 16.hours - 6.minutes %>
|
||||
|
||||
carl1:
|
||||
candidate: carl
|
||||
question: studio1
|
||||
answer: Cras justo odio, dapibus ac facilisis in, egestas eget quam. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 22.minutes %>
|
||||
updated_at: <%= DateTime.now() - 22.minutes %>
|
||||
|
||||
carl2:
|
||||
candidate: carl
|
||||
question: studio2
|
||||
answer: Vestibulum id ligula porta felis euismod semper.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 24.minutes %>
|
||||
updated_at: <%= DateTime.now() - 4.minutes %>
|
||||
|
||||
carl3:
|
||||
candidate: carl
|
||||
question: studio3
|
||||
answer: Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nulla vitae elit libero, a pharetra augue.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 26.minutes %>
|
||||
updated_at: <%= DateTime.now() - 6.minutes %>
|
||||
|
||||
|
39
test/fixtures/candidates.yml
vendored
39
test/fixtures/candidates.yml
vendored
@ -179,3 +179,42 @@ gustov: # Gustov is NOT for FED
|
||||
reminded: false
|
||||
test_hash: kp6tfghjyapJnkz2N
|
||||
|
||||
ethan: # Completed quiz for studio
|
||||
name: Ethan Woodward
|
||||
email: <%= CryptSerializer.dump 'ethan.woodward@mailinator.com' %>
|
||||
experience: 0-3
|
||||
project: Studio Client
|
||||
position: 'full-time'
|
||||
skill_needs: 'Angular, HTML'
|
||||
recruiter: recruiter
|
||||
quiz: studio
|
||||
completed: true
|
||||
reminded: false
|
||||
test_hash: vNgQo2c5/HZL2CN
|
||||
|
||||
adele: # Completed quiz for studio
|
||||
name: Adele Kearney
|
||||
email: <%= CryptSerializer.dump 'adele.kearney@mailinator.com' %>
|
||||
experience: 0-3
|
||||
project: Studio Client
|
||||
position: 'full-time'
|
||||
skill_needs: 'Angular, HTML'
|
||||
recruiter: recruiter
|
||||
quiz: studio
|
||||
completed: true
|
||||
reminded: false
|
||||
test_hash: 37GmHL0Odjwv
|
||||
|
||||
carl: # Completed quiz for studio
|
||||
name: Carl Mitchell
|
||||
email: <%= CryptSerializer.dump 'carle.mitchell@mailinator.com' %>
|
||||
experience: 0-3
|
||||
project: Studio Client
|
||||
position: 'full-time'
|
||||
skill_needs: 'Angular, HTML'
|
||||
recruiter: recruiter
|
||||
quiz: studio
|
||||
completed: true
|
||||
reminded: false
|
||||
test_hash: hANPsTL1XHcmi
|
||||
|
||||
|
29
test/fixtures/questions.yml
vendored
29
test/fixtures/questions.yml
vendored
@ -150,3 +150,32 @@ admin1:
|
||||
sort: 0
|
||||
active: true
|
||||
|
||||
|
||||
studio1:
|
||||
quiz: studio
|
||||
question: 'Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Maecenas faucibus mollis interdum.'
|
||||
category: Ipsum
|
||||
input_type: text
|
||||
input_options:
|
||||
sort: 0
|
||||
active: true
|
||||
|
||||
studio2:
|
||||
quiz: studio
|
||||
question: 'Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Maecenas faucibus mollis interdum.'
|
||||
category: Magna
|
||||
input_type: text
|
||||
input_options:
|
||||
sort: 0
|
||||
active: true
|
||||
|
||||
|
||||
studio3:
|
||||
quiz: studio
|
||||
question: 'Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Maecenas faucibus mollis interdum.'
|
||||
category: Commodo
|
||||
input_type: text
|
||||
input_options:
|
||||
sort: 0
|
||||
active: true
|
||||
|
||||
|
11
test/fixtures/quizzes.yml
vendored
11
test/fixtures/quizzes.yml
vendored
@ -1,11 +1,16 @@
|
||||
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
|
||||
|
||||
fed:
|
||||
name: PDR Standard FED Screening
|
||||
unit: PDR
|
||||
name: PDA Standard FED Screening
|
||||
unit: PD Agency
|
||||
dept: FED
|
||||
|
||||
admin:
|
||||
name: An extra quiz not assigned to anyone
|
||||
unit: PDR
|
||||
unit: PD
|
||||
dept: NOPE
|
||||
|
||||
studio:
|
||||
name: Studio Screening One
|
||||
unit: PD Studio
|
||||
dept: FED
|
||||
|
8
test/fixtures/reviewer_to_quizzes.yml
vendored
8
test/fixtures/reviewer_to_quizzes.yml
vendored
@ -11,3 +11,11 @@ two:
|
||||
three:
|
||||
user: manager
|
||||
quiz: fed
|
||||
|
||||
four:
|
||||
user: studio_manager
|
||||
quiz: studio
|
||||
|
||||
five:
|
||||
user: studio_reviewer
|
||||
quiz: studio
|
||||
|
26
test/fixtures/reviewer_votes.yml
vendored
26
test/fixtures/reviewer_votes.yml
vendored
@ -93,3 +93,29 @@ reviewer2_elsie:
|
||||
candidate: elsie
|
||||
user: reviewer2
|
||||
|
||||
|
||||
studio_reviewer_carle:
|
||||
candidate: carle
|
||||
user: studio_reviewer
|
||||
|
||||
studio_reviewer_ethan:
|
||||
candidate: ethan
|
||||
user: studio_reviewer
|
||||
|
||||
studio_reviewer_adele:
|
||||
candidate: adele
|
||||
user: studio_reviewer
|
||||
|
||||
|
||||
studio_manager_carle:
|
||||
candidate: carle
|
||||
user: studio_manager
|
||||
|
||||
studio_manager_ethan:
|
||||
candidate: ethan
|
||||
user: studio_manager
|
||||
|
||||
studio_manager_adele:
|
||||
user: studio_manager
|
||||
candidate: adele
|
||||
|
||||
|
15
test/fixtures/users.yml
vendored
15
test/fixtures/users.yml
vendored
@ -2,7 +2,7 @@
|
||||
|
||||
recruiter:
|
||||
name: Sam Recruiter
|
||||
email: pdr.recruiter@mailinator.com
|
||||
email: pda.recruiter@mailinator.com
|
||||
password_digest: <%= BCrypt::Password.create("password", cost: 4) %>
|
||||
role: recruiter
|
||||
|
||||
@ -29,3 +29,16 @@ admin:
|
||||
email: alan.admin@mailinator.com
|
||||
password_digest: <%= BCrypt::Password.create("password", cost: 4) %>
|
||||
role: admin
|
||||
|
||||
studio_manager:
|
||||
name: Studio Manager
|
||||
email: studio.manager@mailinator.com
|
||||
password_digest: <%= BCrypt::Password.create("password", cost: 4) %>
|
||||
role: manager
|
||||
|
||||
studio_reviewer:
|
||||
name: Studio Reviewer
|
||||
email: studio.reviewer@mailinator.com
|
||||
password_digest: <%= BCrypt::Password.create("password", cost: 4) %>
|
||||
role: reviewer
|
||||
|
||||
|
@ -34,4 +34,23 @@ class CandidateTest < ActiveSupport::TestCase
|
||||
candidate.build_reviews
|
||||
assert_equal 3, candidate.votes.count
|
||||
end
|
||||
|
||||
test 'can get last answer timestamp' do
|
||||
candidate = candidates(:roy)
|
||||
roy_last = answers(:roy2).updated_at
|
||||
|
||||
assert_equal roy_last, candidate.last_answered_at
|
||||
end
|
||||
|
||||
test 'gillian is stale with no answers' do
|
||||
candidate = candidates(:gillian)
|
||||
|
||||
assert candidate.stale?
|
||||
end
|
||||
|
||||
test 'roy is stale with answers' do
|
||||
candidate = candidates(:roy)
|
||||
|
||||
assert candidate.stale?
|
||||
end
|
||||
end
|
||||
|
@ -14,7 +14,7 @@ class ReviewerVoteTest < ActiveSupport::TestCase
|
||||
|
||||
test "manager has a vote for every completed quiz" do
|
||||
manager = users(:manager)
|
||||
completed_count = Candidate.where(completed: true).count
|
||||
completed_count = 6
|
||||
|
||||
assert_equal completed_count, manager.votes.size
|
||||
end
|
||||
|
@ -15,7 +15,7 @@ class QuizPolicyTest < PolicyAssertions::Test
|
||||
|
||||
test 'should allow manager to scope' do
|
||||
scope = QuizPolicy::Scope.new(users(:manager), Quiz).resolve
|
||||
assert_equal Quiz.count, scope.count
|
||||
assert_equal users(:manager).quizzes.count, scope.count
|
||||
end
|
||||
|
||||
test 'should allow reviewer to scope' do
|
||||
|
27
test/policies/result_policy_test.rb
Normal file
27
test/policies/result_policy_test.rb
Normal file
@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
require 'test_helper'
|
||||
|
||||
class ResultPolicyTest < PolicyAssertions::Test
|
||||
def test_index
|
||||
assert_permit users(:admin), :result
|
||||
assert_permit users(:recruiter), :result
|
||||
assert_permit users(:manager), :result
|
||||
assert_permit users(:reviewer), :result
|
||||
end
|
||||
|
||||
test 'should allow admin to scope' do
|
||||
scope = ResultPolicy::Scope.new(users(:admin), Candidate).resolve
|
||||
assert_equal Candidate.where(completed: true).count, scope.count
|
||||
end
|
||||
|
||||
test 'should allow recruiter to scope' do
|
||||
scope = ResultPolicy::Scope.new(users(:recruiter), Candidate).resolve
|
||||
assert_equal Candidate.where(completed: true).count, scope.count
|
||||
end
|
||||
|
||||
test 'should not allow fed.reviewer to scope studio results' do
|
||||
reviewer = users(:reviewer)
|
||||
scope = ResultPolicy::Scope.new(reviewer, Candidate).resolve
|
||||
assert_equal reviewer.reviewees.where(completed: true).count, scope.count
|
||||
end
|
||||
end
|
@ -21,7 +21,7 @@ module AuthTestHelper
|
||||
|
||||
def auth_recruiter
|
||||
post admin_auth_url, params: { auth:
|
||||
{ email: 'pdr.recruiter@mailinator.com', password: 'password' } }
|
||||
{ email: 'pda.recruiter@mailinator.com', password: 'password' } }
|
||||
end
|
||||
|
||||
def auth_reviewer
|
||||
|
@ -4,7 +4,7 @@ require 'test_helper'
|
||||
class ReviewerReminderTest < ActiveSupport::TestCase
|
||||
test "collection is created with results" do
|
||||
reminders = ReviewerReminder.new
|
||||
assert_equal 6, reminders.size
|
||||
assert_equal 8, reminders.size
|
||||
end
|
||||
|
||||
test "each reminder has needed attributes" do
|
||||
|
Loading…
Reference in New Issue
Block a user