Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### 🚨 Breaking changes

### ✨ New features and improvements
- Added more download options to automated test result summaries (#7599)

### 🐛 Bug fixes

Expand Down
73 changes: 73 additions & 0 deletions app/assets/stylesheets/common/_markus.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1530,3 +1530,76 @@ canvas {
.jcrop-centered {
display: inline-block;
}

.feature-option {
margin: 0.5rem;
}

.feature-option input[type='radio'] {
display: none;
}

.feature-option label {
width: 100px;
background: linear-gradient(to top, $background-main, $background-support);
border: 1px solid $primary-three;
border-radius: $radius;
box-shadow: inset 0 -1px 0 $primary-two;
color: $line;
cursor: pointer;
display: inline-block;
font: 400 1em $fonts;
min-width: 150px;
outline: none;
padding: 0.5em 1.5em;
text-align: center;
transition: all $time-quick;
}

.feature-option input[type='radio']:checked + label {
background: linear-gradient(to bottom, $primary-one, $primary-three);
border: 1px solid $primary-one;
color: $background-main;
box-shadow: inset 0 1px 0 $primary-one;
}

.feature-option label:hover {
border-color: $primary-one;
box-shadow:
inset 0 -1px 0 $primary-two,
0 1px 0 $background-main;
}

.feature-option input[type='radio']:disabled + label {
color: $line;
cursor: not-allowed;
background: linear-gradient(to top, $background-main, $disabled-area);
border-color: $disabled-area;
box-shadow: none;
}

.feature-option input[type='radio']:disabled + label:hover {
border-color: $disabled-area;
}

.feature-grid-wrap {
display: grid;
grid-template-columns: repeat(3, max-content);
column-gap: 0;
}

.feature-row {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
column-gap: inherit;
width: 150px;
}

.feature-submit-group {
display: flex;
flex-direction: row;
gap: 1rem;
justify-content: right;
margin-top: 2rem;
}
54 changes: 43 additions & 11 deletions app/controllers/assignments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -271,22 +271,54 @@ def summary

def download_test_results
@assignment = record

latest = params[:latest] == 'true'
student_run = params[:student_run] == 'true'
instructor_run = params[:instructor_run] == 'true'

test_results = SummaryTestResultsHelper::SummaryTestResults.fetch(
test_groups: @assignment.test_groups,
latest:,
student_run:,
instructor_run:
)

respond_to do |format|
format.json do
data = @assignment.summary_test_result_json
filename = "#{@assignment.short_identifier}_test_results.json"
send_data data,
disposition: 'attachment',
type: 'application/json',
filename: filename
data = test_results.as_json

if latest
send_data(
data,
type: 'application/json',
disposition: 'attachment',
filename: "#{@assignment.short_identifier}_test_results.json"
)
else
zip_path = "tmp/#{@assignment.short_identifier}_test_results.zip"
Zip::File.open(zip_path, create: true) do |zip_file|
zip_file.get_output_stream('test_results.json') do |f|
f.write(data)
end
end

send_file(
zip_path,
disposition: 'attachment',
filename: "#{@assignment.short_identifier}_test_results.zip"
)
end
end

format.csv do
data = @assignment.summary_test_result_csv
filename = "#{@assignment.short_identifier}_test_results.csv"
send_data data,
disposition: 'attachment',
type: 'text/csv',
filename: filename

send_data(
test_results.as_csv,
disposition: 'attachment',
type: 'text/csv',
filename: filename
)
end
end
end
Expand Down
97 changes: 97 additions & 0 deletions app/helpers/summary_test_results_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
module SummaryTestResultsHelper
class SummaryTestResults
class << self
def fetch(test_groups:, latest:, student_run:, instructor_run:)
query = base_query(latest:)

query = query.student_run if student_run && !instructor_run
query = query.instructor_run if !student_run && instructor_run

test_results = fetch_with_query(test_groups:, query:)

SummaryTestResult.new(test_results:)
end

private

def base_query(latest:)
if latest
TestRun.group('grouping_id').select('MAX(created_at) as test_runs_created_at', 'grouping_id')
else
TestRun.select('created_at as test_runs_created_at', 'grouping_id')
end
end

def fetch_with_query(test_groups:, query:)
latest_test_runs = TestRun
.joins(grouping: :group)
.joins("INNER JOIN (#{query.to_sql}) latest_test_run_by_grouping \
ON latest_test_run_by_grouping.grouping_id = test_runs.grouping_id \
AND latest_test_run_by_grouping.test_runs_created_at = test_runs.created_at")
.select('id', 'test_runs.grouping_id', 'groups.group_name')
.to_sql

test_groups.joins(test_group_results: :test_results)
.joins("INNER JOIN (#{latest_test_runs}) latest_test_runs \
ON test_group_results.test_run_id = latest_test_runs.id")
.select('test_groups.name',
'test_groups.id as test_groups_id',
'latest_test_runs.group_name',
'test_results.name as test_result_name',
'test_results.status',
'test_results.marks_earned',
'test_results.marks_total',
:output, :extra_info, :error_type)
end
end
end

class SummaryTestResult
def initialize(test_results:)
@test_results = test_results
end

def as_csv
results = {}
headers = Set.new

summary_test_results = @test_results.as_json

summary_test_results.each do |test_result|
header = "#{test_result['name']}:#{test_result['test_result_name']}"

if results.key?(test_result['group_name'])
results[test_result['group_name']][header] = test_result['status']
else
results[test_result['group_name']] = { header => test_result['status'] }
end

headers << header
end
headers = headers.sort

CSV.generate do |csv|
csv << [nil, *headers]

results.sort_by(&:first).each do |(group_name, _test_group)|
row = [group_name]

headers.each do |header|
if results[group_name].key?(header)
row << results[group_name][header]
else
row << nil
end
end
csv << row
end
end
end

def as_json
@test_results.group_by(&:group_name).transform_values do |grouping|
grouping.group_by(&:name)
end.to_json
end
end
end
Loading