Merge remote-tracking branch 'origin/master' into activity-graphs

# Conflicts:
#	config/locales/de.yml
#	config/locales/en.yml
This commit is contained in:
Maximilian Grundke
2018-04-25 15:46:38 +02:00
12 changed files with 200 additions and 81 deletions

View File

@ -0,0 +1,27 @@
$(document).ready(function () {
var subMenusSelector = 'ul.dropdown-menu [data-toggle=dropdown]';
function openSubMenu(event) {
if (this.pathname === '/') {
event.preventDefault();
}
event.stopPropagation();
$(subMenusSelector).parent().removeClass('open');
$(this).parent().addClass('open');
var menu = $(this).parent().find("ul");
var menupos = menu.offset();
var newPos;
if ((menupos.left + menu.width()) + 30 > $(window).width()) {
newPos = -menu.width();
} else {
newPos = $(this).parent().width();
}
menu.css({left: newPos});
}
$(subMenusSelector).on('click', openSubMenu).on('mouseenter', openSubMenu);
});

View File

@ -0,0 +1,38 @@
.dropdown-submenu {
position: relative;
}
.dropdown-submenu > .dropdown-menu {
top: 0;
left: 100%;
}
.dropdown-submenu > a:after {
display: block;
content: " ";
float: right;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-width: 5px 0 5px 5px;
border-left-color: #cccccc;
margin-top: 5px;
margin-right: -10px;
}
.dropdown-submenu:hover > a:after {
border-left-color: #ffffff;
}
.dropdown-submenu.pull-left {
float: none;
}
.dropdown-submenu.pull-left > .dropdown-menu {
left: -100%;
margin-left: 10px;
-webkit-border-radius: 6px 0 6px 6px;
-moz-border-radius: 6px 0 6px 6px;
border-radius: 6px 0 6px 6px;
}

View File

@ -53,64 +53,72 @@
}
}
}
.testrun-assess-results {
.testrun-assess-results {
.testrun-container {
display: flex;
margin-bottom: 10px;
display: flex;
.testrun-output {
overflow-x: auto;
flex-grow: 1;
}
}
.result {
margin-right: 10px;
margin-top: 20px;
width: 10px;
height: 10px;
}
.passed {
border-radius: 50%;
background-color: #8efa00;
-webkit-box-shadow: 0 0 11px 1px rgba(44,222,0,1);
-moz-box-shadow: 0 0 11px 1px rgba(44,222,0,1);
box-shadow: 0 0 11px 1px rgba(44,222,0,1);
}
.unknown {
border-radius: 50%;
background-color: #ffca00;
-webkit-box-shadow: 0 0 11px 1px rgb(255, 202, 0);
-moz-box-shadow: 0 0 11px 1px rgb(255, 202, 0);
box-shadow: 0 0 11px 1px rgb(255, 202, 0);
}
.failed {
border-radius: 50%;
background-color: #ff2600;
-webkit-box-shadow: 0 0 11px 1px rgba(222,0,0,1);
-moz-box-shadow: 0 0 11px 1px rgba(222,0,0,1);
box-shadow: 0 0 11px 1px rgba(222,0,0,1);
}
.result {
margin-right: 10px;
width: 10px;
height: 10px;
}
.passed {
border-radius: 50%;
background-color: #8efa00;
-webkit-box-shadow: 0 0 11px 1px rgba(44,222,0,1);
-moz-box-shadow: 0 0 11px 1px rgba(44,222,0,1);
box-shadow: 0 0 11px 1px rgba(44,222,0,1);
#mark-as-solved-button {
margin-top: 20px;
}
.unknown {
border-radius: 50%;
background-color: #ffca00;
-webkit-box-shadow: 0 0 11px 1px rgb(255, 202, 0);
-moz-box-shadow: 0 0 11px 1px rgb(255, 202, 0);
box-shadow: 0 0 11px 1px rgb(255, 202, 0);
#thank-you-container {
display: none;
margin-top: 20px;
padding: 5px;
border: solid lightgrey 1px;
background-color: rgba(20, 180, 20, 0.2);
border-radius: 4px;
button {
margin-right: 10px;
}
}
.failed {
border-radius: 50%;
background-color: #ff2600;
-webkit-box-shadow: 0 0 11px 1px rgba(222,0,0,1);
-moz-box-shadow: 0 0 11px 1px rgba(222,0,0,1);
box-shadow: 0 0 11px 1px rgba(222,0,0,1);
#thank-you-note {
width: 100%;
height: 200px;
}
}
#mark-as-solved-button {
margin-top: 20px;
}
#thank-you-container {
display: none;
margin-top: 20px;
padding: 5px;
border: solid lightgrey 1px;
background-color: rgba(20, 180, 20, 0.2);
border-radius: 4px;
button {
margin-right: 10px;
}
}
#thank-you-note {
width: 100%;
height: 200px;
}
#commentitor {

View File

@ -58,6 +58,10 @@ div.negative-result {
box-shadow: 0px 0px 11px 1px rgba(222,0,0,1);
}
tr.highlight {
border-top: 2px solid rgba(222,0,0,1);
}
/////////////////////////////////////////////////////////////////////////////////////////////
// StatisticsController:

View File

@ -326,6 +326,16 @@ class ExercisesController < ApplicationController
def statistics
if(@external_user)
@submissions = Submission.where("user_id = ? AND exercise_id = ?", @external_user.id, @exercise.id).order("created_at")
@submissions_and_interventions = (@submissions + UserExerciseIntervention.where("user_id = ? AND exercise_id = ?", @external_user.id, @exercise.id)).sort_by { |a| a.created_at }
deltas = @submissions.map.with_index do |item, index|
delta = item.created_at - @submissions[index - 1].created_at if index > 0
if delta == nil or delta > 10 * 60 then 0 else delta end
end
@working_times_until = []
@submissions_and_interventions.each_with_index do |submission, index|
@working_times_until.push((Time.at(deltas[1..index].inject(:+)).utc.strftime("%H:%M:%S") if index > 0))
end
render 'exercises/external_users/statistics'
else
user_statistics = {}

View File

@ -9,8 +9,9 @@
li = link_to(t('breadcrumbs.dashboard.show'), admin_dashboard_path)
li = link_to(t('breadcrumbs.statistics.show'), statistics_path)
li.divider
- models = [ExecutionEnvironment, Exercise, ExerciseCollection, ProxyExercise, Tag, Consumer, CodeHarborLink, UserExerciseFeedback,
ErrorTemplate, ErrorTemplateAttribute, ExternalUser, FileType, FileTemplate, InternalUser].sort_by {|model| model.model_name.human(count: 2) }
- models.each do |model|
- if policy(model).index?
li = link_to(model.model_name.human(count: 2), send(:"#{model.model_name.collection}_path"))
= render('navigation_submenu', title: t('activerecord.models.exercise.other'), models: [Exercise, ExerciseCollection, ProxyExercise, Tag], link: exercises_path)
= render('navigation_submenu', title: t('navigation.sections.users'), models: [InternalUser, ExternalUser])
= render('navigation_collection_link', model: ExecutionEnvironment)
= render('navigation_submenu', title: t('navigation.sections.errors'), models: [ErrorTemplate, ErrorTemplateAttribute])
= render('navigation_submenu', title: t('navigation.sections.files'), models: [FileType, FileTemplate])
= render('navigation_submenu', title: t('navigation.sections.integrations'), models: [Consumer, CodeHarborLink])

View File

@ -0,0 +1,2 @@
- if policy(model).index?
li = link_to(model.model_name.human(count: 2), send(:"#{model.model_name.collection}_path"))

View File

@ -0,0 +1,6 @@
li.dropdown.dropdown-submenu
- link = link.nil? ? "#" : link
a href=link class="dropdown-toggle" data-toggle="dropdown" = title
ul class="dropdown-menu"
- models.each do |model|
= render('navigation_collection_link', model: model)

View File

@ -1,19 +1,16 @@
h1 = "#{@exercise} (external user #{@external_user})"
- submissions = Submission.where("user_id = ? AND exercise_id = ?", @external_user.id, @exercise.id).order("created_at")
- current_submission = submissions.first
- submissions_and_interventions = (submissions + UserExerciseIntervention.where("user_id = ? AND exercise_id = ?", @external_user.id, @exercise.id)).sort_by { |a| a.created_at }
- current_submission = @submissions.first
- if current_submission
- initial_files = current_submission.files.to_a
- all_files = []
- file_types = Set.new()
- submissions.each do |submission|
- @submissions.each do |submission|
- submission.files.each do |file|
- file_types.add(ActiveSupport::JSON.encode(file.file_type))
- all_files.push(submission.files)
.hidden#data data-submissions=ActiveSupport::JSON.encode(submissions) data-files=ActiveSupport::JSON.encode(all_files) data-file-types=ActiveSupport::JSON.encode(file_types)
.hidden#data data-submissions=ActiveSupport::JSON.encode(@submissions) data-files=ActiveSupport::JSON.encode(all_files) data-file-types=ActiveSupport::JSON.encode(file_types)
#stats-editor.row
- index = 0
@ -27,14 +24,13 @@ h1 = "#{@exercise} (external user #{@external_user})"
button.btn.btn-default id='play-button'
span.fa.fa-play
#submissions-slider.flex-item
input type='range' orient='horizontal' list='datapoints' min=0 max=submissions.length-1 value=0
input type='range' orient='horizontal' list='datapoints' min=0 max=@submissions.length-1 value=0
datalist#datapoints
- index=0
- submissions.each do |submission|
- @submissions.each do |submission|
option data-submission=submission
=index
- index += 1
- working_times_until = Array.new
#timeline
.table-responsive
table.table
@ -43,28 +39,27 @@ h1 = "#{@exercise} (external user #{@external_user})"
- ['.time', '.cause', '.score', '.tests', '.time_difference'].each do |title|
th.header = t(title)
tbody
- deltas = submissions.map.with_index {|item, index| delta = item.created_at - submissions[index - 1].created_at if index > 0; if delta == nil or delta > 10*60 then 0 else delta end}
- submissions_and_interventions.each_with_index do |submission_or_intervention, index|
tr data-id=submission_or_intervention.id
td.clickable = submission_or_intervention.created_at.strftime("%F %T")
- if submission_or_intervention.is_a?(Submission)
td = submission_or_intervention.cause
td = submission_or_intervention.score
- @submissions_and_interventions.each_with_index do |this, index|
- highlight = (index > 0 and @working_times_until[index] == @working_times_until[index - 1] and this.created_at > @submissions_and_interventions[index - 1].created_at)
tr data-id=this.id class=('highlight' if highlight)
td.clickable = this.created_at.strftime("%F %T")
- if this.is_a?(Submission)
td = this.cause
td = this.score
td
-submission_or_intervention.testruns.each do |run|
-this.testruns.each do |run|
- if run.passed
.unit-test-result.positive-result title=run.output
- else
.unit-test-result.unknown-result title=run.output
td = Time.at(deltas[1..index].inject(:+)).utc.strftime("%H:%M:%S") if index > 0
-working_times_until.push((Time.at(deltas[1..index].inject(:+)).utc.strftime("%H:%M:%S") if index > 0))
- elsif submission_or_intervention.is_a? UserExerciseIntervention
td = submission_or_intervention.intervention.name
td = @working_times_until[index] if index > 0
- elsif this.is_a? UserExerciseIntervention
td = this.intervention.name
td =
td =
td =
p = t('.addendum')
.hidden#wtimes data-working_times=ActiveSupport::JSON.encode(working_times_until);
.hidden#wtimes data-working_times=ActiveSupport::JSON.encode(@working_times_until);
div#progress_chart.col-lg-12
.graph-functions-2

View File

@ -43,10 +43,20 @@
<% output_runs = testruns.select { |run| run.cause == 'run' } %>
<% if output_runs.size > 0 %>
<h5><%= t('request_for_comments.runtime_output') %></h5>
<div class="testrun-output text">
<span class="fa fa-chevron-up collapse-button"></span>
<div class="collapsed testrun-output text">
<span class="fa fa-chevron-down collapse-button"></span>
<% output_runs.each do |testrun| %>
<pre><%= testrun.try(:output) or t('request_for_comments.no_output') %></pre>
<%
output = testrun.try(:output)
if output
messages = output.scan(/{(?:(?:".+?":".+?")+?,?)+}/)
messages.map! {|el| JSON.parse(el)}
messages.keep_if {|message| message['cmd'] == 'write'}
messages.map! {|message| message['data']}
output = messages.join ''
end
%>
<pre><%= output or t('request_for_comments.no_output') %></pre>
<% end %>
</div>
<% end %>
@ -56,7 +66,13 @@
<h5><%= t('request_for_comments.test_results') %></h5>
<div class="testrun-assess-results">
<% assess_runs.each do |testrun| %>
<div class="result <%= testrun.passed ? 'passed' : 'failed' %>" title="<%= testrun.output %>"></div>
<div class="testrun-container">
<div class="result <%= testrun.passed ? 'passed' : 'failed' %>"></div>
<div class="collapsed testrun-output text">
<span class="fa fa-chevron-down collapse-button"></span>
<pre><%= testrun.output or t('request_for_comments.no_output')%></pre>
</div>
</div>
<% end %>
</div>
<% end %>

View File

@ -366,7 +366,7 @@ de:
score: Punktzahl
tests: Unit Tests
time_difference: 'Arbeitszeit bis hier*'
addendum: '* Differenzen von mehr als 30 Minuten werden ignoriert.'
addendum: '* Differenzen von mehr als 10 Minuten werden ignoriert.'
proxy_exercises:
index:
clone: Duplizieren
@ -770,3 +770,9 @@ de:
to: "Bis"
interval: "Intervall"
update: "Aktualisieren"
navigation:
sections:
errors: "Fehler"
files: "Dateien"
users: "Benutzer"
integrations: "Integrationen"

View File

@ -366,7 +366,7 @@ en:
score: Score
tests: Unit Test Results
time_difference: 'Working Time until here*'
addendum: '* Deltas longer than 30 minutes are ignored.'
addendum: '* Deltas longer than 10 minutes are ignored.'
proxy_exercises:
index:
clone: Duplicate
@ -770,3 +770,9 @@ en:
to: "To"
interval: "Interval"
update: "Update"
navigation:
sections:
errors: "Errors"
files: "Files"
users: "Users"
integrations: "Integrations"