From b20d13f86675cd99d53e9a29eba48689306ef08e Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 29 Oct 2015 10:15:40 +0100 Subject: [PATCH 01/28] Add statistics for exercises per user --- .gitignore | 1 + app/views/exercises/statistics.html.slim | 15 +++++++++++++++ config/locales/de.yml | 4 ++++ config/locales/en.yml | 4 ++++ 4 files changed, 24 insertions(+) diff --git a/.gitignore b/.gitignore index 4e183355..89d0de42 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ /rubocop.html /tmp /vagrant/ +/.vagrant *.sublime-* /.idea /.vagrant diff --git a/app/views/exercises/statistics.html.slim b/app/views/exercises/statistics.html.slim index 77ab091d..c044004a 100644 --- a/app/views/exercises/statistics.html.slim +++ b/app/views/exercises/statistics.html.slim @@ -7,3 +7,18 @@ h1 = @exercise = 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) + +.table-responsive + table.table + thead + tr + - ['.user', '.score', '.runs', '.worktime'].each do |title| + th.header = t(title) + tbody + - @exercise.users.each do |user| + tr + - submissions = @exercise.submissions.where('user_id=?', user.id) + td = "#{user.name} (#{user.email})" + td = submissions.maximum('score') + td = submissions.count('id') + td = "#{((submissions.maximum('created_at') - submissions.minimum('created_at')) / 60).round(2)} min" diff --git a/config/locales/de.yml b/config/locales/de.yml index aaedd678..871decd9 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -239,6 +239,10 @@ de: intermediate_submissions: Intermediäre Abgaben participants: Bearbeitende Nutzer users: '%{count} verschiedene Nutzer' + user: 'Nutzer' + score: 'Punktzahl' + runs: 'Versuche' + worktime: 'Arbeitszeit' submit: failure: Beim Übermitteln Ihrer Punktzahl ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut. files: diff --git a/config/locales/en.yml b/config/locales/en.yml index 5baed464..7c28ae29 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -239,6 +239,10 @@ en: intermediate_submissions: Intermediate Submissions participants: Participating Users users: '%{count} distinct users' + user: 'User' + score: 'Score' + runs: 'Runs' + worktime: 'Worktime' submit: failure: An error occured while transmitting your score. Please try again later. files: From 22da25be60312de478850c77fcc449008c68f6c5 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 29 Oct 2015 10:45:43 +0100 Subject: [PATCH 02/28] Output time in readable format --- app/views/exercises/statistics.html.slim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/exercises/statistics.html.slim b/app/views/exercises/statistics.html.slim index c044004a..d76f7b61 100644 --- a/app/views/exercises/statistics.html.slim +++ b/app/views/exercises/statistics.html.slim @@ -21,4 +21,5 @@ h1 = @exercise td = "#{user.name} (#{user.email})" td = submissions.maximum('score') td = submissions.count('id') - td = "#{((submissions.maximum('created_at') - submissions.minimum('created_at')) / 60).round(2)} min" + - seconds = submissions.maximum('created_at') - submissions.minimum('created_at') + td = "#{distance_of_time_in_words(seconds)} (#{seconds})" From 78422647fe40b007b3942cd142b9936193c4b0a8 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 29 Oct 2015 14:19:31 +0100 Subject: [PATCH 03/28] Add execution environment statistics --- Gemfile.lock | 3 --- .../execution_environments_controller.rb | 5 +++- app/policies/execution_environment_policy.rb | 2 +- .../execution_environments/index.html.slim | 3 ++- .../statistics.html.slim | 23 +++++++++++++++++++ config/locales/de.yml | 5 ++++ config/locales/en.yml | 13 +++++++---- config/routes.rb | 1 + .../execution_environments_controller_spec.rb | 8 +++++++ 9 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 app/views/execution_environments/statistics.html.slim diff --git a/Gemfile.lock b/Gemfile.lock index d27789ea..0492a3e1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -392,6 +392,3 @@ DEPENDENCIES uglifier (>= 1.3.0) web-console (~> 2.0) will_paginate (~> 3.0) - -BUNDLED WITH - 1.10.6 diff --git a/app/controllers/execution_environments_controller.rb b/app/controllers/execution_environments_controller.rb index 5e69f8fc..574aaed7 100644 --- a/app/controllers/execution_environments_controller.rb +++ b/app/controllers/execution_environments_controller.rb @@ -2,7 +2,7 @@ class ExecutionEnvironmentsController < ApplicationController include CommonBehavior before_action :set_docker_images, only: [:create, :edit, :new, :update] - before_action :set_execution_environment, only: MEMBER_ACTIONS + [:execute_command, :shell] + before_action :set_execution_environment, only: MEMBER_ACTIONS + [:execute_command, :shell, :statistics] before_action :set_testing_framework_adapters, only: [:create, :edit, :new, :update] def authorize! @@ -28,6 +28,9 @@ class ExecutionEnvironmentsController < ApplicationController render(json: @docker_client.execute_arbitrary_command(params[:command])) end + def statistics + end + def execution_environment_params params[:execution_environment].permit(:docker_image, :exposed_ports, :editor_mode, :file_extension, :file_type_id, :help, :indent_size, :memory_limit, :name, :network_enabled, :permitted_execution_time, :pool_size, :run_command, :test_command, :testing_framework).merge(user_id: current_user.id, user_type: current_user.class.name) end diff --git a/app/policies/execution_environment_policy.rb b/app/policies/execution_environment_policy.rb index 3766f714..bccc9a07 100644 --- a/app/policies/execution_environment_policy.rb +++ b/app/policies/execution_environment_policy.rb @@ -4,7 +4,7 @@ class ExecutionEnvironmentPolicy < AdminOrAuthorPolicy end private :author? - [:execute_command?, :shell?].each do |action| + [:execute_command?, :shell?, :statistics?].each do |action| define_method(action) { admin? || author? } end end diff --git a/app/views/execution_environments/index.html.slim b/app/views/execution_environments/index.html.slim index 7ed2cebb..0ccb880c 100644 --- a/app/views/execution_environments/index.html.slim +++ b/app/views/execution_environments/index.html.slim @@ -9,7 +9,7 @@ h1 = ExecutionEnvironment.model_name.human(count: 2) th = t('activerecord.attributes.execution_environment.memory_limit') th = t('activerecord.attributes.execution_environment.network_enabled') th = t('activerecord.attributes.execution_environment.permitted_execution_time') - th colspan=4 = t('shared.actions') + th colspan=5 = t('shared.actions') th colspan=2 = t('shared.resources') tbody - @execution_environments.each do |execution_environment| @@ -23,6 +23,7 @@ h1 = ExecutionEnvironment.model_name.human(count: 2) td = link_to(t('shared.edit'), edit_execution_environment_path(execution_environment)) td = link_to(t('shared.destroy'), execution_environment, data: {confirm: t('shared.confirm_destroy')}, method: :delete) td = link_to(t('.shell'), shell_execution_environment_path(execution_environment)) + td = link_to(t('shared.statistics'), statistics_execution_environment_path(execution_environment)) td = link_to(t('activerecord.models.error.other'), execution_environment_errors_path(execution_environment.id)) td = link_to(t('activerecord.models.hint.other'), execution_environment_hints_path(execution_environment.id)) diff --git a/app/views/execution_environments/statistics.html.slim b/app/views/execution_environments/statistics.html.slim new file mode 100644 index 00000000..111c6d29 --- /dev/null +++ b/app/views/execution_environments/statistics.html.slim @@ -0,0 +1,23 @@ +h1 = @execution_environment + +.table-responsive + table.table + thead + tr + - ['.exercise', '.score', '.runs', '.worktime'].each do |title| + th.header = t(title) + tbody + - @execution_environment.exercises.each do |exercise| + tr + - submissions = exercise.submissions + td = exercise.title + td = submissions.average(:score) + td = submissions.count() + - minima = submissions.group(:user_id).minimum(:created_at) + - maxima = submissions.group(:user_id).maximum(:created_at) + - result = 0 + - results = {} + - maxima.each {|key, value| results[key] = value - minima[key]} + - results.values.map {|value| result += value} + - result /= results.size if results.size > 0 + td = distance_of_time_in_words(result) diff --git a/config/locales/de.yml b/config/locales/de.yml index 9716a9a0..12b3df92 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -167,6 +167,11 @@ de: shell: command: Befehl headline: Shell + statistics: + exercise: Übung + score: Durchschnittliche Punktzahl + runs: Durchschnittliche Anzahl von Versuchen + worktime: Durchschnittliche Arbeitszeit exercises: editor: confirm_start_over: Wollen Sie wirklich von vorne anfangen? diff --git a/config/locales/en.yml b/config/locales/en.yml index 164082df..771d5a00 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -167,6 +167,11 @@ en: shell: command: Command headline: Shell + statistics: + exercise: Exercise + score: Average Score + runs: Average Number of Runs + worktime: Average Worktime exercises: editor: confirm_start_over: Do you really want to start over? @@ -239,10 +244,10 @@ en: intermediate_submissions: Intermediate Submissions participants: Participating Users users: '%{count} distinct users' - user: 'User' - score: 'Score' - runs: 'Runs' - worktime: 'Worktime' + user: User + score: Score + runs: Runs + worktime: Worktime submit: failure: An error occured while transmitting your score. Please try again later. files: diff --git a/config/routes.rb b/config/routes.rb index 1616d9b8..7bb17297 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -26,6 +26,7 @@ Rails.application.routes.draw do member do get :shell post 'shell', as: :execute_command, to: :execute_command + get :statistics end resources :errors, only: [:create, :index, :show] diff --git a/spec/controllers/execution_environments_controller_spec.rb b/spec/controllers/execution_environments_controller_spec.rb index 331c3ec7..2abf59ef 100644 --- a/spec/controllers/execution_environments_controller_spec.rb +++ b/spec/controllers/execution_environments_controller_spec.rb @@ -130,6 +130,14 @@ describe ExecutionEnvironmentsController do expect_template(:shell) end + describe 'GET #statistics' do + before(:each) { get :statistics, id: execution_environment.id } + + expect_assigns(execution_environment: :execution_environment) + expect_status(200) + expect_template(:statistics) + end + describe 'GET #show' do before(:each) { get :show, id: execution_environment.id } From c5dd1e712b84b10b515e07170f9e52f4ee5add3e Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 29 Oct 2015 14:21:26 +0100 Subject: [PATCH 04/28] Fix users being shown multiple times --- app/views/exercises/statistics.html.slim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/exercises/statistics.html.slim b/app/views/exercises/statistics.html.slim index d76f7b61..3af02b10 100644 --- a/app/views/exercises/statistics.html.slim +++ b/app/views/exercises/statistics.html.slim @@ -15,7 +15,7 @@ h1 = @exercise - ['.user', '.score', '.runs', '.worktime'].each do |title| th.header = t(title) tbody - - @exercise.users.each do |user| + - @exercise.users.distinct().each do |user| tr - submissions = @exercise.submissions.where('user_id=?', user.id) td = "#{user.name} (#{user.email})" From c41818e3247c123ef594e34397308aecf4fb935d Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 29 Oct 2015 15:08:02 +0100 Subject: [PATCH 05/28] Push statistics features to model --- app/models/exercise.rb | 9 +++++++-- .../execution_environments/statistics.html.slim | 14 +++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/app/models/exercise.rb b/app/models/exercise.rb index 152d79bf..b6ff3bd1 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -23,14 +23,19 @@ class Exercise < ActiveRecord::Base validates :token, presence: true, uniqueness: true def average_percentage - (average_score/ maximum_score * 100).round if average_score + (average_score / maximum_score * 100).round if average_score end def average_score if submissions.exists?(cause: 'submit') maximum_scores_query = submissions.select('MAX(score) AS maximum_score').where(cause: 'submit').group(:user_id).to_sql.sub('$1', id.to_s) self.class.connection.execute("SELECT AVG(maximum_score) AS average_score FROM (#{maximum_scores_query}) AS maximum_scores").first['average_score'].to_f - end + else 0 end + end + + def average_working_time + working_time_query = submissions.select('MAX(created_at) - MIN(created_at) AS time').group(:user_id).to_sql.sub('$1', id.to_s) + self.class.connection.execute("SELECT AVG(time) AS average_time FROM (#{working_time_query}) AS working_times").first['average_time'] end def duplicate(attributes = {}) diff --git a/app/views/execution_environments/statistics.html.slim b/app/views/execution_environments/statistics.html.slim index 111c6d29..38112c4f 100644 --- a/app/views/execution_environments/statistics.html.slim +++ b/app/views/execution_environments/statistics.html.slim @@ -9,15 +9,7 @@ h1 = @execution_environment tbody - @execution_environment.exercises.each do |exercise| tr - - submissions = exercise.submissions td = exercise.title - td = submissions.average(:score) - td = submissions.count() - - minima = submissions.group(:user_id).minimum(:created_at) - - maxima = submissions.group(:user_id).maximum(:created_at) - - result = 0 - - results = {} - - maxima.each {|key, value| results[key] = value - minima[key]} - - results.values.map {|value| result += value} - - result /= results.size if results.size > 0 - td = distance_of_time_in_words(result) + td = exercise.average_score + td = exercise.submissions.count() + td = exercise.average_working_time From 7baff6ddaf568493d6b66ce32ae61827c456e49a Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Sun, 1 Nov 2015 14:47:52 +0100 Subject: [PATCH 06/28] Group statistics per user type --- app/models/exercise.rb | 6 ++++- app/views/exercises/statistics.html.slim | 30 +++++++++++++----------- config/locales/de.yml | 10 ++++---- config/locales/en.yml | 2 ++ 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/app/models/exercise.rb b/app/models/exercise.rb index b6ff3bd1..91a9c2b2 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -11,7 +11,10 @@ class Exercise < ActiveRecord::Base belongs_to :execution_environment has_many :submissions belongs_to :team - has_many :users, source_type: ExternalUser, through: :submissions + + has_many :external_users, source: :user, source_type: ExternalUser, through: :submissions + has_many :internal_users, source: :user, source_type: InternalUser, through: :submissions + alias_method :users, :external_users scope :with_submissions, -> { where('id IN (SELECT exercise_id FROM submissions)') } @@ -22,6 +25,7 @@ class Exercise < ActiveRecord::Base validates :title, presence: true validates :token, presence: true, uniqueness: true + def average_percentage (average_score / maximum_score * 100).round if average_score end diff --git a/app/views/exercises/statistics.html.slim b/app/views/exercises/statistics.html.slim index 3af02b10..99e6ead2 100644 --- a/app/views/exercises/statistics.html.slim +++ b/app/views/exercises/statistics.html.slim @@ -8,18 +8,20 @@ h1 = @exercise 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) -.table-responsive - table.table - thead - tr - - ['.user', '.score', '.runs', '.worktime'].each do |title| - th.header = t(title) - tbody - - @exercise.users.distinct().each do |user| +- Hash[:internal_users => t('.internal_users'), :external_users => t('.external_users')].each_pair do |symbol, label| + strong = label + .table-responsive + table.table + thead tr - - submissions = @exercise.submissions.where('user_id=?', user.id) - td = "#{user.name} (#{user.email})" - td = submissions.maximum('score') - td = submissions.count('id') - - seconds = submissions.maximum('created_at') - submissions.minimum('created_at') - td = "#{distance_of_time_in_words(seconds)} (#{seconds})" + - ['.user', '.score', '.runs', '.worktime'].each do |title| + th.header = t(title) + tbody + - @exercise.send(symbol).distinct().each do |user| + tr + - submissions = @exercise.submissions.where('user_id=?', user.id) + td = "#{user.name} (#{user.email})" + td = submissions.maximum('score') + td = submissions.count('id') + - seconds = submissions.maximum('created_at') - submissions.minimum('created_at') + td = "#{distance_of_time_in_words(seconds)} (#{seconds})" diff --git a/config/locales/de.yml b/config/locales/de.yml index 12b3df92..d445aa40 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -244,10 +244,12 @@ de: intermediate_submissions: Intermediäre Abgaben participants: Bearbeitende Nutzer users: '%{count} verschiedene Nutzer' - user: 'Nutzer' - score: 'Punktzahl' - runs: 'Versuche' - worktime: 'Arbeitszeit' + user: Nutzer + score: Punktzahl + runs: Versuche + worktime: Arbeitszeit + internal_users: Interne Nutzer + external_user: Externe Nutzer submit: failure: Beim Übermitteln Ihrer Punktzahl ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut. files: diff --git a/config/locales/en.yml b/config/locales/en.yml index 771d5a00..07d5b6b5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -248,6 +248,8 @@ en: score: Score runs: Runs worktime: Worktime + internal_users: Internal Users + external_users: External Users submit: failure: An error occured while transmitting your score. Please try again later. files: From baed5bcc038b5b36950b564b453da1eaf87e6638 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Sun, 1 Nov 2015 15:54:01 +0100 Subject: [PATCH 07/28] Add first draft of cleansed average working time --- app/models/exercise.rb | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/app/models/exercise.rb b/app/models/exercise.rb index 91a9c2b2..3546c7e7 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -38,8 +38,23 @@ class Exercise < ActiveRecord::Base end def average_working_time - working_time_query = submissions.select('MAX(created_at) - MIN(created_at) AS time').group(:user_id).to_sql.sub('$1', id.to_s) - self.class.connection.execute("SELECT AVG(time) AS average_time FROM (#{working_time_query}) AS working_times").first['average_time'] + self.class.connection.execute(""" + SELECT avg(working_time) as average_time + FROM + (SELECT user_id, + sum(working_time_new) AS working_time + FROM + (SELECT user_id, + CASE WHEN working_time >= '0:30:00' THEN '0' ELSE working_time END AS working_time_new + FROM + (SELECT user_id, + id, + (created_at - lag(created_at) over (PARTITION BY user_id + ORDER BY id)) AS working_time + FROM submissions + WHERE exercise_id=#{id}) AS foo) AS bar + GROUP BY user_id) AS baz; + """).first['average_time'] end def duplicate(attributes = {}) From d4031c363fd5006bbefa66a091d7560efeb71b48 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 5 Nov 2015 10:57:07 +0100 Subject: [PATCH 08/28] Fix number of runs --- app/models/exercise.rb | 5 +++++ app/views/execution_environments/statistics.html.slim | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/models/exercise.rb b/app/models/exercise.rb index 3546c7e7..e7dba16d 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -37,6 +37,11 @@ class Exercise < ActiveRecord::Base else 0 end end + def average_number_of_submissions + user_count = internal_users.distinct.count + external_users.distinct.count + return user_count == 0 ? 0 : submissions.count() / user_count.to_f() + end + def average_working_time self.class.connection.execute(""" SELECT avg(working_time) as average_time diff --git a/app/views/execution_environments/statistics.html.slim b/app/views/execution_environments/statistics.html.slim index 38112c4f..ae2bf4f8 100644 --- a/app/views/execution_environments/statistics.html.slim +++ b/app/views/execution_environments/statistics.html.slim @@ -11,5 +11,5 @@ h1 = @execution_environment tr td = exercise.title td = exercise.average_score - td = exercise.submissions.count() + td = exercise.average_number_of_submissions td = exercise.average_working_time From f3acc426ac4fa2292d6e6085e632d223e714e478 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 5 Nov 2015 11:17:45 +0100 Subject: [PATCH 09/28] Refactor average worktime and add it to exercise statistics per user --- app/models/exercise.rb | 39 ++++++++++++++++-------- app/views/exercises/statistics.html.slim | 3 +- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/app/models/exercise.rb b/app/models/exercise.rb index e7dba16d..192c4513 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -42,26 +42,39 @@ class Exercise < ActiveRecord::Base return user_count == 0 ? 0 : submissions.count() / user_count.to_f() end + def user_working_time_query + """ + SELECT user_id, + sum(working_time_new) AS working_time + FROM + (SELECT user_id, + CASE WHEN working_time >= '0:30:00' THEN '0' ELSE working_time END AS working_time_new + FROM + (SELECT user_id, + id, + (created_at - lag(created_at) over (PARTITION BY user_id + ORDER BY id)) AS working_time + FROM submissions + WHERE exercise_id=#{id}) AS foo) AS bar + GROUP BY user_id + """ + end + def average_working_time self.class.connection.execute(""" SELECT avg(working_time) as average_time FROM - (SELECT user_id, - sum(working_time_new) AS working_time - FROM - (SELECT user_id, - CASE WHEN working_time >= '0:30:00' THEN '0' ELSE working_time END AS working_time_new - FROM - (SELECT user_id, - id, - (created_at - lag(created_at) over (PARTITION BY user_id - ORDER BY id)) AS working_time - FROM submissions - WHERE exercise_id=#{id}) AS foo) AS bar - GROUP BY user_id) AS baz; + (#{user_working_time_query}) AS baz; """).first['average_time'] end + def average_working_time_for(user_id) + self.class.connection.execute(""" + #{user_working_time_query} + HAVING user_id = #{user_id} + """).first['working_time'] + end + def duplicate(attributes = {}) exercise = dup exercise.attributes = attributes diff --git a/app/views/exercises/statistics.html.slim b/app/views/exercises/statistics.html.slim index 99e6ead2..f4a9dde5 100644 --- a/app/views/exercises/statistics.html.slim +++ b/app/views/exercises/statistics.html.slim @@ -23,5 +23,4 @@ h1 = @exercise td = "#{user.name} (#{user.email})" td = submissions.maximum('score') td = submissions.count('id') - - seconds = submissions.maximum('created_at') - submissions.minimum('created_at') - td = "#{distance_of_time_in_words(seconds)} (#{seconds})" + td = @exercise.average_working_time_for(user.id) or 0 From 0ba30c10ddb2aae313c727f30b209794384bad1f Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 5 Nov 2015 11:26:30 +0100 Subject: [PATCH 10/28] Use default value for missing scores --- app/views/exercises/statistics.html.slim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/exercises/statistics.html.slim b/app/views/exercises/statistics.html.slim index f4a9dde5..f2e4e784 100644 --- a/app/views/exercises/statistics.html.slim +++ b/app/views/exercises/statistics.html.slim @@ -21,6 +21,6 @@ h1 = @exercise tr - submissions = @exercise.submissions.where('user_id=?', user.id) td = "#{user.name} (#{user.email})" - td = submissions.maximum('score') + td = submissions.maximum('score') or 0 td = submissions.count('id') td = @exercise.average_working_time_for(user.id) or 0 From df1a77b665a13de39a3f965c96d92a055c0db559 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 19 Nov 2015 16:00:06 +0100 Subject: [PATCH 11/28] Add average working time to exercise statistics --- app/views/exercises/statistics.html.slim | 3 +++ config/locales/de.yml | 1 + config/locales/en.yml | 5 +++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/views/exercises/statistics.html.slim b/app/views/exercises/statistics.html.slim index f2e4e784..28893b6c 100644 --- a/app/views/exercises/statistics.html.slim +++ b/app/views/exercises/statistics.html.slim @@ -8,6 +8,9 @@ h1 = @exercise 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) += row(label: '.average_worktime') do + p = @exercise.average_working_time + - Hash[:internal_users => t('.internal_users'), :external_users => t('.external_users')].each_pair do |symbol, label| strong = label .table-responsive diff --git a/config/locales/de.yml b/config/locales/de.yml index d445aa40..eb44cab3 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -248,6 +248,7 @@ de: score: Punktzahl runs: Versuche worktime: Arbeitszeit + average_worktime: Durchschnittliche Arbeitszeit internal_users: Interne Nutzer external_user: Externe Nutzer submit: diff --git a/config/locales/en.yml b/config/locales/en.yml index 07d5b6b5..5a36aefc 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -171,7 +171,7 @@ en: exercise: Exercise score: Average Score runs: Average Number of Runs - worktime: Average Worktime + worktime: Average Working Time exercises: editor: confirm_start_over: Do you really want to start over? @@ -247,7 +247,8 @@ en: user: User score: Score runs: Runs - worktime: Worktime + worktime: Working Time + average_worktime: Average Working Time internal_users: Internal Users external_users: External Users submit: From 95ec5b0231281443b23156d5ea53521fe1c54684 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Wed, 25 Nov 2015 18:47:49 +0100 Subject: [PATCH 12/28] Routes, empty views and controller changes for statistics concerning external_users, exercises and external_users having exercises --- app/controllers/exercises_controller.rb | 14 ++++++++++++++ app/controllers/external_users_controller.rb | 6 ++++++ app/policies/external_user_policy.rb | 3 +++ .../exercises/external_users/statistics.html.slim | 1 + app/views/external_users/statistics.html.slim | 2 ++ config/routes.rb | 11 ++++++++++- 6 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 app/views/exercises/external_users/statistics.html.slim create mode 100644 app/views/external_users/statistics.html.slim diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index 7a26627a..4f5e5b81 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -7,6 +7,7 @@ class ExercisesController < ApplicationController before_action :handle_file_uploads, only: [:create, :update] before_action :set_execution_environments, only: [:create, :edit, :new, :update] before_action :set_exercise, only: MEMBER_ACTIONS + [:clone, :implement, :run, :statistics, :submit, :reload] + before_action :set_external_user, only: [:statistics] before_action :set_file_types, only: [:create, :edit, :new, :update] before_action :set_teams, only: [:create, :edit, :new, :update] @@ -125,6 +126,14 @@ class ExercisesController < ApplicationController end private :set_exercise + def set_external_user + if params[:external_user_id] + @external_user = ExternalUser.find(params[:external_user_id]) + authorize! + end + end + private :set_exercise + def set_file_types @file_types = FileType.all.order(:name) end @@ -143,6 +152,11 @@ class ExercisesController < ApplicationController end def statistics + if(@external_user) + render 'exercises/external_users/statistics' + else + render 'exercises/statistics' + end end def submit diff --git a/app/controllers/external_users_controller.rb b/app/controllers/external_users_controller.rb index 1d22dc58..26af4fbf 100644 --- a/app/controllers/external_users_controller.rb +++ b/app/controllers/external_users_controller.rb @@ -13,4 +13,10 @@ class ExternalUsersController < ApplicationController @user = ExternalUser.find(params[:id]) authorize! end + + def statistics + @user = ExternalUser.find(params[:id]) + authorize! + end + end diff --git a/app/policies/external_user_policy.rb b/app/policies/external_user_policy.rb index 6f638a8e..2e11060b 100644 --- a/app/policies/external_user_policy.rb +++ b/app/policies/external_user_policy.rb @@ -1,2 +1,5 @@ class ExternalUserPolicy < AdminOnlyPolicy + def statistics? + admin? + end end diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim new file mode 100644 index 00000000..489a7e21 --- /dev/null +++ b/app/views/exercises/external_users/statistics.html.slim @@ -0,0 +1 @@ +h1 = "#{@exercise} for external user #{@external_user}" \ No newline at end of file diff --git a/app/views/external_users/statistics.html.slim b/app/views/external_users/statistics.html.slim new file mode 100644 index 00000000..6c49b967 --- /dev/null +++ b/app/views/external_users/statistics.html.slim @@ -0,0 +1,2 @@ +h1 = @user +H2 = 'Hallo' \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 7bb17297..24f6a0d9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,6 +20,13 @@ Rails.application.routes.draw do get '/help', to: 'application#help' + concern :statistics do + member do + get :statistics + end + end + + resources :consumers resources :execution_environments do @@ -47,7 +54,9 @@ Rails.application.routes.draw do end end - resources :external_users, only: [:index, :show] + resources :external_users, only: [:index, :show], concerns: :statistics do + resources :exercises, concerns: :statistics + end namespace :code_ocean do resources :files, only: [:create, :destroy] From 5e23fbb61f9484b7155ea103fa6b95488b9b3456 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 26 Nov 2015 12:59:36 +0100 Subject: [PATCH 13/28] Add editor editor to code evolution page --- .../exercises/external_users/statistics.html.slim | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index 489a7e21..aa49f96b 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -1 +1,11 @@ -h1 = "#{@exercise} for external user #{@external_user}" \ No newline at end of file +h1 = "#{@exercise} (external user #{@external_user})" + +#editor.row data-exercise-id=@exercise.id data-message-depleted=t('exercises.editor.depleted') data-user-id=@external_user.id + div class=(@exercise.hide_file_tree ? 'hidden col-sm-3' : 'col-sm-3') id='files' data-entries=FileTree.new(@exercise.files).to_js_tree + div id='frames' class=(@exercise.hide_file_tree ? 'col-sm-12' : 'col-sm-9') + - @exercise.files.each do |file| + = render('editor_frame', exercise: @exercise, file: file) + +#slider + +#timeline From 21e28972dce62da3c0be00822590f8c233d4c0c8 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 26 Nov 2015 14:30:41 +0100 Subject: [PATCH 14/28] Add a slider based on submissions --- .../exercises/external_users/statistics.html.slim | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index aa49f96b..2399a195 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -1,11 +1,22 @@ h1 = "#{@exercise} (external user #{@external_user})" +- submissions = Submission.where("user_id = ? AND exercise_id = ?", @external_user.id, @exercise.id).to_a +- current_index = submissions.length - 1 +- current_submission = submissions[current_index] +- files = current_submission.files.to_a #editor.row data-exercise-id=@exercise.id data-message-depleted=t('exercises.editor.depleted') data-user-id=@external_user.id - div class=(@exercise.hide_file_tree ? 'hidden col-sm-3' : 'col-sm-3') id='files' data-entries=FileTree.new(@exercise.files).to_js_tree + div class=(@exercise.hide_file_tree ? 'hidden col-sm-3' : 'col-sm-3') id='files' data-entries=FileTree.new(files).to_js_tree div id='frames' class=(@exercise.hide_file_tree ? 'col-sm-12' : 'col-sm-9') - - @exercise.files.each do |file| + - files.each do |file| = render('editor_frame', exercise: @exercise, file: file) #slider + input type='range' orient='horizontal' list='datapoints' min=0 max=submissions.length-1 value=current_index + datalist#datapoints + - index=0 + - submissions.each do |submission| + option data-submission=submission + =index + - index += 1 #timeline From bd9118328ff9f8c44f397d33db70e9674e61a221 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 26 Nov 2015 15:28:02 +0100 Subject: [PATCH 15/28] Hide interface if no submissions are available and encode submissions for later use in javascript --- .../external_users/statistics.html.slim | 40 ++++++++++--------- config/locales/de.yml | 3 ++ config/locales/en.yml | 3 ++ 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index 2399a195..d45f9334 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -1,22 +1,26 @@ h1 = "#{@exercise} (external user #{@external_user})" -- submissions = Submission.where("user_id = ? AND exercise_id = ?", @external_user.id, @exercise.id).to_a -- current_index = submissions.length - 1 -- current_submission = submissions[current_index] -- files = current_submission.files.to_a +- submissions = Submission.where("user_id = ? AND exercise_id = ?", @external_user.id, @exercise.id) +- current_submission = submissions.last +- if current_submission + - files = current_submission.files.to_a -#editor.row data-exercise-id=@exercise.id data-message-depleted=t('exercises.editor.depleted') data-user-id=@external_user.id - div class=(@exercise.hide_file_tree ? 'hidden col-sm-3' : 'col-sm-3') id='files' data-entries=FileTree.new(files).to_js_tree - div id='frames' class=(@exercise.hide_file_tree ? 'col-sm-12' : 'col-sm-9') - - files.each do |file| - = render('editor_frame', exercise: @exercise, file: file) + .hidden data-submissions=ActiveSupport::JSON.encode(submissions) -#slider - input type='range' orient='horizontal' list='datapoints' min=0 max=submissions.length-1 value=current_index - datalist#datapoints - - index=0 - - submissions.each do |submission| - option data-submission=submission - =index - - index += 1 + #editor.row data-exercise-id=@exercise.id data-message-depleted=t('exercises.editor.depleted') data-user-id=@external_user.id + div class=(@exercise.hide_file_tree ? 'hidden col-sm-3' : 'col-sm-3') id='files' data-entries=FileTree.new(files).to_js_tree + div id='frames' class=(@exercise.hide_file_tree ? 'col-sm-12' : 'col-sm-9') + - files.each do |file| + = render('editor_frame', exercise: @exercise, file: file) -#timeline + #slider + input type='range' orient='horizontal' list='datapoints' min=0 max=submissions.length-1 value=0 + datalist#datapoints + - index=0 + - submissions.each do |submission| + option data-submission=submission + =index + - index += 1 + + #timeline +- else + p = t('.no_data_available') diff --git a/config/locales/de.yml b/config/locales/de.yml index bbd0e5de..56519a72 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -253,6 +253,9 @@ de: external_user: Externe Nutzer submit: failure: Beim Übermitteln Ihrer Punktzahl ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut. + external_users: + statistics: + no_data_available: Keine Daten verfügbar. files: roles: main_file: Hauptdatei diff --git a/config/locales/en.yml b/config/locales/en.yml index fc08792a..c7b0aa64 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -253,6 +253,9 @@ en: external_users: External Users submit: failure: An error occured while transmitting your score. Please try again later. + external_users: + statistics: + no_data_available: No data available. files: roles: main_file: Main File From baf33419cf3c6153979ec5cfe7c3670f3421fc3d Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 26 Nov 2015 16:25:59 +0100 Subject: [PATCH 16/28] Update editor content on submission selection --- .../javascripts/submission_statistics.js | 32 +++++++++++++++++++ .../external_users/statistics.html.slim | 6 +++- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/submission_statistics.js diff --git a/app/assets/javascripts/submission_statistics.js b/app/assets/javascripts/submission_statistics.js new file mode 100644 index 00000000..f8d37026 --- /dev/null +++ b/app/assets/javascripts/submission_statistics.js @@ -0,0 +1,32 @@ +$(function() { + + if ($.isController('exercises') && $('#timeline').isPresent()) { + + var editors = $('.editor'); + var slider = $('#slider>input'); + var submissions = $('#data').data('submissions'); + var files = $('#data').data('files'); + + editors.each(function(index, editor) { + currentEditor = ace.edit(editor); + currentEditor.$blockScrolling = Infinity; + currentEditor.setReadOnly(true); + }); + + slider.on('change', function(event) { + var currentSubmission = slider.val(); + var currentFiles = JSON.parse(files[currentSubmission]); + + editors.each(function(index, editor) { + currentEditor = ace.edit(editor); + fileContent = ""; + if (currentFiles[index]) { + fileContent = currentFiles[index].content + } + currentEditor.getSession().setValue(fileContent); + }); + }); + + } + +}); diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index d45f9334..a4117ae2 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -4,7 +4,11 @@ h1 = "#{@exercise} (external user #{@external_user})" - if current_submission - files = current_submission.files.to_a - .hidden data-submissions=ActiveSupport::JSON.encode(submissions) + - all_files = [] + - submissions.each do |submission| + - all_files.push(ActiveSupport::JSON.encode(submission.files)) + + .hidden#data data-submissions=ActiveSupport::JSON.encode(submissions) data-files=all_files #editor.row data-exercise-id=@exercise.id data-message-depleted=t('exercises.editor.depleted') data-user-id=@external_user.id div class=(@exercise.hide_file_tree ? 'hidden col-sm-3' : 'col-sm-3') id='files' data-entries=FileTree.new(files).to_js_tree From 72b5fa3b8c97e2ab7df4e779009da5632ad14a4f Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 26 Nov 2015 17:04:37 +0100 Subject: [PATCH 17/28] Cleanup code --- .../javascripts/submission_statistics.js | 2 +- .../external_users/statistics.html.slim | 40 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/submission_statistics.js b/app/assets/javascripts/submission_statistics.js index f8d37026..843812fb 100644 --- a/app/assets/javascripts/submission_statistics.js +++ b/app/assets/javascripts/submission_statistics.js @@ -15,7 +15,7 @@ $(function() { slider.on('change', function(event) { var currentSubmission = slider.val(); - var currentFiles = JSON.parse(files[currentSubmission]); + var currentFiles = files[currentSubmission]; editors.each(function(index, editor) { currentEditor = ace.edit(editor); diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index a4117ae2..fc1966f7 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -2,29 +2,29 @@ h1 = "#{@exercise} (external user #{@external_user})" - submissions = Submission.where("user_id = ? AND exercise_id = ?", @external_user.id, @exercise.id) - current_submission = submissions.last - if current_submission - - files = current_submission.files.to_a + - initial_files = current_submission.files.to_a - - all_files = [] - - submissions.each do |submission| - - all_files.push(ActiveSupport::JSON.encode(submission.files)) + - all_files = [] + - submissions.each do |submission| + - all_files.push(submission.files) - .hidden#data data-submissions=ActiveSupport::JSON.encode(submissions) data-files=all_files + .hidden#data data-submissions=ActiveSupport::JSON.encode(submissions) data-files=ActiveSupport::JSON.encode(all_files) - #editor.row data-exercise-id=@exercise.id data-message-depleted=t('exercises.editor.depleted') data-user-id=@external_user.id - div class=(@exercise.hide_file_tree ? 'hidden col-sm-3' : 'col-sm-3') id='files' data-entries=FileTree.new(files).to_js_tree - div id='frames' class=(@exercise.hide_file_tree ? 'col-sm-12' : 'col-sm-9') - - files.each do |file| - = render('editor_frame', exercise: @exercise, file: file) + #editor.row data-exercise-id=@exercise.id data-message-depleted=t('exercises.editor.depleted') data-user-id=@external_user.id + #files class=(@exercise.hide_file_tree ? 'hidden col-sm-3' : 'col-sm-3') data-entries=FileTree.new(initial_files).to_js_tree + div id='frames' class=(@exercise.hide_file_tree ? 'col-sm-12' : 'col-sm-9') + - initial_files.each do |file| + = render('editor_frame', exercise: @exercise, file: file) - #slider - input type='range' orient='horizontal' list='datapoints' min=0 max=submissions.length-1 value=0 - datalist#datapoints - - index=0 - - submissions.each do |submission| - option data-submission=submission - =index - - index += 1 + #slider + input type='range' orient='horizontal' list='datapoints' min=0 max=submissions.length-1 value=0 + datalist#datapoints + - index=0 + - submissions.each do |submission| + option data-submission=submission + =index + - index += 1 - #timeline + #timeline - else - p = t('.no_data_available') + p = t('.no_data_available') From 6c13b8714f96d693c213651ec09ec7aa9d2ffbe8 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Wed, 2 Dec 2015 13:59:31 +0100 Subject: [PATCH 18/28] Remove unnecessary data attributes --- app/views/exercises/external_users/statistics.html.slim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index fc1966f7..b2e404fd 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -1,6 +1,6 @@ h1 = "#{@exercise} (external user #{@external_user})" - submissions = Submission.where("user_id = ? AND exercise_id = ?", @external_user.id, @exercise.id) -- current_submission = submissions.last +- current_submission = submissions.first - if current_submission - initial_files = current_submission.files.to_a @@ -10,7 +10,7 @@ h1 = "#{@exercise} (external user #{@external_user})" .hidden#data data-submissions=ActiveSupport::JSON.encode(submissions) data-files=ActiveSupport::JSON.encode(all_files) - #editor.row data-exercise-id=@exercise.id data-message-depleted=t('exercises.editor.depleted') data-user-id=@external_user.id + #editor.row #files class=(@exercise.hide_file_tree ? 'hidden col-sm-3' : 'col-sm-3') data-entries=FileTree.new(initial_files).to_js_tree div id='frames' class=(@exercise.hide_file_tree ? 'col-sm-12' : 'col-sm-9') - initial_files.each do |file| From c0b4c17de6d3baeb54381efc2deb50733245bc38 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Wed, 2 Dec 2015 15:39:13 +0100 Subject: [PATCH 19/28] Use custom lightweight editor and file-tree --- .../javascripts/submission_statistics.js | 52 ++++++++++++++++++- .../external_users/statistics.html.slim | 2 +- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/submission_statistics.js b/app/assets/javascripts/submission_statistics.js index 843812fb..4ca7d634 100644 --- a/app/assets/javascripts/submission_statistics.js +++ b/app/assets/javascripts/submission_statistics.js @@ -1,16 +1,62 @@ $(function() { + var ACE_FILES_PATH = '/assets/ace/'; + var THEME = 'ace/theme/textmate'; + + var active_file = undefined; + + var showFirstFile = function() { + var frame = $('.frame[data-role="main_file"]').isPresent() ? $('.frame[data-role="main_file"]') : $('.frame').first(); + var file_id = frame.find('.editor').data('file-id'); + $('#files').jstree().select_node(file_id); + showFrame(frame); + }; + + var showFrame = function(frame) { + $('.frame').hide(); + frame.show(); + }; + + var initializeFileTree = function() { + $('#files').jstree($('#files').data('entries')); + $('#files').on('click', 'li.jstree-leaf', function() { + active_file = { + filename: $(this).text(), + id: parseInt($(this).attr('id')) + }; + var frame = $('[data-file-id="' + active_file.id + '"]').parent(); + showFrame(frame); + }); + }; + if ($.isController('exercises') && $('#timeline').isPresent()) { + _.each(['modePath', 'themePath', 'workerPath'], function(attribute) { + ace.config.set(attribute, ACE_FILES_PATH); + }); + var editors = $('.editor'); var slider = $('#slider>input'); var submissions = $('#data').data('submissions'); var files = $('#data').data('files'); - editors.each(function(index, editor) { - currentEditor = ace.edit(editor); + editors.each(function(index, element) { + currentEditor = ace.edit(element); + + var file_id = $(element).data('file-id'); + var content = $('.editor-content[data-file-id=' + file_id + ']'); + + currentEditor.setShowPrintMargin(false); + currentEditor.setTheme(THEME); currentEditor.$blockScrolling = Infinity; currentEditor.setReadOnly(true); + + var session = currentEditor.getSession(); + session.setMode($(element).data('mode')); + session.setTabSize($(element).data('indent-size')); + session.setUseSoftTabs(true); + session.setUseWrapMode(true); + session.setValue(content.text()); }); slider.on('change', function(event) { @@ -27,6 +73,8 @@ $(function() { }); }); + initializeFileTree(); + showFirstFile(); } }); diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index b2e404fd..0aa19140 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -10,7 +10,7 @@ h1 = "#{@exercise} (external user #{@external_user})" .hidden#data data-submissions=ActiveSupport::JSON.encode(submissions) data-files=ActiveSupport::JSON.encode(all_files) - #editor.row + #stats-editor.row #files class=(@exercise.hide_file_tree ? 'hidden col-sm-3' : 'col-sm-3') data-entries=FileTree.new(initial_files).to_js_tree div id='frames' class=(@exercise.hide_file_tree ? 'col-sm-12' : 'col-sm-9') - initial_files.each do |file| From 5c5769bab227dd8f1cc8f1dd58471263038ee735 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Wed, 2 Dec 2015 17:01:31 +0100 Subject: [PATCH 20/28] Change file tree when changing submission --- .../javascripts/submission_statistics.js | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/submission_statistics.js b/app/assets/javascripts/submission_statistics.js index 4ca7d634..4c03a8d2 100644 --- a/app/assets/javascripts/submission_statistics.js +++ b/app/assets/javascripts/submission_statistics.js @@ -3,13 +3,16 @@ $(function() { var ACE_FILES_PATH = '/assets/ace/'; var THEME = 'ace/theme/textmate'; + var currentSubmission = 0; var active_file = undefined; + var fileTrees = [] var showFirstFile = function() { var frame = $('.frame[data-role="main_file"]').isPresent() ? $('.frame[data-role="main_file"]') : $('.frame').first(); var file_id = frame.find('.editor').data('file-id'); - $('#files').jstree().select_node(file_id); + $(fileTrees[currentSubmission]).jstree().select_node(file_id); showFrame(frame); + showFileTree(currentSubmission); }; var showFrame = function(frame) { @@ -18,17 +21,25 @@ $(function() { }; var initializeFileTree = function() { - $('#files').jstree($('#files').data('entries')); - $('#files').on('click', 'li.jstree-leaf', function() { - active_file = { - filename: $(this).text(), - id: parseInt($(this).attr('id')) - }; - var frame = $('[data-file-id="' + active_file.id + '"]').parent(); - showFrame(frame); + $('.files').each(function(index, element) { + fileTree = $(element).jstree($(element).data('entries')); + fileTree.on('click', 'li.jstree-leaf', function() { + active_file = { + filename: $(this).text(), + id: parseInt($(this).attr('id')) + }; + var frame = $('[data-file-id="' + active_file.id + '"]').parent(); + showFrame(frame); + }); + fileTrees.push(fileTree); }); }; + var showFileTree = function(index) { + $('.files').hide(); + $(fileTrees[index].context).show(); + } + if ($.isController('exercises') && $('#timeline').isPresent()) { _.each(['modePath', 'themePath', 'workerPath'], function(attribute) { @@ -60,7 +71,8 @@ $(function() { }); slider.on('change', function(event) { - var currentSubmission = slider.val(); + currentSubmission = slider.val(); + showFileTree(currentSubmission); var currentFiles = files[currentSubmission]; editors.each(function(index, editor) { From efe3895894516afedf3849f6f0570ee9b75282cd Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 3 Dec 2015 13:52:18 +0100 Subject: [PATCH 21/28] Enable switching between files of a submission --- .../javascripts/submission_statistics.js | 25 +++++++------------ .../external_users/statistics.html.slim | 15 +++++++---- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/submission_statistics.js b/app/assets/javascripts/submission_statistics.js index 4c03a8d2..8618a516 100644 --- a/app/assets/javascripts/submission_statistics.js +++ b/app/assets/javascripts/submission_statistics.js @@ -8,14 +8,13 @@ $(function() { var fileTrees = [] var showFirstFile = function() { - var frame = $('.frame[data-role="main_file"]').isPresent() ? $('.frame[data-role="main_file"]') : $('.frame').first(); - var file_id = frame.find('.editor').data('file-id'); - $(fileTrees[currentSubmission]).jstree().select_node(file_id); - showFrame(frame); + $(fileTrees[currentSubmission]).jstree().select_node(active_file.file_id); + showActiveFrame(); showFileTree(currentSubmission); }; - var showFrame = function(frame) { + var showActiveFrame = function() { + var frame = $('.data[data-file-id="' + active_file.id + '"]').parent().find('.frame'); $('.frame').hide(); frame.show(); }; @@ -28,8 +27,7 @@ $(function() { filename: $(this).text(), id: parseInt($(this).attr('id')) }; - var frame = $('[data-file-id="' + active_file.id + '"]').parent(); - showFrame(frame); + showActiveFrame() }); fileTrees.push(fileTree); }); @@ -74,17 +72,12 @@ $(function() { currentSubmission = slider.val(); showFileTree(currentSubmission); var currentFiles = files[currentSubmission]; - - editors.each(function(index, editor) { - currentEditor = ace.edit(editor); - fileContent = ""; - if (currentFiles[index]) { - fileContent = currentFiles[index].content - } - currentEditor.getSession().setValue(fileContent); - }); + console.log(currentFiles); + active_file = currentFiles[0]; + showFirstFile(); }); + active_file = files[0][0] initializeFileTree(); showFirstFile(); } diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index 0aa19140..583adc83 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -10,11 +10,16 @@ h1 = "#{@exercise} (external user #{@external_user})" .hidden#data data-submissions=ActiveSupport::JSON.encode(submissions) data-files=ActiveSupport::JSON.encode(all_files) - #stats-editor.row - #files class=(@exercise.hide_file_tree ? 'hidden col-sm-3' : 'col-sm-3') data-entries=FileTree.new(initial_files).to_js_tree - div id='frames' class=(@exercise.hide_file_tree ? 'col-sm-12' : 'col-sm-9') - - initial_files.each do |file| - = render('editor_frame', exercise: @exercise, file: file) + - index = 0 + - all_files.each do |files| + #stats-editor.row + .files class=(@exercise.hide_file_tree ? 'hidden col-sm-3' : 'col-sm-3') data-index=index data-entries=FileTree.new(files).to_js_tree + .frames class=(@exercise.hide_file_tree ? 'col-sm-12' : 'col-sm-9') + - files.each do |file| + .frame-container + = render('editor_frame', exercise: @exercise, file: file) + .hidden.data data-file-id=file.id + - index += 1 #slider input type='range' orient='horizontal' list='datapoints' min=0 max=submissions.length-1 value=0 From b7b0bf826ef08f4e5df07b5b150b17eaf1019164 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 3 Dec 2015 14:52:33 +0100 Subject: [PATCH 22/28] Use only one editor to reduce complexity --- .../javascripts/submission_statistics.js | 61 ++++++++----------- .../external_users/statistics.html.slim | 15 ++--- 2 files changed, 30 insertions(+), 46 deletions(-) diff --git a/app/assets/javascripts/submission_statistics.js b/app/assets/javascripts/submission_statistics.js index 8618a516..5428061d 100644 --- a/app/assets/javascripts/submission_statistics.js +++ b/app/assets/javascripts/submission_statistics.js @@ -6,28 +6,31 @@ $(function() { var currentSubmission = 0; var active_file = undefined; var fileTrees = [] + var editor = undefined; + + var showActiveFile = function() { + var session = editor.getSession(); + //session.setMode(active_file.file_type.editor_mode); + //session.setTabSize(active_file.file_type.indent_size); + session.setValue(active_file.content); + session.setUseSoftTabs(true); + session.setUseWrapMode(true); - var showFirstFile = function() { - $(fileTrees[currentSubmission]).jstree().select_node(active_file.file_id); - showActiveFrame(); showFileTree(currentSubmission); - }; - - var showActiveFrame = function() { - var frame = $('.data[data-file-id="' + active_file.id + '"]').parent().find('.frame'); - $('.frame').hide(); - frame.show(); + $(fileTrees[currentSubmission]).jstree().select_node(active_file.file_id); }; var initializeFileTree = function() { $('.files').each(function(index, element) { fileTree = $(element).jstree($(element).data('entries')); fileTree.on('click', 'li.jstree-leaf', function() { - active_file = { - filename: $(this).text(), - id: parseInt($(this).attr('id')) - }; - showActiveFrame() + var id = parseInt($(this).attr('id')) + _.each(files[currentSubmission], function(file) { + if (file.file_id === id) { + active_file = file; + } + }); + showActiveFile(); }); fileTrees.push(fileTree); }); @@ -44,42 +47,26 @@ $(function() { ace.config.set(attribute, ACE_FILES_PATH); }); - var editors = $('.editor'); var slider = $('#slider>input'); var submissions = $('#data').data('submissions'); var files = $('#data').data('files'); - editors.each(function(index, element) { - currentEditor = ace.edit(element); - - var file_id = $(element).data('file-id'); - var content = $('.editor-content[data-file-id=' + file_id + ']'); - - currentEditor.setShowPrintMargin(false); - currentEditor.setTheme(THEME); - currentEditor.$blockScrolling = Infinity; - currentEditor.setReadOnly(true); - - var session = currentEditor.getSession(); - session.setMode($(element).data('mode')); - session.setTabSize($(element).data('indent-size')); - session.setUseSoftTabs(true); - session.setUseWrapMode(true); - session.setValue(content.text()); - }); + editor = ace.edit('current-file'); + editor.setShowPrintMargin(false); + editor.setTheme(THEME); + editor.$blockScrolling = Infinity; + editor.setReadOnly(true); slider.on('change', function(event) { currentSubmission = slider.val(); - showFileTree(currentSubmission); var currentFiles = files[currentSubmission]; - console.log(currentFiles); active_file = currentFiles[0]; - showFirstFile(); + showActiveFile(); }); active_file = files[0][0] initializeFileTree(); - showFirstFile(); + showActiveFile(); } }); diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index 583adc83..58a50c43 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -10,16 +10,13 @@ h1 = "#{@exercise} (external user #{@external_user})" .hidden#data data-submissions=ActiveSupport::JSON.encode(submissions) data-files=ActiveSupport::JSON.encode(all_files) - - index = 0 - - all_files.each do |files| - #stats-editor.row + #stats-editor.row + - index = 0 + - all_files.each do |files| .files class=(@exercise.hide_file_tree ? 'hidden col-sm-3' : 'col-sm-3') data-index=index data-entries=FileTree.new(files).to_js_tree - .frames class=(@exercise.hide_file_tree ? 'col-sm-12' : 'col-sm-9') - - files.each do |file| - .frame-container - = render('editor_frame', exercise: @exercise, file: file) - .hidden.data data-file-id=file.id - - index += 1 + - index += 1 + div class=(@exercise.hide_file_tree ? 'col-sm-12' : 'col-sm-9') + #current-file.editor style="height: 400px" #slider input type='range' orient='horizontal' list='datapoints' min=0 max=submissions.length-1 value=0 From 79e7f7b7e870bf7104f76a0e72a0a455ebd1ef9e Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 10 Dec 2015 12:49:36 +0100 Subject: [PATCH 23/28] Apply some styling and add timeline --- app/assets/javascripts/submission_statistics.js | 2 +- app/assets/stylesheets/statistics.css.scss | 9 +++++++++ .../external_users/statistics.html.slim | 17 +++++++++++++++-- config/locales/de.yml | 3 +++ config/locales/en.yml | 3 +++ 5 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 app/assets/stylesheets/statistics.css.scss diff --git a/app/assets/javascripts/submission_statistics.js b/app/assets/javascripts/submission_statistics.js index 5428061d..ec0d827a 100644 --- a/app/assets/javascripts/submission_statistics.js +++ b/app/assets/javascripts/submission_statistics.js @@ -47,7 +47,7 @@ $(function() { ace.config.set(attribute, ACE_FILES_PATH); }); - var slider = $('#slider>input'); + var slider = $('#submissions-slider>input'); var submissions = $('#data').data('submissions'); var files = $('#data').data('files'); diff --git a/app/assets/stylesheets/statistics.css.scss b/app/assets/stylesheets/statistics.css.scss new file mode 100644 index 00000000..a8d1d81d --- /dev/null +++ b/app/assets/stylesheets/statistics.css.scss @@ -0,0 +1,9 @@ +#submissions-slider { + margin-top: 25px; + margin-bottom: 25px; +} + +#current-file.editor { + height: 400px; +} + diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index 58a50c43..97c069d4 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -16,9 +16,9 @@ h1 = "#{@exercise} (external user #{@external_user})" .files class=(@exercise.hide_file_tree ? 'hidden col-sm-3' : 'col-sm-3') data-index=index data-entries=FileTree.new(files).to_js_tree - index += 1 div class=(@exercise.hide_file_tree ? 'col-sm-12' : 'col-sm-9') - #current-file.editor style="height: 400px" + #current-file.editor - #slider + #submissions-slider input type='range' orient='horizontal' list='datapoints' min=0 max=submissions.length-1 value=0 datalist#datapoints - index=0 @@ -28,5 +28,18 @@ h1 = "#{@exercise} (external user #{@external_user})" - index += 1 #timeline + .table-responsive + table.table + thead + tr + - ['.time', '.cause', '.score'].each do |title| + th.header = t(title) + tbody + - submissions.each do |submission| + tr data-id=submission.id + td = submission.created_at.strftime("%F %T") + td = submission.cause + td = submission.score + - else p = t('.no_data_available') diff --git a/config/locales/de.yml b/config/locales/de.yml index 56519a72..525b65ae 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -256,6 +256,9 @@ de: external_users: statistics: no_data_available: Keine Daten verfügbar. + time: Zeit + cause: Grund + score: Punktzahl files: roles: main_file: Hauptdatei diff --git a/config/locales/en.yml b/config/locales/en.yml index c7b0aa64..47539bf1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -256,6 +256,9 @@ en: external_users: statistics: no_data_available: No data available. + time: Time + cause: Cause + score: Score files: roles: main_file: Main File From 842907f1fa1f7f2b36c862fd09f0f6f7ab257b1e Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 10 Dec 2015 13:48:31 +0100 Subject: [PATCH 24/28] Make use of file types for highlighting and tabs --- app/assets/javascripts/submission_statistics.js | 12 ++++++++++-- .../exercises/external_users/statistics.html.slim | 5 ++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/submission_statistics.js b/app/assets/javascripts/submission_statistics.js index ec0d827a..c5cfaab8 100644 --- a/app/assets/javascripts/submission_statistics.js +++ b/app/assets/javascripts/submission_statistics.js @@ -7,11 +7,13 @@ $(function() { var active_file = undefined; var fileTrees = [] var editor = undefined; + var fileTypeById = {} var showActiveFile = function() { var session = editor.getSession(); - //session.setMode(active_file.file_type.editor_mode); - //session.setTabSize(active_file.file_type.indent_size); + var fileType = fileTypeById[active_file.file_type_id] + session.setMode(fileType.editor_mode); + session.setTabSize(fileType.indent_size); session.setValue(active_file.content); session.setUseSoftTabs(true); session.setUseWrapMode(true); @@ -50,6 +52,7 @@ $(function() { var slider = $('#submissions-slider>input'); var submissions = $('#data').data('submissions'); var files = $('#data').data('files'); + var filetypes = $('#data').data('file-types'); editor = ace.edit('current-file'); editor.setShowPrintMargin(false); @@ -57,6 +60,11 @@ $(function() { editor.$blockScrolling = Infinity; editor.setReadOnly(true); + _.each(filetypes, function (filetype) { + filetype = JSON.parse(filetype); + fileTypeById[filetype.id] = filetype; + }); + slider.on('change', function(event) { currentSubmission = slider.val(); var currentFiles = files[currentSubmission]; diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index 97c069d4..3141572a 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -5,10 +5,13 @@ h1 = "#{@exercise} (external user #{@external_user})" - initial_files = current_submission.files.to_a - all_files = [] + - file_types = Set.new() - 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) + .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 From 53330fbdc5e6c8956bce71b0146dba6bddd77e07 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 10 Dec 2015 14:03:23 +0100 Subject: [PATCH 25/28] Make timeline entries clickable --- app/assets/javascripts/submission_statistics.js | 8 ++++++++ app/assets/stylesheets/statistics.css.scss | 3 +++ app/views/exercises/external_users/statistics.html.slim | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/submission_statistics.js b/app/assets/javascripts/submission_statistics.js index c5cfaab8..9f74ed7a 100644 --- a/app/assets/javascripts/submission_statistics.js +++ b/app/assets/javascripts/submission_statistics.js @@ -65,6 +65,14 @@ $(function() { fileTypeById[filetype.id] = filetype; }); + $('tr[data-id]>.clickable').each(function(index, element) { + element = $(element); + element.click(function() { + slider.val(index); + slider.change() + }); + }); + slider.on('change', function(event) { currentSubmission = slider.val(); var currentFiles = files[currentSubmission]; diff --git a/app/assets/stylesheets/statistics.css.scss b/app/assets/stylesheets/statistics.css.scss index a8d1d81d..d148f782 100644 --- a/app/assets/stylesheets/statistics.css.scss +++ b/app/assets/stylesheets/statistics.css.scss @@ -7,3 +7,6 @@ height: 400px; } +.clickable { + cursor: pointer; +} diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index 3141572a..ca5142cc 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -40,7 +40,7 @@ h1 = "#{@exercise} (external user #{@external_user})" tbody - submissions.each do |submission| tr data-id=submission.id - td = submission.created_at.strftime("%F %T") + td.clickable = submission.created_at.strftime("%F %T") td = submission.cause td = submission.score From 0f61a46764f11ebad60a4239a2746b2c18635137 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 10 Dec 2015 15:54:51 +0100 Subject: [PATCH 26/28] Select the same file if possible --- app/assets/javascripts/submission_statistics.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/submission_statistics.js b/app/assets/javascripts/submission_statistics.js index 9f74ed7a..75fe42cd 100644 --- a/app/assets/javascripts/submission_statistics.js +++ b/app/assets/javascripts/submission_statistics.js @@ -19,7 +19,9 @@ $(function() { session.setUseWrapMode(true); showFileTree(currentSubmission); - $(fileTrees[currentSubmission]).jstree().select_node(active_file.file_id); + filetree = $(fileTrees[currentSubmission]) + filetree.jstree("deselect_all"); + filetree.jstree().select_node(active_file.file_id); }; var initializeFileTree = function() { @@ -76,7 +78,13 @@ $(function() { slider.on('change', function(event) { currentSubmission = slider.val(); var currentFiles = files[currentSubmission]; - active_file = currentFiles[0]; + var fileIndex = 0; + _.each(currentFiles, function(file, index) { + if (file.name === active_file.name) { + fileIndex = index; + } + }) + active_file = currentFiles[fileIndex]; showActiveFile(); }); From 50eb45cb71848112dd038645fca905e3fbe0fe11 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Tue, 15 Dec 2015 16:08:34 +0100 Subject: [PATCH 27/28] Link users in exercise statistics to their personal statistics page --- app/views/exercises/statistics.html.slim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/exercises/statistics.html.slim b/app/views/exercises/statistics.html.slim index 28893b6c..0cb26e9a 100644 --- a/app/views/exercises/statistics.html.slim +++ b/app/views/exercises/statistics.html.slim @@ -23,7 +23,7 @@ h1 = @exercise - @exercise.send(symbol).distinct().each do |user| tr - submissions = @exercise.submissions.where('user_id=?', user.id) - td = "#{user.name} (#{user.email})" + td = link_to_if symbol==:external_users, "#{user.name} (#{user.email})", {controller: "exercises", action: "statistics", external_user_id: user.id, id: @exercise.id} td = submissions.maximum('score') or 0 td = submissions.count('id') td = @exercise.average_working_time_for(user.id) or 0 From 4eba0aecd44c38fd94d16e3dfd19daffa0a69b8e Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Tue, 15 Dec 2015 16:11:58 +0100 Subject: [PATCH 28/28] Link to exercise statistics from execution environment statistics --- app/views/execution_environments/statistics.html.slim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/execution_environments/statistics.html.slim b/app/views/execution_environments/statistics.html.slim index ae2bf4f8..57579476 100644 --- a/app/views/execution_environments/statistics.html.slim +++ b/app/views/execution_environments/statistics.html.slim @@ -9,7 +9,7 @@ h1 = @execution_environment tbody - @execution_environment.exercises.each do |exercise| tr - td = exercise.title + td = link_to exercise.title, controller: "exercises", action: "statistics", id: exercise.id td = exercise.average_score td = exercise.average_number_of_submissions td = exercise.average_working_time