diff --git a/app/assets/javascripts/bootstrap-dropdown-submenu.js b/app/assets/javascripts/bootstrap-dropdown-submenu.js new file mode 100644 index 00000000..40850c29 --- /dev/null +++ b/app/assets/javascripts/bootstrap-dropdown-submenu.js @@ -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); +}); diff --git a/app/assets/stylesheets/bootstrap-dropdown-submenu.css.scss b/app/assets/stylesheets/bootstrap-dropdown-submenu.css.scss new file mode 100644 index 00000000..d0855298 --- /dev/null +++ b/app/assets/stylesheets/bootstrap-dropdown-submenu.css.scss @@ -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; +} diff --git a/app/assets/stylesheets/request-for-comments.css.scss b/app/assets/stylesheets/request-for-comments.css.scss index 2cad6446..bf7f0cb4 100644 --- a/app/assets/stylesheets/request-for-comments.css.scss +++ b/app/assets/stylesheets/request-for-comments.css.scss @@ -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 { diff --git a/app/assets/stylesheets/statistics.css.scss b/app/assets/stylesheets/statistics.css.scss index 98bea652..2fc9d69d 100644 --- a/app/assets/stylesheets/statistics.css.scss +++ b/app/assets/stylesheets/statistics.css.scss @@ -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: diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index 7d4d2c43..a41a5794 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -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 = {} diff --git a/app/views/application/_navigation.html.slim b/app/views/application/_navigation.html.slim index 127e170c..ca0a8505 100644 --- a/app/views/application/_navigation.html.slim +++ b/app/views/application/_navigation.html.slim @@ -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]) diff --git a/app/views/application/_navigation_collection_link.html.slim b/app/views/application/_navigation_collection_link.html.slim new file mode 100644 index 00000000..412ea0bd --- /dev/null +++ b/app/views/application/_navigation_collection_link.html.slim @@ -0,0 +1,2 @@ +- if policy(model).index? + li = link_to(model.model_name.human(count: 2), send(:"#{model.model_name.collection}_path")) diff --git a/app/views/application/_navigation_submenu.html.slim b/app/views/application/_navigation_submenu.html.slim new file mode 100644 index 00000000..c12e04a4 --- /dev/null +++ b/app/views/application/_navigation_submenu.html.slim @@ -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) diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index 06132754..7d00f3b1 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -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 diff --git a/app/views/request_for_comments/show.html.erb b/app/views/request_for_comments/show.html.erb index d3751d0b..5f6a53f5 100644 --- a/app/views/request_for_comments/show.html.erb +++ b/app/views/request_for_comments/show.html.erb @@ -43,10 +43,20 @@ <% output_runs = testruns.select { |run| run.cause == 'run' } %> <% if output_runs.size > 0 %>
<%= t('request_for_comments.runtime_output') %>
-
- + <% end %> @@ -56,7 +66,13 @@
<%= t('request_for_comments.test_results') %>
<% assess_runs.each do |testrun| %> -
+
+
+ +
<% end %>
<% end %> diff --git a/config/locales/de.yml b/config/locales/de.yml index 3e9d0d75..f32c4d59 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -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" diff --git a/config/locales/en.yml b/config/locales/en.yml index 32baac46..80857c84 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -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"