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

Sort scoreboard beyond third column, improve CUD validations #1925

Merged
merged 14 commits into from
Jun 21, 2023
44 changes: 10 additions & 34 deletions app/controllers/scoreboards_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ def emitColSpec(colspec)
str += " | "
end
str += hash["hdr"].to_s.upcase
str += hash["asc"] ? " [asc]" : " [desc]" if i < 3
str += hash["asc"] ? " [asc]" : " [desc]"
i += 1
end
str
Expand Down Expand Up @@ -318,46 +318,22 @@ def scoreboardOrderSubmissions(a, b)
# in descending order. Lab authors can modify the default
# direction with the "asc" key in the column spec.
else
a0 = a[:entry][0].to_f
a1 = a[:entry][1].to_f
a2 = a[:entry][2].to_f
b0 = b[:entry][0].to_f
b1 = b[:entry][1].to_f
b2 = b[:entry][2].to_f

begin
parsed = ActiveSupport::JSON.decode(@scoreboard.colspec)
rescue StandardError => e
Rails.logger.error("Error in scoreboards controller updater: #{e.message}")
end

if a0 != b0
if parsed && parsed["scoreboard"] &&
!parsed["scoreboard"].empty? &&
parsed["scoreboard"][0]["asc"]
a0 <=> b0 # ascending order
else
b0 <=> a0 # descending order
end
elsif a1 != b1
if parsed && parsed["scoreboard"] &&
parsed["scoreboard"].size > 1 &&
parsed["scoreboard"][1]["asc"]
a1 <=> b1 # ascending order
else
b1 <=> a1 # descending order
end
elsif a2 != b2
if parsed && parsed["scoreboard"] &&
parsed["scoreboard"].size > 2 &&
parsed["scoreboard"][2]["asc"]
a2 <=> b2 # ascending order
else
b2 <=> a2 # descending order
end
else
a[:time] <=> b[:time] # ascending by submission time
# Validations ensure that colspec is of the correct format
parsed["scoreboard"].each_with_index do |v, i|
ai = a[:entry][i].to_f
bi = b[:entry][i].to_f
next unless ai != bi
return ai <=> bi if v["asc"] # ascending

return bi <=> ai # descending otherwise
end
a[:time] <=> b[:time] # ascending by submission time to tiebreak
end
end
end
15 changes: 6 additions & 9 deletions app/models/scoreboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@ def colspec_is_well_formed

# The parse will throw an exception if the string has a JSON syntax error
begin
# Quote JSON keys and values if they are not already quoted
quoted = colspec.gsub(/([a-zA-Z0-9]+):/, '"\1":').gsub(/:([a-zA-Z0-9]+)/, ':"\1"')
damianhxy marked this conversation as resolved.
Show resolved Hide resolved
parsed = ActiveSupport::JSON.decode(quoted)
parsed = ActiveSupport::JSON.decode(colspec)
rescue StandardError => e
errors.add "colspec", e.to_s
return
end

unless parsed.is_a? Hash
errors.add "colspec", "must be a hash object"
return
end

# Colspecs must include a scoreboard array object
unless parsed["scoreboard"]
errors.add "colspec", "missing 'scoreboard' array object"
Expand Down Expand Up @@ -60,12 +63,6 @@ def colspec_is_well_formed
errors.add "colspec", "unknown key('#{k}') in scoreboard[#{i}]"
return
end

next unless k == "asc" && i > 2

errors.add "colspec",
"'asc' key in col #{i} ignored because only the first",
"three columns are sorted."
end
end
end
Expand Down
42 changes: 25 additions & 17 deletions app/views/scoreboards/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@
<%= javascript_include_tag "sorttable" %>
<% end %>

<h4>Scoreboard</h4>

<% if @grades.values.empty? %>
<h3>No submissions yet.</h3>
<strong>There are currently no submissions.</strong>
<% else %>
<h3>
<strong>
<% if @scoreboard.banner.blank? %>
Here are the most recent scores for the class.
<% else %>
<%= sanitize @scoreboard.banner %>
<% end %>
</h3>
</strong>
<hr>

<table class="sortable prettyBorder">
<thead>
Expand Down Expand Up @@ -73,19 +76,24 @@
<% end %>


<%= form_for @cud, url: course_course_user_datum_path(@course, @cud) do |f| %>
<% if @cud.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@cud.errors.count, "error") %> prohibited the data from being saved:</h2>
<ul>
<% @cud.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<%= form_for @cud, url: course_course_user_datum_path(@course, @cud), builder: FormBuilderWithDateTimeInput do |f| %>
<% if @cud.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@cud.errors.count, "error") %> prohibited the data from being saved:</h2>
<ul>
<% @cud.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<hr>
<div class="row valign-wrapper">
<div class="col s4">
<%= f.text_field :nickname %>
</div>
<div class="col s8">
<input id="user_submit" name="commit" type="submit" class="btn" value="Update" onclick="formvalidation(this.parentNode); return false;">
</div>
</div>
<% end %>
<hr>
<table width=50% class="verticalTable" >
<tr><th><h3>Nickname:</h3> </th><td><%= f.text_field :nickname %> <input id="user_submit" name="commit" type="submit" class="btn" value="Update Nickname" onclick="formvalidation(this.parentNode); return false;"></td></tr>
</table>
<% end %>
2 changes: 1 addition & 1 deletion docs/features/scoreboards.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The column spec consists of a "scoreboard" object, which is an array of JSON obj
}
```

A custom scoreboard sorts the first three columns, from left to right, in descending order. You can change the default sort order for a particular column by adding an optional "asc:1" element to its hash.
A custom scoreboard sorts the columns from left to right in descending order, and tiebreaks by submission time. You can change the default sort order for a particular column by adding an optional "asc:1" element to its hash.

**Example:** Scoreboard with two columns, "Score" and "Ops", with "Score" sorted descending, and then "Ops" ascending:

Expand Down