Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[298] Evaluations routes & controller #336

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions app/controllers/evaluations_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,164 @@
# frozen_string_literal: true

# Controller for evaluations CRUD actions.
# TODO: Needs to be simplified and made shorter for Rubocop
class EvaluationsController < ApplicationController
before_action -> { authorize_user('evaluator') }

def index; end

def show; end

# TODO: This should also prevent any creation if their status is recused?
def new
@evaluator_submission_assignment = find_evaluator_submission_assignment

# TODO: Fine to return a not found to prevent gathering info from alert messages?
if @evaluator_submission_assignment.nil? || !can_access_evaluation?
return redirect_to evaluations_path, alert: I18n.t("evaluations.alerts.evaluator_submission_assignment_not_found")
end

@evaluation_form = find_evaluation_form

if @evaluation_form.nil?
return redirect_to evaluations_path, alert: I18n.t("evaluations.alerts.evaluation_form_not_found")
end

build_evaluation

render :new
end

def edit
@evaluation = Evaluation.find(params[:id])
return unauthorized_redirect unless can_access_evaluation?

render :edit
end

def save_draft
@evaluation = find_or_initialize_evaluation
@evaluation.assign_attributes(evaluation_params)
@evaluation.completed_at = nil

@evaluator_submission_assignment = @evaluation.evaluator_submission_assignment

return unauthorized_redirect unless can_access_evaluation?

begin
@evaluation.save(validate: false)
handle_save_draft_success
rescue ActiveRecord::RecordInvalid, ActiveRecord::NotNullViolation
handle_save_draft_failure
end
end

def mark_complete
@evaluation = find_or_initialize_evaluation
@evaluation.assign_attributes(evaluation_params)
@evaluation.completed_at = Time.current

@evaluator_submission_assignment = @evaluation.evaluator_submission_assignment

# Check if the current user can access the evaluation
return unauthorized_redirect unless can_access_evaluation?

# TODO: Set total_score here when the evaluation is marked complete

if @evaluation.update(evaluation_params)
handle_mark_complete_success
else
handle_mark_complete_failure
end
end

private

def unauthorized_redirect
redirect_to evaluations_path, alert: I18n.t("evaluations.alerts.unauthorized")
end

def handle_save_draft_success
flash[:notice] = I18n.t("evaluations.notices.saved_draft")
redirect_to evaluations_path
end

def handle_save_draft_failure
flash.now[:alert] =
I18n.t("evaluations.alerts.save_draft_error", errors: @evaluation.errors.full_messages.to_sentence)

if @evaluation.new_record?
render :new, status: :unprocessable_entity
else
render :edit, status: :unprocessable_entity
end
end

def handle_mark_complete_success
flash[:notice] = I18n.t("evaluations.notices.marked_complete")
redirect_to evaluations_path
end

def handle_mark_complete_failure
flash.now[:alert] =
I18n.t("evaluations.alerts.mark_complete_error", errors: @evaluation.errors.full_messages.to_sentence)

if @evaluation.new_record?
render :new, status: :unprocessable_entity
else
render :edit, status: :unprocessable_entity
end
end

def find_or_initialize_evaluation
if params[:id]
Evaluation.find(params[:id])
else
Evaluation.new
end
end

def find_evaluator_submission_assignment
EvaluatorSubmissionAssignment.find_by(id: params[:evaluator_submission_assignment_id])
end

def can_access_evaluation?
(@evaluator_submission_assignment && @evaluator_submission_assignment.user_id == current_user.id) ||
(@evaluation && @evaluation.user_id == current_user.id)
end

def find_evaluation_form
@phase = @evaluator_submission_assignment.phase
EvaluationForm.find_by(phase: @phase)
end

def build_evaluation
@submission = @evaluator_submission_assignment.submission
@evaluation = Evaluation.new(
user: current_user,
evaluation_form: @evaluation_form,
submission: @submission,
evaluator_submission_assignment: @evaluator_submission_assignment
)

@evaluation_form.evaluation_criteria.each do |criterion|
@evaluation.evaluation_scores.build(evaluation_criterion: criterion)
end
end

def evaluation_params
params.require(:evaluation).permit(
:user_id,
:evaluator_submission_assignment_id,
:submission_id,
:evaluation_form_id,
:additional_comments,
:revision_comments,
evaluation_scores_attributes: %i[
evaluation_criterion_id
score score_override
comment comment_override
]
)
end
end
1 change: 1 addition & 0 deletions app/models/evaluation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Evaluation < ApplicationRecord
belongs_to :submission
belongs_to :evaluator_submission_assignment
has_many :evaluation_scores, dependent: :destroy
accepts_nested_attributes_for :evaluation_scores

validates :user_id,
uniqueness: { scope: [:evaluation_form_id, :submission_id],
Expand Down
3 changes: 2 additions & 1 deletion app/models/evaluation_score.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ class EvaluationScore < ApplicationRecord
message: I18n.t("evaluation_scores.unique_evaluation_for_evaluation_criterion_error")
}

