Index: modules/statsmaster/files/usercounts.html |
=================================================================== |
new file mode 100644 |
--- /dev/null |
+++ b/modules/statsmaster/files/usercounts.html |
@@ -0,0 +1,256 @@ |
+<!DOCTYPE html> |
+<html> |
+<head> |
+ <meta charset="utf-8"> |
+ <title>User counter</title> |
+ <style type="text/css"> |
+ form |
+ { |
+ display: inline; |
+ } |
+ |
+ .spacer |
+ { |
+ margin: 0 10px; |
+ } |
+ |
+ table |
+ { |
+ border-collapse: collapse; |
+ } |
+ |
+ td, th |
+ { |
+ padding: 5px; |
+ border: 1px solid black; |
+ } |
+ |
+ td |
+ { |
+ text-align: right; |
+ } |
+ </style> |
+ <script type="text/javascript"> |
+ const MILLIS_IN_DAY = 24 * 60 * 60 * 1000; |
+ |
+ function zeroPad(number, digits) |
+ { |
+ var result = number.toString(10); |
+ while (result.length < digits) |
+ result = "0" + result; |
+ return result; |
+ } |
+ |
+ function formatDate(date) |
+ { |
+ return zeroPad(date.getFullYear(), 4) + "-" + zeroPad(date.getMonth() + 1, 2) + "-" + zeroPad(date.getDate(), 2); |
+ } |
+ |
+ var curMonth = null; |
+ var curMonthFilters = null; |
+ var curMonthNotifications = null; |
+ |
+ function checkStats(data, fromDate, offset, callback) |
+ { |
+ var date = new Date(fromDate.getTime() + offset * MILLIS_IN_DAY); |
+ |
+ function doDownload(url, descr, callback) |
+ { |
+ var request = new XMLHttpRequest(); |
+ request.open("GET", url); |
+ request.addEventListener("load", function() |
+ { |
+ if (request.status != 200) |
+ alert("Failed to download " + descr + ", got response " + request.status); |
+ else |
+ callback(JSON.parse(request.responseText)); |
+ }, false); |
+ request.addEventListener("error", function() |
+ { |
+ alert("Failed to download " + descr); |
+ }, false); |
+ request.send(null); |
+ } |
+ |
+ function doCheckStats(source, type) |
+ { |
+ if (!(date.getDate() in source.day)) |
+ return; |
+ |
+ var intervals = source.day[date.getDate()].previousDownload; |
+ for (var interval in intervals) |
+ { |
+ if (!intervals.hasOwnProperty(interval)) |
+ continue; |
+ |
+ var days; |
+ if (/^\d+ day/.test(interval)) |
+ days = parseInt(interval, 10); |
+ else if (/^\d+ month/.test(interval)) |
+ days = 31; |
+ else |
+ continue; |
+ |
+ // Most requests claiming that last download was long ago are bots or broken installations |
+ if (days > 30) |
+ continue; |
+ |
+ var hits = intervals[interval].hits; |
+ for (var i = Math.max(offset, 0); i < data.length; i++) |
+ { |
+ if (i - offset < 30 && i - offset + days >= 30) |
+ data[i]["30day " + type] += hits; |
+ if (i - offset < 7 && i - offset + days >= 7) |
+ data[i]["7day " + type] += hits; |
+ } |
+ } |
+ } |
+ |
+ var month = zeroPad(date.getFullYear(), 4) + zeroPad(date.getMonth() + 1, 2); |
+ if (month != curMonth) |
+ { |
+ doDownload("/raw/subscription/" + month + "/exceptionrules.txt.json", "acceptable ads data for month " + month, function(json) |
+ { |
+ curMonthFilters = json; |
+ doDownload("/raw/notification/" + month + "/notification.json.json", "notification data for month " + month, function(json) |
+ { |
+ curMonthNotifications = json; |
+ curMonth = month; |
+ doCheckStats(curMonthFilters, "acceptable"); |
+ doCheckStats(curMonthNotifications, "total"); |
+ callback(); |
+ }); |
+ }); |
+ } |
+ else |
+ { |
+ doCheckStats(curMonthFilters, "acceptable"); |
+ doCheckStats(curMonthNotifications, "total"); |
+ callback(); |
+ } |
+ } |
+ |
+ function showResults(fromDate, data) |
+ { |
+ function createCell(row, text) |
+ { |
+ var cell = document.createElement("td"); |
+ cell.appendChild(document.createTextNode(text)); |
+ row.appendChild(cell); |
+ } |
+ |
+ var resultBody = document.getElementById("resultBody"); |
+ while (resultBody.lastChild) |
+ resultBody.removeChild(resultBody.lastChild); |
+ |
+ for (var i = 0; i < data.length; i++) |
+ { |
+ var date = new Date(fromDate.getTime() + i * MILLIS_IN_DAY); |
+ |
+ var row = document.createElement("tr"); |
+ createCell(row, date.toLocaleDateString()); |
+ createCell(row, data[i]["7day total"].toLocaleString()); |
+ createCell(row, data[i]["7day acceptable"].toLocaleString()); |
+ createCell(row, (100 - data[i]["7day acceptable"] / data[i]["7day total"] * 100).toFixed(2) + "%"); |
+ createCell(row, data[i]["30day total"].toLocaleString()); |
+ createCell(row, data[i]["30day acceptable"].toLocaleString()); |
+ createCell(row, (100 - data[i]["30day acceptable"] / data[i]["30day total"] * 100).toFixed(2) + "%"); |
+ resultBody.appendChild(row); |
+ } |
+ |
+ document.getElementById("result").removeAttribute("hidden"); |
+ document.getElementById("wait").setAttribute("hidden", "hidden"); |
+ } |
+ |
+ function calculate() |
+ { |
+ document.getElementById("result").setAttribute("hidden", "hidden"); |
+ document.getElementById("wait").removeAttribute("hidden"); |
+ |
+ var fromDate = document.getElementById("from").value.replace(/^\s+/, "").replace(/\s+$/, ""); |
+ var match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(fromDate); |
+ if (!match) |
+ { |
+ alert("Wrong start date format."); |
+ document.getElementById("from").focus(); |
+ return; |
+ } |
+ fromDate = new Date(parseInt(match[1], 10), parseInt(match[2], 10) - 1, parseInt(match[3], 10), 12); |
+ |
+ var toDate = document.getElementById("to").value.replace(/^\s+/, "").replace(/\s+$/, ""); |
+ var match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(toDate); |
+ if (!match) |
+ { |
+ alert("Wrong end date format."); |
+ document.getElementById("to").focus(); |
+ return; |
+ } |
+ toDate = new Date(parseInt(match[1], 10), parseInt(match[2], 10) - 1, parseInt(match[3], 10), 12); |
+ |
+ if (fromDate > toDate) |
+ { |
+ var tmp = fromDate; |
+ fromDate = toDate; |
+ toDate = tmp; |
+ } |
+ |
+ var data = []; |
+ for (var d = fromDate; formatDate(d) <= formatDate(toDate); d = new Date(d.getTime() + MILLIS_IN_DAY)) |
+ data.push({"7day total": 0, "7day acceptable": 0, "30day total": 0, "30day acceptable": 0}); |
+ |
+ var offset = -30; |
+ var callback = function() |
+ { |
+ offset++; |
+ if (offset >= data.length) |
+ { |
+ showResults(fromDate, data); |
+ return; |
+ } |
+ checkStats(data, fromDate, offset, callback); |
+ }; |
+ callback(); |
+ } |
+ |
+ window.onload = function() |
+ { |
+ document.getElementById("from").value = formatDate(new Date(Date.now() - 7 * MILLIS_IN_DAY)); |
+ document.getElementById("to").value = formatDate(new Date(Date.now() - MILLIS_IN_DAY)); |
+ } |
+ </script> |
+</head> |
+<body> |
+ <p> |
+ Please enter the date range that you need user statistics for: |
+ </p> |
+ <p> |
+ <form action="javascript://" onsubmit="calculate();"> |
+ Start date (yyyy-mm-dd format): <input id="from" type="text"> |
+ <span class="spacer"></span> |
+ End date (yyyy-mm-dd format): <input id="to" type="text"> |
+ <span class="spacer"></span> |
+ <button type="submit">Calculate</button> |
+ </form> |
+ </p> |
+ <p id="wait" hidden> |
+ Please wait, data is being fetched... |
+ </p> |
+ <table id="result" hidden> |
+ <tr> |
+ <th rowspan="2">Date</th> |
+ <th colspan="3">7-day active users</th> |
+ <th colspan="3">30-day active users</th> |
+ </tr> |
+ <tr> |
+ <th>Total</th> |
+ <th>Acceptable ads</th> |
+ <th>Opt-out rate</th> |
+ <th>Total</th> |
+ <th>Acceptable ads</th> |
+ <th>Opt-out rate</th> |
+ </tr> |
+ <tbody id="resultBody"> |
+ </tbody> |
+ </table> |
+</body> |
+</html> |