You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by jo...@apache.org on 2013/09/17 21:57:50 UTC
[23/50] git commit: [#6545] Add graph of forum posts
[#6545] Add graph of forum posts
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/aad259f0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/aad259f0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/aad259f0
Branch: refs/heads/cj/6422
Commit: aad259f0bbbc598358a5b2cf08ed30da19014e48
Parents: 192793e
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Wed Sep 4 22:06:19 2013 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Tue Sep 10 14:31:48 2013 +0000
----------------------------------------------------------------------
Allura/allura/lib/helpers.py | 6 ++
Allura/allura/public/nf/js/stats.js | 29 ++++---
.../forgediscussion/controllers/root.py | 81 +++++++++++++++++++-
ForgeDiscussion/forgediscussion/forum_main.py | 2 +-
.../templates/discussionforums/stats_graph.html | 79 +++++++++++++++++++
.../forgetracker/templates/tracker/stats.html | 4 -
6 files changed, 184 insertions(+), 17 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/aad259f0/Allura/allura/lib/helpers.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 0e13fad..9c3d9e4 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -1006,3 +1006,9 @@ def iter_entry_points(group, *a, **kw):
for ep in pkg_resources.iter_entry_points(group, *a, **kw):
if ep.name not in disabled:
yield ep
+
+
+# http://stackoverflow.com/a/1060330/79697
+def daterange(start_date, end_date):
+ for n in range(int((end_date - start_date).days)):
+ yield start_date + timedelta(n)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/aad259f0/Allura/allura/public/nf/js/stats.js
----------------------------------------------------------------------
diff --git a/Allura/allura/public/nf/js/stats.js b/Allura/allura/public/nf/js/stats.js
index 2c803f6..72e12dc 100644
--- a/Allura/allura/public/nf/js/stats.js
+++ b/Allura/allura/public/nf/js/stats.js
@@ -20,26 +20,30 @@
/*global jQuery, $, addCommas */
jQuery(function($) {
// date range picker
- if ($('.picker input').length) {
- $('.picker input').daterangepicker({
+ var input = $('#stats_date_picker input');
+ if (input.length) {
+ input.daterangepicker({
onOpen: function() {
- $('.picker input')[0].prev_value = $('.picker input').val();
+ input[0].prev_value = input.val();
},
onClose: function() {
- if ($('.picker input')[0].prev_value !== $('.picker input').val()) {
- $('.picker input').parents('form').submit();
- //console.log('close',$('.picker input').val());
+ if (input[0].prev_value !== input.val()) {
+ input.parents('form').submit();
}
},
rangeSplitter: 'to',
dateFormat: 'yy-mm-dd', // yy is 4 digit
- earliestDate: new Date($('.picker input').attr('data-start-date')),
+ earliestDate: new Date(input.attr('data-start-date')),
latestDate: new Date()
});
}
});
function chartProjectStats(url, params, series, checkEmpty, tooltipFormat){
+ var timeformat = "%y-%0m-%0d";
+ tooltipFormat = tooltipFormat || function(x,y,item) {
+ return y + " on " + $.plot.formatDate(new Date(parseInt(x, 10)), timeformat);
+ };
var holder = $('#stats-viz');
var dates = $('#dates').val().split(' to ');
var begin = Date.parse(dates[0]).setTimezoneOffset(0);
@@ -64,7 +68,7 @@ function chartProjectStats(url, params, series, checkEmpty, tooltipFormat){
colors: ['#0685c6','#87c706','#c7c706','#c76606'],
xaxis:{
mode: "time",
- timeformat: "%y-%0m-%0d",
+ timeformat: timeformat,
minTickSize: [1, "day"],
min: begin,
max: end,
@@ -103,8 +107,13 @@ function chartProjectStats(url, params, series, checkEmpty, tooltipFormat){
$('<div id="tooltip" class="tooltip">' + tooltipFormat(x,y,item) + '</div>').css( {
position: 'absolute',
display: 'none',
- top: item.pageY + 5,
- left: item.pageX + 5
+ top: item.pageY - 5,
+ left: item.pageX + 5,
+ zIndex: 1,
+ background: 'white',
+ border: '1px solid black',
+ borderRadius: '0.5em',
+ padding: '0 0.3em',
}).appendTo("body").fadeIn(200);
}
}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/aad259f0/ForgeDiscussion/forgediscussion/controllers/root.py
----------------------------------------------------------------------
diff --git a/ForgeDiscussion/forgediscussion/controllers/root.py b/ForgeDiscussion/forgediscussion/controllers/root.py
index fa0e97b..9250ad0 100644
--- a/ForgeDiscussion/forgediscussion/controllers/root.py
+++ b/ForgeDiscussion/forgediscussion/controllers/root.py
@@ -18,10 +18,12 @@
import json
import logging
from urllib import unquote
-from itertools import imap
+from datetime import date, datetime, timedelta, time
+from time import mktime
+from collections import OrderedDict
from tg import expose, validate, redirect, flash, response
-from tg.decorators import with_trailing_slash
+from tg.decorators import with_trailing_slash, without_trailing_slash
from pylons import tmpl_context as c, app_globals as g
from pylons import request
from formencode import validators
@@ -212,6 +214,81 @@ class RootController(BaseController, DispatchIndex, FeedController):
'Recent posts to %s' % app.config.options.mount_label,
app.url)
+ @without_trailing_slash
+ @expose('jinja:forgediscussion:templates/discussionforums/stats_graph.html')
+ def stats(self, dates=None, **kw):
+ if not dates:
+ dates = "{} to {}".format(
+ (date.today() - timedelta(days=60)).strftime('%Y-%m-%d'),
+ date.today().strftime('%Y-%m-%d'))
+ return dict(
+ dates=dates,
+ )
+
+ @expose('json')
+ @validate(dict(
+ begin=h.DateTimeConverter(if_empty=None, if_invalid=None),
+ end=h.DateTimeConverter(if_empty=None, if_invalid=None),
+ ))
+ def stats_data(self, begin=None, end=None, **kw):
+ end = end or date.today()
+ begin = begin or end - timedelta(days=60)
+
+ discussion_id_q = {
+ '$in': [d._id for d in c.app.forums]
+ }
+ # must be ordered dict, so that sorting by this works properly
+ grouping = OrderedDict()
+ grouping['year'] = {'$year': '$timestamp'}
+ grouping['month'] = {'$month': '$timestamp'}
+ grouping['day'] = {'$dayOfMonth': '$timestamp'}
+ {
+ 'year': {'$year': '$timestamp'},
+ 'month': {'$month': '$timestamp'},
+ 'day': {'$dayOfMonth': '$timestamp'},
+ }
+ mongo_data = model.ForumPost.query.aggregate([
+ {'$match': {
+ 'discussion_id': discussion_id_q,
+ 'status': 'ok',
+ 'timestamp': {
+ # convert date to datetime to make pymongo happy
+ '$gte': datetime.combine(begin, time.min),
+ '$lte': datetime.combine(end, time.max),
+ },
+ }},
+ {'$group': {
+ '_id': grouping,
+ 'posts': {'$sum': 1},
+ }},
+ {'$sort': {
+ '_id': pymongo.ASCENDING,
+ }},
+ ])['result']
+
+ def reformat_data(mongo_data):
+ def item(day, val):
+ return [
+ mktime(day.timetuple()) * 1000,
+ val
+ ]
+
+ next_expected_date = begin
+ for d in mongo_data:
+ this_date = datetime(d['_id']['year'], d['_id']['month'], d['_id']['day'])
+ for day in h.daterange(next_expected_date, this_date):
+ yield item(day, 0)
+ yield item(this_date, d['posts'])
+ next_expected_date = this_date + timedelta(days=1)
+ for day in h.daterange(next_expected_date, end + timedelta(days=1)):
+ yield item(day, 0)
+
+ return dict(
+ begin=begin,
+ end=end,
+ data=list(reformat_data(mongo_data)),
+ )
+
class RootRestController(BaseController):
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/aad259f0/ForgeDiscussion/forgediscussion/forum_main.py
----------------------------------------------------------------------
diff --git a/ForgeDiscussion/forgediscussion/forum_main.py b/ForgeDiscussion/forgediscussion/forum_main.py
index 54c4bef..2145037 100644
--- a/ForgeDiscussion/forgediscussion/forum_main.py
+++ b/ForgeDiscussion/forgediscussion/forum_main.py
@@ -172,6 +172,7 @@ class ForgeDiscussionApp(Application):
l.append(SitemapEntry(
'Mark as Spam', 'flag_as_spam',
ui_icon=g.icons['flag'], className='sidebar_thread_spam'))
+ l.append(SitemapEntry('Stats Graph', c.app.url + 'stats', ui_icon=g.icons['stats']))
if forum_links:
l.append(SitemapEntry('Forums'))
l = l + forum_links
@@ -301,4 +302,3 @@ class ForumAdminController(DefaultAdminController):
def add_forum(self, add_forum=None, **kw):
f = utils.create_forum(self.app, add_forum)
redirect(f.url())
-
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/aad259f0/ForgeDiscussion/forgediscussion/templates/discussionforums/stats_graph.html
----------------------------------------------------------------------
diff --git a/ForgeDiscussion/forgediscussion/templates/discussionforums/stats_graph.html b/ForgeDiscussion/forgediscussion/templates/discussionforums/stats_graph.html
new file mode 100644
index 0000000..a6587b3
--- /dev/null
+++ b/ForgeDiscussion/forgediscussion/templates/discussionforums/stats_graph.html
@@ -0,0 +1,79 @@
+{#-
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-#}
+{% extends g.theme.master %}
+
+{% block title %}{{c.project.name}} / {{c.app.config.options.mount_label}} / Stats{% endblock %}
+
+{% block header %}Stats{% endblock %}
+
+{% block content %}
+<form>
+ <div id="stats_date_picker">
+ <label for="dates">Date Range: </label>
+ <input value="{{dates}}" type="text" class="text ui-corner-all" name="dates" id="dates">
+ </div>
+</form>
+
+<div id="stats-viz-container" class="project_stats">
+ <div id="stats-viz" class="ui-corner-left ui-corner-br">
+ <table>
+ <tr>
+ <td class="yaxis">Posts</td>
+ <td>
+ <div id="project_stats_holder">
+ <div id="grid">
+ <div class="busy"></div>
+ </div>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2" class="xaxis">Date</td>
+ </tr>
+ </table>
+ </div>
+</div>
+
+{% endblock %}
+
+{% block extra_css %}
+<link rel="stylesheet" type="text/css" href="{{g.forge_static('css/smoothness/jquery-ui-1.8.4.custom.css')}}"/>
+{% endblock %}
+
+{% block extra_js %}
+<script type="text/javascript" src="{{g.forge_static('js/jquery.flot.js')}}"></script>
+<script type="text/javascript" src="{{g.forge_static('js/jquery.daterangepicker.js')}}"></script>
+<script type="text/javascript" src="{{g.forge_static('js/stats.js')}}"></script>
+<script type="text/javascript">
+ /*global chartProjectStats */
+ $(document).ready(function () {
+ var series = function(data){
+ return [{label: "Posts",
+ lines: {show: true, lineWidth: 3},
+ points: {show:true, radius:2, fill: true, fillColor: '#0685c6'},
+ data: data, shadowSize: 0}
+ ];
+ };
+ var checkEmpty = function(data){
+ return !data.length;
+ };
+ chartProjectStats('{{c.app.url}}stats_data',{},series,checkEmpty);
+ });
+</script>
+{% endblock %}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/aad259f0/ForgeTracker/forgetracker/templates/tracker/stats.html
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/templates/tracker/stats.html b/ForgeTracker/forgetracker/templates/tracker/stats.html
index f4ac5b9..6351cf7 100644
--- a/ForgeTracker/forgetracker/templates/tracker/stats.html
+++ b/ForgeTracker/forgetracker/templates/tracker/stats.html
@@ -91,10 +91,6 @@
return y + " tickets";
};
chartProjectStats('{{c.app.url}}stats_data',{},series,checkEmpty,tooltipFormat);
-
- $('#dates').change(function(){
- $("form.bp").submit();
- });
});
</script>
{% endif %}