Add live graphs for active users and submission volume
This commit is contained in:
104
app/assets/javascripts/statistics_graphs.js
Normal file
104
app/assets/javascripts/statistics_graphs.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
$(document).ready(function () {
|
||||||
|
if ($.isController('statistics') && $('.graph#user-activity').isPresent()) {
|
||||||
|
var CHART_START = window.vis ? vis.moment().add(-1, 'minute') : undefined;
|
||||||
|
var DEFAULT_REFRESH_INTERVAL = 10000;
|
||||||
|
|
||||||
|
var refreshInterval;
|
||||||
|
|
||||||
|
var initialData;
|
||||||
|
var dataset;
|
||||||
|
var graph;
|
||||||
|
var groups;
|
||||||
|
|
||||||
|
var buildChartGroups = function() {
|
||||||
|
initialData = initialData.sort(function (a, b) {return a.data - b.data});
|
||||||
|
return _.map(initialData, function(element, index) {
|
||||||
|
return {
|
||||||
|
content: element.name + (element.unit ? ' [' + element.unit + ']' : ''),
|
||||||
|
id: element.key,
|
||||||
|
visible: false,
|
||||||
|
options: {
|
||||||
|
yAxisOrientation: index >= initialData.length / 2 ? 'right' : 'left'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var initializeChart = function() {
|
||||||
|
dataset = new vis.DataSet();
|
||||||
|
groups = new vis.DataSet(buildChartGroups());
|
||||||
|
graph = new vis.Graph2d(document.getElementById('user-activity'), dataset, groups, {
|
||||||
|
dataAxis: {
|
||||||
|
customRange: {
|
||||||
|
left: {
|
||||||
|
min: 0
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
min: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showMinorLabels: true
|
||||||
|
},
|
||||||
|
drawPoints: {
|
||||||
|
style: 'circle'
|
||||||
|
},
|
||||||
|
end: vis.moment(),
|
||||||
|
legend: true,
|
||||||
|
shaded: true,
|
||||||
|
start: CHART_START
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var refreshChart = function() {
|
||||||
|
var now = vis.moment();
|
||||||
|
var window = graph.getWindow();
|
||||||
|
var interval = window.end - window.start;
|
||||||
|
graph.setWindow(now - interval, now);
|
||||||
|
};
|
||||||
|
|
||||||
|
var refreshData = function(callback) {
|
||||||
|
if (! ($.isController('statistics') && $('#user-activity').isPresent())) {
|
||||||
|
clearInterval(refreshInterval);
|
||||||
|
} else {
|
||||||
|
var jqxhr = $.ajax({
|
||||||
|
dataType: 'json',
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
jqxhr.done(function(response) {
|
||||||
|
(callback || _.noop)(response);
|
||||||
|
setGroupVisibility(response);
|
||||||
|
updateChartData(response);
|
||||||
|
requestAnimationFrame(refreshChart);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var setGroupVisibility = function(response) {
|
||||||
|
_.each(response, function(data) {
|
||||||
|
groups.update({
|
||||||
|
id: data.key,
|
||||||
|
visible: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var updateChartData = function(response) {
|
||||||
|
_.each(response, function(data) {
|
||||||
|
dataset.add({
|
||||||
|
group: data.key,
|
||||||
|
x: vis.moment(),
|
||||||
|
y: data.data
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
refreshData(function (data) {
|
||||||
|
initialData = data;
|
||||||
|
$('#user-activity').parent().find('.spinner').hide();
|
||||||
|
initializeChart();
|
||||||
|
|
||||||
|
var refresh_interval = location.search.match(/interval=(\d+)/) ? parseInt(RegExp.$1) : DEFAULT_REFRESH_INTERVAL;
|
||||||
|
refreshInterval = setInterval(refreshData, refresh_interval);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@ -101,3 +101,34 @@ div.negative-result {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: #333;
|
||||||
|
|
||||||
|
margin: 100px auto;
|
||||||
|
-webkit-animation: sk-rotateplane 1.2s infinite ease-in-out;
|
||||||
|
animation: sk-rotateplane 1.2s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes sk-rotateplane {
|
||||||
|
0% { -webkit-transform: perspective(120px) }
|
||||||
|
50% { -webkit-transform: perspective(120px) rotateY(180deg) }
|
||||||
|
100% { -webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sk-rotateplane {
|
||||||
|
0% {
|
||||||
|
transform: perspective(120px) rotateX(0deg) rotateY(0deg);
|
||||||
|
-webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg)
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
|
||||||
|
-webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg)
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
|
||||||
|
-webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -13,4 +13,12 @@ class StatisticsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def graphs
|
||||||
|
authorize self
|
||||||
|
respond_to do |format|
|
||||||
|
format.html
|
||||||
|
format.json { render(json: graph_live_data) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -122,4 +122,22 @@ module StatisticsHelper
|
|||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def graph_live_data
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: 'active_in_last_hour',
|
||||||
|
name: t('statistics.entries.users.currently_active'),
|
||||||
|
data: ExternalUser.joins(:submissions)
|
||||||
|
.where(['submissions.created_at >= ?', DateTime.now - 5.minutes])
|
||||||
|
.distinct('external_users.id').count,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -1,2 +1,7 @@
|
|||||||
class StatisticsPolicy < AdminOnlyPolicy
|
class StatisticsPolicy < AdminOnlyPolicy
|
||||||
|
|
||||||
|
def graphs?
|
||||||
|
admin?
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
13
app/views/statistics/graphs.html.slim
Normal file
13
app/views/statistics/graphs.html.slim
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
- content_for :head do
|
||||||
|
= javascript_include_tag(asset_path('vis.min.js', type: :javascript))
|
||||||
|
= stylesheet_link_tag(asset_path('vis.min.css', type: :stylesheet))
|
||||||
|
|
||||||
|
.group
|
||||||
|
h1 User Activity
|
||||||
|
.spinner
|
||||||
|
.graph#user-activity
|
||||||
|
|
||||||
|
.group
|
||||||
|
h1 RFC Activity
|
||||||
|
.spinner
|
||||||
|
.graph#rfc-activity
|
@ -221,6 +221,7 @@ de:
|
|||||||
destroy_through_lti: Code-Abgabe
|
destroy_through_lti: Code-Abgabe
|
||||||
statistics:
|
statistics:
|
||||||
show: "Statistiken"
|
show: "Statistiken"
|
||||||
|
graphs: "Visualisierungen"
|
||||||
consumers:
|
consumers:
|
||||||
show:
|
show:
|
||||||
link: Konsument
|
link: Konsument
|
||||||
@ -755,3 +756,4 @@ de:
|
|||||||
with_comments: "Anfragen mit Kommentaren"
|
with_comments: "Anfragen mit Kommentaren"
|
||||||
users:
|
users:
|
||||||
currently_active: "Aktiv (5 Minuten)"
|
currently_active: "Aktiv (5 Minuten)"
|
||||||
|
currently_active60: "Aktiv (60 Minuten)"
|
||||||
|
@ -221,6 +221,7 @@ en:
|
|||||||
destroy_through_lti: Code Submission
|
destroy_through_lti: Code Submission
|
||||||
statistics:
|
statistics:
|
||||||
show: "Statistics"
|
show: "Statistics"
|
||||||
|
graphs: "Graphs"
|
||||||
consumers:
|
consumers:
|
||||||
show:
|
show:
|
||||||
link: Consumer
|
link: Consumer
|
||||||
@ -755,3 +756,4 @@ en:
|
|||||||
with_comments: "RfCs with Comments"
|
with_comments: "RfCs with Comments"
|
||||||
users:
|
users:
|
||||||
currently_active: "Active (5 minutes)"
|
currently_active: "Active (5 minutes)"
|
||||||
|
currently_active60: "Active (60 minutes)"
|
||||||
|
@ -43,6 +43,7 @@ Rails.application.routes.draw do
|
|||||||
get '/help', to: 'application#help'
|
get '/help', to: 'application#help'
|
||||||
|
|
||||||
get 'statistics/', to: 'statistics#show'
|
get 'statistics/', to: 'statistics#show'
|
||||||
|
get 'statistics/graphs', to: 'statistics#graphs'
|
||||||
|
|
||||||
concern :statistics do
|
concern :statistics do
|
||||||
member do
|
member do
|
||||||
|
Reference in New Issue
Block a user