-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch '237/eval-form-phase-uniqueness' of github.com:GSA/Chall…
…enge_platform into 237/eval-form-phase-uniqueness * '237/eval-form-phase-uniqueness' of github.com:GSA/Challenge_platform: (56 commits) 237 Add unique phase constraint on eval forms Bump rubocop from 1.68.0 to 1.69.1 Bump selenium-webdriver from 4.26.0 to 4.27.0 [304] Collapsible Column Layout (#310) [275-FIX] Fix for max input values for scale types (#288) Update app/views/evaluator_submission_assignments/_unassigned_submission_row.html.erb Update .codeclimate.yml rubocop name Disable rubocop check on flash before render quick syntax fix 179 | Adjust sorting scope and evaluation status quick syntax fix update status colors 179 | Adjust sorting scope and evaluation status Rename stat summary for evaluation submission assignments 179 | Add tests, update statuses, and colors 179 | Update flash, closing date, and error status 179 | Update tests for display scores 179 | Remove unused argument in display_score 179 | Update tests wip 179 | Update ordered by status query ...
- Loading branch information
Showing
33 changed files
with
916 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
115 changes: 115 additions & 0 deletions
115
app/controllers/evaluator_submission_assignments_controller.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
# frozen_string_literal: true | ||
|
||
# Controller for evaluator submissions assignments index and update status | ||
class EvaluatorSubmissionAssignmentsController < ApplicationController | ||
before_action -> { authorize_user('challenge_manager') } | ||
before_action :set_challenge_and_phase | ||
before_action :set_evaluator, only: [:index] | ||
before_action :set_assignment, only: [:update] | ||
|
||
def index | ||
@evaluator_assignments = @phase.evaluator_submission_assignments.includes(:submission).where(user_id: @evaluator.id) | ||
@assigned_submissions = @evaluator_assignments. | ||
where(status: %i[assigned recused]). | ||
includes(:evaluation). | ||
ordered_by_status | ||
@unassigned_submissions = @evaluator_assignments. | ||
where(status: %i[unassigned recused_unassigned]). | ||
ordered_by_status | ||
@submissions_count = calculate_submissions_count(@assigned_submissions) | ||
end | ||
|
||
# update only the status of the evaluation submission assignment to unassign or reassign an evaluator | ||
def update | ||
new_status = status_from_params | ||
|
||
unless valid_status?(new_status) | ||
return render_invalid_status_error | ||
end | ||
|
||
if update_assignment_status(new_status) | ||
handle_successful_update(new_status) | ||
else | ||
handle_failed_update(new_status) | ||
end | ||
end | ||
|
||
private | ||
|
||
def set_challenge_and_phase | ||
@phase = Phase.where(challenge: current_user.challenge_manager_challenges).find(params[:phase_id]) | ||
@challenge = @phase.challenge | ||
end | ||
|
||
def set_evaluator | ||
@evaluator = @phase.evaluators.find(params[:evaluator_id]) | ||
end | ||
|
||
def set_assignment | ||
@assignment = @phase.evaluator_submission_assignments.find(params[:id]) | ||
end | ||
|
||
def status_from_params | ||
status = params[:status] || params.dig(:evaluator_submission_assignment, :status) | ||
status&.to_sym | ||
end | ||
|
||
def valid_status?(status) | ||
EvaluatorSubmissionAssignment.statuses.keys.map(&:to_sym).include?(status) | ||
end | ||
|
||
def render_invalid_status_error | ||
render json: { success: false, message: 'Invalid status' }, status: :unprocessable_entity | ||
end | ||
|
||
def update_assignment_status(new_status) | ||
@assignment.update(status: new_status) | ||
end | ||
|
||
def handle_successful_update(new_status) | ||
flash[:success] = t("evaluator_submission_assignments.#{new_status}.success") | ||
respond_to do |format| | ||
format.html { redirect_to_assignment_path } | ||
format.json { render json: { success: true, message: flash[:success] } } | ||
end | ||
end | ||
|
||
def handle_failed_update(new_status) | ||
flash[:error] = t("evaluator_submission_assignments.#{new_status}.failure") | ||
respond_to do |format| | ||
format.html { redirect_to_assignment_path } | ||
format.json { render json: { success: false, message: flash[:error] }, status: :unprocessable_entity } | ||
end | ||
end | ||
|
||
def redirect_to_assignment_path | ||
redirect_to phase_evaluator_submission_assignments_path( | ||
@phase, | ||
evaluator_id: params[:evaluator_id] | ||
) | ||
end | ||
|
||
def calculate_submissions_count(assignments) | ||
counts = count_by_status(assignments) | ||
counts.merge("total" => calculate_total(counts)) | ||
end | ||
|
||
def count_by_status(assignments) | ||
{ | ||
"completed" => count_completed(assignments), | ||
"in_progress" => count_in_progress(assignments), | ||
"not_started" => count_not_started(assignments), | ||
"recused" => count_recused(assignments) | ||
} | ||
end | ||
|
||
def count_completed(assignments) = assignments.count { |a| a.evaluation&.completed_at.present? } | ||
|
||
def count_in_progress(assignments) = assignments.count { |a| a.evaluation.present? && a.evaluation.completed_at.nil? } | ||
|
||
def count_not_started(assignments) = assignments.count { |a| a.assigned? && a.evaluation.nil? } | ||
|
||
def count_recused(assignments) = assignments.count(&:recused?) | ||
|
||
def calculate_total(counts) = counts.values.sum | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,54 +1,85 @@ | ||
import { Controller } from "@hotwired/stimulus" | ||
import { Controller } from "@hotwired/stimulus"; | ||
|
||
// Connects to data-controller="evaluation-form" | ||
export default class extends Controller { | ||
static targets = ["challengeID", "phaseID", "startDate", "datePicker"]; | ||
|
||
handleChallengeSelect(e) { | ||
let id, phase_id, end_date | ||
[id, phase_id, end_date] = e.target.value.split(".") | ||
let id, phase_id, end_date; | ||
[id, phase_id, end_date] = e.target.value.split("."); | ||
if (id) { | ||
// set values of hidden form fields | ||
this.challengeIDTarget.value = id | ||
this.phaseIDTarget.value = phase_id | ||
// set values of hidden form fields | ||
this.challengeIDTarget.value = id; | ||
this.phaseIDTarget.value = phase_id; | ||
|
||
// set the start date of the evaluation form | ||
// set the start date of the evaluation form | ||
// to be the challenge's end date | ||
this.startDateTarget.innerHTML = end_date || "mm/dd/yyyy" | ||
let day, month, year | ||
[month, day, year] = end_date.split("/") | ||
this.datePickerTarget.setAttribute("data-min-date", `${year}-${month}-${day}`) | ||
this.startDateTarget.innerHTML = end_date || "mm/dd/yyyy"; | ||
let day, month, year; | ||
[month, day, year] = end_date.split("/"); | ||
this.datePickerTarget.setAttribute( | ||
"data-min-date", | ||
`${year}-${month}-${day}` | ||
); | ||
|
||
this.updateErrorMessage("evaluation_form_challenge_id", "") | ||
this.updateErrorMessage("evaluation_form_phase_id", "") | ||
this.updateErrorMessage("evaluation_form_challenge_id", ""); | ||
this.updateErrorMessage("evaluation_form_phase_id", ""); | ||
} else { | ||
this.updateErrorMessage("evaluation_form_challenge_id", "can't be blank") | ||
this.startDateTarget.innerHTML = "mm/dd/yyyy" | ||
this.updateErrorMessage("evaluation_form_challenge_id", "can't be blank"); | ||
this.startDateTarget.innerHTML = "mm/dd/yyyy"; | ||
} | ||
} | ||
|
||
// Opens all accordions, remove existing points/weights, update max points/weights values | ||
updateMaxPoints(e) { | ||
const form = e.target.closest('form[data-controller="evaluation-form"]'); | ||
const pointsWeights = form.querySelectorAll(".points-or-weight"); | ||
if (e.target.id == 'weighted_scale') { | ||
pointsWeights.forEach((input) => input.max = "100") | ||
} else { | ||
pointsWeights.forEach((input) => input.max = "9999") | ||
const weightedScale = e.target.value === "true"; | ||
|
||
if (weightedScale && this.hasValuesOverLimit(pointsWeights, 100)) { | ||
this.expandAllAccordions(form); | ||
} | ||
|
||
this.updateMaxValues(pointsWeights, weightedScale ? 100 : 9999); | ||
} | ||
|
||
// Helper: Check if any input values exceed a given limit | ||
hasValuesOverLimit(inputs, limit) { | ||
return Array.from(inputs).some( | ||
(input) => parseInt(input.value.trim()) > limit | ||
); | ||
} | ||
|
||
// Helper: Update max values for inputs | ||
updateMaxValues(inputs, maxValue) { | ||
inputs.forEach((input) => (input.max = maxValue)); | ||
Array.from(inputs).every((input) => { | ||
input.reportValidity(); | ||
}); | ||
} | ||
|
||
// Helper: Expand all accordions | ||
expandAllAccordions(form) { | ||
const accordionButtons = form.querySelectorAll(".usa-accordion__button"); | ||
const accordions = form.querySelectorAll(".usa-accordion__content"); | ||
|
||
accordionButtons.forEach((button) => | ||
button.setAttribute("aria-expanded", true) | ||
); | ||
accordions.forEach((content) => content.removeAttribute("hidden")); | ||
} | ||
|
||
validatePresence(e) { | ||
if (!e.target.value) { | ||
e.target.classList.add("border-secondary") | ||
this.updateErrorMessage(e.target.id, "can't be blank") | ||
|
||
e.target.classList.add("border-secondary"); | ||
this.updateErrorMessage(e.target.id, "can't be blank"); | ||
} else { | ||
e.target.classList.remove("border-secondary") | ||
this.updateErrorMessage(e.target.id, "") | ||
e.target.classList.remove("border-secondary"); | ||
this.updateErrorMessage(e.target.id, ""); | ||
} | ||
} | ||
|
||
updateErrorMessage(field, message) { | ||
document.getElementById(field + "_error").innerHTML = message | ||
document.getElementById(field + "_error").innerHTML = message; | ||
} | ||
} |
Oops, something went wrong.