validates :score, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true
validates :score, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, allow_nil: true
validates :score, presence: true, if: -> { evaluation.completed_at.present? }
validates :score_override, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, allow_nil: true
validates :comment, presence: true, if: -> { evaluation.evaluation_form.comments_required? }
validates :comment, length: { maximum: 3000 }, allow_nil: true
Expand Down
7 changes: 7 additions & 0 deletions app/views/evaluations/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<%= form_with(model: @evaluation, method: form_method, data: { controller: "evaluation modal" }) do |f| %>
<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
<p>Placeholder form in app/views/evaluations/_form.html.erb</p>

<%= f.submit "Save Draft", formaction: draft_action %>
<%= f.submit "Mark Complete", formaction: complete_action %>
<% end %>
9 changes: 9 additions & 0 deletions app/views/evaluations/edit.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div class="usa-card__container col-md-6">
<div class="usa-card__body">
<h1>Evaluations Edit</h1>

<p>Find me in app/views/evaluations/edit.html.erb</p>

<%= render "form", form_method: :patch, draft_action: save_draft_evaluation_path, complete_action: mark_complete_evaluation_path %>
</div>
</div>
9 changes: 9 additions & 0 deletions app/views/evaluations/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div class="usa-card__container col-md-6">
<div class="usa-card__body">
<h1>Evaluations New</h1>

<p>Find me in app/views/evaluations/new.html.erb</p>

<%= render "form", form_method: :post, draft_action: save_draft_evaluations_path, complete_action: mark_complete_evaluations_path %>
</div>
</div>
8 changes: 8 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ en:
evaluations:
unique_user_for_evaluation_form_and_submission_error: "already has an evaluation for this form and submission"
unique_evaluator_submission_assignment: "already has an evaluation"
alerts:
evaluator_submission_assignment_not_found: "No assignment found for this submission"
evaluation_form_not_found: "No evaluation form found for this submission"
save_draft_error: "Failed to save evaluation as draft: #{errors}."
mark_complete_error: "Failed to mark evaluation as complete: %{errors}."
notices:
saved_draft: "Evaluation saved as draft"
marked_complete: "Evaluation complete"
evaluation_scores:
unique_evaluation_for_evaluation_criterion_error: "already has a score for this evaluation criterion"
alerts:
Expand Down
16 changes: 15 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,21 @@

get '/dashboard', to: "dashboard#index"

resources :evaluations, only: [:index]
resources :evaluations, only: [:index, :show, :edit] do
member do
patch 'save_draft'
patch 'mark_complete'
end
collection do
post 'save_draft'
post 'mark_complete'
end
end

resources :evaluator_submission_assignments, only: [] do
resources :evaluations, only: [:new]
end

resources :evaluation_forms do
member do
get 'confirmation'
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20241223190634_allow_evaluation_score_null.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AllowEvaluationScoreNull < ActiveRecord::Migration[7.2]
def change
change_column_null :evaluation_scores, :score, true
end
end
3 changes: 2 additions & 1 deletion db/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ CREATE TABLE public.evaluation_scores (
id bigint NOT NULL,
evaluation_id bigint NOT NULL,
evaluation_criterion_id bigint NOT NULL,
score integer NOT NULL,
score integer,
score_override integer,
comment text,
comment_override text,
Expand Down Expand Up @@ -2465,6 +2465,7 @@ ALTER TABLE ONLY public.winners
SET search_path TO "$user", public;

INSERT INTO "schema_migrations" (version) VALUES
(20241223190634),
(20241217164258),
(20241125060011),
(20241120024946),
Expand Down
24 changes: 14 additions & 10 deletions spec/factories/evaluation_score.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,20 @@

# Evaluator is a FactoryBot param containing attributes from the factory record
after(:build) do |evaluation_score, _evaluator|
criterion = evaluation_score.evaluation_criterion

case criterion.scoring_type
when "numeric"
evaluation_score.score = rand(0..criterion.points_or_weight)
when "rating", "binary"
evaluation_score.score = rand(criterion.option_range_start..criterion.option_range_end)
else
raise ArgumentError, "Invalid scoring type '#{criterion.scoring_type}' for evaluation criterion"
end
valid_score_for_criterion(evaluation_score)
end
end
end

def valid_score_for_criterion(evaluation_score)
criterion = evaluation_score.evaluation_criterion

case criterion.scoring_type
when "numeric"
evaluation_score.score = rand(0..criterion.points_or_weight)
when "rating", "binary"
evaluation_score.score = rand(criterion.option_range_start..criterion.option_range_end)
else
raise ArgumentError, "Invalid scoring type '#{criterion.scoring_type}' for evaluation criterion"
end
end
Loading
Loading