- upgraded Highcharts
- js-hint run on code
- more documentation
- new two-axis overview chart
This commit is contained in:
Matt Claypotch 2011-10-11 18:13:28 -07:00
Родитель b5e5c867a2
Коммит d21cb9955a
12 изменённых файлов: 5137 добавлений и 2679 удалений

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

@ -1,55 +1,40 @@
{% extends "stats/stats.html" %}
{% block chart_menu %}
<ul id='series-select'>
<li class="selected">
<a href="#"
data-report='downloads'
data-series='count'>
{{_('Downloads')}}
</a>
</li>
<li>
<a href="#"
data-report='usage'
data-series='count'>
{{_('Daily Users')}}
</a>
</li>
</ul>
{% endblock %}
{% block stats %}
<div class="featured">
<div class="featured-inner two-up">
<div><a href="downloads/">{{ _('{0} Downloads')|f(addon.total_downloads|numberfmt) }}</a><br/><small>{{ _('{0} in last 30 days')|f('<span id="sum_downloads_range"></span>')|safe }}</small></div>
<div><div><a href="usage/">{{ _('{0} Daily Users')|f(addon.average_daily_users|numberfmt) }}</a><br/><small>{{ _('{0} in last 30 days')|f('<span id="sum_usage_range"></span>')|safe }}</small></div></div>
<section class="island two-up c">
<div>
<a href="downloads/">{{ _('{0} Downloads')|f(addon.total_downloads|numberfmt) }}</a>
<small id="downloads-in-range"></small>
</div>
</div>
<div>
<a href="usage/">{{ _('{0} Average Daily Users')|f(addon.average_daily_users|numberfmt) }}</a>
<small id="users-in-range"></small>
</div>
</section>
<div class="toplists">
<div class="toplist loading">
<div class="statbox">
<h3>{{ _('Most Used Applications') }}</h3>
<div class="toplist">
<div class="island statbox">
<h2>{{ _('Most Used Applications') }}</h3>
<div class="piechart"></div>
<table data-report="apps" data-field="applications">
<table data-metric="apps">
</table>
<a href="apps/">{{ _('See more applications&hellip;') }}</a>
<a href="applications/">{{ _('See more applications&hellip;') }}</a>
</div>
</div>
<div class="toplist loading">
<div class="statbox">
<h3>{{ _('Most Used Languages') }}</h3>
<div class="toplist">
<div class="island statbox">
<h2>{{ _('Most Used Languages') }}</h3>
<div class="piechart"></div>
<table data-report="locales" data-field="locales">
<table data-metric="locales">
</table>
<a href="locales/">{{ _('See more languages&hellip;') }}</a>
<a href="languages/">{{ _('See more languages&hellip;') }}</a>
</div>
</div>
<div class="toplist loading">
<div class='statbox'>
<h3>{{ _('Most Used OSes') }}</h3>
<div class="toplist">
<div class='island statbox'>
<h2>{{ _('Most Used OSes') }}</h3>
<div class="piechart"></div>
<table data-report="os" data-field="oses">
<table data-metric="os">
</table>
<a href="os/">{{ _('See more operating systems&hellip;') }}</a>
</div>

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

@ -84,9 +84,8 @@
data-end_date="{{ view.end }}"
{% endif %}
data-base_url="{{ stats_base_url }}">
<div class="island chart c">
<div id="head-chart" style="background:#fff;height:384px">
</div>
<div class="island chart">
<div id="head-chart"></div>
</div>
{% block stats %}
{% endblock %}

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

