Skip to content

Commit bd04215

Browse files
committed
Improve automated test download functionality
[pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci
1 parent 7398e68 commit bd04215

File tree

13 files changed

+776
-198
lines changed

13 files changed

+776
-198
lines changed

app/assets/stylesheets/common/_markus.scss

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1530,3 +1530,76 @@ canvas {
15301530
.jcrop-centered {
15311531
display: inline-block;
15321532
}
1533+
1534+
.feature-option {
1535+
margin: 0.5rem;
1536+
}
1537+
1538+
.feature-option input[type='radio'] {
1539+
display: none;
1540+
}
1541+
1542+
.feature-option label {
1543+
width: 100px;
1544+
background: linear-gradient(to top, $background-main, $background-support);
1545+
border: 1px solid $primary-three;
1546+
border-radius: $radius;
1547+
box-shadow: inset 0 -1px 0 $primary-two;
1548+
color: $line;
1549+
cursor: pointer;
1550+
display: inline-block;
1551+
font: 400 1em $fonts;
1552+
min-width: 150px;
1553+
outline: none;
1554+
padding: 0.5em 1.5em;
1555+
text-align: center;
1556+
transition: all $time-quick;
1557+
}
1558+
1559+
.feature-option input[type='radio']:checked + label {
1560+
background: linear-gradient(to bottom, $primary-one, $primary-three);
1561+
border: 1px solid $primary-one;
1562+
color: $background-main;
1563+
box-shadow: inset 0 1px 0 $primary-one;
1564+
}
1565+
1566+
.feature-option label:hover {
1567+
border-color: $primary-one;
1568+
box-shadow:
1569+
inset 0 -1px 0 $primary-two,
1570+
0 1px 0 $background-main;
1571+
}
1572+
1573+
.feature-option input[type='radio']:disabled + label {
1574+
color: $line;
1575+
cursor: not-allowed;
1576+
background: linear-gradient(to top, $background-main, $disabled-area);
1577+
border-color: $disabled-area;
1578+
box-shadow: none;
1579+
}
1580+
1581+
.feature-option input[type='radio']:disabled + label:hover {
1582+
border-color: $disabled-area;
1583+
}
1584+
1585+
.feature-grid-wrap {
1586+
display: grid;
1587+
grid-template-columns: repeat(3, max-content);
1588+
column-gap: 0;
1589+
}
1590+
1591+
.feature-row {
1592+
display: grid;
1593+
grid-template-columns: subgrid;
1594+
grid-column: 1 / -1;
1595+
column-gap: inherit;
1596+
width: 150px;
1597+
}
1598+
1599+
.feature-submit-group {
1600+
display: flex;
1601+
flex-direction: row;
1602+
gap: 1rem;
1603+
justify-content: right;
1604+
margin-top: 2rem;
1605+
}

app/controllers/assignments_controller.rb

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -271,22 +271,54 @@ def summary
271271

272272
def download_test_results
273273
@assignment = record
274+
275+
latest = params[:latest] == 'true'
276+
student_run = params[:student_run] == 'true'
277+
instructor_run = params[:instructor_run] == 'true'
278+
279+
test_results = SummaryTestResultsHelper::SummaryTestResults.fetch(
280+
test_groups: @assignment.test_groups,
281+
latest:,
282+
student_run:,
283+
instructor_run:
284+
)
285+
274286
respond_to do |format|
275287
format.json do
276-
data = @assignment.summary_test_result_json
277-
filename = "#{@assignment.short_identifier}_test_results.json"
278-
send_data data,
279-
disposition: 'attachment',
280-
type: 'application/json',
281-
filename: filename
288+
data = test_results.as_json
289+
290+
if latest
291+
send_data(
292+
data,
293+
type: 'application/json',
294+
disposition: 'attachment',
295+
filename: "#{@assignment.short_identifier}_test_results.json"
296+
)
297+
else
298+
zip_path = "tmp/#{@assignment.short_identifier}_test_results.zip"
299+
Zip::File.open(zip_path, create: true) do |zip_file|
300+
zip_file.get_output_stream('test_results.json') do |f|
301+
f.write(data)
302+
end
303+
end
304+
305+
send_file(
306+
zip_path,
307+
disposition: 'attachment',
308+
filename: "#{@assignment.short_identifier}_test_results.zip"
309+
)
310+
end
282311
end
312+
283313
format.csv do
284-
data = @assignment.summary_test_result_csv
285314
filename = "#{@assignment.short_identifier}_test_results.csv"
286-
send_data data,
287-
disposition: 'attachment',
288-
type: 'text/csv',
289-
filename: filename
315+
316+
send_data(
317+
test_results.as_csv,
318+
disposition: 'attachment',
319+
type: 'text/csv',
320+
filename: filename
321+
)
290322
end
291323
end
292324
end
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
module SummaryTestResultsHelper
2+
class SummaryTestResults
3+
class << self
4+
def fetch(test_groups:, latest:, student_run:, instructor_run:)
5+
query = base_query(latest:)
6+
7+
query = query.student_run if student_run && !instructor_run
8+
query = query.instructor_run if !student_run && instructor_run
9+
10+
test_results = fetch_with_query(test_groups:, query:)
11+
12+
SummaryTestResult.new(test_results:)
13+
end
14+
15+
private
16+
17+
def base_query(latest:)
18+
if latest
19+
TestRun.group('grouping_id').select('MAX(created_at) as test_runs_created_at', 'grouping_id')
20+
else
21+
TestRun.select('created_at as test_runs_created_at', 'grouping_id')
22+
end
23+
end
24+
25+
def fetch_with_query(test_groups:, query:)
26+
latest_test_runs = TestRun
27+
.joins(grouping: :group)
28+
.joins("INNER JOIN (#{query.to_sql}) latest_test_run_by_grouping \
29+
ON latest_test_run_by_grouping.grouping_id = test_runs.grouping_id \
30+
AND latest_test_run_by_grouping.test_runs_created_at = test_runs.created_at")
31+
.select('id', 'test_runs.grouping_id', 'groups.group_name')
32+
.to_sql
33+
34+
test_groups.joins(test_group_results: :test_results)
35+
.joins("INNER JOIN (#{latest_test_runs}) latest_test_runs \
36+
ON test_group_results.test_run_id = latest_test_runs.id")
37+
.select('test_groups.name',
38+
'test_groups.id as test_groups_id',
39+
'latest_test_runs.group_name',
40+
'test_results.name as test_result_name',
41+
'test_results.status',
42+
'test_results.marks_earned',
43+
'test_results.marks_total',
44+
:output, :extra_info, :error_type)
45+
end
46+
end
47+
end
48+
49+
class SummaryTestResult
50+
def initialize(test_results:)
51+
@test_results = test_results
52+
end
53+
54+
def as_csv
55+
results = {}
56+
headers = Set.new
57+
58+
summary_test_results = @test_results.as_json
59+
60+
summary_test_results.each do |test_result|
61+
header = "#{test_result['name']}:#{test_result['test_result_name']}"
62+
63+
if results.key?(test_result['group_name'])
64+
results[test_result['group_name']][header] = test_result['status']
65+
else
66+
results[test_result['group_name']] = { header => test_result['status'] }
67+
end
68+
69+
headers << header
70+
end
71+
headers = headers.sort
72+
73+
CSV.generate do |csv|
74+
csv << [nil, *headers]
75+
76+
results.sort_by(&:first).each do |(group_name, _test_group)|
77+
row = [group_name]
78+
79+
headers.each do |header|
80+
if results[group_name].key?(header)
81+
row << results[group_name][header]
82+
else
83+
row << nil
84+
end
85+
end
86+
csv << row
87+
end
88+
end
89+
end
90+
91+
def as_json
92+
@test_results.group_by(&:group_name).transform_values do |grouping|
93+
grouping.group_by(&:name)
94+
end.to_json
95+
end
96+
end
97+
end

0 commit comments

Comments
 (0)