This commit is contained in:
Matt Claypotch 2011-10-17 10:57:27 -07:00
Родитель 9d4fe9a21d
Коммит d9b3f8d7a8
7 изменённых файлов: 265 добавлений и 172 удалений

Просмотреть файл

@ -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>

Просмотреть файл

@ -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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -0,0 +1,4 @@
$(function() {
if ($('.primary').attr('data-report') != 'overview') return;
$('.toplist').topChart();
});

Просмотреть файл

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

Просмотреть файл

@ -746,6 +746,8 @@ MINIFY_BUNDLES = {
'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',
),