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

[291 & 292] Evaluation Status Details #321

Merged
merged 49 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
8e1edf2
291 | Add frontend for evaluators, score, & judging status checkboxes
emmabjj Dec 13, 2024
6e6c3b9
291 | Add average score
emmabjj Dec 13, 2024
cc8c9a8
291 | Add evaluators and display status
emmabjj Dec 13, 2024
50eef08
292 | Add judging status checkbox logic
emmabjj Dec 13, 2024
382894b
292 | Add js to update judging status checkboxes
emmabjj Dec 13, 2024
668bba0
Merge branch 'dev' into 291_evaluation_status_details
emmabjj Dec 13, 2024
bed653d
Small adjustments
emmabjj Dec 13, 2024
20ac36e
292 | Adjust submissions query
emmabjj Dec 13, 2024
53b9ba6
Adjust query to include evaluators/assignments
emmabjj Dec 13, 2024
c080498
Merge branch 'dev' into 291_evaluation_status_details
emmabjj Dec 19, 2024
c438b5c
Merge branch 'dev' into 291_evaluation_status_details
emmabjj Dec 20, 2024
fceba91
291 | Move judging status validation to submission model
emmabjj Dec 20, 2024
d7f0dbf
291 | Display active assignment evaluator names after unassign
emmabjj Dec 20, 2024
0028973
292 | Update tests to reflect judging status validation
emmabjj Dec 20, 2024
ef0b8a4
Merge branch 'dev' into 291_evaluation_status_details
emmabjj Dec 20, 2024
fbdb6a2
292 | format fixes
emmabjj Dec 20, 2024
304ad2d
291 | add spacing
emmabjj Dec 20, 2024
38f5591
292 | Remove eager loading of evaluators
emmabjj Dec 27, 2024
3fc774b
Merge branch 'dev' into 291_evaluation_status_details
emmabjj Dec 27, 2024
bde972d
291 | Update success message
emmabjj Dec 30, 2024
90ef960
Update copy
stepchud Jan 6, 2025
71b4182
Update copy
stepchud Jan 6, 2025
6e7f905
291 | Redirect to submission id
emmabjj Jan 6, 2025
2a7ca67
291 | Rename model methods and remove redundant judging status check
emmabjj Jan 6, 2025
e2fa456
291 | Utilitze renamed model methods
emmabjj Jan 6, 2025
d87188b
Merge branch 'dev' into 291_evaluation_status_details
emmabjj Jan 6, 2025
a490677
fix syntax error
stepchud Jan 6, 2025
2370d1f
convert #order_by_average_score class method to scope, make #average_…
stepchud Jan 6, 2025
06ad73e
update error message, fix linter
stepchud Jan 6, 2025
f45288e
linting
stepchud Jan 6, 2025
b5a0123
reformat to combine specs with the same name
stepchud Jan 7, 2025
1c377b8
linting
stepchud Jan 7, 2025
52ed1e3
remove unused helper methods
stepchud Jan 7, 2025
b27170a
291 | Fix test by mocking and adding an evaluation
emmabjj Jan 6, 2025
a32c5e1
291 | Fix merge conflict, combine submission index
emmabjj Jan 7, 2025
3c79563
fix submission_details_spec
stepchud Jan 7, 2025
e414fd8
Merge remote-tracking branch 'origin/291_evaluation_status_details' i…
stepchud Jan 7, 2025
07c0e5b
expect UI state
stepchud Jan 7, 2025
3ab2cd6
fix merge conflict
stepchud Jan 7, 2025
e220288
291 | Make submission table rows consistent in size
emmabjj Jan 7, 2025
0be31fa
291 | Add format scoring
emmabjj Jan 7, 2025
4477db3
291 | Remove redundant evaluation inclusion
emmabjj Jan 7, 2025
f144573
291 | Move sort and filter logic to service
emmabjj Jan 7, 2025
b4a06ee
291 | Add option to skip bullet warnings on RSpec tests
emmabjj Jan 7, 2025
8515494
291 | Reduce arguments initialized in sort and filter service
emmabjj Jan 7, 2025
eded377
291 | Add more specific name for submissions sort and filter service
emmabjj Jan 7, 2025
cc430c2
291 | Only calculate avg score with assigned and completed evaluation…
emmabjj Jan 7, 2025
c80157f
291 | Skip bullet warning on rspec avg score sort
emmabjj Jan 7, 2025
3fbe1d0
add alert text expectation
stepchud Jan 7, 2025
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
99 changes: 29 additions & 70 deletions app/controllers/phases_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,25 @@ def index
end

def submissions
@submissions = @phase.submissions
@submissions = @phase.submissions.includes(evaluator_submission_assignments: [:evaluator, :evaluation])

set_submission_counts
set_submission_statuses

apply_filters
apply_sorting
@submissions = SubmissionsSortAndFilterService.new(
@submissions,
params,
{
not_started: @not_started,
in_progress: @in_progress,
completed: @completed
}
).sort_and_filter

@filtered_count = @submissions.unscope(:group).distinct.count(:id)
@submissions = paginate_submissions(@submissions)

respond_to do |format|
format.html do
if params[:partial]
render partial: 'submissions_table_rows',
locals: { submissions: @submissions },
formats: [:html]
else
render :submissions
end
end
end
render_response
end

private
Expand All @@ -41,6 +38,10 @@ def set_phase
@challenge = @phase.challenge
end

def evaluator_assignments?
EvaluatorSubmissionAssignment.exists?(submission_id: @submissions.select(:id))
end

def set_submission_counts
@submissions_count = @submissions.count
@eligible_count = @submissions.eligible_for_evaluation.count
Expand All @@ -62,65 +63,23 @@ def set_submission_statuses
}
end

def apply_filters
filter_by_eligibility
filter_by_status
end

def filter_by_eligibility
return unless params[:eligible_for_evaluation] == 'true' ||
params[:selected_to_advance] == 'true'

@submissions = apply_eligibility_filter(@submissions)
end

def filter_by_status
return unless params[:status]

@submissions = apply_status_filter(@submissions)
end

def apply_status_filter(submissions)
case params[:status]
when 'not_started' then @not_started
when 'in_progress' then @in_progress
when 'completed' then @completed
when 'recused' then filter_recused_submissions
else submissions
end
end

def filter_recused_submissions
@submissions.joins(:evaluator_submission_assignments).
where(evaluator_submission_assignments: { status: :recused })
end

def apply_eligibility_filter(submissions)
if params[:selected_to_advance] == 'true'
submissions.where(judging_status: %w[winner])
elsif params[:eligible_for_evaluation] == 'true'
submissions.where(judging_status: %w[selected winner])
else
submissions
end
end

def apply_sorting
case params[:sort]
when 'average_score_high_to_low'
@submissions = @submissions.order_by_average_score(:desc)
when 'average_score_low_to_high'
@submissions = @submissions.order_by_average_score(:asc)
when 'submission_id_high_to_low'
@submissions = @submissions.order(id: :desc)
when 'submission_id_low_to_high'
@submissions = @submissions.order(id: :asc)
end
end

def paginate_submissions(submissions)
page = (params[:page] || 1).to_i
per_page = 20
submissions.offset((page - 1) * per_page).limit(per_page)
end

def render_response
respond_to do |format|
format.html do
if params[:partial]
render partial: 'submissions_table_rows',
locals: { submissions: @submissions },
formats: [:html]
else
render :submissions
end
end
end
end
end
24 changes: 19 additions & 5 deletions app/controllers/submissions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ class SubmissionsController < ApplicationController
def show; end

def update
if @submission.update!(submission_params)
flash.now[:success] = I18n.t("submission_updated")
render :show, submission: @submission
else
render :show, status: :unprocessable_entity, submission: @submission
respond_to do |format|
if @submission.update(submission_params)
stepchud marked this conversation as resolved.
Show resolved Hide resolved
handle_successful_update(format)
else
handle_failed_update(format)
end
end
end

Expand All @@ -26,4 +27,17 @@ def submission_params
def set_submission
@submission = Submission.by_user(current_user).find(params[:id])
end

def handle_successful_update(format)
format.html do
flash[:success] = I18n.t("submission_updated")
redirect_to submission_path(@submission)
end
format.json { render json: { submission: @submission } }
end

def handle_failed_update(format)
format.html { render :show }
format.json { render json: { errors: @submission.errors }, status: :unprocessable_entity }
end
end
21 changes: 21 additions & 0 deletions app/helpers/evaluations_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

# View helpers for calculating evaluation & submission details.
module EvaluationsHelper
Score = Struct.new(:raw_score, :formatted_score, :display_score)

# individual evaluator score
def evaluator_score(assignment)
score = display_score(assignment)
return Score.new(0, "0", "N/A") if score == 'N/A'

Score.new(score, score.to_s, score.to_s)
end

def average_score(submission)
score = submission.average_score
return Score.new(0, "0", "N/A") if score.zero?

Score.new(score, score.to_s, score.to_s)
end
end
7 changes: 5 additions & 2 deletions app/javascript/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ application.register("evaluation-form", EvaluationFormController);
import HotdogController from "./hotdog_controller";
application.register("hotdog", HotdogController);

import LoadMoreController from "./load_more_controller";
application.register("load-more", LoadMoreController);

import SubmissionDetailsController from "./submission_details_controller";
application.register("submission-details", SubmissionDetailsController);

import LoadMoreController from "./load_more_controller";
application.register("load-more", LoadMoreController);
import SubmissionJudgingStatusController from "./submission_judging_status_controller";
application.register("submission-judging-status", SubmissionJudgingStatusController);

import ModalController from "./modal_controller";
application.register("modal", ModalController);
Expand Down
45 changes: 45 additions & 0 deletions app/javascript/controllers/submission_judging_status_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// app/javascript/controllers/submission_judging_status_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = ["eligibilityForm", "advancementForm", "eligibilityCheckbox", "advancementCheckbox"]

toggleEligibility(event) {
event.preventDefault()
const formData = new FormData(this.eligibilityFormTarget)
formData.set('submission[judging_status]',
event.target.checked ? 'selected' : 'not_selected'
)
this.submitForm(formData, this.eligibilityFormTarget)
}

toggleAdvancement(event) {
event.preventDefault()
const formData = new FormData(this.advancementFormTarget)
formData.set('submission[judging_status]',
event.target.checked ? 'winner' : 'selected'
)

if (event.target.checked) {
this.eligibilityCheckboxTarget.checked = true
}
this.submitForm(formData, this.advancementFormTarget)
}

submitForm(formData, form) {
const csrfToken = document.querySelector('[name="csrf-token"]').content

fetch(form.action, {
method: 'PATCH',
headers: {
'X-CSRF-Token': csrfToken,
'Accept': 'application/json'
},
body: formData
})
.catch(() => {
const checkbox = form.querySelector('input[type="checkbox"]')
checkbox.checked = !checkbox.checked
})
}
}
7 changes: 5 additions & 2 deletions app/models/evaluator_submission_assignment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,12 @@ def self.ordered_by_status
end

def evaluation_status
return status.to_sym unless assigned?
return :recused if recused?
return :unassigned if unassigned?
return :recused_unassigned if recused_unassigned?
return assigned_evaluation_status if assigned?

assigned_evaluation_status
status&.to_sym
end

private
Expand Down
63 changes: 49 additions & 14 deletions app/models/submission.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ class Submission < ApplicationRecord

# Validations
validates :title, presence: true
validate :can_be_selected_to_advance,
if: -> { judging_status_change == %w[selected winner] }
validate :can_be_ineligible_for_evaluation,
if: -> { judging_status_change == %w[selected not_selected] }

scope :by_user, lambda { |user|
case user.role
Expand All @@ -63,20 +67,7 @@ class Submission < ApplicationRecord
}
scope :eligible_for_evaluation, -> { where(judging_status: [:selected, :winner]) }

def eligible_for_evaluation?
selected? or winner?
end

def selected_to_advance?
winner?
end

def average_score
avg = evaluations.average(:total_score)
avg ? avg.round : 0
end

def self.order_by_average_score(direction)
scope :order_by_average_score, lambda { |direction|
direction_sql = direction == :desc ? 'DESC' : 'ASC'

joins(
Expand All @@ -90,5 +81,49 @@ def self.order_by_average_score(direction)
"submissions.id #{direction_sql}"
)
)
}

def eligible_for_evaluation?
selected? or winner?
end

def average_score
avg = evaluations.joins(:evaluator_submission_assignment).
where(evaluator_submission_assignments: { status: :assigned }).
where.not(completed_at: nil).
average(:total_score)

avg ? avg.round : 0
end

def selected_to_advance?
winner?
end

def evaluators_assigned?
evaluator_submission_assignments.exists?(status: [:assigned, :recused])
end

def evaluations_missing_or_incomplete?
!eligible_for_evaluation? || !all_evaluations_completed? || evaluator_submission_assignments.empty?
end

private

def all_evaluations_completed?
evaluator_submission_assignments.
all? { |assignment| assignment.evaluation_status == :completed }
end

def can_be_selected_to_advance
return unless evaluations_missing_or_incomplete?

errors.add(:judging_status, "can't be selected to advance until all evaluations are complete")
end

def can_be_ineligible_for_evaluation
return unless evaluators_assigned?

errors.add(:judging_status, "must remain eligible for evaluation when evaluators are assigned")
end
end
Loading
Loading