A very fine release.
This commit is contained in:
commit
12a1502037
@ -5,6 +5,9 @@ AllCops:
|
||||
- bin/**/*
|
||||
- vendor/assets/**/*
|
||||
|
||||
Lint/Debugger:
|
||||
AutoCorrect: False
|
||||
|
||||
Style/AndOr:
|
||||
Enabled: false
|
||||
|
||||
|
52
.sass-lint.yml
Normal file
52
.sass-lint.yml
Normal file
@ -0,0 +1,52 @@
|
||||
files:
|
||||
include: site/**/*.scss
|
||||
ignore:
|
||||
- site/assets/scss/*bootstrap*
|
||||
|
||||
options:
|
||||
formatter: stylish
|
||||
merge-default-rules: true
|
||||
|
||||
# https://github.com/sasstools/sass-lint/tree/master/docs/rules
|
||||
rules:
|
||||
|
||||
class-name-format:
|
||||
- 1
|
||||
- convention: 'hyphenatedbem'
|
||||
|
||||
force-pseudo-nesting: 0
|
||||
|
||||
id-name-format: 0
|
||||
|
||||
leading-zero:
|
||||
- 1
|
||||
- include: true
|
||||
|
||||
nesting-depth:
|
||||
- 1
|
||||
- max-depth: 4
|
||||
|
||||
no-css-comments: 0
|
||||
|
||||
no-color-literals:
|
||||
- 1
|
||||
-
|
||||
allow-rgba: true
|
||||
|
||||
no-duplicate-properties: 1
|
||||
|
||||
no-qualifying-elements:
|
||||
- 1
|
||||
- allow-element-with-attribute: true # input[type='email'] but not div.class-name
|
||||
|
||||
no-vendor-prefixes: 1
|
||||
|
||||
property-sort-order:
|
||||
- 1
|
||||
-
|
||||
# https://github.com/sasstools/sass-lint/blob/develop/lib/config/property-sort-orders/concentric.yml
|
||||
order: concentric
|
||||
# https://github.com/sasstools/sass-lint/blob/develop/lib/config/property-sort-orders/smacss.yml
|
||||
# order: smacss
|
||||
|
||||
quotes: 0
|
4
Gemfile
4
Gemfile
@ -4,11 +4,12 @@ source 'https://rubygems.org'
|
||||
gem 'figaro', '~> 1.1.1'
|
||||
gem 'bcrypt', '~> 3.1.7'
|
||||
gem 'mysql2', '>= 0.3.18', '< 0.5'
|
||||
gem 'rails', '~> 5.0', '>= 5.0.0.1'
|
||||
gem 'rails', '~> 5.0', '>= 5.0.1'
|
||||
|
||||
gem 'jbuilder', '~> 2.6'
|
||||
gem 'jquery-rails'
|
||||
gem 'json', '~> 2.0.2'
|
||||
gem 'kaminari'
|
||||
gem 'mailjet', '~> 1.3.8'
|
||||
gem 'puma', '~> 3.0'
|
||||
gem 'pundit'
|
||||
@ -53,6 +54,7 @@ group :development, :test do
|
||||
gem 'byebug', platform: :mri
|
||||
gem 'pry-byebug'
|
||||
gem 'pry-rails'
|
||||
gem 'faker'
|
||||
|
||||
gem 'brakeman'
|
||||
gem 'rubocop', '~> 0.42.0'
|
||||
|
168
Gemfile.lock
168
Gemfile.lock
@ -1,46 +1,47 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (5.0.0.1)
|
||||
actionpack (= 5.0.0.1)
|
||||
actioncable (5.0.1)
|
||||
actionpack (= 5.0.1)
|
||||
nio4r (~> 1.2)
|
||||
websocket-driver (~> 0.6.1)
|
||||
actionmailer (5.0.0.1)
|
||||
actionpack (= 5.0.0.1)
|
||||
actionview (= 5.0.0.1)
|
||||
activejob (= 5.0.0.1)
|
||||
actionmailer (5.0.1)
|
||||
actionpack (= 5.0.1)
|
||||
actionview (= 5.0.1)
|
||||
activejob (= 5.0.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.0.0.1)
|
||||
actionview (= 5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
actionpack (5.0.1)
|
||||
actionview (= 5.0.1)
|
||||
activesupport (= 5.0.1)
|
||||
rack (~> 2.0)
|
||||
rack-test (~> 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
actionview (5.0.1)
|
||||
activesupport (= 5.0.1)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
activejob (5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
activejob (5.0.1)
|
||||
activesupport (= 5.0.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
activerecord (5.0.0.1)
|
||||
activemodel (= 5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
activemodel (5.0.1)
|
||||
activesupport (= 5.0.1)
|
||||
activerecord (5.0.1)
|
||||
activemodel (= 5.0.1)
|
||||
activesupport (= 5.0.1)
|
||||
arel (~> 7.0)
|
||||
activesupport (5.0.0.1)
|
||||
activesupport (5.0.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.4.0)
|
||||
addressable (2.5.0)
|
||||
public_suffix (~> 2.0, >= 2.0.2)
|
||||
ansi (1.5.0)
|
||||
arel (7.1.1)
|
||||
arel (7.1.4)
|
||||
ast (2.3.0)
|
||||
awesome_print (1.7.0)
|
||||
bcrypt (3.1.11)
|
||||
@ -53,29 +54,31 @@ GEM
|
||||
bourbon (4.2.7)
|
||||
sass (~> 3.4)
|
||||
thor (~> 0.19)
|
||||
brakeman (3.4.0)
|
||||
brakeman (3.4.1)
|
||||
builder (3.2.2)
|
||||
byebug (9.0.5)
|
||||
byebug (9.0.6)
|
||||
choice (0.2.0)
|
||||
coderay (1.1.1)
|
||||
concurrent-ruby (1.0.2)
|
||||
css_parser (1.4.5)
|
||||
concurrent-ruby (1.0.4)
|
||||
css_parser (1.4.7)
|
||||
addressable
|
||||
debug_inspector (0.0.2)
|
||||
docile (1.1.5)
|
||||
domain_name (0.5.20160615)
|
||||
domain_name (0.5.20161129)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
em-websocket (0.5.1)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
erubis (2.7.0)
|
||||
eventmachine (1.2.0.1)
|
||||
eventmachine (1.2.1)
|
||||
execjs (2.7.0)
|
||||
faker (1.7.3)
|
||||
i18n (~> 0.5)
|
||||
ffi (1.9.14)
|
||||
figaro (1.1.1)
|
||||
thor (~> 0.14)
|
||||
formatador (0.2.5)
|
||||
foundation_emails (2.2.0.0)
|
||||
foundation_emails (2.2.1.0)
|
||||
globalid (0.3.7)
|
||||
activesupport (>= 4.1.0)
|
||||
guard (2.14.0)
|
||||
@ -106,27 +109,40 @@ GEM
|
||||
guard (>= 2.0.0)
|
||||
guard-compat (~> 1.0)
|
||||
htmlentities (4.3.4)
|
||||
http-cookie (1.0.2)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (0.7.0)
|
||||
inky-rb (1.3.6.1)
|
||||
inky-rb (1.3.7.2)
|
||||
foundation_emails (~> 2)
|
||||
jbuilder (2.6.0)
|
||||
nokogiri
|
||||
jbuilder (2.6.1)
|
||||
activesupport (>= 3.0.0, < 5.1)
|
||||
multi_json (~> 1.2)
|
||||
jquery-rails (4.2.1)
|
||||
jquery-rails (4.2.2)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (2.0.2)
|
||||
json (2.0.3)
|
||||
kaminari (1.0.1)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.0.1)
|
||||
kaminari-activerecord (= 1.0.1)
|
||||
kaminari-core (= 1.0.1)
|
||||
kaminari-actionview (1.0.1)
|
||||
actionview
|
||||
kaminari-core (= 1.0.1)
|
||||
kaminari-activerecord (1.0.1)
|
||||
activerecord
|
||||
kaminari-core (= 1.0.1)
|
||||
kaminari-core (1.0.1)
|
||||
listen (3.1.5)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
ruby_dep (~> 1.2)
|
||||
loofah (2.0.3)
|
||||
nokogiri (>= 1.5.9)
|
||||
lumberjack (1.0.10)
|
||||
lumberjack (1.0.11)
|
||||
mail (2.6.4)
|
||||
mime-types (>= 1.16, < 4)
|
||||
mailjet (1.3.8)
|
||||
@ -138,29 +154,27 @@ GEM
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0521)
|
||||
mini_portile2 (2.1.0)
|
||||
minitest (5.9.0)
|
||||
minitest-reporters (1.1.11)
|
||||
minitest (5.10.1)
|
||||
minitest-reporters (1.1.13)
|
||||
ansi
|
||||
builder
|
||||
minitest (>= 5.0)
|
||||
ruby-progressbar
|
||||
multi_json (1.12.1)
|
||||
mysql2 (0.4.4)
|
||||
mysql2 (0.4.5)
|
||||
neat (1.8.0)
|
||||
sass (>= 3.3)
|
||||
thor (~> 0.19)
|
||||
nenv (0.3.0)
|
||||
netrc (0.11.0)
|
||||
nio4r (1.2.1)
|
||||
nokogiri (1.6.8)
|
||||
nokogiri (1.7.0.1)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
pkg-config (~> 1.1.7)
|
||||
notiffany (0.1.1)
|
||||
nenv (~> 0.1)
|
||||
shellany (~> 0.0)
|
||||
parser (2.3.1.2)
|
||||
parser (2.3.3.1)
|
||||
ast (~> 2.2)
|
||||
pkg-config (1.1.7)
|
||||
policy-assertions (0.0.3)
|
||||
activesupport (>= 3.0.0)
|
||||
pundit (>= 1.0.0)
|
||||
@ -168,19 +182,20 @@ GEM
|
||||
premailer (1.8.7)
|
||||
css_parser (>= 1.4.5)
|
||||
htmlentities (>= 4.0.0)
|
||||
premailer-rails (1.9.4)
|
||||
premailer-rails (1.9.5)
|
||||
actionmailer (>= 3, < 6)
|
||||
premailer (~> 1.7, >= 1.7.9)
|
||||
pry (0.10.4)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
pry-byebug (3.4.0)
|
||||
pry-byebug (3.4.2)
|
||||
byebug (~> 9.0)
|
||||
pry (~> 0.10)
|
||||
pry-rails (0.3.4)
|
||||
pry (>= 0.9.10)
|
||||
puma (3.6.0)
|
||||
public_suffix (2.0.5)
|
||||
puma (3.6.2)
|
||||
pundit (1.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
rack (2.0.1)
|
||||
@ -188,25 +203,25 @@ GEM
|
||||
rack
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (5.0.0.1)
|
||||
actioncable (= 5.0.0.1)
|
||||
actionmailer (= 5.0.0.1)
|
||||
actionpack (= 5.0.0.1)
|
||||
actionview (= 5.0.0.1)
|
||||
activejob (= 5.0.0.1)
|
||||
activemodel (= 5.0.0.1)
|
||||
activerecord (= 5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
rails (5.0.1)
|
||||
actioncable (= 5.0.1)
|
||||
actionmailer (= 5.0.1)
|
||||
actionpack (= 5.0.1)
|
||||
actionview (= 5.0.1)
|
||||
activejob (= 5.0.1)
|
||||
activemodel (= 5.0.1)
|
||||
activerecord (= 5.0.1)
|
||||
activesupport (= 5.0.1)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 5.0.0.1)
|
||||
railties (= 5.0.1)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.1)
|
||||
actionpack (~> 5.x)
|
||||
actionview (~> 5.x)
|
||||
activesupport (~> 5.x)
|
||||
rails-dom-testing (2.0.1)
|
||||
rails-dom-testing (2.0.2)
|
||||
activesupport (>= 4.2.0, < 6.0)
|
||||
nokogiri (~> 1.6.0)
|
||||
nokogiri (~> 1.6)
|
||||
rails-erd (1.5.0)
|
||||
activerecord (>= 3.2)
|
||||
activesupport (>= 3.2)
|
||||
@ -214,15 +229,15 @@ GEM
|
||||
ruby-graphviz (~> 1.2)
|
||||
rails-html-sanitizer (1.0.3)
|
||||
loofah (~> 2.0)
|
||||
railties (5.0.0.1)
|
||||
actionpack (= 5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
railties (5.0.1)
|
||||
actionpack (= 5.0.1)
|
||||
activesupport (= 5.0.1)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.1.0)
|
||||
rake (11.2.2)
|
||||
rb-fsevent (0.9.7)
|
||||
rainbow (2.2.1)
|
||||
rake (12.0.0)
|
||||
rb-fsevent (0.9.8)
|
||||
rb-inotify (0.9.7)
|
||||
ffi (>= 0.5.0)
|
||||
rest-client (2.0.0)
|
||||
@ -237,8 +252,8 @@ GEM
|
||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||
ruby-graphviz (1.2.2)
|
||||
ruby-progressbar (1.8.1)
|
||||
ruby_dep (1.4.0)
|
||||
sass (3.4.22)
|
||||
ruby_dep (1.5.0)
|
||||
sass (3.4.23)
|
||||
sass-rails (5.0.6)
|
||||
railties (>= 4.0.0, < 6)
|
||||
sass (~> 3.1)
|
||||
@ -253,18 +268,19 @@ GEM
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.0)
|
||||
slop (3.6.0)
|
||||
spring (1.7.2)
|
||||
spring-watcher-listen (2.0.0)
|
||||
spring (2.0.0)
|
||||
activesupport (>= 4.2)
|
||||
spring-watcher-listen (2.0.1)
|
||||
listen (>= 2.7, < 4.0)
|
||||
spring (~> 1.2)
|
||||
sprockets (3.7.0)
|
||||
spring (>= 1.2, < 3.0)
|
||||
sprockets (3.7.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.1.1)
|
||||
sprockets-rails (3.2.0)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
thor (0.19.1)
|
||||
thor (0.19.4)
|
||||
thread_safe (0.3.5)
|
||||
tilt (2.0.5)
|
||||
turbolinks (5.0.1)
|
||||
@ -272,13 +288,13 @@ GEM
|
||||
turbolinks-source (5.0.0)
|
||||
tzinfo (1.2.2)
|
||||
thread_safe (~> 0.1)
|
||||
uglifier (3.0.2)
|
||||
uglifier (3.0.4)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.2)
|
||||
unicode-display_width (1.1.1)
|
||||
web-console (3.3.1)
|
||||
unicode-display_width (1.1.2)
|
||||
web-console (3.4.0)
|
||||
actionview (>= 5.0)
|
||||
activemodel (>= 5.0)
|
||||
debug_inspector
|
||||
@ -298,6 +314,7 @@ DEPENDENCIES
|
||||
bourbon
|
||||
brakeman
|
||||
byebug
|
||||
faker
|
||||
figaro (~> 1.1.1)
|
||||
guard
|
||||
guard-brakeman
|
||||
@ -309,6 +326,7 @@ DEPENDENCIES
|
||||
jbuilder (~> 2.6)
|
||||
jquery-rails
|
||||
json (~> 2.0.2)
|
||||
kaminari
|
||||
listen
|
||||
mailjet (~> 1.3.8)
|
||||
minitest-reporters
|
||||
@ -321,7 +339,7 @@ DEPENDENCIES
|
||||
puma (~> 3.0)
|
||||
pundit
|
||||
rack-livereload
|
||||
rails (~> 5.0, >= 5.0.0.1)
|
||||
rails (~> 5.0, >= 5.0.1)
|
||||
rails-controller-testing
|
||||
rails-erd
|
||||
rubocop (~> 0.42.0)
|
||||
@ -336,4 +354,4 @@ DEPENDENCIES
|
||||
web-console
|
||||
|
||||
BUNDLED WITH
|
||||
1.12.5
|
||||
1.13.3
|
||||
|
@ -79,7 +79,7 @@ guard :shell, all_on_start: true do
|
||||
end
|
||||
end
|
||||
|
||||
guard :rubocop, cli: %w(-D -S) do
|
||||
guard :rubocop, cli: %w(-D -S -a) do
|
||||
watch(/.rubocop.yml/)
|
||||
watch(/.+\.rb$/)
|
||||
watch(/Rakefile/)
|
||||
|
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,6 +7,7 @@
|
||||
<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>
|
||||
|
@ -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>
|
@ -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
|
@ -30,5 +30,8 @@ module SkillAssessmentApp
|
||||
g.helper false
|
||||
g.routes false
|
||||
end
|
||||
|
||||
# let's use lib/utils for dev only utility classes & helpers
|
||||
config.autoload_paths << "#{Rails.root}/lib/utils"
|
||||
end
|
||||
end
|
||||
|
@ -1,10 +1,81 @@
|
||||
{
|
||||
"ignored_warnings": [
|
||||
{
|
||||
"warning_type": "SQL Injection",
|
||||
"warning_code": 0,
|
||||
"fingerprint": "6f3216446dca0fa79e96267eb0323d50cc59e7bc1e1529fd160cd5beb185e2f2",
|
||||
"message": "Possible SQL injection",
|
||||
"file": "app/controllers/admin/candidate_controller.rb",
|
||||
"line": 7,
|
||||
"link": "http://brakemanscanner.org/docs/warning_types/sql_injection/",
|
||||
"code": "Candidate.order(\"#{sort_column} #{sort_direction}\")",
|
||||
"render_path": null,
|
||||
"location": {
|
||||
"type": "method",
|
||||
"class": "Admin::CandidateController",
|
||||
"method": "index"
|
||||
},
|
||||
"user_input": "sort_column",
|
||||
"confidence": "Medium",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"warning_type": "SQL Injection",
|
||||
"warning_code": 0,
|
||||
"fingerprint": "9e802ac2067c1ee551f97c1f37816b585451abc42b6a2c7903c6d97aa042da61",
|
||||
"message": "Possible SQL injection",
|
||||
"file": "app/controllers/admin/user_controller.rb",
|
||||
"line": 5,
|
||||
"link": "http://brakemanscanner.org/docs/warning_types/sql_injection/",
|
||||
"code": "User.order(\"#{sort_column} #{sort_direction}\")",
|
||||
"render_path": null,
|
||||
"location": {
|
||||
"type": "method",
|
||||
"class": "Admin::UserController",
|
||||
"method": "index"
|
||||
},
|
||||
"user_input": "sort_column",
|
||||
"confidence": "Medium",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"warning_type": "SQL Injection",
|
||||
"warning_code": 0,
|
||||
"fingerprint": "a65b53d63f1d43cb831947d0693d2d1b8819e21aec5ee18bf09b577ade02a0ee",
|
||||
"message": "Possible SQL injection",
|
||||
"file": "app/controllers/admin/result_controller.rb",
|
||||
"line": 16,
|
||||
"link": "http://brakemanscanner.org/docs/warning_types/sql_injection/",
|
||||
"code": "Candidate.where(:completed => true).includes(:recruiter).order(\"#{(\"(case when review_status = 0 then '' else name end)\" or sort_column)} #{sort_direction}\")",
|
||||
"render_path": null,
|
||||
"location": {
|
||||
"type": "method",
|
||||
"class": "Admin::ResultController",
|
||||
"method": "index"
|
||||
},
|
||||
"user_input": "sort_column",
|
||||
"confidence": "Medium",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"warning_type": "Dynamic Render Path",
|
||||
"warning_code": 15,
|
||||
"fingerprint": "da17225c940987e6239cc4ecfe27bcb1e5da2db1134435dc3e1025d97927e0ba",
|
||||
"message": "Render path contains parameter value",
|
||||
"file": "app/views/admin/question/options.html.erb",
|
||||
"line": 3,
|
||||
"link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
|
||||
"code": "render(partial => \"admin/question/#{params[:input_type]}\", { :locals => ({ :question => ((Question.find(params[:question_id]) or Question.new)) }) })",
|
||||
"render_path": [{"type":"controller","class":"Admin::QuestionController","method":"options","line":58,"file":"app/controllers/admin/question_controller.rb"}],
|
||||
"location": {
|
||||
"type": "template",
|
||||
"template": "admin/question/options"
|
||||
},
|
||||
"user_input": "params[:input_type]",
|
||||
"confidence": "Medium",
|
||||
"note": "false positive"
|
||||
}
|
||||
],
|
||||
"updated": "2016-09-19 09:06:25 -0500",
|
||||
"brakeman_version": "3.4.0"
|
||||
"updated": "2017-03-06 12:16:23 -0600",
|
||||
"brakeman_version": "3.4.1"
|
||||
}
|
||||
|
12
config/initializers/kaminari_config.rb
Normal file
12
config/initializers/kaminari_config.rb
Normal file
@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
Kaminari.configure do |config|
|
||||
config.default_per_page = 10
|
||||
# config.max_per_page = nil
|
||||
# config.window = 4
|
||||
# config.outer_window = 0
|
||||
# config.left = 0
|
||||
# config.right = 0
|
||||
# config.page_method_name = :page
|
||||
# config.param_name = :page
|
||||
# config.params_on_first_page = false
|
||||
end
|
@ -1,4 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
# Neat routing visualizer, in irb:
|
||||
# File.open('routing.html', 'w+'){|f| f.write Rails.application.routes.router.visualizer }
|
||||
Rails.application.routes.draw do
|
||||
get "/styleguide", to: "application#styleguide", as: :styleguide
|
||||
get "/admin/styleguide", to: "application#styleguide"
|
||||
@ -45,15 +47,17 @@ Rails.application.routes.draw do
|
||||
post "/admin/candidate/new", to: "admin/candidate#create", as: :admin_create_candidate
|
||||
get "/admin/candidate/:id", to: "admin/candidate#edit", as: :admin_edit_candidate
|
||||
post "/admin/candidate/:id", to: "admin/candidate#update", as: :admin_update_candidate
|
||||
get "/admin/candidate/:id/resend", to: "admin/candidate#resend_welcome", as: :admin_resend_welcome
|
||||
get "/admin/candidate/:id/resend", to: "admin/candidate#resend_welcome", as: :admin_resend_welcome, defaults: { format: 'json' }
|
||||
|
||||
get "/admin/results", to: "admin/result#index", as: :admin_results
|
||||
get "/admin/result/:test_hash", to: "admin/result#view", as: :admin_result
|
||||
|
||||
get "admin/vote/:test_hash/up", to: "admin/vote#up", as: :admin_up_vote
|
||||
get "admin/vote/:test_hash/down", to: "admin/vote#down", as: :admin_down_vote
|
||||
get "admin/vote/:test_hash/approve", to: "admin/vote#approve", as: :admin_approve_vote
|
||||
get "admin/vote/:test_hash/decline", to: "admin/vote#decline", as: :admin_decline_vote
|
||||
post "/admin/comment/:test_hash/:id", to: "admin/comment#update", as: :admin_update_comment
|
||||
post "/admin/comment/:test_hash", to: "admin/comment#create", as: :admin_create_comment
|
||||
|
||||
get "admin/vote/:test_hash/up", to: "admin/vote#up", as: :admin_up_vote, defaults: { format: 'json' }
|
||||
get "admin/vote/:test_hash/down", to: "admin/vote#down", as: :admin_down_vote, defaults: { format: 'json' }
|
||||
post "admin/vote/:test_hash", to: "admin/vote#interview_request", as: :admin_interview
|
||||
|
||||
get "/admin", to: "admin/dashboard#show", as: :admin
|
||||
|
||||
|
6
db/migrate/20161204213828_remove_vote_locking.rb
Normal file
6
db/migrate/20161204213828_remove_vote_locking.rb
Normal file
@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
class RemoveVoteLocking < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
remove_column :reviewer_votes, :locked, :boolean, default: false, null: false
|
||||
end
|
||||
end
|
12
db/migrate/20161204220527_add_completed_at.rb
Normal file
12
db/migrate/20161204220527_add_completed_at.rb
Normal file
@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
class AddCompletedAt < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
add_column :candidates, :completed_at, :datetime, after: :completed
|
||||
|
||||
Candidate.reset_column_information
|
||||
Candidate.where(completed: true).each do |candidate|
|
||||
candidate.completed_at = candidate.updated_at
|
||||
candidate.save
|
||||
end
|
||||
end
|
||||
end
|
6
db/migrate/20170208212526_add_project_to_client.rb
Normal file
6
db/migrate/20170208212526_add_project_to_client.rb
Normal file
@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
class AddProjectToClient < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
add_column :candidates, :project, :string, after: :experience
|
||||
end
|
||||
end
|
12
db/migrate/20170210165110_create_quiz_comments.rb
Normal file
12
db/migrate/20170210165110_create_quiz_comments.rb
Normal file
@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
class CreateQuizComments < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
create_table :quiz_comments do |t|
|
||||
t.integer :user_id
|
||||
t.string :test_hash
|
||||
t.text :message
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
7
db/migrate/20170227154554_add_attributes_to_candidate.rb
Normal file
7
db/migrate/20170227154554_add_attributes_to_candidate.rb
Normal file
@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
class AddAttributesToCandidate < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
add_column :candidates, :skill_needs, :string, after: :project
|
||||
add_column :candidates, :position, :string, after: :project
|
||||
end
|
||||
end
|
6
db/migrate/20170228161543_add_comments_to_candidate.rb
Normal file
6
db/migrate/20170228161543_add_comments_to_candidate.rb
Normal file
@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
class AddCommentsToCandidate < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
add_column :candidates, :review_comments, :text, after: :review_status
|
||||
end
|
||||
end
|
@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
class RemoveLastRemindedFromReviewerVotes < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
remove_column :reviewer_votes, :last_reminded, :datetime
|
||||
end
|
||||
end
|
31
db/schema.rb
31
db/schema.rb
@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20161120175737) do
|
||||
ActiveRecord::Schema.define(version: 20170228161729) do
|
||||
|
||||
create_table "answers", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
|
||||
t.integer "candidate_id"
|
||||
@ -30,13 +30,18 @@ ActiveRecord::Schema.define(version: 20161120175737) do
|
||||
t.string "name"
|
||||
t.string "email"
|
||||
t.string "experience"
|
||||
t.string "project"
|
||||
t.string "position"
|
||||
t.string "skill_needs"
|
||||
t.integer "recruiter_id"
|
||||
t.boolean "completed"
|
||||
t.datetime "completed_at"
|
||||
t.boolean "reminded"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "quiz_id"
|
||||
t.integer "review_status", default: 0, null: false
|
||||
t.integer "review_status", default: 0, null: false
|
||||
t.text "review_comments", limit: 65535
|
||||
t.index ["quiz_id"], name: "index_candidates_on_quiz_id", using: :btree
|
||||
t.index ["recruiter_id"], name: "index_candidates_on_recruiter_id", using: :btree
|
||||
t.index ["test_hash"], name: "index_candidates_on_test_hash", unique: true, using: :btree
|
||||
@ -58,6 +63,14 @@ ActiveRecord::Schema.define(version: 20161120175737) do
|
||||
t.index ["sort"], name: "index_questions_on_sort", using: :btree
|
||||
end
|
||||
|
||||
create_table "quiz_comments", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
|
||||
t.integer "user_id"
|
||||
t.string "test_hash"
|
||||
t.text "message", limit: 65535
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "quizzes", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
|
||||
t.string "unit"
|
||||
t.string "dept"
|
||||
@ -77,12 +90,10 @@ ActiveRecord::Schema.define(version: 20161120175737) do
|
||||
create_table "reviewer_votes", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
|
||||
t.integer "candidate_id"
|
||||
t.integer "user_id"
|
||||
t.integer "vote", default: 0, null: false
|
||||
t.integer "veto", default: 0, null: false
|
||||
t.datetime "last_reminded"
|
||||
t.boolean "locked", default: false, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "vote", default: 0, null: false
|
||||
t.integer "veto", default: 0, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["candidate_id", "user_id"], name: "index_reviewer_votes_on_candidate_id_and_user_id", unique: true, using: :btree
|
||||
end
|
||||
|
||||
|
@ -2,7 +2,13 @@
|
||||
namespace :reminders do
|
||||
desc "send reminders to stagnate quizes"
|
||||
task send_all: :environment do
|
||||
reminders = Reminder.new
|
||||
reminders = CandidateReminder.new
|
||||
reminders.send_all
|
||||
end
|
||||
|
||||
desc "send reminders to reviewers"
|
||||
task reviewers: :environment do
|
||||
reminders = ReviewerReminder.new
|
||||
reminders.send_all
|
||||
end
|
||||
end
|
||||
|
69
lib/utils/fake_quiz.rb
Normal file
69
lib/utils/fake_quiz.rb
Normal file
@ -0,0 +1,69 @@
|
||||
# frozen_string_literal: true
|
||||
class FakeQuiz
|
||||
def create_completed_quizzes num = 10
|
||||
num.times do
|
||||
candidate = create_candidate Faker::Name.name
|
||||
answer_questions candidate
|
||||
candidate.update_attributes(completed: true, completed_at: Time.zone.now - rand(0..112).days)
|
||||
candidate.build_reviews
|
||||
end
|
||||
end
|
||||
|
||||
def create_candidate name
|
||||
Candidate.create(name: name,
|
||||
email: "#{Faker::Internet.user_name(name)}@mailinator.com",
|
||||
experience: rando_experience,
|
||||
project: Faker::Company.name,
|
||||
recruiter_id: recruiter_id,
|
||||
quiz_id: fed_quiz_id)
|
||||
end
|
||||
|
||||
def answer_questions candidate
|
||||
candidate.quiz.questions.each do |question|
|
||||
candidate.answers.create(question_id: question.id,
|
||||
answer: generate_answer(question),
|
||||
submitted: true)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fed_quiz_id
|
||||
Quiz.find_by(dept: 'fed').id
|
||||
end
|
||||
|
||||
def recruiter_id
|
||||
User.find_by(name: 'Sam Recruiter').id
|
||||
end
|
||||
|
||||
def rando_experience
|
||||
%w(0-3 4-6 7-9 10-14 15+)[rand(0..4)]
|
||||
end
|
||||
|
||||
def generate_answer question # rubocop:disable Metrics/MethodLength
|
||||
case question.input_type
|
||||
when "checkbox"
|
||||
question.input_options
|
||||
when "checkbox_other"
|
||||
{
|
||||
other: Faker::TwinPeaks.quote,
|
||||
options: question.input_options
|
||||
}
|
||||
when "radio"
|
||||
question.input_options.sample
|
||||
when "radio_other"
|
||||
{
|
||||
other: Faker::TwinPeaks.quote,
|
||||
options: question.input_options.sample
|
||||
}
|
||||
when "live_code"
|
||||
{
|
||||
html: "<p>#{Faker::TwinPeaks.quote}</p>",
|
||||
css: "body {color: #{Faker::Color.hex_color}}",
|
||||
text: Faker::TwinPeaks.quote
|
||||
}
|
||||
else
|
||||
Faker::TwinPeaks.quote
|
||||
end
|
||||
end # rubocop:enable Metrics/MethodLength
|
||||
end
|
@ -26,6 +26,18 @@ module Admin
|
||||
assert_redirected_to admin_url
|
||||
end
|
||||
|
||||
test "should auth to original request" do
|
||||
elsie = candidates(:elsie)
|
||||
|
||||
get admin_result_url(test_hash: elsie.test_hash)
|
||||
|
||||
assert_redirected_to admin_login_url
|
||||
post admin_auth_url, params: { auth:
|
||||
{ email: 'fed.reviewer@mailinator.com', password: 'password' } }
|
||||
|
||||
assert_redirected_to admin_result_url(test_hash: elsie.test_hash)
|
||||
end
|
||||
|
||||
test "should FAIL auth" do
|
||||
post admin_auth_url, params: { auth:
|
||||
{ email: 'alan.admin@mailinator.com', password: 'b@d9a$$werD' } }
|
||||
|
@ -23,8 +23,14 @@ module Admin
|
||||
|
||||
assert_enqueued_jobs 2 do
|
||||
assert_difference("Candidate.count") do
|
||||
post admin_create_candidate_path, params: { candidate:
|
||||
{ name: 'new name', email: 'test@mailinator.com', experience: '0-3', quiz_id: quizzes(:fed).id } }
|
||||
post admin_create_candidate_path, params: { candidate: {
|
||||
name: 'new name',
|
||||
email: 'test@mailinator.com',
|
||||
experience: '0-3',
|
||||
position: 'full-time',
|
||||
project: 'client project',
|
||||
quiz_id: quizzes(:fed).id
|
||||
} }
|
||||
end
|
||||
end
|
||||
assert_redirected_to admin_candidates_path
|
||||
|
103
test/controllers/admin/comment_controller_test.rb
Normal file
103
test/controllers/admin/comment_controller_test.rb
Normal file
@ -0,0 +1,103 @@
|
||||
# frozen_string_literal: true
|
||||
require 'test_helper'
|
||||
|
||||
module Admin
|
||||
class CommentControllerTest < ActionDispatch::IntegrationTest
|
||||
include ActiveJob::TestHelper
|
||||
|
||||
test "should post update" do
|
||||
auth_manager
|
||||
comment = quiz_comments(:com5)
|
||||
post admin_update_comment_url(test_hash: comment.test_hash, id: comment.id),
|
||||
params: { quiz_comment: { message: 'updated comment' } }
|
||||
|
||||
assert_redirected_to admin_result_url(test_hash: comment.test_hash)
|
||||
assert flash[:success]
|
||||
refute_equal comment.message, QuizComment.find_by(id: comment.id).message
|
||||
end
|
||||
|
||||
test "should require message to update" do
|
||||
auth_manager
|
||||
comment = quiz_comments(:com5)
|
||||
post admin_update_comment_url(test_hash: comment.test_hash, id: comment.id),
|
||||
params: { quiz_comment: { message: '' } }
|
||||
|
||||
assert_redirected_to admin_result_url(test_hash: comment.test_hash)
|
||||
assert flash[:error]
|
||||
assert_equal comment.message, QuizComment.find_by(id: comment.id).message
|
||||
end
|
||||
|
||||
test "should post create" do
|
||||
auth_reviewer
|
||||
candidate = candidates(:stacy)
|
||||
|
||||
assert_difference("QuizComment.count") do
|
||||
post admin_create_comment_url(test_hash: candidate.test_hash),
|
||||
params: { quiz_comment: { message: 'this is a test comment' } }
|
||||
end
|
||||
|
||||
assert_redirected_to admin_result_url(test_hash: candidate.test_hash)
|
||||
assert flash[:success]
|
||||
end
|
||||
|
||||
test "admin can post comment" do
|
||||
auth_admin
|
||||
candidate = candidates(:stacy)
|
||||
|
||||
assert_difference("QuizComment.count") do
|
||||
post admin_create_comment_url(test_hash: candidate.test_hash),
|
||||
params: { quiz_comment: { message: 'this is an admin comment' } }
|
||||
end
|
||||
|
||||
assert_redirected_to admin_result_url(test_hash: candidate.test_hash)
|
||||
assert flash[:success]
|
||||
end
|
||||
|
||||
test "should queue emails on create" do
|
||||
auth_reviewer
|
||||
candidate = candidates(:stacy)
|
||||
|
||||
assert_enqueued_jobs 1 do
|
||||
assert_difference("QuizComment.count", 1) do
|
||||
post admin_create_comment_url(test_hash: candidate.test_hash),
|
||||
params: { quiz_comment: { message: 'this is a test comment' } }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "should require comment to create" do
|
||||
auth_reviewer
|
||||
candidate = candidates(:stacy)
|
||||
|
||||
assert_difference("QuizComment.count", 0) do
|
||||
post admin_create_comment_url(test_hash: candidate.test_hash),
|
||||
params: { quiz_comment: { message: '' } }
|
||||
end
|
||||
|
||||
assert_redirected_to admin_result_url(test_hash: candidate.test_hash)
|
||||
assert flash[:error]
|
||||
end
|
||||
|
||||
test "should not edit others comments" do
|
||||
auth_reviewer
|
||||
comment = quiz_comments(:com5)
|
||||
post admin_update_comment_url(test_hash: comment.test_hash, id: comment.id),
|
||||
params: { quiz_comment: { message: 'updated comment' } }
|
||||
|
||||
assert_redirected_to admin_login_url
|
||||
assert_equal comment.message, QuizComment.find_by(id: comment.id).message
|
||||
end
|
||||
|
||||
test "can not comment on Gustov" do
|
||||
auth_reviewer
|
||||
candidate = candidates(:gustov)
|
||||
|
||||
assert_difference("QuizComment.count", 0) do
|
||||
post admin_create_comment_url(test_hash: candidate.test_hash),
|
||||
params: { quiz_comment: { message: 'this is a test comment' } }
|
||||
end
|
||||
|
||||
assert_redirected_to admin_login_url
|
||||
end
|
||||
end
|
||||
end
|
@ -3,10 +3,30 @@ require 'test_helper'
|
||||
|
||||
module Admin
|
||||
class VoteControllerTest < ActionDispatch::IntegrationTest
|
||||
test "reviewer can up vote henry" do
|
||||
include ActiveJob::TestHelper
|
||||
|
||||
test "reviewer can only vote after commenting" do
|
||||
auth_user users(:reviewer)
|
||||
henry = candidates(:henry)
|
||||
|
||||
assert_difference("Candidate.find(#{henry.id}).votes.yea.count", 0) do
|
||||
get admin_up_vote_url(henry.test_hash)
|
||||
end
|
||||
|
||||
post admin_create_comment_url(test_hash: henry.test_hash),
|
||||
params: { quiz_comment: { message: 'this is a comment to vote' } }
|
||||
|
||||
assert_difference("Candidate.find(#{henry.id}).votes.yea.count", 1) do
|
||||
get admin_up_vote_url(henry.test_hash)
|
||||
end
|
||||
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "reviewer can up vote henry" do
|
||||
auth_user users(:reviewer2)
|
||||
henry = candidates(:henry)
|
||||
|
||||
assert_difference("Candidate.find(#{henry.id}).votes.yea.count", 1) do
|
||||
get admin_up_vote_url(henry.test_hash)
|
||||
end
|
||||
@ -14,7 +34,7 @@ module Admin
|
||||
end
|
||||
|
||||
test "reviewer can down vote henry" do
|
||||
auth_user users(:reviewer)
|
||||
auth_user users(:reviewer2)
|
||||
henry = candidates(:henry)
|
||||
|
||||
assert_difference("Candidate.find(#{henry.id}).votes.nay.count", 1) do
|
||||
@ -24,7 +44,7 @@ module Admin
|
||||
end
|
||||
|
||||
test "reviewer can change vote on henry" do
|
||||
auth_user users(:reviewer)
|
||||
auth_user users(:reviewer2)
|
||||
henry = candidates(:henry)
|
||||
get admin_up_vote_url(henry.test_hash)
|
||||
|
||||
@ -36,24 +56,30 @@ module Admin
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "manager can approve henry" do
|
||||
test "manager can approve henry and notify recruiter" do
|
||||
auth_user users(:manager)
|
||||
henry = candidates(:henry)
|
||||
get admin_approve_vote_url(henry.test_hash)
|
||||
|
||||
assert_equal 1, henry.votes.approved.count
|
||||
assert_enqueued_jobs 1 do
|
||||
post admin_interview_url(henry.test_hash), params: {
|
||||
review_status: 'approved',
|
||||
review_comments: 'ipsum'
|
||||
}
|
||||
end
|
||||
assert_equal 'approved', Candidate.find(henry.to_i).review_status
|
||||
assert_response :success
|
||||
assert_equal 'ipsum', Candidate.find(henry.to_i).review_comments
|
||||
assert_redirected_to admin_result_url(henry.test_hash)
|
||||
end
|
||||
|
||||
test "manager can decline henry" do
|
||||
test "approve fails without comment" do
|
||||
auth_user users(:manager)
|
||||
henry = candidates(:henry)
|
||||
get admin_decline_vote_url(henry.test_hash)
|
||||
|
||||
assert_equal 1, henry.votes.rejected.count
|
||||
assert_equal 'declined', Candidate.find(henry.to_i).review_status
|
||||
assert_response :success
|
||||
assert_enqueued_jobs 0 do
|
||||
post admin_interview_url(henry.test_hash), params: { review_status: 'approved' }
|
||||
end
|
||||
assert_match 'comment', flash[:error]
|
||||
assert_redirected_to admin_result_url(henry.test_hash)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -9,4 +9,11 @@ class AdminControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_redirected_to admin_login_path
|
||||
assert_match 'not authorized', flash[:error]
|
||||
end
|
||||
|
||||
test 'sort_column present' do
|
||||
# a stupid coverage report thing.
|
||||
admin_controller = AdminController.new
|
||||
|
||||
assert_equal :completed_at, admin_controller.send(:sort_column)
|
||||
end
|
||||
end
|
||||
|
194
test/fixtures/answers.yml
vendored
194
test/fixtures/answers.yml
vendored
@ -696,3 +696,197 @@ wade10:
|
||||
created_at: <%= DateTime.now() - 36.hours - 40.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 20.minutes %>
|
||||
|
||||
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
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 22.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 22.minutes %>
|
||||
|
||||
jorge2:
|
||||
candidate: jorge
|
||||
question: fed2
|
||||
answer: 'indexOf()'
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 24.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 4.minutes %>
|
||||
|
||||
jorge3:
|
||||
candidate: jorge
|
||||
question: fed3
|
||||
answer: {html: '<h1>Salmon</h1>', css: 'h1 {color: salmon;}', js: '', text: 'Gotta lotta GOOD things on sale, strangah.'}
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 26.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 6.minutes %>
|
||||
|
||||
jorge4:
|
||||
candidate: jorge
|
||||
question: fed4
|
||||
answer: Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 28.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 28.minutes %>
|
||||
|
||||
jorge5:
|
||||
candidate: jorge
|
||||
question: fed5
|
||||
answer: 'Dynamic listeners'
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 30.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 30.minutes %>
|
||||
|
||||
jorge6:
|
||||
candidate: jorge
|
||||
question: fed6
|
||||
answer: Integer posuere erat a ante venenatis dapibus posuere velit aliquet.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 32.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 12.minutes %>
|
||||
|
||||
jorge7:
|
||||
candidate: jorge
|
||||
question: fed7
|
||||
answer: {html: '<p>This means <strong>jQuery</strong> needs to be available in live-coder!</p>', css: "strong {font-size: 1.6em;}\n.green {color: green;}", js: '$("strong").addClass("green");'}
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 34.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 14.minutes %>
|
||||
|
||||
jorge8:
|
||||
candidate: jorge
|
||||
question: fed8
|
||||
answer:
|
||||
other: Some generic user input
|
||||
options:
|
||||
- other
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 36.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 16.minutes %>
|
||||
|
||||
jorge9:
|
||||
candidate: jorge
|
||||
question: fed9
|
||||
answer:
|
||||
other: Brunch
|
||||
options:
|
||||
- Neither
|
||||
- other
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 38.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 18.minutes %>
|
||||
|
||||
jorge10:
|
||||
candidate: jorge
|
||||
question: fed10
|
||||
answer: ["Live long and prosper", "Who you calling Scruffy?"]
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 40.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 20.minutes %>
|
||||
|
||||
elsie1:
|
||||
candidate: elsie
|
||||
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
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 22.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 22.minutes %>
|
||||
|
||||
elsie2:
|
||||
candidate: elsie
|
||||
question: fed2
|
||||
answer: 'indexOf()'
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 24.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 4.minutes %>
|
||||
|
||||
elsie3:
|
||||
candidate: elsie
|
||||
question: fed3
|
||||
answer: {html: '<h1>Salmon</h1>', css: 'h1 {color: salmon;}', js: '', text: 'Gotta lotta GOOD things on sale, strangah.'}
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 26.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 6.minutes %>
|
||||
|
||||
elsie4:
|
||||
candidate: elsie
|
||||
question: fed4
|
||||
answer: Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 28.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 28.minutes %>
|
||||
|
||||
elsie5:
|
||||
candidate: elsie
|
||||
question: fed5
|
||||
answer: 'Dynamic listeners'
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 30.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 30.minutes %>
|
||||
|
||||
elsie6:
|
||||
candidate: elsie
|
||||
question: fed6
|
||||
answer: Integer posuere erat a ante venenatis dapibus posuere velit aliquet.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 32.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 12.minutes %>
|
||||
|
||||
elsie7:
|
||||
candidate: elsie
|
||||
question: fed7
|
||||
answer: {html: '<p>This means <strong>jQuery</strong> needs to be available in live-coder!</p>', css: "strong {font-size: 1.6em;}\n.green {color: green;}", js: '$("strong").addClass("green");'}
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 34.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 14.minutes %>
|
||||
|
||||
elsie8:
|
||||
candidate: elsie
|
||||
question: fed8
|
||||
answer:
|
||||
other: Some generic user input
|
||||
options:
|
||||
- other
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 36.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 16.minutes %>
|
||||
|
||||
elsie9:
|
||||
candidate: elsie
|
||||
question: fed9
|
||||
answer:
|
||||
other: Brunch
|
||||
options:
|
||||
- Neither
|
||||
- other
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 38.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 18.minutes %>
|
||||
|
||||
elsie10:
|
||||
candidate: elsie
|
||||
question: fed10
|
||||
answer: ["Live long and prosper", "Who you calling Scruffy?"]
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 40.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 20.minutes %>
|
||||
|
||||
|
67
test/fixtures/candidates.yml
vendored
67
test/fixtures/candidates.yml
vendored
@ -4,6 +4,9 @@ roy: # Roy should have started, and is ready for a reminder
|
||||
name: Roy Cruz
|
||||
email: <%= CryptSerializer.dump 'roy.cruz@mailinator.com' %>
|
||||
experience: 0-3
|
||||
project: Client/Project
|
||||
position: 'full-time'
|
||||
skill_needs: 'css/html'
|
||||
recruiter: recruiter
|
||||
quiz: fed
|
||||
completed: false
|
||||
@ -14,6 +17,9 @@ gillian: # Gillian has not begun the test
|
||||
name: Gillian Anderson
|
||||
email: <%= CryptSerializer.dump 'gillian.anderson@mailinator.com' %>
|
||||
experience: 4-6
|
||||
project: Client/Project
|
||||
position: 'full-time'
|
||||
skill_needs: 'css/html'
|
||||
recruiter: recruiter
|
||||
quiz: fed
|
||||
completed: false
|
||||
@ -24,6 +30,9 @@ martha: # Martha has not begun the test
|
||||
name: Martha Watts
|
||||
email: <%= CryptSerializer.dump 'martha.watts@mailinator.com' %>
|
||||
experience: 4-6
|
||||
project: Client/Project
|
||||
position: 'contract'
|
||||
skill_needs: 'angular'
|
||||
recruiter: recruiter
|
||||
quiz: fed
|
||||
completed: false
|
||||
@ -34,6 +43,9 @@ dawn: # Dawn has completed, and been reminded, but not submitted the test
|
||||
name: Dawn Hopkins
|
||||
email: <%= CryptSerializer.dump 'dawn.hopkins@mailinator.com' %>
|
||||
experience: 0-2
|
||||
project: Client/Project
|
||||
position: 'contract'
|
||||
skill_needs: 'javascript'
|
||||
recruiter: recruiter
|
||||
quiz: fed
|
||||
completed: false
|
||||
@ -44,6 +56,9 @@ peggy: # Peggy has completed, and been reminded, but not submitted the test
|
||||
name: Peggy Blisters
|
||||
email: <%= CryptSerializer.dump 'peggy.blisters@mailinator.com' %>
|
||||
experience: 0-2
|
||||
project: Client/Project
|
||||
position: 'contract'
|
||||
skill_needs: 'css/html'
|
||||
recruiter: recruiter
|
||||
quiz: fed
|
||||
completed: false
|
||||
@ -54,17 +69,25 @@ richard: # Richard has completed AND submitted the test
|
||||
name: Richard Burns
|
||||
email: <%= CryptSerializer.dump 'richard.burns@mailinator.com' %>
|
||||
experience: 15+
|
||||
project: Client/Project
|
||||
position: 'full-time'
|
||||
skill_needs: 'css/html'
|
||||
recruiter: recruiter
|
||||
quiz: fed
|
||||
completed: true
|
||||
completed_at: <%= DateTime.current - 20.days %>
|
||||
reminded: false
|
||||
test_hash: 6NjnourLE6Y
|
||||
review_status: 1
|
||||
review_comments: "Some reasons why or why not, but here they are."
|
||||
|
||||
juan: # Juan has chosen "finish later" for live coders
|
||||
name: Juan Campbell
|
||||
email: <%= CryptSerializer.dump 'juan.campbell@mailinator.com' %>
|
||||
experience: 15+
|
||||
project: Client/Project
|
||||
position: 'full-time'
|
||||
skill_needs: 'css/html'
|
||||
recruiter: recruiter
|
||||
quiz: fed
|
||||
completed: false
|
||||
@ -75,20 +98,29 @@ stacy: # Stacy has completed AND submitted the test
|
||||
name: Stacy Scott
|
||||
email: <%= CryptSerializer.dump 'stacy.scott@mailinator.com' %>
|
||||
experience: 7-9
|
||||
project: Client/Project
|
||||
position: 'full-time'
|
||||
skill_needs: 'css/html'
|
||||
recruiter: recruiter
|
||||
quiz: fed
|
||||
completed: true
|
||||
completed_at: <%= DateTime.current - 13.hours %>
|
||||
reminded: false
|
||||
test_hash: s6oFExZliYYFx
|
||||
review_status: 2
|
||||
review_comments: "Some reasons why or why not, but here they are."
|
||||
|
||||
henry: # Henry has completed AND submitted the test
|
||||
name: Henry Butler
|
||||
email: <%= CryptSerializer.dump 'henry.butler@mailinator.com' %>
|
||||
experience: 4-6
|
||||
project: Client/Project
|
||||
position: 'full-time'
|
||||
skill_needs: 'css/html'
|
||||
recruiter: recruiter
|
||||
quiz: fed
|
||||
completed: true
|
||||
completed_at: <%= DateTime.current - 3.days %>
|
||||
reminded: false
|
||||
test_hash: egPomAuVDeCEp
|
||||
|
||||
@ -96,16 +128,51 @@ wade: # Wade has completed AND submitted the test
|
||||
name: Wade Armstrong
|
||||
email: <%= CryptSerializer.dump 'wade.armstrong@mailinator.com' %>
|
||||
experience: 0-3
|
||||
project: Client/Project
|
||||
position: 'full-time'
|
||||
skill_needs: 'css/html'
|
||||
recruiter: recruiter
|
||||
quiz: fed
|
||||
completed: true
|
||||
completed_at: <%= DateTime.current - 8.days %>
|
||||
reminded: false
|
||||
test_hash: BkSkpapJnkz2N
|
||||
|
||||
jorge: # Jorge has completed AND submitted the test
|
||||
name: Jorge Holmes
|
||||
email: <%= CryptSerializer.dump 'jorge.holmes@mailinator.com' %>
|
||||
experience: 0-3
|
||||
project: Client/Project
|
||||
position: 'full-time'
|
||||
skill_needs: 'css/html'
|
||||
recruiter: recruiter
|
||||
quiz: fed
|
||||
completed: true
|
||||
completed_at: <%= DateTime.current - 12.days + 3.hours %>
|
||||
reminded: false
|
||||
test_hash: iC5FdWJxcyySBmpOpU
|
||||
|
||||
elsie: # Elsie has completed AND submitted the test
|
||||
name: Elsie Lowe
|
||||
email: <%= CryptSerializer.dump 'elsie.lowe@mailinator.com' %>
|
||||
experience: 0-3
|
||||
project: Client/Project
|
||||
position: 'full-time'
|
||||
skill_needs: 'css/html'
|
||||
recruiter: recruiter
|
||||
quiz: fed
|
||||
completed: true
|
||||
completed_at: <%= DateTime.current - 45.days + 6.hours %>
|
||||
reminded: false
|
||||
test_hash: rLSoizA3ATMNSCx
|
||||
|
||||
gustov: # Gustov is NOT for FED
|
||||
name: Gustov
|
||||
email: <%= CryptSerializer.dump 'gustov@mailinator.com' %>
|
||||
experience: 0-3
|
||||
project: Client/Project
|
||||
position: 'full-time'
|
||||
skill_needs: 'C#, SQL'
|
||||
recruiter: recruiter
|
||||
quiz: admin
|
||||
completed: false
|
||||
|
87
test/fixtures/quiz_comments.yml
vendored
Normal file
87
test/fixtures/quiz_comments.yml
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
com1:
|
||||
test_hash: BkSkpapJnkz2N #wade
|
||||
user: reviewer
|
||||
message: Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Etiam porta sem malesuada magna mollis euismod. Aenean lacinia bibendum nulla sed consectetur. Maecenas faucibus mollis interdum.
|
||||
|
||||
com2:
|
||||
test_hash: BkSkpapJnkz2N #wade
|
||||
user: reviewer
|
||||
message: Donec sed odio dui. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Curabitur blandit tempus porttitor. Nullam quis risus eget urna mollis ornare vel eu leo. Nullam id dolor id nibh ultricies vehicula ut id elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
|
||||
com3:
|
||||
test_hash: BkSkpapJnkz2N #wade
|
||||
user: reviewer2
|
||||
message: Cras mattis consectetur purus sit amet fermentum. Donec sed odio dui. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
|
||||
|
||||
com4:
|
||||
test_hash: iC5FdWJxcyySBmpOpU #jorge
|
||||
user: manager
|
||||
message: Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed posuere consectetur est at lobortis.
|
||||
|
||||
com5:
|
||||
test_hash: egPomAuVDeCEp #henry
|
||||
user: manager
|
||||
message: no.
|
||||
|
||||
com6:
|
||||
test_hash: egPomAuVDeCEp #henry
|
||||
user: reviewer2
|
||||
message: fine.
|
||||
|
||||
com7:
|
||||
test_hash: iC5FdWJxcyySBmpOpU #jorge
|
||||
user: reviewer
|
||||
message: Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
|
||||
|
||||
com8:
|
||||
test_hash: egPomAuVDeCEp #henry
|
||||
user: manager
|
||||
message: no.
|
||||
|
||||
com9:
|
||||
test_hash: rLSoizA3ATMNSCx #elsie
|
||||
user: reviewer
|
||||
message: Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Donec ullamcorper nulla non metus auctor fringilla. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Etiam porta sem malesuada magna mollis euismod. Vestibulum id ligula porta felis euismod semper. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Maecenas faucibus mollis interdum.
|
||||
|
||||
com10:
|
||||
test_hash: rLSoizA3ATMNSCx #elsie
|
||||
user: reviewer2
|
||||
message: Ornare Tellus Nullam Mattis
|
||||
|
||||
com11:
|
||||
test_hash: rLSoizA3ATMNSCx #elsie
|
||||
user: reviewer2
|
||||
message: Nibh Ultricies Purus
|
||||
|
||||
com12:
|
||||
test_hash: rLSoizA3ATMNSCx #elsie
|
||||
user: reviewer
|
||||
message: Donec id elit non mi porta gravida at eget metus.
|
||||
|
||||
com13:
|
||||
test_hash: rLSoizA3ATMNSCx #elsie
|
||||
user: manager
|
||||
message: Donec id elit non mi porta gravida at eget metus.
|
||||
|
||||
com14:
|
||||
test_hash: rLSoizA3ATMNSCx #elsie
|
||||
user: reviewer2
|
||||
message: Ultricies Vulputate Bibendum Parturient
|
||||
|
||||
com15:
|
||||
test_hash: 6NjnourLE6Y #richard
|
||||
user: manager
|
||||
message: gibberish
|
||||
|
||||
com16:
|
||||
test_hash: egPomAuVDeCEp #henry
|
||||
user: admin
|
||||
message: word.
|
||||
|
||||
com17:
|
||||
test_hash: 6NjnourLE6Y #richard
|
||||
user: reviewer
|
||||
message: more gibberish
|
||||
|
27
test/fixtures/reviewer_votes.yml
vendored
27
test/fixtures/reviewer_votes.yml
vendored
@ -66,3 +66,30 @@ reviewer2_wade:
|
||||
candidate: wade
|
||||
user: reviewer2
|
||||
|
||||
|
||||
manager_jorge:
|
||||
candidate: jorge
|
||||
user: manager
|
||||
|
||||
reviewer_jorge:
|
||||
candidate: jorge
|
||||
user: reviewer
|
||||
vote: 1
|
||||
|
||||
reviewer2_jorge:
|
||||
candidate: jorge
|
||||
user: reviewer2
|
||||
|
||||
|
||||
manager_elsie:
|
||||
candidate: elsie
|
||||
user: manager
|
||||
|
||||
reviewer_elsie:
|
||||
candidate: elsie
|
||||
user: reviewer
|
||||
|
||||
reviewer2_elsie:
|
||||
candidate: elsie
|
||||
user: reviewer2
|
||||
|
||||
|
@ -8,4 +8,8 @@ class RecruiterMailerPreview < ActionMailer::Preview
|
||||
def candidate_submitted
|
||||
RecruiterMailer.candidate_submitted Candidate.find_by(test_hash: 'OvP0ZqGKwJ0') # Dawn
|
||||
end
|
||||
|
||||
def candidate_reviewed
|
||||
RecruiterMailer.candidate_reviewed Candidate.find_by(test_hash: 's6oFExZliYYFx') # Stacy
|
||||
end
|
||||
end
|
||||
|
@ -4,4 +4,18 @@ class ReviewerMailerPreview < ActionMailer::Preview
|
||||
def candidate_submission
|
||||
ReviewerMailer.candidate_submission Candidate.find_by(test_hash: 'OvP0ZqGKwJ0') # Dawn
|
||||
end
|
||||
|
||||
def reminder
|
||||
reminders = ReviewerReminder.new
|
||||
reminder = reminders.reminders.first
|
||||
ReviewerMailer.reminder reminder
|
||||
end
|
||||
|
||||
def notify_manager
|
||||
ReviewerMailer.notify_manager Candidate.find_by(test_hash: 'OvP0ZqGKwJ0').id # Dawn
|
||||
end
|
||||
|
||||
def new_comment
|
||||
ReviewerMailer.new_comment QuizComment.first
|
||||
end
|
||||
end
|
||||
|
@ -3,6 +3,7 @@
|
||||
class UserMailerPreview < ActionMailer::Preview
|
||||
def password_reset
|
||||
user = User.find_by(email: 'alan.admin@mailinator.com')
|
||||
user.send(:gen_reset_token)
|
||||
UserMailer.password_reset user
|
||||
end
|
||||
|
||||
|
@ -12,11 +12,24 @@ class RecruiterMailerTest < ActionMailer::TestCase
|
||||
end
|
||||
|
||||
test "candidate_submitted" do
|
||||
candidate = candidates :dawn
|
||||
candidate = candidates :elsie
|
||||
manager = users :manager
|
||||
mail = RecruiterMailer.candidate_submitted candidate
|
||||
assert_match candidate.name, mail.subject
|
||||
assert_equal [candidate.recruiter.email], mail.to
|
||||
assert_equal [ENV["default_mail_from"]], mail.from
|
||||
assert_match candidate.name, mail.body.encoded
|
||||
assert_match manager.name, mail.body.encoded
|
||||
end
|
||||
|
||||
test "candidate_reviewed" do
|
||||
candidate = candidates :stacy
|
||||
mail = RecruiterMailer.candidate_reviewed candidate
|
||||
assert_match candidate.name, mail.subject
|
||||
assert_equal [candidate.recruiter.email], mail.to
|
||||
assert_equal [ENV["default_mail_from"]], mail.from
|
||||
assert_match candidate.review_status, mail.body.encoded
|
||||
assert_match candidate.name, mail.body.encoded
|
||||
assert_match candidate.review_comments, mail.body.encoded
|
||||
end
|
||||
end
|
||||
|
@ -11,4 +11,33 @@ class ReviewerMailerTest < ActionMailer::TestCase
|
||||
assert_equal [ENV["default_mail_from"]], mail.from
|
||||
assert_match candidate.test_hash, mail.body.encoded
|
||||
end
|
||||
|
||||
test "reminder" do
|
||||
reminders = ReviewerReminder.new
|
||||
reminder = reminders.reminders.first
|
||||
mail = ReviewerMailer.reminder reminder
|
||||
assert_match "Review Reminder", mail.subject
|
||||
assert_equal [reminder.email], mail.to
|
||||
assert_equal [ENV["default_mail_from"]], mail.from
|
||||
assert_match reminder.test_hash, mail.body.encoded
|
||||
end
|
||||
|
||||
test "notify_manager" do
|
||||
candidate = candidates(:richard)
|
||||
mail = ReviewerMailer.notify_manager candidate.id
|
||||
assert_match "Voting Complete", mail.subject
|
||||
assert_equal [candidate.manager.email], mail.to
|
||||
assert_equal [ENV["default_mail_from"]], mail.from
|
||||
assert_match candidate.test_hash, mail.body.encoded
|
||||
end
|
||||
|
||||
test "comment notification" do
|
||||
comment = quiz_comments(:com5)
|
||||
mail = ReviewerMailer.new_comment comment
|
||||
assert_match "Comment", mail.subject
|
||||
assert_match comment.test_hash, mail.subject
|
||||
assert_equal comment.candidate.reviewers.map(&:email), mail.to
|
||||
assert_equal [ENV["default_mail_from"]], mail.from
|
||||
assert_match comment.test_hash, mail.body.encoded
|
||||
end
|
||||
end
|
||||
|
@ -6,6 +6,7 @@ class CandidateTest < ActiveSupport::TestCase
|
||||
candidate = Candidate.create(name: 'new name',
|
||||
email: 'test@mailinator.com',
|
||||
experience: '0-3',
|
||||
project: 'Client',
|
||||
quiz_id: quizzes(:fed).id)
|
||||
|
||||
assert candidate.test_hash.present?
|
||||
@ -16,6 +17,8 @@ class CandidateTest < ActiveSupport::TestCase
|
||||
candidate = Candidate.create(name: 'new name',
|
||||
email: email,
|
||||
experience: '0-3',
|
||||
project: 'Client',
|
||||
position: 'full-time',
|
||||
recruiter_id: users(:recruiter).id,
|
||||
quiz_id: quizzes(:fed).id)
|
||||
|
||||
|
27
test/models/quiz_comment_test.rb
Normal file
27
test/models/quiz_comment_test.rb
Normal file
@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
require 'test_helper'
|
||||
|
||||
class QuizCommentTest < ActiveSupport::TestCase
|
||||
test "the truth" do
|
||||
assert QuizComment
|
||||
end
|
||||
|
||||
test "user to comments association" do
|
||||
manager = users(:manager)
|
||||
|
||||
assert_equal 5, manager.quiz_comments.size
|
||||
end
|
||||
|
||||
test "candidate to comments association" do
|
||||
candidate = candidates(:elsie)
|
||||
|
||||
assert_equal 6, candidate.quiz_comments.size
|
||||
end
|
||||
|
||||
test 'comment to user' do
|
||||
comment = quiz_comments(:com1)
|
||||
|
||||
assert_match 'Wade', comment.candidate.name
|
||||
assert_match 'Tina', comment.user.name
|
||||
end
|
||||
end
|
@ -3,7 +3,7 @@ require 'test_helper'
|
||||
|
||||
class ReviewerVoteTest < ActiveSupport::TestCase
|
||||
test "the truth" do
|
||||
assert ReviewerVoteTest
|
||||
assert ReviewerVote
|
||||
end
|
||||
|
||||
test "richard has 3 votes" do
|
||||
@ -12,10 +12,11 @@ class ReviewerVoteTest < ActiveSupport::TestCase
|
||||
assert_equal 3, richard.votes.size
|
||||
end
|
||||
|
||||
test "manager has 4 votes" do
|
||||
test "manager has a vote for every completed quiz" do
|
||||
manager = users(:manager)
|
||||
completed_count = Candidate.where(completed: true).count
|
||||
|
||||
assert_equal 4, manager.votes.size
|
||||
assert_equal completed_count, manager.votes.size
|
||||
end
|
||||
|
||||
test "richard has been approved" do
|
||||
@ -31,4 +32,22 @@ class ReviewerVoteTest < ActiveSupport::TestCase
|
||||
assert stacy.declined?
|
||||
refute stacy.approved?
|
||||
end
|
||||
|
||||
test "mailer is queued on last vote" do
|
||||
reviewer = users(:reviewer2)
|
||||
candidate = candidates(:jorge)
|
||||
|
||||
assert_difference("ActionMailer::Base.deliveries.size", 1) do
|
||||
reviewer.cast_yea_on(candidate)
|
||||
end
|
||||
end
|
||||
|
||||
test "mailer is NOT queued on first vote" do
|
||||
reviewer = users(:reviewer2)
|
||||
candidate = candidates(:elsie)
|
||||
|
||||
assert_difference("ActionMailer::Base.deliveries.size", 0) do
|
||||
reviewer.cast_yea_on(candidate)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
30
test/policies/quiz_comment_policy_test.rb
Normal file
30
test/policies/quiz_comment_policy_test.rb
Normal file
@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
require 'test_helper'
|
||||
|
||||
class QuizCommentPolicyTest < PolicyAssertions::Test
|
||||
test 'should require current_user' do
|
||||
assert_raise Pundit::NotAuthorizedError do
|
||||
QuizCommentPolicy.new(nil, User.first).create?
|
||||
end
|
||||
end
|
||||
|
||||
def test_create
|
||||
candidate = candidates(:stacy)
|
||||
comment = QuizComment.new(test_hash: candidate.test_hash)
|
||||
|
||||
assert_permit users(:manager), comment
|
||||
assert_permit users(:reviewer), comment
|
||||
assert_permit users(:admin), comment
|
||||
|
||||
refute_permit users(:recruiter), comment
|
||||
end
|
||||
|
||||
def test_update
|
||||
assert_permit users(:reviewer2), quiz_comments(:com6)
|
||||
|
||||
refute_permit users(:reviewer), quiz_comments(:com6)
|
||||
refute_permit users(:manager), quiz_comments(:com6)
|
||||
refute_permit users(:admin), quiz_comments(:com6)
|
||||
refute_permit users(:recruiter), quiz_comments(:com6)
|
||||
end
|
||||
end
|
@ -31,8 +31,8 @@ class ReviewerVotePolicyTest < PolicyAssertions::Test
|
||||
def test_up
|
||||
assert_permit users(:manager), reviewer_votes(:manager_richard)
|
||||
assert_permit users(:reviewer), reviewer_votes(:reviewer_richard)
|
||||
assert_permit users(:admin), reviewer_votes(:manager_henry)
|
||||
|
||||
refute_permit users(:admin), reviewer_votes(:manager_henry)
|
||||
refute_permit users(:recruiter), reviewer_votes(:manager_henry)
|
||||
refute_permit users(:reviewer), reviewer_votes(:gustov)
|
||||
refute_permit users(:manager), reviewer_votes(:gustov)
|
||||
@ -41,22 +41,14 @@ class ReviewerVotePolicyTest < PolicyAssertions::Test
|
||||
def test_down
|
||||
assert_permit users(:manager), reviewer_votes(:manager_richard)
|
||||
assert_permit users(:reviewer), reviewer_votes(:reviewer_richard)
|
||||
assert_permit users(:admin), reviewer_votes(:manager_henry)
|
||||
|
||||
refute_permit users(:admin), reviewer_votes(:manager_henry)
|
||||
refute_permit users(:recruiter), reviewer_votes(:manager_henry)
|
||||
refute_permit users(:reviewer), reviewer_votes(:gustov)
|
||||
refute_permit users(:manager), reviewer_votes(:gustov)
|
||||
end
|
||||
|
||||
def approve
|
||||
assert_permit users(:manager), reviewer_votes(:manager_richard)
|
||||
assert_permit users(:admin), reviewer_votes(:manager_henry)
|
||||
|
||||
refute_permit users(:recruiter), reviewer_votes(:manager_henry)
|
||||
refute_permit users(:reviewer), reviewer_votes(:reviewer_richard)
|
||||
end
|
||||
|
||||
def decline
|
||||
def interview_request
|
||||
assert_permit users(:manager), reviewer_votes(:manager_richard)
|
||||
assert_permit users(:admin), reviewer_votes(:manager_henry)
|
||||
|
||||
|
@ -8,6 +8,8 @@ SimpleCov.start 'rails' do
|
||||
add_group 'Services & Workers', %w(app/workers app/services)
|
||||
add_group "Jobs", 'app/jobs'
|
||||
add_group "Policies", 'app/policies'
|
||||
|
||||
add_filter "/lib/utils/" # no need to test dev only utility classes
|
||||
end
|
||||
|
||||
require File.expand_path('../../config/environment', __FILE__)
|
||||
@ -19,6 +21,7 @@ Dir[Rails.root.join("test/test_helpers/**/*.rb")].each { |f| require f }
|
||||
Minitest::Reporters.use! [Minitest::Reporters::DefaultReporter.new(color: true)]
|
||||
|
||||
class ActiveSupport::TestCase
|
||||
ActiveRecord::Migration.maintain_test_schema!
|
||||
ActiveRecord::Migration.check_pending!
|
||||
|
||||
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
|
||||
|
@ -1,14 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
require 'test_helper'
|
||||
|
||||
class ReminderTest < ActiveSupport::TestCase
|
||||
class CandidateReminderTest < ActiveSupport::TestCase
|
||||
test "collection is created with one result" do
|
||||
reminders = Reminder.new
|
||||
reminders = CandidateReminder.new
|
||||
assert_equal 1, reminders.size
|
||||
end
|
||||
|
||||
test "each candidate has needed attributes" do
|
||||
reminders = Reminder.new
|
||||
reminders = CandidateReminder.new
|
||||
|
||||
assert_instance_of String, reminders.candidates.first.name
|
||||
assert_instance_of String, reminders.candidates.first.test_hash
|
||||
@ -16,7 +16,7 @@ class ReminderTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
test "send reminders sends email, and flags reminded" do
|
||||
reminders = Reminder.new
|
||||
reminders = CandidateReminder.new
|
||||
pre_reminded = Candidate.find(reminders.candidates.first.id).reminded
|
||||
|
||||
assert_difference("ActionMailer::Base.deliveries.size", reminders.count) do
|
@ -97,7 +97,7 @@ class QuizStatusTest < ActiveSupport::TestCase
|
||||
dawn = candidates :dawn
|
||||
status = QuizStatus.new dawn
|
||||
|
||||
assert_equal nil, status.current_question_id
|
||||
assert_nil status.current_question_id
|
||||
end
|
||||
|
||||
test "richard has no_finish_laters" do
|
||||
|
25
test/workers/reviewer_reminder_test.rb
Normal file
25
test/workers/reviewer_reminder_test.rb
Normal file
@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
require 'test_helper'
|
||||
|
||||
class ReviewerReminderTest < ActiveSupport::TestCase
|
||||
test "collection is created with results" do
|
||||
reminders = ReviewerReminder.new
|
||||
assert_equal 6, reminders.size
|
||||
end
|
||||
|
||||
test "each reminder has needed attributes" do
|
||||
collection = ReviewerReminder.new
|
||||
|
||||
assert_instance_of String, collection.reminders.first.name
|
||||
assert_instance_of String, collection.reminders.first.email
|
||||
assert_instance_of String, collection.reminders.first.test_hash
|
||||
end
|
||||
|
||||
test "send_all sends emails for each reviewer and test" do
|
||||
collection = ReviewerReminder.new
|
||||
|
||||
assert_difference("ActionMailer::Base.deliveries.size", collection.count) do
|
||||
collection.send_all
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user