Commit d9836e21 authored by Andreas Valder's avatar Andreas Valder
Browse files

Merge branch '289-hilfreichere-darstellung-von-statistiken' into 'master'

Resolve "Hilfreichere Darstellung von Statistiken"

Closes #289

See merge request !12
parents 7352503e 1902365f
......@@ -250,56 +250,65 @@ var moderator = {
}
}
},
plots: {
init: function() {
$(window).on("resize", moderator.plots.resize);
$(".plotlyresize").on("click", moderator.plots.resize);
moderator.plots.createplots(".plot-view")
},
resize: function() {
$(".plot-view").each(function () {Plotly.Plots.resize(this)});
},
createplots: function (selector) {
var l = $(selector);
for (var i = 0; i < l.length; i ++) {
if (!l[i].id)
l[i].id = "plot-"+i;
$(l[i]).html('<div class="plot-loader"></div>');
$.ajax({
divobj: l[i],
method: "GET",
url: l[i].dataset.url,
dataType: "json",
error: function (jqXHR, textStatus, errorThrow) {
$(this.divobj).html('<div class="plot-error">'+errorThrow+'</div>');
},
success: function (traces) {
var layout = {margin: {l: 30, r: 30, t: 10, b: 70, pad: 0}};
for (var i = 0; i < traces.length; i ++) {
traces[i].type = this.divobj.dataset.type;
}
if (this.divobj.dataset.type == "pie")
layout.showlegend = false;
traces.sort(function (a, b) {
asum = 0;
bsum = 0;
for (var i = 0; i < a.y.length; i++)
asum += a.y[i]
for (var i = 0; i < b.y.length; i++)
bsum += b.y[i]
return bsum-asum;
});
for (var i = 0; i < traces.length; i++) {
if (i > 30) {
traces[i].visible = "legendonly";
}
}
$(this.divobj).html("");
Plotly.newPlot(this.divobj.id, traces, layout, { "modeBarButtonsToRemove": ['sendDataToCloud','hoverCompareCartesian'], "displaylogo": false});
}
});
};
},
},
init: function () {
moderator.api.init();
moderator.editor.init();
moderator.permissioneditor.init();
moderator.plots.init();
}
};
$( document ).ready( function () {
moderator.init();
} );
$( document ).ready( function () {
var l = $(".plot-view");
for (var i = 0; i < l.length; i ++) {
if (!l[i].id)
l[i].id = "plot-"+i;
$(l[i]).html('<div class="plot-loader"></div>');
$.ajax({
divobj: l[i],
method: "GET",
url: l[i].dataset.url,
dataType: "json",
error: function (jqXHR, textStatus, errorThrow) {
$(this.divobj).html('<div class="plot-error">'+errorThrow+'</div>');
},
success: function (traces) {
var layout = {margin: {l: 30, r: 30, t: 10, b: 70, pad: 0}};
for (var i = 0; i < traces.length; i ++) {
traces[i].type = this.divobj.dataset.type;
}
if (this.divobj.dataset.type == "pie")
layout.showlegend = false;
traces.sort(function (a, b) {
asum = 0;
bsum = 0;
for (var i = 0; i < a.y.length; i++)
asum += a.y[i]
for (var i = 0; i < b.y.length; i++)
bsum += b.y[i]
return bsum-asum;
});
for (var i = 0; i < traces.length; i++)
if (i > 20)
traces[i].visible = "legendonly";
$(this.divobj).html("");
Plotly.newPlot(this.divobj.id, traces, layout, { "modeBarButtonsToRemove": ['sendDataToCloud','hoverCompareCartesian'], "displaylogo": false});
}
});
};
});
$(window).on("resize", function () {
$(".plot-view").each(function () {Plotly.Plots.resize(this)});
});
......@@ -2,12 +2,23 @@ from server import *
import json
from jobs import date_json_handler
from hashlib import md5
from datetime import datetime
@app.route('/internal/stats')
@app.route('/internal/stats/<semester>')
@register_navbar('Statistiken', icon='stats')
@mod_required
def stats():
return render_template('stats.html')
semester = query('SELECT DISTINCT semester from courses WHERE semester != ""');
for s in semester:
year = int(s['semester'][0:4])
if s['semester'].endswith('ss'):
s['from'] = datetime(year,4,1)
s['to'] = datetime(year,10,1)
if s['semester'].endswith('ws'):
s['from'] = datetime(year,10,1)
s['to'] = datetime(year+1,4,1)
return render_template('stats.html',semester=semester,filter=request.args.get('filter'))
statsqueries = {}
statsqueries['formats_views'] = "SELECT formats.description AS labels, count(DISTINCT log.id) AS `values` FROM log JOIN videos ON (videos.id = log.video) JOIN formats ON (formats.id = videos.video_format) GROUP BY formats.id"
......@@ -18,6 +29,7 @@ statsqueries['organizer_courses'] = "SELECT courses.organizer AS labels, count(c
statsqueries['categories_lectures'] = "SELECT courses.subject AS labels, count(lectures.id) AS `values` FROM lectures JOIN courses ON (courses.id = lectures.course_id) WHERE lectures.visible GROUP BY courses.subject ORDER BY `values` DESC LIMIT 100"
statsqueries['lecture_views'] = "SELECT lectures.time AS x, count(DISTINCT log.id) AS y FROM log JOIN videos ON (videos.id = log.video) JOIN lectures ON (lectures.id = videos.lecture_id) WHERE (lectures.course_id = ?) GROUP BY lectures.id ORDER BY lectures.time"
statsqueries['live_views'] = "SELECT hlslog.segment AS x, COUNT(DISTINCT hlslog.id) AS y FROM hlslog WHERE hlslog.lecture = ? GROUP BY hlslog.segment ORDER BY hlslog.segment"
statsqueries['lecture_totalviews'] = "SELECT 42"
def plotly_date_handler(obj):
return obj.strftime("%Y-%m-%d %H:%M:%S")
......@@ -45,10 +57,52 @@ def stats_viewsperday(req, param=""):
query_expr = 'SELECT date, trace, value AS y FROM logcache WHERE req = "%s" AND param = ? UNION SELECT * FROM (%s) AS cachetmp'
date_subexpr = 'SELECT CASE WHEN MAX(date) IS NULL THEN "2000-00-00" ELSE MAX(date) END AS t FROM `logcache` WHERE req = "%s" AND param = ?'
queries = {
'lecture': 'SELECT log.date AS date, formats.description AS trace, COUNT(DISTINCT log.id) AS y FROM log JOIN videos ON videos.id = log.video JOIN formats ON formats.id = videos.video_format WHERE log.date > %T AND videos.lecture_id = ? GROUP BY log.date, videos.video_format UNION SELECT log.date AS date, "total" AS trace, COUNT(DISTINCT log.id) AS y FROM log JOIN videos ON videos.id = log.video WHERE log.date > %T AND videos.lecture_id = ? GROUP BY log.date',
'course': 'SELECT log.date AS date, formats.description AS trace, COUNT(DISTINCT log.id) AS y FROM log JOIN videos ON videos.id = log.video JOIN lectures ON lectures.id = videos.lecture_id JOIN formats ON formats.id = videos.video_format WHERE log.date > %T AND lectures.course_id = ? GROUP BY log.date, videos.video_format UNION SELECT log.date AS date, "total" AS trace, COUNT(DISTINCT log.id) AS y FROM log JOIN videos ON videos.id = log.video JOIN lectures ON lectures.id = videos.lecture_id WHERE log.date > %T AND lectures.course_id = ? GROUP BY log.date',
'global': 'SELECT log.date AS date, formats.description AS trace, COUNT(DISTINCT log.id) AS y FROM log JOIN videos ON videos.id = log.video JOIN formats ON formats.id = videos.video_format WHERE log.date > %T GROUP BY log.date, videos.video_format UNION SELECT log.date AS date, "total" AS trace, COUNT(DISTINCT log.id) AS y FROM log WHERE log.date > %T GROUP BY log.date',
'courses': 'SELECT log.date AS date, courses.handle AS trace, COUNT(DISTINCT log.id) AS y FROM log JOIN videos ON videos.id = log.video JOIN lectures ON lectures.id = videos.lecture_id JOIN courses ON courses.id = lectures.course_id WHERE log.date > %T GROUP BY log.date, courses.id'
'lecture': # views per day per lecture (split per format)
'''SELECT log.date AS date, formats.description AS trace, COUNT(DISTINCT log.id) AS y
FROM log
JOIN videos ON videos.id = log.video
JOIN formats ON formats.id = videos.video_format
WHERE log.date > %T AND videos.lecture_id = ?
GROUP BY log.date, videos.video_format
UNION
SELECT log.date AS date, "total" AS trace, COUNT(DISTINCT log.id) AS y
FROM log JOIN videos ON videos.id = log.video
WHERE log.date > %T AND videos.lecture_id = ? GROUP BY log.date''',
'course': # views per day per format for a single course
'''SELECT log.date AS date, formats.description AS trace, COUNT(DISTINCT log.id) AS y
FROM log JOIN videos ON videos.id = log.video
JOIN lectures ON lectures.id = videos.lecture_id
JOIN formats ON formats.id = videos.video_format
WHERE log.date > %T AND lectures.course_id = ?
GROUP BY log.date, videos.video_format
UNION
SELECT log.date AS date, "total" AS trace, COUNT(DISTINCT log.id) AS y
FROM log
JOIN videos ON videos.id = log.video
JOIN lectures ON lectures.id = videos.lecture_id
WHERE log.date > %T AND lectures.course_id = ?
GROUP BY log.date''',
'global': # views per format per day (split per format)
'''SELECT log.date AS date, formats.description AS trace, COUNT(DISTINCT log.id) AS y
FROM log
JOIN videos ON videos.id = log.video
JOIN formats ON formats.id = videos.video_format
WHERE log.date > %T GROUP BY log.date, videos.video_format
UNION
SELECT log.date AS date, "total" AS trace, COUNT(DISTINCT log.id) AS y
FROM log
WHERE log.date > %T
GROUP BY log.date''',
'courses': # views per course per day
'''SELECT log.date AS date, courses.handle AS trace, COUNT(DISTINCT log.id) AS y
FROM log JOIN videos ON videos.id = log.video
JOIN lectures ON lectures.id = videos.lecture_id
JOIN courses ON courses.id = lectures.course_id
WHERE log.date > %T
GROUP BY log.date, courses.id'''
}
expr = queries[req].replace('%T', '"'+query(date_subexpr%('viewsperday.'+req), param)[0]['t']+'"')
params = [param]*expr.count('?')
......@@ -71,6 +125,12 @@ def stats_viewsperday(req, param=""):
data[row['date']][row['trace']] = row['y']
end = date.today()
res = [{'name': trace, 'x': [], 'y': []} for trace in traces]
filter = request.args.get('filter')
if filter:
filter = filter.split('-')
start = date.fromtimestamp(int(filter[0]))
end = date.fromtimestamp(int(filter[1]))
while start and start <= end:
for trace in res:
trace['x'].append(start)
......
......@@ -35,7 +35,7 @@
</tbody>
</table>
</div>
{% if ismod() %}
{% if ismod() %}
<div class="col-xs-12" style="margin-top: 20px">
<table class="table-condensed table-top-aligned">
<tbody>
......@@ -51,11 +51,28 @@
</tbody>
</table>
</div>
<div class="col-xs-6 plot-view" data-url="{{url_for('stats_viewsperday', req="course", param=course.id)}}"></div>
<div class="col-xs-6 plot-view" data-type="bar" data-url="{{url_for('stats_generic', req="lecture_views", param=course.id)}}"></div>
{% endif %}
{% endif %}
</div>
</div>
{% if ismod() %}
<div class="panel panel-default">
<div class="panel-heading">
<a data-toggle="collapse" href="#statspanel" class="plotlyresize"><h1 class="panel-title">Statistiken</h1></a>
</div>
<div class="row panel-body collapse out panel-collapse" id="statspanel">
<div class="col-md-6 col-xs-12">
<p class="text-center">Zuschauer pro Tag</p>
<div class="plot-view" data-url="{{url_for('stats_viewsperday', req="course", param=course.id)}}"></div>
</div>
<div class="col-md-6 col-xs-12">
<p class="text-center">Zuschauer pro Termin</p>
<div class="plot-view" data-type="bar" data-url="{{url_for('stats_generic', req="lecture_views", param=course.id)}}"></div>
</div>
</div>
</div>
{% endif %}
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="panel-title">Videos{% if ismod() %} <a class="btn btn-default" style="margin-right: 5px;" href="{{ url_for('create', table='lectures', time=datetime.now(), title='Noch kein Titel', visible='0', course_id=course.id, ref=request.url) }}">Neuer Termin</a><a class="btn btn-default" style="margin-right: 5px;" href="{{url_for('list_import_sources', id=course['id'])}}">Campus Import</a>{% endif %} <a class="fa fa-rss-square pull-right" aria-hidden="true" href="{{url_for('feed', handle=course.handle)}}" style="text-decoration: none"></a> </h1>
......@@ -66,4 +83,23 @@
{% endfor %}
</ul>
</div>
<script>
$.ajax({
method: "GET",
url: "{{url_for('stats_generic', req="lecture_views", param=course.id)}}",
dataType: "json",
success: function (traces) {
var dates={};
var t = traces[0]
for (var i=0; i<t.x.length; i++) {
dates[t.x[i]] = t.y[i];
}
var counter = $(".viewcounter");
for (var i=0; i<counter.length; i++) {
$(counter[i]).text(dates[$(counter[i]).data("lecturedate")]);
}
}
});
</script>
{% endblock %}
......@@ -70,13 +70,30 @@
{% endfor %}
</table>
</div>
{% if ismod() %}
<div class="col-xs-12 plot-view" data-url="{{url_for('stats_viewsperday', req="lecture", param=lecture.id)}}"></div>
<div class="col-xs-12 plot-view" data-url="{{url_for('stats_generic', req="live_views", param=lecture.id)}}"></div>
{% endif %}
</div>
</div>
</div>
{% if ismod() %}
<div class="panel panel-default">
<div class="panel-heading">
<a data-toggle="collapse" href="#statspanel" class="plotlyresize"><h1 class="panel-title">Statistiken</h1></a>
</div>
<div class="row panel-body collapse out panel-collapse" id="statspanel">
<div class="col-md-6 col-xs-12">
<p class="text-center">Zuschauer pro Tag</p>
<div class="plot-view" data-url="{{url_for('stats_viewsperday', req="lecture", param=lecture.id)}}"></div>
</div>
<div class="col-md-6 col-xs-12">
<p class="text-center">Zuschauer im Livestream</p>
<div class="plot-view" data-url="{{url_for('stats_generic', req="live_views", param=lecture.id)}}"></div>
</div>
</div>
</div>
{% endif %}
<script>
function hintchapterclick (src) {
$.ajax({
......@@ -132,4 +149,4 @@ $(document).ready(function() {
});
});
</script>
{% endblock %}
{% endblock %}
\ No newline at end of file
......@@ -226,16 +226,25 @@ $('#embedcodebtn').popover(
<li>Hörsaal: {{ moderator_editor(['lectures',lecture.id,'place'], lecture.place) }} </li>
{% endif %}
</ul>
<ul class="list-inline col-sm-4 col-xs-12">
<li class="dropdown">
{{ video_download_btn(videos) }}
</li>
<li class="pull-right">
{{ moderator_permissioneditor('lecture', lecture.id, lecture.perm, global_permissions) }}
<ul class="col-sm-4 col-xs-12 list-unstyled">
<li>
<ul class="list-inline">
<li class="dropdown">
{{ video_download_btn(videos) }}
</li>
<li class="pull-right">
{{ moderator_permissioneditor('lecture', lecture.id, lecture.perm, global_permissions) }}
</li>
<li class="pull-right">
{{ moderator_delete(['lectures',lecture.id,'deleted']) }}
</li>
</ul>
</li>
{% if ismod() %}
<li class="pull-right">
{{ moderator_delete(['lectures',lecture.id,'deleted']) }}
<p>Zuschauer: <span data-lectureid="{{ lecture.id }}" data-lecturedate="{{ lecture.time }}" class="viewcounter">loading...</span></p>
</li>
{% endif %}
</ul>
{% else %}
<div class="col-sm-2 col-xs-12">
......
......@@ -3,7 +3,7 @@
<div class="panel-group">
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="panel-title">Statistiken</h1>
<h1 class="panel-title">Gesamt</h1>
</div>
<div class="panel-body">
<div class="row col-xs-12">
......@@ -11,13 +11,31 @@
<div class="col-xs-12 col-md-6 plot-view" data-url="{{url_for('stats_generic', req="lectures_count")}}"></div>
<div class="col-xs-12 col-md-6 plot-view" data-type="pie" data-url="{{url_for('stats_generic', req="categories_courses")}}"></div>
<div class="col-xs-12 col-md-6 plot-view" data-type="pie" data-url="{{url_for('stats_generic', req="categories_lectures")}}"></div>
<!--<div class="col-xs-12 col-md-6 plot-view" data-type="pie" data-url="{{url_for('stats_generic', req="organizer_courses")}}"></div>-->
<!--<div class="col-xs-12 col-md-6 plot-view" data-type="pie" data-url="{{url_for('stats_generic', req="formats_views")}}"></div>-->
<div class="col-xs-12 plot-view" data-url="{{url_for('stats_viewsperday', req="global")}}"></div>
<div class="col-xs-12 plot-view" data-url="{{url_for('stats_viewsperday', req="courses")}}"></div>
<div class="col-xs-12 col-md-6 plot-view" data-type="pie" data-url="{{url_for('stats_generic', req="organizer_courses")}}"></div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<span class="panel-title"><a name="semesterstats"></a>Semester <select id="semesterselect" name="semester"><option value="">alle</option></select></span>
</div>
<div class="panel-body" >
<div class="row col-xs-12">
<div class="col-xs-12 plot-view" data-url="{{url_for('stats_viewsperday', req="global", filter=filter)}}"></div>
<div class="col-xs-12 plot-view" data-url="{{url_for('stats_viewsperday', req="courses", filter=filter)}}"></div>
</div>
</div>
</div>
</div>
<script>
$( document ).ready(function () {
{% for s in semester if s.semester != '' %}
$("#semesterselect").append('<option value="{{ s.from.timestamp()|int }}-{{ s.to.timestamp()|int }}">{{ s.semester }}</option>');
{% endfor %}
$("#semesterselect").val("{{ filter }}")
$("#semesterselect").on("change", function () {
window.location.href="{{ url_for('stats') }}?filter="+$("#semesterselect").val()+"#semesterstats";
});
});
</script>
{% endblock %}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment