mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-07 13:41: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>
|
||||
</div>
|
||||
<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">
|
||||
<label class="btn btn-dark active">
|
||||
<input type="radio" name="date-options" id="graph-30" value="30" autocomplete="off" checked> 30 days
|
||||
|
@ -25,15 +33,15 @@
|
|||
</div>
|
||||
<div class='table-card-back'>
|
||||
<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"><a id="watch-totals-graphs" href="#tabs-2" aria-controls="tabs-2" role="tab" data-toggle="tab">Play Totals</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-3" aria-controls="tabs-2" role="tab" data-toggle="tab">Play Totals</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="tabs-1">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<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 class="graphs-instance">
|
||||
<div class="watch-chart" id="chart_div_plays_by_day">
|
||||
|
@ -46,7 +54,7 @@
|
|||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<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 class="graphs-instance">
|
||||
<div class="watch-chart" id="chart_div_plays_by_dayofweek" style="float: left;">
|
||||
|
@ -58,7 +66,7 @@
|
|||
</div>
|
||||
<div class="col-md-6">
|
||||
<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 class="graphs-instance">
|
||||
<div class="watch-chart" id="chart_div_plays_by_hourofday">
|
||||
|
@ -72,7 +80,7 @@
|
|||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<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 class="graphs-instance">
|
||||
<div class="watch-chart" id="chart_div_plays_by_platform" style="float: left;">
|
||||
|
@ -84,7 +92,7 @@
|
|||
</div>
|
||||
<div class="col-md-6">
|
||||
<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 class="graphs-instance">
|
||||
<div class="watch-chart" id="chart_div_plays_by_user">
|
||||
|
@ -97,7 +105,7 @@
|
|||
</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="col-md-12">
|
||||
<div class="padded-header">
|
||||
|
@ -121,6 +129,7 @@
|
|||
|
||||
<%def name="javascriptIncludes()">
|
||||
<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/graphs/plays_by_day.js"></script>
|
||||
<script src="interfaces/default/js/graphs/plays_by_dayofweek.js"></script>
|
||||
|
@ -132,13 +141,15 @@
|
|||
$(document).ready(function () {
|
||||
|
||||
var current_range = 30;
|
||||
var yaxis = 'plays';
|
||||
|
||||
function loadGraphsTab1(time_range) {
|
||||
function loadGraphsTab1(time_range, yaxis) {
|
||||
setGraphFormat(yaxis);
|
||||
|
||||
$.ajax({
|
||||
url: "get_plays_by_date",
|
||||
type: 'get',
|
||||
data: { time_range: time_range },
|
||||
data: { time_range: time_range, y_axis: yaxis },
|
||||
dataType: "json",
|
||||
cache: false,
|
||||
success: function(data) {
|
||||
|
@ -156,7 +167,7 @@
|
|||
$.ajax({
|
||||
url: "get_plays_by_dayofweek",
|
||||
type: 'get',
|
||||
data: { time_range: time_range },
|
||||
data: { time_range: time_range, y_axis: yaxis },
|
||||
dataType: "json",
|
||||
cache: false,
|
||||
success: function(data) {
|
||||
|
@ -169,7 +180,7 @@
|
|||
$.ajax({
|
||||
url: "get_plays_by_hourofday",
|
||||
type: 'get',
|
||||
data: { time_range: time_range },
|
||||
data: { time_range: time_range, y_axis: yaxis },
|
||||
dataType: "json",
|
||||
cache: false,
|
||||
success: function(data) {
|
||||
|
@ -182,7 +193,7 @@
|
|||
$.ajax({
|
||||
url: "get_plays_by_top_10_platforms",
|
||||
type: 'get',
|
||||
data: { time_range: time_range },
|
||||
data: { time_range: time_range, y_axis: yaxis },
|
||||
dataType: "json",
|
||||
cache: false,
|
||||
success: function(data) {
|
||||
|
@ -195,7 +206,7 @@
|
|||
$.ajax({
|
||||
url: "get_plays_by_top_10_users",
|
||||
type: 'get',
|
||||
data: { time_range: time_range },
|
||||
data: { time_range: time_range, y_axis: yaxis },
|
||||
dataType: "json",
|
||||
cache: false,
|
||||
success: function(data) {
|
||||
|
@ -206,10 +217,13 @@
|
|||
});
|
||||
}
|
||||
|
||||
function loadGraphsTab2() {
|
||||
function loadGraphsTab3(yaxis) {
|
||||
setGraphFormat(yaxis);
|
||||
|
||||
$.ajax({
|
||||
url: "get_plays_per_month",
|
||||
type: 'get',
|
||||
data: { y_axis: yaxis },
|
||||
dataType: "json",
|
||||
cache: false,
|
||||
success: function(data) {
|
||||
|
@ -226,25 +240,106 @@
|
|||
}
|
||||
|
||||
// Set initial state
|
||||
loadGraphsTab1(current_range);
|
||||
loadGraphsTab1(current_range, yaxis);
|
||||
|
||||
$('#watch-count-graphs').click(function() {
|
||||
loadGraphsTab1(current_range);
|
||||
loadGraphsTab1(current_range, yaxis);
|
||||
$('#days-selection').show();
|
||||
});
|
||||
|
||||
$('#watch-totals-graphs').click(function() {
|
||||
loadGraphsTab2();
|
||||
loadGraphsTab3(yaxis);
|
||||
$('#days-selection').hide();
|
||||
});
|
||||
|
||||
$('#days-selection').on('change', function() {
|
||||
current_range = $('input[name=date-options]:checked', '#days-selection').val();
|
||||
loadGraphsTab1(current_range);
|
||||
loadGraphsTab1(current_range, yaxis);
|
||||
$('.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>
|
||||
</%def>
|
|
@ -47,15 +47,6 @@ var hc_plays_by_day_options = {
|
|||
}
|
||||
},
|
||||
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
|
||||
},
|
||||
series: [{}]
|
||||
|
|
|
@ -49,7 +49,7 @@ var hc_plays_by_dayofweek_options = {
|
|||
}
|
||||
},
|
||||
tooltip: {
|
||||
|
||||
shared: true
|
||||
},
|
||||
series: [{}]
|
||||
};
|
|
@ -49,7 +49,7 @@ var hc_plays_by_hourofday_options = {
|
|||
}
|
||||
},
|
||||
tooltip: {
|
||||
|
||||
shared: true
|
||||
},
|
||||
series: [{}]
|
||||
};
|
|
@ -65,15 +65,6 @@ var hc_plays_by_month_options = {
|
|||
}
|
||||
},
|
||||
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
|
||||
},
|
||||
series: [{}]
|
||||
|
|
|
@ -49,7 +49,7 @@ var hc_plays_by_platform_options = {
|
|||
}
|
||||
},
|
||||
tooltip: {
|
||||
|
||||
shared: true
|
||||
},
|
||||
series: [{}]
|
||||
};
|
|
@ -49,7 +49,7 @@ var hc_plays_by_user_options = {
|
|||
}
|
||||
},
|
||||
tooltip: {
|
||||
|
||||
shared: true
|
||||
},
|
||||
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);
|
110
plexpy/graphs.py
110
plexpy/graphs.py
|
@ -23,13 +23,14 @@ class Graphs(object):
|
|||
def __init__(self):
|
||||
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()
|
||||
|
||||
if not time_range.isdigit():
|
||||
time_range = '30'
|
||||
|
||||
try:
|
||||
if y_axis == 'plays':
|
||||
query = 'SELECT date(started, "unixepoch", "localtime") as date_played, ' \
|
||||
'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 ' \
|
||||
|
@ -38,6 +39,16 @@ class Graphs(object):
|
|||
'GROUP BY date_played ' \
|
||||
'ORDER BY started ASC' % time_range
|
||||
|
||||
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:
|
||||
logger.warn("Unable to execute database query.")
|
||||
|
@ -78,12 +89,13 @@ class Graphs(object):
|
|||
'series': [series_1_output, series_2_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()
|
||||
|
||||
if not time_range.isdigit():
|
||||
time_range = '30'
|
||||
|
||||
if y_axis == 'plays':
|
||||
query = 'SELECT strftime("%w", datetime(started, "unixepoch", "localtime")) as daynumber, ' \
|
||||
'case cast (strftime("%w", datetime(started, "unixepoch", "localtime")) as integer) ' \
|
||||
'when 0 then "Sunday" ' \
|
||||
|
@ -101,6 +113,26 @@ class Graphs(object):
|
|||
'ORDER BY daynumber'
|
||||
|
||||
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',
|
||||
'Thursday', 'Friday', 'Saturday']
|
||||
|
@ -120,19 +152,20 @@ class Graphs(object):
|
|||
|
||||
series_1.append(series_1_value)
|
||||
|
||||
series_1_output = {'name': 'Total plays',
|
||||
series_1_output = {'name': y_axis_label,
|
||||
'data': series_1}
|
||||
|
||||
output = {'categories': categories,
|
||||
'series': [series_1_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()
|
||||
|
||||
if not time_range.isdigit():
|
||||
time_range = '30'
|
||||
|
||||
if y_axis == 'plays':
|
||||
query = 'select strftime("%H", datetime(started, "unixepoch", "localtime")) as hourofday, ' \
|
||||
'COUNT(id) ' \
|
||||
'FROM session_history ' \
|
||||
|
@ -142,6 +175,18 @@ class Graphs(object):
|
|||
'ORDER BY hourofday'
|
||||
|
||||
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',
|
||||
'06','07','08','09','10','11',
|
||||
|
@ -163,18 +208,18 @@ class Graphs(object):
|
|||
|
||||
series_1.append(series_1_value)
|
||||
|
||||
series_1_output = {'name': 'Total plays',
|
||||
series_1_output = {'name': y_axis_label,
|
||||
'data': series_1}
|
||||
|
||||
output = {'categories': categories,
|
||||
'series': [series_1_output]}
|
||||
return output
|
||||
|
||||
def get_total_plays_per_month(self):
|
||||
def get_total_plays_per_month(self, y_axis='plays'):
|
||||
import time as time
|
||||
|
||||
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 ' \
|
||||
|
@ -184,6 +229,16 @@ class Graphs(object):
|
|||
'ORDER BY datestring DESC LIMIT 12'
|
||||
|
||||
result = monitor_db.select(query)
|
||||
else:
|
||||
query = 'SELECT strftime("%Y-%m", datetime(started, "unixepoch", "localtime")) as datestring, ' \
|
||||
'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(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)
|
||||
|
||||
# create our date range as some months may not have any data
|
||||
# but we still want to display them
|
||||
|
@ -224,12 +279,13 @@ class Graphs(object):
|
|||
'series': [series_1_output, series_2_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()
|
||||
|
||||
if not time_range.isdigit():
|
||||
time_range = '30'
|
||||
|
||||
if y_axis == 'plays':
|
||||
query = 'SELECT platform, ' \
|
||||
'count(id) as platform_count ' \
|
||||
'FROM session_history ' \
|
||||
|
@ -241,6 +297,20 @@ class Graphs(object):
|
|||
'LIMIT 10'
|
||||
|
||||
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 = []
|
||||
series_1 = []
|
||||
|
@ -249,19 +319,20 @@ class Graphs(object):
|
|||
categories.append(item[0])
|
||||
series_1.append(item[1])
|
||||
|
||||
series_1_output = {'name': 'Total plays',
|
||||
series_1_output = {'name': y_axis_label,
|
||||
'data': series_1}
|
||||
|
||||
output = {'categories': categories,
|
||||
'series': [series_1_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()
|
||||
|
||||
if not time_range.isdigit():
|
||||
time_range = '30'
|
||||
|
||||
if y_axis == 'plays':
|
||||
query = 'SELECT ' \
|
||||
'(case when users.friendly_name is null then session_history.user else ' \
|
||||
'users.friendly_name end) as friendly_name,' \
|
||||
|
@ -276,6 +347,23 @@ class Graphs(object):
|
|||
'LIMIT 10'
|
||||
|
||||
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 = []
|
||||
series_1 = []
|
||||
|
@ -284,7 +372,7 @@ class Graphs(object):
|
|||
categories.append(item[0])
|
||||
series_1.append(item[1])
|
||||
|
||||
series_1_output = {'name': 'Total plays',
|
||||
series_1_output = {'name': y_axis_label,
|
||||
'data': series_1}
|
||||
|
||||
output = {'categories': categories,
|
||||
|
|
|
@ -823,10 +823,10 @@ class WebInterface(object):
|
|||
return json.dumps(history)
|
||||
|
||||
@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()
|
||||
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:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
|
@ -835,10 +835,10 @@ class WebInterface(object):
|
|||
logger.warn('Unable to retrieve data.')
|
||||
|
||||
@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()
|
||||
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:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
|
@ -847,10 +847,10 @@ class WebInterface(object):
|
|||
logger.warn('Unable to retrieve data.')
|
||||
|
||||
@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()
|
||||
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:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
|
@ -859,10 +859,10 @@ class WebInterface(object):
|
|||
logger.warn('Unable to retrieve data.')
|
||||
|
||||
@cherrypy.expose
|
||||
def get_plays_per_month(self, **kwargs):
|
||||
def get_plays_per_month(self, y_axis='plays', **kwargs):
|
||||
|
||||
graph = graphs.Graphs()
|
||||
result = graph.get_total_plays_per_month()
|
||||
result = graph.get_total_plays_per_month(y_axis=y_axis)
|
||||
|
||||
if result:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
|
@ -871,10 +871,10 @@ class WebInterface(object):
|
|||
logger.warn('Unable to retrieve data.')
|
||||
|
||||
@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()
|
||||
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:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
|
@ -883,10 +883,10 @@ class WebInterface(object):
|
|||
logger.warn('Unable to retrieve data.')
|
||||
|
||||
@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()
|
||||
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:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue