Add live graphs for active users and submission volume

This commit is contained in:
Maximilian Grundke
2018-04-11 13:43:10 +02:00
parent 8cdf909188
commit 2a4e9bc94b
9 changed files with 184 additions and 0 deletions

View 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);
});
}
});

View File

@ -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);
}
}

View File

@ -13,4 +13,12 @@ class StatisticsController < ApplicationController
end
end
def graphs
authorize self
respond_to do |format|
format.html
format.json { render(json: graph_live_data) }
end
end
end

View File

@ -122,4 +122,22 @@ module StatisticsHelper
]
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

View File

@ -1,2 +1,7 @@
class StatisticsPolicy < AdminOnlyPolicy
def graphs?
admin?
end
end

View 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

View File

@ -221,6 +221,7 @@ de:
destroy_through_lti: Code-Abgabe
statistics:
show: "Statistiken"
graphs: "Visualisierungen"
consumers:
show:
link: Konsument
@ -755,3 +756,4 @@ de:
with_comments: "Anfragen mit Kommentaren"
users:
currently_active: "Aktiv (5 Minuten)"
currently_active60: "Aktiv (60 Minuten)"

View File

@ -221,6 +221,7 @@ en:
destroy_through_lti: Code Submission
statistics:
show: "Statistics"
graphs: "Graphs"
consumers:
show:
link: Consumer
@ -755,3 +756,4 @@ en:
with_comments: "RfCs with Comments"
users:
currently_active: "Active (5 minutes)"
currently_active60: "Active (60 minutes)"

View File

@ -43,6 +43,7 @@ Rails.application.routes.draw do
get '/help', to: 'application#help'
get 'statistics/', to: 'statistics#show'
get 'statistics/graphs', to: 'statistics#graphs'
concern :statistics do
member do