@ -1,21 +1,19 @@
@import 'lib';
/* Clearfix! */
.statistics #page {
max-width: 1280px;
width: auto;
min-width: 1024px;
padding-bottom: 500px;
padding-left: 20px;
padding-right: 20px;
}
.statbox:after {
content: ".";
display: block;
clear: both;
height: 0;
visibility: hidden;
.statistics {
#page {
max-width: 1280px;
width: auto;
min-width: 1024px;
padding-bottom: 500px;
padding-left: 20px;
padding-right: 20px;
}
.island {
float: none;
margin-bottom: 2em;
}
}
.statbox .pagination {
@ -61,16 +59,6 @@ table {
* common styles
**/
.statbox .listing-header {
-moz-border-radius:0 0 4px 4px;
background-color:#F0F8FC;
background-image: -moz-linear-gradient(#daf0f6,#fdfefe);
border:0;
border-top:1px solid #A5BFCE;
line-height:2.5em;
overflow:hidden;
padding:0.1em 0 0.1em 0.25em;
}
table tbody tr {
border-top: 1px dotted #B5D9E5;
@ -80,14 +68,6 @@ table tbody tr {
background: -moz-linear-gradient(#ffffff, #eff8fb);
}
div.statbox {
border: 1px solid #C9E8F3;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
background: #fff;
}
.secondary {
.island {
float: none;
@ -102,7 +82,7 @@ div.statbox {
* Rules for date criteria selection
**/
.criteria {
.island.criteria {
padding: 0 0 0 12px;
margin: 0 0 0 1em;
z-index: 1000;
@ -133,32 +113,31 @@ div.statbox {
* Three-up stats
**/
.two-up > div {
float: left;
width: 50%;
margin: 10px 0;
text-align:center;
}
.two-up > div > div {
border-left: 1px dotted #aaa;
}
.two-up div a {
font-weight: bold;
font-size: 140%;
}
.two-up div small {
font-size: 110%;
}
.listing-header ul.chart_legend {
float: right;
}
.listing-header ul.chart_legend li {
margin: 0 .5em;
line-height: 2.5em;
display: block;
float: right;
.two-up {
div {
float: left;
text-align: center;
width: 50%;
height: 48px;
position: relative;
&:first-child:after {
content: '';
display: block;
position: absolute;
top: 0;
right: 0;
border-left: 1px dotted @medium-gray;
height: 100%;
}
a {
display: block;
font-weight: bold;
font-size: 140%;
}
small {
font-size: 110%;
}
}
}
/**
@ -413,10 +392,13 @@ table.stats-aggregate tbody span.change.minus {
width: 648px;
padding: 0;
width: 100%;
overflow: hidden;
}
#head-chart {
border-radius: 1em;
position: relative;
background: #fff;
overflow: hidden;
height: 384px;
}
.loadmessage {

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

@ -11,6 +11,8 @@
"jquery" : true,
"predef" : [ // AMO's globals
"z"
"z",
"_",
"Highcharts"
]
}

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

@ -1,9 +1,11 @@
(function () {
// "use strict";
var $win = $(window),
$chart = $('#head-chart');
baseConfig = {
chart: {
renderTo: 'head-chart',
zoomType: 'x',
zoomType: 'x'
},
credits: { enabled: false },
title: {
@ -69,6 +71,10 @@
"sources" : "downloads"
};
$win.bind("changeview", function() {
$chart.addClass('loading');
});
$win.bind("dataready", function(e, obj) {
var view = obj.view,
metric = view.metric,
@ -120,7 +126,7 @@
}
// Populate the chart config object.
var chartData = [], id
var chartData = [], id;
for (i = 0; i < fields.length; i++) {
field = fields[i];
id = field.split("|").slice(-1)[0];
@ -149,28 +155,79 @@
} else {
xFormatter = dayFormatter;
}
if (metricTypes[metric] == "users") {
yFormatter = userFormatter;
if (metric == 'overview') {
return function() {
return "<b>" + xFormatter(this.x) + "</b><br>" +
downloadFormatter(this.points[0].y) + "<br>" +
userFormatter(this.points[1].y);
};
} else {
yFormatter = downloadFormatter;
}
return function() {
return "<b>" + this.series.name + "</b><br>" +
xFormatter(this.x) + "<br>" +
yFormatter(this.y);
if (metricTypes[metric] == "users") {
yFormatter = userFormatter;
} else {
yFormatter = downloadFormatter;
}
return function() {
return "<b>" + this.series.name + "</b><br>" +
xFormatter(this.x) + "<br>" +
yFormatter(this.y);
};
}
})();
// Set up the new chart's configuration.
var newConfig = $.extend(baseConfig, { series: chartData });
if (fields.length == 1) {
newConfig.legend.enabled = false;
newConfig.chart.margin = [50, 50, 50, 80]
// 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);
}
}
}, { // Daily Users
title: {
text: gettext('Daily Users')
},
labels: {
formatter: function() {
return Highcharts.numberFormat(this.value, 0);
}
},
// min: 0,
opposite: true
}
];
// set Daily Users series to use the right yAxis.
newConfig.series[1].yAxis = 1;
newConfig.tooltip.shared = true;
}
newConfig.tooltip.formatter = tooltipFormatter;
if (fields.length == 1) {
newConfig.legend.enabled = false;
// newConfig.chart.margin = [50, 50, 50, 80];
}
// Generate a pretty title for the chart.
var title;
if (typeof obj.view.range == 'string') {
title = format(csv_keys.chartTitle[metric][0], obj.view.range);
} else {
title = format(csv_keys.chartTitle[metric][1], [z.date.date_string(new Date(start), '-'),
z.date.date_string(new Date(end), '-')]);
}
newConfig.title = {
text: title
};
if (chart) chart.destroy();
chart = new Highcharts.Chart(newConfig);
$chart.removeClass('loading');
});
})();

14
media/js/impala/stats/controls.js поставляемый
Просмотреть файл

@ -52,10 +52,13 @@
if (newState.range) {
if (!newState.range.custom) {
var newRange = newState.range,
$rangeEl = $('[data-range="' + newRange + '"]');
$rangeEl = $('li[data-range="' + newRange + '"]');
if ($rangeEl.length) {
$rangeSelector.children("li.selected").removeClass("selected");
$rangeEl.addClass("selected");
} else {
$rangeSelector.children("li.selected").removeClass("selected");
$('li[data-range="custom"]').addClass("selected");
}
} else {
$rangeSelector.children("li.selected").removeClass("selected");
@ -65,12 +68,11 @@
}
if (newState.group) {
$groupSelector.children('.selected').removeClass('selected');
$('[data-group="' + newState.group + '"]').addClass('selected');
$('li[data-group="' + newState.group + '"]').addClass('selected');
}
});
$("#date-range-form").submit(function(e) {
e.preventDefault();
$("#date-range-form").submit(_pd(function(e) {
var start = new Date($("#date-range-start").val()),
end = new Date($("#date-range-end").val()),
newRange = {
@ -78,9 +80,7 @@
start: z.date.date(start),
end: z.date.date(end)
};
$rangeSelector.trigger('changeview', {range: newRange});
$customModal.trigger('close');
return false;
});
}));
})();

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

@ -1,7 +1,11 @@
csv_keys = {
var csv_keys = {
sources: {
"null": gettext('unknown')
},
overview: {
'downloads' : gettext('Downloads'),
'updates' : gettext('Daily Users')
},
apps : {
'{ec8030f7-c20a-464f-9b0e-13a3a9e97384}' : gettext('Firefox'),
'{86c18b42-e466-45a9-ae7a-9b95ba6f5640}' : gettext('Mozilla'),
@ -9,5 +13,43 @@ csv_keys = {
'{718e30fb-e89b-41dd-9da7-e25a45638b28}' : gettext('Sunbird'),
'{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}' : gettext('SeaMonkey'),
'{a23983c0-fd0e-11dc-95ff-0800200c9a66}' : gettext('Fennec')
},
chartTitle: {
"overview" : [
gettext("Downloads and Daily Users, last {0}"),
gettext("Downloads and Daily Users from {0} to {1}")
],
"downloads" : [
gettext("Downloads, last {0}"),
gettext("Downloads from {0} to {1}")
],
"usage" : [
gettext("Daily Users, last {0}"),
gettext("Daily Users from {0} to {1}")
],
"apps" : [
gettext("Applications, last {0}"),
gettext("Applications from {0} to {1}")
],
"os" : [
gettext("Operating Systems, last {0}"),
gettext("Operating Systems from {0} to {1}")
],
"locales" : [
gettext("Languages, last {0}"),
gettext("Languages from {0} to {1}")
],
"versions" : [
gettext("Add-on Versions, last {0}"),
gettext("Add-on Versions from {0} to {1}")
],
"statuses" : [
gettext("Add-on Status, last {0}"),
gettext("Add-on Status from {0} to {1}")
],
"sources" : [
gettext("Download Sources, last {0}"),
gettext("Download Sources from {0} to {1}")
]
}
};

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

@ -12,13 +12,13 @@ z.date = (function() {
// millis("2 days")
// > 172800000
function millis(str) {
var tokens = str.split(/\s+/);
n = parseInt(tokens[0]);
var tokens = str.split(/\s+/),
n = parseInt(tokens[0], 10);
if (!tokens[1]) throw "Invalid duration string";
unit = tokens[1].replace(/s$/,'').toLowerCase();
var unit = tokens[1].replace(/s$/,'').toLowerCase();
if (!_millis[ unit ]) throw "Invalid time unit";
return n * _millis[ unit ];
};
}
// pads a number with a preceding zero.
// pad2(2)
@ -28,32 +28,31 @@ z.date = (function() {
function pad2(n) {
var str = n.toString();
return ('0' + str).substr(-2);
};
}
// Takes a date object and converts it to a time-less
// representation of today's date.
function date(d) {
return Date.parse(date_string(d, '-'));
};
}
function date_string(d, del) {
del = del || '-';
return [d.getFullYear(), pad2(d.getMonth()+1), pad2(d.getDate())].join(del);
};
}
function datepicker_format(d) {
return [pad2(d.getMonth()+1), pad2(d.getDate()), d.getFullYear()].join('/');
};
}
// Truncates the current time off today's date.
function today() {
var d = new Date();
return date(d);
};
}
// returns a millisecond timestamp for a specified duration in the past.
function ago(str, times) {
times = (times !== undefined) ? times : 1;
return today() - millis(str) * times;
};
}
// takes a range object and normalizes it to have a `start` and `end` property.
function normalizeRange(range) {
@ -65,7 +64,7 @@ z.date = (function() {
ret.start = range.start;
ret.end = range.end;
} else {
throw "Invalid range values found."
throw "Invalid range values found.";
}
return ret;
}

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

@ -7,11 +7,11 @@ function dbg() {
z.hasPushState = (typeof history.replaceState === "function");
z.StatsManager = (function() {
"use strict";
// "use strict";
// The version of the stats localStorage we are using.
// If you increment this number, you cache-bust everyone!
var STATS_VERSION = 21;
var STATS_VERSION = 22;
var storage = z.Storage("stats"),
storageCache = z.Storage("statscache"),
@ -30,7 +30,8 @@ z.StatsManager = (function() {
"os": true,
"sources": true,
"versions": true,
"statuses": true
"statuses": true,
"overview": true
};
// is a metric an average or a sum?
@ -46,7 +47,7 @@ z.StatsManager = (function() {
};
// Initialize from localStorage when dom is ready.
$(function() {
function init() {
dbg("looking for local data");
if (verifyLocalStorage()) {
var cacheObject = storageCache.get(addonId);
@ -58,8 +59,8 @@ z.StatsManager = (function() {
}
}
}
});
}
$(init);
// These functions deal with our localStorage cache.
@ -80,6 +81,7 @@ z.StatsManager = (function() {
return true;
} else {
dbg("wrong offline data verion");
clearLocalStorage();
return false;
}
}
@ -93,11 +95,12 @@ z.StatsManager = (function() {
currentView = $.extend(currentView, newView);
// Fetch the data from the server or storage, and notify other components.
getDataRange(currentView, function(data) {
$.when( getDataRange(currentView) )
.then( function(data) {
$(window).trigger("dataready", {
'view': currentView,
'view' : currentView,
'fields': getAvailableFields(currentView),
'data': data
'data' : data
});
});
}
@ -153,17 +156,18 @@ z.StatsManager = (function() {
// the range currently stored locally. Once all server requests return,
// we move on.
function getDataRange(view, callback) {
dbg("enter getDataRange");
dbg("enter getDataRange", view.metric);
var range = z.date.normalizeRange(view.range),
metric = view.metric,
ds,
needed = 0;
needed = 0,
$def = $.Deferred();
function finished() {
needed--;
dbg(pendingFetches, " fetches pending");
if (needed < 1) {
var ret = {}, i, row,
var ret = {}, row,
step = z.date.millis("1 day");
ds = dataStore[metric];
for (var i=range.start; i<range.end; i+= step) {
@ -172,13 +176,14 @@ z.StatsManager = (function() {
}
}
ret = groupData(ret, view);
callback.call(this, ret);
ret.metric = metric;
$def.resolve(ret);
}
}
if (dataStore[metric]) {
ds = dataStore[metric];
dbg("range", range.start, range.end)
dbg("range", range.start, range.end);
if (ds.maxdate < range.end) {
needed++;
fetchData(metric, ds.maxdate, range.end, finished);
@ -196,10 +201,11 @@ z.StatsManager = (function() {
needed++;
fetchData(metric, range.start, range.end, finished);
}
return $def;
}
// Aggregate data based on our view's `group` setting.
// Aggregate data based on view's `group` setting.
function groupData(data, view) {
var metric = view.metric,
range = z.date.normalizeRange(view.range),
@ -216,17 +222,20 @@ z.StatsManager = (function() {
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)) {
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;
groupVal.count /= groupCount;
}
if (metric in breakdownMetrics) {
// 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) {
// average for mean metrics.
if (metricTypes[metric] == 'mean') {
groupVal.data[field] /= groupCount;
}
@ -264,8 +273,8 @@ z.StatsManager = (function() {
// The beef. Negotiates with the server for data.
function fetchData(metric, start, end, callback) {
var seriesStart = start;
var seriesEnd = end;
var seriesStart = start,
seriesEnd = end;
pendingFetches++;
@ -291,9 +300,8 @@ z.StatsManager = (function() {
dataStore[metric].maxdate = 0;
}
var ds = dataStore[metric], data;
data = JSON.parse(raw_data);
var ds = dataStore[metric],
data = JSON.parse(raw_data);
var i, datekey;
for (i=0; i<data.length; i++) {
@ -318,7 +326,7 @@ z.StatsManager = (function() {
}
setTimeout(function () {
AMO.fetchData(metric, start, end, callback);
fetchData(metric, start, end, callback);
}, retry_delay);
}
@ -363,7 +371,10 @@ z.StatsManager = (function() {
var parts = field.split('|'),
val = row;
// give up if the row is falsy.
if (!val) return null;
// drill into the row object for a nested key.
// `data|api` means row['data']['api']
for (var i = 0; i < parts.length; i++) {
val = val[parts[i]];
if (!val) {
@ -376,8 +387,8 @@ z.StatsManager = (function() {
function getPrettyName(metric, field) {
var parts = field.split('_');
var key = parts[0];
var parts = field.split('_'),
key = parts[0];
parts = parts.slice(1);
if (metric in csv_keys) {

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

@ -6,9 +6,10 @@ $(function() {
range = view.range;
if (range) {
if (typeof range == "string") {
queryParams['last'] = range.split(/\s+/)[0];
queryParams.last = range.split(/\s+/)[0];
} else if (typeof range == "object") {
// queryParams.start = z.date.date_string(new Date(range.start), '');
// queryParams.end = z.date.date_string(new Date(range.end), '');
}
}
queryParams = $.param(queryParams);
@ -17,10 +18,11 @@ $(function() {
}
});
// Set up initial default view.
var initView = {
metric: $('.primary').attr('data-report'),
range: $('.primary').attr('data-range'),
group: 'day'
group: 'week'
};
$(window).trigger('changeview', initView);

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -727,7 +727,7 @@ MINIFY_BUNDLES = {
'js/lib/jquery-datepicker.js',
'js/lib/highcharts.src.js',
'js/impala/stats/csv_keys.js',
# 'js/zamboni/stats/helpers.js',
'js/zamboni/stats/helpers.js',
# 'js/zamboni/stats/stats_manager.js',
# 'js/zamboni/stats/stats_tables.js',
# 'js/zamboni/stats/stats.js',