merged master into disable_rfcs
This commit is contained in:
@@ -7,9 +7,11 @@
|
||||
ul.dropdown-menu role='menu'
|
||||
- if current_user.admin?
|
||||
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])
|
||||
|
@@ -0,0 +1,2 @@
|
||||
- if policy(model).index?
|
||||
li = link_to(model.model_name.human(count: 2), send(:"#{model.model_name.collection}_path"))
|
6
app/views/application/_navigation_submenu.html.slim
Normal file
6
app/views/application/_navigation_submenu.html.slim
Normal 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)
|
@@ -16,4 +16,5 @@
|
||||
.form-group
|
||||
= f.label(:hint)
|
||||
= f.text_field(:hint, class: 'form-control')
|
||||
.help-block == t('error_templates.hints.hint_templates')
|
||||
.actions = render('shared/submit_button', f: f, object: @error_template)
|
||||
|
@@ -1,11 +1,18 @@
|
||||
- exercises = Exercise.order(:title)
|
||||
- users = InternalUser.order(:name)
|
||||
|
||||
= form_for(@exercise_collection, data: {exercises: exercises}, multipart: true) do |f|
|
||||
= form_for(@exercise_collection, data: {exercises: exercises, users: users}, multipart: true) do |f|
|
||||
= render('shared/form_errors', object: @exercise_collection)
|
||||
.form-group
|
||||
= f.label(:name)
|
||||
= f.label(t('activerecord.attributes.exercise_collections.name'))
|
||||
= f.text_field(:name, class: 'form-control', required: true)
|
||||
.form-group
|
||||
= f.label(:exercises)
|
||||
= f.label(t('activerecord.attributes.exercise_collections.use_anomaly_detection'))
|
||||
= f.check_box(:use_anomaly_detection, {class: 'form-control'})
|
||||
.form-group
|
||||
= f.label(t('activerecord.attributes.exercise_collections.user'))
|
||||
= f.collection_select(:user_id, users, :id, :name, {}, {class: 'form-control'})
|
||||
.form-group
|
||||
= f.label(t('activerecord.attributes.exercise_collections.exercises'))
|
||||
= f.collection_select(:exercise_ids, exercises, :id, :title, {}, {class: 'form-control', multiple: true})
|
||||
.actions = render('shared/submit_button', f: f, object: @exercise_collection)
|
||||
|
@@ -8,7 +8,7 @@ h1 = ExerciseCollection.model_name.human(count: 2)
|
||||
th = t('activerecord.attributes.exercise_collections.name')
|
||||
th = t('activerecord.attributes.exercise_collections.updated_at')
|
||||
th = t('activerecord.attributes.exercise_collections.exercises')
|
||||
th colspan=3 = t('shared.actions')
|
||||
th colspan=4 = t('shared.actions')
|
||||
tbody
|
||||
- @exercise_collections.each do |collection|
|
||||
tr
|
||||
@@ -18,6 +18,7 @@ h1 = ExerciseCollection.model_name.human(count: 2)
|
||||
td = collection.exercises.size
|
||||
td = link_to(t('shared.show'), collection)
|
||||
td = link_to(t('shared.edit'), edit_exercise_collection_path(collection))
|
||||
td = link_to(t('shared.statistics'), statistics_exercise_collection_path(collection))
|
||||
td = link_to(t('shared.destroy'), collection, data: {confirm: t('shared.confirm_destroy')}, method: :delete)
|
||||
|
||||
= render('shared/pagination', collection: @exercise_collections)
|
||||
|
@@ -3,9 +3,25 @@ h1
|
||||
= render('shared/edit_button', object: @exercise_collection)
|
||||
|
||||
= row(label: 'exercise_collections.name', value: @exercise_collection.name)
|
||||
= row(label: 'exercise_collections.user', value: link_to(@exercise_collection.user.name, @exercise_collection.user)) unless @exercise_collection.user.nil?
|
||||
= row(label: 'exercise_collections.use_anomaly_detection', value: @exercise_collection.use_anomaly_detection)
|
||||
= row(label: 'exercise_collections.updated_at', value: @exercise_collection.updated_at)
|
||||
|
||||
h4 = t('activerecord.attributes.exercise_collections.exercises')
|
||||
ul.list-unstyled
|
||||
- @exercise_collection.exercises.sort_by{|c| c.title}.each do |exercise|
|
||||
li = link_to(exercise, exercise)
|
||||
.table-responsive
|
||||
table.table
|
||||
thead
|
||||
tr
|
||||
th = t('activerecord.attributes.exercise.title')
|
||||
th = t('activerecord.attributes.exercise.execution_environment')
|
||||
th = t('activerecord.attributes.exercise.user')
|
||||
th = t('shared.actions')
|
||||
tbody
|
||||
- @exercises.sort_by{|c| c.title}.each do |exercise|
|
||||
tr
|
||||
td = link_to(exercise.title, exercise)
|
||||
td = link_to_if(exercise.execution_environment && policy(exercise.execution_environment).show?, exercise.execution_environment, exercise.execution_environment)
|
||||
td = exercise.user.name
|
||||
td = link_to(t('shared.statistics'), statistics_exercise_path(exercise))
|
||||
|
||||
= render('shared/pagination', collection: @exercises)
|
||||
|
17
app/views/exercise_collections/statistics.html.slim
Normal file
17
app/views/exercise_collections/statistics.html.slim
Normal file
@@ -0,0 +1,17 @@
|
||||
h1 = @exercise_collection
|
||||
|
||||
= row(label: 'exercise_collections.name', value: @exercise_collection.name)
|
||||
= row(label: 'exercise_collections.updated_at', value: @exercise_collection.updated_at)
|
||||
= row(label: 'exercise_collections.exercises', value: @exercise_collection.exercises.count)
|
||||
= row(label: 'exercises.statistics.average_worktime', value: @exercise_collection.average_working_time.round(3).to_s + 's')
|
||||
|
||||
#graph
|
||||
#data.hidden(data-working-times=ActiveSupport::JSON.encode(@exercise_collection.exercise_working_times) data-average-working-time=@exercise_collection.average_working_time)
|
||||
#legend
|
||||
- {time: t('exercises.statistics.average_worktime'),
|
||||
min: 'min. anomaly threshold',
|
||||
avg: 'average time',
|
||||
max: 'max. anomaly threshold'}.each_pair do |klass, label|
|
||||
.legend-entry
|
||||
div(class="box #{klass}")
|
||||
.box-label = label
|
@@ -47,9 +47,12 @@ div id='output_sidebar_uncollapsed' class='hidden col-sm-12 enforce-bottom-margi
|
||||
input#prompt-input.form-control type='text'
|
||||
span.input-group-btn
|
||||
button#prompt-submit.btn.btn-primary type="button" = t('exercises.editor.send')
|
||||
#error-hints
|
||||
.heading = t('exercises.implement.error_hints.heading')
|
||||
ul.body
|
||||
#output
|
||||
pre = t('exercises.implement.no_output_yet')
|
||||
- if CodeOcean::Config.new(:code_ocean).read[:flowr][:enabled]
|
||||
#flowrHint.panel.panel-info data-url=CodeOcean::Config.new(:code_ocean).read[:flowr][:url] role='tab'
|
||||
.panel-heading = 'Gain more insights here'
|
||||
.panel-body
|
||||
.panel-body
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -2,9 +2,15 @@ script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"
|
||||
h1 = @exercise
|
||||
|
||||
= row(label: '.participants', value: @exercise.users.distinct.count)
|
||||
|
||||
- [:intermediate, :final].each do |scope|
|
||||
= row(label: ".#{scope}_submissions") do
|
||||
= "#{@exercise.submissions.send(scope).count} (#{t('.users', count: @exercise.submissions.send(scope).distinct.count(:user_id))})"
|
||||
|
||||
= row(label: '.finishing_rate') do
|
||||
p == @exercise.finishers.count ? "#{t('shared.out_of', maximum_value: @exercise.users.distinct.count, value: @exercise.finishers.count)} #{t('exercises.statistics.external_users')}" : empty
|
||||
p = progress_bar((100.0 / @exercise.users.distinct.count * @exercise.finishers.count).round(2))
|
||||
|
||||
= row(label: '.average_score') do
|
||||
p == @exercise.average_score ? t('shared.out_of', maximum_value: @exercise.maximum_score, value: @exercise.average_score.round(2)) : empty
|
||||
p = progress_bar(@exercise.average_percentage)
|
||||
@@ -40,4 +46,4 @@ h1 = @exercise
|
||||
td = link_to_if symbol==:external_users, label, {controller: "exercises", action: "statistics", external_user_id: user.id, id: @exercise.id}
|
||||
td = us['maximum_score'] or 0
|
||||
td = us['runs']
|
||||
td = @exercise.average_working_time_for(user.id) or 0
|
||||
td = @exercise.average_working_time_for(user.id) or 0
|
||||
|
@@ -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 %>
|
||||
|
22
app/views/statistics/activity_history.html.slim
Normal file
22
app/views/statistics/activity_history.html.slim
Normal file
@@ -0,0 +1,22 @@
|
||||
- content_for :head do
|
||||
= javascript_include_tag(asset_path('vis.min.js', type: :javascript))
|
||||
= stylesheet_link_tag(asset_path('vis.min.css', type: :stylesheet))
|
||||
|
||||
.group
|
||||
.title
|
||||
h1 = t("statistics.graphs.#{resource}_activity")
|
||||
.spinner
|
||||
.graph id="#{resource}-activity-history"
|
||||
form
|
||||
.form-group
|
||||
label for="from-date" = t('.from')
|
||||
input type="date" class="form-control" id="from-date" name="from" value=(params[:from] || DateTime.new(2016).to_date)
|
||||
.form-group
|
||||
label for="to-date" = t('.to')
|
||||
input type="date" class="form-control" id="to-date" name="to" value=(params[:to] || DateTime.now.to_date)
|
||||
.form-group
|
||||
label for="interval" = t('.interval')
|
||||
select class="form-control" id="interval" name="interval"
|
||||
= [:year, :quarter, :month, :day, :hour, :minute, :second].each do | key |
|
||||
option selected=(key.to_s == params[:interval]) = key
|
||||
button type="submit" class="btn btn-primary" = t('.update')
|
17
app/views/statistics/graphs.html.slim
Normal file
17
app/views/statistics/graphs.html.slim
Normal file
@@ -0,0 +1,17 @@
|
||||
- content_for :head do
|
||||
= javascript_include_tag(asset_path('vis.min.js', type: :javascript))
|
||||
= stylesheet_link_tag(asset_path('vis.min.css', type: :stylesheet))
|
||||
|
||||
.group
|
||||
.title
|
||||
h1 = t('.user_activity')
|
||||
a href=statistics_graphs_user_activity_history_path = t('.history')
|
||||
.spinner
|
||||
.graph#user-activity
|
||||
|
||||
.group
|
||||
.title
|
||||
h1 = t('.rfc_activity')
|
||||
a href=statistics_graphs_rfc_activity_history_path = t('.history')
|
||||
.spinner
|
||||
.graph#rfc-activity
|
12
app/views/statistics/show.html.slim
Normal file
12
app/views/statistics/show.html.slim
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
#statistics-container
|
||||
- statistics_data.each do | section |
|
||||
h2 = section[:name]
|
||||
.statistics-wrapper data-key=section[:key]
|
||||
- section[:entries].each do | entry |
|
||||
a href=entry[:url]
|
||||
div data-key=entry[:key]
|
||||
.title = entry[:name]
|
||||
.data
|
||||
span = entry[:data].to_s
|
||||
span.unit = entry[:unit] if entry.key? :unit
|
38
app/views/user_mailer/exercise_anomaly_detected.html.slim
Normal file
38
app/views/user_mailer/exercise_anomaly_detected.html.slim
Normal file
@@ -0,0 +1,38 @@
|
||||
== t('mailers.user_mailer.exercise_anomaly_detected.body1',
|
||||
receiver_displayname: @receiver_displayname,
|
||||
collection_name: @collection.name)
|
||||
|
||||
table(border=1)
|
||||
thead
|
||||
tr
|
||||
td = t('activerecord.attributes.exercise.title', locale: :de)
|
||||
td = t('exercises.statistics.average_worktime', locale: :de)
|
||||
td = t('shared.actions', locale: :de)
|
||||
tbody
|
||||
- @anomalies.keys.each do | id |
|
||||
- exercise = Exercise.find(id)
|
||||
tr
|
||||
td = link_to(exercise.title, exercise_path(exercise))
|
||||
td = @anomalies[id]
|
||||
td = link_to(t('shared.statistics', locale: :de), statistics_exercise_path(exercise))
|
||||
|
||||
|
||||
== t('mailers.user_mailer.exercise_anomaly_detected.body2',
|
||||
receiver_displayname: @receiver_displayname,
|
||||
collection_name: @collection.name)
|
||||
|
||||
table(border=1)
|
||||
thead
|
||||
tr
|
||||
td = t('activerecord.attributes.exercise.title', locale: :en)
|
||||
td = t('exercises.statistics.average_worktime', locale: :en)
|
||||
td = t('shared.actions', locale: :en)
|
||||
tbody
|
||||
- @anomalies.keys.each do | id |
|
||||
- exercise = Exercise.find(id)
|
||||
tr
|
||||
td = link_to(exercise.title, exercise_path(exercise))
|
||||
td = @anomalies[id]
|
||||
td = link_to(t('shared.statistics', locale: :en), statistics_exercise_path(exercise))
|
||||
|
||||
== t('mailers.user_mailer.exercise_anomaly_detected.body3')
|
@@ -0,0 +1 @@
|
||||
== t('mailers.user_mailer.exercise_anomaly_needs_feedback.body', receiver_displayname: @receiver_displayname, exercise: @exercise_title, link: link_to(@link, @link))
|
Reference in New Issue
Block a user