A very fine release.
This commit is contained in:
BIN
app/assets/images/ic_arrow_drop_down_black_24dp_1x.png
Normal file
BIN
app/assets/images/ic_arrow_drop_down_black_24dp_1x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 94 B |
BIN
app/assets/images/ic_arrow_drop_down_black_24dp_2x.png
Normal file
BIN
app/assets/images/ic_arrow_drop_down_black_24dp_2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 119 B |
BIN
app/assets/images/ic_arrow_drop_up_black_24dp_1x.png
Normal file
BIN
app/assets/images/ic_arrow_drop_up_black_24dp_1x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 95 B |
BIN
app/assets/images/ic_arrow_drop_up_black_24dp_2x.png
Normal file
BIN
app/assets/images/ic_arrow_drop_up_black_24dp_2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 119 B |
BIN
app/assets/images/ic_sort_black_24dp_1x.png
Normal file
BIN
app/assets/images/ic_sort_black_24dp_1x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 89 B |
BIN
app/assets/images/ic_sort_black_24dp_2x.png
Normal file
BIN
app/assets/images/ic_sort_black_24dp_2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 103 B |
@ -18,11 +18,6 @@ function updateVotes(data){
|
||||
$("[data-id=my-vote]").html(data.myVote);
|
||||
}
|
||||
|
||||
function updateVeto(data){
|
||||
$("[data-id=interview-request]").html(data.requestCopy);
|
||||
$("[data-id=interview-decline]").html(data.declineCopy);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('[data-id=ajax-action]').each(function(){ handleAjaxResponse($(this)); });
|
||||
});
|
||||
@ -30,7 +25,3 @@ $(document).ready(function() {
|
||||
$(document).ready(function() {
|
||||
$('[data-id=vote-count]').each(function(){ handleAjaxResponse($(this), updateVotes); });
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
$('[data-id=veto-status]').each(function(){ handleAjaxResponse($(this), updateVeto); });
|
||||
});
|
||||
|
@ -1,12 +1,14 @@
|
||||
.admin-review {
|
||||
counter-reset: question;
|
||||
float: left;
|
||||
width: 66%;
|
||||
|
||||
form {
|
||||
margin-left: 2.3em;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: counter(question) ") ";
|
||||
content: counter(question) ') ';
|
||||
counter-increment: question;
|
||||
font-size: 1.25em;
|
||||
left: -1.8em;
|
||||
@ -17,6 +19,67 @@
|
||||
|
||||
}
|
||||
|
||||
.review-comments {
|
||||
float: right;
|
||||
padding-left: 30px;
|
||||
width: 33%;
|
||||
|
||||
> div {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.comment-message {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.comment-author {
|
||||
font-size: 0.85em;
|
||||
font-weight: 700;
|
||||
margin-bottom: 30px;
|
||||
text-align: right;
|
||||
|
||||
&::before {
|
||||
content: '- ';
|
||||
}
|
||||
}
|
||||
|
||||
.comment-edit-stamp {
|
||||
color: rgba($gray-dark, 0.65);
|
||||
font-size: 0.75em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.comment-edit-btn {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 0.85em;
|
||||
font-weight: bold;
|
||||
padding: 2px 5px;
|
||||
|
||||
&:hover {
|
||||
background-color: $gray-base;
|
||||
color: $gray-lighter;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-edit-form {
|
||||
display: none;
|
||||
margin: 30px 0;
|
||||
padding: 10px 0;
|
||||
|
||||
}
|
||||
|
||||
[type="checkbox"] {
|
||||
&:checked + .comment-edit-form {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.review_meta {
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
@ -24,8 +87,32 @@
|
||||
& > div { flex: 1 1 auto; }
|
||||
}
|
||||
|
||||
.review_meta__votes,
|
||||
.review_meta__vetos {
|
||||
.review_meta__votes {
|
||||
margin-bottom: 15px;
|
||||
a { padding: 5px; }
|
||||
}
|
||||
|
||||
.review_meta__vetos {
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.review-status-comments {
|
||||
opacity: 0;
|
||||
padding: 15px 0;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: all 0.500s;
|
||||
}
|
||||
|
||||
input:checked ~ .review-status-comments {
|
||||
opacity: 1;
|
||||
height: auto;
|
||||
max-height: 9999px;
|
||||
overflow: auto;
|
||||
transition: all 0.7500s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,50 @@ th {
|
||||
font-weight: 600;
|
||||
padding: $small-spacing 0;
|
||||
text-align: left;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
margin-right: 18px;
|
||||
padding-right: 5px;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
|
||||
&::after {
|
||||
background-image: asset_data_url("ic_sort_black_24dp_2x.png");
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 18px;
|
||||
left: 100%;
|
||||
opacity: 0.5;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
&.asc {
|
||||
&::after {
|
||||
background-image: asset_data_url("ic_arrow_drop_up_black_24dp_2x.png");
|
||||
height: 25px;
|
||||
left: calc(100% - 5px);
|
||||
opacity: 1;
|
||||
top: 1px;
|
||||
width: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
&.desc {
|
||||
&::after {
|
||||
background-image: asset_data_url("ic_arrow_drop_down_black_24dp_2x.png");
|
||||
height: 25px;
|
||||
left: calc(100% - 5px);
|
||||
opacity: 1;
|
||||
top: 1px;
|
||||
width: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
|
@ -15,7 +15,7 @@ module Admin
|
||||
|
||||
if user && user.authenticate(auth_params[:password])
|
||||
session[:user] = user.to_i
|
||||
redirect_to admin_path
|
||||
redirect_to session[:request] || admin_path
|
||||
else
|
||||
redirect_to admin_login_path,
|
||||
flash: { error: "Sorry, incorrect email or password. Please try again." }
|
||||
|
@ -4,7 +4,8 @@ module Admin
|
||||
before_action :collect_quizzes, except: [:login, :auth]
|
||||
|
||||
def index
|
||||
@candidates = policy_scope Candidate.order(:name)
|
||||
@candidates = policy_scope Candidate.order("#{sort_column} #{sort_direction}")
|
||||
.page(params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
@ -49,13 +50,14 @@ module Admin
|
||||
authorize Candidate
|
||||
candidate = Candidate.find_by(id: params[:id])
|
||||
CandidateMailer.welcome(candidate).deliver_later
|
||||
render json: { message: "Email queued!" }.to_json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def candidate_params
|
||||
params.require(:candidate).permit(:name, :email, :experience, :quiz_id)
|
||||
params.require(:candidate).permit(
|
||||
:name, :email, :experience, :quiz_id, :project, :position, :skill_needs
|
||||
)
|
||||
end
|
||||
|
||||
def collect_quizzes
|
||||
@ -66,5 +68,9 @@ module Admin
|
||||
CandidateMailer.welcome(candidate).deliver_later
|
||||
RecruiterMailer.candidate_created(candidate).deliver_later
|
||||
end
|
||||
|
||||
def sort_column
|
||||
Candidate.column_names.include?(params[:sort]) ? params[:sort] : 'name'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
37
app/controllers/admin/comment_controller.rb
Normal file
37
app/controllers/admin/comment_controller.rb
Normal file
@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
module Admin
|
||||
class CommentController < AdminController
|
||||
def update
|
||||
comment = QuizComment.find_by(id: params[:id], test_hash: params[:test_hash])
|
||||
authorize comment
|
||||
|
||||
comment.update(comment_params)
|
||||
flash_message = if comment.save
|
||||
{ success: "Sucessfully updated comment" }
|
||||
else
|
||||
{ error: "Failed to update comment" }
|
||||
end
|
||||
redirect_to admin_result_path(params[:test_hash]), flash: flash_message
|
||||
end
|
||||
|
||||
def create
|
||||
comment = QuizComment.new(comment_params.merge(user_id: current_user.id, test_hash: params[:test_hash]))
|
||||
authorize comment
|
||||
|
||||
flash_message = if comment.save
|
||||
{ success: "Sucessfully created comment" }
|
||||
else
|
||||
{ error: "Failed to save comment" }
|
||||
end
|
||||
|
||||
ReviewerMailer.new_comment(comment).deliver_later if comment.persisted?
|
||||
redirect_to admin_result_path(params[:test_hash]), flash: flash_message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def comment_params
|
||||
params.require(:quiz_comment).permit(:message)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,7 +1,6 @@
|
||||
# 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
|
||||
@ -10,13 +9,30 @@ module Admin
|
||||
|
||||
# TODO: Limit results to the quizzes current_user has access to
|
||||
def index
|
||||
@candidates = Candidate.where(completed: true).includes(:recruiter)
|
||||
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)
|
||||
.order("#{sort_with_case} #{sort_direction}")
|
||||
.page(params[:page])
|
||||
end
|
||||
|
||||
def view
|
||||
@candidate = Candidate.find_by(test_hash: params[:test_hash])
|
||||
@quiz = @candidate.my_quiz
|
||||
@status = QuizStatus.new(@candidate)
|
||||
@comments = QuizComment.includes(:user).where(test_hash: @candidate.test_hash).order(:created_at)
|
||||
@comment = QuizComment.new
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sort_column
|
||||
@sort_col ||= Candidate.column_names.include?(params[:sort]) ? params[:sort] : 'completed_at'
|
||||
end
|
||||
|
||||
def sort_direction
|
||||
%w(asc desc).include?(params[:direction]) ? params[:direction] : 'desc'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2,7 +2,8 @@
|
||||
module Admin
|
||||
class UserController < AdminController
|
||||
def index
|
||||
@users = policy_scope User.order(:name)
|
||||
@users = policy_scope User.order("#{sort_column} #{sort_direction}")
|
||||
.page(params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
@ -52,5 +53,9 @@ module Admin
|
||||
def user_params
|
||||
params.require(:user).permit(policy(User).permitted_attributes)
|
||||
end
|
||||
|
||||
def sort_column
|
||||
User.column_names.include?(params[:sort]) ? params[:sort] : 'name'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -5,54 +5,41 @@ module Admin
|
||||
@candidate = Candidate.find_by(test_hash: params[:test_hash])
|
||||
authorize ReviewerVote.find_by(user_id: current_user.id, candidate_id: @candidate.id)
|
||||
current_user.cast_yea_on(@candidate)
|
||||
|
||||
results = {
|
||||
message: "Vote Counted",
|
||||
upCount: @candidate.votes.yea.count,
|
||||
downCount: @candidate.votes.nay.count,
|
||||
myVote: "yea"
|
||||
}
|
||||
render json: results.to_json
|
||||
end
|
||||
|
||||
def down
|
||||
@candidate = Candidate.find_by(test_hash: params[:test_hash])
|
||||
authorize ReviewerVote.find_by(user_id: current_user.id, candidate_id: @candidate.id)
|
||||
current_user.cast_nay_on(@candidate)
|
||||
|
||||
results = {
|
||||
message: "Vote Counted",
|
||||
upCount: @candidate.votes.yea.count,
|
||||
downCount: @candidate.votes.nay.count,
|
||||
myVote: "nay"
|
||||
}
|
||||
render json: results.to_json
|
||||
end
|
||||
|
||||
def approve
|
||||
def interview_request
|
||||
@candidate = Candidate.find_by(test_hash: params[:test_hash])
|
||||
authorize ReviewerVote.find_by(user_id: current_user.id, candidate_id: @candidate.id)
|
||||
current_user.approve_candidate(@candidate)
|
||||
|
||||
results = {
|
||||
message: "Interview requested!",
|
||||
requestCopy: "Requested",
|
||||
declineCopy: "Decline Interview"
|
||||
}
|
||||
render json: results.to_json
|
||||
if interview_params[:review_comments].blank?
|
||||
refuse_interview_request
|
||||
else
|
||||
send_interview_request
|
||||
end
|
||||
end
|
||||
|
||||
def decline
|
||||
@candidate = Candidate.find_by(test_hash: params[:test_hash])
|
||||
authorize ReviewerVote.find_by(user_id: current_user.id, candidate_id: @candidate.id)
|
||||
current_user.decline_candidate(@candidate)
|
||||
private
|
||||
|
||||
results = {
|
||||
message: "Interview declined.",
|
||||
requestCopy: "Request Interview",
|
||||
declineCopy: "Declined"
|
||||
}
|
||||
render json: results.to_json
|
||||
end
|
||||
def refuse_interview_request
|
||||
redirect_to admin_result_path(@candidate.test_hash),
|
||||
flash: { error: "Must provide a comment" }
|
||||
end
|
||||
|
||||
def send_interview_request
|
||||
current_user.review_candidate(@candidate, interview_params)
|
||||
RecruiterMailer.candidate_reviewed(@candidate).deliver_later
|
||||
redirect_to admin_result_path(@candidate.test_hash),
|
||||
flash: { notice: "Quiz #{interview_params[:review_status]}" }
|
||||
end
|
||||
|
||||
def interview_params
|
||||
params.permit(:review_status, :review_comments)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -9,6 +9,9 @@ class AdminController < ApplicationController
|
||||
after_action :verify_authorized, except: :index
|
||||
after_action :verify_policy_scoped, only: :index
|
||||
|
||||
helper_method :sort_direction
|
||||
helper_method :sort_column
|
||||
|
||||
def current_user
|
||||
@current_user ||= User.find_by(id: session[:user]) if session[:user]
|
||||
end
|
||||
@ -16,7 +19,16 @@ class AdminController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def sort_column
|
||||
:completed_at
|
||||
end
|
||||
|
||||
def sort_direction
|
||||
%w(asc desc).include?(params[:direction]) ? params[:direction] : 'asc'
|
||||
end
|
||||
|
||||
def authorize_user
|
||||
session[:request] = request.fullpath
|
||||
redirect_to admin_login_path unless current_user
|
||||
end
|
||||
|
||||
|
@ -35,7 +35,7 @@ class QuizController < ApplicationController
|
||||
private
|
||||
|
||||
def complete_and_email
|
||||
if current_candidate.update_attributes(completed: true)
|
||||
if current_candidate.update_attributes(completed: true, completed_at: DateTime.current)
|
||||
current_candidate.build_reviews
|
||||
CandidateMailer.submitted(current_candidate).deliver_later
|
||||
RecruiterMailer.candidate_submitted(current_candidate).deliver_later
|
||||
|
@ -44,4 +44,11 @@ module ApplicationHelper
|
||||
@js_blocks << code_label
|
||||
content_for :custom_javascipt, &block
|
||||
end
|
||||
|
||||
def sortable(column, title = nil)
|
||||
title ||= column.titleize
|
||||
css_class = column == sort_column ? sort_direction.to_s : nil
|
||||
direction = column == sort_column && sort_direction == "desc" ? "asc" : "desc"
|
||||
link_to title, { sort: column, direction: direction }, class: css_class
|
||||
end
|
||||
end
|
||||
|
@ -1,14 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
class RecruiterMailer < ApplicationMailer
|
||||
def candidate_created candidate
|
||||
@candidate = candidate
|
||||
@candidate = Candidate.find_by(id: candidate.to_i)
|
||||
|
||||
mail to: @candidate.recruiter.email, subject: "Skills Assessment Test - #{candidate.name}"
|
||||
mail to: @candidate.recruiter.email,
|
||||
subject: "Skills Assessment Test - #{candidate.name}"
|
||||
end
|
||||
|
||||
def candidate_submitted candidate
|
||||
@candidate = candidate
|
||||
@candidate = Candidate.find_by(id: candidate.to_i)
|
||||
|
||||
mail to: @candidate.recruiter.email, subject: "Skills Assessment Test - #{candidate.name}"
|
||||
mail to: @candidate.recruiter.email,
|
||||
subject: "Skills Assessment Test - #{candidate.name}"
|
||||
end
|
||||
|
||||
def candidate_reviewed candidate
|
||||
@candidate = Candidate.find_by(id: candidate.to_i)
|
||||
|
||||
mail to: @candidate.recruiter.email,
|
||||
subject: "Skills Assesment - Review Complete for #{candidate.name}"
|
||||
end
|
||||
end
|
||||
|
@ -6,4 +6,24 @@ class ReviewerMailer < ApplicationMailer
|
||||
|
||||
mail to: recipients, subject: "Skills Assessment Results - #{@candidate.test_hash}"
|
||||
end
|
||||
|
||||
def reminder reminder
|
||||
@reminder = reminder
|
||||
|
||||
mail to: reminder.email, subject: "Review Reminder"
|
||||
end
|
||||
|
||||
def notify_manager candidate_id
|
||||
@candidate = Candidate.find_by(id: candidate_id)
|
||||
@manager = @candidate.manager
|
||||
|
||||
mail to: @manager.email, subject: "Voting Complete"
|
||||
end
|
||||
|
||||
def new_comment comment
|
||||
@comment = comment
|
||||
recipients = comment.candidate.reviewers.map(&:email)
|
||||
|
||||
mail to: recipients, subject: "Skills Assessment Review Comment - #{@comment.test_hash}"
|
||||
end
|
||||
end
|
||||
|
@ -6,6 +6,7 @@ class Candidate < ApplicationRecord
|
||||
belongs_to :recruiter, class_name: "User"
|
||||
has_many :votes, class_name: "ReviewerVote"
|
||||
has_many :reviewers, through: :quiz
|
||||
has_many :quiz_comments, foreign_key: :test_hash, primary_key: :test_hash
|
||||
|
||||
serialize :email, CryptSerializer
|
||||
|
||||
@ -14,6 +15,8 @@ class Candidate < ApplicationRecord
|
||||
validates :recruiter_id, presence: true
|
||||
validates :name, presence: true
|
||||
validates :experience, presence: true
|
||||
validates :project, presence: true
|
||||
validates :position, presence: true
|
||||
validates :email, uniqueness: true, presence: true, email_format: true
|
||||
validates :test_hash, uniqueness: true, presence: true
|
||||
|
||||
@ -23,12 +26,24 @@ class Candidate < ApplicationRecord
|
||||
declined: 2
|
||||
}
|
||||
|
||||
def interview?
|
||||
return 'yes' if approved?
|
||||
'no' if declined?
|
||||
end
|
||||
|
||||
def build_reviews
|
||||
reviewers.each do |reviewer|
|
||||
votes.find_or_create_by(user_id: reviewer.id)
|
||||
end
|
||||
end
|
||||
|
||||
def manager
|
||||
manager_votes = votes.joins(:user).where("users.role = 'manager'")
|
||||
return User.new(name: "No Manager") if manager_votes.empty?
|
||||
|
||||
manager_votes.first.user
|
||||
end
|
||||
|
||||
def submitted_answers
|
||||
answers.where(submitted: true)
|
||||
end
|
||||
|
11
app/models/quiz_comment.rb
Normal file
11
app/models/quiz_comment.rb
Normal file
@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
class QuizComment < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :candidate, foreign_key: :test_hash, primary_key: :test_hash
|
||||
|
||||
validates :message, presence: true
|
||||
|
||||
def edits?
|
||||
updated_at > (created_at + 5.seconds)
|
||||
end
|
||||
end
|
@ -5,6 +5,8 @@ class ReviewerVote < ApplicationRecord
|
||||
|
||||
validates :user_id, uniqueness: { scope: :candidate_id }
|
||||
|
||||
after_save :notify_manager
|
||||
|
||||
enum vote: {
|
||||
undecided: 0,
|
||||
yea: 1,
|
||||
@ -13,6 +15,40 @@ class ReviewerVote < ApplicationRecord
|
||||
|
||||
enum veto: {
|
||||
approved: 1,
|
||||
rejected: 2
|
||||
declined: 2
|
||||
}
|
||||
|
||||
private
|
||||
|
||||
def notify_manager
|
||||
ReviewerMailer.notify_manager(candidate_id).deliver_now if all_reviewers_voted?
|
||||
end
|
||||
|
||||
def all_reviewers_voted? # rubocop:disable Metrics/MethodLength
|
||||
sql = " select distinct rev.candidate_id
|
||||
, full_votes.voters, full_votes.vote_count
|
||||
, c.test_hash
|
||||
from reviewer_votes rev
|
||||
inner join users u on u.id = rev.user_id
|
||||
inner join candidates c on c.id = rev.candidate_id
|
||||
left join (
|
||||
select candidate_id
|
||||
from reviewer_votes
|
||||
where veto > 0 or veto is null
|
||||
) as vetos on vetos.candidate_id = rev.candidate_id
|
||||
left join (
|
||||
select candidate_id
|
||||
, count(vote) voters
|
||||
, sum(case when vote != 0 then 1 else 0 end) vote_count
|
||||
from reviewer_votes
|
||||
left join users on users.id = reviewer_votes.user_id
|
||||
where users.role != 'manager'
|
||||
group by candidate_id
|
||||
) as full_votes on full_votes.candidate_id = rev.candidate_id
|
||||
where vetos.candidate_id is null
|
||||
and full_votes.voters = full_votes.vote_count
|
||||
and rev.candidate_id = #{candidate_id};"
|
||||
result = ActiveRecord::Base.connection.exec_query(sql)
|
||||
result.count == 1
|
||||
end # rubocop:enable Metrics/MethodLength
|
||||
end
|
||||
|
@ -5,6 +5,7 @@ class User < ApplicationRecord
|
||||
has_many :reviewer_to_quizzes
|
||||
has_many :quizzes, through: :reviewer_to_quizzes
|
||||
has_many :votes, class_name: 'ReviewerVote'
|
||||
has_many :quiz_comments
|
||||
|
||||
has_many :reviewees, through: :quizzes, source: :candidates
|
||||
|
||||
@ -18,7 +19,13 @@ class User < ApplicationRecord
|
||||
save
|
||||
end
|
||||
|
||||
def commented_on? test_hash
|
||||
quiz_comments.where(test_hash: test_hash).count.positive?
|
||||
end
|
||||
|
||||
# Voting
|
||||
# TODO: Refactor this out of User, belongs on ReviewerVote
|
||||
# ie: cast_yea(candidate, user)
|
||||
def cast_yea_on candidate
|
||||
vote = votes.find_by(candidate_id: candidate.to_i)
|
||||
vote.vote = :yea
|
||||
@ -31,20 +38,16 @@ class User < ApplicationRecord
|
||||
vote.save
|
||||
end
|
||||
|
||||
def approve_candidate candidate
|
||||
def review_candidate candidate, parms_hash
|
||||
candidate = Candidate.find(candidate.to_i)
|
||||
|
||||
vote = votes.find_by(candidate_id: candidate.to_i)
|
||||
vote.veto = :approved
|
||||
candidate.update_attribute(:review_status, :approved) if vote.save
|
||||
end
|
||||
|
||||
def decline_candidate candidate
|
||||
candidate = Candidate.find(candidate.to_i)
|
||||
|
||||
vote = votes.find_by(candidate_id: candidate.to_i)
|
||||
vote.veto = :rejected
|
||||
candidate.update_attribute(:review_status, :declined) if vote.save
|
||||
vote.veto = parms_hash[:review_status]
|
||||
if vote.save
|
||||
# skipping validations on candidate because that's not the managers responsibility
|
||||
candidate.review_comments = parms_hash[:review_comments]
|
||||
candidate.update_attribute(:review_status, parms_hash[:review_status])
|
||||
end
|
||||
end
|
||||
|
||||
def my_vote candidate
|
||||
|
22
app/policies/quiz_comment_policy.rb
Normal file
22
app/policies/quiz_comment_policy.rb
Normal file
@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
class QuizCommentPolicy < ApplicationPolicy
|
||||
# Quiz Comment Policy
|
||||
#
|
||||
# Anyone who can vote on results, can comment
|
||||
# Only comment owner can edit her comment
|
||||
|
||||
def new?
|
||||
user.acts_as_reviewer?
|
||||
end
|
||||
|
||||
def create?
|
||||
return true if user.acts_as_admin?
|
||||
|
||||
user.acts_as_reviewer? &&
|
||||
record.candidate.reviewers.where(id: user.id).count.positive?
|
||||
end
|
||||
|
||||
def update?
|
||||
user.acts_as_reviewer? && user.id == record.user_id
|
||||
end
|
||||
end
|
@ -2,30 +2,26 @@
|
||||
class ReviewerVotePolicy < ApplicationPolicy
|
||||
# Voting Policy
|
||||
#
|
||||
# Only Reviewers, Managers, and Admins, can cast a vote on a quiz result
|
||||
# Only Reviewers and Managers can cast a vote on a quiz result
|
||||
#
|
||||
# Reviewers can vote any quiz they are linked to
|
||||
# Only Managers, and Admins, can veto a quiz result
|
||||
|
||||
def up?
|
||||
return true if user.acts_as_admin?
|
||||
return false unless user.commented_on?(record.candidate.test_hash)
|
||||
return false unless record.candidate.reviewers.include? user
|
||||
return false if user.admin?
|
||||
user.acts_as_reviewer?
|
||||
end
|
||||
|
||||
def down?
|
||||
return true if user.acts_as_admin?
|
||||
return false unless user.commented_on?(record.candidate.test_hash)
|
||||
return false unless record.candidate.reviewers.include? user
|
||||
return false if user.admin?
|
||||
user.acts_as_reviewer?
|
||||
end
|
||||
|
||||
def approve?
|
||||
return true if user.acts_as_admin?
|
||||
return false unless record.candidate.reviewers.include? user
|
||||
user.acts_as_manager?
|
||||
end
|
||||
|
||||
def decline?
|
||||
def interview_request?
|
||||
return true if user.acts_as_admin?
|
||||
return false unless record.candidate.reviewers.include? user
|
||||
user.acts_as_manager?
|
||||
|
@ -16,6 +16,24 @@
|
||||
<%= form.select :experience, experience_options(candidate.experience), include_blank: false %>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<%= form.label :project, "Client or project" %>
|
||||
<%= form.text_field :project %>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<%= form.radio_button :position, 'full-time' %>
|
||||
<%= form.label "position_full-time", "Full-time" %>
|
||||
|
||||
<%= form.radio_button :position, 'contract' %>
|
||||
<%= form.label :position_contract, "Contract" %>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<%= form.label :skill_needs, "Specific skill needs" %>
|
||||
<%= form.text_field :skill_needs %>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<%= form.label :quiz_id, "Quiz" %>
|
||||
<%= form.select :quiz_id, quiz_options(quizzes, candidate.quiz_id), include_blank: (quizzes.size > 1) %>
|
||||
|
@ -9,14 +9,14 @@
|
||||
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<th>Candidate</th>
|
||||
<th>Test ID</th>
|
||||
<th>Email</th>
|
||||
<th>Experience</th>
|
||||
<th><%= sortable "name", "Candidate" %></th>
|
||||
<th><%= sortable "test_hash", "Test ID" %></th>
|
||||
<th><%= sortable "email" %></th>
|
||||
<th><%= sortable "experience" %></th>
|
||||
<th>Progress</th>
|
||||
<th>Completed</th>
|
||||
<th>Reminded</th>
|
||||
<th>Interview Request</th>
|
||||
<th><%= sortable "completed_at", "Completed" %></th>
|
||||
<th><%= sortable "reminded" %></th>
|
||||
<th>Interview?</th>
|
||||
</tr>
|
||||
|
||||
<% @candidates.each do |candidate| %>
|
||||
@ -32,8 +32,9 @@
|
||||
<td><%= candidate.status %></td>
|
||||
<td><%= candidate.completed ? link_to("Submitted", admin_result_path(candidate.test_hash)) : "" %></td>
|
||||
<td><%= candidate.reminded ? "Yes" : "" %></td>
|
||||
<td><%= candidate.review_status unless candidate.pending? %></td>
|
||||
<td><%= candidate.interview? %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
<%= paginate @candidates %>
|
||||
</main>
|
||||
|
1
app/views/admin/candidate/resend_welcome.json.erb
Normal file
1
app/views/admin/candidate/resend_welcome.json.erb
Normal file
@ -0,0 +1 @@
|
||||
{ "message" : "Email queued!" }
|
23
app/views/admin/result/_comment.html.erb
Normal file
23
app/views/admin/result/_comment.html.erb
Normal file
@ -0,0 +1,23 @@
|
||||
<div class="comment-message">
|
||||
<%= comment.message %>
|
||||
|
||||
<% if policy(comment).update? %>
|
||||
<label class="comment-edit-btn" for="comment-<%= comment.id %>">edit</label>
|
||||
<% end %>
|
||||
|
||||
<% if comment.edits? %>
|
||||
<div class="comment-edit-stamp">Updated <%= time_ago_in_words(comment.updated_at) %> ago</div>
|
||||
<% else %>
|
||||
<div class="comment-edit-stamp"><%= time_ago_in_words(comment.created_at) %> ago</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="comment-author"><%= comment.user.name %></div>
|
||||
|
||||
|
||||
<% if policy(comment).update? %>
|
||||
<input type="checkbox" id="comment-<%= comment.id %>">
|
||||
<div class="comment-edit-form">
|
||||
<%= render partial: 'comment_form', locals: {comment: comment, test_hash: comment.test_hash } %>
|
||||
</div>
|
||||
<% end %>
|
23
app/views/admin/result/_comment_form.html.erb
Normal file
23
app/views/admin/result/_comment_form.html.erb
Normal file
@ -0,0 +1,23 @@
|
||||
<% if comment.id.nil? %>
|
||||
|
||||
<%= form_for comment, url: admin_create_comment_path(test_hash: test_hash), method: :post do |form| %>
|
||||
<div class="form-group">
|
||||
<%= form.label :message, "New Comment" %>
|
||||
<%= form.text_area :message %>
|
||||
</div>
|
||||
|
||||
<%= submit_tag "Save Comment" %>
|
||||
<% end %>
|
||||
|
||||
<% else %>
|
||||
|
||||
<%= form_for comment, url: admin_update_comment_path(test_hash: test_hash, id: comment.id), method: :post do |form| %>
|
||||
<div class="form-group">
|
||||
<%= form.label :message, "Update Comment" %>
|
||||
<%= form.text_area :message %>
|
||||
</div>
|
||||
|
||||
<%= submit_tag "Update" %>
|
||||
<% end %>
|
||||
|
||||
<% end %>
|
@ -1,34 +1,54 @@
|
||||
<% # TODO: This needs to be extracted into a decorator, or something. It is only a quick hack solution. %>
|
||||
<% # TODO: This should be extracted into a decorator, or something. It is only a quick hack solution. %>
|
||||
|
||||
<% if current_user.acts_as_reviewer? %>
|
||||
<div class="review_meta__votes" data-id="vote-count">
|
||||
<strong>Votes: </strong>
|
||||
<%= link_to admin_up_vote_path(test_hash: @candidate.test_hash), remote: true do %>
|
||||
Yea (<span data-id="up-votes"><%= @candidate.votes.yea.count %></span>)
|
||||
<% end %>
|
||||
<%= link_to admin_down_vote_path(test_hash: @candidate.test_hash), remote: true do %>
|
||||
Nay (<span data-id="down-votes"><%= @candidate.votes.nay.count %></span>)
|
||||
|
||||
<% if @candidate.pending? && current_user.commented_on?(@candidate.test_hash) %>
|
||||
<%= link_to admin_up_vote_path(test_hash: @candidate.test_hash), remote: true do %>
|
||||
Yea (<span data-id="up-votes"><%= @candidate.votes.yea.count %></span>)
|
||||
<% end %>
|
||||
|
||||
<%= link_to admin_down_vote_path(test_hash: @candidate.test_hash), remote: true do %>
|
||||
Nay (<span data-id="down-votes"><%= @candidate.votes.nay.count %></span>)
|
||||
<% end %>
|
||||
<% elsif @candidate.pending? %>
|
||||
<div>You must comment before you can vote</div>
|
||||
<span>Yea (<span data-id="up-votes"><%= @candidate.votes.yea.count %></span>)</span>
|
||||
<span>Nay (<span data-id="down-votes"><%= @candidate.votes.nay.count %></span>)</span>
|
||||
<% else %>
|
||||
Voting closed -
|
||||
Yea (<%= @candidate.votes.yea.count %>) -
|
||||
Nay (<%= @candidate.votes.nay.count %>)
|
||||
<% end %>
|
||||
|
||||
<small>(Your vote: <span data-id="my-vote"><%= current_user.my_vote(@candidate) %></span>)</small>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if current_user.acts_as_manager? %>
|
||||
<div class="review_meta__vetos" data-id="veto-status">
|
||||
<strong>Manager Vetos: </strong>
|
||||
<%= link_to admin_approve_vote_path(test_hash: @candidate.test_hash), remote: true do %>
|
||||
<span data-id="interview-request">
|
||||
<%= @candidate.approved? ? "Requested" : "Request Interview" %>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_to admin_decline_vote_path(test_hash: @candidate.test_hash), remote: true do %>
|
||||
<span data-id="interview-decline">
|
||||
<%= @candidate.declined? ? "Declined" : "Decline Interview" %>
|
||||
</span>
|
||||
<div class="review_meta__vetos">
|
||||
<%= form_tag admin_interview_path(test_hash: @candidate.test_hash) do %>
|
||||
<strong>Interview: </strong>
|
||||
|
||||
<%= radio_button_tag :review_status, :approved, checked = @candidate.approved? %>
|
||||
<%= label_tag :review_status_approved, "Yes" %>
|
||||
|
||||
<%= radio_button_tag :review_status, :declined, checked = @candidate.declined? %>
|
||||
<%= label_tag :review_status_declined, "No" %>
|
||||
|
||||
<div class="review-status-comments">
|
||||
<span>Review comments for recruiter</span>
|
||||
<%= text_area_tag :review_comments, @candidate.review_comments %>
|
||||
<%= submit_tag 'Send Request' %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% else %>
|
||||
|
||||
<strong>Candidate Interview Status: </strong><%= @candidate.review_status %>
|
||||
<% unless @candidate.review_status.blank? %>
|
||||
<div>Review Status Comments:</div>
|
||||
<div><%= @candidate.review_comments %></div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
@ -5,19 +5,24 @@
|
||||
<main class="summary_tpl">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<th>Test ID</th>
|
||||
<th>Experience</th>
|
||||
<th><%= sortable "test_hash", "Test ID" %></th>
|
||||
<th><%= sortable "name" %></th>
|
||||
<th><%= sortable "project", "Client/Project" %></th>
|
||||
<th>Recruiter</th>
|
||||
<th>Interview Request</th>
|
||||
<th><%= sortable "completed_at", "Submitted on" %></th>
|
||||
<th>Interview?</th>
|
||||
</tr>
|
||||
|
||||
<% @candidates.each do |candidate| %>
|
||||
<tr>
|
||||
<td><%= link_to candidate.test_hash, admin_result_path(candidate.test_hash) %></td>
|
||||
<td><%= candidate.experience %> years</td>
|
||||
<td><%= candidate.name if !candidate.pending? %></td>
|
||||
<td><%= candidate.project %></td>
|
||||
<td><%= mail_to(candidate.recruiter.email) %></td>
|
||||
<td><%= candidate.review_status unless candidate.pending? %></td>
|
||||
<td><%= candidate.completed_at.strftime('%D') unless candidate.completed_at.nil? %></td>
|
||||
<td><%= candidate.interview? %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
<%= paginate @candidates %>
|
||||
</main>
|
||||
|
@ -2,41 +2,65 @@
|
||||
content_for :title, "Quiz Review - Skills Assessment Admin"
|
||||
%>
|
||||
|
||||
<main class="summary_tpl admin-review">
|
||||
<h2 class="prft-heading">Quiz Review</h2>
|
||||
<div class="summary_tpl">
|
||||
<div class="admin-review">
|
||||
<h2 class="prft-heading">Quiz Review</h2>
|
||||
|
||||
<div class="review_meta">
|
||||
<div>
|
||||
<strong>Test ID:</strong> <%= @candidate.test_hash %><br />
|
||||
<strong>Years of Experience:</strong> <%= @candidate.experience %><br />
|
||||
<strong>Recruiter Email:</strong> <%= mail_to @candidate.recruiter.name, @candidate.recruiter.email %><br />
|
||||
<div class="review_meta">
|
||||
<div>
|
||||
<% unless @candidate.pending? %>
|
||||
<strong>Name:</strong> <%= @candidate.name %><br />
|
||||
<% end %>
|
||||
<strong>Test ID:</strong> <%= @candidate.test_hash %><br />
|
||||
<strong>Years of Experience:</strong> <%= @candidate.experience %><br />
|
||||
<strong>Client/Project:</strong> <%= @candidate.project %><br />
|
||||
<strong>Position Type:</strong> <%= @candidate.position %><br />
|
||||
<strong>Skill Needs:</strong> <%= @candidate.skill_needs %><br />
|
||||
<strong>Recruiter Email:</strong> <%= mail_to @candidate.recruiter.name, @candidate.recruiter.email %><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div><%= render partial: 'voting' %></div>
|
||||
<% @quiz.each do |question| %>
|
||||
<%= form_for(:answer, url: '#never-post', html:{id: 'summary-form'}) do |form| %>
|
||||
<article class="answer-sec <%= question.input_type %>-type" data-qid="<%= question.question_id %>">
|
||||
<div class="question-heading">
|
||||
<div class="question-title">
|
||||
<h3><%= question.question %></h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="answer-container">
|
||||
<% if question.attachment.present? %>
|
||||
<%= image_tag question.attachment %>
|
||||
<% end %>
|
||||
<fieldset disabled class="answer-block">
|
||||
<%= render partial: "quiz/#{question.input_type}", locals: {question: question, answer: question.answer, form: form} %>
|
||||
</fieldset>
|
||||
</div>
|
||||
</article>
|
||||
<% end #form_tag %>
|
||||
<% end #questions loop %>
|
||||
|
||||
<%= link_to(admin_results_path, { class: 'secondary-btn' }) do %>
|
||||
<button>Back to list</button>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% @quiz.each do |question| %>
|
||||
<%= form_for(:answer, url: '#never-post', html:{id: 'summary-form'}) do |form| %>
|
||||
<article class="answer-sec <%= question.input_type %>-type" data-qid="<%= question.question_id %>">
|
||||
<div class="question-heading">
|
||||
<div class="question-title">
|
||||
<h3><%= question.question %></h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="review-comments">
|
||||
<div>
|
||||
<h2 class="prft-heading">Voting</h2>
|
||||
<div class="review_meta">
|
||||
<div><%= render partial: 'voting' %></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="answer-container">
|
||||
<% if question.attachment.present? %>
|
||||
<%= image_tag question.attachment %>
|
||||
<% end %>
|
||||
<fieldset disabled class="answer-block">
|
||||
<%= render partial: "quiz/#{question.input_type}", locals: {question: question, answer: question.answer, form: form} %>
|
||||
</fieldset>
|
||||
</div>
|
||||
</article>
|
||||
<% end #form_tag %>
|
||||
<% end #questions loop %>
|
||||
|
||||
<%= link_to(admin_results_path, { class: 'secondary-btn' }) do %>
|
||||
<button>Back to list</button>
|
||||
<% end %>
|
||||
</main>
|
||||
<div>
|
||||
<h2 id="comment-header" class="prft-heading">Comments</h2>
|
||||
<% if policy(QuizComment).new? %>
|
||||
<%= render partial: 'comment_form', locals: {comment: @comment, test_hash: @candidate.test_hash } %>
|
||||
<% end %>
|
||||
<%= render partial: 'comment', collection: @comments, locals: { test_hash: @candidate.test_hash } %>
|
||||
<a href="#comment-header">Back to top</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th><%= sortable "name", "User" %></th>
|
||||
<th><%= sortable "email" %></th>
|
||||
<th><%= sortable "role" %></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
||||
|
6
app/views/admin/vote/down.json.erb
Normal file
6
app/views/admin/vote/down.json.erb
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"message" : "Vote Counted",
|
||||
"upCount" : <%= @candidate.votes.yea.count %>,
|
||||
"downCount" : <%= @candidate.votes.nay.count %>,
|
||||
"myVote" : "nay"
|
||||
}
|
6
app/views/admin/vote/up.json.erb
Normal file
6
app/views/admin/vote/up.json.erb
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"message" : "Vote Counted",
|
||||
"upCount" : <%= @candidate.votes.yea.count %>,
|
||||
"downCount" : <%= @candidate.votes.nay.count %>,
|
||||
"myVote" : "yea"
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
<table>
|
||||
<tr>
|
||||
<td class="email-copyright">
|
||||
©2016 All Rights Reserved - Perficient Digital
|
||||
©2016-<%= Time.now.year %> All Rights Reserved - Perficient Digital
|
||||
</td>
|
||||
<td class="email-logo">
|
||||
<%= image_tag(attachments["perficientdigital-logo.jpg"].url, alt:"Perficient Digital") %>
|
||||
|
@ -7,8 +7,9 @@
|
||||
<strong>Candidate email:</strong> <%= @candidate.email %><br />
|
||||
<strong>Candidate ID:</strong> <%= @candidate.test_hash %><br />
|
||||
<strong>Years of experience:</strong> <%= @candidate.experience %> Years<br />
|
||||
<strong>Client/Project:</strong> <%= @candidate.project %><br />
|
||||
</p>
|
||||
|
||||
<p>You will be notified when the candidate has finished taking the test.</p>
|
||||
</columns>
|
||||
</row>
|
||||
</row>
|
||||
|
@ -6,5 +6,6 @@ Candidate name: <%= @candidate.name %>
|
||||
Candidate email: <%= @candidate.email %>
|
||||
Candidate ID: <%= @candidate.test_hash %>
|
||||
Years of experience: <%= @candidate.experience %> Years
|
||||
Client/Project: <%= @candidate.project %>
|
||||
|
||||
You will be notified when the candidate has finished taking the test.
|
||||
|
9
app/views/recruiter_mailer/candidate_reviewed.html.inky
Normal file
9
app/views/recruiter_mailer/candidate_reviewed.html.inky
Normal file
@ -0,0 +1,9 @@
|
||||
<row>
|
||||
<columns class="email-body">
|
||||
<p>The team has <%= @candidate.review_status %> an interview with <strong><%= @candidate.name %></strong> with the following comments:</p>
|
||||
|
||||
<p><%= @candidate.review_comments %></p>
|
||||
|
||||
<p>Thank you</p>
|
||||
</columns>
|
||||
</row>
|
7
app/views/recruiter_mailer/candidate_reviewed.text.erb
Normal file
7
app/views/recruiter_mailer/candidate_reviewed.text.erb
Normal file
@ -0,0 +1,7 @@
|
||||
PERFICIENT/digital - Skills Assessment Test
|
||||
|
||||
The team has <%= @candidate.review_status %> an interview with <%= @candidate.name %> with the following comments:
|
||||
|
||||
<%= @candidate.review_comments %>
|
||||
|
||||
Thank you.
|
@ -1,6 +1,6 @@
|
||||
<row>
|
||||
<columns class="email-body">
|
||||
<p><strong><%= @candidate.name %></strong> has completed the Skills Assessment Test.</p>
|
||||
<p><strong>Martin Ridgway</strong> will let you know if we would like to interview this candidate.</p>
|
||||
<p><strong><%= @candidate.manager.name %></strong> will let you know if we would like to interview this candidate.</p>
|
||||
</columns>
|
||||
</row>
|
||||
</row>
|
||||
|
@ -1,4 +1,4 @@
|
||||
PERFICIENT/digital - Skills Assessment Test
|
||||
|
||||
<%= @candidate.name %> has completed the Skills Assessment Test.
|
||||
Martin Ridgway will let you know if we would like to interview this candidate.
|
||||
<%= @candidate.manager.name %> will let you know if we would like to interview this candidate.
|
||||
|
@ -1,6 +1,9 @@
|
||||
<row>
|
||||
<columns class="email-body">
|
||||
<p>Candidate <strong><%= @candidate.test_hash %></strong> has completed the Skills Assessment Test.</p>
|
||||
<p>
|
||||
Candidate <strong><%= @candidate.test_hash %></strong> has completed the
|
||||
Skills Assessment Test for client/project <%= @candidate.project %>.
|
||||
</p>
|
||||
<p>You can view the results here: <%= link_to nil, admin_result_url(@candidate.test_hash) %>.</p>
|
||||
</columns>
|
||||
</row>
|
||||
|
@ -1,5 +1,5 @@
|
||||
PERFICIENT/digital SKILLS ASSESSMENT RESULTS
|
||||
|
||||
Candidate <%= @candidate.test_hash %> has completed the Skills Assessment Test.
|
||||
Candidate <%= @candidate.test_hash %> has completed the Skills Assessment Test for client/project <%= @candidate.project %>.
|
||||
|
||||
You can view the results here: <%= admin_result_url(@candidate.test_hash) %>.
|
||||
|
13
app/views/reviewer_mailer/new_comment.html.inky
Normal file
13
app/views/reviewer_mailer/new_comment.html.inky
Normal file
@ -0,0 +1,13 @@
|
||||
<row>
|
||||
<columns class="email-body">
|
||||
<p>
|
||||
<%= @comment.user.name %> wrote a comment for quiz <%= @comment.test_hash %>
|
||||
</p>
|
||||
|
||||
<p style="border-top: 1px solid; border-bottom: 1px solid;">
|
||||
<%= @comment.message %>
|
||||
</p>
|
||||
|
||||
<p>You can view and reply here: <%= link_to nil, admin_result_url(@comment.test_hash) %>.</p>
|
||||
</columns>
|
||||
</row>
|
9
app/views/reviewer_mailer/new_comment.text.erb
Normal file
9
app/views/reviewer_mailer/new_comment.text.erb
Normal file
@ -0,0 +1,9 @@
|
||||
SKILLS ASSESSMENT RESULT COMMENT
|
||||
|
||||
<%= @comment.user.name %> wrote a comment for quiz <%= @comment.test_hash %>
|
||||
|
||||
--- --- --- ---
|
||||
<%= @comment.message %>
|
||||
--- --- --- ---
|
||||
|
||||
You can view and reply here: <%= admin_result_url(@comment.test_hash) %>.
|
10
app/views/reviewer_mailer/notify_manager.html.inky
Normal file
10
app/views/reviewer_mailer/notify_manager.html.inky
Normal file
@ -0,0 +1,10 @@
|
||||
<row>
|
||||
<columns class="email-body">
|
||||
<p>Hello <%= @manager.name %>,</p>
|
||||
<p>
|
||||
Everyone has voted and you need to request or decline an interview for
|
||||
<%= link_to nil, admin_result_url(@candidate.test_hash) %>
|
||||
</p>
|
||||
</columns>
|
||||
</row>
|
||||
|
6
app/views/reviewer_mailer/notify_manager.text.erb
Normal file
6
app/views/reviewer_mailer/notify_manager.text.erb
Normal file
@ -0,0 +1,6 @@
|
||||
PERFICIENT/digital SKILLS ASSESSMENT RESULTS
|
||||
|
||||
Hello <%= @manager.name %>,
|
||||
|
||||
Everyone has voted and you need to request or decline an interview for
|
||||
<%= admin_result_url(@candidate.test_hash) %>
|
9
app/views/reviewer_mailer/reminder.html.inky
Normal file
9
app/views/reviewer_mailer/reminder.html.inky
Normal file
@ -0,0 +1,9 @@
|
||||
<row>
|
||||
<columns class="email-body">
|
||||
<p>Hello <%= @reminder.name %>,</p>
|
||||
<p>
|
||||
Please review and vote on the results for <%= link_to nil, admin_result_url(@reminder.test_hash) %>
|
||||
</p>
|
||||
</columns>
|
||||
</row>
|
||||
|
5
app/views/reviewer_mailer/reminder.text.erb
Normal file
5
app/views/reviewer_mailer/reminder.text.erb
Normal file
@ -0,0 +1,5 @@
|
||||
PERFICIENT/digital SKILLS ASSESSMENT RESULTS
|
||||
|
||||
Hello <%= @reminder.name %>,
|
||||
|
||||
Please review and vote on the results for <%= admin_result_url(@reminder.test_hash) %>.
|
@ -1,5 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
class Reminder
|
||||
class CandidateReminder
|
||||
def initialize
|
||||
@collection = reminder_collection
|
||||
end
|
33
app/workers/reviewer_reminder.rb
Normal file
33
app/workers/reviewer_reminder.rb
Normal file
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
class ReviewerReminder
|
||||
def initialize
|
||||
@collection = reminder_collection
|
||||
end
|
||||
|
||||
def count
|
||||
@collection.count
|
||||
end
|
||||
alias size count
|
||||
|
||||
def reminders
|
||||
@reminders ||= @collection.to_hash.map { |r| OpenStruct.new(r) }
|
||||
end
|
||||
|
||||
def send_all
|
||||
reminders.each do |reminder|
|
||||
ReviewerMailer.reminder(reminder).deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def reminder_collection
|
||||
sql = "select u.name, u.email, c.test_hash, c.project
|
||||
from reviewer_votes rev
|
||||
inner join users u on u.id = rev.user_id
|
||||
inner join candidates c on c.id = rev.candidate_id
|
||||
where rev.vote = 0 and rev.veto = 0
|
||||
and u.role != 'manager' and u.active is not false;"
|
||||
ActiveRecord::Base.connection.exec_query(sql)
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user