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

[207] Sort and Filter Component #313

Merged
merged 28 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b4638a4
207 | Add logic to apply filters for statuses and apply sorting for a…
emmabjj Dec 5, 2024
558a93f
207 | Add js for sorting and filtering
emmabjj Dec 5, 2024
eba1bde
207 | Sort & Filter menu UI
emmabjj Dec 5, 2024
bb23ee3
207 | Add call to sort and filter menu
emmabjj Dec 5, 2024
5e753f7
207 | Add sort and filter tests
emmabjj Dec 5, 2024
d97453a
207 | Adjust call punctuation
emmabjj Dec 5, 2024
1b54c46
207 | Adjust indention
emmabjj Dec 6, 2024
ddbcd89
indent
emmabjj Dec 6, 2024
1c1b1dd
Merge branch 'dev' into 207_sort_and_filter_component
stepchud Dec 10, 2024
d7bef8a
Merge branch 'dev' into 207_sort_and_filter_component
stepchud Dec 18, 2024
c52d6b4
207 | Mobile responsiveness
emmabjj Dec 19, 2024
4302201
207 | Prevent dynamic stats updating on sort and filter
emmabjj Dec 19, 2024
61b8f97
207 | Exclude incomplete evaluations from average scoring
emmabjj Dec 19, 2024
cb0fc62
207 | Update sort and filter tests
emmabjj Dec 19, 2024
eec808d
Merge branch 'dev' into 207_sort_and_filter_component
emmabjj Dec 19, 2024
fcda9ab
207 | Split out assignments
emmabjj Dec 19, 2024
7f9e590
207 | Adjust eligibility filters
emmabjj Dec 19, 2024
1fd1b7b
207 | Combine status and eligibility to prevent error with order of o…
emmabjj Dec 19, 2024
513f6fc
207 | Add guards
emmabjj Dec 19, 2024
527963c
Merge branch 'dev' into 207_sort_and_filter_component
emmabjj Dec 19, 2024
8b49758
207 | Update tests to use data-submission-id
emmabjj Dec 19, 2024
dcd3c90
Merge branch 'dev' into 207_sort_and_filter_component
emmabjj Dec 23, 2024
a6e6642
207 | Fix mobile UI
emmabjj Dec 26, 2024
21cb976
207 | Override low contrast on hover in mobile
emmabjj Jan 3, 2025
38b9860
207 | Remove :through association and scope complete submissions
emmabjj Jan 3, 2025
5cb3650
207 | Simplify scoring format
emmabjj Jan 3, 2025
f330e8b
207 | Include submissions with not_started evaluations that are parti…
emmabjj Jan 6, 2025
81195b4
207 | Adjust query
emmabjj Jan 6, 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
31 changes: 30 additions & 1 deletion app/assets/stylesheets/application.sass.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,35 @@ dialog::backdrop {
background-color: #4d8055;
}

.usa-accordion__button.usa-nav__link.usa-current {
&:hover {
color: inherit;
outline: none;
}

&::after {
display: none;
}
}

.usa-nav__primary {
button[aria-expanded="false"],
button[aria-expanded="true"] {
span {
padding-right: 2rem;
padding-bottom: 4px;
font-weight: 700;

&::after {
right: 12px;
background-color: #005ea2 !important;
display: block !important;
position: absolute;
}
}
}
}

.usa-combo-box .border-secondary + input {
border: 1px solid red;
}
}
87 changes: 80 additions & 7 deletions app/controllers/phases_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@ def index

def submissions
@submissions = @phase.submissions
@submissions_count = @submissions.count
not_started = @submissions.left_outer_joins(:evaluations).
where({ "evaluations.id" => nil }).count
in_progress = @submissions.joins(:evaluations).
select("submissions.id").where({ "evaluations.completed_at" => nil }).distinct.count
completed = @submissions_count - in_progress - not_started
@submissions_by_status = { not_started:, in_progress:, completed: }

set_submission_counts
set_submission_statuses

apply_filters
apply_sorting
end

private
Expand All @@ -26,4 +25,78 @@ def set_phase
@phase = Phase.where(challenge: current_user.challenge_manager_challenges).find(params[:id])
@challenge = @phase.challenge
end

def set_submission_counts
@submissions_count = @submissions.count
@eligible_count = @submissions.eligible_for_evaluation.count
@selected_count = @submissions.winner.count
end

def set_submission_statuses
@not_started = @submissions.where.missing(:evaluations)
@in_progress = @submissions.joins(:evaluations).
where(evaluations: { completed_at: nil }).distinct
@completed = @submissions.joins(:evaluations).
where.not(evaluations: { completed_at: nil }).distinct
stepchud marked this conversation as resolved.
Show resolved Hide resolved

