From 7031dd389ed202e9a9e2eca1c548b1d6b6dc3ae3 Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Tue, 13 Mar 2018 16:23:49 +0100
Subject: [PATCH 01/19] Scaffold controller and route
---
app/assets/stylesheets/statistics.scss | 3 +++
app/controllers/statistics_controller.rb | 11 +++++++++++
app/policies/statistics_policy.rb | 2 ++
app/views/application/_navigation.html.slim | 1 +
app/views/statistics/show.html.slim | 1 +
config/locales/de.yml | 2 ++
config/locales/en.yml | 2 ++
config/routes.rb | 2 ++
8 files changed, 24 insertions(+)
create mode 100644 app/assets/stylesheets/statistics.scss
create mode 100644 app/controllers/statistics_controller.rb
create mode 100644 app/policies/statistics_policy.rb
create mode 100644 app/views/statistics/show.html.slim
diff --git a/app/assets/stylesheets/statistics.scss b/app/assets/stylesheets/statistics.scss
new file mode 100644
index 00000000..3645fc0b
--- /dev/null
+++ b/app/assets/stylesheets/statistics.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Statistics controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/statistics_controller.rb b/app/controllers/statistics_controller.rb
new file mode 100644
index 00000000..66ffea02
--- /dev/null
+++ b/app/controllers/statistics_controller.rb
@@ -0,0 +1,11 @@
+class StatisticsController < ApplicationController
+
+ def policy_class
+ StatisticsPolicy
+ end
+
+ def show
+ authorize self
+ end
+
+end
diff --git a/app/policies/statistics_policy.rb b/app/policies/statistics_policy.rb
new file mode 100644
index 00000000..36ca2b51
--- /dev/null
+++ b/app/policies/statistics_policy.rb
@@ -0,0 +1,2 @@
+class StatisticsPolicy < AdminOnlyPolicy
+end
diff --git a/app/views/application/_navigation.html.slim b/app/views/application/_navigation.html.slim
index a2604c7a..127e170c 100644
--- a/app/views/application/_navigation.html.slim
+++ b/app/views/application/_navigation.html.slim
@@ -7,6 +7,7 @@
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) }
diff --git a/app/views/statistics/show.html.slim b/app/views/statistics/show.html.slim
new file mode 100644
index 00000000..14372a07
--- /dev/null
+++ b/app/views/statistics/show.html.slim
@@ -0,0 +1 @@
+h1 = t('shared.statistics')
diff --git a/config/locales/de.yml b/config/locales/de.yml
index ce03d90f..4db92696 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -217,6 +217,8 @@ de:
show: Dashboard
sessions:
destroy_through_lti: Code-Abgabe
+ statistics:
+ show: "Statistiken"
consumers:
show:
link: Konsument
diff --git a/config/locales/en.yml b/config/locales/en.yml
index be92de5b..1a7ef575 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -217,6 +217,8 @@ en:
show: Dashboard
sessions:
destroy_through_lti: Code Submission
+ statistics:
+ show: "Statistics"
consumers:
show:
link: Consumer
diff --git a/config/routes.rb b/config/routes.rb
index 264e7471..21b1b719 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -42,6 +42,8 @@ Rails.application.routes.draw do
get '/help', to: 'application#help'
+ get 'statistics/', to: 'statistics#show'
+
concern :statistics do
member do
get :statistics
From da0859d4838f76ee50d80957ed9c7a2bc7f18fbd Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Tue, 13 Mar 2018 17:37:43 +0100
Subject: [PATCH 02/19] Prepare grid
---
app/assets/stylesheets/statistics.css.scss | 27 ++++++++++++++++++++++
app/assets/stylesheets/statistics.scss | 3 ---
app/views/statistics/show.html.slim | 2 ++
3 files changed, 29 insertions(+), 3 deletions(-)
delete mode 100644 app/assets/stylesheets/statistics.scss
diff --git a/app/assets/stylesheets/statistics.css.scss b/app/assets/stylesheets/statistics.css.scss
index a5ea4430..c1c8d993 100644
--- a/app/assets/stylesheets/statistics.css.scss
+++ b/app/assets/stylesheets/statistics.css.scss
@@ -58,3 +58,30 @@ div.negative-result {
box-shadow: 0px 0px 11px 1px rgba(222,0,0,1);
}
+/////////////////////////////////////////////////////////////////////////////////////////////
+// StatisticsController:
+
+.statistics-wrapper {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ grid-auto-rows: 150px;
+ grid-gap: 10px;
+
+ > div {
+ border: 2px solid #0055ba;
+ border-radius: 5px;
+ background-color: #008cba;
+ padding: 1em;
+ color: #fff;
+ display: flex;
+ flex-flow: column-reverse;
+ text-align: center;
+
+ > .data {
+ flex-grow: 1;
+ font-size: 40px;
+ vertical-align: middle;
+ line-height: 100px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/statistics.scss b/app/assets/stylesheets/statistics.scss
deleted file mode 100644
index 3645fc0b..00000000
--- a/app/assets/stylesheets/statistics.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-// Place all the styles related to the Statistics controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/views/statistics/show.html.slim b/app/views/statistics/show.html.slim
index 14372a07..c151cf29 100644
--- a/app/views/statistics/show.html.slim
+++ b/app/views/statistics/show.html.slim
@@ -1 +1,3 @@
h1 = t('shared.statistics')
+
+.statistics-wrapper
From 68d3fd174e3af3853e0f883529adaf37c9de1624 Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Tue, 13 Mar 2018 18:38:54 +0100
Subject: [PATCH 03/19] Implement basic statistics
---
app/assets/stylesheets/statistics.css.scss | 10 ++-
app/controllers/statistics_controller.rb | 5 ++
app/helpers/statistics_helper.rb | 74 ++++++++++++++++++++++
app/views/statistics/show.html.slim | 10 ++-
config/locales/de.yml | 10 +++
config/locales/en.yml | 10 +++
6 files changed, 116 insertions(+), 3 deletions(-)
create mode 100644 app/helpers/statistics_helper.rb
diff --git a/app/assets/stylesheets/statistics.css.scss b/app/assets/stylesheets/statistics.css.scss
index c1c8d993..bf5e5c05 100644
--- a/app/assets/stylesheets/statistics.css.scss
+++ b/app/assets/stylesheets/statistics.css.scss
@@ -61,6 +61,10 @@ div.negative-result {
/////////////////////////////////////////////////////////////////////////////////////////////
// StatisticsController:
+#statistics-container {
+ margin-bottom: 40px;
+}
+
.statistics-wrapper {
display: grid;
grid-template-columns: repeat(4, 1fr);
@@ -81,7 +85,11 @@ div.negative-result {
flex-grow: 1;
font-size: 40px;
vertical-align: middle;
- line-height: 100px;
+ line-height: 50px;
+ }
+
+ > .title {
+ height: 42px;
}
}
}
diff --git a/app/controllers/statistics_controller.rb b/app/controllers/statistics_controller.rb
index 66ffea02..a26d5670 100644
--- a/app/controllers/statistics_controller.rb
+++ b/app/controllers/statistics_controller.rb
@@ -1,4 +1,5 @@
class StatisticsController < ApplicationController
+ include StatisticsHelper
def policy_class
StatisticsPolicy
@@ -6,6 +7,10 @@ class StatisticsController < ApplicationController
def show
authorize self
+ respond_to do |format|
+ format.html
+ format.json { render(json: statistics_data) }
+ end
end
end
diff --git a/app/helpers/statistics_helper.rb b/app/helpers/statistics_helper.rb
new file mode 100644
index 00000000..124ddf3d
--- /dev/null
+++ b/app/helpers/statistics_helper.rb
@@ -0,0 +1,74 @@
+module StatisticsHelper
+
+ def statistics_data
+ [
+ {
+ key: 'users',
+ name: t('statistics.sections.users'),
+ entries: user_statistics
+ },
+ {
+ key: 'exercises',
+ name: t('statistics.sections.exercises'),
+ entries: exercise_statistics
+ },
+ {
+ key: 'request_for_comments',
+ name: t('statistics.sections.request_for_comments'),
+ entries: rfc_statistics
+ }
+ ]
+ end
+
+ def user_statistics
+ [
+ {
+ key: 'internal_users',
+ title: t('activerecord.models.internal_user.other'),
+ data: InternalUser.count
+ },
+ {
+ key: 'external_users',
+ title: t('activerecord.models.external_user.other'),
+ data: ExternalUser.count
+ }
+ ]
+ end
+
+ def exercise_statistics
+ [
+ {
+ key: 'exercises',
+ title: t('activerecord.models.exercise.other'),
+ data: Exercise.count
+ },
+ {
+ key: 'average_submissions',
+ title: t('statistics.entries.exercises.average_number_of_submissions'),
+ data: Submission.count / Exercise.count
+ }
+ ]
+ end
+
+ def rfc_statistics
+ [
+ {
+ key: 'rfcs',
+ title: t('activerecord.models.request_for_comment.other'),
+ data: RequestForComment.count
+ },
+ {
+ key: 'percent_solved',
+ title: t('statistics.entries.request_for_comments.percent_solved'),
+ data: (100.0 / RequestForComment.count * RequestForComment.where(solved: true).count).round(2),
+ unit: '%'
+ },
+ {
+ key: 'comments',
+ title: t('activerecord.models.comment.other'),
+ data: Comment.count
+ },
+ ]
+ end
+
+end
diff --git a/app/views/statistics/show.html.slim b/app/views/statistics/show.html.slim
index c151cf29..e34f8933 100644
--- a/app/views/statistics/show.html.slim
+++ b/app/views/statistics/show.html.slim
@@ -1,3 +1,9 @@
-h1 = t('shared.statistics')
-.statistics-wrapper
+#statistics-container
+ - statistics_data.each do | section |
+ h2 = section[:name]
+ .statistics-wrapper
+ - section[:entries].each do | entry |
+ div
+ .title = entry[:title]
+ .data = entry[:data].to_s + (entry[:unit] or '')
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 4db92696..9e8b180c 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -676,3 +676,13 @@ de:
subscriptions:
successfully_unsubscribed: "Ihr Abonnement für weitere Kommentare auf dieser Kommentaranfrage wurde erfolgreich beendet."
subscription_not_existent: "Das Abonnement, von dem Sie sich abmelden wollen, existiert nicht."
+ statistics:
+ sections:
+ users: "Benutzer"
+ exercises: "Aufgaben"
+ request_for_comments: "Kommentaranfragen"
+ entries:
+ exercises:
+ average_number_of_submissions: "Durchschnittliche Zahl von Abgaben"
+ request_for_comments:
+ percent_solved: "Beantwortete Anfragen"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 1a7ef575..3dca3a02 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -676,3 +676,13 @@ en:
subscriptions:
successfully_unsubscribed: "You successfully unsubscribed from this Request for Comment"
subscription_not_existent: "The subscription you want to unsubscribe from does not exist."
+ statistics:
+ sections:
+ users: "Users"
+ exercises: "Exercises"
+ request_for_comments: "Requests for Comment"
+ entries:
+ exercises:
+ average_number_of_submissions: "Average Number of Submissions"
+ request_for_comments:
+ percent_solved: "Solved Requests"
From 1bf2757c442f369c4653d9fb4deb9b860e8803e2 Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Wed, 14 Mar 2018 08:42:49 +0100
Subject: [PATCH 04/19] Adjust attribute names
---
app/helpers/statistics_helper.rb | 14 +++++++-------
app/views/statistics/show.html.slim | 6 +++---
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/app/helpers/statistics_helper.rb b/app/helpers/statistics_helper.rb
index 124ddf3d..0b91b808 100644
--- a/app/helpers/statistics_helper.rb
+++ b/app/helpers/statistics_helper.rb
@@ -24,12 +24,12 @@ module StatisticsHelper
[
{
key: 'internal_users',
- title: t('activerecord.models.internal_user.other'),
+ name: t('activerecord.models.internal_user.other'),
data: InternalUser.count
},
{
key: 'external_users',
- title: t('activerecord.models.external_user.other'),
+ name: t('activerecord.models.external_user.other'),
data: ExternalUser.count
}
]
@@ -39,12 +39,12 @@ module StatisticsHelper
[
{
key: 'exercises',
- title: t('activerecord.models.exercise.other'),
+ name: t('activerecord.models.exercise.other'),
data: Exercise.count
},
{
key: 'average_submissions',
- title: t('statistics.entries.exercises.average_number_of_submissions'),
+ name: t('statistics.entries.exercises.average_number_of_submissions'),
data: Submission.count / Exercise.count
}
]
@@ -54,18 +54,18 @@ module StatisticsHelper
[
{
key: 'rfcs',
- title: t('activerecord.models.request_for_comment.other'),
+ name: t('activerecord.models.request_for_comment.other'),
data: RequestForComment.count
},
{
key: 'percent_solved',
- title: t('statistics.entries.request_for_comments.percent_solved'),
+ name: t('statistics.entries.request_for_comments.percent_solved'),
data: (100.0 / RequestForComment.count * RequestForComment.where(solved: true).count).round(2),
unit: '%'
},
{
key: 'comments',
- title: t('activerecord.models.comment.other'),
+ name: t('activerecord.models.comment.other'),
data: Comment.count
},
]
diff --git a/app/views/statistics/show.html.slim b/app/views/statistics/show.html.slim
index e34f8933..8846457b 100644
--- a/app/views/statistics/show.html.slim
+++ b/app/views/statistics/show.html.slim
@@ -2,8 +2,8 @@
#statistics-container
- statistics_data.each do | section |
h2 = section[:name]
- .statistics-wrapper
+ .statistics-wrapper data-key=section[:key]
- section[:entries].each do | entry |
- div
- .title = entry[:title]
+ div data-key=entry[:key]
+ .title = entry[:name]
.data = entry[:data].to_s + (entry[:unit] or '')
From 6252df3a74d273e82f3133b064b3228ffc031329 Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Wed, 14 Mar 2018 08:54:33 +0100
Subject: [PATCH 05/19] Allow linking to specific pages
---
app/assets/stylesheets/statistics.css.scss | 36 ++++++++++++----------
app/helpers/statistics_helper.rb | 15 ++++++---
app/views/statistics/show.html.slim | 7 +++--
3 files changed, 34 insertions(+), 24 deletions(-)
diff --git a/app/assets/stylesheets/statistics.css.scss b/app/assets/stylesheets/statistics.css.scss
index bf5e5c05..dd66f0e6 100644
--- a/app/assets/stylesheets/statistics.css.scss
+++ b/app/assets/stylesheets/statistics.css.scss
@@ -71,25 +71,29 @@ div.negative-result {
grid-auto-rows: 150px;
grid-gap: 10px;
- > div {
- border: 2px solid #0055ba;
- border-radius: 5px;
- background-color: #008cba;
- padding: 1em;
+ > a {
color: #fff;
- display: flex;
- flex-flow: column-reverse;
- text-align: center;
+ text-decoration: none;
- > .data {
- flex-grow: 1;
- font-size: 40px;
- vertical-align: middle;
- line-height: 50px;
- }
+ > div {
+ border: 2px solid #0055ba;
+ border-radius: 5px;
+ background-color: #008cba;
+ padding: 1em;
+ display: flex;
+ flex-flow: column-reverse;
+ text-align: center;
- > .title {
- height: 42px;
+ > .data {
+ flex-grow: 1;
+ font-size: 40px;
+ vertical-align: middle;
+ line-height: 50px;
+ }
+
+ > .title {
+ height: 42px;
+ }
}
}
}
diff --git a/app/helpers/statistics_helper.rb b/app/helpers/statistics_helper.rb
index 0b91b808..08e7ee88 100644
--- a/app/helpers/statistics_helper.rb
+++ b/app/helpers/statistics_helper.rb
@@ -25,12 +25,14 @@ module StatisticsHelper
{
key: 'internal_users',
name: t('activerecord.models.internal_user.other'),
- data: InternalUser.count
+ data: InternalUser.count,
+ url: internal_users_path
},
{
key: 'external_users',
name: t('activerecord.models.external_user.other'),
- data: ExternalUser.count
+ data: ExternalUser.count,
+ url: external_users_path
}
]
end
@@ -40,7 +42,8 @@ module StatisticsHelper
{
key: 'exercises',
name: t('activerecord.models.exercise.other'),
- data: Exercise.count
+ data: Exercise.count,
+ url: exercises_path
},
{
key: 'average_submissions',
@@ -55,13 +58,15 @@ module StatisticsHelper
{
key: 'rfcs',
name: t('activerecord.models.request_for_comment.other'),
- data: RequestForComment.count
+ data: RequestForComment.count,
+ url: request_for_comments_path
},
{
key: 'percent_solved',
name: t('statistics.entries.request_for_comments.percent_solved'),
data: (100.0 / RequestForComment.count * RequestForComment.where(solved: true).count).round(2),
- unit: '%'
+ unit: '%',
+ url: request_for_comments_path + '?q%5Bsolved_not_eq%5D=0'
},
{
key: 'comments',
diff --git a/app/views/statistics/show.html.slim b/app/views/statistics/show.html.slim
index 8846457b..bf06d2e0 100644
--- a/app/views/statistics/show.html.slim
+++ b/app/views/statistics/show.html.slim
@@ -4,6 +4,7 @@
h2 = section[:name]
.statistics-wrapper data-key=section[:key]
- section[:entries].each do | entry |
- div data-key=entry[:key]
- .title = entry[:name]
- .data = entry[:data].to_s + (entry[:unit] or '')
+ a href=entry[:url]
+ div data-key=entry[:key]
+ .title = entry[:name]
+ .data = entry[:data].to_s + (entry[:unit] or '')
From 0fe3ce000b77bc64a410953dee7a54f031478588 Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Wed, 14 Mar 2018 09:20:05 +0100
Subject: [PATCH 06/19] Add more statistics
---
app/helpers/statistics_helper.rb | 26 +++++++++++++++++++++++++-
config/locales/de.yml | 3 +++
config/locales/en.yml | 3 +++
3 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/app/helpers/statistics_helper.rb b/app/helpers/statistics_helper.rb
index 08e7ee88..9a8e7a29 100644
--- a/app/helpers/statistics_helper.rb
+++ b/app/helpers/statistics_helper.rb
@@ -33,6 +33,11 @@ module StatisticsHelper
name: t('activerecord.models.external_user.other'),
data: ExternalUser.count,
url: external_users_path
+ },
+ {
+ key: 'currently_active',
+ name: t('statistics.entries.users.currently_active'),
+ data: ExternalUser.joins(:submissions).where(['submissions.created_at >= ?', DateTime.now - 5.minutes]).count
}
]
end
@@ -49,6 +54,18 @@ module StatisticsHelper
key: 'average_submissions',
name: t('statistics.entries.exercises.average_number_of_submissions'),
data: Submission.count / Exercise.count
+ },
+ {
+ key: 'execution_environments',
+ name: t('activerecord.models.execution_environment.other'),
+ data: ExecutionEnvironment.count,
+ url: execution_environments_path
+ },
+ {
+ key: 'exercise_collections',
+ name: t('activerecord.models.exercise_collection.other'),
+ data: ExerciseCollection.count,
+ url: exercise_collections_path
}
]
end
@@ -64,10 +81,17 @@ module StatisticsHelper
{
key: 'percent_solved',
name: t('statistics.entries.request_for_comments.percent_solved'),
- data: (100.0 / RequestForComment.count * RequestForComment.where(solved: true).count).round(2),
+ data: (100.0 / RequestForComment.count * RequestForComment.where(solved: true).count).round(1),
unit: '%',
url: request_for_comments_path + '?q%5Bsolved_not_eq%5D=0'
},
+ {
+ key: 'percent_unsolved',
+ name: t('statistics.entries.request_for_comments.percent_unsolved'),
+ data: (100.0 / RequestForComment.count * RequestForComment.where(solved: false).count).round(1),
+ unit: '%',
+ url: request_for_comments_path + '?q%5Bsolved_not_eq%5D=1'
+ },
{
key: 'comments',
name: t('activerecord.models.comment.other'),
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 9e8b180c..8799808b 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -686,3 +686,6 @@ de:
average_number_of_submissions: "Durchschnittliche Zahl von Abgaben"
request_for_comments:
percent_solved: "Beantwortete Anfragen"
+ percent_unsolved: "Unbeantwortete Anfragen"
+ users:
+ currently_active: "Aktiv (5 Minuten)"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 3dca3a02..138ba105 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -686,3 +686,6 @@ en:
average_number_of_submissions: "Average Number of Submissions"
request_for_comments:
percent_solved: "Solved Requests"
+ percent_unsolved: "Unsolved Requests"
+ users:
+ currently_active: "Active (5 minutes)"
From be608e4c12cdd17a11ac0a717a8a5e68c186ba9b Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Wed, 14 Mar 2018 11:04:58 +0100
Subject: [PATCH 07/19] Fix query
---
app/helpers/statistics_helper.rb | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/helpers/statistics_helper.rb b/app/helpers/statistics_helper.rb
index 9a8e7a29..320d925a 100644
--- a/app/helpers/statistics_helper.rb
+++ b/app/helpers/statistics_helper.rb
@@ -37,7 +37,9 @@ module StatisticsHelper
{
key: 'currently_active',
name: t('statistics.entries.users.currently_active'),
- data: ExternalUser.joins(:submissions).where(['submissions.created_at >= ?', DateTime.now - 5.minutes]).count
+ data: ExternalUser.joins(:submissions)
+ .where(['submissions.created_at >= ?', DateTime.now - 5.minutes])
+ .distinct('external_users.id').count
}
]
end
From 5a2c8335814bd372347369d9537908432cc40b96 Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Wed, 14 Mar 2018 11:09:12 +0100
Subject: [PATCH 08/19] Improve unit visuals
---
app/assets/stylesheets/statistics.css.scss | 4 ++++
app/views/statistics/show.html.slim | 4 +++-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/app/assets/stylesheets/statistics.css.scss b/app/assets/stylesheets/statistics.css.scss
index dd66f0e6..9ce3d61f 100644
--- a/app/assets/stylesheets/statistics.css.scss
+++ b/app/assets/stylesheets/statistics.css.scss
@@ -89,6 +89,10 @@ div.negative-result {
font-size: 40px;
vertical-align: middle;
line-height: 50px;
+
+ > .unit {
+ font-size: 20px;
+ }
}
> .title {
diff --git a/app/views/statistics/show.html.slim b/app/views/statistics/show.html.slim
index bf06d2e0..9b7edefe 100644
--- a/app/views/statistics/show.html.slim
+++ b/app/views/statistics/show.html.slim
@@ -7,4 +7,6 @@
a href=entry[:url]
div data-key=entry[:key]
.title = entry[:name]
- .data = entry[:data].to_s + (entry[:unit] or '')
+ .data
+ span = entry[:data].to_s
+ span.unit = entry[:unit] if entry.key? :unit
From be2a4d84fd9be0de8c2d6db0f1f2b5b8619cc278 Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Wed, 14 Mar 2018 11:43:59 +0100
Subject: [PATCH 09/19] Create TimeHelper module
---
app/helpers/time_helper.rb | 12 ++++++++++++
1 file changed, 12 insertions(+)
create mode 100644 app/helpers/time_helper.rb
diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb
new file mode 100644
index 00000000..ed05ede5
--- /dev/null
+++ b/app/helpers/time_helper.rb
@@ -0,0 +1,12 @@
+module TimeHelper
+
+ # convert timestamps ('12:34:56.789') to seconds
+ def time_to_f(timestamp)
+ unless timestamp.nil?
+ timestamp = timestamp.split(':')
+ return timestamp[0].to_i * 60 * 60 + timestamp[1].to_i * 60 + timestamp[2].to_f
+ end
+ nil
+ end
+
+end
From 119cc9ee7189d0ce46dba1fc94af9bcc708bc8cd Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Wed, 14 Mar 2018 11:44:18 +0100
Subject: [PATCH 10/19] Scaffold exercise collection statistics
---
app/controllers/exercise_collections_controller.rb | 11 ++++++++++-
app/policies/exercise_collection_policy.rb | 4 ++++
app/views/exercise_collections/index.html.slim | 3 ++-
app/views/exercise_collections/statistics.html.slim | 6 ++++++
config/routes.rb | 6 +++++-
5 files changed, 27 insertions(+), 3 deletions(-)
create mode 100644 app/views/exercise_collections/statistics.html.slim
diff --git a/app/controllers/exercise_collections_controller.rb b/app/controllers/exercise_collections_controller.rb
index 4861a062..6d239d9c 100644
--- a/app/controllers/exercise_collections_controller.rb
+++ b/app/controllers/exercise_collections_controller.rb
@@ -1,7 +1,8 @@
class ExerciseCollectionsController < ApplicationController
include CommonBehavior
+ include TimeHelper
- before_action :set_exercise_collection, only: [:show, :edit, :update, :destroy]
+ before_action :set_exercise_collection, only: [:show, :edit, :update, :destroy, :statistics]
def index
@exercise_collections = ExerciseCollection.all.paginate(:page => params[:page])
@@ -34,6 +35,14 @@ class ExerciseCollectionsController < ApplicationController
update_and_respond(object: @exercise_collection, params: exercise_collection_params)
end
+ def statistics
+ @working_times = {}
+ @exercise_collection.exercises.each do |exercise|
+ @working_times[exercise.id] = time_to_f exercise.average_working_time
+ end
+ @average = @working_times.values.reduce(:+) / @working_times.size
+ end
+
private
def set_exercise_collection
diff --git a/app/policies/exercise_collection_policy.rb b/app/policies/exercise_collection_policy.rb
index ff150290..3d6b725e 100644
--- a/app/policies/exercise_collection_policy.rb
+++ b/app/policies/exercise_collection_policy.rb
@@ -1,3 +1,7 @@
class ExerciseCollectionPolicy < AdminOnlyPolicy
+ def statistics?
+ admin?
+ end
+
end
diff --git a/app/views/exercise_collections/index.html.slim b/app/views/exercise_collections/index.html.slim
index 75a9d011..e0e8ebbc 100644
--- a/app/views/exercise_collections/index.html.slim
+++ b/app/views/exercise_collections/index.html.slim
@@ -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)
diff --git a/app/views/exercise_collections/statistics.html.slim b/app/views/exercise_collections/statistics.html.slim
new file mode 100644
index 00000000..9ca7cc78
--- /dev/null
+++ b/app/views/exercise_collections/statistics.html.slim
@@ -0,0 +1,6 @@
+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: @average.round(3).to_s + 's')
diff --git a/config/routes.rb b/config/routes.rb
index 21b1b719..399bee40 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -84,7 +84,11 @@ Rails.application.routes.draw do
end
end
- resources :exercise_collections
+ resources :exercise_collections do
+ member do
+ get :statistics
+ end
+ end
resources :proxy_exercises do
member do
From 0564c881059d88221264d480ffcc21314b976f61 Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Wed, 14 Mar 2018 13:16:00 +0100
Subject: [PATCH 11/19] Add soft solved rfcs
---
app/helpers/statistics_helper.rb | 9 ++++++++-
config/locales/de.yml | 1 +
config/locales/en.yml | 1 +
3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/app/helpers/statistics_helper.rb b/app/helpers/statistics_helper.rb
index 320d925a..ed2d82b7 100644
--- a/app/helpers/statistics_helper.rb
+++ b/app/helpers/statistics_helper.rb
@@ -87,10 +87,17 @@ module StatisticsHelper
unit: '%',
url: request_for_comments_path + '?q%5Bsolved_not_eq%5D=0'
},
+ {
+ key: 'percent_soft_solved',
+ name: t('statistics.entries.request_for_comments.percent_soft_solved'),
+ data: (100.0 / RequestForComment.count * RequestForComment.unsolved.where(full_score_reached: true).count).round(1),
+ unit: '%',
+ url: request_for_comments_path
+ },
{
key: 'percent_unsolved',
name: t('statistics.entries.request_for_comments.percent_unsolved'),
- data: (100.0 / RequestForComment.count * RequestForComment.where(solved: false).count).round(1),
+ data: (100.0 / RequestForComment.count * RequestForComment.unsolved.count).round(1),
unit: '%',
url: request_for_comments_path + '?q%5Bsolved_not_eq%5D=1'
},
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 8799808b..cb042f7d 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -687,5 +687,6 @@ de:
request_for_comments:
percent_solved: "Beantwortete Anfragen"
percent_unsolved: "Unbeantwortete Anfragen"
+ percent_soft_solved: "Ungelöst mit voller Punktzahl"
users:
currently_active: "Aktiv (5 Minuten)"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 138ba105..4e3564bd 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -687,5 +687,6 @@ en:
request_for_comments:
percent_solved: "Solved Requests"
percent_unsolved: "Unsolved Requests"
+ percent_soft_solved: "Unsolved with full score"
users:
currently_active: "Active (5 minutes)"
From a7451a505734cd4d45e389df50f752f89886deba Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Wed, 14 Mar 2018 14:53:02 +0100
Subject: [PATCH 12/19] Add RfCs with comments
---
app/helpers/statistics_helper.rb | 7 +++++++
config/locales/de.yml | 1 +
config/locales/en.yml | 1 +
3 files changed, 9 insertions(+)
diff --git a/app/helpers/statistics_helper.rb b/app/helpers/statistics_helper.rb
index ed2d82b7..058cf4c1 100644
--- a/app/helpers/statistics_helper.rb
+++ b/app/helpers/statistics_helper.rb
@@ -106,6 +106,13 @@ module StatisticsHelper
name: t('activerecord.models.comment.other'),
data: Comment.count
},
+ {
+ key: 'rfcs_with_comments',
+ name: t('statistics.entries.request_for_comments.with_comments'),
+ data: RequestForComment.joins('join "submissions" s on s.id = request_for_comments.submission_id
+ join "files" f on f.context_id = s.id and f.context_type = \'Submission\'
+ join "comments" c on c.file_id = f.id').group('request_for_comments.id').count.size
+ }
]
end
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 281d349d..9b3aeb37 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -748,5 +748,6 @@ de:
percent_solved: "Beantwortete Anfragen"
percent_unsolved: "Unbeantwortete Anfragen"
percent_soft_solved: "Ungelöst mit voller Punktzahl"
+ with_comments: "Anfragen mit Kommentaren"
users:
currently_active: "Aktiv (5 Minuten)"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index c5e05183..93b095be 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -748,5 +748,6 @@ en:
percent_solved: "Solved Requests"
percent_unsolved: "Unsolved Requests"
percent_soft_solved: "Unsolved with full score"
+ with_comments: "RfCs with Comments"
users:
currently_active: "Active (5 minutes)"
From 6c5cd8d1ee1b4473714fc8e4ab258bb3b3e67cee Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Mon, 19 Mar 2018 14:38:39 +0100
Subject: [PATCH 13/19] Use TimeHelper in rake task
---
lib/tasks/detect_exercise_anomalies.rake | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/lib/tasks/detect_exercise_anomalies.rake b/lib/tasks/detect_exercise_anomalies.rake
index e22550b2..3436b87a 100644
--- a/lib/tasks/detect_exercise_anomalies.rake
+++ b/lib/tasks/detect_exercise_anomalies.rake
@@ -22,6 +22,8 @@ namespace :detect_exercise_anomalies do
AVERAGE_WORKING_TIME_CACHE = {}
task :with_at_least, [:number_of_exercises, :number_of_solutions] => :environment do |task, args|
+ include TimeHelper
+
number_of_exercises = args[:number_of_exercises]
number_of_solutions = args[:number_of_solutions]
@@ -71,14 +73,6 @@ namespace :detect_exercise_anomalies do
end
end
- def time_to_f(timestamp)
- unless timestamp.nil?
- timestamp = timestamp.split(':')
- return timestamp[0].to_i * 60 * 60 + timestamp[1].to_i * 60 + timestamp[2].to_f
- end
- nil
- end
-
def get_average_working_time(exercise)
unless AVERAGE_WORKING_TIME_CACHE.key?(exercise.id)
seconds = time_to_f exercise.average_working_time
From 667d1cb38b82e1037bb06f1ffbe60f9d54485f33 Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Mon, 19 Mar 2018 14:55:14 +0100
Subject: [PATCH 14/19] Add submission volume metric
---
app/helpers/statistics_helper.rb | 8 +++++++-
config/locales/de.yml | 1 +
config/locales/en.yml | 1 +
3 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/app/helpers/statistics_helper.rb b/app/helpers/statistics_helper.rb
index 058cf4c1..bd597cc1 100644
--- a/app/helpers/statistics_helper.rb
+++ b/app/helpers/statistics_helper.rb
@@ -55,7 +55,13 @@ module StatisticsHelper
{
key: 'average_submissions',
name: t('statistics.entries.exercises.average_number_of_submissions'),
- data: Submission.count / Exercise.count
+ data: (Submission.count.to_f / Exercise.count).round(2)
+ },
+ {
+ key: 'submissions_per_minute',
+ name: t('statistics.entries.exercises.submissions_per_minute'),
+ data: (Submission.where('created_at >= ?', DateTime.now - 1.hours).count.to_f / 60).round(2),
+ unit: '/min'
},
{
key: 'execution_environments',
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 9b3aeb37..4f5b9dcb 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -744,6 +744,7 @@ de:
entries:
exercises:
average_number_of_submissions: "Durchschnittliche Zahl von Abgaben"
+ submissions_per_minute: "Aktuelle Abgabenhäufigkeit (1h)"
request_for_comments:
percent_solved: "Beantwortete Anfragen"
percent_unsolved: "Unbeantwortete Anfragen"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 93b095be..f65b90f4 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -744,6 +744,7 @@ en:
entries:
exercises:
average_number_of_submissions: "Average Number of Submissions"
+ submissions_per_minute: "Current Submission Volume (1h)"
request_for_comments:
percent_solved: "Solved Requests"
percent_unsolved: "Unsolved Requests"
From f1f1594e5b289cef41c36ec8aa1097df396b4a8c Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Mon, 19 Mar 2018 15:01:50 +0100
Subject: [PATCH 15/19] Move method to model
---
app/controllers/exercise_collections_controller.rb | 6 ------
app/models/exercise_collection.rb | 9 +++++++++
app/views/exercise_collections/statistics.html.slim | 2 +-
3 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/app/controllers/exercise_collections_controller.rb b/app/controllers/exercise_collections_controller.rb
index 3a6112ef..de425dcd 100644
--- a/app/controllers/exercise_collections_controller.rb
+++ b/app/controllers/exercise_collections_controller.rb
@@ -1,6 +1,5 @@
class ExerciseCollectionsController < ApplicationController
include CommonBehavior
- include TimeHelper
before_action :set_exercise_collection, only: [:show, :edit, :update, :destroy, :statistics]
@@ -37,11 +36,6 @@ class ExerciseCollectionsController < ApplicationController
end
def statistics
- @working_times = {}
- @exercise_collection.exercises.each do |exercise|
- @working_times[exercise.id] = time_to_f exercise.average_working_time
- end
- @average = @working_times.values.reduce(:+) / @working_times.size
end
private
diff --git a/app/models/exercise_collection.rb b/app/models/exercise_collection.rb
index f9f09269..249e7269 100644
--- a/app/models/exercise_collection.rb
+++ b/app/models/exercise_collection.rb
@@ -1,8 +1,17 @@
class ExerciseCollection < ActiveRecord::Base
+ include TimeHelper
has_and_belongs_to_many :exercises
belongs_to :user, polymorphic: true
+ def average_working_time
+ working_times = {}
+ exercises.each do |exercise|
+ working_times[exercise.id] = time_to_f exercise.average_working_time
+ end
+ working_times.values.reduce(:+) / working_times.size
+ end
+
def to_s
"#{I18n.t('activerecord.models.exercise_collection.one')}: #{name} (#{id})"
end
diff --git a/app/views/exercise_collections/statistics.html.slim b/app/views/exercise_collections/statistics.html.slim
index 9ca7cc78..686c79c1 100644
--- a/app/views/exercise_collections/statistics.html.slim
+++ b/app/views/exercise_collections/statistics.html.slim
@@ -3,4 +3,4 @@ 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: @average.round(3).to_s + 's')
+= row(label: 'exercises.statistics.average_worktime', value: @exercise_collection.average_working_time.round(3).to_s + 's')
From 2fc46fe9abe4eef3c1c9a02c53f7ef7b1720cfd7 Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Mon, 19 Mar 2018 15:33:12 +0100
Subject: [PATCH 16/19] Fix d3 at ~4.0
---
Gemfile | 2 +-
Gemfile.lock | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Gemfile b/Gemfile
index 6a6486a6..076ed2aa 100644
--- a/Gemfile
+++ b/Gemfile
@@ -40,7 +40,7 @@ gem 'tubesock'
gem 'faye-websocket'
gem 'eventmachine', '1.0.9.1' # explicitly added, this is used by faye-websocket, version 1.2.5 still has an error in eventmachine.rb:202: [BUG] Segmentation fault, which is not yet fixed and causes the whole ruby process to crash
gem 'nokogiri'
-gem 'd3-rails'
+gem 'd3-rails', '~>4.0'
gem 'rest-client'
gem 'rubyzip'
gem 'whenever', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 7edb56f0..456aeb24 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -388,7 +388,7 @@ DEPENDENCIES
coffee-rails
concurrent-ruby
concurrent-ruby-ext
- d3-rails
+ d3-rails (~> 4.0)
database_cleaner
docker-api
eventmachine (= 1.0.9.1)
From 36d0c2839dcfb6c5b61ae9e72fdfa460eaa47d6b Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Mon, 19 Mar 2018 15:33:37 +0100
Subject: [PATCH 17/19] Fix working time graphs that were broken by d3 update
---
app/assets/javascripts/exercise_graphs.js | 56 +++++--------------
app/assets/javascripts/working_time_graphs.js | 37 ++++--------
2 files changed, 23 insertions(+), 70 deletions(-)
diff --git a/app/assets/javascripts/exercise_graphs.js b/app/assets/javascripts/exercise_graphs.js
index 5f521b39..b095e1a5 100644
--- a/app/assets/javascripts/exercise_graphs.js
+++ b/app/assets/javascripts/exercise_graphs.js
@@ -1,9 +1,7 @@
$(function() {
- // http://localhost:3333/exercises/38/statistics good for testing
- // originally at--> localhost:3333/exercises/69/statistics
+ // /exercises/38/statistics good for testing
if ($.isController('exercises') && $('.graph-functions-2').isPresent()) {
- // GET THE DATA
var submissions = $('#data').data('submissions');
var submissions_length = submissions.length;
@@ -14,10 +12,7 @@ $(function() {
submissionsAutosaves = [];
var maximumValue = 0;
- var wtimes = $('#wtimes').data('working_times'); //.hidden#wtimes data-working_times=ActiveSupport::JSON.encode(working_times_until)
-
- // console.log(submissions);
- // console.log(wtimes);
+ var wtimes = $('#wtimes').data('working_times');
for (var i = 0;i
")
-
- // var minutes_count = new Array(10);
- // var minutes_array_len = minutes_array.length;
- // for (var i=0; i< minutes_count; i++){
- //
- // for (var j = 0; j < minutes_array_len; j++){
- // if ()
- // }
- // }
-
function getWidth() {
if (self.innerHeight) {
return self.innerWidth;
@@ -81,22 +69,17 @@ $(function() {
//var formatDate = d3.time.format("%M");
- var x = d3.scale.linear()
+ var x = d3.scaleLinear()
.range([0, width]);
- var y = d3.scale.linear()
+ var y = d3.scaleLinear()
.range([height, 0]); // - (height/20
- var xAxis = d3.svg.axis()
- .scale(x)
- .orient("bottom")
- .ticks(20);
- var yAxis = d3.svg.axis()
- .scale(y)
- .orient("left")
+ var xAxis = d3.axisBottom(x).ticks(20);
+ var yAxis = d3.axisLeft(y)
.ticks(20)
- .innerTickSize(-width)
- .outerTickSize(0);
+ .tickSizeInner(-width)
+ .tickSizeOuter(0);
- var line = d3.svg.line()
+ var line = d3.line()
.x(function (d, i) {
return x(i);
})
@@ -225,7 +208,7 @@ $(function() {
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
- var y = d3.scale.linear()
+ var y = d3.scaleLinear()
.range([0,height-(margin.top + margin.bottom)]);
@@ -236,7 +219,7 @@ $(function() {
var yAxis = d3.svg.axis()
- .scale(d3.scale.linear().domain([0,max_of_array]).range([height,0]))//y
+ .scale(d3.scaleLinear().domain([0,max_of_array]).range([height,0]))//y
.orient("left")
.ticks(10)
.innerTickSize(-width);
@@ -299,7 +282,7 @@ $(function() {
.text("Working Time (Minutes)")
.style('font-size', 14);
- y = d3.scale.linear()
+ y = d3.scaleLinear()
.domain([(0),max_of_array])
.range([0,height]);
From d55b39eb487859ed377c19e4f76b3cc9cef5c310 Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Mon, 19 Mar 2018 18:17:34 +0100
Subject: [PATCH 18/19] Visualize exercise working times in exercise collection
statstics
---
.../javascripts/exercise_collections.js.erb | 103 ++++++++++++++++++
.../stylesheets/exercise_collections.scss | 22 ++++
app/models/exercise_collection.rb | 8 +-
.../exercise_collections/statistics.html.slim | 3 +
4 files changed, 134 insertions(+), 2 deletions(-)
create mode 100644 app/assets/javascripts/exercise_collections.js.erb
create mode 100644 app/assets/stylesheets/exercise_collections.scss
diff --git a/app/assets/javascripts/exercise_collections.js.erb b/app/assets/javascripts/exercise_collections.js.erb
new file mode 100644
index 00000000..7e048b55
--- /dev/null
+++ b/app/assets/javascripts/exercise_collections.js.erb
@@ -0,0 +1,103 @@
+$(function() {
+ if ($.isController('exercise_collections')) {
+ var data = $('#data').data('working-times');
+ var averageWorkingTimeValue = parseFloat($('#data').data('average-working-time'));
+
+ var margin = { top: 30, right: 40, bottom: 30, left: 50 },
+ width = 720 - margin.left - margin.right,
+ height = 500 - margin.top - margin.bottom;
+
+ var x = d3.scaleBand().range([0, width]);
+ var y = d3.scaleLinear().range([height, 0]);
+
+ var xAxis = d3.axisBottom(x);
+ var yAxisLeft = d3.axisLeft(y);
+
+ var tooltip = d3.select("#graph").append("div").attr("class", "exercise-id-tooltip");
+
+ var averageWorkingTime = d3.line()
+ .x(function (d) { return x(d.index) + x.bandwidth()/2; })
+ .y(function () { return y(averageWorkingTimeValue); });
+
+ var minWorkingTime = d3.line()
+ .x(function (d) { return x(d.index) + x.bandwidth()/2; })
+ .y(function () { return y(0.1*averageWorkingTimeValue); });
+
+ var maxWorkingTime = d3.line()
+ .x(function (d) { return x(d.index) + x.bandwidth()/2; })
+ .y(function () { return y(2*averageWorkingTimeValue); });
+
+ var svg = d3.select('#graph')
+ .append("svg")
+ .attr("width", width + margin.left + margin.right)
+ .attr("height", height + margin.top + margin.bottom)
+ .append("g")
+ .attr("transform",
+ "translate(" + margin.left + "," + margin.top + ")");
+
+ // Get the data
+ data = Object.keys(data).map(function (key, index) {
+ return {
+ index: index,
+ exercise_id: parseInt(key),
+ working_time: parseFloat(data[key])
+ };
+ });
+
+ // Scale the range of the data
+ x.domain(data.map(function (d) { return d.index; }));
+ y.domain([0, d3.max(data, function (d) { return d.working_time; })]);
+
+ // Add the X Axis
+ svg.append("g")
+ .attr("class", "x axis")
+ .attr("transform", "translate(0," + height + ")")
+ .call(xAxis);
+
+ // Add the Y Axis
+ svg.append("g")
+ .attr("class", "y axis")
+ .style("fill", "steelblue")
+ .call(yAxisLeft);
+
+ // Draw the bars
+ svg.selectAll("bar")
+ .data(data)
+ .enter()
+ .append("rect")
+ .style("fill", "#008cba")
+ .style("cursor", "pointer")
+ .on("mousemove", function (d){
+ tooltip
+ .style("left", d3.event.pageX - 50 + "px")
+ .style("top", d3.event.pageY + 50 + "px")
+ .style("display", "inline-block")
+ .html("<%= I18n.t('activerecord.models.exercise.one') %> ID: " + d.exercise_id + "
" +
+ "<%= I18n.t('exercises.statistics.average_worktime') %>: " + d.working_time + "s");
+ })
+ .on("mouseout", function (){ tooltip.style("display", "none");})
+ .on("click", function (d) {
+ window.location.href = "/exercises/" + d.exercise_id + "/statistics";
+ })
+ .attr("x", function (d) { return x(d.index); })
+ .attr("width", x.bandwidth())
+ .attr("y", function (d) { return y(d.working_time); })
+ .attr("height", function (d) { return height - y(d.working_time); });
+
+ // Add the average working time path
+ svg.append("path")
+ .datum(data)
+ .attr("class", "line average-working-time")
+ .attr("d", averageWorkingTime);
+
+ // Add the anomaly paths (min/max average exercise working time)
+ svg.append("path")
+ .datum(data)
+ .attr("class", "line minimum-working-time")
+ .attr("d", minWorkingTime);
+ svg.append("path")
+ .datum(data)
+ .attr("class", "line maximum-working-time")
+ .attr("d", maxWorkingTime);
+ }
+});
diff --git a/app/assets/stylesheets/exercise_collections.scss b/app/assets/stylesheets/exercise_collections.scss
new file mode 100644
index 00000000..6a2d630e
--- /dev/null
+++ b/app/assets/stylesheets/exercise_collections.scss
@@ -0,0 +1,22 @@
+path.line.minimum-working-time {
+ stroke: #8efa00;
+}
+
+path.line.average-working-time {
+ stroke: #ffca00;
+}
+
+path.line.maximum-working-time {
+ stroke: #ff2600;
+}
+
+.exercise-id-tooltip {
+ position: absolute;
+ display: none;
+ min-width: 80px;
+ height: auto;
+ background: none repeat scroll 0 0 #ffffff;
+ border: 1px solid #008cba;
+ padding: 14px;
+ text-align: center;
+}
diff --git a/app/models/exercise_collection.rb b/app/models/exercise_collection.rb
index 249e7269..661bed81 100644
--- a/app/models/exercise_collection.rb
+++ b/app/models/exercise_collection.rb
@@ -4,12 +4,16 @@ class ExerciseCollection < ActiveRecord::Base
has_and_belongs_to_many :exercises
belongs_to :user, polymorphic: true
- def average_working_time
+ def exercise_working_times
working_times = {}
exercises.each do |exercise|
working_times[exercise.id] = time_to_f exercise.average_working_time
end
- working_times.values.reduce(:+) / working_times.size
+ working_times
+ end
+
+ def average_working_time
+ exercise_working_times.values.reduce(:+) / exercises.size
end
def to_s
diff --git a/app/views/exercise_collections/statistics.html.slim b/app/views/exercise_collections/statistics.html.slim
index 686c79c1..4a8a04ac 100644
--- a/app/views/exercise_collections/statistics.html.slim
+++ b/app/views/exercise_collections/statistics.html.slim
@@ -4,3 +4,6 @@ h1 = @exercise_collection
= 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)
From 0e613d941d0bdfd85988088b36e7efe654549ce7 Mon Sep 17 00:00:00 2001
From: Maximilian Grundke
Date: Mon, 19 Mar 2018 18:45:50 +0100
Subject: [PATCH 19/19] Add legend
---
.../javascripts/exercise_collections.js.erb | 3 +-
.../stylesheets/exercise_collections.scss | 53 +++++++++++++++++--
.../exercise_collections/statistics.html.slim | 8 +++
3 files changed, 59 insertions(+), 5 deletions(-)
diff --git a/app/assets/javascripts/exercise_collections.js.erb b/app/assets/javascripts/exercise_collections.js.erb
index 7e048b55..3530b636 100644
--- a/app/assets/javascripts/exercise_collections.js.erb
+++ b/app/assets/javascripts/exercise_collections.js.erb
@@ -65,8 +65,7 @@ $(function() {
.data(data)
.enter()
.append("rect")
- .style("fill", "#008cba")
- .style("cursor", "pointer")
+ .attr("class", "value-bar")
.on("mousemove", function (d){
tooltip
.style("left", d3.event.pageX - 50 + "px")
diff --git a/app/assets/stylesheets/exercise_collections.scss b/app/assets/stylesheets/exercise_collections.scss
index 6a2d630e..11b6b3a1 100644
--- a/app/assets/stylesheets/exercise_collections.scss
+++ b/app/assets/stylesheets/exercise_collections.scss
@@ -1,13 +1,60 @@
+$time-color: #008cba;
+$min-color: #8efa00;
+$avg-color: #ffca00;
+$max-color: #ff2600;
+
path.line.minimum-working-time {
- stroke: #8efa00;
+ stroke: $min-color;
}
path.line.average-working-time {
- stroke: #ffca00;
+ stroke: $avg-color;
}
path.line.maximum-working-time {
- stroke: #ff2600;
+ stroke: $max-color;
+}
+
+rect.value-bar {
+ fill: $time-color;
+ cursor: pointer;
+}
+
+#legend {
+ display: flex;
+ margin-top: 20px;
+
+ .legend-entry {
+ flex-grow: 1;
+ display: flex;
+
+ .box {
+ width: 20px;
+ height: 20px;
+ border: solid 1px #000;
+ }
+
+ .box.time {
+ background-color: $time-color;
+ }
+
+ .box.min {
+ background-color: $min-color;
+ }
+
+ .box.avg {
+ background-color: $avg-color;
+ }
+
+ .box.max {
+ background-color: $max-color;
+ }
+
+ .box-label {
+ margin-left: 5px;
+ margin-right: 15px;
+ }
+ }
}
.exercise-id-tooltip {
diff --git a/app/views/exercise_collections/statistics.html.slim b/app/views/exercise_collections/statistics.html.slim
index 4a8a04ac..486a0dbd 100644
--- a/app/views/exercise_collections/statistics.html.slim
+++ b/app/views/exercise_collections/statistics.html.slim
@@ -7,3 +7,11 @@ h1 = @exercise_collection
#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