Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

stats overview page

  • Loading branch information...
commit d9b3f8d7a815eb3a4aa2da7dbe7d2b188e006347 1 parent 9d4fe9a
@potch potch authored
View
6 apps/stats/templates/stats/overview.html
@@ -18,7 +18,7 @@
<div class="piechart"></div>
<table data-metric="apps">
</table>
- <a href="applications/">{{ _('See more applications&hellip;') }}</a>
+ <a href="usage/applications/">{{ _('See more applications&hellip;') }}</a>
</div>
</div>
<div class="toplist">
@@ -27,7 +27,7 @@
<div class="piechart"></div>
<table data-metric="locales">
</table>
- <a href="languages/">{{ _('See more languages&hellip;') }}</a>
+ <a href="usage/languages/">{{ _('See more languages&hellip;') }}</a>
</div>
</div>
<div class="toplist">
@@ -36,7 +36,7 @@
<div class="piechart"></div>
<table data-metric="os">
</table>
- <a href="os/">{{ _('See more operating systems&hellip;') }}</a>
+ <a href="usage/os/">{{ _('See more operating systems&hellip;') }}</a>
</div>
</div>
</div>
View
123 media/css/impala/stats.less
@@ -6,7 +6,6 @@
max-width: 1280px;
width: auto;
min-width: 1024px;
- padding-bottom: 500px;
padding-left: 20px;
padding-right: 20px;
.header-search {
@@ -172,99 +171,45 @@ table tbody tr {
overflow: hidden;
}
.toplist {
+ overflow: hidden;
float: left;
width: 31%;
padding: 0;
margin-left: 3.5%;
+ &:first-child {
+ margin-left: 0;
+ }
+ h3 {
+ margin: 0 0 .2em 0;
+ }
+ .highcharts-tooltip {
+ position: absolute;
+ top: 0 !important;
+ left: 0 !important;
+ }
+ table {
+ width: 100%;
+ height: 160px;
+ margin: .7em 0;
+ }
+ tr:first-child {
+ border-top: 0;
+ }
+ td {
+ text-align: right;
+ padding: 0;
+ line-height: 2em;
+ &:first-child {
+ text-align: left;
+ }
+ &:last-child {
+ text-align: left;
+ font-size: 90%;
+ width:40px;
+ padding-left: .3em;
+ }
+ }
}
-.toplist:first-child {
- margin-left: 0;
-}
-.toplist .statbox {
- padding: 1em;
- margin: 0;
-}
-.toplist h3 {
- margin: 0 0 .2em 0;
-}
-#toplist1 {
- width: 210px;
- margin: .5em auto 0 auto;
-}
-.toplist tr:first-child {
- border-top: 0;
-}
-.toplist table {
- width: 100%;
- height: 160px;
- margin: .7em 0;
-}
-.toplist td {
- text-align: right;
- padding: 0;
- line-height: 2em;
-}
-.toplist td:last-child {
- text-align: left;
- font-size: 90%;
- width:40px;
- padding-left: .3em;
-}
-.toplist td:first-child {
- text-align: left;
-}
-
-/**
- * Rules for Report Menu
-**/
-
-.report-menu nav {
- display: block;
-}
-.report-menu h3 {
- margin-top: 0;
-}
-.report-menu ul li {
- font-size: 110%;
-}
-.report-menu ul li ul li {
- font-size: 90%;
-}
-.report-menu ul li ul li a {
- margin: .4em 0;
-}
-.report-menu ul li a {
- margin: .4em 0;
-}
-.report-menu ul ul {
- margin-left: 25px;
-}
-.report-menu ul li.selected > a {
- position: relative;
- color: #333;
-}
-.report-menu ul li.selected > a:hover {
- text-decoration: none;
-}
-
-/* Makes the triangles for selected reports */
-.report-menu ul li.selected > a:before {
- content: "\00a0";
- display: block; /* reduce the damage in FF3.0 */
- position: absolute;
- width: 0;
- height: 0;
- top: 20%; /* value = - border-top-width - border-bottom-width */
- left: 10px; /* value = (:before right) + (:before border-right) - (:after border-right) */
- border: 5px solid transparent;
- border-left-color: #333;
- border-style: solid;
-}
-.report-menu ul li ul li.selected > a:before {
- border-width: 4px;
-}
-
-/* @end */
/**
* bar-chart tables
View
56 media/js/impala/stats/chart.js
@@ -57,7 +57,7 @@
}
}
};
-
+ Highcharts.setOptions({ lang: { resetZoom: '' } });
var chart;
// which unit do we use for a given metric?
var metricTypes = {
@@ -85,6 +85,7 @@
fields = obj.fields ? obj.fields.slice(0,5) : ['count'],
data = obj.data,
series = {},
+ chartRange = {},
t, row, i, field, val;
// Initialize the empty series object.
@@ -179,33 +180,38 @@
var newConfig = $.extend(baseConfig, { series: chartData });
// set up dual-axes for the overview chart.
if (metric == "overview" && newConfig.series.length) {
- newConfig.yAxis = [
- { // Downloads
- title: {
- text: gettext('Downloads')
- },
- // min: 0,
- labels: {
- formatter: function() {
- return Highcharts.numberFormat(this.value, 0);
+ _.extend(newConfig, {
+ yAxis : [
+ { // Downloads
+ title: {
+ text: gettext('Downloads')
+ },
+ // min: 0,
+ labels: {
+ formatter: function() {
+ return Highcharts.numberFormat(this.value, 0);
+ }
}
+ }, { // Daily Users
+ title: {
+ text: gettext('Daily Users')
+ },
+ labels: {
+ formatter: function() {
+ return Highcharts.numberFormat(this.value, 0);
+ }
+ },
+ // min: 0,
+ opposite: true
}
- }, { // Daily Users
- title: {
- text: gettext('Daily Users')
- },
- labels: {
- formatter: function() {
- return Highcharts.numberFormat(this.value, 0);
- }
- },
- // min: 0,
- opposite: true
+ ],
+ tooltip: {
+ shared : true,
+ crosshairs : true
}
- ];
+ });
// set Daily Users series to use the right yAxis.
newConfig.series[1].yAxis = 1;
- newConfig.tooltip.shared = true;
}
newConfig.tooltip.formatter = tooltipFormatter;
@@ -228,6 +234,10 @@
if (chart) chart.destroy();
chart = new Highcharts.Chart(newConfig);
+ chartRange = chart.xAxis[0].getExtremes();
+ $("h1").click(function() {
+ chart.xAxis[0].setExtremes(chartRange.min, chartRange.max);
+ })
$chart.removeClass('loading');
});
})();
View
131 media/js/impala/stats/manager.js
@@ -11,7 +11,7 @@ z.StatsManager = (function() {
// The version of the stats localStorage we are using.
// If you increment this number, you cache-bust everyone!
- var STATS_VERSION = 22;
+ var STATS_VERSION = 1;
var storage = z.Storage("stats"),
storageCache = z.Storage("statscache"),
@@ -155,21 +155,18 @@ z.StatsManager = (function() {
// and queues up requests to the server if the requested data is outside
// the range currently stored locally. Once all server requests return,
// we move on.
- function getDataRange(view, callback) {
+ function getDataRange(view) {
dbg("enter getDataRange", view.metric);
var range = z.date.normalizeRange(view.range),
metric = view.metric,
- ds,
- needed = 0,
+ ds = dataStore[metric],
+ reqs = [],
$def = $.Deferred();
function finished() {
- needed--;
- dbg(pendingFetches, " fetches pending");
- if (needed < 1) {
- var ret = {}, row,
- step = z.date.millis("1 day");
- ds = dataStore[metric];
+ var ret = {}, row,
+ step = z.date.millis("1 day");
+ if (ds) {
for (var i=range.start; i<range.end; i+= step) {
if (ds[i]) {
ret[i] = (metric == 'apps') ? collapseVersions(ds[i], 1) : ds[i];
@@ -177,30 +174,26 @@ z.StatsManager = (function() {
}
ret = groupData(ret, view);
ret.metric = metric;
- $def.resolve(ret);
}
+ if (_.isEmpty(ret)) {
+ ret.empty = true;
+ }
+ $def.resolve(ret);
}
- if (dataStore[metric]) {
- ds = dataStore[metric];
+ if (ds) {
dbg("range", range.start, range.end);
if (ds.maxdate < range.end) {
- needed++;
- fetchData(metric, ds.maxdate, range.end, finished);
+ reqs.push(fetchData(metric, ds.maxdate, range.end));
}
if (ds.mindate > range.start) {
- needed++;
- fetchData(metric, range.start, ds.mindate, finished);
- }
- if (ds.mindate <= range.start && ds.maxdate >= range.end) {
- dbg("all data found locally");
- finished();
+ reqs.push(fetchData(metric, range.start, ds.mindate));
}
} else {
- dbg("metric not found");
- needed++;
- fetchData(metric, range.start, range.end, finished);
+ reqs.push(fetchData(metric, range.start, range.end));
}
+
+ $.when.apply(null, reqs).then(finished);
return $def;
}
@@ -217,32 +210,49 @@ z.StatsManager = (function() {
groupVal = false,
groupCount = 0,
d, row;
+
+ if (group == 'all') {
+ groupKey = range.start;
+ groupCount = 0;
+ groupVal = {
+ date: z.date.date_string(new Date(groupKey), '-'),
+ count: 0,
+ data: {}
+ };
+ }
+
+ function performAggregation() {
+ // we drop the some days of data from the result set
+ // if they are not a complete grouping.
+ if (groupKey && groupVal) {
+ // average `count` for mean metrics
+ if (metricTypes[metric] == 'mean') {
+ groupVal.count /= groupCount;
+ }
+ // overview gets special treatment. Only average ADUs.
+ if (metric == "overview") {
+ groupVal.data.updates /= groupCount;
+ } else if (metric in breakdownMetrics) {
+ // average for mean metrics.
+ _.each(groupVal.data, function(val, field) {
+ if (metricTypes[metric] == 'mean') {
+ groupVal.data[field] /= groupCount;
+ }
+ });
+ }
+ groupedData[groupKey] = groupVal;
+ }
+ }
+
// big loop!
- for (var i=range.start; i<range.end; i+= z.date.millis('1 day')) {
+ for (var i=range.start; i <= range.end; i+= z.date.millis('1 day')) {
d = new Date(i);
row = data[i];
// Here's where grouping points are caluculated.
- if ((group == 'week' && d.getDay() === 0) || (group == 'month' && d.getDate() == 1)) {
- // we drop the some days of data from the result set
- // if they are not a complete grouping.
- if (groupKey && groupVal) {
- // average `count` for mean metrics
- if (metricTypes[metric] == 'mean') {
- groupVal.count /= groupCount;
- }
- // overview gets special treatment. Only average ADUs.
- if (metric == "overview") {
- groupVal.data.updates /= groupCount;
- } else if (metric in breakdownMetrics) {
- // average for mean metrics.
- _.each(groupVal.data, function(val, field) {
- if (metricTypes[metric] == 'mean') {
- groupVal.data[field] /= groupCount;
- }
- });
- }
- groupedData[groupKey] = groupVal;
- }
+ if ((group == 'week' && d.getDay() === 0) ||
+ (group == 'month' && d.getDate() == 1)) {
+
+ performAggregation();
// set the new group date to the current iteration.
groupKey = i;
// reset our aggregates.
@@ -267,16 +277,16 @@ z.StatsManager = (function() {
}
groupCount++;
}
+ if (group == 'all') performAggregation();
return groupedData;
}
// The beef. Negotiates with the server for data.
- function fetchData(metric, start, end, callback) {
+ function fetchData(metric, start, end) {
var seriesStart = start,
- seriesEnd = end;
-
- pendingFetches++;
+ seriesEnd = end,
+ $def = $.Deferred();
var seriesURLStart = Highcharts.dateFormat('%Y%m%d', seriesStart),
seriesURLEnd = Highcharts.dateFormat('%Y%m%d', seriesEnd),
@@ -286,7 +296,12 @@ z.StatsManager = (function() {
$.ajax({ url: seriesURL,
dataType: 'text',
- success: fetchHandler});
+ success: fetchHandler,
+ error: errorHandler });
+
+ function errorHandler() {
+ $def.fail();
+ }
function fetchHandler(raw_data, status, xhr) {
var maxdate = 0,
@@ -312,10 +327,9 @@ z.StatsManager = (function() {
}
ds.maxdate = Math.max(parseInt(maxdate, 10), parseInt(ds.maxdate, 10));
ds.mindate = Math.min(parseInt(mindate, 10), parseInt(ds.mindate, 10));
- pendingFetches--;
- callback.call(this, true);
clearTimeout(writeInterval);
writeInterval = setTimeout(writeLocalStorage, 1000);
+ $def.resolve();
} else if (xhr.status == 202) { //Handle a successful fetch but with no reponse
@@ -331,6 +345,7 @@ z.StatsManager = (function() {
}
}
+ return $def;
}
@@ -402,9 +417,11 @@ z.StatsManager = (function() {
// Expose some functionality to the z.StatsManager api.
return {
- 'fetchData' : fetchData,
- 'dataStore' : dataStore,
- 'getPrettyName' : getPrettyName,
- 'getField' : getField
+ 'getDataRange' : getDataRange,
+ 'fetchData' : fetchData,
+ 'dataStore' : dataStore,
+ 'getPrettyName' : getPrettyName,
+ 'getField' : getField,
+ 'clearLocalStorage' : clearLocalStorage
};
})();
View
4 media/js/impala/stats/overview.js
@@ -0,0 +1,4 @@
+$(function() {
+ if ($('.primary').attr('data-report') != 'overview') return;
+ $('.toplist').topChart();
+});
View
115 media/js/impala/stats/topchart.js
@@ -0,0 +1,115 @@
+(function($) {
+ // "use strict";
+ var baseConfig = {
+ chart: {
+ backgroundColor: null
+ },
+ title: {
+ text: null
+ },
+ plotArea: {
+ shadow: null,
+ borderWidth: null,
+ },
+ tooltip: {
+ enabled: false
+ },
+ plotOptions: {
+ pie: {
+ allowPointSelect: true,
+ dataLabels: {
+ enabled: false,
+ color: '#333'
+ },
+ animation: false,
+ size:190
+ }
+ },
+ credits: {enabled:false},
+ legend: {
+ enabled:false
+ },
+ series: [{
+ type: 'pie'
+ }]
+ };
+
+ $.fn.topChart = function(cfg) {
+ $(this).each(function() {
+ var $self = $(this),
+ $win = $(window),
+ $chart = $self.find('.piechart'),
+ hChart,
+ $table = $self.find('table'),
+ metric = $table.attr('data-metric'),
+ view = {
+ 'metric': metric,
+ 'group' : 'all'
+ };
+
+ $win.bind('changeview', function(e, newView) {
+ // we only want to respond to changes in range.
+ if (!newView.range) return;
+ $self.addClass('loading');
+ _.extend(view, {'range' : z.date.normalizeRange(newView.range)});
+ $.when(z.StatsManager.getDataRange(view))
+ .then(function(data) {
+ generateRankedList(data, render);
+ });
+ });
+
+ // We take the data (aggregated to one row)
+ function generateRankedList(data, done) {
+ var totalValue = data[view.range.start].count,
+ otherValue = totalValue;
+ data = data[view.range.start].data;
+ if (_.isEmpty(data)) return;
+ // Convert all fields to percentages and prettify names.
+ var rankedList = _.map(data, function(val, key) {
+ var field = key.split("|").slice(-1)[0];
+ return [z.StatsManager.getPrettyName(metric, field),
+ val, val/totalValue*100];
+ });
+ // Sort by value.
+ rankedList = _.sortBy(rankedList, function(a) {
+ return -a[1];
+ });
+ // Calculate the 'Other' percentage
+ for (var i=0; i<5; i++) {
+ otherValue -= rankedList[i][1];
+ }
+ // Take the top 5 values and append an 'Other' row.
+ rankedList = rankedList.slice(0,5);
+ rankedList.push([gettext('Other'), otherValue, otherValue/totalValue*100]);
+ // Move on with our lives.
+ done(rankedList);
+ }
+
+ var tableRow = template("<tr><td>{0}</td><td>{1}</td><td>({2}%)</td></tr>");
+
+ function render(data) {
+ var newBody = "<tbody>";
+ _.each(data, function(row) {
+ var pct = Math.round(row[2]);
+ num = Highcharts.numberFormat(row[1], 0);
+ if (pct < 1) pct = "<1";
+ newBody += tableRow([row[0], num, pct]);
+ });
+ newBody += "</tbody>";
+ $table.html(newBody);
+
+ // set up chart.
+ var newConfig = _.clone(baseConfig),
+ row;
+ newConfig.chart.renderTo = $chart[0];
+ newConfig.series[0].data = _.map(data, function(r) { return r.slice(0,2); });
+ hChart = new Highcharts.Chart(newConfig);
+ for (i = 0; i < data.length; i++) {
+ row = $table.find('tr').eq(i);
+ row.children().eq(0).append($("<b class='seriesdot' style='background:" + hChart.series[0].data[i].color + "'>&nbsp;</b>"));
+ }
+ $self.removeClass('loading');
+ }
+ });
+ };
+})(jQuery);
View
2  settings.py
@@ -746,6 +746,8 @@ def JINJA_CONFIG():
'js/impala/stats/dateutils.js',
'js/impala/stats/manager.js',
'js/impala/stats/controls.js',
+ 'js/impala/stats/overview.js',
+ 'js/impala/stats/topchart.js',
'js/impala/stats/chart.js',
'js/impala/stats/stats.js',
),
Please sign in to comment.
Something went wrong with that request. Please try again.