@submissions_by_status = {
not_started: @not_started.count,
stepchud marked this conversation as resolved.
Show resolved Hide resolved
in_progress: @in_progress.count,
completed: @completed.count
}
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])
else
submissions.where(judging_status: %w[selected winner])
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
end
12 changes: 6 additions & 6 deletions app/javascript/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ import { application } from "./application";
import DeleteEvaluatorModalController from "./delete_evaluator_modal_controller";
application.register("delete-evaluator-modal", DeleteEvaluatorModalController);

import UnassignEvaluatorSubmissionModalController from "./unassign_evaluator_submission_modal_controller";
application.register(
"unassign-evaluator-submission-modal",
UnassignEvaluatorSubmissionModalController
);

import EvaluationCriteriaController from "./evaluation_criteria_controller";
application.register("evaluation-criteria", EvaluationCriteriaController);

Expand All @@ -27,3 +21,9 @@ application.register("submission-details", SubmissionDetailsController);

import ModalController from "./modal_controller";
application.register("modal", ModalController);

import SortFilterMenuController from "./sort_filter_menu_controller";
application.register("sort-filter-menu", SortFilterMenuController);

import UnassignEvaluatorSubmissionModalController from "./unassign_evaluator_submission_modal_controller";
application.register("unassign-evaluator-submission-modal", UnassignEvaluatorSubmissionModalController);
60 changes: 60 additions & 0 deletions app/javascript/controllers/sort_filter_menu_controller.js
stepchud marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// app/javascript/controllers/sort_filter_menu_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = ["menuItem"]

connect() {
this.updateCheckmarks()
}

toggle(event) {
event.preventDefault()
const clickedItem = event.currentTarget
const filterValue = clickedItem.dataset.filterValue

const currentUrl = new URL(window.location.href)
const [param, value] = filterValue.split('=')

if (currentUrl.searchParams.get(param) === value) {
this.clearAllFilters()
} else {
this.applyFilter(filterValue)
}
}

applyFilter(filterValue) {
const currentUrl = new URL(window.location.href)
const [param, value] = filterValue.split('=')

this.removeExistingParams(currentUrl)
currentUrl.searchParams.set(param, value)
window.location.href = currentUrl.toString()
}

clearAllFilters() {
const currentUrl = new URL(window.location.href)
this.removeExistingParams(currentUrl)
window.location.href = currentUrl.toString()
}

removeExistingParams(url) {
const paramsToRemove = ['status', 'eligible_for_evaluation', 'selected_to_advance', 'sort']
paramsToRemove.forEach(key => {
url.searchParams.delete(key)
})
}

updateCheckmarks() {
const currentUrl = new URL(window.location.href)
this.menuItemTargets.forEach(item => {
const checkmark = item.querySelector('.checkmark')
const [param, value] = item.dataset.filterValue.split('=')
if (currentUrl.searchParams.get(param) === value) {
checkmark.style.display = 'inline'
} else {
checkmark.style.display = 'none'
}
})
}
}
24 changes: 23 additions & 1 deletion app/models/submission.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class Submission < ApplicationRecord
belongs_to :manager, class_name: 'User'
has_many :evaluator_submission_assignments, dependent: :destroy
has_many :evaluators, through: :evaluator_submission_assignments, class_name: "User"
has_many :evaluations, dependent: :destroy
has_many :evaluations, through: :evaluator_submission_assignments, dependent: :destroy
stepchud marked this conversation as resolved.
Show resolved Hide resolved

# Fields
attribute :title, :string
Expand Down Expand Up @@ -70,4 +70,26 @@ def eligible_for_evaluation?
def selected_to_advance?
winner?
end

def average_score
avg = evaluations.average(:total_score)
score = avg ? avg.round : 0
[score, score.to_s]
stepchud marked this conversation as resolved.
Show resolved Hide resolved
end

def self.order_by_average_score(direction)
direction_sql = direction == :desc ? 'DESC' : 'ASC'

joins(
"LEFT JOIN evaluations ON evaluations.submission_id = submissions.id " \
"AND evaluations.completed_at IS NOT NULL"
).
group('submissions.id').
order(
Arel.sql(
"COALESCE(ROUND(AVG(evaluations.total_score)), 0) #{direction_sql}, " \
"submissions.id #{direction_sql}"
)
)
end
end
52 changes: 52 additions & 0 deletions app/views/phases/_sort_and_filter.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<div class="grid-container padding-x-0">
<div class="grid-row">
<div class="grid-col-12 display-flex flex-column flex-align-start padding-top-105">
<nav aria-label="Sort and filter options" class="tablet:usa-nav display-flex" data-controller="sort-filter-menu">
<ul class="usa-nav__primary usa-accordion" style="position: relative;">
<li class="radius-md">
<button
type="button"
class="usa-accordion__button usa-nav__link usa-current"
aria-expanded="false"
aria-controls="basic-nav-section-one"
style="background-color: transparent; border: none; padding: 0;"
>
<span class="text-primary font-sans-md text-underline" style="text-decoration-thickness: 2px;">Sort & Filter</span>
</button>
<ul id="basic-nav-section-one" class="usa-nav__submenu text-no-wrap bg-transparent font-sans-xs width-auto"
style="position: absolute; top: 100%; left: 0; z-index: 1000; max-height: 80vh; overflow-y: auto;">
<% menu_items = [
{ label: "Evaluation progress: Completed", value: "status=completed" },
{ label: "Evaluation progress: In progress", value: "status=in_progress" },
{ label: "Evaluation progress: Not started", value: "status=not_started" },
{ label: "Evaluation progress: Recused", value: "status=recused" },
{ label: "Eligible for Evaluation", value: "eligible_for_evaluation=true" },
{ label: "Selected to Advance", value: "selected_to_advance=true" },
{ label: "Average score: high to low", value: "sort=average_score_high_to_low" },
{ label: "Average score: low to high", value: "sort=average_score_low_to_high" },
{ label: "Submission ID: high to low", value: "sort=submission_id_high_to_low" },
{ label: "Submission ID: low to high", value: "sort=submission_id_low_to_high" }
] %>
<% menu_items.each do |item| %>
<li class="usa-nav__submenu-item bg-primary-darker margin-y-2px">
<a href="#" class="text-white text-no-underline menu-item display-flex flex-align-center padding-y-05 padding-x-1 width-full"
data-action="click->sort-filter-menu#toggle"
data-sort-filter-menu-target="menuItem"
data-filter-value="<%= item[:value] %>">
<%= image_tag "images/usa-icons/check.svg",
width: 18,
height: 18,
alt: "Selected",
class: "icon-white checkmark margin-left-neg-1 padding-top-05",
style: "display: none;" %>
<span class="text-white"><%= item[:label] %></span>
</a>
</li>
<% end %>
</ul>
</li>
</ul>
</nav>
</div>
</div>
</div>
4 changes: 2 additions & 2 deletions app/views/phases/_submissions_stats.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
alt: ""
)%>
<span class="text-top">
<span class="text-primary"><%= @submissions.eligible_for_evaluation.count %> of <%= @submissions_count %></span>
<span class="text-primary"><%= @eligible_count %> of <%= @submissions_count %></span>
submissions selected for evaluation
</span>
</div>
Expand All @@ -58,7 +58,7 @@
alt: ""
)%>
<span class="text-top">
<span class="text-primary"><%= @submissions.winner.count %> of <%= @submissions.eligible_for_evaluation.count %></span>
<span class="text-primary"><%= @selected_count %> of <%= @eligible_count %></span>
submissions selected to advance
</span>
</div>
Expand Down
28 changes: 16 additions & 12 deletions app/views/phases/_submissions_table.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@
<%= render 'shared/alert_error', alert_heading: t('alerts.recused_evaluator.heading'), alert_text: t('alerts.recused_evaluator.text') %>
<% end %>

<table class="usa-table usa-table--stacked-header usa-table--borderless width-full">
<%= render partial: "phases/sort_and_filter" %>

<table class="usa-table usa-table--stacked-header usa-table--borderless width-full gray-header">
<thead>
<tr>
<th scope="col">Submission ID</th>
<th scope="col">Eligible for Evaluation</th>
<th scope="col">Selected to Advance</th>
<th scope="col">Assigned Evaluators</th>
<th scope="col">Average Score</th>
<th scope="col"><span class="usa-sr-only">View Submission</span></th>
</tr>
</thead>
<tbody>
Expand All @@ -25,15 +28,15 @@
<td data-label="Eligible for Evaluation">
<div class="display-flex flex-align-center">
<% if submission.eligible_for_evaluation? %>
<input type="checkbox" disabled checked class="display-none mobile-lg:display-block">
<div class="mobile-lg:display-none">
<%= image_tag(
"images/usa-icons/verified.svg",
class: "usa-icon--size-3 margin-right-1",
alt: ""
)%>
<span class="text-top">Eligible for Evaluation</span>
</div>
<input type="checkbox" disabled checked class="display-none mobile-lg:display-block">
<div class="mobile-lg:display-none">
<%= image_tag(
"images/usa-icons/verified.svg",
class: "usa-icon--size-3 margin-right-1",
alt: ""
)%>
<span class="text-top">Eligible for Evaluation</span>
</div>
<% else %>
<input type="checkbox" disabled class="display-none mobile-lg:display-block">
<div class="mobile-lg:display-none">
Expand Down Expand Up @@ -75,8 +78,9 @@
<td data-label="Assigned Evaluators">
No evaluators assigned to this submission
</td>
<td>
N/A
<% score, formatted_score = submission.average_score %>
<td data-label="Average Score" data-score="<%= score %>">
<%= formatted_score %>
</td>
<td>
<div class="display-flex flex-no-wrap grid-row grid-gap-1">
Expand Down
Loading
Loading