Milimetric has uploaded a new change for review.
https://gerrit.wikimedia.org/r/181179
Change subject: Add basic flask server with highcharts
......................................................................
Add basic flask server with highcharts
Change-Id: Ia9132f884e59427cd349318efb9c8321f36169d6
---
A .gitignore
A README
A abacuserver/__init__.py
A abacuserver/static/bindings.js
A abacuserver/static/main.js
A abacuserver/static/style.css
A abacuserver/templates/graph.html
A abacuserver/templates/index.html
A abacuserver/views/__init__.py
A abacuserver/views/data.py
A abacuserver/views/graph.py
A run.py
A setup.py
A websiteconfig.py
14 files changed, 242 insertions(+), 0 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/analytics/abacist
refs/changes/79/181179/1
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a36e13e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+*.py[co]
+*.egg
+*swp
+*swo
+
+*egg-info
+
+.coverage
+.idea
+
+/.tox
diff --git a/README b/README
new file mode 100644
index 0000000..f5bbd4c
--- /dev/null
+++ b/README
@@ -0,0 +1,4 @@
+sudo pip install -e .
+python run.py
+
+Browse to localhost:5000/graph and type something in the input box at the top,
then tab out to update
diff --git a/abacuserver/__init__.py b/abacuserver/__init__.py
new file mode 100644
index 0000000..d1ecfd8
--- /dev/null
+++ b/abacuserver/__init__.py
@@ -0,0 +1,13 @@
+from flask import Flask, render_template
+from abacuserver.views import data, graph
+
+app = Flask(__name__)
+app.config.from_object('websiteconfig')
+
+app.register_blueprint(data.mod)
+app.register_blueprint(graph.mod)
+
+
[email protected]('/')
+def index():
+ return render_template('index.html')
diff --git a/abacuserver/static/bindings.js b/abacuserver/static/bindings.js
new file mode 100644
index 0000000..76986bc
--- /dev/null
+++ b/abacuserver/static/bindings.js
@@ -0,0 +1,88 @@
+ko.bindingHandlers.graph = {
+ init: function (element) {
+ $(element).highcharts({
+ title: {
+ text: 'Hits To This Post',
+ x: -20 //center
+ },
+ xAxis: {},
+ yAxis: {
+ title: {text: 'Hits'},
+ plotLines: [{
+ value: 0,
+ width: 1,
+ color: '#808080'
+ }]
+ },
+ legend: {
+ layout: 'vertical',
+ align: 'right',
+ verticalAlign: 'middle',
+ borderWidth: 0
+ },
+ series: [{
+ name: 'Total',
+ data: [],
+ }]
+ });
+ },
+ update: function (element, valueAccessor) {
+ var params = ko.unwrap(valueAccessor()),
+ title = ko.unwrap(params.title),
+ resolution = ko.unwrap(params.resolution),
+ data = ko.unwrap(params.data),
+ categories = [],
+ totalData = [],
+ newSeries = {};
+
+ if (!data || !resolution) { return; }
+
+ // assumes the data is sorted already on the server
+ data[resolution].forEach(function (d) {
+ Object.keys(d.topReferers).forEach(function (referer) {
+ if (!newSeries.hasOwnProperty(referer)) {
+ // cath up with the main series
+ newSeries[referer] = categories.map(function(){ return 0;
});
+ }
+ newSeries[referer].push(d.topReferers[referer]);
+ });
+
+ categories.push(d.unitsAgo);
+ totalData.push(d.hits);
+
+ // catch up any series that didn't show up at this point
+ Object.keys(newSeries).forEach(function (referer) {
+ while (newSeries[referer].length < categories.length) {
+ newSeries[referer].push(0);
+ }
+ });
+ });
+ newSeries.Total = totalData;
+
+ var chart = $(element).highcharts();
+
+ chart.setTitle({
+ text: title,
+ });
+ chart.xAxis[0].update({
+ categories: categories,
+ title: {text: resolution},
+ });
+
+ Object.keys(newSeries).forEach(function (series) {
+ var updated = false;
+ chart.series.forEach(function (existingSeries) {
+ if (existingSeries.name === series) {
+ existingSeries.update({data: newSeries[series]});
+ updated = true;
+ }
+ });
+ if (!updated) {
+ chart.addSeries({
+ name: series,
+ data: newSeries[series],
+ });
+ }
+ });
+ }
+};
diff --git a/abacuserver/static/main.js b/abacuserver/static/main.js
new file mode 100644
index 0000000..8bebfae
--- /dev/null
+++ b/abacuserver/static/main.js
@@ -0,0 +1,17 @@
+(function () {
+ var vm = {
+ post: ko.observable(),
+ data: ko.observable(),
+ resolution: ko.observable(),
+ };
+
+ vm.post.subscribe(function (post) {
+ $.get('data/' + post).done(this.data);
+ }, vm);
+
+ vm.title = ko.computed(function () {
+ return 'Hits to ' + this.post();
+ }, vm);
+
+ ko.applyBindings(vm);
+}());
diff --git a/abacuserver/static/style.css b/abacuserver/static/style.css
new file mode 100644
index 0000000..5ed9530
--- /dev/null
+++ b/abacuserver/static/style.css
@@ -0,0 +1 @@
+.graph { height: 90vh; }
diff --git a/abacuserver/templates/graph.html b/abacuserver/templates/graph.html
new file mode 100644
index 0000000..d1cee9e
--- /dev/null
+++ b/abacuserver/templates/graph.html
@@ -0,0 +1,25 @@
+<html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static',
filename='style.css') }}">
+ </head>
+ <body>
+
+ <input data-bind="value: post"/>
+ <select data-bind="value: resolution">
+ <option>hourly</option>
+ <option>daily</option>
+ <option>monthly</option>
+ <option>yearly</option>
+ </select>
+
+ <div class="graph" data-bind="graph: {data: data, resolution:
resolution, title: title}"></div>
+
+
+ <script
src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
+ <script
src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
+ <script src="//code.highcharts.com/highcharts.js"></script>
+ <script src="//code.highcharts.com/modules/exporting.js"></script>
+ <script src="{{ url_for('static', filename='bindings.js') }}"></script>
+ <script src="{{ url_for('static', filename='main.js') }}"></script>
+ </body>
+</html>
diff --git a/abacuserver/templates/index.html b/abacuserver/templates/index.html
new file mode 100644
index 0000000..7b4438b
--- /dev/null
+++ b/abacuserver/templates/index.html
@@ -0,0 +1,3 @@
+<html>
+ <body>hello world</body>
+</html>
diff --git a/abacuserver/views/__init__.py b/abacuserver/views/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/abacuserver/views/__init__.py
diff --git a/abacuserver/views/data.py b/abacuserver/views/data.py
new file mode 100644
index 0000000..dd6297f
--- /dev/null
+++ b/abacuserver/views/data.py
@@ -0,0 +1,43 @@
+from flask import Blueprint, jsonify
+
+mod = Blueprint('data', __name__)
+
+
[email protected]('/data/<string:post>')
+def data_for_post(post):
+
+ # TODO: get post data from redis
+ from random import randint
+
+ def fake(index, lowest_hits, highest_hits, google, yahoo, bing):
+ stats = dict(
+ unitsAgo=index,
+ hits=randint(lowest_hits, highest_hits),
+ topReferers={
+ 'Google': google,
+ 'Yahoo': yahoo,
+ 'Bing': bing,
+ }
+ )
+ # simulate some missing data
+ if index % 3 == 0:
+ stats['topReferers'].pop('Bing')
+ # simulate some missing data at the beginning
+ if index > 20:
+ stats['topReferers'].pop('Yahoo')
+
+ stats['topReferers']['Other'] = (
+ stats['hits'] - sum([v for k, v in stats['topReferers'].items()])
+ )
+
+ return stats
+
+ r = lambda x: range(x, 0, -1)
+ stats = dict(
+ hourly=[fake(i, 5, 30, 2, 2, 1) for i in r(25)],
+ daily=[fake(i, 120, 480, 20, 80, 10) for i in r(31)],
+ monthly=[fake(i, 3600, 7200, 400, 1000, 2000) for i in r(13)],
+ yearly=[fake(i, 43200, 100000, 30000, 10000, 3000) for i in r(5)],
+ )
+
+ return jsonify(stats)
diff --git a/abacuserver/views/graph.py b/abacuserver/views/graph.py
new file mode 100644
index 0000000..f7d1020
--- /dev/null
+++ b/abacuserver/views/graph.py
@@ -0,0 +1,9 @@
+from flask import Blueprint, render_template
+
+mod = Blueprint('graph', __name__)
+
+
[email protected]('/graph')
+def index():
+ # maybe get a list of the most recent posts or something
+ return render_template('graph.html')
diff --git a/run.py b/run.py
new file mode 100644
index 0000000..356ff2c
--- /dev/null
+++ b/run.py
@@ -0,0 +1,2 @@
+from abacuserver import app
+app.run(debug=True)
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..e5a2ee7
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+# follow the frog
+
+from setuptools import setup
+
+setup(
+ name='abacuserver',
+ version='0.0.1',
+ author='Ori Livneh, Dan Andreescu',
+ packages=[
+ 'abacuserver',
+ ],
+ install_requires=[
+ 'Flask',
+ 'redis'
+ ],
+)
diff --git a/websiteconfig.py b/websiteconfig.py
new file mode 100644
index 0000000..fe2b5fd
--- /dev/null
+++ b/websiteconfig.py
@@ -0,0 +1,9 @@
+import os
+
+_basedir = os.path.abspath(os.path.dirname(__file__))
+
+DEBUG = True
+
+SECRET_KEY = 'testkey'
+
+del os
--
To view, visit https://gerrit.wikimedia.org/r/181179
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ia9132f884e59427cd349318efb9c8321f36169d6
Gerrit-PatchSet: 1
Gerrit-Project: analytics/abacist
Gerrit-Branch: master
Gerrit-Owner: Milimetric <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits