mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-07 05:31:15 -07:00
Graphs now have the option to show duration as well as play count.
This commit is contained in:
parent
94d2d04bf9
commit
c28d403186
10 changed files with 773 additions and 126 deletions
|
@ -10,6 +10,14 @@
|
||||||
<span><i class="fa fa-bar-chart"></i> Graphs</span>
|
<span><i class="fa fa-bar-chart"></i> Graphs</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-bar hidden-xs">
|
<div class="button-bar hidden-xs">
|
||||||
|
<div class="btn-group" data-toggle="buttons" id="yaxis-selection">
|
||||||
|
<label class="btn btn-dark active">
|
||||||
|
<input type="radio" name="yaxis-options" id="yaxis-count" value="plays" autocomplete="off" checked> Play Count
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-dark">
|
||||||
|
<input type="radio" name="yaxis-options" id="yaxis-duration" value="duration" autocomplete="off"> Play Duration
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="btn-group" data-toggle="buttons" id="days-selection">
|
<div class="btn-group" data-toggle="buttons" id="days-selection">
|
||||||
<label class="btn btn-dark active">
|
<label class="btn btn-dark active">
|
||||||
<input type="radio" name="date-options" id="graph-30" value="30" autocomplete="off" checked> 30 days
|
<input type="radio" name="date-options" id="graph-30" value="30" autocomplete="off" checked> 30 days
|
||||||
|
@ -25,15 +33,15 @@
|
||||||
</div>
|
</div>
|
||||||
<div class='table-card-back'>
|
<div class='table-card-back'>
|
||||||
<ul class="nav nav-pills" role="tablist">
|
<ul class="nav nav-pills" role="tablist">
|
||||||
<li role="presentation" class="active"><a id="watch-count-graphs" href="#tabs-1" aria-controls="tabs-1" role="tab" data-toggle="tab">Play Counts</a></li>
|
<li role="presentation" class="active"><a id="watch-count-graphs" href="#tabs-1" aria-controls="tabs-1" role="tab" data-toggle="tab">Plays by period</a></li>
|
||||||
<li role="presentation"><a id="watch-totals-graphs" href="#tabs-2" aria-controls="tabs-2" role="tab" data-toggle="tab">Play Totals</a></li>
|
<li role="presentation"><a id="watch-totals-graphs" href="#tabs-3" aria-controls="tabs-2" role="tab" data-toggle="tab">Play Totals</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div role="tabpanel" class="tab-pane active" id="tabs-1">
|
<div role="tabpanel" class="tab-pane active" id="tabs-1">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="padded-header">
|
<div class="padded-header">
|
||||||
<h3><i class="fa fa-history"></i> Daily Play Count <small>Last <span class="days">30</span> days</small></h3>
|
<h3><i class="fa fa-history"></i> Daily <span class="yaxis-text">Play count</span> <small>Last <span class="days">30</span> days</small></h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="graphs-instance">
|
<div class="graphs-instance">
|
||||||
<div class="watch-chart" id="chart_div_plays_by_day">
|
<div class="watch-chart" id="chart_div_plays_by_day">
|
||||||
|
@ -46,7 +54,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="padded-header">
|
<div class="padded-header">
|
||||||
<h3><i class="fa fa-calendar"></i> Plays by day of week <small>Last <span class="days">30</span> days</small></h3>
|
<h3><i class="fa fa-calendar"></i> <span class="yaxis-text">Play count</span> by day of week <small>Last <span class="days">30</span> days</small></h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="graphs-instance">
|
<div class="graphs-instance">
|
||||||
<div class="watch-chart" id="chart_div_plays_by_dayofweek" style="float: left;">
|
<div class="watch-chart" id="chart_div_plays_by_dayofweek" style="float: left;">
|
||||||
|
@ -58,7 +66,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="padded-header">
|
<div class="padded-header">
|
||||||
<h3><i class="fa fa-clock-o"></i> Plays by hour of day <small>Last <span class="days">30</span> days</small></h3>
|
<h3><i class="fa fa-clock-o"></i> <span class="yaxis-text">Play count</span> by hour of day <small>Last <span class="days">30</span> days</small></h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="graphs-instance">
|
<div class="graphs-instance">
|
||||||
<div class="watch-chart" id="chart_div_plays_by_hourofday">
|
<div class="watch-chart" id="chart_div_plays_by_hourofday">
|
||||||
|
@ -72,7 +80,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="padded-header">
|
<div class="padded-header">
|
||||||
<h3><i class="fa fa-television"></i> Plays by top 10 platforms <small>Last <span class="days">30</span> days</small></h3>
|
<h3><i class="fa fa-television"></i> <span class="yaxis-text">Play count</span> by top 10 platforms <small>Last <span class="days">30</span> days</small></h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="graphs-instance">
|
<div class="graphs-instance">
|
||||||
<div class="watch-chart" id="chart_div_plays_by_platform" style="float: left;">
|
<div class="watch-chart" id="chart_div_plays_by_platform" style="float: left;">
|
||||||
|
@ -84,7 +92,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="padded-header">
|
<div class="padded-header">
|
||||||
<h3><i class="fa fa-user"></i> Plays by top 10 users <small>Last <span class="days">30</span> days</small></h3>
|
<h3><i class="fa fa-user"></i> <span class="yaxis-text">Play count</span> by top 10 users <small>Last <span class="days">30</span> days</small></h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="graphs-instance">
|
<div class="graphs-instance">
|
||||||
<div class="watch-chart" id="chart_div_plays_by_user">
|
<div class="watch-chart" id="chart_div_plays_by_user">
|
||||||
|
@ -97,7 +105,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div role="tabpanel" class="tab-pane" id="tabs-2">
|
<div role="tabpanel" class="tab-pane" id="tabs-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="padded-header">
|
<div class="padded-header">
|
||||||
|
@ -121,6 +129,7 @@
|
||||||
|
|
||||||
<%def name="javascriptIncludes()">
|
<%def name="javascriptIncludes()">
|
||||||
<script src="interfaces/default/js/moment-with-locale.js"></script>
|
<script src="interfaces/default/js/moment-with-locale.js"></script>
|
||||||
|
<script src="interfaces/default/js/moment-duration-format.js"></script>
|
||||||
<script src="interfaces/default/js/highcharts/js/highcharts.js"></script>
|
<script src="interfaces/default/js/highcharts/js/highcharts.js"></script>
|
||||||
<script src="interfaces/default/js/graphs/plays_by_day.js"></script>
|
<script src="interfaces/default/js/graphs/plays_by_day.js"></script>
|
||||||
<script src="interfaces/default/js/graphs/plays_by_dayofweek.js"></script>
|
<script src="interfaces/default/js/graphs/plays_by_dayofweek.js"></script>
|
||||||
|
@ -132,13 +141,15 @@
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
|
||||||
var current_range = 30;
|
var current_range = 30;
|
||||||
|
var yaxis = 'plays';
|
||||||
|
|
||||||
function loadGraphsTab1(time_range) {
|
function loadGraphsTab1(time_range, yaxis) {
|
||||||
|
setGraphFormat(yaxis);
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "get_plays_by_date",
|
url: "get_plays_by_date",
|
||||||
type: 'get',
|
type: 'get',
|
||||||
data: { time_range: time_range },
|
data: { time_range: time_range, y_axis: yaxis },
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
cache: false,
|
cache: false,
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
|
@ -156,7 +167,7 @@
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "get_plays_by_dayofweek",
|
url: "get_plays_by_dayofweek",
|
||||||
type: 'get',
|
type: 'get',
|
||||||
data: { time_range: time_range },
|
data: { time_range: time_range, y_axis: yaxis },
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
cache: false,
|
cache: false,
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
|
@ -169,7 +180,7 @@
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "get_plays_by_hourofday",
|
url: "get_plays_by_hourofday",
|
||||||
type: 'get',
|
type: 'get',
|
||||||
data: { time_range: time_range },
|
data: { time_range: time_range, y_axis: yaxis },
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
cache: false,
|
cache: false,
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
|
@ -182,7 +193,7 @@
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "get_plays_by_top_10_platforms",
|
url: "get_plays_by_top_10_platforms",
|
||||||
type: 'get',
|
type: 'get',
|
||||||
data: { time_range: time_range },
|
data: { time_range: time_range, y_axis: yaxis },
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
cache: false,
|
cache: false,
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
|
@ -195,7 +206,7 @@
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "get_plays_by_top_10_users",
|
url: "get_plays_by_top_10_users",
|
||||||
type: 'get',
|
type: 'get',
|
||||||
data: { time_range: time_range },
|
data: { time_range: time_range, y_axis: yaxis },
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
cache: false,
|
cache: false,
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
|
@ -206,10 +217,13 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadGraphsTab2() {
|
function loadGraphsTab3(yaxis) {
|
||||||
|
setGraphFormat(yaxis);
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "get_plays_per_month",
|
url: "get_plays_per_month",
|
||||||
type: 'get',
|
type: 'get',
|
||||||
|
data: { y_axis: yaxis },
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
cache: false,
|
cache: false,
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
|
@ -226,25 +240,106 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set initial state
|
// Set initial state
|
||||||
loadGraphsTab1(current_range);
|
loadGraphsTab1(current_range, yaxis);
|
||||||
|
|
||||||
$('#watch-count-graphs').click(function() {
|
$('#watch-count-graphs').click(function() {
|
||||||
loadGraphsTab1(current_range);
|
loadGraphsTab1(current_range, yaxis);
|
||||||
$('#days-selection').show();
|
$('#days-selection').show();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#watch-totals-graphs').click(function() {
|
$('#watch-totals-graphs').click(function() {
|
||||||
loadGraphsTab2();
|
loadGraphsTab3(yaxis);
|
||||||
$('#days-selection').hide();
|
$('#days-selection').hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#days-selection').on('change', function() {
|
$('#days-selection').on('change', function() {
|
||||||
current_range = $('input[name=date-options]:checked', '#days-selection').val();
|
current_range = $('input[name=date-options]:checked', '#days-selection').val();
|
||||||
loadGraphsTab1(current_range);
|
loadGraphsTab1(current_range, yaxis);
|
||||||
$('.days').html(current_range);
|
$('.days').html(current_range);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#yaxis-selection').on('change', function() {
|
||||||
|
yaxis = $('input[name=yaxis-options]:checked', '#yaxis-selection').val();
|
||||||
|
loadGraphsTab1(current_range, yaxis);
|
||||||
|
loadGraphsTab3(yaxis);
|
||||||
|
$('.days').html(current_range);
|
||||||
|
});
|
||||||
|
|
||||||
|
function setGraphFormat(type) {
|
||||||
|
if (type === 'plays') {
|
||||||
|
yaxis_format = function() { return this.value; };
|
||||||
|
hc_plays_by_day_options.yAxis.labels.formatter = yaxis_format;
|
||||||
|
hc_plays_by_dayofweek_options.yAxis.labels.formatter = yaxis_format;
|
||||||
|
hc_plays_by_hourofday_options.yAxis.labels.formatter = yaxis_format;
|
||||||
|
hc_plays_by_platform_options.yAxis.labels.formatter = yaxis_format;
|
||||||
|
hc_plays_by_user_options.yAxis.labels.formatter = yaxis_format;
|
||||||
|
hc_plays_by_month_options.yAxis.labels.formatter = yaxis_format;
|
||||||
|
|
||||||
|
tooltip_format = function() {
|
||||||
|
if (moment(this.x).isValid()) {
|
||||||
|
var s = '<b>'+ moment(this.x).format("ddd MMM D") +'</b>';
|
||||||
|
} else {
|
||||||
|
var s = '<b>'+ this.x +'</b>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$.each(this.points, function(i, point) {
|
||||||
|
s += '<br/>'+point.series.name+': '+point.y;
|
||||||
|
});
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
stack_labels_format = function() {
|
||||||
|
return this.total;
|
||||||
|
}
|
||||||
|
hc_plays_by_day_options.tooltip.formatter = tooltip_format;
|
||||||
|
hc_plays_by_dayofweek_options.tooltip.formatter = tooltip_format;
|
||||||
|
hc_plays_by_hourofday_options.tooltip.formatter = tooltip_format;
|
||||||
|
hc_plays_by_platform_options.tooltip.formatter = tooltip_format;
|
||||||
|
hc_plays_by_user_options.tooltip.formatter = tooltip_format;
|
||||||
|
hc_plays_by_month_options.tooltip.formatter = tooltip_format;
|
||||||
|
hc_plays_by_month_options.yAxis.stackLabels.formatter = stack_labels_format;
|
||||||
|
|
||||||
|
$('.yaxis-text').html('Play count');
|
||||||
|
} else {
|
||||||
|
yaxis_format = function() { return moment.duration(this.value, 'seconds').format("m [mins]"); };
|
||||||
|
hc_plays_by_day_options.yAxis.labels.formatter = yaxis_format;
|
||||||
|
hc_plays_by_dayofweek_options.yAxis.labels.formatter = yaxis_format;
|
||||||
|
hc_plays_by_hourofday_options.yAxis.labels.formatter = yaxis_format;
|
||||||
|
hc_plays_by_platform_options.yAxis.labels.formatter = yaxis_format;
|
||||||
|
hc_plays_by_user_options.yAxis.labels.formatter = yaxis_format;
|
||||||
|
hc_plays_by_month_options.yAxis.labels.formatter = yaxis_format;
|
||||||
|
|
||||||
|
tooltip_format = function() {
|
||||||
|
if (moment(this.x).isValid()) {
|
||||||
|
var s = '<b>'+ moment(this.x).format("ddd MMM D") +'</b>';
|
||||||
|
} else {
|
||||||
|
var s = '<b>'+ this.x +'</b>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$.each(this.points, function(i, point) {
|
||||||
|
s += '<br/>'+point.series.name+': '+moment.duration(point.y, 'seconds').format('D [days] H [hrs] m [mins]');
|
||||||
|
});
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
stack_labels_format = function() {
|
||||||
|
if (moment(this.total).isValid()) {
|
||||||
|
console.log(this);
|
||||||
|
var s = moment.duration(this.total, 'seconds').format("H [hrs] m [mins]");
|
||||||
|
} else {
|
||||||
|
var s = this.total;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
hc_plays_by_day_options.tooltip.formatter = tooltip_format;
|
||||||
|
hc_plays_by_dayofweek_options.tooltip.formatter = tooltip_format;
|
||||||
|
hc_plays_by_hourofday_options.tooltip.formatter = tooltip_format;
|
||||||
|
hc_plays_by_platform_options.tooltip.formatter = tooltip_format;
|
||||||
|
hc_plays_by_user_options.tooltip.formatter = tooltip_format;
|
||||||
|
hc_plays_by_month_options.tooltip.formatter = tooltip_format;
|
||||||
|
hc_plays_by_month_options.yAxis.stackLabels.formatter = stack_labels_format;
|
||||||
|
|
||||||
|
$('.yaxis-text').html('Play duration');
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
|
@ -47,15 +47,6 @@ var hc_plays_by_day_options = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
formatter: function() {
|
|
||||||
var monthStr = moment(this.x).format("ddd MMM D");
|
|
||||||
var s = '<b>'+ monthStr +'</b>';
|
|
||||||
|
|
||||||
$.each(this.points, function(i, point) {
|
|
||||||
s += '<br/>'+point.series.name+': '+point.y;
|
|
||||||
});
|
|
||||||
return s;
|
|
||||||
},
|
|
||||||
shared: true
|
shared: true
|
||||||
},
|
},
|
||||||
series: [{}]
|
series: [{}]
|
||||||
|
|
|
@ -49,7 +49,7 @@ var hc_plays_by_dayofweek_options = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
shared: true
|
||||||
},
|
},
|
||||||
series: [{}]
|
series: [{}]
|
||||||
};
|
};
|
|
@ -49,7 +49,7 @@ var hc_plays_by_hourofday_options = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
shared: true
|
||||||
},
|
},
|
||||||
series: [{}]
|
series: [{}]
|
||||||
};
|
};
|
|
@ -65,15 +65,6 @@ var hc_plays_by_month_options = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
formatter: function() {
|
|
||||||
var monthStr = moment(this.x).format("MMM YYYY");
|
|
||||||
var s = '<b>'+ monthStr +'</b>';
|
|
||||||
|
|
||||||
$.each(this.points, function(i, point) {
|
|
||||||
s += '<br/>'+point.series.name+': '+point.y;
|
|
||||||
});
|
|
||||||
return s;
|
|
||||||
},
|
|
||||||
shared: true
|
shared: true
|
||||||
},
|
},
|
||||||
series: [{}]
|
series: [{}]
|
||||||
|
|
|
@ -49,7 +49,7 @@ var hc_plays_by_platform_options = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
shared: true
|
||||||
},
|
},
|
||||||
series: [{}]
|
series: [{}]
|
||||||
};
|
};
|
|
@ -49,7 +49,7 @@ var hc_plays_by_user_options = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
shared: true
|
||||||
},
|
},
|
||||||
series: [{}]
|
series: [{}]
|
||||||
};
|
};
|
482
data/interfaces/default/js/moment-duration-format.js
Executable file
482
data/interfaces/default/js/moment-duration-format.js
Executable file
|
@ -0,0 +1,482 @@
|
||||||
|
/*! Moment Duration Format v1.3.0
|
||||||
|
* https://github.com/jsmreese/moment-duration-format
|
||||||
|
* Date: 2014-07-15
|
||||||
|
*
|
||||||
|
* Duration format plugin function for the Moment.js library
|
||||||
|
* http://momentjs.com/
|
||||||
|
*
|
||||||
|
* Copyright 2014 John Madhavan-Reese
|
||||||
|
* Released under the MIT license
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function (root, undefined) {
|
||||||
|
|
||||||
|
// repeatZero(qty)
|
||||||
|
// returns "0" repeated qty times
|
||||||
|
function repeatZero(qty) {
|
||||||
|
var result = "";
|
||||||
|
|
||||||
|
// exit early
|
||||||
|
// if qty is 0 or a negative number
|
||||||
|
// or doesn't coerce to an integer
|
||||||
|
qty = parseInt(qty, 10);
|
||||||
|
if (!qty || qty < 1) { return result; }
|
||||||
|
|
||||||
|
while (qty) {
|
||||||
|
result += "0";
|
||||||
|
qty -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// padZero(str, len [, isRight])
|
||||||
|
// pads a string with zeros up to a specified length
|
||||||
|
// will not pad a string if its length is aready
|
||||||
|
// greater than or equal to the specified length
|
||||||
|
// default output pads with zeros on the left
|
||||||
|
// set isRight to `true` to pad with zeros on the right
|
||||||
|
function padZero(str, len, isRight) {
|
||||||
|
if (str == null) { str = ""; }
|
||||||
|
str = "" + str;
|
||||||
|
|
||||||
|
return (isRight ? str : "") + repeatZero(len - str.length) + (isRight ? "" : str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// isArray
|
||||||
|
function isArray(array) {
|
||||||
|
return Object.prototype.toString.call(array) === "[object Array]";
|
||||||
|
}
|
||||||
|
|
||||||
|
// isObject
|
||||||
|
function isObject(obj) {
|
||||||
|
return Object.prototype.toString.call(obj) === "[object Object]";
|
||||||
|
}
|
||||||
|
|
||||||
|
// findLast
|
||||||
|
function findLast(array, callback) {
|
||||||
|
var index = array.length;
|
||||||
|
|
||||||
|
while (index -= 1) {
|
||||||
|
if (callback(array[index])) { return array[index]; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// find
|
||||||
|
function find(array, callback) {
|
||||||
|
var index = 0,
|
||||||
|
max = array.length,
|
||||||
|
match;
|
||||||
|
|
||||||
|
if (typeof callback !== "function") {
|
||||||
|
match = callback;
|
||||||
|
callback = function (item) {
|
||||||
|
return item === match;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
while (index < max) {
|
||||||
|
if (callback(array[index])) { return array[index]; }
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// each
|
||||||
|
function each(array, callback) {
|
||||||
|
var index = 0,
|
||||||
|
max = array.length;
|
||||||
|
|
||||||
|
if (!array || !max) { return; }
|
||||||
|
|
||||||
|
while (index < max) {
|
||||||
|
if (callback(array[index], index) === false) { return; }
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// map
|
||||||
|
function map(array, callback) {
|
||||||
|
var index = 0,
|
||||||
|
max = array.length,
|
||||||
|
ret = [];
|
||||||
|
|
||||||
|
if (!array || !max) { return ret; }
|
||||||
|
|
||||||
|
while (index < max) {
|
||||||
|
ret[index] = callback(array[index], index);
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pluck
|
||||||
|
function pluck(array, prop) {
|
||||||
|
return map(array, function (item) {
|
||||||
|
return item[prop];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// compact
|
||||||
|
function compact(array) {
|
||||||
|
var ret = [];
|
||||||
|
|
||||||
|
each(array, function (item) {
|
||||||
|
if (item) { ret.push(item); }
|
||||||
|
});
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// unique
|
||||||
|
function unique(array) {
|
||||||
|
var ret = [];
|
||||||
|
|
||||||
|
each(array, function (_a) {
|
||||||
|
if (!find(ret, _a)) { ret.push(_a); }
|
||||||
|
});
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// intersection
|
||||||
|
function intersection(a, b) {
|
||||||
|
var ret = [];
|
||||||
|
|
||||||
|
each(a, function (_a) {
|
||||||
|
each(b, function (_b) {
|
||||||
|
if (_a === _b) { ret.push(_a); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return unique(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rest
|
||||||
|
function rest(array, callback) {
|
||||||
|
var ret = [];
|
||||||
|
|
||||||
|
each(array, function (item, index) {
|
||||||
|
if (!callback(item)) {
|
||||||
|
ret = array.slice(index);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initial
|
||||||
|
function initial(array, callback) {
|
||||||
|
var reversed = array.slice().reverse();
|
||||||
|
|
||||||
|
return rest(reversed, callback).reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// extend
|
||||||
|
function extend(a, b) {
|
||||||
|
for (var key in b) {
|
||||||
|
if (b.hasOwnProperty(key)) { a[key] = b[key]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// define internal moment reference
|
||||||
|
var moment;
|
||||||
|
|
||||||
|
if (typeof require === "function") {
|
||||||
|
try { moment = require('moment'); }
|
||||||
|
catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!moment && root.moment) {
|
||||||
|
moment = root.moment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!moment) {
|
||||||
|
throw "Moment Duration Format cannot find Moment.js";
|
||||||
|
}
|
||||||
|
|
||||||
|
// moment.duration.format([template] [, precision] [, settings])
|
||||||
|
moment.duration.fn.format = function () {
|
||||||
|
|
||||||
|
var tokenizer, tokens, types, typeMap, momentTypes, foundFirst, trimIndex,
|
||||||
|
args = [].slice.call(arguments),
|
||||||
|
settings = extend({}, this.format.defaults),
|
||||||
|
// keep a shadow copy of this moment for calculating remainders
|
||||||
|
remainder = moment.duration(this);
|
||||||
|
|
||||||
|
// add a reference to this duration object to the settings for use
|
||||||
|
// in a template function
|
||||||
|
settings.duration = this;
|
||||||
|
|
||||||
|
// parse arguments
|
||||||
|
each(args, function (arg) {
|
||||||
|
if (typeof arg === "string" || typeof arg === "function") {
|
||||||
|
settings.template = arg;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof arg === "number") {
|
||||||
|
settings.precision = arg;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isObject(arg)) {
|
||||||
|
extend(settings, arg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// types
|
||||||
|
types = settings.types = (isArray(settings.types) ? settings.types : settings.types.split(" "));
|
||||||
|
|
||||||
|
// template
|
||||||
|
if (typeof settings.template === "function") {
|
||||||
|
settings.template = settings.template.apply(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenizer regexp
|
||||||
|
tokenizer = new RegExp(map(types, function (type) {
|
||||||
|
return settings[type].source;
|
||||||
|
}).join("|"), "g");
|
||||||
|
|
||||||
|
// token type map function
|
||||||
|
typeMap = function (token) {
|
||||||
|
return find(types, function (type) {
|
||||||
|
return settings[type].test(token);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// tokens array
|
||||||
|
tokens = map(settings.template.match(tokenizer), function (token, index) {
|
||||||
|
var type = typeMap(token),
|
||||||
|
length = token.length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
index: index,
|
||||||
|
length: length,
|
||||||
|
|
||||||
|
// replace escaped tokens with the non-escaped token text
|
||||||
|
token: (type === "escape" ? token.replace(settings.escape, "$1") : token),
|
||||||
|
|
||||||
|
// ignore type on non-moment tokens
|
||||||
|
type: ((type === "escape" || type === "general") ? null : type)
|
||||||
|
|
||||||
|
// calculate base value for all moment tokens
|
||||||
|
//baseValue: ((type === "escape" || type === "general") ? null : this.as(type))
|
||||||
|
};
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
// unique moment token types in the template (in order of descending magnitude)
|
||||||
|
momentTypes = intersection(types, unique(compact(pluck(tokens, "type"))));
|
||||||
|
|
||||||
|
// exit early if there are no momentTypes
|
||||||
|
if (!momentTypes.length) {
|
||||||
|
return pluck(tokens, "token").join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate values for each token type in the template
|
||||||
|
each(momentTypes, function (momentType, index) {
|
||||||
|
var value, wholeValue, decimalValue, isLeast, isMost;
|
||||||
|
|
||||||
|
// calculate integer and decimal value portions
|
||||||
|
value = remainder.as(momentType);
|
||||||
|
wholeValue = (value > 0 ? Math.floor(value) : Math.ceil(value));
|
||||||
|
decimalValue = value - wholeValue;
|
||||||
|
|
||||||
|
// is this the least-significant moment token found?
|
||||||
|
isLeast = ((index + 1) === momentTypes.length);
|
||||||
|
|
||||||
|
// is this the most-significant moment token found?
|
||||||
|
isMost = (!index);
|
||||||
|
|
||||||
|
// update tokens array
|
||||||
|
// using this algorithm to not assume anything about
|
||||||
|
// the order or frequency of any tokens
|
||||||
|
each(tokens, function (token) {
|
||||||
|
if (token.type === momentType) {
|
||||||
|
extend(token, {
|
||||||
|
value: value,
|
||||||
|
wholeValue: wholeValue,
|
||||||
|
decimalValue: decimalValue,
|
||||||
|
isLeast: isLeast,
|
||||||
|
isMost: isMost
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isMost) {
|
||||||
|
// note the length of the most-significant moment token:
|
||||||
|
// if it is greater than one and forceLength is not set, default forceLength to `true`
|
||||||
|
if (settings.forceLength == null && token.length > 1) {
|
||||||
|
settings.forceLength = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// rationale is this:
|
||||||
|
// if the template is "h:mm:ss" and the moment value is 5 minutes, the user-friendly output is "5:00", not "05:00"
|
||||||
|
// shouldn't pad the `minutes` token even though it has length of two
|
||||||
|
// if the template is "hh:mm:ss", the user clearly wanted everything padded so we should output "05:00"
|
||||||
|
// if the user wanted the full padded output, they can set `{ trim: false }` to get "00:05:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// update remainder
|
||||||
|
remainder.subtract(wholeValue, momentType);
|
||||||
|
});
|
||||||
|
|
||||||
|
// trim tokens array
|
||||||
|
if (settings.trim) {
|
||||||
|
tokens = (settings.trim === "left" ? rest : initial)(tokens, function (token) {
|
||||||
|
// return `true` if:
|
||||||
|
// the token is not the least moment token (don't trim the least moment token)
|
||||||
|
// the token is a moment token that does not have a value (don't trim moment tokens that have a whole value)
|
||||||
|
return !(token.isLeast || (token.type != null && token.wholeValue));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// build output
|
||||||
|
|
||||||
|
// the first moment token can have special handling
|
||||||
|
foundFirst = false;
|
||||||
|
|
||||||
|
// run the map in reverse order if trimming from the right
|
||||||
|
if (settings.trim === "right") {
|
||||||
|
tokens.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens = map(tokens, function (token) {
|
||||||
|
var val,
|
||||||
|
decVal;
|
||||||
|
|
||||||
|
if (!token.type) {
|
||||||
|
// if it is not a moment token, use the token as its own value
|
||||||
|
return token.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply negative precision formatting to the least-significant moment token
|
||||||
|
if (token.isLeast && (settings.precision < 0)) {
|
||||||
|
val = (Math.floor(token.wholeValue * Math.pow(10, settings.precision)) * Math.pow(10, -settings.precision)).toString();
|
||||||
|
} else {
|
||||||
|
val = token.wholeValue.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove negative sign from the beginning
|
||||||
|
val = val.replace(/^\-/, "");
|
||||||
|
|
||||||
|
// apply token length formatting
|
||||||
|
// special handling for the first moment token that is not the most significant in a trimmed template
|
||||||
|
if (token.length > 1 && (foundFirst || token.isMost || settings.forceLength)) {
|
||||||
|
val = padZero(val, token.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add decimal value if precision > 0
|
||||||
|
if (token.isLeast && (settings.precision > 0)) {
|
||||||
|
decVal = token.decimalValue.toString().replace(/^\-/, "").split(/\.|e\-/);
|
||||||
|
switch (decVal.length) {
|
||||||
|
case 1:
|
||||||
|
val += "." + padZero(decVal[0], settings.precision, true).slice(0, settings.precision);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
val += "." + padZero(decVal[1], settings.precision, true).slice(0, settings.precision);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
val += "." + padZero(repeatZero((+decVal[2]) - 1) + (decVal[0] || "0") + decVal[1], settings.precision, true).slice(0, settings.precision);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw "Moment Duration Format: unable to parse token decimal value.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add a negative sign if the value is negative and token is most significant
|
||||||
|
if (token.isMost && token.value < 0) {
|
||||||
|
val = "-" + val;
|
||||||
|
}
|
||||||
|
|
||||||
|
foundFirst = true;
|
||||||
|
|
||||||
|
return val;
|
||||||
|
});
|
||||||
|
|
||||||
|
// undo the reverse if trimming from the right
|
||||||
|
if (settings.trim === "right") {
|
||||||
|
tokens.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens.join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
moment.duration.fn.format.defaults = {
|
||||||
|
// token definitions
|
||||||
|
escape: /\[(.+?)\]/,
|
||||||
|
years: /[Yy]+/,
|
||||||
|
months: /M+/,
|
||||||
|
weeks: /[Ww]+/,
|
||||||
|
days: /[Dd]+/,
|
||||||
|
hours: /[Hh]+/,
|
||||||
|
minutes: /m+/,
|
||||||
|
seconds: /s+/,
|
||||||
|
milliseconds: /S+/,
|
||||||
|
general: /.+?/,
|
||||||
|
|
||||||
|
// token type names
|
||||||
|
// in order of descending magnitude
|
||||||
|
// can be a space-separated token name list or an array of token names
|
||||||
|
types: "escape years months weeks days hours minutes seconds milliseconds general",
|
||||||
|
|
||||||
|
// format options
|
||||||
|
|
||||||
|
// trim
|
||||||
|
// "left" - template tokens are trimmed from the left until the first moment token that has a value >= 1
|
||||||
|
// "right" - template tokens are trimmed from the right until the first moment token that has a value >= 1
|
||||||
|
// (the final moment token is not trimmed, regardless of value)
|
||||||
|
// `false` - template tokens are not trimmed
|
||||||
|
trim: "left",
|
||||||
|
|
||||||
|
// precision
|
||||||
|
// number of decimal digits to include after (to the right of) the decimal point (positive integer)
|
||||||
|
// or the number of digits to truncate to 0 before (to the left of) the decimal point (negative integer)
|
||||||
|
precision: 0,
|
||||||
|
|
||||||
|
// force first moment token with a value to render at full length even when template is trimmed and first moment token has length of 1
|
||||||
|
forceLength: null,
|
||||||
|
|
||||||
|
// template used to format duration
|
||||||
|
// may be a function or a string
|
||||||
|
// template functions are executed with the `this` binding of the settings object
|
||||||
|
// so that template strings may be dynamically generated based on the duration object
|
||||||
|
// (accessible via `this.duration`)
|
||||||
|
// or any of the other settings
|
||||||
|
template: function () {
|
||||||
|
var types = this.types,
|
||||||
|
dur = this.duration,
|
||||||
|
lastType = findLast(types, function (type) {
|
||||||
|
return dur._data[type];
|
||||||
|
});
|
||||||
|
|
||||||
|
// default template strings for each duration dimension type
|
||||||
|
switch (lastType) {
|
||||||
|
case "seconds":
|
||||||
|
return "h:mm:ss";
|
||||||
|
case "minutes":
|
||||||
|
return "d[d] h:mm";
|
||||||
|
case "hours":
|
||||||
|
return "d[d] h[h]";
|
||||||
|
case "days":
|
||||||
|
return "M[m] d[d]";
|
||||||
|
case "weeks":
|
||||||
|
return "y[y] w[w]";
|
||||||
|
case "months":
|
||||||
|
return "y[y] M[m]";
|
||||||
|
case "years":
|
||||||
|
return "y[y]";
|
||||||
|
default:
|
||||||
|
return "y[y] M[m] d[d] h:mm:ss";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})(this);
|
234
plexpy/graphs.py
234
plexpy/graphs.py
|
@ -23,22 +23,33 @@ class Graphs(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_total_plays_per_day(self, time_range='30'):
|
def get_total_plays_per_day(self, time_range='30', y_axis='plays'):
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
if not time_range.isdigit():
|
if not time_range.isdigit():
|
||||||
time_range = '30'
|
time_range = '30'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
query = 'SELECT date(started, "unixepoch", "localtime") as date_played, ' \
|
if y_axis == 'plays':
|
||||||
'SUM(case when media_type = "episode" then 1 else 0 end) as tv_count, ' \
|
query = 'SELECT date(started, "unixepoch", "localtime") as date_played, ' \
|
||||||
'SUM(case when media_type = "movie" then 1 else 0 end) as movie_count ' \
|
'SUM(case when media_type = "episode" then 1 else 0 end) as tv_count, ' \
|
||||||
'FROM session_history ' \
|
'SUM(case when media_type = "movie" then 1 else 0 end) as movie_count ' \
|
||||||
'WHERE datetime(stopped, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") ' \
|
'FROM session_history ' \
|
||||||
'GROUP BY date_played ' \
|
'WHERE datetime(stopped, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") ' \
|
||||||
'ORDER BY started ASC' % time_range
|
'GROUP BY date_played ' \
|
||||||
|
'ORDER BY started ASC' % time_range
|
||||||
|
|
||||||
result = monitor_db.select(query)
|
result = monitor_db.select(query)
|
||||||
|
else:
|
||||||
|
query = 'SELECT date(started, "unixepoch", "localtime") as date_played, ' \
|
||||||
|
'SUM(case when media_type = "episode" and stopped > 0 then (stopped - started) else 0 end) as tv_duration, ' \
|
||||||
|
'SUM(case when media_type = "movie" and stopped > 0 then (stopped - started) else 0 end) as movie_duration ' \
|
||||||
|
'FROM session_history ' \
|
||||||
|
'WHERE datetime(stopped, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") ' \
|
||||||
|
'GROUP BY date_played ' \
|
||||||
|
'ORDER BY started ASC' % time_range
|
||||||
|
|
||||||
|
result = monitor_db.select(query)
|
||||||
except:
|
except:
|
||||||
logger.warn("Unable to execute database query.")
|
logger.warn("Unable to execute database query.")
|
||||||
return None
|
return None
|
||||||
|
@ -78,29 +89,50 @@ class Graphs(object):
|
||||||
'series': [series_1_output, series_2_output]}
|
'series': [series_1_output, series_2_output]}
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def get_total_plays_per_dayofweek(self, time_range='30'):
|
def get_total_plays_per_dayofweek(self, time_range='30', y_axis='plays'):
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
if not time_range.isdigit():
|
if not time_range.isdigit():
|
||||||
time_range = '30'
|
time_range = '30'
|
||||||
|
|
||||||
query = 'SELECT strftime("%w", datetime(started, "unixepoch", "localtime")) as daynumber, ' \
|
if y_axis == 'plays':
|
||||||
'case cast (strftime("%w", datetime(started, "unixepoch", "localtime")) as integer) ' \
|
query = 'SELECT strftime("%w", datetime(started, "unixepoch", "localtime")) as daynumber, ' \
|
||||||
'when 0 then "Sunday" ' \
|
'case cast (strftime("%w", datetime(started, "unixepoch", "localtime")) as integer) ' \
|
||||||
'when 1 then "Monday" ' \
|
'when 0 then "Sunday" ' \
|
||||||
'when 2 then "Tuesday" ' \
|
'when 1 then "Monday" ' \
|
||||||
'when 3 then "Wednesday" ' \
|
'when 2 then "Tuesday" ' \
|
||||||
'when 4 then "Thursday" ' \
|
'when 3 then "Wednesday" ' \
|
||||||
'when 5 then "Friday" ' \
|
'when 4 then "Thursday" ' \
|
||||||
'else "Saturday" end as dayofweek, ' \
|
'when 5 then "Friday" ' \
|
||||||
'COUNT(id) as total_plays ' \
|
'else "Saturday" end as dayofweek, ' \
|
||||||
'from session_history ' \
|
'COUNT(id) as total_plays ' \
|
||||||
'WHERE datetime(stopped, "unixepoch", "localtime") >= ' \
|
'from session_history ' \
|
||||||
'datetime("now", "-' + time_range + ' days", "localtime") ' \
|
'WHERE datetime(stopped, "unixepoch", "localtime") >= ' \
|
||||||
'GROUP BY dayofweek ' \
|
'datetime("now", "-' + time_range + ' days", "localtime") ' \
|
||||||
'ORDER BY daynumber'
|
'GROUP BY dayofweek ' \
|
||||||
|
'ORDER BY daynumber'
|
||||||
|
|
||||||
result = monitor_db.select(query)
|
result = monitor_db.select(query)
|
||||||
|
y_axis_label = 'Total plays'
|
||||||
|
else:
|
||||||
|
query = 'SELECT strftime("%w", datetime(started, "unixepoch", "localtime")) as daynumber, ' \
|
||||||
|
'case cast (strftime("%w", datetime(started, "unixepoch", "localtime")) as integer) ' \
|
||||||
|
'when 0 then "Sunday" ' \
|
||||||
|
'when 1 then "Monday" ' \
|
||||||
|
'when 2 then "Tuesday" ' \
|
||||||
|
'when 3 then "Wednesday" ' \
|
||||||
|
'when 4 then "Thursday" ' \
|
||||||
|
'when 5 then "Friday" ' \
|
||||||
|
'else "Saturday" end as dayofweek, ' \
|
||||||
|
'SUM(case when media_type != "track" and stopped > 0 then (stopped - started) else 0 end) as duration ' \
|
||||||
|
'from session_history ' \
|
||||||
|
'WHERE datetime(stopped, "unixepoch", "localtime") >= ' \
|
||||||
|
'datetime("now", "-' + time_range + ' days", "localtime") ' \
|
||||||
|
'GROUP BY dayofweek ' \
|
||||||
|
'ORDER BY daynumber'
|
||||||
|
|
||||||
|
result = monitor_db.select(query)
|
||||||
|
y_axis_label = 'Total duration'
|
||||||
|
|
||||||
days_list = ['Sunday', 'Monday', 'Tuesday', 'Wednesday',
|
days_list = ['Sunday', 'Monday', 'Tuesday', 'Wednesday',
|
||||||
'Thursday', 'Friday', 'Saturday']
|
'Thursday', 'Friday', 'Saturday']
|
||||||
|
@ -120,28 +152,41 @@ class Graphs(object):
|
||||||
|
|
||||||
series_1.append(series_1_value)
|
series_1.append(series_1_value)
|
||||||
|
|
||||||
series_1_output = {'name': 'Total plays',
|
series_1_output = {'name': y_axis_label,
|
||||||
'data': series_1}
|
'data': series_1}
|
||||||
|
|
||||||
output = {'categories': categories,
|
output = {'categories': categories,
|
||||||
'series': [series_1_output]}
|
'series': [series_1_output]}
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def get_total_plays_per_hourofday(self, time_range='30'):
|
def get_total_plays_per_hourofday(self, time_range='30', y_axis='plays'):
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
if not time_range.isdigit():
|
if not time_range.isdigit():
|
||||||
time_range = '30'
|
time_range = '30'
|
||||||
|
|
||||||
query = 'select strftime("%H", datetime(started, "unixepoch", "localtime")) as hourofday, ' \
|
if y_axis == 'plays':
|
||||||
'COUNT(id) ' \
|
query = 'select strftime("%H", datetime(started, "unixepoch", "localtime")) as hourofday, ' \
|
||||||
'FROM session_history ' \
|
'COUNT(id) ' \
|
||||||
'WHERE datetime(stopped, "unixepoch", "localtime") >= ' \
|
'FROM session_history ' \
|
||||||
'datetime("now", "-' + time_range + ' days", "localtime") ' \
|
'WHERE datetime(stopped, "unixepoch", "localtime") >= ' \
|
||||||
'GROUP BY hourofday ' \
|
'datetime("now", "-' + time_range + ' days", "localtime") ' \
|
||||||
'ORDER BY hourofday'
|
'GROUP BY hourofday ' \
|
||||||
|
'ORDER BY hourofday'
|
||||||
|
|
||||||
result = monitor_db.select(query)
|
result = monitor_db.select(query)
|
||||||
|
y_axis_label = 'Total plays'
|
||||||
|
else:
|
||||||
|
query = 'select strftime("%H", datetime(started, "unixepoch", "localtime")) as hourofday, ' \
|
||||||
|
'SUM(case when media_type != "track" and stopped > 0 then (stopped - started) else 0 end) as duration ' \
|
||||||
|
'FROM session_history ' \
|
||||||
|
'WHERE datetime(stopped, "unixepoch", "localtime") >= ' \
|
||||||
|
'datetime("now", "-' + time_range + ' days", "localtime") ' \
|
||||||
|
'GROUP BY hourofday ' \
|
||||||
|
'ORDER BY hourofday'
|
||||||
|
|
||||||
|
result = monitor_db.select(query)
|
||||||
|
y_axis_label = 'Total duration'
|
||||||
|
|
||||||
hours_list = ['00','01','02','03','04','05',
|
hours_list = ['00','01','02','03','04','05',
|
||||||
'06','07','08','09','10','11',
|
'06','07','08','09','10','11',
|
||||||
|
@ -163,27 +208,37 @@ class Graphs(object):
|
||||||
|
|
||||||
series_1.append(series_1_value)
|
series_1.append(series_1_value)
|
||||||
|
|
||||||
series_1_output = {'name': 'Total plays',
|
series_1_output = {'name': y_axis_label,
|
||||||
'data': series_1}
|
'data': series_1}
|
||||||
|
|
||||||
output = {'categories': categories,
|
output = {'categories': categories,
|
||||||
'series': [series_1_output]}
|
'series': [series_1_output]}
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def get_total_plays_per_month(self):
|
def get_total_plays_per_month(self, y_axis='plays'):
|
||||||
import time as time
|
import time as time
|
||||||
|
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
if y_axis == 'plays':
|
||||||
|
query = 'SELECT strftime("%Y-%m", datetime(started, "unixepoch", "localtime")) as datestring, ' \
|
||||||
|
'SUM(case when media_type = "episode" then 1 else 0 end) as tv_count, ' \
|
||||||
|
'SUM(case when media_type = "movie" then 1 else 0 end) as movie_count ' \
|
||||||
|
'FROM session_history ' \
|
||||||
|
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-12 months", "localtime") ' \
|
||||||
|
'GROUP BY strftime("%Y-%m", datetime(started, "unixepoch", "localtime")) ' \
|
||||||
|
'ORDER BY datestring DESC LIMIT 12'
|
||||||
|
|
||||||
query = 'SELECT strftime("%Y-%m", datetime(started, "unixepoch", "localtime")) as datestring, ' \
|
result = monitor_db.select(query)
|
||||||
'SUM(case when media_type = "episode" then 1 else 0 end) as tv_count, ' \
|
else:
|
||||||
'SUM(case when media_type = "movie" then 1 else 0 end) as movie_count ' \
|
query = 'SELECT strftime("%Y-%m", datetime(started, "unixepoch", "localtime")) as datestring, ' \
|
||||||
'FROM session_history ' \
|
'SUM(case when media_type = "episode" and stopped > 0 then (stopped - started) else 0 end) as tv_duration, ' \
|
||||||
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-12 months", "localtime") ' \
|
'SUM(case when media_type = "movie" and stopped > 0 then (stopped - started) else 0 end) as movie_duration ' \
|
||||||
'GROUP BY strftime("%Y-%m", datetime(started, "unixepoch", "localtime")) ' \
|
'FROM session_history ' \
|
||||||
'ORDER BY datestring DESC LIMIT 12'
|
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-12 months", "localtime") ' \
|
||||||
|
'GROUP BY strftime("%Y-%m", datetime(started, "unixepoch", "localtime")) ' \
|
||||||
|
'ORDER BY datestring DESC LIMIT 12'
|
||||||
|
|
||||||
result = monitor_db.select(query)
|
result = monitor_db.select(query)
|
||||||
|
|
||||||
# create our date range as some months may not have any data
|
# create our date range as some months may not have any data
|
||||||
# but we still want to display them
|
# but we still want to display them
|
||||||
|
@ -224,23 +279,38 @@ class Graphs(object):
|
||||||
'series': [series_1_output, series_2_output]}
|
'series': [series_1_output, series_2_output]}
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def get_total_plays_by_top_10_platforms(self, time_range='30'):
|
def get_total_plays_by_top_10_platforms(self, time_range='30', y_axis='plays'):
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
if not time_range.isdigit():
|
if not time_range.isdigit():
|
||||||
time_range = '30'
|
time_range = '30'
|
||||||
|
|
||||||
query = 'SELECT platform, ' \
|
if y_axis == 'plays':
|
||||||
'count(id) as platform_count ' \
|
query = 'SELECT platform, ' \
|
||||||
'FROM session_history ' \
|
'count(id) as platform_count ' \
|
||||||
'WHERE (datetime(stopped, "unixepoch", "localtime") >= ' \
|
'FROM session_history ' \
|
||||||
'datetime("now", "-' + time_range + ' days", "localtime")) AND ' \
|
'WHERE (datetime(stopped, "unixepoch", "localtime") >= ' \
|
||||||
'(media_type = "episode" OR media_type = "movie") ' \
|
'datetime("now", "-' + time_range + ' days", "localtime")) AND ' \
|
||||||
'GROUP BY platform ' \
|
'(media_type = "episode" OR media_type = "movie") ' \
|
||||||
'ORDER BY platform_count DESC ' \
|
'GROUP BY platform ' \
|
||||||
'LIMIT 10'
|
'ORDER BY platform_count DESC ' \
|
||||||
|
'LIMIT 10'
|
||||||
|
|
||||||
result = monitor_db.select(query)
|
result = monitor_db.select(query)
|
||||||
|
y_axis_label = 'Total plays'
|
||||||
|
else:
|
||||||
|
query = 'SELECT platform, ' \
|
||||||
|
'SUM(case when stopped > 0 then (stopped - started) else 0 end) as duration ' \
|
||||||
|
'FROM session_history ' \
|
||||||
|
'WHERE (datetime(stopped, "unixepoch", "localtime") >= ' \
|
||||||
|
'datetime("now", "-' + time_range + ' days", "localtime")) AND ' \
|
||||||
|
'(media_type = "episode" OR media_type = "movie") ' \
|
||||||
|
'GROUP BY platform ' \
|
||||||
|
'ORDER BY duration DESC ' \
|
||||||
|
'LIMIT 10'
|
||||||
|
|
||||||
|
result = monitor_db.select(query)
|
||||||
|
y_axis_label = 'Total duration'
|
||||||
|
|
||||||
categories = []
|
categories = []
|
||||||
series_1 = []
|
series_1 = []
|
||||||
|
@ -249,33 +319,51 @@ class Graphs(object):
|
||||||
categories.append(item[0])
|
categories.append(item[0])
|
||||||
series_1.append(item[1])
|
series_1.append(item[1])
|
||||||
|
|
||||||
series_1_output = {'name': 'Total plays',
|
series_1_output = {'name': y_axis_label,
|
||||||
'data': series_1}
|
'data': series_1}
|
||||||
|
|
||||||
output = {'categories': categories,
|
output = {'categories': categories,
|
||||||
'series': [series_1_output]}
|
'series': [series_1_output]}
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def get_total_plays_by_top_10_users(self, time_range='30'):
|
def get_total_plays_by_top_10_users(self, time_range='30', y_axis='plays'):
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
if not time_range.isdigit():
|
if not time_range.isdigit():
|
||||||
time_range = '30'
|
time_range = '30'
|
||||||
|
|
||||||
query = 'SELECT ' \
|
if y_axis == 'plays':
|
||||||
'(case when users.friendly_name is null then session_history.user else ' \
|
query = 'SELECT ' \
|
||||||
'users.friendly_name end) as friendly_name,' \
|
'(case when users.friendly_name is null then session_history.user else ' \
|
||||||
'count(session_history.id) as user_count ' \
|
'users.friendly_name end) as friendly_name,' \
|
||||||
'FROM session_history ' \
|
'count(session_history.id) as user_count ' \
|
||||||
'JOIN users on session_history.user_id = users.user_id ' \
|
'FROM session_history ' \
|
||||||
'WHERE (datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
|
'JOIN users on session_history.user_id = users.user_id ' \
|
||||||
'datetime("now", "-' + time_range + ' days", "localtime")) AND ' \
|
'WHERE (datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
|
||||||
'(session_history.media_type = "episode" OR session_history.media_type = "movie") ' \
|
'datetime("now", "-' + time_range + ' days", "localtime")) AND ' \
|
||||||
'GROUP BY session_history.user_id ' \
|
'(session_history.media_type = "episode" OR session_history.media_type = "movie") ' \
|
||||||
'ORDER BY user_count DESC ' \
|
'GROUP BY session_history.user_id ' \
|
||||||
'LIMIT 10'
|
'ORDER BY user_count DESC ' \
|
||||||
|
'LIMIT 10'
|
||||||
|
|
||||||
result = monitor_db.select(query)
|
result = monitor_db.select(query)
|
||||||
|
y_axis_label = 'Total plays'
|
||||||
|
else:
|
||||||
|
query = 'SELECT ' \
|
||||||
|
'(case when users.friendly_name is null then session_history.user else ' \
|
||||||
|
'users.friendly_name end) as friendly_name,' \
|
||||||
|
'SUM(case when stopped > 0 then (stopped - started) else 0 end) as duration ' \
|
||||||
|
'FROM session_history ' \
|
||||||
|
'JOIN users on session_history.user_id = users.user_id ' \
|
||||||
|
'WHERE (datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
|
||||||
|
'datetime("now", "-' + time_range + ' days", "localtime")) AND ' \
|
||||||
|
'(session_history.media_type = "episode" OR session_history.media_type = "movie") ' \
|
||||||
|
'GROUP BY session_history.user_id ' \
|
||||||
|
'ORDER BY duration DESC ' \
|
||||||
|
'LIMIT 10'
|
||||||
|
|
||||||
|
result = monitor_db.select(query)
|
||||||
|
y_axis_label = 'Total duration'
|
||||||
|
|
||||||
categories = []
|
categories = []
|
||||||
series_1 = []
|
series_1 = []
|
||||||
|
@ -284,7 +372,7 @@ class Graphs(object):
|
||||||
categories.append(item[0])
|
categories.append(item[0])
|
||||||
series_1.append(item[1])
|
series_1.append(item[1])
|
||||||
|
|
||||||
series_1_output = {'name': 'Total plays',
|
series_1_output = {'name': y_axis_label,
|
||||||
'data': series_1}
|
'data': series_1}
|
||||||
|
|
||||||
output = {'categories': categories,
|
output = {'categories': categories,
|
||||||
|
|
|
@ -823,10 +823,10 @@ class WebInterface(object):
|
||||||
return json.dumps(history)
|
return json.dumps(history)
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_plays_by_date(self, time_range='30', **kwargs):
|
def get_plays_by_date(self, time_range='30', y_axis='plays', **kwargs):
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_total_plays_per_day(time_range=time_range)
|
result = graph.get_total_plays_per_day(time_range=time_range, y_axis=y_axis)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
|
@ -835,10 +835,10 @@ class WebInterface(object):
|
||||||
logger.warn('Unable to retrieve data.')
|
logger.warn('Unable to retrieve data.')
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_plays_by_dayofweek(self, time_range='30', **kwargs):
|
def get_plays_by_dayofweek(self, time_range='30', y_axis='plays', **kwargs):
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_total_plays_per_dayofweek(time_range=time_range)
|
result = graph.get_total_plays_per_dayofweek(time_range=time_range, y_axis=y_axis)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
|
@ -847,10 +847,10 @@ class WebInterface(object):
|
||||||
logger.warn('Unable to retrieve data.')
|
logger.warn('Unable to retrieve data.')
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_plays_by_hourofday(self, time_range='30', **kwargs):
|
def get_plays_by_hourofday(self, time_range='30', y_axis='plays', **kwargs):
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_total_plays_per_hourofday(time_range=time_range)
|
result = graph.get_total_plays_per_hourofday(time_range=time_range, y_axis=y_axis)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
|
@ -859,10 +859,10 @@ class WebInterface(object):
|
||||||
logger.warn('Unable to retrieve data.')
|
logger.warn('Unable to retrieve data.')
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_plays_per_month(self, **kwargs):
|
def get_plays_per_month(self, y_axis='plays', **kwargs):
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_total_plays_per_month()
|
result = graph.get_total_plays_per_month(y_axis=y_axis)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
|
@ -871,10 +871,10 @@ class WebInterface(object):
|
||||||
logger.warn('Unable to retrieve data.')
|
logger.warn('Unable to retrieve data.')
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_plays_by_top_10_platforms(self, time_range='30', **kwargs):
|
def get_plays_by_top_10_platforms(self, time_range='30', y_axis='plays', **kwargs):
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_total_plays_by_top_10_platforms(time_range=time_range)
|
result = graph.get_total_plays_by_top_10_platforms(time_range=time_range, y_axis=y_axis)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
|
@ -883,10 +883,10 @@ class WebInterface(object):
|
||||||
logger.warn('Unable to retrieve data.')
|
logger.warn('Unable to retrieve data.')
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_plays_by_top_10_users(self, time_range='30', **kwargs):
|
def get_plays_by_top_10_users(self, time_range='30', y_axis='plays', **kwargs):
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_total_plays_by_top_10_users(time_range=time_range)
|
result = graph.get_total_plays_by_top_10_users(time_range=time_range, y_axis=y_axis)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue