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 %}