You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2013/03/28 17:57:30 UTC

[01/42] [5453] adding support for user stats

Updated Branches:
  refs/heads/db/5453 [created] 114ac9c02


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/templates/.svn/text-base/artifacts.html.svn-base
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/.svn/text-base/artifacts.html.svn-base b/ForgeUserStats/forgeuserstats/templates/.svn/text-base/artifacts.html.svn-base
new file mode 100644
index 0000000..0b3cfb8
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/templates/.svn/text-base/artifacts.html.svn-base
@@ -0,0 +1,48 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}User stats{% endblock %}
+
+{% block header %}
+    Statistics about {{user.display_name}}'s contribution – Artifacts
+{% endblock %}
+
+{% block content %}
+
+  {% if user %}
+
+    {% if data %}
+      <h2>Statistics by category</h2>
+      <table>
+        <thead>
+          <tr>
+            <th>Category</th>
+            <th>Created artifacts</th>
+            <th>Modified artifacts</th>
+          </tr>
+        </thead>
+        <tbody> 
+          {% for cat, row in data.items() %}
+            <tr>
+              <td>{% if cat %}{{cat.fullname}}{% else %}All categories{% endif %}</td>
+              <td>
+                {% for details in row %}
+                  {% if details.messagetype %} {{details.messagetype}}:
+                  {% else %}Total:{% endif %} {{details.created}}<br/>
+                {% endfor %}
+              </td>
+              <td>
+                {% for details in row %}
+                  {% if details.messagetype %} {{details.messagetype}}:
+                  {% else %}Total:{% endif %} {{details.modified}}<br/>
+                {% endfor %}
+              </td>
+            </tr>
+          {% endfor %}
+        </tbody>
+      </table>
+    {% endif %}
+    <div class="grid-20"><a href="/userstats/{{user.username}}">Go back to general statistics</a></div>
+  {% endif %}
+
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/templates/.svn/text-base/commits.html.svn-base
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/.svn/text-base/commits.html.svn-base b/ForgeUserStats/forgeuserstats/templates/.svn/text-base/commits.html.svn-base
new file mode 100644
index 0000000..c574c9f
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/templates/.svn/text-base/commits.html.svn-base
@@ -0,0 +1,37 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}User stats{% endblock %}
+
+{% block header %}
+    Statistics about {{user.display_name}}'s contribution – Code contribution
+{% endblock %}
+
+{% block content %}
+
+  {% if user %}
+
+    {% if data %}
+      <h2>Statistics by category</h2>
+      <table>
+        <thead>
+          <tr>
+            <th>Category</th>
+            <th>Number of commits</th>
+            <th>Lines of code</th>
+          </tr>
+        </thead>
+        <tbody> 
+          {% for cat, el in data.items() %}
+            <tr>
+              <td>{% if cat %}{{cat.fullname}}{% else %}All categories{% endif %}</td>
+              <td>{{el.number}}</td>
+              <td>{{el.lines}}</td>
+            {% endfor %}
+          </tr>
+        </tbody>
+      </table>
+    {% endif %}
+    <div class="grid-20"><a href="/userstats/{{user.username}}">Go back to general statistics</a></div>
+  {% endif %}
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/templates/.svn/text-base/index.html.svn-base
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/.svn/text-base/index.html.svn-base b/ForgeUserStats/forgeuserstats/templates/.svn/text-base/index.html.svn-base
new file mode 100644
index 0000000..b53e596
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/templates/.svn/text-base/index.html.svn-base
@@ -0,0 +1,341 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}User stats{% endblock %}
+
+{% block header %}
+    Statistics about {{user.display_name}}'s contribution
+    {% if category %}
+        in projects of category {{category.fullname}}
+    {% endif %}
+{% endblock %}
+
+{% block content %}
+  {% if user %}
+
+    <h2>General statistics</h2>
+    <table>
+      <thead>
+        <tr>
+          <th>Parameter</th>
+          <th>Value</th>
+        </tr>
+      </thead>
+      <tbody> 
+        <tr>
+          <td>Registration date</td>
+          <td>
+            {{registration_date.strftime("%d %b %Y, %H:%M:%S (UTC)")}}, 
+            {{days}} day{% if days != 1 %}s{% endif %} ago</td>
+        </tr>
+        {% if last_login %}
+          <tr>
+            <td>Last login</td>
+            <td>
+              {{last_login.strftime("%d %b %Y, %H:%M:%S (UTC)")}},
+              {{last_login_days}} day{% if last_login_days != 1 %}s{% endif %} ago</td>
+            </td>
+          </tr>
+        {% endif %}
+      </tbody>
+    </table>
+
+    <h2>Contribution statistics</h2>
+
+    <table>
+      <thead>
+        <tr>
+          <th>Parameter</th>
+          <th>Total value</th>
+          <th>Average per-month value</th>
+          <th>Last 30 days</th>
+          {% if days >= 30 %}
+            <th>Trend</th>
+          {% endif %}
+        </tr>
+      </thead>
+      <tbody>
+        {% if not category %}
+          <tr>
+            <td>Logins</td>
+            <td>{{totlogins}}</td>
+            <td>{{permonthlogins}}</td>
+            <td>{{lastmonth_logins}}</td>
+            {% if days >= 30 %}
+              <td>
+                {% if lastmonth_logins > permonthlogins %}
+                  Up
+                {% elif lastmonth_logins == permonthlogins %}
+                  =
+                {% else %}
+                  Down
+                {%endif%}
+              </td>
+            {% endif %}
+          </tr>
+        {% endif %}
+        <tr>
+          <td><a href="/userstats/{{user.username}}/metric/commits/">Commits number</a></td>
+          <td>{{totcommits.number}}</td>
+          <td>{{permonthcommits.number}}</td>
+          <td>{{lastmonthcommits.number}}</td>
+          {% if days >= 30 %}
+            <td>
+              {% if permonthcommits.number > permonthcommits.number %}
+                Up
+              {% elif permonthcommits.number == permonthcommits.number %}
+                =
+              {% else %}
+                Down
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+        <tr>
+          <td><a href="/userstats/{{user.username}}/metric/commits/">Added/modified LOCs</a></td>
+          <td>{{totcommits.lines}}</td>
+          <td>{{permonthcommits.lines}}</td>
+          <td>{{lastmonthcommits.lines}}</td>
+          {% if days >= 30 %}
+            <td>
+              {% if permonthcommits.lines > permonthcommits.lines %}
+                Up
+              {% elif permonthcommits.lines == permonthcommits.lines %}
+                =
+              {% else %}
+                Down
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+        <tr>
+          <td><a href="/userstats/{{user.username}}/metric/artifacts/">Total number of created artifacts</a></td>
+          <td>{{totartifacts.created}}</td>
+          <td>{{permonthartifacts.created}}</td>
+          <td>{{lastmonthartifacts.created}}</td>
+          {% if days >= 30 %}
+            <td>
+              {% if lastmonthartifacts.created > permonthartifacts.created %}
+                Up
+              {% elif lastmonthartifacts.created == permonthartifacts.created %}
+                =
+              {% else %}
+                Down
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+        <tr>
+          <td><a href="/userstats/{{user.username}}/metric/commits/">Total number of edited artifacts</a></td>
+          <td>{{totartifacts.modified}}</td>
+          <td>{{permonthartifacts.modified}}</td>
+          <td>{{lastmonthartifacts.modified}}</td>
+          {% if days >= 30 %}
+            <td>
+              {% if lastmonthartifacts.modified > permonthartifacts.modified %}
+                Up
+              {% elif lastmonthartifacts.modified == permonthartifacts.modified %}
+                =
+              {% else %}
+                Down
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+        
+        {% for key, value in artifacts_by_type.items() %}
+          <tr>
+            <td><a href="/userstats/{{user.username}}/metric/artifacts/">Created {{key}} artifacts</a></td>
+            <td>{{value.created}}</td>
+            <td>{{value.pmcreated}}</td>
+            <td>
+              {% if lastmonth_artifacts_by_type.get(key) %}
+                 {{lastmonth_artifacts_by_type[key].created}}
+              {% else %}
+                 0
+              {% endif %}
+            </td>
+            {% if days >= 30 %}
+              <td>
+                {% if lastmonth_artifacts_by_type.get(key) %}
+                  {% if lastmonth_artifacts_by_type[key].created > value.pmcreated %}
+                    Up
+                  {% elif lastmonth_artifacts_by_type[key].created == value.pmcreated %}
+                    =
+                  {% else %}
+                    Down
+                  {%endif%}
+                {%else%} Down {%endif%}
+              </td>
+            {% endif %}
+          </tr>
+          <tr>
+            <td><a href="/userstats/{{user.username}}/metric/artifacts/">Edited {{key}} artifacts</a></td>
+            <td>{{value.modified}}</td>
+            <td>{{value.pmmodified}}</td>
+            <td>
+              {% if lastmonth_artifacts_by_type.get(key) %}
+                 {{lastmonth_artifacts_by_type[key].modified}}
+              {% else %}
+                 0
+              {% endif %}
+            </td>
+            {% if days >= 30 %}
+              <td>
+                {% if lastmonth_artifacts_by_type.get(key) %}
+                  {% if lastmonth_artifacts_by_type[key].modified > value.pmmodified %}
+                    Up
+                  {% elif lastmonth_artifacts_by_type[key].modified == value.pmmodified %}
+                    =
+                  {% else %}
+                    Down
+                  {%endif%}
+                {%else%} Down {%endif%}
+              </td>
+            {% endif %}
+          </tr>
+        {% endfor %}
+
+        <tr>
+          <td><a href="/userstats/{{user.username}}/metric/tickets/">Assigned tickets</a></td>
+          <td>{{tottickets.assigned}}</td>
+          <td>{{permonthtickets.assigned}}</td>
+          <td>{{lastmonthtickets.assigned}}</td>
+          {% if days >= 30 %}
+            <td>
+              {% if lastmonthtickets.assigned > permonthtickets.assigned %}
+                Up
+              {% elif lastmonthtickets.assigned == permonthtickets.assigned %}
+                =
+              {% else %}
+                Down
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+        <tr>
+          <td><a href="/userstats/{{user.username}}/metric/tickets/">Revoked tickets</a></td>
+          <td>{{tottickets.revoked}}</td>
+          <td>{{permonthtickets.revoked}}</td>
+          <td>{{lastmonthtickets.revoked}}</td>
+          {% if days >= 30 %}
+            <td>
+              {% if lastmonthtickets.revoked > permonthtickets.revoked %}
+                Up
+              {% elif lastmonthtickets.revoked == permonthtickets.revoked %}
+                =
+              {% else %}
+                Down
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+        <tr>
+          <td><a href="/userstats/{{user.username}}/metric/tickets/">Solved tickets</a></td>
+          <td>{{tottickets.solved}}</td>
+          <td>{{permonthtickets.solved}}</td>
+          <td>{{lastmonthtickets.solved}}</td>
+          {% if days >= 30 %}
+            <td>
+              {% if lastmonthtickets.solved > permonthtickets.solved %}
+                Up
+              {% elif lastmonthtickets.solved == permonthtickets.solved %}
+                =
+              {% else %}
+                Down
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+        <tr>
+          <td><a href="/userstats/{{user.username}}/metric/tickets/">Average tickets solving time</a></td>
+          <td>
+            {% if tottickets.averagesolvingtime %}
+              {{tottickets.averagesolvingtime.days}} days, 
+              {{tottickets.averagesolvingtime.hours}} hours,
+              {{tottickets.averagesolvingtime.minutes}} min
+            {% else %}n/a{% endif %}
+          </td>
+          <td>n/a</td>
+          <td>
+            {% if lastmonthtickets.averagesolvingtime %}
+              {{lastmonthtickets.averagesolvingtime.days}} days, 
+              {{lastmonthtickets.averagesolvingtime.hours}} hours,
+              {{lastmonthtickets.averagesolvingtime.minutes}} min
+            {% else %}n/a{% endif %}
+          </td>
+          {% if days >= 30 %}
+            <td>
+              {% if lastmonthtickets.averagesolvingtime > tottickets.averagesolvingtime %}
+                Up
+              {% elif lastmonthtickets.averagesolvingtime == tottickets.averagesolvingtime %}
+                =
+              {% else %}
+                Down
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+      </tbody>
+    </table>
+
+    {% if categories %}
+        <h2>Prefered categories</h2>
+        <table>
+          <thead>
+            <tr>
+              <th>Category name</th>
+              <th>Number of projects</th>
+            </tr>
+          </thead>
+          <tbody>
+            {% for cat, count in categories %}
+              <tr>
+                <td><a href="/userstats/{{user.username}}/category/{{cat.fullname}}">{{cat.fullname}}</a></td>
+                <td>{{count}}</td>
+              </tr>
+            {% endfor %}
+          </tbody>
+        </table>
+    {% endif %}
+    {% if category %}
+        <div class="grid-20"><a href="/userstats/{{user.username}}">Go back to general statistics</a></div>
+    {% else %}
+      <h2>Overall evaluation</h2>
+      <table>
+        <thead>
+          <tr>
+            <th>Field</th>
+            <th>Evaluation</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td>Code contribution</td>
+            <td>
+               {% for i in range(codestars) %}★{% endfor %}
+               {% for i in range(5 - codestars) %}☆{% endfor %}
+            </td>
+          </tr>
+          <tr>
+            <td>Contribution to discussions on the forge</td>
+            <td>
+               {% for i in range(discussionstars) %}★{% endfor %}
+               {% for i in range(5 - discussionstars) %}☆{% endfor %}
+            </td>
+          </tr>
+          <tr>
+            <td>Contribution to issues solving</td>
+            <td>
+              {% for i in range(ticketsstars) %}★{% endfor %}
+              {% for i in range(5 - ticketsstars) %}☆{% endfor %}
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    {% endif %}
+  {% else %}
+    Invalid user!
+  {% endif %}
+
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/templates/.svn/text-base/tickets.html.svn-base
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/.svn/text-base/tickets.html.svn-base b/ForgeUserStats/forgeuserstats/templates/.svn/text-base/tickets.html.svn-base
new file mode 100644
index 0000000..148cfa8
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/templates/.svn/text-base/tickets.html.svn-base
@@ -0,0 +1,47 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}User stats{% endblock %}
+
+{% block header %}
+    Statistics about {{user.display_name}}'s contribution – Tickets
+{% endblock %}
+
+{% block content %}
+
+  {% if user %}
+
+    {% if data %}
+      <h2>Statistics by category</h2>
+      <table>
+        <thead>
+          <tr>
+            <th>Category</th>
+            <th>Assigned tickets</th>
+            <th>Solved tickets</th>
+            <th>Revoked tickets</th>
+            <th>Average solving time</th>
+          </tr>
+        </thead>
+        <tbody> 
+          {% for cat, el in data.items() %}
+            <tr>
+              <td>{% if cat %}{{cat.fullname}}{% else %}All categories{% endif %}</td>
+              <td>{{el.assigned}}</td>
+              <td>{{el.solved}}</td>
+              <td>{{el.revoked}}</td>
+              <td>
+                {% if el.averagesolvingtime %}
+                  {{el.averagesolvingtime.days}} days, 
+                  {{el.averagesolvingtime.hours}} hours,
+                  {{el.averagesolvingtime.minutes}} min
+                {% else %}n/a{% endif %}
+              </td>
+            {% endfor %}
+          </tr>
+        </tbody>
+      </table>
+    {% endif %}
+    <div class="grid-20"><a href="/userstats/{{user.username}}">Go back to general statistics</a></div>
+  {% endif %}
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/templates/artifacts.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/artifacts.html b/ForgeUserStats/forgeuserstats/templates/artifacts.html
new file mode 100644
index 0000000..9492628
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/templates/artifacts.html
@@ -0,0 +1,52 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}User stats{% endblock %}
+
+{% block header %}
+    Statistics about {{user.display_name}}'s contribution – Artifacts
+{% endblock %}
+
+{% block content %}
+
+  {% if user %}
+    <div class="grid-20">
+      <ul><li><a href="/userstats/{{user.username}}">Go back to general statistics</a></li></ul>
+    </div>
+
+    {% if data %}
+    <div class="grid-20">
+      <h2>Statistics by category</h2>
+      <table>
+        <thead>
+          <tr>
+            <th>Category</th>
+            <th>Created artifacts</th>
+            <th>Modified artifacts</th>
+          </tr>
+        </thead>
+        <tbody> 
+          {% for cat, row in data.items() %}
+            <tr>
+              <td>{% if cat %}{{cat.fullname}}{% else %}All categories{% endif %}</td>
+              <td>
+                {% for details in row %}
+                  {% if details.messagetype %} {{details.messagetype}}:
+                  {% else %}Total:{% endif %} {{details.created}}<br/>
+                {% endfor %}
+              </td>
+              <td>
+                {% for details in row %}
+                  {% if details.messagetype %} {{details.messagetype}}:
+                  {% else %}Total:{% endif %} {{details.modified}}<br/>
+                {% endfor %}
+              </td>
+            </tr>
+          {% endfor %}
+        </tbody>
+      </table>
+    </div>
+    {% endif %}
+  {% endif %}
+
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/templates/commits.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/commits.html b/ForgeUserStats/forgeuserstats/templates/commits.html
new file mode 100644
index 0000000..2bca003
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/templates/commits.html
@@ -0,0 +1,42 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}User stats{% endblock %}
+
+{% block header %}
+    Statistics about {{user.display_name}}'s contribution – Code contribution
+{% endblock %}
+
+{% block content %}
+
+  {% if user %}
+    <div class="grid-20">
+      <ul><li><a href="/userstats/{{user.username}}">Go back to general statistics</a></li></ul>
+    </div>
+
+    {% if data %}
+    <div class="grid-20">
+      <h2>Statistics by category</h2>
+      <table>
+        <thead>
+          <tr>
+            <th>Category</th>
+            <th>Number of commits</th>
+            <th>Lines of code</th>
+          </tr>
+        </thead>
+        <tbody> 
+          {% for cat, el in data.items() %}
+            <tr>
+              <td>{% if cat %}{{cat.fullname}}{% else %}All categories{% endif %}</td>
+              <td>{{el.number}}</td>
+              <td>{{el.lines}}</td>
+            {% endfor %}
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    {% endif %}
+    <div class="grid-20"><a href="/userstats/{{user.username}}">Go back to general statistics</a></div>
+  {% endif %}
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/templates/index.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/index.html b/ForgeUserStats/forgeuserstats/templates/index.html
new file mode 100644
index 0000000..502cb25
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/templates/index.html
@@ -0,0 +1,423 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}User stats{% endblock %}
+
+{% block header %}
+    Statistics about {{user.display_name}}'s contribution
+    {% if category %}
+        in projects of category {{category.fullname}}
+    {% endif %}
+{% endblock %}
+
+{% block content %}
+  {% if user %}
+
+    <h2>General statistics</h2>
+    <table>
+      <thead>
+        <tr>
+          <th>Parameter</th>
+          <th>Date</th>
+          <th>Time interval</th>
+        </tr>
+      </thead>
+      <tbody> 
+        <tr>
+          <td>Registration date</td>
+          <td>{{registration_date.strftime("%d %b %Y, %H:%M:%S (UTC)")}}</td> 
+           <td>{{days}} day{% if days != 1 %}s{% endif %} ago</td>
+        </tr>
+        {% if last_login %}
+          <tr>
+            <td>Last login</td>
+            <td>{{last_login.strftime("%d %b %Y, %H:%M:%S (UTC)")}}</td>
+            <td>{{last_login_days}} day{% if last_login_days != 1 %}s{% endif %} ago</td>
+          </tr>
+        {% endif %}
+      </tbody>
+    </table>
+
+    <h2>Contribution statistics</h2>
+
+    <table>
+      <thead>
+        <tr>
+          <th>Parameter</th>
+          <th>Total value</th>
+          <th>Average per-month value</th>
+          <th>Last 30 days</th>
+          {% if days >= 30 %}
+            <th>Trend</th>
+          {% endif %}
+        </tr>
+      </thead>
+      <tbody>
+        {% if not category %}
+          <tr>
+            <td>Logins</td>
+            <td>{{totlogins}}</td>
+            <td>{{permonthlogins}}</td>
+            <td>{{lastmonth_logins}}</td>
+            {% if days >= 30 %}
+              <td style="text-align:center;">
+                {% if lastmonth_logins > permonthlogins %}
+                  <img src="{{g.forge_static('images/up.png')}}"/>
+                {% elif lastmonth_logins == permonthlogins %}
+                  <img src="{{g.forge_static('images/equal.png')}}"/>
+                {% else %}
+                  <img src="{{g.forge_static('images/down.png')}}"/>
+                {%endif%}
+              </td>
+            {% endif %}
+          </tr>
+        {% endif %}
+        <tr>
+          <td>
+            {% if totcommits.number > 0 %}
+              <a href="/userstats/{{user.username}}/metric/commits/">Commits number</a>
+            {% else %}
+              Commits number
+            {% endif %}
+          </td>
+          <td>{{totcommits.number}}</td>
+          <td>{{permonthcommits.number}}</td>
+          <td>{{lastmonthcommits.number}}</td>
+          {% if days >= 30 %}
+            <td style="text-align:center;">
+              {% if permonthcommits.number > permonthcommits.number %}
+                  <img src="{{g.forge_static('images/up.png')}}"/>
+              {% elif permonthcommits.number == permonthcommits.number %}
+                <img src="{{g.forge_static('images/equal.png')}}"/>
+              {% else %}
+                <img src="{{g.forge_static('images/down.png')}}"/>
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+        <tr>
+          <td>
+            {% if totcommits.lines > 0 %}
+              <a href="/userstats/{{user.username}}/metric/commits/">Added/modified LOCs</a>
+            {% else %}
+              Added/modified LOCs
+            {% endif %}
+          </td>
+          <td>{{totcommits.lines}}</td>
+          <td>{{permonthcommits.lines}}</td>
+          <td>{{lastmonthcommits.lines}}</td>
+          {% if days >= 30 %}
+            <td style="text-align:center;">
+              {% if permonthcommits.lines > permonthcommits.lines %}
+                  <img src="{{g.forge_static('images/up.png')}}"/>
+              {% elif permonthcommits.lines == permonthcommits.lines %}
+                <img src="{{g.forge_static('images/equal.png')}}"/>
+              {% else %}
+                <img src="{{g.forge_static('images/down.png')}}"/>
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+        <tr>
+          <td>
+            {% if totartifacts.created > 0 %}
+              <a href="/userstats/{{user.username}}/metric/artifacts/">Total number of created artifacts</a>
+            {% else %}
+              Total number of created artifacts
+            {% endif %}
+          </td>
+          <td>{{totartifacts.created}}</td>
+          <td>{{permonthartifacts.created}}</td>
+          <td>{{lastmonthartifacts.created}}</td>
+          {% if days >= 30 %}
+            <td style="text-align:center;">
+              {% if lastmonthartifacts.created > permonthartifacts.created %}
+                <img src="{{g.forge_static('images/up.png')}}"/>
+              {% elif lastmonthartifacts.created == permonthartifacts.created %}
+                <img src="{{g.forge_static('images/equal.png')}}"/>
+              {% else %}
+                <img src="{{g.forge_static('images/down.png')}}"/>
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+        <tr>
+          <td>
+            {% if totartifacts.modified > 0 %}
+              <a href="/userstats/{{user.username}}/metric/artifacts/">Total number of edited artifacts</a>
+            {% else %}
+              Total number of edited artifacts
+            {% endif %}
+          </td>
+          <td>{{totartifacts.modified}}</td>
+          <td>{{permonthartifacts.modified}}</td>
+          <td>{{lastmonthartifacts.modified}}</td>
+          {% if days >= 30 %}
+            <td style="text-align:center;">
+              {% if lastmonthartifacts.modified > permonthartifacts.modified %}
+                <img src="{{g.forge_static('images/up.png')}}"/>
+              {% elif lastmonthartifacts.modified == permonthartifacts.modified %}
+                <img src="{{g.forge_static('images/equal.png')}}"/>
+              {% else %}
+                <img src="{{g.forge_static('images/down.png')}}"/>
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+        
+        {% for key, value in artifacts_by_type.items() %}
+          <tr>
+            <td>
+              {% if value.created > 0 %}
+                <a href="/userstats/{{user.username}}/metric/artifacts/">Created {{key}} artifacts</a>
+              {% else %}
+                Created {{key}} artifacts
+              {% endif %}
+            </td>
+            <td>{{value.created}}</td>
+            <td>{{value.pmcreated}}</td>
+            <td>
+              {% if lastmonth_artifacts_by_type.get(key) %}
+                 {{lastmonth_artifacts_by_type[key].created}}
+              {% else %}
+                 0
+              {% endif %}
+            </td>
+            {% if days >= 30 %}
+              <td style="text-align:center;">
+                {% if lastmonth_artifacts_by_type.get(key) %}
+                  {% if lastmonth_artifacts_by_type[key].created > value.pmcreated %}
+                    <img src="{{g.forge_static('images/up.png')}}"/>
+                  {% elif lastmonth_artifacts_by_type[key].created == value.pmcreated %}
+                    <img src="{{g.forge_static('images/equal.png')}}"/>
+                  {% else %}
+                    <img src="{{g.forge_static('images/down.png')}}"/>
+                  {%endif%}
+                {%else%} Down {%endif%}
+              </td>
+            {% endif %}
+          </tr>
+          <tr>
+            <td>
+              {% if value.modified > 0 %}
+                <a href="/userstats/{{user.username}}/metric/artifacts/">Edited {{key}} artifacts</a>
+              {% else %}
+                Edited {{key}} artifacts
+              {% endif %}
+            </td>
+            <td>{{value.modified}}</td>
+            <td>{{value.pmmodified}}</td>
+            <td>
+              {% if lastmonth_artifacts_by_type.get(key) %}
+                 {{lastmonth_artifacts_by_type[key].modified}}
+              {% else %}
+                 0
+              {% endif %}
+            </td>
+            {% if days >= 30 %}
+              <td style="text-align:center;">
+                {% if lastmonth_artifacts_by_type.get(key) %}
+                  {% if lastmonth_artifacts_by_type[key].modified > value.pmmodified %}
+                    <img src="{{g.forge_static('images/up.png')}}"/>
+                  {% elif lastmonth_artifacts_by_type[key].modified == value.pmmodified %}
+                    <img src="{{g.forge_static('images/equal.png')}}"/>
+                  {% else %}
+                    <img src="{{g.forge_static('images/down.png')}}"/>
+                  {%endif%}
+                {%else%} Down {%endif%}
+              </td>
+            {% endif %}
+          </tr>
+        {% endfor %}
+
+        <tr>
+          <td>
+            {% if tottickets.assigned > 0 %}
+              <a href="/userstats/{{user.username}}/metric/tickets/">Assigned tickets</a>
+            {% else %}
+              Assigned tickets
+            {% endif %}
+          </td>
+          <td>{{tottickets.assigned}}</td>
+          <td>{{permonthtickets.assigned}}</td>
+          <td>{{lastmonthtickets.assigned}}</td>
+          {% if days >= 30 %}
+            <td style="text-align:center;">
+              {% if lastmonthtickets.assigned > permonthtickets.assigned %}
+                <img src="{{g.forge_static('images/up.png')}}"/>
+              {% elif lastmonthtickets.assigned == permonthtickets.assigned %}
+                <img src="{{g.forge_static('images/equal.png')}}"/>
+              {% else %}
+                <img src="{{g.forge_static('images/down.png')}}"/>
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+        <tr>
+          <td>
+            {% if tottickets.revoked > 0 %}
+              <a href="/userstats/{{user.username}}/metric/tickets/">Revoked tickets</a>
+            {% else %}
+              Revoked tickets
+            {% endif %}
+          </td>
+          <td>{{tottickets.revoked}}</td>
+          <td>{{permonthtickets.revoked}}</td>
+          <td>{{lastmonthtickets.revoked}}</td>
+          {% if days >= 30 %}
+            <td style="text-align:center;">
+              {% if lastmonthtickets.revoked > permonthtickets.revoked %}
+                <img src="{{g.forge_static('images/up.png')}}"/>
+              {% elif lastmonthtickets.revoked == permonthtickets.revoked %}
+                <img src="{{g.forge_static('images/equal.png')}}"/>
+              {% else %}
+                <img src="{{g.forge_static('images/down.png')}}"/>
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+        <tr>
+          <td>
+            {% if tottickets.solved > 0 %}
+              <a href="/userstats/{{user.username}}/metric/tickets/">Solved tickets</a>
+            {% else %}
+              Solved tickets
+            {% endif %}
+          </td>
+          <td>{{tottickets.solved}}</td>
+          <td>{{permonthtickets.solved}}</td>
+          <td>{{lastmonthtickets.solved}}</td>
+          {% if days >= 30 %}
+            <td style="text-align:center;">
+              {% if lastmonthtickets.solved > permonthtickets.solved %}
+                <img src="{{g.forge_static('images/up.png')}}"/>
+              {% elif lastmonthtickets.solved == permonthtickets.solved %}
+                <img src="{{g.forge_static('images/equal.png')}}"/>
+              {% else %}
+                <img src="{{g.forge_static('images/down.png')}}"/>
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+        <tr>
+          <td>
+            {% if tottickets.averagesolvingtime > 0 %}
+              <a href="/userstats/{{user.username}}/metric/tickets/">Average tickets solving time</a>
+            {% else %}
+              Average tickets solving time
+            {% endif %}
+          </td>
+          <td>
+            {% if tottickets.averagesolvingtime %}
+              {{tottickets.averagesolvingtime.days}} days, 
+              {{tottickets.averagesolvingtime.hours}} hours,
+              {{tottickets.averagesolvingtime.minutes}} min
+            {% else %}n/a{% endif %}
+          </td>
+          <td>n/a</td>
+          <td>
+            {% if lastmonthtickets.averagesolvingtime %}
+              {{lastmonthtickets.averagesolvingtime.days}} days, 
+              {{lastmonthtickets.averagesolvingtime.hours}} hours,
+              {{lastmonthtickets.averagesolvingtime.minutes}} min
+            {% else %}n/a{% endif %}
+          </td>
+          {% if days >= 30 %}
+            <td style="text-align:center;">
+              {% if lastmonthtickets.averagesolvingtime > tottickets.averagesolvingtime %}
+                <img src="{{g.forge_static('images/up.png')}}"/>
+              {% elif lastmonthtickets.averagesolvingtime == tottickets.averagesolvingtime %}
+                <img src="{{g.forge_static('images/equal.png')}}"/>
+              {% else %}
+                <img src="{{g.forge_static('images/down.png')}}"/>
+              {%endif%}
+            </td>
+          {% endif %}
+        </tr>
+      </tbody>
+    </table>
+
+    {% if categories %}
+        <h2>Prefered categories</h2>
+        <p>
+          The following table shows the number projects tagged as belonging to each single category in which this user is involved.
+        </p>
+        <table>
+          <thead>
+            <tr>
+              <th>Category name</th>
+              <th>Number of projects</th>
+            </tr>
+          </thead>
+          <tbody>
+            {% for cat, count in categories %}
+              <tr>
+                <td><a href="/userstats/{{user.username}}/category/{{cat.fullname}}">{{cat.fullname}}</a></td>
+                <td>{{count}}</td>
+              </tr>
+            {% endfor %}
+          </tbody>
+        </table>
+         
+        {% if categories|length > 1 %}
+          <p>
+            The same data listed in the previous table is graphically presented by the following histogram.
+          </p>
+          <p>
+            <img src="/userstats/{{user.username}}/categories_graph"/>
+          </p>
+        {% else %}
+          The following table shows the number projects tagged as belonging to each single category in which this user is involved.
+        {% endif %}
+    {% endif %}
+    {% if category %}
+        <div class="grid-20"><a href="/userstats/{{user.username}}">Go back to general statistics</a></div>
+    {% else %}
+      <h2>Overview</h2>
+      <table>
+        <thead>
+          <tr>
+            <th>Field</th>
+            <th>Value</th>
+            <th>Average per-user value</th>
+            <th>Maximum per-user value</th>
+            <th>Rank bar</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td>Code</td>
+            <td>{{codecontribution}} LOC{% if codecontribution != 1 %}s{% endif %}/month</td>
+            <td>{{averagecodecontrib}} LOC{% if averagecodecontrib != 1 %}s{% endif %}/month</td>
+            <td>{{maxcodecontrib}} LOC{% if maxcodecontrib != 1 %}s{% endif %}/month</td>
+            <td><img src="/userstats/{{user.username}}/code_ranking_bar"/> {{codepercentage}} %</td>
+          </tr>
+          <tr>
+            <td>Discussion</td>
+            <td>{{discussioncontribution}} contr./month</td>
+            <td>{{averagedisccontrib}} contr./month</td>
+            <td>{{maxdisccontrib}} contr./month</td>
+            <td><img src="/userstats/{{user.username}}/discussion_ranking_bar"/> {{discussionpercentage}} %</td>
+          </tr>
+          <tr>
+            <td>Solved issues</td>
+            <td>{{ticketcontribution}} %</td>
+            <td>{{averageticketcontrib}} %</td>
+            <td>{{maxticketcontrib}} %</td>
+            <td><img src="/userstats/{{user.username}}/tickets_ranking_bar"/> {{ticketspercentage}} %</td>
+          </tr>
+        </tbody>
+      </table>
+      <h3>Note</h3>
+      <p>
+         The above table compares the average monthly contribution of this user with the average monthly contributions of the
+         other users of the forge. The progressbar and the percentage refer to the user's position in an overall ranking of the
+         users of this forge. For example, a value of 100% in the field "Code" is associated to the user who has the highest
+         average number of committed LOCs per month. Of course, this doesn't consider the quality of the contributions.
+      </p>
+    {% endif %}
+  {% else %}
+    Invalid user!
+  {% endif %}
+
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/templates/tickets.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/tickets.html b/ForgeUserStats/forgeuserstats/templates/tickets.html
new file mode 100644
index 0000000..9bf411b
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/templates/tickets.html
@@ -0,0 +1,52 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}User stats{% endblock %}
+
+{% block header %}
+    Statistics about {{user.display_name}}'s contribution – Tickets
+{% endblock %}
+
+{% block content %}
+
+  {% if user %}
+    <div class="grid-20">
+      <ul><li><a href="/userstats/{{user.username}}">Go back to general statistics</a></li></ul>
+    </div>
+
+    {% if data %}
+    <div class="grid-20">
+      <h2>Statistics by category</h2>
+      <table>
+        <thead>
+          <tr>
+            <th>Category</th>
+            <th>Assigned tickets</th>
+            <th>Solved tickets</th>
+            <th>Revoked tickets</th>
+            <th>Average solving time</th>
+          </tr>
+        </thead>
+        <tbody> 
+          {% for cat, el in data.items() %}
+            <tr>
+              <td>{% if cat %}{{cat.fullname}}{% else %}All categories{% endif %}</td>
+              <td>{{el.assigned}}</td>
+              <td>{{el.solved}}</td>
+              <td>{{el.revoked}}</td>
+              <td>
+                {% if el.averagesolvingtime %}
+                  {{el.averagesolvingtime.days}} days, 
+                  {{el.averagesolvingtime.hours}} hours,
+                  {{el.averagesolvingtime.minutes}} min
+                {% else %}n/a{% endif %}
+              </td>
+            {% endfor %}
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    {% endif %}
+    <div class="grid-20"><a href="/userstats/{{user.username}}">Go back to general statistics</a></div>
+  {% endif %}
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/version.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/version.py b/ForgeUserStats/forgeuserstats/version.py
new file mode 100644
index 0000000..6514373
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/version.py
@@ -0,0 +1,2 @@
+__version_info__ = (0, 0)
+__version__ = '.'.join(map(str, __version_info__))

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/setup.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/setup.py b/ForgeUserStats/setup.py
new file mode 100644
index 0000000..dc2f07b
--- /dev/null
+++ b/ForgeUserStats/setup.py
@@ -0,0 +1,29 @@
+from setuptools import setup, find_packages
+import sys, os
+
+from forgeuserstats.version import __version__
+
+setup(name='ForgeUserStats',
+      version=__version__,
+      description="",
+      long_description="""\
+""",
+      classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+      keywords='',
+      author='',
+      author_email='',
+      url='',
+      license='',
+      packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
+      include_package_data=True,
+      zip_safe=False,
+      install_requires=[
+          # -*- Extra requirements: -*-
+          'allura',
+      ],
+      entry_points="""
+      # -*- Entry points: -*-
+      [allura.stats]
+      userstats=forgeuserstats.main:ForgeUserStatsApp
+      """,
+      )

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/requirements-common.txt
----------------------------------------------------------------------
diff --git a/requirements-common.txt b/requirements-common.txt
index 03d76f4..5656d40 100644
--- a/requirements-common.txt
+++ b/requirements-common.txt
@@ -47,6 +47,8 @@ TurboGears2==2.1.5
 WebOb==1.0.8
 # part of the stdlib, but with a version number.  see http://guide.python-distribute.org/pip.html#listing-installed-packages
 wsgiref==0.1.2
+numpy==1.6.1
+matplotlib==1.1.1rc
 
 # tg2 deps (not used directly)
 Babel==0.9.6


[03/42] git commit: [5453] Improved tests

Posted by br...@apache.org.
[5453] Improved tests


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/60b11feb
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/60b11feb
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/60b11feb

Branch: refs/heads/db/5453
Commit: 60b11feb08e6d0f8f40db125dee21655fd407c63
Parents: bb12ad3
Author: Stefano Invernizzi <st...@apache.org>
Authored: Mon Jan 14 14:43:56 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:30 2013 +0000

----------------------------------------------------------------------
 ForgeUserStats/forgeuserstats/tests/test_stats.py |   60 ++++++---------
 ForgeUserStats/test.ini                           |    1 +
 2 files changed, 25 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/60b11feb/ForgeUserStats/forgeuserstats/tests/test_stats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/test_stats.py b/ForgeUserStats/forgeuserstats/tests/test_stats.py
index 2972a0e..a3e3482 100644
--- a/ForgeUserStats/forgeuserstats/tests/test_stats.py
+++ b/ForgeUserStats/forgeuserstats/tests/test_stats.py
@@ -16,25 +16,16 @@ from forgetracker import model as TM
 
 class TestStats(TestController):
 
-    test_username = 'teststats'
-    test_password = 'foo'
-
-    def setUp(self):
-        super(TestStats, self).setUp()
-        for ep in pkg_resources.iter_entry_points("allura.stats"):
-            if ep.name.lower() == 'userstats':
-                g.statslisteners = [ep.load()().listener]
-
-        self.user = User.register(dict(username=self.test_username,
+    @td.with_user_project('test-user')
+    def test_init_values(self):
+        user = User.register(dict(username='test-new-user',
             display_name='Test Stats'),
             make_project=False)
-        self.user.set_password(self.test_password)
-        
-    def test_init_values(self):
-        artifacts = self.user.stats.getArtifacts()
-        tickets = self.user.stats.getTickets()
-        commits = self.user.stats.getCommits()
-        assert self.user.stats.tot_logins_count == 0
+
+        artifacts = user.stats.getArtifacts()
+        tickets = user.stats.getTickets()
+        commits = user.stats.getCommits()
+        assert user.stats.tot_logins_count == 0
         assert artifacts['created'] == 0
         assert artifacts['modified'] == 0
         assert tickets['assigned'] == 0
@@ -44,10 +35,10 @@ class TestStats(TestController):
         assert commits['number'] == 0
         assert commits['lines'] == 0
 
-        lmartifacts = self.user.stats.getLastMonthArtifacts()
-        lmtickets = self.user.stats.getLastMonthTickets()
-        lmcommits = self.user.stats.getLastMonthCommits()
-        assert self.user.stats.getLastMonthLogins() == 0
+        lmartifacts = user.stats.getLastMonthArtifacts()
+        lmtickets = user.stats.getLastMonthTickets()
+        lmcommits = user.stats.getLastMonthCommits()
+        assert user.stats.getLastMonthLogins() == 0
         assert lmartifacts['created'] == 0
         assert lmartifacts['modified'] == 0
         assert lmtickets['assigned'] == 0
@@ -58,12 +49,13 @@ class TestStats(TestController):
         assert lmcommits['lines'] == 0
 
     def test_login(self):
-        init_logins = self.user.stats.tot_logins_count
+        user = User.by_username('test-user')
+        init_logins = c.user.stats.tot_logins_count
         r = self.app.post('/auth/do_login', params=dict(
-                username=self.test_username, password=self.test_password))
+                username=user.username, password='foo'))
 
-        assert self.user.stats.tot_logins_count == 1 + init_logins
-        assert self.user.stats.getLastMonthLogins() == 1 + init_logins
+        assert user.stats.tot_logins_count == 1 + init_logins
+        assert user.stats.getLastMonthLogins() == 1 + init_logins
 
     @td.with_user_project('test-admin')
     @td.with_wiki
@@ -163,7 +155,7 @@ class TestStats(TestController):
         assert tickets_artifacts['created'] == initial_tickets_artifacts['created'] + 2
         assert tickets_artifacts['modified'] == initial_tickets_artifacts['modified'] + 2
 
-        ticket.assigned_to_id = self.user._id
+        ticket.assigned_to_id = User.by_username('test-user')._id
         ticket.commit()
 
         tickets = c.user.stats.getTickets()
@@ -179,16 +171,11 @@ class TestGitCommit(unittest.TestCase, TestController):
 
     def setUp(self):
         setup_basic_test()
-        for ep in pkg_resources.iter_entry_points("allura.stats"):
-            if ep.name.lower() == 'userstats':
-                g.statslisteners = [ep.load()().listener]
 
-        self.user = User.register(dict(username='testuser',
-            display_name='Test'),
-            make_project=False)
-        self.user.set_password('testpassword')
+        user = User.by_username('test-admin')
+        user.set_password('testpassword')
         addr = M.EmailAddress.upsert('rcopeland@geek.net')
-        self.user.claim_address('rcopeland@geek.net')
+        user.claim_address('rcopeland@geek.net')
         self.setup_with_tools()
 
     @with_git
@@ -205,9 +192,10 @@ class TestGitCommit(unittest.TestCase, TestController):
         self.rev = M.repo.Commit.query.get(_id=self.repo.heads[0]['object_id'])
         self.rev.repo = self.repo
 
+    @td.with_user_project('test-admin')
     def test_commit(self):
-        commits = self.user.stats.getCommits()
+        commits = c.user.stats.getCommits()
         assert commits['number'] == 4
-        lmcommits = self.user.stats.getLastMonthCommits()
+        lmcommits = c.user.stats.getLastMonthCommits()
         assert lmcommits['number'] == 4
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/60b11feb/ForgeUserStats/test.ini
----------------------------------------------------------------------
diff --git a/ForgeUserStats/test.ini b/ForgeUserStats/test.ini
index 6753aa4..ca15ecd 100644
--- a/ForgeUserStats/test.ini
+++ b/ForgeUserStats/test.ini
@@ -5,6 +5,7 @@
 #
 [DEFAULT]
 debug = true
+user.stats.enable = true
 
 [server:main]
 use = egg:Paste#http


[04/42] git commit: [5453] Adding tests and fixing errors related to commit stats

Posted by br...@apache.org.
[5453] Adding tests and fixing errors related to commit stats


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/bb12ad35
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/bb12ad35
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/bb12ad35

Branch: refs/heads/db/5453
Commit: bb12ad353f33098dd9448763f7e609ca0020f34a
Parents: a133da1
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sat Jan 12 18:37:26 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:30 2013 +0000

----------------------------------------------------------------------
 ForgeUserStats/forgeuserstats/model/stats.py       |   22 ++-
 .../forgeuserstats/tests/data/testgit.git/HEAD     |    1 +
 .../forgeuserstats/tests/data/testgit.git/config   |    4 +
 .../tests/data/testgit.git/description             |    1 +
 .../data/testgit.git/hooks/applypatch-msg.sample   |   15 ++
 .../tests/data/testgit.git/hooks/commit-msg.sample |   24 ++
 .../data/testgit.git/hooks/post-commit.sample      |    8 +
 .../tests/data/testgit.git/hooks/post-receive      |    1 +
 .../data/testgit.git/hooks/post-receive.sample     |   15 ++
 .../data/testgit.git/hooks/post-update.sample      |    8 +
 .../data/testgit.git/hooks/pre-applypatch.sample   |   14 ++
 .../tests/data/testgit.git/hooks/pre-commit.sample |   46 ++++
 .../tests/data/testgit.git/hooks/pre-rebase.sample |  169 +++++++++++++++
 .../testgit.git/hooks/prepare-commit-msg.sample    |   36 +++
 .../tests/data/testgit.git/hooks/update            |    1 +
 .../tests/data/testgit.git/hooks/update.sample     |  128 +++++++++++
 .../tests/data/testgit.git/info/exclude            |    6 +
 .../0d/666fc313e6f4829e8d4446d4529394f8e464a9      |  Bin 0 -> 29 bytes
 .../0f/419b54b956f4a849a689105216282f1a4c749a      |  Bin 0 -> 51 bytes
 .../1e/146e67985dcd71c74de79613719bef7bddca4a      |  Bin 0 -> 164 bytes
 .../43/80f66cf5cbd858b73156681d665593ec80a58d      |  Bin 0 -> 43 bytes
 .../4b/825dc642cb6eb9a060e54bf8d69288fbee4904      |  Bin 0 -> 15 bytes
 .../6a/45885ae7347f1cac5103b0050cc1be6a1496c8      |  Bin 0 -> 158 bytes
 .../6d/2ce67bdd55ae2c2ac72cbc879ed7c67ecc9786      |  Bin 0 -> 43 bytes
 .../8c/3c7fbcd903744b20fd7567a1fcefa99133b5bc      |  Bin 0 -> 53 bytes
 .../9a/7df788cf800241e3bb5a849c8870f2f8259d98      |  Bin 0 -> 129 bytes
 .../be/00c63250248c284b842deee5d8fb0b8132acab      |  Bin 0 -> 42 bytes
 .../d7/c40db3ffe2b87e96b94c280a67265c8de7a4ad      |  Bin 0 -> 51 bytes
 .../df/30427c488aeab84b2352bdf88a3b19223f9d7a      |    2 +
 .../e5/6a4d15295d3754310f114c86d93645308110ad      |  Bin 0 -> 43 bytes
 .../e9/65047ad7c57865823c7d992b1d046ea66edf78      |  Bin 0 -> 21 bytes
 .../tests/data/testgit.git/refs/heads/master       |    1 +
 ForgeUserStats/forgeuserstats/tests/test_stats.py  |   42 ++++-
 ForgeUserStats/test.ini                            |   54 +++++
 34 files changed, 589 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/model/stats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/stats.py b/ForgeUserStats/forgeuserstats/model/stats.py
index 0dcf5af..2f5e1b9 100644
--- a/ForgeUserStats/forgeuserstats/model/stats.py
+++ b/ForgeUserStats/forgeuserstats/model/stats.py
@@ -475,16 +475,19 @@ class UserStats(MappedClass):
         self.checkOldArtifacts()
 
     def addCommit(self, newcommit, project):
-        def _addCommitData(stats, topics, languages, newblob, oldblob = None):
+        def _computeLines(newblob, oldblob = None):
             if oldblob:
                 listold = list(oldblob)
             else:
                 listold = []
-            listnew = list(newblob)
+            if newblob:
+                listnew = list(newblob)
+            else:
+                listnew = []
 
             if oldblob is None:
                 lines = len(listnew)
-            elif newblob.has_html_view:
+            elif newblob and newblob.has_html_view:
                 diff = difflib.unified_diff(
                     listold, listnew,
                     ('old' + oldblob.path()).encode('utf-8'),
@@ -492,7 +495,9 @@ class UserStats(MappedClass):
                 lines = len([l for l in diff if len(l) > 0 and l[0] == '+'])-1
             else:
                 lines = 0
-            
+            return lines
+
+        def _addCommitData(stats, topics, languages, lines):          
             lt = topics + [None]
             ll = languages + [None]
             for t in lt:
@@ -518,7 +523,6 @@ class UserStats(MappedClass):
                     else:
                         stats.general[i]['commits'][j].lines += lines
                         stats.general[i]['commits'][j].number += 1
-            return lines
 
         topics = [t for t in project.trove_topic if t]
         languages = [l for l in project.trove_language if l]
@@ -532,16 +536,18 @@ class UserStats(MappedClass):
         for changed in d.changed:
             newblob = newcommit.tree.get_blob_by_path(changed)
             oldblob = oldcommit.tree.get_blob_by_path(changed)
-            totlines+=_addCommitData(self, topics, languages, newblob, oldblob)
+            totlines+=_computeLines(newblob, oldblob)
 
         for copied in d.copied:
             newblob = newcommit.tree.get_blob_by_path(copied['new'])
             oldblob = oldcommit.tree.get_blob_by_path(copied['old'])
-            totlines+=_addCommitData(self, topics, languages, newblob, oldblob)
+            totlines+=_computeLines(newblob, oldblob)
 
         for added in d.added:
             newblob = newcommit.tree.get_blob_by_path(added)
-            totlines+=_addCommitData(self, topics, languages, newblob)
+            totlines+=_computeLines(newblob)
+
+        _addCommitData(self, topics, languages, totlines)
 
         self.lastmonth.commits.append(dict(
             datetime=now, 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/HEAD
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/HEAD b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/HEAD
new file mode 100644
index 0000000..cb089cd
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/config
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/config b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/config
new file mode 100644
index 0000000..07d359d
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/config
@@ -0,0 +1,4 @@
+[core]
+	repositoryformatversion = 0
+	filemode = true
+	bare = true

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/description
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/description b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/description
new file mode 100644
index 0000000..498b267
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/applypatch-msg.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/applypatch-msg.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/applypatch-msg.sample
new file mode 100755
index 0000000..8b2a2fe
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/applypatch-msg.sample
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message taken by
+# applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.  The hook is
+# allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "applypatch-msg".
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/commit-msg" &&
+	exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/commit-msg.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/commit-msg.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/commit-msg.sample
new file mode 100755
index 0000000..6ef1d29
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/commit-msg.sample
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message.
+# Called by git-commit with one argument, the name of the file
+# that has the commit message.  The hook should exit with non-zero
+# status after issuing an appropriate message if it wants to stop the
+# commit.  The hook is allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "commit-msg".
+
+# Uncomment the below to add a Signed-off-by line to the message.
+# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
+# hook is more suited to it.
+#
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
+
+# This example catches duplicate Signed-off-by lines.
+
+test "" = "$(grep '^Signed-off-by: ' "$1" |
+	 sort | uniq -c | sed -e '/^[ 	]*1[ 	]/d')" || {
+	echo >&2 Duplicate Signed-off-by lines.
+	exit 1
+}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-commit.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-commit.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-commit.sample
new file mode 100755
index 0000000..2266821
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-commit.sample
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script that is called after a successful
+# commit is made.
+#
+# To enable this hook, rename this file to "post-commit".
+
+: Nothing

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive
new file mode 100755
index 0000000..0f7a148
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive
@@ -0,0 +1 @@
+post-receive

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive.sample
new file mode 100755
index 0000000..7a83e17
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive.sample
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# An example hook script for the "post-receive" event.
+#
+# The "post-receive" script is run after receive-pack has accepted a pack
+# and the repository has been updated.  It is passed arguments in through
+# stdin in the form
+#  <oldrev> <newrev> <refname>
+# For example:
+#  aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
+#
+# see contrib/hooks/ for a sample, or uncomment the next line and
+# rename the file to "post-receive".
+
+#. /usr/share/doc/git-core/contrib/hooks/post-receive-email

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-update.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-update.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-update.sample
new file mode 100755
index 0000000..5323b56
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-update.sample
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script to prepare a packed repository for use over
+# dumb transports.
+#
+# To enable this hook, rename this file to "post-update".
+
+exec git-update-server-info

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-applypatch.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-applypatch.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-applypatch.sample
new file mode 100755
index 0000000..b1f187c
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-applypatch.sample
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed
+# by applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.
+#
+# To enable this hook, rename this file to "pre-applypatch".
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/pre-commit" &&
+	exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-commit.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-commit.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-commit.sample
new file mode 100755
index 0000000..439eefd
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-commit.sample
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed.
+# Called by git-commit with no arguments.  The hook should
+# exit with non-zero status after issuing an appropriate message if
+# it wants to stop the commit.
+#
+# To enable this hook, rename this file to "pre-commit".
+
+if git-rev-parse --verify HEAD >/dev/null 2>&1
+then
+	against=HEAD
+else
+	# Initial commit: diff against an empty tree object
+	against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+fi
+
+# If you want to allow non-ascii filenames set this variable to true.
+allownonascii=$(git config hooks.allownonascii)
+
+# Cross platform projects tend to avoid non-ascii filenames; prevent
+# them from being added to the repository. We exploit the fact that the
+# printable range starts at the space character and ends with tilde.
+if [ "$allownonascii" != "true" ] &&
+	# Note that the use of brackets around a tr range is ok here, (it's
+	# even required, for portability to Solaris 10's /usr/bin/tr), since
+	# the square bracket bytes happen to fall in the designated range.
+	test "$(git diff --cached --name-only --diff-filter=A -z $against |
+	  LC_ALL=C tr -d '[ -~]\0')"
+then
+	echo "Error: Attempt to add a non-ascii file name."
+	echo
+	echo "This can cause problems if you want to work"
+	echo "with people on other platforms."
+	echo
+	echo "To be portable it is advisable to rename the file ..."
+	echo
+	echo "If you know what you are doing you can disable this"
+	echo "check using:"
+	echo
+	echo "  git config hooks.allownonascii true"
+	echo
+	exit 1
+fi
+
+exec git diff-index --check --cached $against --

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-rebase.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-rebase.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-rebase.sample
new file mode 100755
index 0000000..be1b06e
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-rebase.sample
@@ -0,0 +1,169 @@
+#!/bin/sh
+#
+# Copyright (c) 2006, 2008 Junio C Hamano
+#
+# The "pre-rebase" hook is run just before "git-rebase" starts doing
+# its job, and can prevent the command from running by exiting with
+# non-zero status.
+#
+# The hook is called with the following parameters:
+#
+# $1 -- the upstream the series was forked from.
+# $2 -- the branch being rebased (or empty when rebasing the current branch).
+#
+# This sample shows how to prevent topic branches that are already
+# merged to 'next' branch from getting rebased, because allowing it
+# would result in rebasing already published history.
+
+publish=next
+basebranch="$1"
+if test "$#" = 2
+then
+	topic="refs/heads/$2"
+else
+	topic=`git symbolic-ref HEAD` ||
+	exit 0 ;# we do not interrupt rebasing detached HEAD
+fi
+
+case "$topic" in
+refs/heads/??/*)
+	;;
+*)
+	exit 0 ;# we do not interrupt others.
+	;;
+esac
+
+# Now we are dealing with a topic branch being rebased
+# on top of master.  Is it OK to rebase it?
+
+# Does the topic really exist?
+git show-ref -q "$topic" || {
+	echo >&2 "No such branch $topic"
+	exit 1
+}
+
+# Is topic fully merged to master?
+not_in_master=`git-rev-list --pretty=oneline ^master "$topic"`
+if test -z "$not_in_master"
+then
+	echo >&2 "$topic is fully merged to master; better remove it."
+	exit 1 ;# we could allow it, but there is no point.
+fi
+
+# Is topic ever merged to next?  If so you should not be rebasing it.
+only_next_1=`git-rev-list ^master "^$topic" ${publish} | sort`
+only_next_2=`git-rev-list ^master           ${publish} | sort`
+if test "$only_next_1" = "$only_next_2"
+then
+	not_in_topic=`git-rev-list "^$topic" master`
+	if test -z "$not_in_topic"
+	then
+		echo >&2 "$topic is already up-to-date with master"
+		exit 1 ;# we could allow it, but there is no point.
+	else
+		exit 0
+	fi
+else
+	not_in_next=`git-rev-list --pretty=oneline ^${publish} "$topic"`
+	perl -e '
+		my $topic = $ARGV[0];
+		my $msg = "* $topic has commits already merged to public branch:\n";
+		my (%not_in_next) = map {
+			/^([0-9a-f]+) /;
+			($1 => 1);
+		} split(/\n/, $ARGV[1]);
+		for my $elem (map {
+				/^([0-9a-f]+) (.*)$/;
+				[$1 => $2];
+			} split(/\n/, $ARGV[2])) {
+			if (!exists $not_in_next{$elem->[0]}) {
+				if ($msg) {
+					print STDERR $msg;
+					undef $msg;
+				}
+				print STDERR " $elem->[1]\n";
+			}
+		}
+	' "$topic" "$not_in_next" "$not_in_master"
+	exit 1
+fi
+
+exit 0
+
+################################################################
+
+This sample hook safeguards topic branches that have been
+published from being rewound.
+
+The workflow assumed here is:
+
+ * Once a topic branch forks from "master", "master" is never
+   merged into it again (either directly or indirectly).
+
+ * Once a topic branch is fully cooked and merged into "master",
+   it is deleted.  If you need to build on top of it to correct
+   earlier mistakes, a new topic branch is created by forking at
+   the tip of the "master".  This is not strictly necessary, but
+   it makes it easier to keep your history simple.
+
+ * Whenever you need to test or publish your changes to topic
+   branches, merge them into "next" branch.
+
+The script, being an example, hardcodes the publish branch name
+to be "next", but it is trivial to make it configurable via
+$GIT_DIR/config mechanism.
+
+With this workflow, you would want to know:
+
+(1) ... if a topic branch has ever been merged to "next".  Young
+    topic branches can have stupid mistakes you would rather
+    clean up before publishing, and things that have not been
+    merged into other branches can be easily rebased without
+    affecting other people.  But once it is published, you would
+    not want to rewind it.
+
+(2) ... if a topic branch has been fully merged to "master".
+    Then you can delete it.  More importantly, you should not
+    build on top of it -- other people may already want to
+    change things related to the topic as patches against your
+    "master", so if you need further changes, it is better to
+    fork the topic (perhaps with the same name) afresh from the
+    tip of "master".
+
+Let's look at this example:
+
+		   o---o---o---o---o---o---o---o---o---o "next"
+		  /       /           /           /
+		 /   a---a---b A     /           /
+		/   /               /           /
+	       /   /   c---c---c---c B         /
+	      /   /   /             \         /
+	     /   /   /   b---b C     \       /
+	    /   /   /   /             \     /
+    ---o---o---o---o---o---o---o---o---o---o---o "master"
+
+
+A, B and C are topic branches.
+
+ * A has one fix since it was merged up to "next".
+
+ * B has finished.  It has been fully merged up to "master" and "next",
+   and is ready to be deleted.
+
+ * C has not merged to "next" at all.
+
+We would want to allow C to be rebased, refuse A, and encourage
+B to be deleted.
+
+To compute (1):
+
+	git-rev-list ^master ^topic next
+	git-rev-list ^master        next
+
+	if these match, topic has not merged in next at all.
+
+To compute (2):
+
+	git-rev-list master..topic
+
+	if this is empty, it is fully merged to "master".

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/prepare-commit-msg.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/prepare-commit-msg.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/prepare-commit-msg.sample
new file mode 100755
index 0000000..3652424
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/prepare-commit-msg.sample
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# An example hook script to prepare the commit log message.
+# Called by git-commit with the name of the file that has the
+# commit message, followed by the description of the commit
+# message's source.  The hook's purpose is to edit the commit
+# message file.  If the hook fails with a non-zero status,
+# the commit is aborted.
+#
+# To enable this hook, rename this file to "prepare-commit-msg".
+
+# This hook includes three examples.  The first comments out the
+# "Conflicts:" part of a merge commit.
+#
+# The second includes the output of "git diff --name-status -r"
+# into the message, just before the "git status" output.  It is
+# commented because it doesn't cope with --amend or with squashed
+# commits.
+#
+# The third example adds a Signed-off-by line to the message, that can
+# still be edited.  This is rarely a good idea.
+
+case "$2,$3" in
+  merge,)
+    perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;
+
+# ,|template,)
+#   perl -i.bak -pe '
+#      print "\n" . `git diff --cached --name-status -r`
+#	 if /^#/ && $first++ == 0' "$1" ;;
+
+  *) ;;
+esac
+
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update
new file mode 100755
index 0000000..4ea5e4d
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update
@@ -0,0 +1 @@
+update

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update.sample
new file mode 100755
index 0000000..fd63b2d
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update.sample
@@ -0,0 +1,128 @@
+#!/bin/sh
+#
+# An example hook script to blocks unannotated tags from entering.
+# Called by git-receive-pack with arguments: refname sha1-old sha1-new
+#
+# To enable this hook, rename this file to "update".
+#
+# Config
+# ------
+# hooks.allowunannotated
+#   This boolean sets whether unannotated tags will be allowed into the
+#   repository.  By default they won't be.
+# hooks.allowdeletetag
+#   This boolean sets whether deleting tags will be allowed in the
+#   repository.  By default they won't be.
+# hooks.allowmodifytag
+#   This boolean sets whether a tag may be modified after creation. By default
+#   it won't be.
+# hooks.allowdeletebranch
+#   This boolean sets whether deleting branches will be allowed in the
+#   repository.  By default they won't be.
+# hooks.denycreatebranch
+#   This boolean sets whether remotely creating branches will be denied
+#   in the repository.  By default this is allowed.
+#
+
+# --- Command line
+refname="$1"
+oldrev="$2"
+newrev="$3"
+
+# --- Safety check
+if [ -z "$GIT_DIR" ]; then
+	echo "Don't run this script from the command line." >&2
+	echo " (if you want, you could supply GIT_DIR then run" >&2
+	echo "  $0 <ref> <oldrev> <newrev>)" >&2
+	exit 1
+fi
+
+if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
+	echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
+	exit 1
+fi
+
+# --- Config
+allowunannotated=$(git config --bool hooks.allowunannotated)
+allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
+denycreatebranch=$(git config --bool hooks.denycreatebranch)
+allowdeletetag=$(git config --bool hooks.allowdeletetag)
+allowmodifytag=$(git config --bool hooks.allowmodifytag)
+
+# check for no description
+projectdesc=$(sed -e '1q' "$GIT_DIR/description")
+case "$projectdesc" in
+"Unnamed repository"* | "")
+	echo "*** Project description file hasn't been set" >&2
+	exit 1
+	;;
+esac
+
+# --- Check types
+# if $newrev is 0000...0000, it's a commit to delete a ref.
+zero="0000000000000000000000000000000000000000"
+if [ "$newrev" = "$zero" ]; then
+	newrev_type=delete
+else
+	newrev_type=$(git-cat-file -t $newrev)
+fi
+
+case "$refname","$newrev_type" in
+	refs/tags/*,commit)
+		# un-annotated tag
+		short_refname=${refname##refs/tags/}
+		if [ "$allowunannotated" != "true" ]; then
+			echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
+			echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
+			exit 1
+		fi
+		;;
+	refs/tags/*,delete)
+		# delete tag
+		if [ "$allowdeletetag" != "true" ]; then
+			echo "*** Deleting a tag is not allowed in this repository" >&2
+			exit 1
+		fi
+		;;
+	refs/tags/*,tag)
+		# annotated tag
+		if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
+		then
+			echo "*** Tag '$refname' already exists." >&2
+			echo "*** Modifying a tag is not allowed in this repository." >&2
+			exit 1
+		fi
+		;;
+	refs/heads/*,commit)
+		# branch
+		if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
+			echo "*** Creating a branch is not allowed in this repository" >&2
+			exit 1
+		fi
+		;;
+	refs/heads/*,delete)
+		# delete branch
+		if [ "$allowdeletebranch" != "true" ]; then
+			echo "*** Deleting a branch is not allowed in this repository" >&2
+			exit 1
+		fi
+		;;
+	refs/remotes/*,commit)
+		# tracking branch
+		;;
+	refs/remotes/*,delete)
+		# delete tracking branch
+		if [ "$allowdeletebranch" != "true" ]; then
+			echo "*** Deleting a tracking branch is not allowed in this repository" >&2
+			exit 1
+		fi
+		;;
+	*)
+		# Anything else (is there anything else?)
+		echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
+		exit 1
+		;;
+esac
+
+# --- Finished
+exit 0

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/info/exclude
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/info/exclude b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/info/exclude
new file mode 100644
index 0000000..2c87b72
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/info/exclude
@@ -0,0 +1,6 @@
+# git-ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/0d/666fc313e6f4829e8d4446d4529394f8e464a9
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/0d/666fc313e6f4829e8d4446d4529394f8e464a9 b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/0d/666fc313e6f4829e8d4446d4529394f8e464a9
new file mode 100644
index 0000000..934760d
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/0d/666fc313e6f4829e8d4446d4529394f8e464a9 differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/0f/419b54b956f4a849a689105216282f1a4c749a
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/0f/419b54b956f4a849a689105216282f1a4c749a b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/0f/419b54b956f4a849a689105216282f1a4c749a
new file mode 100644
index 0000000..a7d3884
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/0f/419b54b956f4a849a689105216282f1a4c749a differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/1e/146e67985dcd71c74de79613719bef7bddca4a
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/1e/146e67985dcd71c74de79613719bef7bddca4a b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/1e/146e67985dcd71c74de79613719bef7bddca4a
new file mode 100644
index 0000000..2f0027a
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/1e/146e67985dcd71c74de79613719bef7bddca4a differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/43/80f66cf5cbd858b73156681d665593ec80a58d
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/43/80f66cf5cbd858b73156681d665593ec80a58d b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/43/80f66cf5cbd858b73156681d665593ec80a58d
new file mode 100644
index 0000000..fe031e3
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/43/80f66cf5cbd858b73156681d665593ec80a58d differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
new file mode 100644
index 0000000..adf6411
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/6a/45885ae7347f1cac5103b0050cc1be6a1496c8
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/6a/45885ae7347f1cac5103b0050cc1be6a1496c8 b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/6a/45885ae7347f1cac5103b0050cc1be6a1496c8
new file mode 100644
index 0000000..42efcfb
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/6a/45885ae7347f1cac5103b0050cc1be6a1496c8 differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/6d/2ce67bdd55ae2c2ac72cbc879ed7c67ecc9786
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/6d/2ce67bdd55ae2c2ac72cbc879ed7c67ecc9786 b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/6d/2ce67bdd55ae2c2ac72cbc879ed7c67ecc9786
new file mode 100644
index 0000000..838b73a
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/6d/2ce67bdd55ae2c2ac72cbc879ed7c67ecc9786 differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/8c/3c7fbcd903744b20fd7567a1fcefa99133b5bc
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/8c/3c7fbcd903744b20fd7567a1fcefa99133b5bc b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/8c/3c7fbcd903744b20fd7567a1fcefa99133b5bc
new file mode 100644
index 0000000..7c9f6bb
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/8c/3c7fbcd903744b20fd7567a1fcefa99133b5bc differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/9a/7df788cf800241e3bb5a849c8870f2f8259d98
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/9a/7df788cf800241e3bb5a849c8870f2f8259d98 b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/9a/7df788cf800241e3bb5a849c8870f2f8259d98
new file mode 100644
index 0000000..04693ec
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/9a/7df788cf800241e3bb5a849c8870f2f8259d98 differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/be/00c63250248c284b842deee5d8fb0b8132acab
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/be/00c63250248c284b842deee5d8fb0b8132acab b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/be/00c63250248c284b842deee5d8fb0b8132acab
new file mode 100644
index 0000000..2cab292
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/be/00c63250248c284b842deee5d8fb0b8132acab differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/d7/c40db3ffe2b87e96b94c280a67265c8de7a4ad
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/d7/c40db3ffe2b87e96b94c280a67265c8de7a4ad b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/d7/c40db3ffe2b87e96b94c280a67265c8de7a4ad
new file mode 100644
index 0000000..491fcfa
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/d7/c40db3ffe2b87e96b94c280a67265c8de7a4ad differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/df/30427c488aeab84b2352bdf88a3b19223f9d7a
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/df/30427c488aeab84b2352bdf88a3b19223f9d7a b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/df/30427c488aeab84b2352bdf88a3b19223f9d7a
new file mode 100644
index 0000000..8ad2f5e
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/df/30427c488aeab84b2352bdf88a3b19223f9d7a
@@ -0,0 +1,2 @@
+x��K
+�@@]�)r%�f~ ��K7� MSm+e��"��������0�*x�u6�J�-!�,���\����IX�G���6V��!� �N=�h lZĀ��Z�5;y��4���w�OO{���z�n/f��hu�s䔐	�ȈN�����ݮ��|�NG�c�E�
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/e5/6a4d15295d3754310f114c86d93645308110ad
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/e5/6a4d15295d3754310f114c86d93645308110ad b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/e5/6a4d15295d3754310f114c86d93645308110ad
new file mode 100644
index 0000000..7d5ae85
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/e5/6a4d15295d3754310f114c86d93645308110ad differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/e9/65047ad7c57865823c7d992b1d046ea66edf78
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/e9/65047ad7c57865823c7d992b1d046ea66edf78 b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/e9/65047ad7c57865823c7d992b1d046ea66edf78
new file mode 100644
index 0000000..341688a
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/objects/e9/65047ad7c57865823c7d992b1d046ea66edf78 differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/data/testgit.git/refs/heads/master
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/refs/heads/master b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/refs/heads/master
new file mode 100644
index 0000000..7e970a5
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/refs/heads/master
@@ -0,0 +1 @@
+1e146e67985dcd71c74de79613719bef7bddca4a

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/forgeuserstats/tests/test_stats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/test_stats.py b/ForgeUserStats/forgeuserstats/tests/test_stats.py
index f0ee6a2..2972a0e 100644
--- a/ForgeUserStats/forgeuserstats/tests/test_stats.py
+++ b/ForgeUserStats/forgeuserstats/tests/test_stats.py
@@ -1,13 +1,16 @@
 import pkg_resources
+import unittest
 
 from pylons import app_globals as g
 from pylons import tmpl_context as c
 
-from alluratest.controller import TestController
+from alluratest.controller import TestController, setup_basic_test, setup_global_objects
 from allura.tests import decorators as td
 from allura.lib import helpers as h
 from allura.model import User
+from allura import model as M
 
+from forgegit.tests import with_git
 from forgewiki import model as WM
 from forgetracker import model as TM
 
@@ -171,3 +174,40 @@ class TestStats(TestController):
         assert tickets['revoked'] == initial_tickets['revoked'] + 1
         assert tickets_artifacts['created'] == initial_tickets_artifacts['created'] + 2
         assert tickets_artifacts['modified'] == initial_tickets_artifacts['modified'] + 3
+
+class TestGitCommit(unittest.TestCase, TestController):
+
+    def setUp(self):
+        setup_basic_test()
+        for ep in pkg_resources.iter_entry_points("allura.stats"):
+            if ep.name.lower() == 'userstats':
+                g.statslisteners = [ep.load()().listener]
+
+        self.user = User.register(dict(username='testuser',
+            display_name='Test'),
+            make_project=False)
+        self.user.set_password('testpassword')
+        addr = M.EmailAddress.upsert('rcopeland@geek.net')
+        self.user.claim_address('rcopeland@geek.net')
+        self.setup_with_tools()
+
+    @with_git
+    @td.with_wiki
+    def setup_with_tools(self):
+        setup_global_objects()
+        h.set_context('test', 'src-git', neighborhood='Projects')
+        repo_dir = pkg_resources.resource_filename(
+            'forgeuserstats', 'tests/data')
+        c.app.repo.fs_path = repo_dir
+        c.app.repo.name = 'testgit.git'
+        self.repo = c.app.repo
+        self.repo.refresh()
+        self.rev = M.repo.Commit.query.get(_id=self.repo.heads[0]['object_id'])
+        self.rev.repo = self.repo
+
+    def test_commit(self):
+        commits = self.user.stats.getCommits()
+        assert commits['number'] == 4
+        lmcommits = self.user.stats.getLastMonthCommits()
+        assert lmcommits['number'] == 4
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb12ad35/ForgeUserStats/test.ini
----------------------------------------------------------------------
diff --git a/ForgeUserStats/test.ini b/ForgeUserStats/test.ini
new file mode 100644
index 0000000..6753aa4
--- /dev/null
+++ b/ForgeUserStats/test.ini
@@ -0,0 +1,54 @@
+#
+# allura - TurboGears 2 testing environment configuration
+#
+# The %(here)s variable will be replaced with the parent directory of this file
+#
+[DEFAULT]
+debug = true
+
+[server:main]
+use = egg:Paste#http
+host = 0.0.0.0
+port = 5000
+
+[app:main]
+use = config:../Allura/test.ini
+
+[app:main_without_authn]
+use = config:../Allura/test.ini#main_without_authn
+
+[app:main_with_amqp]
+use = config:../Allura/test.ini#main_with_amqp
+
+[loggers]
+keys = root, allura, tool
+
+[handlers]
+keys = test
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = test
+
+[logger_allura]
+level = DEBUG
+handlers =
+qualname = allura
+
+[logger_tool]
+level = DEBUG
+handlers =
+qualname = forgeuserstats
+
+[handler_test]
+class = FileHandler
+args = ('test.log',)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S


[06/42] git commit: [5453] Added some tests

Posted by br...@apache.org.
[5453] Added some tests


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/a133da10
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/a133da10
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/a133da10

Branch: refs/heads/db/5453
Commit: a133da109258a3218344ee2c48556cae123c0f04
Parents: 61f53eb
Author: Stefano Invernizzi <st...@apache.org>
Authored: Fri Jan 11 19:08:03 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:30 2013 +0000

----------------------------------------------------------------------
 ForgeUserStats/forgeuserstats/tests/test_stats.py |  173 ++++++++++++++++
 1 files changed, 173 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a133da10/ForgeUserStats/forgeuserstats/tests/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/__init__.py b/ForgeUserStats/forgeuserstats/tests/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a133da10/ForgeUserStats/forgeuserstats/tests/test_stats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/test_stats.py b/ForgeUserStats/forgeuserstats/tests/test_stats.py
new file mode 100644
index 0000000..f0ee6a2
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/test_stats.py
@@ -0,0 +1,173 @@
+import pkg_resources
+
+from pylons import app_globals as g
+from pylons import tmpl_context as c
+
+from alluratest.controller import TestController
+from allura.tests import decorators as td
+from allura.lib import helpers as h
+from allura.model import User
+
+from forgewiki import model as WM
+from forgetracker import model as TM
+
+class TestStats(TestController):
+
+    test_username = 'teststats'
+    test_password = 'foo'
+
+    def setUp(self):
+        super(TestStats, self).setUp()
+        for ep in pkg_resources.iter_entry_points("allura.stats"):
+            if ep.name.lower() == 'userstats':
+                g.statslisteners = [ep.load()().listener]
+
+        self.user = User.register(dict(username=self.test_username,
+            display_name='Test Stats'),
+            make_project=False)
+        self.user.set_password(self.test_password)
+        
+    def test_init_values(self):
+        artifacts = self.user.stats.getArtifacts()
+        tickets = self.user.stats.getTickets()
+        commits = self.user.stats.getCommits()
+        assert self.user.stats.tot_logins_count == 0
+        assert artifacts['created'] == 0
+        assert artifacts['modified'] == 0
+        assert tickets['assigned'] == 0
+        assert tickets['solved'] == 0
+        assert tickets['revoked'] == 0
+        assert tickets['averagesolvingtime'] is None
+        assert commits['number'] == 0
+        assert commits['lines'] == 0
+
+        lmartifacts = self.user.stats.getLastMonthArtifacts()
+        lmtickets = self.user.stats.getLastMonthTickets()
+        lmcommits = self.user.stats.getLastMonthCommits()
+        assert self.user.stats.getLastMonthLogins() == 0
+        assert lmartifacts['created'] == 0
+        assert lmartifacts['modified'] == 0
+        assert lmtickets['assigned'] == 0
+        assert lmtickets['solved'] == 0
+        assert lmtickets['revoked'] == 0
+        assert lmtickets['averagesolvingtime'] is None
+        assert lmcommits['number'] == 0
+        assert lmcommits['lines'] == 0
+
+    def test_login(self):
+        init_logins = self.user.stats.tot_logins_count
+        r = self.app.post('/auth/do_login', params=dict(
+                username=self.test_username, password=self.test_password))
+
+        assert self.user.stats.tot_logins_count == 1 + init_logins
+        assert self.user.stats.getLastMonthLogins() == 1 + init_logins
+
+    @td.with_user_project('test-admin')
+    @td.with_wiki
+    def test_wiki_stats(self):
+        initial_artifacts = c.user.stats.getArtifacts()
+        initial_wiki = c.user.stats.getArtifacts(art_type="Wiki")
+
+        h.set_context('test', 'wiki', neighborhood='Projects')
+        page = WM.Page(title="TestPage", text="some text")
+        page.commit()
+
+        artifacts = c.user.stats.getArtifacts()
+        wiki = c.user.stats.getArtifacts(art_type="Wiki")
+
+        assert artifacts['created'] == 1 + initial_artifacts['created']
+        assert artifacts['modified'] == initial_artifacts['modified']
+        assert wiki['created'] == 1 + initial_wiki['created']
+        assert wiki['modified'] == initial_wiki['modified']
+
+        page = WM.Page(title="TestPage2", text="some different text")
+        page.commit()
+
+        artifacts = c.user.stats.getArtifacts()
+        wiki = c.user.stats.getArtifacts(art_type="Wiki")
+
+        assert artifacts['created'] == 2 + initial_artifacts['created']
+        assert artifacts['modified'] == initial_artifacts['modified']
+        assert wiki['created'] == 2 + initial_wiki['created']
+        assert wiki['modified'] == initial_wiki['modified']
+
+
+        page.text="some modified text"
+        page.commit()
+
+        artifacts = c.user.stats.getArtifacts()
+        wiki = c.user.stats.getArtifacts(art_type="Wiki")
+
+        assert artifacts['created'] == 2 + initial_artifacts['created']
+        assert artifacts['modified'] == 1 + initial_artifacts['modified']
+        assert wiki['created'] == 2 + initial_wiki['created']
+        assert wiki['modified'] == 1 + initial_wiki['modified']
+
+
+    @td.with_user_project('test-admin')
+    @td.with_tracker
+    def test_tracker_stats(self):
+        initial_tickets = c.user.stats.getTickets()
+        initial_tickets_artifacts = c.user.stats.getArtifacts(art_type="Ticket")
+
+        h.set_context('test', 'bugs', neighborhood='Projects')
+        ticket = TM.Ticket(ticket_num=12, summary="test", assigned_to_id = c.user._id)
+        ticket.commit()
+
+        tickets = c.user.stats.getTickets()
+        tickets_artifacts = c.user.stats.getArtifacts(art_type="Ticket")
+
+        assert tickets['assigned'] == initial_tickets['assigned'] + 1
+        assert tickets['solved'] == initial_tickets['solved']
+        assert tickets['revoked'] == initial_tickets['revoked']
+        assert tickets_artifacts['created'] == initial_tickets_artifacts['created'] + 1
+        assert tickets_artifacts['modified'] == initial_tickets_artifacts['modified']
+
+        ticket.status = 'closed'
+        ticket.commit()
+
+        tickets = c.user.stats.getTickets()
+        tickets_artifacts = c.user.stats.getArtifacts(art_type="Ticket")
+
+        assert tickets['assigned'] == initial_tickets['assigned'] + 1
+        assert tickets['solved'] == initial_tickets['solved'] + 1
+        assert tickets['revoked'] == initial_tickets['revoked']
+        assert tickets_artifacts['created'] == initial_tickets_artifacts['created'] + 1
+        assert tickets_artifacts['modified'] == initial_tickets_artifacts['modified'] + 1
+
+        h.set_context('test', 'bugs', neighborhood='Projects')
+        ticket = TM.Ticket(ticket_num=13, summary="test")
+        ticket.commit()
+        
+        tickets = c.user.stats.getTickets()
+        tickets_artifacts = c.user.stats.getArtifacts(art_type="Ticket")
+
+        assert tickets['assigned'] == initial_tickets['assigned'] + 1
+        assert tickets['solved'] == initial_tickets['solved'] + 1
+        assert tickets['revoked'] == initial_tickets['revoked']
+        assert tickets_artifacts['created'] == initial_tickets_artifacts['created'] + 2
+        assert tickets_artifacts['modified'] == initial_tickets_artifacts['modified'] + 1
+
+        ticket.assigned_to_id = c.user._id
+        ticket.commit()
+
+        tickets = c.user.stats.getTickets()
+        tickets_artifacts = c.user.stats.getArtifacts(art_type="Ticket")
+        
+        assert tickets['assigned'] == initial_tickets['assigned'] + 2
+        assert tickets['solved'] == initial_tickets['solved'] + 1
+        assert tickets['revoked'] == initial_tickets['revoked']
+        assert tickets_artifacts['created'] == initial_tickets_artifacts['created'] + 2
+        assert tickets_artifacts['modified'] == initial_tickets_artifacts['modified'] + 2
+
+        ticket.assigned_to_id = self.user._id
+        ticket.commit()
+
+        tickets = c.user.stats.getTickets()
+        tickets_artifacts = c.user.stats.getArtifacts(art_type="Ticket")
+        
+        assert tickets['assigned'] == initial_tickets['assigned'] + 2
+        assert tickets['solved'] == initial_tickets['solved'] + 1
+        assert tickets['revoked'] == initial_tickets['revoked'] + 1
+        assert tickets_artifacts['created'] == initial_tickets_artifacts['created'] + 2
+        assert tickets_artifacts['modified'] == initial_tickets_artifacts['modified'] + 3


[40/42] git commit: Remove user_preferences.html

Posted by br...@apache.org.
Remove user_preferences.html


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/c0eb784d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/c0eb784d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/c0eb784d

Branch: refs/heads/db/5453
Commit: c0eb784d8758635e21e918c4597919c52a6d76b2
Parents: 08a31a1
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sun Mar 24 11:36:25 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:34 2013 +0000

----------------------------------------------------------------------
 Allura/allura/templates/user_preferences.html |  279 --------------------
 1 files changed, 0 insertions(+), 279 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c0eb784d/Allura/allura/templates/user_preferences.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/user_preferences.html b/Allura/allura/templates/user_preferences.html
deleted file mode 100644
index 768cd2e..0000000
--- a/Allura/allura/templates/user_preferences.html
+++ /dev/null
@@ -1,279 +0,0 @@
-{% set hide_left_bar = True %}
-{% extends g.theme.master %}
-
-{% block title %}{{c.user.username}} / Preferences{% endblock %}
-
-{% block header %}User Preferences for {{c.user.username}}{% endblock %}
-
-{% block content %}
-  <ul id="account-nav-menu" class="b-hornav droppy">
-      {% for item in menu -%}
-      <li id="{{ item.tabid }}">
-      <a href="{{ item.target }}">
-          {{ item.title }}
-          <div class="marker{% if item.target.rstrip('/') == request.path.rstrip('/') %} current{% endif %}"></div>
-      </a>
-      </li>
-      {%- endfor %}
-   </ul>
-
-  <div style="clear:both" class="grid-20">
-    <h2>Personal Settings</h2>
-    {{g.theme.personal_data_form.display(action="/auth/prefs/change_personal_data", user=c.user)}}
-  </div>
-
-  <div style="clear:both" class="grid-20">
-    <a name="Contacts"></a>
-    <h2>Personal Contacts</h2>
-    <h3>Skype account</h3>
-
-    {{g.theme.skype_account_form.display(action="/auth/prefs/skype_account",
-          initial_value=c.user.get_pref('skypeaccount'))}}
-
-    {%if c.user.get_pref('socialnetworks') or c.user.get_pref('telnumbers') or c.user.get_pref('webpages') %}
-      <h3>Other existing contacts</h3>
-        <table>
-          <tr>
-            <thead>
-              <th>Type</th>
-              <th>Contact</th>
-              <th>Actions</th>
-            </thead>
-          </tr>
-          {% for sn in c.user.get_pref('socialnetworks') %}
-             {{g.theme.remove_socialnetwork_form.display(account=sn.accounturl, socialnetwork=sn.socialnetwork)}}
-          {% endfor %}
-
-          {% for tn in c.user.get_pref('telnumbers') %}
-              {{g.theme.remove_textvalue_form.display(action="/auth/prefs/remove_telnumber", value=tn, label="Telephone number")}}
-          {%endfor%}
-
-          {% for ws in c.user.get_pref('webpages') %}
-              {{g.theme.remove_textvalue_form.display(action="/auth/prefs/remove_webpage", value=ws, label="Website url")}}
-          {%endfor%}
-        </table>
-    {% endif %}
-
-    <h3>Add a social network account</h3>
-    {{g.theme.add_socialnetwork_form.display(action="/auth/prefs/add_social_network")}}
-    <h3>Add a telephone number</h3>
-    {{g.theme.add_telnumber_form.display(action="/auth/prefs/add_telnumber")}}
-    <h3>Add a personal website</h3>
-    {{g.theme.add_website_form.display(action="/auth/prefs/add_webpage")}}
-  </div>
-
-  <a name="Availability"></a>
-  <div style="clear:both" class="grid-20">
-    <h2>Availability</h2>
-    <div class="grid-18">
-      If you want, you can set the weekly timeslot during which you are usually available to support other users of the forge.
-      Please, set your time intervals choosing a weekday and entering the time interval according to the timezone specified in your
-      personal data, using the format HH:MM. If you didn't set any timezone, your timeslots could be meaningless to other users,
-      therefore they will be ignored.
-    </div>
-    <div class="grid-18">
-      You can also specify periods of time during which you won't be able to work on the forge, in orther to communicate other users
-      that they can't contact you during those days. Please, do it specifying date intervals in format DD/MM/YYYY.
-    </div>
-  </div>
-  <div class="grid-20">
-    {%if c.user.get_availability_timeslots() %}
-      <h3>Existing availability timeslots</h3>
-      <table>
-        <tr>
-          <thead>
-            <th>Weekday</th>
-            <th>Start time</th>
-            <th>End time</th>
-            <th>Actions</th>
-          </thead>
-        </tr>
-        {% for ts in c.user.get_availability_timeslots() %}
-          {{g.theme.remove_timeslot_form.display(
-                action="/auth/prefs/remove_timeslot",
-                weekday=ts.week_day,
-                starttime=ts.start_time,
-                endtime=ts.end_time)}}
-        {%endfor%}
-      </table>
-    {% endif %}
-    <h3>Add a new availability timeslot</h3>
-    {{g.theme.add_timeslot_form.display(action="/auth/prefs/add_timeslot")}}
-  </div>
-
-  <div class="grid-20">
-    {%if c.user.get_inactive_periods() %}
-      <h3>Existing periods of inactivity on the forge</h3>
-      <table>
-        <tr>
-          <thead>
-            <th>Start date</th>
-            <th>End date</th>
-            <th>Actions</th>
-          </thead>
-        </tr>
-        {% for ip in c.user.get_inactive_periods() %}
-          {{g.theme.remove_inactive_period_form.display(
-                action="/auth/prefs/remove_inactive_period",
-                startdate=ip.start_date,
-                enddate=ip.end_date)}}
-        {%endfor%}
-      </table>
-    {% endif %}
-    <h3>Add a new period of inactivity on the forge</h3>
-    {{g.theme.add_inactive_period_form.display(action="/auth/prefs/add_inactive_period")}}
-  </div>
-
-  <div class="grid-20">
-    <h2>Skills list</h2>
-    <ul><li><a href="/auth/prefs/user_skills">Click here to check and change your skills list</a></li></ul>
-  </div>
-
-  {% if g.theme.password_change_form %}
-  <div class="grid-20">
-    <h2>Change Password</h2>
-    {{ g.theme.password_change_form.display() }}
-  </div>
-  {% endif %}
-  {% if g.theme.upload_key_form %}
-  <div class="grid-20">
-    <h2>Upload ssh public key</h2>
-    {{ g.theme.upload_key_form.display() }}
-  </div>
-  {% endif %}
-
-  {% if tg.config.get('auth.method', 'local') == 'local' %}
-      <br style="clear:both"/>
-      <h2>API Token</h2>
-      {% if api_token %}
-        <p>
-          <b>API Key:</b><br/>
-          {{api_token.api_key}}<br/>
-          <b>Secret Key:</b><br/>
-          {{api_token.secret_key}}<br/>
-        </p>
-        <form method="POST" action="del_api_token" class="grid-18">
-          <input type="submit" value="Delete API Token">
-        </form>
-      {% else %}
-        <p>No API token generated</p>
-      {% endif %}
-      <form method="POST" action="gen_api_token" class="grid-18">
-        <input type="submit" value="(Re)generate API Token">
-      </form>
-  {% endif %}
-
-  <div style="clear:both"></div>
-
-  <h2>Authorized Third-party Applications</h2>
-  {% for access_tok in authorized_applications %}
-    <div>
-      <h3>{{access_tok.consumer_token.name}}</h3>
-      {{access_tok.consumer_token.description_html}}
-      {{ c.revoke_access.display(value=access_tok) }}
-      <br style="clear:both"/>
-  </div>
- {% endfor %}
-    {% if not authorized_applications %}<p>No authorized third-party applications</p>{% endif %}
-
-
-  <h2>Subscriptions</h2>
-  {% if subscriptions %}
-    <p><em>Mark tools that you want to subscribe to. Unmark tools that you want to unsubscribe from. Press 'Save' button.</em></p>
-    {{c.form.display(action='update_subscriptions', value=dict(subscriptions=subscriptions))}}
-  {% else%}
-    <p>No subscriptions.</p>
-  {% endif %}
-  <hr/>
-  <div style="clear:both">&nbsp;</div>
-  <form action="update" method="post">
-        {% if tg.config.get('auth.method', 'local') == 'local' %}
-        <label class="grid-4">Display Name</label>
-        <div class="grid-18">
-          <input name="preferences.display_name" value="{{c.user.display_name}}" type="text">
-        </div>
-        {% endif %}
-        <label class="grid-4">Email Format</label>
-        <div class="grid-18">
-          <select name="preferences.email_format">
-            <option value="plain" {{'selected' if c.user.preferences.email_format == 'plain' else ''}}>Plain Text</option>
-            <option value="html" {{'selected' if c.user.preferences.email_format == 'html' else ''}}>HTML</option>
-            <option value="both" {{'selected' if c.user.preferences.email_format == 'both' else ''}}>Combined</option>
-          </select>
-        </div>
-        {% if tg.config.get('auth.method', 'local') == 'local' %}
-        <label class="grid-4">Page Size</label>
-        <div class="grid-18">
-          <select name="preferences.results_per_page">
-            {% for per_page in [25, 50, 100, 250] %}
-                <option {% if per_page == c.user.preferences.results_per_page %}selected="selected"{% endif %}
-                   value="{{per_page}}">{{per_page}}</option>
-            {% endfor %}
-          </select>
-        </div>
-        {% endif %}
-
-    {% if tg.config.get('auth.method', 'local') == 'local' %}
-      {% for a in c.user.email_addresses %}
-        <input name="addr-{{loop.index0}}.ord" value="{{loop.index0}}" type="hidden"/>
-      {% endfor %}
-      {% if c.user.email_addresses %}
-        <h3 class="grid-18">Email Addresses</h3>
-        <table class="grid-18">
-          <tr>
-            <th>Primary?</th>
-            <th>Address</th>
-            <th>Confirmed</th>
-            <th></th>
-          </tr>
-          {% for a in c.user.email_addresses %}
-          <tr>
-            {% set obj = c.user.address_object(a) %}
-            <td>{{lib.radio_button('primary_addr', None, a, c.user.preferences.email_address)}}</td>
-            <td>{{a}}</td>
-            {% if obj %}
-            <td>
-              {% if obj.confirmed %}
-                yes
-              {% else %}
-                no (<a href="{{g.url('/auth/send_verification_link', a=a)}}">verify</a>)
-              {% endif %}
-            </td>
-            {% else %}
-              <td>Unknown addr obj {{a}}</td>
-            {% endif %}
-            <td>{{lib.submit_button('Delete', 'addr-%s.delete' % i)}}</td>
-          </tr>
-          {% endfor %}
-        </table>
-        {% endif %}
-        <div class="grid-18">
-        {{lib.text_field('new_addr.addr', 'New Email Address')}}
-        {{lib.submit_button('Claim Address', name='new_addr.claim')}}
-        </div>
-
-        {% if c.user.open_ids %}
-        <h3 class="grid-18">OpenIDs Claimed</h3>
-        <table class="grid-18">
-          <tr>
-            <th>OpenID</th>
-            <th></th>
-          </tr>
-          {% for oid in c.user.open_ids %}
-            {% set obj = c.user.openid_object(oid) %}
-          <tr>
-            <td>{{oid}}</td>
-            <td>{{lib.submit_button('Delete', 'oid-%s.delete' % loop.index0)}}</td>
-          </tr>
-          {% endfor %}
-        </table>
-        {% endif %}
-        <div class="grid-18">
-        <a href="/auth/claim_oid">Claim New OpenID</a>
-        </div>
-    {% endif %}
-    <div class="grid-18">
-    {{lib.submit_button('Save Changes')}}
-    </div>
-  </form>
-{% endblock %}


[07/42] git commit: [5453] Fixed bug in user stats

Posted by br...@apache.org.
[5453] Fixed bug in user stats


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/102d0e7a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/102d0e7a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/102d0e7a

Branch: refs/heads/db/5453
Commit: 102d0e7a4835a6843b533fcc19bc8e358290c84a
Parents: 2ef57f0
Author: Stefano Invernizzi <st...@apache.org>
Authored: Fri Jan 11 17:19:53 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:30 2013 +0000

----------------------------------------------------------------------
 ForgeUserStats/forgeuserstats/model/stats.py |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/102d0e7a/ForgeUserStats/forgeuserstats/model/stats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/stats.py b/ForgeUserStats/forgeuserstats/model/stats.py
index d196709..0dcf5af 100644
--- a/ForgeUserStats/forgeuserstats/model/stats.py
+++ b/ForgeUserStats/forgeuserstats/model/stats.py
@@ -182,7 +182,7 @@ class UserStats(MappedClass):
         if i is None:
             return dict(created=0, modified=0)
         cat = self.general[i]
-        j = getElementIndex(cat.messages, art_type = art_type)
+        j = getElementIndex(cat.messages, messagetype = art_type)
         if j is None:
             return dict(created=0, modified=0)
         return dict(created=cat.messages[j].created, modified=cat.messages[j].modified)


[27/42] git commit: Fix pylons globals imports

Posted by br...@apache.org.
Fix pylons globals imports


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/0a7c1195
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/0a7c1195
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/0a7c1195

Branch: refs/heads/db/5453
Commit: 0a7c119505086399b1da07015fc52ece8f51b670
Parents: 597b1b3
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Mon Feb 25 21:55:33 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:32 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/contrib_stats.py               |    3 ++-
 .../forgeuserstats/controllers/userstats.py        |    2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/0a7c1195/Allura/allura/model/contrib_stats.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/contrib_stats.py b/Allura/allura/model/contrib_stats.py
index d9cada2..4d9e1e9 100644
--- a/Allura/allura/model/contrib_stats.py
+++ b/Allura/allura/model/contrib_stats.py
@@ -1,5 +1,6 @@
 import pymongo
-from pylons import c, g, request
+from pylons import tmpl_context as c, app_globals as g
+from pylons import request
 
 import bson
 from ming import schema as S

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/0a7c1195/ForgeUserStats/forgeuserstats/controllers/userstats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/controllers/userstats.py b/ForgeUserStats/forgeuserstats/controllers/userstats.py
index c77dded..40ad92c 100644
--- a/ForgeUserStats/forgeuserstats/controllers/userstats.py
+++ b/ForgeUserStats/forgeuserstats/controllers/userstats.py
@@ -5,7 +5,7 @@ from allura.controllers import BaseController
 import allura.model as M
 from allura.lib.graphics.graphic_methods import create_histogram, create_progress_bar
 from forgeuserstats.model.stats import UserStats
-from pylons import c
+from pylons import tmpl_context as c
 
 class ForgeUserStatsController(BaseController):
 


[16/42] git commit: [5453] Removed loops through entry points

Posted by br...@apache.org.
[5453] Removed loops through entry points


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/0bd06174
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/0bd06174
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/0bd06174

Branch: refs/heads/db/5453
Commit: 0bd0617490010e4e8e51ab886e37713f32241f99
Parents: dd02cc2
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sat Jan 19 17:29:34 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:31 2013 +0000

----------------------------------------------------------------------
 Allura/allura/controllers/root.py |    6 +++---
 Allura/allura/model/auth.py       |   21 +++++++++------------
 2 files changed, 12 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/0bd06174/Allura/allura/controllers/root.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/root.py b/Allura/allura/controllers/root.py
index 859394b..a387b98 100644
--- a/Allura/allura/controllers/root.py
+++ b/Allura/allura/controllers/root.py
@@ -69,9 +69,9 @@ class RootController(WsgiDispatchController):
         if n and not n.url_prefix.startswith('//'):
             n.bind_controller(self)
         self.browse = ProjectBrowseController()
-        for ep in pkg_resources.iter_entry_points("allura.stats"):
-            if ep.name.lower() == 'userstats' and g.show_userstats:
-                setattr(self, ep.name.lower(), ep.load()().root)
+        ep = g.entry_points["stats"].get('userstats')
+        if ep and g.show_userstats:
+            self.userstats = ep().root
         super(RootController, self).__init__()
 
     def _setup_request(self):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/0bd06174/Allura/allura/model/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index 9ed7067..23f5366 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -36,13 +36,6 @@ from .timeline import ActivityNode, ActivityObject
 
 log = logging.getLogger(__name__)
 
-#This is just to keep the UserStats module completely optional
-has_user_stats_module = False
-for ep in iter_entry_points("allura.stats"):
-    if ep.name.lower() == 'userstats':
-        from forgeuserstats.model.stats import UserStats
-        has_user_stats_module = True
-
 def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
     """
     Returns a bytestring version of 's', encoded as specified in 'encoding'.
@@ -340,16 +333,20 @@ class User(MappedClass, ActivityNode, ActivityObject):
         comment=str)])
 
     #Statistics
-    if has_user_stats_module:
-        stats_id = ForeignIdProperty('UserStats', if_missing=None)
-        stats = RelationProperty('UserStats', via='stats_id')
-    else:
-        stats_id = FieldProperty(S.ObjectId, if_missing=None)
+    stats_id = FieldProperty(S.ObjectId, if_missing=None)
 
     @property
     def activity_name(self):
         return self.display_name or self.username
 
+    @property
+    def stats(self):
+        if g.show_userstats:
+            from forgeuserstats.model.stats import UserStats
+            return UserStats.query.get(_id=self.stats_id)
+        else: 
+            return None
+
     def get_pref(self, pref_name):
         return plugin.UserPreferencesProvider.get().get_pref(self, pref_name)
 


[34/42] git commit: [#5453] Avoid conflict with mongo's built-in stats collection

Posted by br...@apache.org.
[#5453] Avoid conflict with mongo's built-in stats collection


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/63db556f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/63db556f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/63db556f

Branch: refs/heads/db/5453
Commit: 63db556f692f22f308c91da7ea88b487f90fbb5d
Parents: a01a110
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Mar 5 21:45:38 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:33 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/contrib_stats.py |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/63db556f/Allura/allura/model/contrib_stats.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/contrib_stats.py b/Allura/allura/model/contrib_stats.py
index 4d9e1e9..adc36db 100644
--- a/Allura/allura/model/contrib_stats.py
+++ b/Allura/allura/model/contrib_stats.py
@@ -16,7 +16,7 @@ from allura.lib import helpers as h
 
 class Stats(MappedClass):
     class __mongometa__:
-        name='stats'
+        name='userstats'
         session = main_orm_session
         unique_indexes = [ '_id']
 


[20/42] git commit: [5453] correct typo error

Posted by br...@apache.org.
[5453] correct typo error


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/32746773
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/32746773
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/32746773

Branch: refs/heads/db/5453
Commit: 3274677325e45dcc4992aa1d19d68a1754879a68
Parents: 04bc59f
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Jan 30 17:25:36 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:31 2013 +0000

----------------------------------------------------------------------
 ForgeUserStats/forgeuserstats/templates/index.html |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/32746773/ForgeUserStats/forgeuserstats/templates/index.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/index.html b/ForgeUserStats/forgeuserstats/templates/index.html
index 6d6a25b..abcb5de 100644
--- a/ForgeUserStats/forgeuserstats/templates/index.html
+++ b/ForgeUserStats/forgeuserstats/templates/index.html
@@ -338,7 +338,7 @@
     </table>
 
     {% if categories %}
-        <h2>Prefered categories</h2>
+        <h2>Preferred categories</h2>
         <p>
           The following table shows the number projects tagged as belonging to each single category in which this user is involved.
         </p>


[38/42] git commit: [#5453] make tool_label match, so tool sorting is blocked when userstats is installed as an anchored tool

Posted by br...@apache.org.
[#5453] make tool_label match, so tool sorting is blocked when userstats is installed as an anchored tool


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/eaa4420d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/eaa4420d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/eaa4420d

Branch: refs/heads/db/5453
Commit: eaa4420d401f61a8110267121db280a1daf02267
Parents: ddbfd02
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Thu Mar 28 14:07:28 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:34 2013 +0000

----------------------------------------------------------------------
 ForgeUserStats/forgeuserstats/main.py |    6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/eaa4420d/ForgeUserStats/forgeuserstats/main.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/main.py b/ForgeUserStats/forgeuserstats/main.py
index acc196d..194c7d6 100644
--- a/ForgeUserStats/forgeuserstats/main.py
+++ b/ForgeUserStats/forgeuserstats/main.py
@@ -45,7 +45,7 @@ class UserStatsListener(EventsListener):
         if not stats:
             stats = UserStats.create(user)
 
-        if event_type == "assigned": 
+        if event_type == "assigned":
             stats.addAssignedTicket(ticket.mod_date, project)
         elif event_type == "revoked":
             stats.addRevokedTicket(ticket.mod_date, project)
@@ -71,7 +71,7 @@ class UserStatsListener(EventsListener):
 
 class ForgeUserStatsApp(Application):
     __version__ = version.__version__
-    tool_label='Stats'
+    tool_label='UserStats'
     default_mount_label='Stats'
     default_mount_point='stats'
     permissions = ['configure', 'read', 'write',
@@ -128,7 +128,7 @@ class ForgeUserStatsApp(Application):
         return links
 
     def install(self, project):
-        #It doesn't make any sense to install the tool twice on the same 
+        #It doesn't make any sense to install the tool twice on the same
         #project therefore, if it already exists, it doesn't install it
         #a second time.
         for tool in project.app_configs:


[41/42] git commit: [#5453] Remove old stats.py file, improve userstats code

Posted by br...@apache.org.
[#5453] Remove old stats.py file, improve userstats code


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/08a31a1e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/08a31a1e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/08a31a1e

Branch: refs/heads/db/5453
Commit: 08a31a1e96224f09d0763c21daccbb9cdd2d092d
Parents: 74dca41
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sun Mar 24 11:00:39 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:34 2013 +0000

----------------------------------------------------------------------
 Allura/allura/controllers/auth.py                  |   12 -
 Allura/allura/controllers/site_admin.py            |   24 -
 .../ext/user_profile/templates/user_index.html     |    9 -
 Allura/allura/lib/plugin.py                        |   14 +
 Allura/allura/model/contrib_stats.py               |  637 --------------
 Allura/allura/model/stats.py                       |  640 ++++++++++++++-
 Allura/allura/templates/site_admin.html            |    1 -
 Allura/allura/tests/functional/test_site_admin.py  |   15 -
 ForgeUserStats/forgeuserstats/model/stats.py       |    9 +-
 .../data/testgit.git/hooks/applypatch-msg.sample   |   15 -
 .../tests/data/testgit.git/hooks/commit-msg.sample |   24 -
 .../data/testgit.git/hooks/post-commit.sample      |    8 -
 .../tests/data/testgit.git/hooks/post-receive      |    1 -
 .../data/testgit.git/hooks/post-receive.sample     |   15 -
 .../data/testgit.git/hooks/post-update.sample      |    8 -
 .../data/testgit.git/hooks/pre-applypatch.sample   |   14 -
 .../tests/data/testgit.git/hooks/pre-commit.sample |   46 -
 .../tests/data/testgit.git/hooks/pre-rebase.sample |  169 ----
 .../testgit.git/hooks/prepare-commit-msg.sample    |   36 -
 .../tests/data/testgit.git/hooks/update            |    1 -
 .../tests/data/testgit.git/hooks/update.sample     |  128 ---
 21 files changed, 652 insertions(+), 1174 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/Allura/allura/controllers/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index 1b9c74e..8e18767 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -726,18 +726,6 @@ class SubscriptionsController(BaseController):
     @h.vardec
     @expose()
     @require_post()
-    def upload_sshkey(self, key=None):
-        ap = plugin.AuthenticationProvider.get(request)
-        try:
-            ap.upload_sshkey(c.user.username, key)
-        except AssertionError, ae:
-            flash('Error uploading key: %s' % ae, 'error')
-        flash('Key uploaded')
-        redirect('.')
-
-    @h.vardec
-    @expose()
-    @require_post()
     @validate(F.subscription_form, error_handler=index)
     def update_subscriptions(self, subscriptions=None, **kw):
         for s in subscriptions:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/Allura/allura/controllers/site_admin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/site_admin.py b/Allura/allura/controllers/site_admin.py
index 096aba7..6d8fdd8 100644
--- a/Allura/allura/controllers/site_admin.py
+++ b/Allura/allura/controllers/site_admin.py
@@ -53,30 +53,6 @@ class SiteAdminController(object):
         neighborhoods.sort(key=lambda n:n[0])
         return dict(neighborhoods=neighborhoods)
 
-    @expose('jinja:allura:templates/site_admin_stats.html')
-    @without_trailing_slash
-    def stats(self, limit=25):
-        stats = defaultdict(lambda:defaultdict(list))
-        agg_timings = defaultdict(list)
-        for doc in M.Stats.m.find():
-            if doc.url.startswith('/_debug'): continue
-            doc_stats = stats[doc.url]
-            for t,val in doc.timers.iteritems():
-                doc_stats[t].append(val)
-                agg_timings[t].append(val)
-        for url, timings in stats.iteritems():
-            new_timings = dict(
-                (timer, round(sum(readings)/len(readings),3))
-                for timer, readings in timings.iteritems())
-            timings.update(new_timings)
-        agg_timings = dict(
-            (timer, round(sum(readings)/len(readings),3))
-            for timer, readings in agg_timings.iteritems())
-        stats = sorted(stats.iteritems(), key=lambda x:-x[1]['total'])
-        return dict(
-            agg_timings=agg_timings,
-            stats=stats[:int(limit)])
-
     @expose('jinja:allura:templates/site_admin_api_tickets.html')
     @without_trailing_slash
     def api_tickets(self, **data):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/Allura/allura/ext/user_profile/templates/user_index.html
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/user_profile/templates/user_index.html b/Allura/allura/ext/user_profile/templates/user_index.html
index 2b17b79..2614953 100644
--- a/Allura/allura/ext/user_profile/templates/user_index.html
+++ b/Allura/allura/ext/user_profile/templates/user_index.html
@@ -236,15 +236,6 @@
     </div>
   </div>
 
-  {% if user.stats.visible %}
-    <div class="grid-24">
-      <div class="grid-24" style="margin:0;"><b>User statistics</b></div>
-      <div class="grid-24" style="margin-top:5px;margin-bottom:5px;">
-        <div><a href="{{c.project.url()}}userstats"/>Go to the personal statistics of this user</a></div>
-      </div>
-    </div>
-  {% endif %}
-
   {% if c.user.username == user.username %}
       <div class="address-list grid-18">
         <b>Email Addresses</b>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/Allura/allura/lib/plugin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index ed704d4..efc5d07 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -174,6 +174,15 @@ class AuthenticationProvider(object):
     def update_notifications(self, user):
         raise NotImplemented, 'update_notifications'
 
+    def user_registration_date(self, user):
+        '''
+        Returns the date in which a user registered himself/herself on the forge.
+
+        :param user: a :class:`User <allura.model.auth.User>`
+        :rtype: :class:`datetime <datetime.datetime>`
+        '''
+        raise NotImplementedError, 'user_registration_date'
+
 class LocalAuthenticationProvider(AuthenticationProvider):
     '''
     Stores user passwords on the User model, in mongo.  Uses per-user salt and
@@ -230,6 +239,11 @@ class LocalAuthenticationProvider(AuthenticationProvider):
     def update_notifications(self, user):
         return ''
 
+    def user_registration_date(self, user):
+        if user._id:
+            return user._id.generation_time
+        return datetime.utcnow()
+
 class LdapAuthenticationProvider(AuthenticationProvider):
     def register_user(self, user_doc):
         from allura import model as M

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/Allura/allura/model/contrib_stats.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/contrib_stats.py b/Allura/allura/model/contrib_stats.py
deleted file mode 100644
index 3799ea5..0000000
--- a/Allura/allura/model/contrib_stats.py
+++ /dev/null
@@ -1,637 +0,0 @@
-import pymongo
-from pylons import tmpl_context as c, app_globals as g
-from pylons import request
-
-import bson
-from ming import schema as S
-from ming import Field, Index, collection
-from ming.orm import session, state, Mapper
-from ming.orm import FieldProperty
-from ming.orm.declarative import MappedClass
-from datetime import datetime, timedelta
-import difflib
-
-from allura.model.session import main_orm_session
-from allura.lib import helpers as h
-
-class Stats(MappedClass):
-    class __mongometa__:
-        name='basestats'
-        session = main_orm_session
-        unique_indexes = [ '_id']
-
-    _id=FieldProperty(S.ObjectId)
-
-    visible = FieldProperty(bool, if_missing = True)
-    registration_date = FieldProperty(datetime)
-    general = FieldProperty([dict(
-        category = S.ObjectId,
-        messages = [dict(
-            messagetype = str,
-            created = int,
-            modified = int)],
-        tickets = dict(
-            solved = int,
-            assigned = int,
-            revoked = int,
-            totsolvingtime = int),
-        commits = [dict(
-            lines = int,
-            number = int,
-            language = S.ObjectId)])])
-
-    lastmonth=FieldProperty(dict(
-        messages=[dict(
-            datetime=datetime,
-            created=bool,
-            categories=[S.ObjectId],
-            messagetype=str)],
-        assignedtickets=[dict(
-            datetime=datetime,
-            categories=[S.ObjectId])],
-        revokedtickets=[dict(
-            datetime=datetime,
-            categories=[S.ObjectId])],
-        solvedtickets=[dict(
-            datetime=datetime,
-            categories=[S.ObjectId],
-            solvingtime=int)],
-        commits=[dict(
-            datetime=datetime,
-            categories=[S.ObjectId],
-            programming_languages=[S.ObjectId],
-            lines=int)]))
-
-    def getCodeContribution(self):
-        days=(datetime.today() - self.registration_date).days
-        if not days:
-            days=1
-        for val in self['general']:
-            if val['category'] is None:
-                for commits in val['commits']:
-                    if commits['language'] is None: 
-                        if days > 30:
-                            return round(float(commits.lines)/days*30, 2)
-                        else:
-                            return float(commits.lines)
-        return 0
-
-    def getDiscussionContribution(self):
-        days=(datetime.today() - self.registration_date).days
-        if not days:
-            days=1
-        for val in self['general']:
-            if val['category'] is None:
-                for artifact in val['messages']:
-                    if artifact['messagetype'] is None: 
-                        tot = artifact.created+artifact.modified
-                        if days > 30:
-                            return round(float(tot)/days*30,2)
-                        else:
-                            return float(tot)
-        return 0
-
-    def getTicketsContribution(self):
-        for val in self['general']:
-            if val['category'] is None:
-                tickets = val['tickets']
-                if tickets.assigned == 0:
-                    return 0
-                return float(tickets.solved) / tickets.assigned
-        return 0
-
-    @classmethod
-    def getMaxAndAverageCodeContribution(self):
-        lst = list(self.query.find())
-        n = len(lst)
-        if n == 0:
-            return 0, 0
-        maxcontribution=max([x.getCodeContribution() for x in lst])
-        averagecontribution=sum([x.getCodeContribution() for x in lst]) / n
-        return maxcontribution, round(averagecontribution, 2)
-
-    @classmethod
-    def getMaxAndAverageDiscussionContribution(self):
-        lst = list(self.query.find())
-        n = len(lst)
-        if n == 0:
-            return 0, 0
-        maxcontribution=max([x.getDiscussionContribution() for x in lst])
-        averagecontribution=sum([x.getDiscussionContribution() for x in lst])/n
-        return maxcontribution, round(averagecontribution, 2)
-
-    @classmethod
-    def getMaxAndAverageTicketsSolvingPercentage(self):
-        lst = list(self.query.find())
-        n = len(lst)
-        if n == 0:
-            return 0, 0
-        maxcontribution=max([x.getTicketsContribution() for x in lst])
-        averagecontribution=sum([x.getTicketsContribution() for x in lst])/n
-        return maxcontribution, round(averagecontribution, 2)
-
-    def codeRanking(self):
-        lst = list(self.query.find())
-        totn = len(lst)
-        codcontr = self.getCodeContribution()
-        upper = len([x for x in lst if x.getCodeContribution() > codcontr])
-        return round((totn - upper) * 100.0 / totn, 2)
-
-    def discussionRanking(self):
-        lst = list(self.query.find())
-        totn = len(lst)
-        disccontr = self.getDiscussionContribution()
-        upper=len([x for x in lst if x.getDiscussionContribution()>disccontr])
-        return round((totn - upper) * 100.0 / totn, 2)
-
-    def ticketsRanking(self):
-        lst = list(self.query.find())
-        totn = len(lst)
-        ticketscontr = self.getTicketsContribution()
-        upper=len([x for x in lst if x.getTicketsContribution()>ticketscontr])
-        return round((totn - upper) * 100.0 / totn, 2)
-
-    def getCommits(self, category = None):
-        i = getElementIndex(self.general, category = category)
-        if i is None: 
-            return dict(number=0, lines=0)
-        cat = self.general[i]
-        j = getElementIndex(cat.commits, language = None)
-        if j is None:
-            return dict(number=0, lines=0)
-        return dict(
-            number=cat.commits[j]['number'], 
-            lines=cat.commits[j]['lines'])
-
-    def getArtifacts(self, category = None, art_type = None):
-        i = getElementIndex(self.general, category = category)
-        if i is None:
-            return dict(created=0, modified=0)
-        cat = self.general[i]
-        j = getElementIndex(cat.messages, messagetype = art_type)
-        if j is None:
-            return dict(created=0, modified=0)
-        return dict(created=cat.messages[j].created, modified=cat.messages[j].modified)
-
-    def getTickets(self, category = None):
-        i = getElementIndex(self.general, category = category)
-        if i is None:
-            return dict(
-                assigned=0,
-                solved=0,
-                revoked=0,
-                averagesolvingtime=None)
-        if self.general[i].tickets.solved > 0:
-            tot = self.general[i].tickets.totsolvingtime 
-            number = self.general[i].tickets.solved
-            average = tot / number
-        else: 
-            average = None
-        return dict(
-            assigned=self.general[i].tickets.assigned,
-            solved=self.general[i].tickets.solved,
-            revoked=self.general[i].tickets.revoked,
-            averagesolvingtime=_convertTimeDiff(average))
-
-    def getCommitsByCategory(self):
-        from allura.model.project import TroveCategory
-
-        by_cat = {}
-        for entry in self.general:
-            cat = entry.category
-            i = getElementIndex(entry.commits, language = None)
-            if i is None: 
-                n, lines = 0, 0
-            else: 
-                n, lines = entry.commits[i].number, entry.commits[i].lines
-            if cat != None:
-                cat = TroveCategory.query.get(_id = cat)
-            by_cat[cat] = dict(number=n, lines=lines)
-        return by_cat
-
-    #For the moment, commit stats by language are not used, since each project
-    #can be linked to more than one programming language and we don't know how
-    #to which programming language should be credited a line of code modified
-    #within a project including two or more languages.
-    def getCommitsByLanguage(self):
-        langlist = []
-        by_lang = {}
-        i = getElementIndex(self.general, category=None)
-        if i is None: 
-            return dict(number=0, lines=0)
-        return dict([(el.language, dict(lines=el.lines, number=el.number))
-                     for el in self.general[i].commits])
-
-    def getArtifactsByCategory(self, detailed=False):
-        from allura.model.project import TroveCategory
-
-        by_cat = {}
-        for entry in self.general:
-            cat = entry.category
-            if cat != None: 
-                cat = TroveCategory.query.get(_id = cat)
-            if detailed: 
-                by_cat[cat] = entry.messages
-            else:
-                i = getElementIndex(entry.messages, messagetype=None)
-                if i is not None:
-                    by_cat[cat] = entry.messages[i]
-                else: 
-                    by_cat[cat] = dict(created=0, modified=0)
-        return by_cat
-
-    def getArtifactsByType(self, category=None):
-        i = getElementIndex(self.general, category = category)
-        if i is None: 
-            return {}
-        entry = self.general[i].messages
-        by_type = dict([(el.messagetype, dict(created=el.created,
-                                              modified=el.modified))
-                         for el in entry])
-        return by_type
-
-    def getTicketsByCategory(self):
-        from allura.model.project import TroveCategory
-
-        by_cat = {}
-        for entry in self.general:
-            cat = entry.category
-            if cat != None:
-                cat = TroveCategory.query.get(_id = cat)
-            a, s = entry.tickets.assigned, entry.tickets.solved
-            r, time = entry.tickets.solved, entry.tickets.totsolvingtime
-            if s:
-                average = time / s
-            else:
-                average = None
-            by_cat[cat] = dict(
-                assigned=a,
-                solved=s,
-                revoked=r, 
-                averagesolvingtime=_convertTimeDiff(average))
-        return by_cat
-
-    def getLastMonthCommits(self, category = None):
-        self.checkOldArtifacts() 
-        lineslist = [el.lines for el in self.lastmonth.commits
-                     if category in el.categories + [None]]
-        return dict(number=len(lineslist), lines=sum(lineslist))
-
-    def getLastMonthCommitsByCategory(self):
-        from allura.model.project import TroveCategory
-
-        self.checkOldArtifacts() 
-        seen = set()
-        catlist=[el.category for el in self.general
-                 if el.category not in seen and not seen.add(el.category)]
-
-        by_cat = {}
-        for cat in catlist:
-            lineslist = [el.lines for el in self.lastmonth.commits
-                         if cat in el.categories + [None]]
-            n = len(lineslist)
-            lines = sum(lineslist)
-            if cat != None:
-                cat = TroveCategory.query.get(_id = cat)
-            by_cat[cat] = dict(number=n, lines=lines)
-        return by_cat
-
-    def getLastMonthCommitsByLanguage(self):
-        from allura.model.project import TroveCategory
-
-        self.checkOldArtifacts() 
-        seen = set()
-        langlist=[el.language for el in self.general
-                  if el.language not in seen and not seen.add(el.language)]
-
-        by_lang = {}
-        for lang in langlist:
-            lineslist = [el.lines for el in self.lastmonth.commits
-                         if lang in el.programming_languages + [None]]
-            n = len(lineslist)
-            lines = sum(lineslist)
-            if lang != None:
-                lang = TroveCategory.query.get(_id = lang)
-            by_lang[lang] = dict(number=n, lines=lines)
-        return by_lang
-
-    def getLastMonthArtifacts(self, category = None, art_type = None):
-        self.checkOldArtifacts() 
-        cre, mod = reduce(
-            addtuple, 
-            [(int(el.created),1-int(el.created))
-                for el in self.lastmonth.messages
-                if (category is None or category in el.categories) and 
-                (el.messagetype == art_type or art_type is None)], 
-            (0,0))
-        return dict(created=cre, modified=mod)
-
-    def getLastMonthArtifactsByType(self, category = None):
-        self.checkOldArtifacts()
-        seen = set()
-        types=[el.messagetype for el in self.lastmonth.messages
-               if el.messagetype not in seen and not seen.add(el.messagetype)]
-
-        by_type = {}
-        for t in types:
-            cre, mod = reduce(
-                addtuple, 
-                [(int(el.created),1-int(el.created))
-                 for el in self.lastmonth.messages
-                 if el.messagetype == t and
-                 category in [None]+el.categories],
-                (0,0))
-            by_type[t] = dict(created=cre, modified=mod)
-        return by_type
-
-    def getLastMonthArtifactsByCategory(self):
-        from allura.model.project import TroveCategory
-
-        self.checkOldArtifacts() 
-        seen = set()
-        catlist=[el.category for el in self.general
-                 if el.category not in seen and not seen.add(el.category)]
-
-        by_cat = {}
-        for cat in catlist:
-            cre, mod = reduce(
-                addtuple, 
-                [(int(el.created),1-int(el.created))
-                 for el in self.lastmonth.messages 
-                 if cat in el.categories + [None]], (0,0))
-            if cat != None:
-                cat = TroveCategory.query.get(_id = cat)
-            by_cat[cat] = dict(created=cre, modified=mod)
-        return by_cat
-
-    def getLastMonthTickets(self, category = None):
-        from allura.model.project import TroveCategory
-
-        self.checkOldArtifacts()
-        a = len([el for el in self.lastmonth.assignedtickets
-                 if category in el.categories + [None]])
-        r = len([el for el in self.lastmonth.revokedtickets
-                 if category in el.categories + [None]])
-        s, time = reduce(
-            addtuple, 
-            [(1, el.solvingtime)
-             for el in self.lastmonth.solvedtickets
-             if category in el.categories + [None]],
-            (0,0))
-        if category!=None:
-            category = TroveCategory.query.get(_id=category)
-        if s > 0:
-            time = time / s
-        else:
-            time = None
-        return dict(
-            assigned=a,
-            revoked=r,
-            solved=s, 
-            averagesolvingtime=_convertTimeDiff(time))
-        
-    def getLastMonthTicketsByCategory(self):
-        from allura.model.project import TroveCategory
-
-        self.checkOldArtifacts()
-        seen = set()
-        catlist=[el.category for el in self.general
-                 if el.category not in seen and not seen.add(el.category)]
-        by_cat = {}
-        for cat in catlist:
-            a = len([el for el in self.lastmonth.assignedtickets
-                     if cat in el.categories + [None]])
-            r = len([el for el in self.lastmonth.revokedtickets
-                     if cat in el.categories + [None]])
-            s, time = reduce(addtuple, [(1, el.solvingtime)
-                                        for el in self.lastmonth.solvedtickets
-                                        if cat in el.categories+[None]],(0,0))
-            if cat != None:
-                cat = TroveCategory.query.get(_id = cat)
-            if s > 0: 
-                time = time / s
-            else:
-                time = None
-            by_cat[cat] = dict(
-                assigned=a,
-                revoked=r,
-                solved=s, 
-                averagesolvingtime=_convertTimeDiff(time))
-        return by_cat
-        
-    def checkOldArtifacts(self):
-        now = datetime.utcnow()
-        for m in self.lastmonth.messages:
-            if now - m.datetime > timedelta(30):
-                self.lastmonth.messages.remove(m)
-        for t in self.lastmonth.assignedtickets:
-            if now - t.datetime > timedelta(30):
-                self.lastmonth.assignedtickets.remove(t)
-        for t in self.lastmonth.revokedtickets:
-            if now - t.datetime > timedelta(30):
-                self.lastmonth.revokedtickets.remove(t)
-        for t in self.lastmonth.solvedtickets:
-            if now - t.datetime > timedelta(30):
-                self.lastmonth.solvedtickets.remove(t)
-        for c in self.lastmonth.commits:
-            if now - c.datetime > timedelta(30):
-                self.lastmonth.commits.remove(c)
-
-    def addNewArtifact(self, art_type, art_datetime, project):
-        self._updateArtifactsStats(art_type, art_datetime, project, "created")
-
-    def addModifiedArtifact(self, art_type, art_datetime, project):
-        self._updateArtifactsStats(art_type, art_datetime, project, "modified")
-
-    def addAssignedTicket(self, ticket_datetime, project):
-        topics = [t for t in project.trove_topic if t]
-        self._updateTicketsStats(topics, 'assigned')
-        self.lastmonth.assignedtickets.append(
-            dict(datetime=ticket_datetime, categories=topics))
-
-    def addRevokedTicket(self, ticket_datetime, project):
-        topics = [t for t in project.trove_topic if t]
-        self._updateTicketsStats(topics, 'revoked')
-        self.lastmonth.revokedtickets.append(
-            dict(datetime=ticket_datetime, categories=topics))
-        self.checkOldArtifacts()
-
-    def addClosedTicket(self, open_datetime, close_datetime, project):
-        topics = [t for t in project.trove_topic if t]
-        s_time=int((close_datetime-open_datetime).total_seconds())
-        self._updateTicketsStats(topics, 'solved', s_time = s_time)
-        self.lastmonth.solvedtickets.append(dict(
-            datetime=close_datetime,
-            categories=topics,
-            solvingtime=s_time))
-        self.checkOldArtifacts()
-
-    def addCommit(self, newcommit, commit_datetime, project):
-        def _computeLines(newblob, oldblob = None):
-            if oldblob:
-                listold = list(oldblob)
-            else:
-                listold = []
-            if newblob:
-                listnew = list(newblob)
-            else:
-                listnew = []
-
-            if oldblob is None:
-                lines = len(listnew)
-            elif newblob and newblob.has_html_view:
-                diff = difflib.unified_diff(
-                    listold, listnew,
-                    ('old' + oldblob.path()).encode('utf-8'),
-                    ('new' + newblob.path()).encode('utf-8'))
-                lines = len([l for l in diff if len(l) > 0 and l[0] == '+'])-1
-            else:
-                lines = 0
-            return lines
-
-        def _addCommitData(stats, topics, languages, lines):          
-            lt = topics + [None]
-            ll = languages + [None]
-            for t in lt:
-                i = getElementIndex(stats.general, category=t) 
-                if i is None:
-                    newstats = dict(
-                        category=t,
-                        commits=[],
-                        messages=[],
-                        tickets=dict(
-                            assigned=0,
-                            solved=0,
-                            revoked=0,
-                            totsolvingtime=0))
-                    stats.general.append(newstats)
-                    i = getElementIndex(stats.general, category=t)
-                for lang in ll:
-                    j = getElementIndex(
-                        stats.general[i]['commits'], language=lang)
-                    if j is None:
-                        stats.general[i]['commits'].append(dict(
-                            language=lang, lines=lines, number=1))
-                    else:
-                        stats.general[i]['commits'][j].lines += lines
-                        stats.general[i]['commits'][j].number += 1
-
-        topics = [t for t in project.trove_topic if t]
-        languages = [l for l in project.trove_language if l]
-
-        d = newcommit.diffs
-        if len(newcommit.parent_ids) > 0:
-            oldcommit = newcommit.repo.commit(newcommit.parent_ids[0])
-
-        totlines = 0
-        for changed in d.changed:
-            newblob = newcommit.tree.get_blob_by_path(changed)
-            oldblob = oldcommit.tree.get_blob_by_path(changed)
-            totlines+=_computeLines(newblob, oldblob)
-
-        for copied in d.copied:
-            newblob = newcommit.tree.get_blob_by_path(copied['new'])
-            oldblob = oldcommit.tree.get_blob_by_path(copied['old'])
-            totlines+=_computeLines(newblob, oldblob)
-
-        for added in d.added:
-            newblob = newcommit.tree.get_blob_by_path(added)
-            totlines+=_computeLines(newblob)
-
-        _addCommitData(self, topics, languages, totlines)
-
-        self.lastmonth.commits.append(dict(
-            datetime=commit_datetime, 
-            categories=topics, 
-            programming_languages=languages,
-            lines=totlines))
-        self.checkOldArtifacts()
-
-    def _updateArtifactsStats(self, art_type, art_datetime, project, action):
-        if action not in ['created', 'modified']: 
-            return
-        topics = [t for t in project.trove_topic if t]
-        lt = [None] + topics
-        for mtype in [None, art_type]:
-            for t in lt:
-                i = getElementIndex(self.general, category = t)
-                if i is None:
-                    msg = dict(
-                        category=t,
-                        commits=[],
-                        tickets=dict(
-                            solved=0,
-                            assigned=0,
-                            revoked=0,
-                            totsolvingtime=0),
-                        messages=[])
-                    self.general.append(msg)
-                    i = getElementIndex(self.general, category = t)
-                j = getElementIndex(
-                    self.general[i]['messages'], messagetype=mtype)
-                if j is None:
-                    entry = dict(messagetype=mtype, created=0, modified=0)
-                    entry[action] += 1
-                    self.general[i]['messages'].append(entry)
-                else:
-                    self.general[i]['messages'][j][action] += 1
-
-        self.lastmonth.messages.append(dict(
-            datetime=art_datetime,
-            created=(action == 'created'),
-            categories=topics,
-            messagetype=art_type))
-        self.checkOldArtifacts() 
-
-    def _updateTicketsStats(self, topics, action, s_time = None):
-        if action not in ['solved', 'assigned', 'revoked']:
-            return
-        lt = topics + [None]
-        for t in lt:
-            i = getElementIndex(self.general, category = t)
-            if i is None:
-                stats = dict(
-                    category=t,
-                    commits=[],
-                    tickets=dict(
-                        solved=0,
-                        assigned=0,
-                        revoked=0,
-                        totsolvingtime=0),
-                    messages=[])
-                self.general.append(stats)
-                i = getElementIndex(self.general, category = t)
-            self.general[i]['tickets'][action] += 1 
-            if action == 'solved': 
-                self.general[i]['tickets']['totsolvingtime']+=s_time
-
-def getElementIndex(el_list, **kw):
-    for i in range(len(el_list)):
-        for k in kw:
-            if el_list[i].get(k) != kw[k]:
-                break
-        else:
-            return i
-    return None
-
-def addtuple(l1, l2):
-    a, b = l1
-    x, y = l2
-    return (a+x, b+y)
-
-def _convertTimeDiff(int_seconds):
-    if int_seconds is None:
-        return None
-    diff = timedelta(seconds = int_seconds)
-    days, seconds = diff.days, diff.seconds
-    hours = seconds / 3600
-    seconds = seconds % 3600
-    minutes = seconds / 60
-    seconds = seconds % 60
-    return dict(
-        days=days, 
-        hours=hours, 
-        minutes=minutes,
-        seconds=seconds)
-
-Mapper.compile_all()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/Allura/allura/model/stats.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/stats.py b/Allura/allura/model/stats.py
index ee1f297..df4e45a 100644
--- a/Allura/allura/model/stats.py
+++ b/Allura/allura/model/stats.py
@@ -1,13 +1,637 @@
-import logging
+import pymongo
+from pylons import tmpl_context as c, app_globals as g
+from pylons import request
 
-import ming
+import bson
+from ming import schema as S
+from ming import Field, Index, collection
+from ming.orm import session, state, Mapper
+from ming.orm import FieldProperty
+from ming.orm.declarative import MappedClass
+from datetime import datetime, timedelta
+import difflib
 
-from .session import main_doc_session
+from allura.model.session import main_orm_session
+from allura.lib import helpers as h
 
-log = logging.getLogger(__name__)
-
-class Stats(ming.Document):
+class Stats(MappedClass):
     class __mongometa__:
-        session = main_doc_session
-        name='stats'
+        name='basestats'
+        session = main_orm_session
+        unique_indexes = [ '_id']
+
+    _id=FieldProperty(S.ObjectId)
+
+    visible = FieldProperty(bool, if_missing = True)
+    registration_date = FieldProperty(datetime)
+    general = FieldProperty([dict(
+        category = S.ObjectId,
+        messages = [dict(
+            messagetype = str,
+            created = int,
+            modified = int)],
+        tickets = dict(
+            solved = int,
+            assigned = int,
+            revoked = int,
+            totsolvingtime = int),
+        commits = [dict(
+            lines = int,
+            number = int,
+            language = S.ObjectId)])])
+
+    lastmonth=FieldProperty(dict(
+        messages=[dict(
+            datetime=datetime,
+            created=bool,
+            categories=[S.ObjectId],
+            messagetype=str)],
+        assignedtickets=[dict(
+            datetime=datetime,
+            categories=[S.ObjectId])],
+        revokedtickets=[dict(
+            datetime=datetime,
+            categories=[S.ObjectId])],
+        solvedtickets=[dict(
+            datetime=datetime,
+            categories=[S.ObjectId],
+            solvingtime=int)],
+        commits=[dict(
+            datetime=datetime,
+            categories=[S.ObjectId],
+            programming_languages=[S.ObjectId],
+            lines=int)]))
+
+    def getCodeContribution(self):
+        days=(datetime.today() - self.registration_date).days
+        if not days:
+            days=1
+        for val in self['general']:
+            if val['category'] is None:
+                for commits in val['commits']:
+                    if commits['language'] is None: 
+                        if days > 30:
+                            return round(float(commits.lines)/days*30, 2)
+                        else:
+                            return float(commits.lines)
+        return 0
+
+    def getDiscussionContribution(self):
+        days=(datetime.today() - self.registration_date).days
+        if not days:
+            days=1
+        for val in self['general']:
+            if val['category'] is None:
+                for artifact in val['messages']:
+                    if artifact['messagetype'] is None: 
+                        tot = artifact.created+artifact.modified
+                        if days > 30:
+                            return round(float(tot)/days*30,2)
+                        else:
+                            return float(tot)
+        return 0
+
+    def getTicketsContribution(self):
+        for val in self['general']:
+            if val['category'] is None:
+                tickets = val['tickets']
+                if tickets.assigned == 0:
+                    return 0
+                return float(tickets.solved) / tickets.assigned
+        return 0
+
+    @classmethod
+    def getMaxAndAverageCodeContribution(self):
+        res = self.query.find()
+        n = res.count()
+        if n == 0:
+            return 0, 0
+        maxcontribution=max([x.getCodeContribution() for x in res])
+        averagecontribution=sum([x.getCodeContribution() for x in res]) / n
+        return maxcontribution, round(averagecontribution, 2)
+
+    @classmethod
+    def getMaxAndAverageDiscussionContribution(self):
+        res = self.query.find()
+        n = res.count()
+        if n == 0:
+            return 0, 0
+        maxcontribution=max([x.getDiscussionContribution() for x in res])
+        averagecontribution=sum([x.getDiscussionContribution() for x in res])/n
+        return maxcontribution, round(averagecontribution, 2)
+
+    @classmethod
+    def getMaxAndAverageTicketsSolvingPercentage(self):
+        res = self.query.find()
+        n = res.count()
+        if n == 0:
+            return 0, 0
+        maxcontribution=max([x.getTicketsContribution() for x in res])
+        averagecontribution=sum([x.getTicketsContribution() for x in res])/n
+        return maxcontribution, round(averagecontribution, 2)
+
+    def codeRanking(self):
+        res = self.query.find()
+        totn = res.count()
+        codcontr = self.getCodeContribution()
+        upper = len([x for x in res if x.getCodeContribution() > codcontr])
+        return round((totn - upper) * 100.0 / totn, 2)
+
+    def discussionRanking(self):
+        res = self.query.find()
+        totn = res.count()
+        disccontr = self.getDiscussionContribution()
+        upper=len([x for x in res if x.getDiscussionContribution()>disccontr])
+        return round((totn - upper) * 100.0 / totn, 2)
+
+    def ticketsRanking(self):
+        res = self.query.find()
+        totn = res.count()
+        ticketscontr = self.getTicketsContribution()
+        upper=len([x for x in res if x.getTicketsContribution()>ticketscontr])
+        return round((totn - upper) * 100.0 / totn, 2)
+
+    def getCommits(self, category = None):
+        i = getElementIndex(self.general, category = category)
+        if i is None: 
+            return dict(number=0, lines=0)
+        cat = self.general[i]
+        j = getElementIndex(cat.commits, language = None)
+        if j is None:
+            return dict(number=0, lines=0)
+        return dict(
+            number=cat.commits[j]['number'], 
+            lines=cat.commits[j]['lines'])
+
+    def getArtifacts(self, category = None, art_type = None):
+        i = getElementIndex(self.general, category = category)
+        if i is None:
+            return dict(created=0, modified=0)
+        cat = self.general[i]
+        j = getElementIndex(cat.messages, messagetype = art_type)
+        if j is None:
+            return dict(created=0, modified=0)
+        return dict(created=cat.messages[j].created, modified=cat.messages[j].modified)
+
+    def getTickets(self, category = None):
+        i = getElementIndex(self.general, category = category)
+        if i is None:
+            return dict(
+                assigned=0,
+                solved=0,
+                revoked=0,
+                averagesolvingtime=None)
+        if self.general[i].tickets.solved > 0:
+            tot = self.general[i].tickets.totsolvingtime 
+            number = self.general[i].tickets.solved
+            average = tot / number
+        else: 
+            average = None
+        return dict(
+            assigned=self.general[i].tickets.assigned,
+            solved=self.general[i].tickets.solved,
+            revoked=self.general[i].tickets.revoked,
+            averagesolvingtime=_convertTimeDiff(average))
+
+    def getCommitsByCategory(self):
+        from allura.model.project import TroveCategory
+
+        by_cat = {}
+        for entry in self.general:
+            cat = entry.category
+            i = getElementIndex(entry.commits, language = None)
+            if i is None: 
+                n, lines = 0, 0
+            else: 
+                n, lines = entry.commits[i].number, entry.commits[i].lines
+            if cat != None:
+                cat = TroveCategory.query.get(_id = cat)
+            by_cat[cat] = dict(number=n, lines=lines)
+        return by_cat
+
+    #For the moment, commit stats by language are not used, since each project
+    #can be linked to more than one programming language and we don't know how
+    #to which programming language should be credited a line of code modified
+    #within a project including two or more languages.
+    def getCommitsByLanguage(self):
+        langlist = []
+        by_lang = {}
+        i = getElementIndex(self.general, category=None)
+        if i is None: 
+            return dict(number=0, lines=0)
+        return dict([(el.language, dict(lines=el.lines, number=el.number))
+                     for el in self.general[i].commits])
+
+    def getArtifactsByCategory(self, detailed=False):
+        from allura.model.project import TroveCategory
+
+        by_cat = {}
+        for entry in self.general:
+            cat = entry.category
+            if cat != None: 
+                cat = TroveCategory.query.get(_id = cat)
+            if detailed: 
+                by_cat[cat] = entry.messages
+            else:
+                i = getElementIndex(entry.messages, messagetype=None)
+                if i is not None:
+                    by_cat[cat] = entry.messages[i]
+                else: 
+                    by_cat[cat] = dict(created=0, modified=0)
+        return by_cat
+
+    def getArtifactsByType(self, category=None):
+        i = getElementIndex(self.general, category = category)
+        if i is None: 
+            return {}
+        entry = self.general[i].messages
+        by_type = dict([(el.messagetype, dict(created=el.created,
+                                              modified=el.modified))
+                         for el in entry])
+        return by_type
+
+    def getTicketsByCategory(self):
+        from allura.model.project import TroveCategory
+
+        by_cat = {}
+        for entry in self.general:
+            cat = entry.category
+            if cat != None:
+                cat = TroveCategory.query.get(_id = cat)
+            a, s = entry.tickets.assigned, entry.tickets.solved
+            r, time = entry.tickets.solved, entry.tickets.totsolvingtime
+            if s:
+                average = time / s
+            else:
+                average = None
+            by_cat[cat] = dict(
+                assigned=a,
+                solved=s,
+                revoked=r, 
+                averagesolvingtime=_convertTimeDiff(average))
+        return by_cat
+
+    def getLastMonthCommits(self, category = None):
+        self.checkOldArtifacts() 
+        lineslist = [el.lines for el in self.lastmonth.commits
+                     if category in el.categories + [None]]
+        return dict(number=len(lineslist), lines=sum(lineslist))
+
+    def getLastMonthCommitsByCategory(self):
+        from allura.model.project import TroveCategory
+
+        self.checkOldArtifacts() 
+        seen = set()
+        catlist=[el.category for el in self.general
+                 if el.category not in seen and not seen.add(el.category)]
+
+        by_cat = {}
+        for cat in catlist:
+            lineslist = [el.lines for el in self.lastmonth.commits
+                         if cat in el.categories + [None]]
+            n = len(lineslist)
+            lines = sum(lineslist)
+            if cat != None:
+                cat = TroveCategory.query.get(_id = cat)
+            by_cat[cat] = dict(number=n, lines=lines)
+        return by_cat
+
+    def getLastMonthCommitsByLanguage(self):
+        from allura.model.project import TroveCategory
+
+        self.checkOldArtifacts() 
+        seen = set()
+        langlist=[el.language for el in self.general
+                  if el.language not in seen and not seen.add(el.language)]
+
+        by_lang = {}
+        for lang in langlist:
+            lineslist = [el.lines for el in self.lastmonth.commits
+                         if lang in el.programming_languages + [None]]
+            n = len(lineslist)
+            lines = sum(lineslist)
+            if lang != None:
+                lang = TroveCategory.query.get(_id = lang)
+            by_lang[lang] = dict(number=n, lines=lines)
+        return by_lang
+
+    def getLastMonthArtifacts(self, category = None, art_type = None):
+        self.checkOldArtifacts() 
+        cre, mod = reduce(
+            addtuple, 
+            [(int(el.created),1-int(el.created))
+                for el in self.lastmonth.messages
+                if (category is None or category in el.categories) and 
+                (el.messagetype == art_type or art_type is None)], 
+            (0,0))
+        return dict(created=cre, modified=mod)
+
+    def getLastMonthArtifactsByType(self, category = None):
+        self.checkOldArtifacts()
+        seen = set()
+        types=[el.messagetype for el in self.lastmonth.messages
+               if el.messagetype not in seen and not seen.add(el.messagetype)]
+
+        by_type = {}
+        for t in types:
+            cre, mod = reduce(
+                addtuple, 
+                [(int(el.created),1-int(el.created))
+                 for el in self.lastmonth.messages
+                 if el.messagetype == t and
+                 category in [None]+el.categories],
+                (0,0))
+            by_type[t] = dict(created=cre, modified=mod)
+        return by_type
+
+    def getLastMonthArtifactsByCategory(self):
+        from allura.model.project import TroveCategory
+
+        self.checkOldArtifacts() 
+        seen = set()
+        catlist=[el.category for el in self.general
+                 if el.category not in seen and not seen.add(el.category)]
+
+        by_cat = {}
+        for cat in catlist:
+            cre, mod = reduce(
+                addtuple, 
+                [(int(el.created),1-int(el.created))
+                 for el in self.lastmonth.messages 
+                 if cat in el.categories + [None]], (0,0))
+            if cat != None:
+                cat = TroveCategory.query.get(_id = cat)
+            by_cat[cat] = dict(created=cre, modified=mod)
+        return by_cat
+
+    def getLastMonthTickets(self, category = None):
+        from allura.model.project import TroveCategory
+
+        self.checkOldArtifacts()
+        a = len([el for el in self.lastmonth.assignedtickets
+                 if category in el.categories + [None]])
+        r = len([el for el in self.lastmonth.revokedtickets
+                 if category in el.categories + [None]])
+        s, time = reduce(
+            addtuple, 
+            [(1, el.solvingtime)
+             for el in self.lastmonth.solvedtickets
+             if category in el.categories + [None]],
+            (0,0))
+        if category!=None:
+            category = TroveCategory.query.get(_id=category)
+        if s > 0:
+            time = time / s
+        else:
+            time = None
+        return dict(
+            assigned=a,
+            revoked=r,
+            solved=s, 
+            averagesolvingtime=_convertTimeDiff(time))
+        
+    def getLastMonthTicketsByCategory(self):
+        from allura.model.project import TroveCategory
+
+        self.checkOldArtifacts()
+        seen = set()
+        catlist=[el.category for el in self.general
+                 if el.category not in seen and not seen.add(el.category)]
+        by_cat = {}
+        for cat in catlist:
+            a = len([el for el in self.lastmonth.assignedtickets
+                     if cat in el.categories + [None]])
+            r = len([el for el in self.lastmonth.revokedtickets
+                     if cat in el.categories + [None]])
+            s, time = reduce(addtuple, [(1, el.solvingtime)
+                                        for el in self.lastmonth.solvedtickets
+                                        if cat in el.categories+[None]],(0,0))
+            if cat != None:
+                cat = TroveCategory.query.get(_id = cat)
+            if s > 0: 
+                time = time / s
+            else:
+                time = None
+            by_cat[cat] = dict(
+                assigned=a,
+                revoked=r,
+                solved=s, 
+                averagesolvingtime=_convertTimeDiff(time))
+        return by_cat
+        
+    def checkOldArtifacts(self):
+        now = datetime.utcnow()
+        for m in self.lastmonth.messages:
+            if now - m.datetime > timedelta(30):
+                self.lastmonth.messages.remove(m)
+        for t in self.lastmonth.assignedtickets:
+            if now - t.datetime > timedelta(30):
+                self.lastmonth.assignedtickets.remove(t)
+        for t in self.lastmonth.revokedtickets:
+            if now - t.datetime > timedelta(30):
+                self.lastmonth.revokedtickets.remove(t)
+        for t in self.lastmonth.solvedtickets:
+            if now - t.datetime > timedelta(30):
+                self.lastmonth.solvedtickets.remove(t)
+        for c in self.lastmonth.commits:
+            if now - c.datetime > timedelta(30):
+                self.lastmonth.commits.remove(c)
+
+    def addNewArtifact(self, art_type, art_datetime, project):
+        self._updateArtifactsStats(art_type, art_datetime, project, "created")
+
+    def addModifiedArtifact(self, art_type, art_datetime, project):
+        self._updateArtifactsStats(art_type, art_datetime, project, "modified")
+
+    def addAssignedTicket(self, ticket_datetime, project):
+        topics = [t for t in project.trove_topic if t]
+        self._updateTicketsStats(topics, 'assigned')
+        self.lastmonth.assignedtickets.append(
+            dict(datetime=ticket_datetime, categories=topics))
+
+    def addRevokedTicket(self, ticket_datetime, project):
+        topics = [t for t in project.trove_topic if t]
+        self._updateTicketsStats(topics, 'revoked')
+        self.lastmonth.revokedtickets.append(
+            dict(datetime=ticket_datetime, categories=topics))
+        self.checkOldArtifacts()
+
+    def addClosedTicket(self, open_datetime, close_datetime, project):
+        topics = [t for t in project.trove_topic if t]
+        s_time=int((close_datetime-open_datetime).total_seconds())
+        self._updateTicketsStats(topics, 'solved', s_time = s_time)
+        self.lastmonth.solvedtickets.append(dict(
+            datetime=close_datetime,
+            categories=topics,
+            solvingtime=s_time))
+        self.checkOldArtifacts()
+
+    def addCommit(self, newcommit, commit_datetime, project):
+        def _computeLines(newblob, oldblob = None):
+            if oldblob:
+                listold = list(oldblob)
+            else:
+                listold = []
+            if newblob:
+                listnew = list(newblob)
+            else:
+                listnew = []
+
+            if oldblob is None:
+                lines = len(listnew)
+            elif newblob and newblob.has_html_view:
+                diff = difflib.unified_diff(
+                    listold, listnew,
+                    ('old' + oldblob.path()).encode('utf-8'),
+                    ('new' + newblob.path()).encode('utf-8'))
+                lines = len([l for l in diff if len(l) > 0 and l[0] == '+'])-1
+            else:
+                lines = 0
+            return lines
+
+        def _addCommitData(stats, topics, languages, lines):          
+            lt = topics + [None]
+            ll = languages + [None]
+            for t in lt:
+                i = getElementIndex(stats.general, category=t) 
+                if i is None:
+                    newstats = dict(
+                        category=t,
+                        commits=[],
+                        messages=[],
+                        tickets=dict(
+                            assigned=0,
+                            solved=0,
+                            revoked=0,
+                            totsolvingtime=0))
+                    stats.general.append(newstats)
+                    i = getElementIndex(stats.general, category=t)
+                for lang in ll:
+                    j = getElementIndex(
+                        stats.general[i]['commits'], language=lang)
+                    if j is None:
+                        stats.general[i]['commits'].append(dict(
+                            language=lang, lines=lines, number=1))
+                    else:
+                        stats.general[i]['commits'][j].lines += lines
+                        stats.general[i]['commits'][j].number += 1
+
+        topics = [t for t in project.trove_topic if t]
+        languages = [l for l in project.trove_language if l]
+
+        d = newcommit.diffs
+        if len(newcommit.parent_ids) > 0:
+            oldcommit = newcommit.repo.commit(newcommit.parent_ids[0])
+
+        totlines = 0
+        for changed in d.changed:
+            newblob = newcommit.tree.get_blob_by_path(changed)
+            oldblob = oldcommit.tree.get_blob_by_path(changed)
+            totlines+=_computeLines(newblob, oldblob)
+
+        for copied in d.copied:
+            newblob = newcommit.tree.get_blob_by_path(copied['new'])
+            oldblob = oldcommit.tree.get_blob_by_path(copied['old'])
+            totlines+=_computeLines(newblob, oldblob)
+
+        for added in d.added:
+            newblob = newcommit.tree.get_blob_by_path(added)
+            totlines+=_computeLines(newblob)
+
+        _addCommitData(self, topics, languages, totlines)
+
+        self.lastmonth.commits.append(dict(
+            datetime=commit_datetime, 
+            categories=topics, 
+            programming_languages=languages,
+            lines=totlines))
+        self.checkOldArtifacts()
+
+    def _updateArtifactsStats(self, art_type, art_datetime, project, action):
+        if action not in ['created', 'modified']: 
+            return
+        topics = [t for t in project.trove_topic if t]
+        lt = [None] + topics
+        for mtype in [None, art_type]:
+            for t in lt:
+                i = getElementIndex(self.general, category = t)
+                if i is None:
+                    msg = dict(
+                        category=t,
+                        commits=[],
+                        tickets=dict(
+                            solved=0,
+                            assigned=0,
+                            revoked=0,
+                            totsolvingtime=0),
+                        messages=[])
+                    self.general.append(msg)
+                    i = getElementIndex(self.general, category = t)
+                j = getElementIndex(
+                    self.general[i]['messages'], messagetype=mtype)
+                if j is None:
+                    entry = dict(messagetype=mtype, created=0, modified=0)
+                    entry[action] += 1
+                    self.general[i]['messages'].append(entry)
+                else:
+                    self.general[i]['messages'][j][action] += 1
+
+        self.lastmonth.messages.append(dict(
+            datetime=art_datetime,
+            created=(action == 'created'),
+            categories=topics,
+            messagetype=art_type))
+        self.checkOldArtifacts() 
+
+    def _updateTicketsStats(self, topics, action, s_time = None):
+        if action not in ['solved', 'assigned', 'revoked']:
+            return
+        lt = topics + [None]
+        for t in lt:
+            i = getElementIndex(self.general, category = t)
+            if i is None:
+                stats = dict(
+                    category=t,
+                    commits=[],
+                    tickets=dict(
+                        solved=0,
+                        assigned=0,
+                        revoked=0,
+                        totsolvingtime=0),
+                    messages=[])
+                self.general.append(stats)
+                i = getElementIndex(self.general, category = t)
+            self.general[i]['tickets'][action] += 1 
+            if action == 'solved': 
+                self.general[i]['tickets']['totsolvingtime']+=s_time
+
+def getElementIndex(el_list, **kw):
+    for i in range(len(el_list)):
+        for k in kw:
+            if el_list[i].get(k) != kw[k]:
+                break
+        else:
+            return i
+    return None
+
+def addtuple(l1, l2):
+    a, b = l1
+    x, y = l2
+    return (a+x, b+y)
+
+def _convertTimeDiff(int_seconds):
+    if int_seconds is None:
+        return None
+    diff = timedelta(seconds = int_seconds)
+    days, seconds = diff.days, diff.seconds
+    hours = seconds / 3600
+    seconds = seconds % 3600
+    minutes = seconds / 60
+    seconds = seconds % 60
+    return dict(
+        days=days, 
+        hours=hours, 
+        minutes=minutes,
+        seconds=seconds)
 
+Mapper.compile_all()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/Allura/allura/templates/site_admin.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/site_admin.html b/Allura/allura/templates/site_admin.html
index b8b1b7d..5ba439b 100644
--- a/Allura/allura/templates/site_admin.html
+++ b/Allura/allura/templates/site_admin.html
@@ -16,7 +16,6 @@
   <div>&nbsp;</div>
   <ul>
     <li class="{{page=='index' and 'active' or ''}}"><a href="{{sidebar_rel}}."><b data-icon="{{g.icons['admin'].char}}" class="ico {{g.icons['admin'].css}}"></b>Home</a></li>
-    <li class="{{page=='stats' and 'active' or ''}}"><a href="{{sidebar_rel}}stats"><b data-icon="{{g.icons['stats'].char}}" class="ico {{g.icons['stats'].css}}"></b>Stats</a></li>
     <li class="{{page=='api_tickets' and 'active' or ''}}"><a href="{{sidebar_rel}}api_tickets"><b data-icon="{{g.icons['admin'].char}}" class="ico {{g.icons['admin'].css}}"></b>API Tickets</a></li>
     <li class="{{page=='add_subscribers' and 'active' or ''}}"><a href="{{sidebar_rel}}add_subscribers"><b data-icon="{{g.icons['admin'].char}}" class="ico {{g.icons['admin'].css}}"></b>Add Subscribers</a></li>
     <li class="{{page=='new_projects' and 'active' or ''}}"><a href="{{sidebar_rel}}new_projects"><b data-icon="{{g.icons['admin'].char}}" class="ico {{g.icons['admin'].css}}"></b>New Projects</a></li>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/Allura/allura/tests/functional/test_site_admin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_site_admin.py b/Allura/allura/tests/functional/test_site_admin.py
index b5f2d10..3e1925a 100644
--- a/Allura/allura/tests/functional/test_site_admin.py
+++ b/Allura/allura/tests/functional/test_site_admin.py
@@ -23,21 +23,6 @@ class TestSiteAdmin(TestController):
         cells = stats_table.findAll('td')
         assert cells[0].contents[0] == 'Adobe', cells[0].contents[0]
 
-    def test_performance(self):
-        r = self.app.get('/nf/admin/stats', extra_environ=dict(
-                username='test-user'), status=403)
-        r = self.app.get('/nf/admin/stats', extra_environ=dict(
-                username='root'))
-        assert 'Forge Site Admin' in r.html.find('h2',{'class':'dark title'}).contents[0]
-        stats_table = r.html.find('table')
-        headers = stats_table.findAll('th')
-        assert headers[0].contents[0] == 'Url'
-        assert headers[1].contents[0] == 'Ming'
-        assert headers[2].contents[0] == 'Mongo'
-        assert headers[3].contents[0] == 'Render'
-        assert headers[4].contents[0] == 'Template'
-        assert headers[5].contents[0] == 'Total Time'
-
     def test_tickets_access(self):
         r = self.app.get('/nf/admin/api_tickets', extra_environ=dict(
                 username='test-user'), status=403)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/ForgeUserStats/forgeuserstats/model/stats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/stats.py b/ForgeUserStats/forgeuserstats/model/stats.py
index b2f70a4..722b8d0 100644
--- a/ForgeUserStats/forgeuserstats/model/stats.py
+++ b/ForgeUserStats/forgeuserstats/model/stats.py
@@ -2,9 +2,11 @@ from ming.orm import FieldProperty
 from ming import schema as S
 from datetime import datetime, timedelta
 from ming.orm import session, Mapper
+from pylons import request
 
+from allura.lib import plugin
 from allura.model.session import main_orm_session
-from allura.model.contrib_stats import Stats
+from allura.model import Stats
 
 class UserStats(Stats):
     class __mongometa__:
@@ -19,11 +21,12 @@ class UserStats(Stats):
 
     @classmethod
     def create(cls, user):
+        auth_provider = plugin.AuthenticationProvider.get(request)
+        reg_date = auth_provider.user_registration_date(user)
         stats = cls.query.get(user_id = user._id)
         if stats:
             return stats
-        stats = cls(user_id=user._id,
-            registration_date = datetime.utcnow())
+        stats = cls(user_id=user._id, registration_date = reg_date)
         user.stats_id = stats._id
         return stats
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/applypatch-msg.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/applypatch-msg.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/applypatch-msg.sample
deleted file mode 100755
index 8b2a2fe..0000000
--- a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/applypatch-msg.sample
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/sh
-#
-# An example hook script to check the commit log message taken by
-# applypatch from an e-mail message.
-#
-# The hook should exit with non-zero status after issuing an
-# appropriate message if it wants to stop the commit.  The hook is
-# allowed to edit the commit message file.
-#
-# To enable this hook, rename this file to "applypatch-msg".
-
-. git-sh-setup
-test -x "$GIT_DIR/hooks/commit-msg" &&
-	exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
-:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/commit-msg.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/commit-msg.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/commit-msg.sample
deleted file mode 100755
index 6ef1d29..0000000
--- a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/commit-msg.sample
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/sh
-#
-# An example hook script to check the commit log message.
-# Called by git-commit with one argument, the name of the file
-# that has the commit message.  The hook should exit with non-zero
-# status after issuing an appropriate message if it wants to stop the
-# commit.  The hook is allowed to edit the commit message file.
-#
-# To enable this hook, rename this file to "commit-msg".
-
-# Uncomment the below to add a Signed-off-by line to the message.
-# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
-# hook is more suited to it.
-#
-# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
-# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
-
-# This example catches duplicate Signed-off-by lines.
-
-test "" = "$(grep '^Signed-off-by: ' "$1" |
-	 sort | uniq -c | sed -e '/^[ 	]*1[ 	]/d')" || {
-	echo >&2 Duplicate Signed-off-by lines.
-	exit 1
-}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-commit.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-commit.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-commit.sample
deleted file mode 100755
index 2266821..0000000
--- a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-commit.sample
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-#
-# An example hook script that is called after a successful
-# commit is made.
-#
-# To enable this hook, rename this file to "post-commit".
-
-: Nothing

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive
deleted file mode 100755
index 0f7a148..0000000
--- a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive
+++ /dev/null
@@ -1 +0,0 @@
-post-receive

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive.sample
deleted file mode 100755
index 7a83e17..0000000
--- a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-receive.sample
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/sh
-#
-# An example hook script for the "post-receive" event.
-#
-# The "post-receive" script is run after receive-pack has accepted a pack
-# and the repository has been updated.  It is passed arguments in through
-# stdin in the form
-#  <oldrev> <newrev> <refname>
-# For example:
-#  aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
-#
-# see contrib/hooks/ for a sample, or uncomment the next line and
-# rename the file to "post-receive".
-
-#. /usr/share/doc/git-core/contrib/hooks/post-receive-email

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-update.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-update.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-update.sample
deleted file mode 100755
index 5323b56..0000000
--- a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/post-update.sample
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-#
-# An example hook script to prepare a packed repository for use over
-# dumb transports.
-#
-# To enable this hook, rename this file to "post-update".
-
-exec git-update-server-info

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-applypatch.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-applypatch.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-applypatch.sample
deleted file mode 100755
index b1f187c..0000000
--- a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-applypatch.sample
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/sh
-#
-# An example hook script to verify what is about to be committed
-# by applypatch from an e-mail message.
-#
-# The hook should exit with non-zero status after issuing an
-# appropriate message if it wants to stop the commit.
-#
-# To enable this hook, rename this file to "pre-applypatch".
-
-. git-sh-setup
-test -x "$GIT_DIR/hooks/pre-commit" &&
-	exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
-:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-commit.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-commit.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-commit.sample
deleted file mode 100755
index 439eefd..0000000
--- a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-commit.sample
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/bin/sh
-#
-# An example hook script to verify what is about to be committed.
-# Called by git-commit with no arguments.  The hook should
-# exit with non-zero status after issuing an appropriate message if
-# it wants to stop the commit.
-#
-# To enable this hook, rename this file to "pre-commit".
-
-if git-rev-parse --verify HEAD >/dev/null 2>&1
-then
-	against=HEAD
-else
-	# Initial commit: diff against an empty tree object
-	against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
-fi
-
-# If you want to allow non-ascii filenames set this variable to true.
-allownonascii=$(git config hooks.allownonascii)
-
-# Cross platform projects tend to avoid non-ascii filenames; prevent
-# them from being added to the repository. We exploit the fact that the
-# printable range starts at the space character and ends with tilde.
-if [ "$allownonascii" != "true" ] &&
-	# Note that the use of brackets around a tr range is ok here, (it's
-	# even required, for portability to Solaris 10's /usr/bin/tr), since
-	# the square bracket bytes happen to fall in the designated range.
-	test "$(git diff --cached --name-only --diff-filter=A -z $against |
-	  LC_ALL=C tr -d '[ -~]\0')"
-then
-	echo "Error: Attempt to add a non-ascii file name."
-	echo
-	echo "This can cause problems if you want to work"
-	echo "with people on other platforms."
-	echo
-	echo "To be portable it is advisable to rename the file ..."
-	echo
-	echo "If you know what you are doing you can disable this"
-	echo "check using:"
-	echo
-	echo "  git config hooks.allownonascii true"
-	echo
-	exit 1
-fi
-
-exec git diff-index --check --cached $against --

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-rebase.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-rebase.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-rebase.sample
deleted file mode 100755
index be1b06e..0000000
--- a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/pre-rebase.sample
+++ /dev/null
@@ -1,169 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2006, 2008 Junio C Hamano
-#
-# The "pre-rebase" hook is run just before "git-rebase" starts doing
-# its job, and can prevent the command from running by exiting with
-# non-zero status.
-#
-# The hook is called with the following parameters:
-#
-# $1 -- the upstream the series was forked from.
-# $2 -- the branch being rebased (or empty when rebasing the current branch).
-#
-# This sample shows how to prevent topic branches that are already
-# merged to 'next' branch from getting rebased, because allowing it
-# would result in rebasing already published history.
-
-publish=next
-basebranch="$1"
-if test "$#" = 2
-then
-	topic="refs/heads/$2"
-else
-	topic=`git symbolic-ref HEAD` ||
-	exit 0 ;# we do not interrupt rebasing detached HEAD
-fi
-
-case "$topic" in
-refs/heads/??/*)
-	;;
-*)
-	exit 0 ;# we do not interrupt others.
-	;;
-esac
-
-# Now we are dealing with a topic branch being rebased
-# on top of master.  Is it OK to rebase it?
-
-# Does the topic really exist?
-git show-ref -q "$topic" || {
-	echo >&2 "No such branch $topic"
-	exit 1
-}
-
-# Is topic fully merged to master?
-not_in_master=`git-rev-list --pretty=oneline ^master "$topic"`
-if test -z "$not_in_master"
-then
-	echo >&2 "$topic is fully merged to master; better remove it."
-	exit 1 ;# we could allow it, but there is no point.
-fi
-
-# Is topic ever merged to next?  If so you should not be rebasing it.
-only_next_1=`git-rev-list ^master "^$topic" ${publish} | sort`
-only_next_2=`git-rev-list ^master           ${publish} | sort`
-if test "$only_next_1" = "$only_next_2"
-then
-	not_in_topic=`git-rev-list "^$topic" master`
-	if test -z "$not_in_topic"
-	then
-		echo >&2 "$topic is already up-to-date with master"
-		exit 1 ;# we could allow it, but there is no point.
-	else
-		exit 0
-	fi
-else
-	not_in_next=`git-rev-list --pretty=oneline ^${publish} "$topic"`
-	perl -e '
-		my $topic = $ARGV[0];
-		my $msg = "* $topic has commits already merged to public branch:\n";
-		my (%not_in_next) = map {
-			/^([0-9a-f]+) /;
-			($1 => 1);
-		} split(/\n/, $ARGV[1]);
-		for my $elem (map {
-				/^([0-9a-f]+) (.*)$/;
-				[$1 => $2];
-			} split(/\n/, $ARGV[2])) {
-			if (!exists $not_in_next{$elem->[0]}) {
-				if ($msg) {
-					print STDERR $msg;
-					undef $msg;
-				}
-				print STDERR " $elem->[1]\n";
-			}
-		}
-	' "$topic" "$not_in_next" "$not_in_master"
-	exit 1
-fi
-
-exit 0
-
-################################################################
-
-This sample hook safeguards topic branches that have been
-published from being rewound.
-
-The workflow assumed here is:
-
- * Once a topic branch forks from "master", "master" is never
-   merged into it again (either directly or indirectly).
-
- * Once a topic branch is fully cooked and merged into "master",
-   it is deleted.  If you need to build on top of it to correct
-   earlier mistakes, a new topic branch is created by forking at
-   the tip of the "master".  This is not strictly necessary, but
-   it makes it easier to keep your history simple.
-
- * Whenever you need to test or publish your changes to topic
-   branches, merge them into "next" branch.
-
-The script, being an example, hardcodes the publish branch name
-to be "next", but it is trivial to make it configurable via
-$GIT_DIR/config mechanism.
-
-With this workflow, you would want to know:
-
-(1) ... if a topic branch has ever been merged to "next".  Young
-    topic branches can have stupid mistakes you would rather
-    clean up before publishing, and things that have not been
-    merged into other branches can be easily rebased without
-    affecting other people.  But once it is published, you would
-    not want to rewind it.
-
-(2) ... if a topic branch has been fully merged to "master".
-    Then you can delete it.  More importantly, you should not
-    build on top of it -- other people may already want to
-    change things related to the topic as patches against your
-    "master", so if you need further changes, it is better to
-    fork the topic (perhaps with the same name) afresh from the
-    tip of "master".
-
-Let's look at this example:
-
-		   o---o---o---o---o---o---o---o---o---o "next"
-		  /       /           /           /
-		 /   a---a---b A     /           /
-		/   /               /           /
-	       /   /   c---c---c---c B         /
-	      /   /   /             \         /
-	     /   /   /   b---b C     \       /
-	    /   /   /   /             \     /
-    ---o---o---o---o---o---o---o---o---o---o---o "master"
-
-
-A, B and C are topic branches.
-
- * A has one fix since it was merged up to "next".
-
- * B has finished.  It has been fully merged up to "master" and "next",
-   and is ready to be deleted.
-
- * C has not merged to "next" at all.
-
-We would want to allow C to be rebased, refuse A, and encourage
-B to be deleted.
-
-To compute (1):
-
-	git-rev-list ^master ^topic next
-	git-rev-list ^master        next
-
-	if these match, topic has not merged in next at all.
-
-To compute (2):
-
-	git-rev-list master..topic
-
-	if this is empty, it is fully merged to "master".

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/prepare-commit-msg.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/prepare-commit-msg.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/prepare-commit-msg.sample
deleted file mode 100755
index 3652424..0000000
--- a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/prepare-commit-msg.sample
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/sh
-#
-# An example hook script to prepare the commit log message.
-# Called by git-commit with the name of the file that has the
-# commit message, followed by the description of the commit
-# message's source.  The hook's purpose is to edit the commit
-# message file.  If the hook fails with a non-zero status,
-# the commit is aborted.
-#
-# To enable this hook, rename this file to "prepare-commit-msg".
-
-# This hook includes three examples.  The first comments out the
-# "Conflicts:" part of a merge commit.
-#
-# The second includes the output of "git diff --name-status -r"
-# into the message, just before the "git status" output.  It is
-# commented because it doesn't cope with --amend or with squashed
-# commits.
-#
-# The third example adds a Signed-off-by line to the message, that can
-# still be edited.  This is rarely a good idea.
-
-case "$2,$3" in
-  merge,)
-    perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;
-
-# ,|template,)
-#   perl -i.bak -pe '
-#      print "\n" . `git diff --cached --name-status -r`
-#	 if /^#/ && $first++ == 0' "$1" ;;
-
-  *) ;;
-esac
-
-# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
-# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update
deleted file mode 100755
index 4ea5e4d..0000000
--- a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update
+++ /dev/null
@@ -1 +0,0 @@
-update

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/08a31a1e/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update.sample
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update.sample b/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update.sample
deleted file mode 100755
index fd63b2d..0000000
--- a/ForgeUserStats/forgeuserstats/tests/data/testgit.git/hooks/update.sample
+++ /dev/null
@@ -1,128 +0,0 @@
-#!/bin/sh
-#
-# An example hook script to blocks unannotated tags from entering.
-# Called by git-receive-pack with arguments: refname sha1-old sha1-new
-#
-# To enable this hook, rename this file to "update".
-#
-# Config
-# ------
-# hooks.allowunannotated
-#   This boolean sets whether unannotated tags will be allowed into the
-#   repository.  By default they won't be.
-# hooks.allowdeletetag
-#   This boolean sets whether deleting tags will be allowed in the
-#   repository.  By default they won't be.
-# hooks.allowmodifytag
-#   This boolean sets whether a tag may be modified after creation. By default
-#   it won't be.
-# hooks.allowdeletebranch
-#   This boolean sets whether deleting branches will be allowed in the
-#   repository.  By default they won't be.
-# hooks.denycreatebranch
-#   This boolean sets whether remotely creating branches will be denied
-#   in the repository.  By default this is allowed.
-#
-
-# --- Command line
-refname="$1"
-oldrev="$2"
-newrev="$3"
-
-# --- Safety check
-if [ -z "$GIT_DIR" ]; then
-	echo "Don't run this script from the command line." >&2
-	echo " (if you want, you could supply GIT_DIR then run" >&2
-	echo "  $0 <ref> <oldrev> <newrev>)" >&2
-	exit 1
-fi
-
-if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
-	echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
-	exit 1
-fi
-
-# --- Config
-allowunannotated=$(git config --bool hooks.allowunannotated)
-allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
-denycreatebranch=$(git config --bool hooks.denycreatebranch)
-allowdeletetag=$(git config --bool hooks.allowdeletetag)
-allowmodifytag=$(git config --bool hooks.allowmodifytag)
-
-# check for no description
-projectdesc=$(sed -e '1q' "$GIT_DIR/description")
-case "$projectdesc" in
-"Unnamed repository"* | "")
-	echo "*** Project description file hasn't been set" >&2
-	exit 1
-	;;
-esac
-
-# --- Check types
-# if $newrev is 0000...0000, it's a commit to delete a ref.
-zero="0000000000000000000000000000000000000000"
-if [ "$newrev" = "$zero" ]; then
-	newrev_type=delete
-else
-	newrev_type=$(git-cat-file -t $newrev)
-fi
-
-case "$refname","$newrev_type" in
-	refs/tags/*,commit)
-		# un-annotated tag
-		short_refname=${refname##refs/tags/}
-		if [ "$allowunannotated" != "true" ]; then
-			echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
-			echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
-			exit 1
-		fi
-		;;
-	refs/tags/*,delete)
-		# delete tag
-		if [ "$allowdeletetag" != "true" ]; then
-			echo "*** Deleting a tag is not allowed in this repository" >&2
-			exit 1
-		fi
-		;;
-	refs/tags/*,tag)
-		# annotated tag
-		if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
-		then
-			echo "*** Tag '$refname' already exists." >&2
-			echo "*** Modifying a tag is not allowed in this repository." >&2
-			exit 1
-		fi
-		;;
-	refs/heads/*,commit)
-		# branch
-		if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
-			echo "*** Creating a branch is not allowed in this repository" >&2
-			exit 1
-		fi
-		;;
-	refs/heads/*,delete)
-		# delete branch
-		if [ "$allowdeletebranch" != "true" ]; then
-			echo "*** Deleting a branch is not allowed in this repository" >&2
-			exit 1
-		fi
-		;;
-	refs/remotes/*,commit)
-		# tracking branch
-		;;
-	refs/remotes/*,delete)
-		# delete tracking branch
-		if [ "$allowdeletebranch" != "true" ]; then
-			echo "*** Deleting a tracking branch is not allowed in this repository" >&2
-			exit 1
-		fi
-		;;
-	*)
-		# Anything else (is there anything else?)
-		echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
-		exit 1
-		;;
-esac
-
-# --- Finished
-exit 0


[17/42] git commit: [5453] Fixed stats template and improved controller

Posted by br...@apache.org.
[5453] Fixed stats template and improved controller


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/cd824578
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/cd824578
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/cd824578

Branch: refs/heads/db/5453
Commit: cd82457846bb90b25256c1ecbf84384a74dc41cd
Parents: bb4a339
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Jan 30 16:21:49 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:31 2013 +0000

----------------------------------------------------------------------
 .../forgeuserstats/controllers/userstats.py        |   33 +++++++--------
 ForgeUserStats/forgeuserstats/templates/index.html |   12 +++---
 2 files changed, 21 insertions(+), 24 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/cd824578/ForgeUserStats/forgeuserstats/controllers/userstats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/controllers/userstats.py b/ForgeUserStats/forgeuserstats/controllers/userstats.py
index fe14449..b84eb0f 100644
--- a/ForgeUserStats/forgeuserstats/controllers/userstats.py
+++ b/ForgeUserStats/forgeuserstats/controllers/userstats.py
@@ -15,16 +15,15 @@ class ForgeUserStatsController(BaseController):
         if not self.user:
             return ForgeUserStatsController(user=user), remainder
         if part == "category":
-            return ForgeUserStatsCatController(self.user, self.stats, None), remainder
+            return ForgeUserStatsCatController(self.user, None), remainder
         if part == "metric":
-            return ForgeUserStatsMetricController(self.user, self.stats), remainder
+            return ForgeUserStatsMetricController(self.user), remainder
 
     def __init__(self, user=None):
         self.user = user
         if self.user:
-            self.stats = self.user.stats
-            if not self.stats:
-                self.stats = UserStats.create(self.user)
+            if not user.stats:
+                UserStats.create(self.user)
 
         super(ForgeUserStatsController, self).__init__()
 
@@ -33,7 +32,7 @@ class ForgeUserStatsController(BaseController):
     def index(self, **kw):
         if not self.user: 
             return dict(user=None)
-        stats = self.stats
+        stats = self.user.stats
 
         ret_dict = _getDataForCategory(None, stats)
         ret_dict['user'] = self.user
@@ -107,25 +106,24 @@ class ForgeUserStatsController(BaseController):
 
     @expose()
     def code_ranking_bar(self):
-        return create_progress_bar(self.stats.codeRanking())
+        return create_progress_bar(self.user.stats.codeRanking())
 
     @expose()
     def discussion_ranking_bar(self):
-        return create_progress_bar(self.stats.discussionRanking())
+        return create_progress_bar(self.user.stats.discussionRanking())
 
     @expose()
     def tickets_ranking_bar(self):
-        return create_progress_bar(self.stats.ticketsRanking())
+        return create_progress_bar(self.user.stats.ticketsRanking())
 
 class ForgeUserStatsCatController(BaseController):
     @expose()
     def _lookup(self, category, *remainder):
-        cat = M.TroveCategory.query.get(fullname=category)
+        cat = M.TroveCategory.query.get(shortname=category)
         return ForgeUserStatsCatController(self.user, cat), remainder
 
-    def __init__(self, user, stats, category):
+    def __init__(self, user, category):
         self.user = user
-        self.stats = stats
         self.category = category
         super(ForgeUserStatsCatController, self).__init__()
 
@@ -134,7 +132,7 @@ class ForgeUserStatsCatController(BaseController):
     def index(self, **kw):
         if not self.user:
             return dict(user=None)
-        stats = self.stats
+        stats = self.user.stats
         
         cat_id = None
         if self.category: 
@@ -148,9 +146,8 @@ class ForgeUserStatsCatController(BaseController):
 
 class ForgeUserStatsMetricController(BaseController):
 
-    def __init__(self, user, stats):
+    def __init__(self, user):
         self.user = user
-        self.stats = stats
         super(ForgeUserStatsMetricController, self).__init__()
 
     @expose('jinja:forgeuserstats:templates/commits.html')
@@ -158,7 +155,7 @@ class ForgeUserStatsMetricController(BaseController):
     def commits(self, **kw):
         if not self.user:
             return dict(user=None)
-        stats = self.stats
+        stats = self.user.stats
         
         commits = stats.getCommitsByCategory()
         return dict(user = self.user,
@@ -170,7 +167,7 @@ class ForgeUserStatsMetricController(BaseController):
         if not self.user:
             return dict(user=None)
 
-        stats = self.stats       
+        stats = self.user.stats       
         artifacts = stats.getArtifactsByCategory(detailed=True)
         return dict(user = self.user, data = artifacts) 
 
@@ -180,7 +177,7 @@ class ForgeUserStatsMetricController(BaseController):
         if not self.user: 
             return dict(user=None)
 
-        artifacts = self.stats.getTicketsByCategory()
+        artifacts = self.user.stats.getTicketsByCategory()
         return dict(user = self.user, data = artifacts) 
 
 def _getDataForCategory(category, stats):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/cd824578/ForgeUserStats/forgeuserstats/templates/index.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/index.html b/ForgeUserStats/forgeuserstats/templates/index.html
index f5a71c6..6d6a25b 100644
--- a/ForgeUserStats/forgeuserstats/templates/index.html
+++ b/ForgeUserStats/forgeuserstats/templates/index.html
@@ -85,9 +85,9 @@
           <td>{{lastmonthcommits.number}}</td>
           {% if days >= 30 %}
             <td style="text-align:center;">
-              {% if permonthcommits.number > permonthcommits.number %}
+              {% if lastmonthcommits.number > permonthcommits.number %}
                   <img src="{{g.forge_static('images/up.png')}}"/>
-              {% elif permonthcommits.number == permonthcommits.number %}
+              {% elif lastmonthcommits.number == permonthcommits.number %}
                 <img src="{{g.forge_static('images/equal.png')}}"/>
               {% else %}
                 <img src="{{g.forge_static('images/down.png')}}"/>
@@ -108,9 +108,9 @@
           <td>{{lastmonthcommits.lines}}</td>
           {% if days >= 30 %}
             <td style="text-align:center;">
-              {% if permonthcommits.lines > permonthcommits.lines %}
+              {% if lastmonthcommits.lines > permonthcommits.lines %}
                   <img src="{{g.forge_static('images/up.png')}}"/>
-              {% elif permonthcommits.lines == permonthcommits.lines %}
+              {% elif lastmonthcommits.lines == permonthcommits.lines %}
                 <img src="{{g.forge_static('images/equal.png')}}"/>
               {% else %}
                 <img src="{{g.forge_static('images/down.png')}}"/>
@@ -193,7 +193,7 @@
                   {% else %}
                     <img src="{{g.forge_static('images/down.png')}}"/>
                   {%endif%}
-                {%else%} Down {%endif%}
+                {%else%} <img src="{{g.forge_static('images/down.png')}}"/> {%endif%}
               </td>
             {% endif %}
           </tr>
@@ -224,7 +224,7 @@
                   {% else %}
                     <img src="{{g.forge_static('images/down.png')}}"/>
                   {%endif%}
-                {%else%} Down {%endif%}
+                {%else%} <img src="{{g.forge_static('images/down.png')}}"/> {%endif%}
               </td>
             {% endif %}
           </tr>


[10/42] git commit: [5453] fixed bug

Posted by br...@apache.org.
[5453] fixed bug


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/2ef57f05
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/2ef57f05
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/2ef57f05

Branch: refs/heads/db/5453
Commit: 2ef57f05e46d34fab4e5651bb081da4038a6f742
Parents: b77d338
Author: Stefano Invernizzi <st...@apache.org>
Authored: Thu Jan 10 19:25:44 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:30 2013 +0000

----------------------------------------------------------------------
 ForgeUserStats/forgeuserstats/model/stats.py |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2ef57f05/ForgeUserStats/forgeuserstats/model/stats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/stats.py b/ForgeUserStats/forgeuserstats/model/stats.py
index c622888..d196709 100644
--- a/ForgeUserStats/forgeuserstats/model/stats.py
+++ b/ForgeUserStats/forgeuserstats/model/stats.py
@@ -185,7 +185,7 @@ class UserStats(MappedClass):
         j = getElementIndex(cat.messages, art_type = art_type)
         if j is None:
             return dict(created=0, modified=0)
-        return dict(created=cat[j].created, modified=cat[j].modified)
+        return dict(created=cat.messages[j].created, modified=cat.messages[j].modified)
 
     def getTickets(self, category = None):
         i = getElementIndex(self.general, category = category)


[09/42] git commit: [5453] Logins count

Posted by br...@apache.org.
[5453] Logins count


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/b9c46c6a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/b9c46c6a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/b9c46c6a

Branch: refs/heads/db/5453
Commit: b9c46c6a39874e0ad3702e1bbb7184d916d30c8c
Parents: af20372
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Jan 9 09:19:56 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:30 2013 +0000

----------------------------------------------------------------------
 Allura/allura/lib/plugin.py |    2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/b9c46c6a/Allura/allura/lib/plugin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index a3d01b8..61debc9 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -98,6 +98,8 @@ class AuthenticationProvider(object):
             self.session['userid'] = user._id
             self.session.save()
             g.zarkov_event('login', user=user)
+            for l in g.statslisteners:
+                l.addUserLogin(user)
             return user
         except exc.HTTPUnauthorized:
             self.logout()


[36/42] git commit: [#5453] Fixed bug in userstats model

Posted by br...@apache.org.
[#5453] Fixed bug in userstats model


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/df9c9748
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/df9c9748
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/df9c9748

Branch: refs/heads/db/5453
Commit: df9c9748e31b4d22809ee30c62bc863331ef7a21
Parents: 10ce7f2
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sat Mar 9 16:34:02 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:33 2013 +0000

----------------------------------------------------------------------
 ForgeUserStats/forgeuserstats/model/stats.py |    6 ++++--
 1 files changed, 4 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/df9c9748/ForgeUserStats/forgeuserstats/model/stats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/stats.py b/ForgeUserStats/forgeuserstats/model/stats.py
index 8575e79..4171d52 100644
--- a/ForgeUserStats/forgeuserstats/model/stats.py
+++ b/ForgeUserStats/forgeuserstats/model/stats.py
@@ -25,8 +25,10 @@ class UserStats(Stats):
         stats = cls(user_id=user._id,
             registration_date = datetime.utcnow())
         user.stats_id = stats._id
-        session(stats).flush(stats)
-        session(user).flush(user)
+        if session(stats):
+            session(stats).flush(stats)
+        if session(user):
+            session(user).flush(user)
         return stats
 
     def getLastMonthLogins(self):


[33/42] git commit: [#5453] Fixing errors in userstats

Posted by br...@apache.org.
[#5453] Fixing errors in userstats


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/74dca41d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/74dca41d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/74dca41d

Branch: refs/heads/db/5453
Commit: 74dca41de49095c583aa3eb7c13f671697d2328e
Parents: 5d9da32
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Mar 13 01:25:10 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:33 2013 +0000

----------------------------------------------------------------------
 ForgeUserStats/forgeuserstats/model/stats.py      |    4 ---
 ForgeUserStats/forgeuserstats/tests/test_model.py |   23 +++++++--------
 2 files changed, 11 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/74dca41d/ForgeUserStats/forgeuserstats/model/stats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/stats.py b/ForgeUserStats/forgeuserstats/model/stats.py
index 4171d52..b2f70a4 100644
--- a/ForgeUserStats/forgeuserstats/model/stats.py
+++ b/ForgeUserStats/forgeuserstats/model/stats.py
@@ -25,10 +25,6 @@ class UserStats(Stats):
         stats = cls(user_id=user._id,
             registration_date = datetime.utcnow())
         user.stats_id = stats._id
-        if session(stats):
-            session(stats).flush(stats)
-        if session(user):
-            session(user).flush(user)
         return stats
 
     def getLastMonthLogins(self):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/74dca41d/ForgeUserStats/forgeuserstats/tests/test_model.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/test_model.py b/ForgeUserStats/forgeuserstats/tests/test_model.py
index 3a9fcde..1f0236e 100644
--- a/ForgeUserStats/forgeuserstats/tests/test_model.py
+++ b/ForgeUserStats/forgeuserstats/tests/test_model.py
@@ -18,9 +18,8 @@ class TestUserStats(unittest.TestCase):
 
         setup_basic_test()
         setup_global_objects()
-        self.user = User.register(dict(username='test-new-user',
-            display_name='Test Stats'),
-            make_project=False)
+        self.user = User.by_username('test-user-2')
+        c.user = self.user
 
     def test_init_values(self):
         artifacts = self.user.stats.getArtifacts()
@@ -49,9 +48,9 @@ class TestUserStats(unittest.TestCase):
         assert lmcommits['number'] == 0
         assert lmcommits['lines'] == 0
 
-    @td.with_user_project('test-new-user')
+    @td.with_user_project('test-user-2')
     def test_create_artifact_stats(self):
-        p = Project.query.get(shortname='u/test-new-user')
+        p = Project.query.get(shortname='u/test-user-2')
         topic = TroveCategory.query.get(shortname='scientific')
 
         init_lm_art = self.user.stats.getLastMonthArtifacts()
@@ -126,9 +125,9 @@ class TestUserStats(unittest.TestCase):
         art_by_cat = self.user.stats.getArtifactsByCategory(detailed=False)
         assert art_by_cat[topic]['created'] == 1 and art_by_cat[topic]['modified'] == 0
 
-    @td.with_user_project('test-new-user')
+    @td.with_user_project('test-user-2')
     def test_modify_artifact_stats(self):
-        p = Project.query.get(shortname='u/test-new-user')
+        p = Project.query.get(shortname='u/test-user-2')
         topic = TroveCategory.query.get(shortname='scientific')
 
         init_lm_art = self.user.stats.getLastMonthArtifacts()
@@ -203,9 +202,9 @@ class TestUserStats(unittest.TestCase):
         art_by_cat = self.user.stats.getArtifactsByCategory(detailed=False)
         assert art_by_cat[topic]['created'] == 0 and art_by_cat[topic]['modified'] == 1
 
-    @td.with_user_project('test-new-user')
+    @td.with_user_project('test-user-2')
     def test_ticket_stats(self):
-        p = Project.query.get(shortname='u/test-new-user')
+        p = Project.query.get(shortname='u/test-user-2')
         topic = TroveCategory.query.get(shortname='scientific')
         create_time = datetime.utcnow() + timedelta(-5)
 
@@ -299,9 +298,9 @@ class TestUserStats(unittest.TestCase):
         assert lm_by_cat[topic]['averagesolvingtime'] == solving_time
 
     @with_git
-    @td.with_user_project('test-new-user')
+    @td.with_user_project('test-user-2')
     def test_commit_stats(self):
-        p = Project.query.get(shortname='u/test-new-user')
+        p = Project.query.get(shortname='u/test-user-2')
         topic = TroveCategory.query.get(shortname='scientific')
         commit_time = datetime.utcnow() + timedelta(-1)
 
@@ -353,7 +352,7 @@ class TestUserStats(unittest.TestCase):
         assert lm_by_cat[topic]['number'] == 1
         assert lm_by_cat[topic]['lines'] == 1
 
-    @td.with_user_project('test-new-user')
+    @td.with_user_project('test-user-2')
     def test_login_stats(self):
         init_logins = self.user.stats.tot_logins_count
         init_lm_logins = self.user.stats.getLastMonthLogins()


[29/42] git commit: [#5453] Moving userstats into a tool

Posted by br...@apache.org.
[#5453] Moving userstats into a tool


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/350e1bef
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/350e1bef
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/350e1bef

Branch: refs/heads/db/5453
Commit: 350e1bef97f22d908336b3cea3bfec7ae9736c85
Parents: 0a7c119
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Feb 27 23:47:24 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:33 2013 +0000

----------------------------------------------------------------------
 Allura/allura/controllers/auth.py                  |   10 -
 Allura/allura/controllers/root.py                  |    4 +-
 .../ext/user_profile/templates/user_index.html     |    4 +-
 Allura/allura/ext/user_profile/user_main.py        |    9 +-
 Allura/allura/lib/app_globals.py                   |    2 +-
 Allura/allura/lib/plugin.py                        |    8 -
 Allura/allura/lib/widgets/forms.py                 |   15 -
 Allura/allura/model/project.py                     |    2 +
 Allura/allura/nf/allura/css/allura.css             |   13 +
 Allura/allura/templates/user_preferences.html      |    9 -
 .../forgeuserstats/controllers/userstats.py        |  235 +++++++++------
 ForgeUserStats/forgeuserstats/main.py              |   90 +++++-
 .../forgeuserstats/templates/artifacts.html        |    2 +-
 .../forgeuserstats/templates/commits.html          |    2 +-
 ForgeUserStats/forgeuserstats/templates/index.html |   32 +-
 .../forgeuserstats/templates/settings.html         |   19 ++
 .../forgeuserstats/templates/tickets.html          |    2 +-
 ForgeUserStats/forgeuserstats/widgets/forms.py     |   22 ++
 ForgeUserStats/setup.py                            |    6 +-
 19 files changed, 311 insertions(+), 175 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/Allura/allura/controllers/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index 1118d86..1b9c74e 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -58,7 +58,6 @@ class F(object):
     remove_inactive_period_form = forms.RemoveInactivePeriodForm()
     save_skill_form = forms.AddUserSkillForm()
     remove_skill_form = forms.RemoveSkillForm()
-    set_statistics = forms.StatsPreferencesForm()
 
 class AuthController(BaseController):
 
@@ -727,15 +726,6 @@ class SubscriptionsController(BaseController):
     @h.vardec
     @expose()
     @require_post()
-    @validate(F.set_statistics, error_handler=index)
-    def set_statistics(self, **kw):
-        require_authenticated()
-        c.user.stats.visible = kw.get('visible', True)
-        flash('Your preferences about statistics were successfully updated!')
-        redirect('.#Statistics')
-
-    @expose()
-    @require_post()
     def upload_sshkey(self, key=None):
         ap = plugin.AuthenticationProvider.get(request)
         try:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/Allura/allura/controllers/root.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/root.py b/Allura/allura/controllers/root.py
index a387b98..83aa5e5 100644
--- a/Allura/allura/controllers/root.py
+++ b/Allura/allura/controllers/root.py
@@ -69,9 +69,7 @@ class RootController(WsgiDispatchController):
         if n and not n.url_prefix.startswith('//'):
             n.bind_controller(self)
         self.browse = ProjectBrowseController()
-        ep = g.entry_points["stats"].get('userstats')
-        if ep and g.show_userstats:
-            self.userstats = ep().root
+
         super(RootController, self).__init__()
 
     def _setup_request(self):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/Allura/allura/ext/user_profile/templates/user_index.html
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/user_profile/templates/user_index.html b/Allura/allura/ext/user_profile/templates/user_index.html
index 7371eb6..2b17b79 100644
--- a/Allura/allura/ext/user_profile/templates/user_index.html
+++ b/Allura/allura/ext/user_profile/templates/user_index.html
@@ -236,11 +236,11 @@
     </div>
   </div>
 
-  {% if statslinkurl %}
+  {% if user.stats.visible %}
     <div class="grid-24">
       <div class="grid-24" style="margin:0;"><b>User statistics</b></div>
       <div class="grid-24" style="margin-top:5px;margin-bottom:5px;">
-        <div><a href="{{statslinkurl}}"/>{{statslinkdescription}}</a></div>
+        <div><a href="{{c.project.url()}}userstats"/>Go to the personal statistics of this user</a></div>
       </div>
     </div>
   {% endif %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/Allura/allura/ext/user_profile/user_main.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/user_profile/user_main.py b/Allura/allura/ext/user_profile/user_main.py
index f3c3331..7754228 100644
--- a/Allura/allura/ext/user_profile/user_main.py
+++ b/Allura/allura/ext/user_profile/user_main.py
@@ -64,14 +64,7 @@ class UserProfileController(BaseController):
         user = c.project.user_project_of
         if not user:
             raise exc.HTTPNotFound()
-        if g.show_userstats and user.stats.visible:
-            from forgeuserstats.main import ForgeUserStatsApp
-            link, description = ForgeUserStatsApp.createlink(user)
-        else:
-            link, description = None, None
-        return dict(user=user,
-                    statslinkurl = link,
-                    statslinkdescription = description)
+        return dict(user=user)
     # This will be fully implemented in a future iteration
     # @expose('jinja:allura.ext.user_profile:templates/user_subscriptions.html')
     # def subscriptions(self):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/Allura/allura/lib/app_globals.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index 632a29f..ee2c9b9 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -177,7 +177,7 @@ class Globals(object):
         statslisteners = []
         for name, ep in self.entry_points['stats'].iteritems():
             if config.get('%s.enable' % name,'false')=='true':
-                statslisteners.append(ep().listener)
+                statslisteners.append(ep())
         self.statsUpdater = PostEvent(statslisteners)
 
     @LazyProperty

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/Allura/allura/lib/plugin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index 59f03e5..ed704d4 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -736,14 +736,6 @@ class ThemeProvider(object):
         return RemoveInactivePeriodForm()
 
     @LazyProperty
-    def statistics_form(self):
-        '''
-        :return: None, or an easywidgets Form to render on the user preferences page
-        '''
-        from allura.lib.widgets.forms import StatsPreferencesForm
-        return StatsPreferencesForm(action='/auth/prefs/set_statistics')
-
-    @LazyProperty
     def add_trove_category(self):
         '''
         :return: None, or an easywidgets Form to render on the page to create a

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/Allura/allura/lib/widgets/forms.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/widgets/forms.py b/Allura/allura/lib/widgets/forms.py
index c1a1d75..96f832d 100644
--- a/Allura/allura/lib/widgets/forms.py
+++ b/Allura/allura/lib/widgets/forms.py
@@ -452,21 +452,6 @@ class RemoveTimeSlotForm(ForgeForm):
         return d
 
 
-class StatsPreferencesForm(ForgeForm):
-    defaults=dict(ForgeForm.defaults)
-
-    class fields(ew_core.NameList):
-        visible = ew.Checkbox(
-            label='Make my personal statistics visible to other users.')
-            
-    def display(self, **kw):
-        if kw.get('user').stats.visible:
-            self.fields['visible'].attrs = {'checked':'true'}      
-        else:
-            self.fields['visible'].attrs = {}    
-        return super(ForgeForm, self).display(**kw)
-                
-
 class RemoveTroveCategoryForm(ForgeForm):
     defaults=dict(ForgeForm.defaults, submit_text=None, show_errors=False)
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/Allura/allura/model/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 9f07f67..ab47f61 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -715,6 +715,8 @@ class Project(MappedClass, ActivityNode, ActivityObject):
                         ('admin', 'admin', 'Admin'),
                         ('search', 'search', 'Search'),
                         ('activity', 'activity', 'Activity')]
+                if g.show_userstats:
+                    apps = apps + [('userstats', 'userstats', 'Statistics')]
             else:
                 apps = [('admin', 'admin', 'Admin'),
                         ('search', 'search', 'Search'),

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/Allura/allura/nf/allura/css/allura.css
----------------------------------------------------------------------
diff --git a/Allura/allura/nf/allura/css/allura.css b/Allura/allura/nf/allura/css/allura.css
index 1f32750..f3863b8 100644
--- a/Allura/allura/nf/allura/css/allura.css
+++ b/Allura/allura/nf/allura/css/allura.css
@@ -60,6 +60,11 @@ b.ico.ico-vote-down { background-image: url('../images/vote_down.png'); }
   background-repeat: no-repeat;
 }
 
+.ui-icon-tool-userstats {
+  background-image: url("../images/stats_24.png");
+  background-repeat: no-repeat;
+}
+
 .ui-icon-tool-admin, .ui-icon-admin {
   background-image: url("../images/admin_24.png");
   background-repeat: no-repeat;
@@ -118,6 +123,10 @@ b.ico.ico-vote-down { background-image: url('../images/vote_down.png'); }
 #top_nav .ui-icon-tool-stats {
   background-image: url("../images/stats_32.png");
 }
+#top_nav .ui-icon-tool-userstats {
+  background-image: url("../images/stats_32.png");
+}
+
 #top_nav .ui-icon-tool-admin, #top_nav .ui-icon-admin {
   background-image: url("../images/admin_32.png");
 }
@@ -152,6 +161,10 @@ b.ico.ico-vote-down { background-image: url('../images/vote_down.png'); }
 .big_icon.ui-icon-tool-stats {
   background-image: url("../images/stats_48.png");
 }
+.big_icon.ui-icon-tool-userstats {
+  background-image: url("../images/stats_48.png");
+}
+
 .big_icon.ui-icon-tool-admin, .big_icon.ui-icon-admin {
   background-image: url("../images/admin_48.png");
 }

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/Allura/allura/templates/user_preferences.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/user_preferences.html b/Allura/allura/templates/user_preferences.html
index bd4c71e..768cd2e 100644
--- a/Allura/allura/templates/user_preferences.html
+++ b/Allura/allura/templates/user_preferences.html
@@ -129,15 +129,6 @@
     <ul><li><a href="/auth/prefs/user_skills">Click here to check and change your skills list</a></li></ul>
   </div>
 
-  {% if g.show_userstats %}
-    <a name="Statistics"></a>
-    <div class="grid-20">
-      <h2>Contribution statistics</h2>
-      <ul><li><a href="/userstats/{{c.user.username}}">Click here to check your personal statistics</a></li></ul>
-      {{g.theme.statistics_form.display(user=c.user)}}
-    </div>
-  {% endif %}
-
   {% if g.theme.password_change_form %}
   <div class="grid-20">
     <h2>Change Password</h2>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/ForgeUserStats/forgeuserstats/controllers/userstats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/controllers/userstats.py b/ForgeUserStats/forgeuserstats/controllers/userstats.py
index 40ad92c..b93e77b 100644
--- a/ForgeUserStats/forgeuserstats/controllers/userstats.py
+++ b/ForgeUserStats/forgeuserstats/controllers/userstats.py
@@ -1,4 +1,4 @@
-from tg import expose
+from tg import expose, validate, redirect
 from tg.decorators import with_trailing_slash
 from datetime import datetime
 from allura.controllers import BaseController
@@ -6,33 +6,86 @@ import allura.model as M
 from allura.lib.graphics.graphic_methods import create_histogram, create_progress_bar
 from forgeuserstats.model.stats import UserStats
 from pylons import tmpl_context as c
+from allura.lib.security import require_access
+from forgeuserstats.widgets.forms import StatsPreferencesForm
+from allura.lib.decorators import require_post
+from allura.lib import validators as V
 
-class ForgeUserStatsController(BaseController):
+stats_preferences_form = StatsPreferencesForm()
+
+class ForgeUserStatsCatController(BaseController):
 
     @expose()
-    def _lookup(self, part, *remainder):
-        user = M.User.query.get(username=part)
+    def _lookup(self, category, *remainder):
+        cat = M.TroveCategory.query.get(shortname=category)
+        return ForgeUserStatsCatController(category = cat), remainder
 
-        if not self.user:
-            return ForgeUserStatsController(user=user), remainder
-        if part == "category":
-            return ForgeUserStatsCatController(self.user, None), remainder
-        if part == "metric":
-            return ForgeUserStatsMetricController(self.user), remainder
+    def __init__(self, category = None):
+        self.category = category
+        super(ForgeUserStatsCatController, self).__init__()
 
-    def __init__(self, user=None):
-        self.user = user
-        if self.user:
-            if not user.stats:
-                UserStats.create(self.user)
+    @expose('jinja:forgeuserstats:templates/index.html')
+    @with_trailing_slash
+    def index(self, **kw):
+        self.user = c.project.user_project_of
+        if not self.user: 
+            return None
+        stats = self.user.stats
+        if (not stats.visible) and (c.user != self.user):
+            return dict(user=self.user)
+        
+        cat_id = None
+        if self.category: 
+            cat_id = self.category._id
+        ret_dict = _getDataForCategory(cat_id, stats)
+        ret_dict['user'] = self.user
+        ret_dict['registration_date'] = stats.registration_date
+        ret_dict['category'] = self.category
+        return ret_dict
 
-        super(ForgeUserStatsController, self).__init__()
+class ForgeUserStatsController(BaseController):
+
+    category = ForgeUserStatsCatController()            
+    
+    @expose('jinja:forgeuserstats:templates/settings.html')
+    @with_trailing_slash
+    def settings(self, **kw):
+        require_access(c.project, 'admin')
+
+        self.user = c.project.user_project_of
+        if not self.user: 
+            return dict(user=None)
+        if not self.user.stats:
+            UserStats.create(self.user)
+        return dict(
+            user = self.user, 
+            form = StatsPreferencesForm(
+                action = c.project.url() + 'userstats/change_settings'))
+      
+    @expose()
+    @require_post()
+    @validate(stats_preferences_form, error_handler=settings)
+    def change_settings(self, **kw):
+        require_access(c.project, 'admin')
+
+        self.user = c.project.user_project_of
+        if not self.user: 
+            return dict(user=None)
+        if not self.user.stats:
+            UserStats.create(self.user)
+        visible = kw.get('visible')
+        self.user.stats.visible = visible
+        redirect(c.project.url() + 'userstats/settings')
 
     @expose('jinja:forgeuserstats:templates/index.html')
     @with_trailing_slash
     def index(self, **kw):
+        self.user = c.project.user_project_of
         if not self.user: 
             return dict(user=None)
+        if not self.user.stats:
+            UserStats.create(self.user)
+
         stats = self.user.stats
         if (not stats.visible) and (c.user != self.user):
             return dict(user=self.user)
@@ -79,11 +132,71 @@ class ForgeUserStatsController(BaseController):
             stats.getMaxAndAverageDiscussionContribution()
         ret_dict['maxticketcontrib'], ret_dict['averageticketcontrib'] =\
             stats.getMaxAndAverageTicketsSolvingPercentage()
-
+        
         return ret_dict
 
+
+    @expose('jinja:forgeuserstats:templates/commits.html')
+    @with_trailing_slash
+    def commits(self, **kw):
+        self.user = c.project.user_project_of
+        if not self.user: 
+            return dict(user=None)
+        if not self.user.stats:
+            UserStats.create(self.user)
+        stats = self.user.stats
+
+        if (not stats.visible) and (c.user != self.user):
+            return dict(user=self.user)
+        
+        commits = stats.getCommitsByCategory()
+        return dict(
+            user = self.user,
+            data = commits) 
+
+    @expose('jinja:forgeuserstats:templates/artifacts.html')
+    @with_trailing_slash
+    def artifacts(self, **kw):
+        self.user = c.project.user_project_of
+        if not self.user: 
+            return dict(user=None)
+        if not self.user.stats:
+            UserStats.create(self.user)
+        stats = self.user.stats
+
+        if (not stats.visible) and (c.user != self.user):
+            return dict(user=self.user)
+
+        stats = self.user.stats       
+        artifacts = stats.getArtifactsByCategory(detailed=True)
+        return dict(
+            user = self.user,
+            data = artifacts)
+
+    @expose('jinja:forgeuserstats:templates/tickets.html')
+    @with_trailing_slash
+    def tickets(self, **kw):
+        self.user = c.project.user_project_of
+        if not self.user: 
+            return dict(user=None)
+        if not self.user.stats:
+            UserStats.create(self.user)
+        stats = self.user.stats
+
+        if (not stats.visible) and (c.user != self.user):
+            return dict(user=self.user)
+
+        artifacts = self.user.stats.getTicketsByCategory()
+        return dict(
+            user=self.user,
+            data=artifacts)
+
     @expose()
     def categories_graph(self):
+        self.user = c.project.user_project_of
+        if not self.user: 
+            return None
+
         categories = {}
         for p in self.user.my_projects():
             for cat in p.trove_topic:
@@ -109,89 +222,27 @@ class ForgeUserStatsController(BaseController):
 
     @expose()
     def code_ranking_bar(self):
-        return create_progress_bar(self.user.stats.codeRanking())
+        self.user = c.project.user_project_of
+        if not self.user: 
+            return None
+        stats = self.user.stats
+        return create_progress_bar(stats.codeRanking())
 
     @expose()
     def discussion_ranking_bar(self):
-        return create_progress_bar(self.user.stats.discussionRanking())
+        self.user = c.project.user_project_of
+        if not self.user: 
+            return None
+        stats = self.user.stats
+        return create_progress_bar(stats.discussionRanking())
 
     @expose()
     def tickets_ranking_bar(self):
-        return create_progress_bar(self.user.stats.ticketsRanking())
-
-class ForgeUserStatsCatController(BaseController):
-    @expose()
-    def _lookup(self, category, *remainder):
-        cat = M.TroveCategory.query.get(shortname=category)
-        return ForgeUserStatsCatController(self.user, cat), remainder
-
-    def __init__(self, user, category):
-        self.user = user
-        self.category = category
-        super(ForgeUserStatsCatController, self).__init__()
-
-    @expose('jinja:forgeuserstats:templates/index.html')
-    @with_trailing_slash
-    def index(self, **kw):
-        if not self.user:
-            return dict(user=None)
-        stats = self.user.stats
-        if (not stats.visible) and (c.user != self.user):
-            return dict(user=self.user)
-        
-        cat_id = None
-        if self.category: 
-            cat_id = self.category._id
-        ret_dict = _getDataForCategory(cat_id, stats)
-        ret_dict['user'] = self.user
-        ret_dict['registration_date'] = stats.registration_date
-        ret_dict['category'] = self.category
-        
-        return ret_dict
-
-class ForgeUserStatsMetricController(BaseController):
-
-    def __init__(self, user):
-        self.user = user
-        super(ForgeUserStatsMetricController, self).__init__()
-
-    @expose('jinja:forgeuserstats:templates/commits.html')
-    @with_trailing_slash
-    def commits(self, **kw):
-        if not self.user:
-            return dict(user=None)
-        stats = self.user.stats
-        if (not stats.visible) and (c.user != self.user):
-            return dict(user=self.user)
-        
-        commits = stats.getCommitsByCategory()
-        return dict(user = self.user,
-                    data = commits) 
-
-    @expose('jinja:forgeuserstats:templates/artifacts.html')
-    @with_trailing_slash
-    def artifacts(self, **kw):
-        if not self.user:
-            return dict(user=None)
-        stats = self.user.stats
-        if (not stats.visible) and (c.user != self.user):
-            return dict(user=self.user)
-
-        stats = self.user.stats       
-        artifacts = stats.getArtifactsByCategory(detailed=True)
-        return dict(user = self.user, data = artifacts) 
-
-    @expose('jinja:forgeuserstats:templates/tickets.html')
-    @with_trailing_slash
-    def tickets(self, **kw):
+        self.user = c.project.user_project_of
         if not self.user: 
-            return dict(user=None)
+            return None
         stats = self.user.stats
-        if (not stats.visible) and (c.user != self.user):
-            return dict(user=self.user)
-
-        artifacts = self.user.stats.getTicketsByCategory()
-        return dict(user = self.user, data = artifacts) 
+        return create_progress_bar(stats.ticketsRanking())
 
 def _getDataForCategory(category, stats):
     totcommits = stats.getCommits(category)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/ForgeUserStats/forgeuserstats/main.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/main.py b/ForgeUserStats/forgeuserstats/main.py
index 8eeb113..dcecdb9 100644
--- a/ForgeUserStats/forgeuserstats/main.py
+++ b/ForgeUserStats/forgeuserstats/main.py
@@ -1,10 +1,24 @@
+#-*- python -*-
 import logging
+from pylons import tmpl_context as c
+import formencode
+from formencode import validators
+from webob import exc
 from datetime import datetime
 
+from allura.app import Application, SitemapEntry
+from allura.lib import helpers as h
+from allura.lib.security import has_access
+from allura import model as M
 from allura.eventslistener import EventsListener
 from model.stats import UserStats
 from controllers.userstats import ForgeUserStatsController
 
+from forgeuserstats import version
+from forgeuserstats.controllers.userstats import ForgeUserStatsController
+
+from ming.orm import session
+
 log = logging.getLogger(__name__)
 
 class UserStatsListener(EventsListener):
@@ -55,12 +69,74 @@ class UserStatsListener(EventsListener):
     def newOrganization(self, organization):
         pass
 
-class ForgeUserStatsApp:
+class ForgeUserStatsApp(Application):
+    __version__ = version.__version__
+    tool_label='Statistics'
+    default_mount_label='Statistics'
+    default_mount_point='stats'
+    permissions = ['configure', 'read', 'write',
+                    'unmoderated_post', 'post', 'moderate', 'admin']
+    ordinal=15
+    installable=False
+    config_options = Application.config_options
+    default_external_feeds = []
+    icons={
+        24:'images/stats_24.png',
+        32:'images/stats_32.png',
+        48:'images/stats_48.png'
+    }
     root = ForgeUserStatsController()
-    listener = UserStatsListener()
 
-    @classmethod
-    def createlink(cls, user):
-        return (
-            "/userstats/%s/" % user.username, 
-            "%s personal statistcs" % user.display_name)
+    def __init__(self, project, config):
+        Application.__init__(self, project, config)
+        role_admin = M.ProjectRole.by_name('Admin')._id
+        role_anon = M.ProjectRole.by_name('*anonymous')._id
+        self.config.acl = [
+            M.ACE.allow(role_anon, 'read'),
+            M.ACE.allow(role_admin, 'admin')]
+
+    def main_menu(self):
+        return [SitemapEntry(self.config.options.mount_label.title(), '.')]
+
+    @property
+    @h.exceptionless([], log)
+    def sitemap(self):
+        menu_id = self.config.options.mount_label.title()
+        with h.push_config(c, app=self):
+            return [
+                SitemapEntry(menu_id, '.')[self.sidebar_menu()] ]
+
+    @property
+    def show_discussion(self):
+        if 'show_discussion' in self.config.options:
+            return self.config.options['show_discussion']
+        else:
+            return True
+
+    @h.exceptionless([], log)
+    def sidebar_menu(self):
+        base = c.app.url
+        links = [SitemapEntry('Overview', base),
+                 SitemapEntry('Commits', base + 'commits'),
+                 SitemapEntry('Artifacts', base + 'artifacts'),
+                 SitemapEntry('Tickets', base + 'tickets')]
+        return links
+
+    def admin_menu(self):
+        links = [SitemapEntry(
+                     'Settings', c.project.url() + 'userstats/settings')]
+        return links
+
+    def install(self, project):
+        #It doesn't make any sense to install the tool twice on the same 
+        #project therefore, if it already exists, it doesn't install it
+        #a second time.
+        for tool in project.app_configs:
+            if tool.tool_name == 'userstats':
+                if self.config.options.mount_point!=tool.options.mount_point:
+                    project.uninstall_app(self.config.options.mount_point)
+                    return
+
+    def uninstall(self, project):
+        self.config.delete()
+        session(self.config).flush()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/ForgeUserStats/forgeuserstats/templates/artifacts.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/artifacts.html b/ForgeUserStats/forgeuserstats/templates/artifacts.html
index 013c108..ede6dde 100644
--- a/ForgeUserStats/forgeuserstats/templates/artifacts.html
+++ b/ForgeUserStats/forgeuserstats/templates/artifacts.html
@@ -11,7 +11,7 @@
 
   {% if user and (user.stats.visible or (c.user == user)) %}
     <div class="grid-20">
-      <ul><li><a href="/userstats/{{user.username}}">Go back to general statistics</a></li></ul>
+      <ul><li><a href="{{c.project.url()}}userstats">Go back to general statistics</a></li></ul>
     </div>
 
     {% if data %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/ForgeUserStats/forgeuserstats/templates/commits.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/commits.html b/ForgeUserStats/forgeuserstats/templates/commits.html
index 10d1c67..f0aca6f 100644
--- a/ForgeUserStats/forgeuserstats/templates/commits.html
+++ b/ForgeUserStats/forgeuserstats/templates/commits.html
@@ -11,7 +11,7 @@
 
   {% if user and (user.stats.visible or (c.user == user)) %}
     <div class="grid-20">
-      <ul><li><a href="/userstats/{{user.username}}">Go back to general statistics</a></li></ul>
+      <ul><li><a href="{{c.project.url()}}userstats">Go back to general statistics</a></li></ul>
     </div>
 
     {% if data %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/ForgeUserStats/forgeuserstats/templates/index.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/index.html b/ForgeUserStats/forgeuserstats/templates/index.html
index 653cd31..ae58cf8 100644
--- a/ForgeUserStats/forgeuserstats/templates/index.html
+++ b/ForgeUserStats/forgeuserstats/templates/index.html
@@ -15,7 +15,7 @@
 
     {% if category %}
        <ul>
-         <li><a href="/userstats/{{user.username}}">Go back to general statistics</a></li>
+         <li><a href="{{c.project.url()}}userstats">Go back to general statistics</a></li>
        </ul>
     {% endif %}
     <h2>General statistics</h2>
@@ -80,7 +80,7 @@
         <tr>
           <td>
             {% if totcommits.number > 0 %}
-              <a href="/userstats/{{user.username}}/metric/commits/">Commits number</a>
+              <a href="{{c.project.url()}}userstats/commits/">Commits number</a>
             {% else %}
               Commits number
             {% endif %}
@@ -103,7 +103,7 @@
         <tr>
           <td>
             {% if totcommits.lines > 0 %}
-              <a href="/userstats/{{user.username}}/metric/commits/">Added/modified LOCs</a>
+              <a href="{{c.project.url()}}userstats/commits/">Added/modified LOCs</a>
             {% else %}
               Added/modified LOCs
             {% endif %}
@@ -126,7 +126,7 @@
         <tr>
           <td>
             {% if totartifacts.created > 0 %}
-              <a href="/userstats/{{user.username}}/metric/artifacts/">Total number of created artifacts</a>
+              <a href="{{c.project.url()}}userstats/artifacts/">Total number of created artifacts</a>
             {% else %}
               Total number of created artifacts
             {% endif %}
@@ -149,7 +149,7 @@
         <tr>
           <td>
             {% if totartifacts.modified > 0 %}
-              <a href="/userstats/{{user.username}}/metric/artifacts/">Total number of edited artifacts</a>
+              <a href="{{c.project.url()}}userstats/artifacts/">Total number of edited artifacts</a>
             {% else %}
               Total number of edited artifacts
             {% endif %}
@@ -174,7 +174,7 @@
           <tr>
             <td>
               {% if value.created > 0 %}
-                <a href="/userstats/{{user.username}}/metric/artifacts/">Created {{key}} artifacts</a>
+                <a href="{{c.project.url()}}userstats/artifacts/">Created {{key}} artifacts</a>
               {% else %}
                 Created {{key}} artifacts
               {% endif %}
@@ -205,7 +205,7 @@
           <tr>
             <td>
               {% if value.modified > 0 %}
-                <a href="/userstats/{{user.username}}/metric/artifacts/">Edited {{key}} artifacts</a>
+                <a href="{{c.project.url()}}userstats/artifacts/">Edited {{key}} artifacts</a>
               {% else %}
                 Edited {{key}} artifacts
               {% endif %}
@@ -238,7 +238,7 @@
         <tr>
           <td>
             {% if tottickets.assigned > 0 %}
-              <a href="/userstats/{{user.username}}/metric/tickets/">Assigned tickets</a>
+              <a href="{{c.project.url()}}userstats/tickets/">Assigned tickets</a>
             {% else %}
               Assigned tickets
             {% endif %}
@@ -261,7 +261,7 @@
         <tr>
           <td>
             {% if tottickets.revoked > 0 %}
-              <a href="/userstats/{{user.username}}/metric/tickets/">Revoked tickets</a>
+              <a href="{{c.project.url()}}userstats/tickets/">Revoked tickets</a>
             {% else %}
               Revoked tickets
             {% endif %}
@@ -284,7 +284,7 @@
         <tr>
           <td>
             {% if tottickets.solved > 0 %}
-              <a href="/userstats/{{user.username}}/metric/tickets/">Solved tickets</a>
+              <a href="{{c.project.url()}}userstats/tickets/">Solved tickets</a>
             {% else %}
               Solved tickets
             {% endif %}
@@ -307,7 +307,7 @@
         <tr>
           <td>
             {% if tottickets.averagesolvingtime > 0 %}
-              <a href="/userstats/{{user.username}}/metric/tickets/">Average tickets solving time</a>
+              <a href="{{c.project.url()}}userstats/tickets/">Average tickets solving time</a>
             {% else %}
               Average tickets solving time
             {% endif %}
@@ -357,7 +357,7 @@
           <tbody>
             {% for cat, count in categories %}
               <tr>
-                <td><a href="/userstats/{{user.username}}/category/{{cat.shortname}}">{{cat.fullname}}</a></td>
+                <td><a href="{{c.project.url()}}userstats/category/{{cat.shortname}}">{{cat.fullname}}</a></td>
                 <td>{{count}}</td>
               </tr>
             {% endfor %}
@@ -369,7 +369,7 @@
             The same data listed in the previous table is graphically presented by the following histogram.
           </p>
           <p>
-            <img src="/userstats/{{user.username}}/categories_graph"/>
+            <img src="{{c.project.url()}}userstats/categories_graph"/>
           </p>
         {% endif %}
     {% endif %}
@@ -391,21 +391,21 @@
             <td>{{codecontribution}} LOC{% if codecontribution != 1 %}s{% endif %}/month</td>
             <td>{{averagecodecontrib}} LOC{% if averagecodecontrib != 1 %}s{% endif %}/month</td>
             <td>{{maxcodecontrib}} LOC{% if maxcodecontrib != 1 %}s{% endif %}/month</td>
-            <td><img src="/userstats/{{user.username}}/code_ranking_bar"/> {{codepercentage}} %</td>
+            <td><img src="{{c.project.url()}}userstats/code_ranking_bar"/> {{codepercentage}} %</td>
           </tr>
           <tr>
             <td>Discussion</td>
             <td>{{discussioncontribution}} contr./month</td>
             <td>{{averagedisccontrib}} contr./month</td>
             <td>{{maxdisccontrib}} contr./month</td>
-            <td><img src="/userstats/{{user.username}}/discussion_ranking_bar"/> {{discussionpercentage}} %</td>
+            <td><img src="{{c.project.url()}}userstats/discussion_ranking_bar"/> {{discussionpercentage}} %</td>
           </tr>
           <tr>
             <td>Solved issues</td>
             <td>{{ticketcontribution}} %</td>
             <td>{{averageticketcontrib}} %</td>
             <td>{{maxticketcontrib}} %</td>
-            <td><img src="/userstats/{{user.username}}/tickets_ranking_bar"/> {{ticketspercentage}} %</td>
+            <td><img src="{{c.project.url()}}userstats/tickets_ranking_bar"/> {{ticketspercentage}} %</td>
           </tr>
         </tbody>
       </table>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/ForgeUserStats/forgeuserstats/templates/settings.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/settings.html b/ForgeUserStats/forgeuserstats/templates/settings.html
new file mode 100644
index 0000000..c07301a
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/templates/settings.html
@@ -0,0 +1,19 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}User stats – Settings{% endblock %}
+
+{% block header %}
+    Statistics about {{user.display_name}}'s contribution – Settings
+{% endblock %}
+
+{% block content %}
+
+    <div class="grid-20">
+      In this page you can set the visibility of your personal statistics. If you decide to hide your personal statistics to 
+      other users, data collected about your contributions to projects hosted on this forge will be available only for your
+      personal use. 
+      {{form.display(user = user)}}
+    </div>
+
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/ForgeUserStats/forgeuserstats/templates/tickets.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/tickets.html b/ForgeUserStats/forgeuserstats/templates/tickets.html
index a713b25..0252021 100644
--- a/ForgeUserStats/forgeuserstats/templates/tickets.html
+++ b/ForgeUserStats/forgeuserstats/templates/tickets.html
@@ -11,7 +11,7 @@
 
   {% if user and (user.stats.visible or (c.user == user)) %}
     <div class="grid-20">
-      <ul><li><a href="/userstats/{{user.username}}">Go back to general statistics</a></li></ul>
+      <ul><li><a href="{{c.project.url()}}userstats">Go back to general statistics</a></li></ul>
     </div>
 
     {% if data %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/ForgeUserStats/forgeuserstats/widgets/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/widgets/__init__.py b/ForgeUserStats/forgeuserstats/widgets/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/ForgeUserStats/forgeuserstats/widgets/forms.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/widgets/forms.py b/ForgeUserStats/forgeuserstats/widgets/forms.py
new file mode 100644
index 0000000..c4da4fb
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/widgets/forms.py
@@ -0,0 +1,22 @@
+from allura.lib import validators as V
+from allura.lib.widgets.forms import ForgeForm
+
+from formencode import validators as fev
+
+import ew as ew_core
+import ew.jinja2_ew as ew
+
+class StatsPreferencesForm(ForgeForm):
+    defaults=dict(ForgeForm.defaults)
+
+    class fields(ew_core.NameList):
+        visible = ew.Checkbox(
+            label='Make my personal statistics visible to other users.')
+            
+    def display(self, **kw):
+        if kw.get('user').stats.visible:
+            self.fields['visible'].attrs = {'checked':'true'}      
+        else:
+            self.fields['visible'].attrs = {}    
+        return super(ForgeForm, self).display(**kw)
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/350e1bef/ForgeUserStats/setup.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/setup.py b/ForgeUserStats/setup.py
index dc2f07b..733ebd8 100644
--- a/ForgeUserStats/setup.py
+++ b/ForgeUserStats/setup.py
@@ -23,7 +23,11 @@ setup(name='ForgeUserStats',
       ],
       entry_points="""
       # -*- Entry points: -*-
-      [allura.stats]
+      [allura]
       userstats=forgeuserstats.main:ForgeUserStatsApp
+
+      [allura.stats]
+      userstats=forgeuserstats.main:UserStatsListener
+
       """,
       )


[13/42] git commit: [5453] Added unit tests to userstats

Posted by br...@apache.org.
[5453] Added unit tests to userstats


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/169e9d98
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/169e9d98
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/169e9d98

Branch: refs/heads/db/5453
Commit: 169e9d98e8709a105ffae0e4bbd22b4e842e1b71
Parents: 0bd0617
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sun Jan 20 16:33:25 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:31 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/contrib_stats.py               |  635 +++++++++++++++
 .../forgeuserstats/controllers/userstats.py        |    1 -
 ForgeUserStats/forgeuserstats/main.py              |   10 +-
 ForgeUserStats/forgeuserstats/model/stats.py       |  631 +--------------
 ForgeUserStats/forgeuserstats/tests/test_model.py  |  375 +++++++++
 ForgeUserStats/forgeuserstats/tests/test_stats.py  |   33 -
 6 files changed, 1029 insertions(+), 656 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/169e9d98/Allura/allura/model/contrib_stats.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/contrib_stats.py b/Allura/allura/model/contrib_stats.py
new file mode 100644
index 0000000..8a71d86
--- /dev/null
+++ b/Allura/allura/model/contrib_stats.py
@@ -0,0 +1,635 @@
+import pymongo
+from pylons import c, g, request
+
+import bson
+from ming import schema as S
+from ming import Field, Index, collection
+from ming.orm import session, state, Mapper
+from ming.orm import FieldProperty
+from ming.orm.declarative import MappedClass
+from datetime import datetime, timedelta
+import difflib
+
+from allura.model.session import main_orm_session
+from allura.lib import helpers as h
+
+class Stats(MappedClass):
+    class __mongometa__:
+        name='stats'
+        session = main_orm_session
+        unique_indexes = [ '_id']
+
+    _id=FieldProperty(S.ObjectId)
+
+    registration_date = FieldProperty(datetime)
+    general = FieldProperty([dict(
+        category = S.ObjectId,
+        messages = [dict(
+            messagetype = str,
+            created = int,
+            modified = int)],
+        tickets = dict(
+            solved = int,
+            assigned = int,
+            revoked = int,
+            totsolvingtime = int),
+        commits = [dict(
+            lines = int,
+            number = int,
+            language = S.ObjectId)])])
+
+    lastmonth=FieldProperty(dict(
+        messages=[dict(
+            datetime=datetime,
+            created=bool,
+            categories=[S.ObjectId],
+            messagetype=str)],
+        assignedtickets=[dict(
+            datetime=datetime,
+            categories=[S.ObjectId])],
+        revokedtickets=[dict(
+            datetime=datetime,
+            categories=[S.ObjectId])],
+        solvedtickets=[dict(
+            datetime=datetime,
+            categories=[S.ObjectId],
+            solvingtime=int)],
+        commits=[dict(
+            datetime=datetime,
+            categories=[S.ObjectId],
+            programming_languages=[S.ObjectId],
+            lines=int)]))
+
+    def getCodeContribution(self):
+        days=(datetime.today() - self.registration_date).days
+        if not days:
+            days=1
+        for val in self['general']:
+            if val['category'] is None:
+                for commits in val['commits']:
+                    if commits['language'] is None: 
+                        if days > 30:
+                            return round(float(commits.lines)/days*30, 2)
+                        else:
+                            return float(commits.lines)
+        return 0
+
+    def getDiscussionContribution(self):
+        days=(datetime.today() - self.registration_date).days
+        if not days:
+            days=1
+        for val in self['general']:
+            if val['category'] is None:
+                for artifact in val['messages']:
+                    if artifact['messagetype'] is None: 
+                        tot = artifact.created+artifact.modified
+                        if days > 30:
+                            return round(float(tot)/days*30,2)
+                        else:
+                            return float(tot)
+        return 0
+
+    def getTicketsContribution(self):
+        for val in self['general']:
+            if val['category'] is None:
+                tickets = val['tickets']
+                if tickets.assigned == 0:
+                    return 0
+                return float(tickets.solved) / tickets.assigned
+        return 0
+
+    @classmethod
+    def getMaxAndAverageCodeContribution(self):
+        lst = list(self.query.find())
+        n = len(lst)
+        if n == 0:
+            return 0, 0
+        maxcontribution=max([x.getCodeContribution() for x in lst])
+        averagecontribution=sum([x.getCodeContribution() for x in lst]) / n
+        return maxcontribution, round(averagecontribution, 2)
+
+    @classmethod
+    def getMaxAndAverageDiscussionContribution(self):
+        lst = list(self.query.find())
+        n = len(lst)
+        if n == 0:
+            return 0, 0
+        maxcontribution=max([x.getDiscussionContribution() for x in lst])
+        averagecontribution=sum([x.getDiscussionContribution() for x in lst])/n
+        return maxcontribution, round(averagecontribution, 2)
+
+    @classmethod
+    def getMaxAndAverageTicketsSolvingPercentage(self):
+        lst = list(self.query.find())
+        n = len(lst)
+        if n == 0:
+            return 0, 0
+        maxcontribution=max([x.getTicketsContribution() for x in lst])
+        averagecontribution=sum([x.getTicketsContribution() for x in lst])/n
+        return maxcontribution, round(averagecontribution, 2)
+
+    def codeRanking(self):
+        lst = list(self.query.find())
+        totn = len(lst)
+        codcontr = self.getCodeContribution()
+        upper = len([x for x in lst if x.getCodeContribution() > codcontr])
+        return round((totn - upper) * 100.0 / totn, 2)
+
+    def discussionRanking(self):
+        lst = list(self.query.find())
+        totn = len(lst)
+        disccontr = self.getDiscussionContribution()
+        upper=len([x for x in lst if x.getDiscussionContribution()>disccontr])
+        return round((totn - upper) * 100.0 / totn, 2)
+
+    def ticketsRanking(self):
+        lst = list(self.query.find())
+        totn = len(lst)
+        ticketscontr = self.getTicketsContribution()
+        upper=len([x for x in lst if x.getTicketsContribution()>ticketscontr])
+        return round((totn - upper) * 100.0 / totn, 2)
+
+    def getCommits(self, category = None):
+        i = getElementIndex(self.general, category = category)
+        if i is None: 
+            return dict(number=0, lines=0)
+        cat = self.general[i]
+        j = getElementIndex(cat.commits, language = None)
+        if j is None:
+            return dict(number=0, lines=0)
+        return dict(
+            number=cat.commits[j]['number'], 
+            lines=cat.commits[j]['lines'])
+
+    def getArtifacts(self, category = None, art_type = None):
+        i = getElementIndex(self.general, category = category)
+        if i is None:
+            return dict(created=0, modified=0)
+        cat = self.general[i]
+        j = getElementIndex(cat.messages, messagetype = art_type)
+        if j is None:
+            return dict(created=0, modified=0)
+        return dict(created=cat.messages[j].created, modified=cat.messages[j].modified)
+
+    def getTickets(self, category = None):
+        i = getElementIndex(self.general, category = category)
+        if i is None:
+            return dict(
+                assigned=0,
+                solved=0,
+                revoked=0,
+                averagesolvingtime=None)
+        if self.general[i].tickets.solved > 0:
+            tot = self.general[i].tickets.totsolvingtime 
+            number = self.general[i].tickets.solved
+            average = tot / number
+        else: 
+            average = None
+        return dict(
+            assigned=self.general[i].tickets.assigned,
+            solved=self.general[i].tickets.solved,
+            revoked=self.general[i].tickets.revoked,
+            averagesolvingtime=_convertTimeDiff(average))
+
+    def getCommitsByCategory(self):
+        from allura.model.project import TroveCategory
+
+        by_cat = {}
+        for entry in self.general:
+            cat = entry.category
+            i = getElementIndex(entry.commits, language = None)
+            if i is None: 
+                n, lines = 0, 0
+            else: 
+                n, lines = entry.commits[i].number, entry.commits[i].lines
+            if cat != None:
+                cat = TroveCategory.query.get(_id = cat)
+            by_cat[cat] = dict(number=n, lines=lines)
+        return by_cat
+
+    #For the moment, commit stats by language are not used, since each project
+    #can be linked to more than one programming language and we don't know how
+    #to which programming language should be credited a line of code modified
+    #within a project including two or more languages.
+    def getCommitsByLanguage(self):
+        langlist = []
+        by_lang = {}
+        i = getElementIndex(self.general, category=None)
+        if i is None: 
+            return dict(number=0, lines=0)
+        return dict([(el.language, dict(lines=el.lines, number=el.number))
+                     for el in self.general[i].commits])
+
+    def getArtifactsByCategory(self, detailed=False):
+        from allura.model.project import TroveCategory
+
+        by_cat = {}
+        for entry in self.general:
+            cat = entry.category
+            if cat != None: 
+                cat = TroveCategory.query.get(_id = cat)
+            if detailed: 
+                by_cat[cat] = entry.messages
+            else:
+                i = getElementIndex(entry.messages, messagetype=None)
+                if i is not None:
+                    by_cat[cat] = entry.messages[i]
+                else: 
+                    by_cat[cat] = dict(created=0, modified=0)
+        return by_cat
+
+    def getArtifactsByType(self, category=None):
+        i = getElementIndex(self.general, category = category)
+        if i is None: 
+            return {}
+        entry = self.general[i].messages
+        by_type = dict([(el.messagetype, dict(created=el.created,
+                                              modified=el.modified))
+                         for el in entry])
+        return by_type
+
+    def getTicketsByCategory(self):
+        from allura.model.project import TroveCategory
+
+        by_cat = {}
+        for entry in self.general:
+            cat = entry.category
+            if cat != None:
+                cat = TroveCategory.query.get(_id = cat)
+            a, s = entry.tickets.assigned, entry.tickets.solved
+            r, time = entry.tickets.solved, entry.tickets.totsolvingtime
+            if s:
+                average = time / s
+            else:
+                average = None
+            by_cat[cat] = dict(
+                assigned=a,
+                solved=s,
+                revoked=r, 
+                averagesolvingtime=_convertTimeDiff(average))
+        return by_cat
+
+    def getLastMonthCommits(self, category = None):
+        self.checkOldArtifacts() 
+        lineslist = [el.lines for el in self.lastmonth.commits
+                     if category in el.categories + [None]]
+        return dict(number=len(lineslist), lines=sum(lineslist))
+
+    def getLastMonthCommitsByCategory(self):
+        from allura.model.project import TroveCategory
+
+        self.checkOldArtifacts() 
+        seen = set()
+        catlist=[el.category for el in self.general
+                 if el.category not in seen and not seen.add(el.category)]
+
+        by_cat = {}
+        for cat in catlist:
+            lineslist = [el.lines for el in self.lastmonth.commits
+                         if cat in el.categories + [None]]
+            n = len(lineslist)
+            lines = sum(lineslist)
+            if cat != None:
+                cat = TroveCategory.query.get(_id = cat)
+            by_cat[cat] = dict(number=n, lines=lines)
+        return by_cat
+
+    def getLastMonthCommitsByLanguage(self):
+        from allura.model.project import TroveCategory
+
+        self.checkOldArtifacts() 
+        seen = set()
+        langlist=[el.language for el in self.general
+                  if el.language not in seen and not seen.add(el.language)]
+
+        by_lang = {}
+        for lang in langlist:
+            lineslist = [el.lines for el in self.lastmonth.commits
+                         if lang in el.programming_languages + [None]]
+            n = len(lineslist)
+            lines = sum(lineslist)
+            if lang != None:
+                lang = TroveCategory.query.get(_id = lang)
+            by_lang[lang] = dict(number=n, lines=lines)
+        return by_lang
+
+    def getLastMonthArtifacts(self, category = None, art_type = None):
+        self.checkOldArtifacts() 
+        cre, mod = reduce(
+            addtuple, 
+            [(int(el.created),1-int(el.created))
+                for el in self.lastmonth.messages
+                if (category is None or category in el.categories) and 
+                (el.messagetype == art_type or art_type is None)], 
+            (0,0))
+        return dict(created=cre, modified=mod)
+
+    def getLastMonthArtifactsByType(self, category = None):
+        self.checkOldArtifacts()
+        seen = set()
+        types=[el.messagetype for el in self.lastmonth.messages
+               if el.messagetype not in seen and not seen.add(el.messagetype)]
+
+        by_type = {}
+        for t in types:
+            cre, mod = reduce(
+                addtuple, 
+                [(int(el.created),1-int(el.created))
+                 for el in self.lastmonth.messages
+                 if el.messagetype == t and
+                 category in [None]+el.categories],
+                (0,0))
+            by_type[t] = dict(created=cre, modified=mod)
+        return by_type
+
+    def getLastMonthArtifactsByCategory(self):
+        from allura.model.project import TroveCategory
+
+        self.checkOldArtifacts() 
+        seen = set()
+        catlist=[el.category for el in self.general
+                 if el.category not in seen and not seen.add(el.category)]
+
+        by_cat = {}
+        for cat in catlist:
+            cre, mod = reduce(
+                addtuple, 
+                [(int(el.created),1-int(el.created))
+                 for el in self.lastmonth.messages 
+                 if cat in el.categories + [None]], (0,0))
+            if cat != None:
+                cat = TroveCategory.query.get(_id = cat)
+            by_cat[cat] = dict(created=cre, modified=mod)
+        return by_cat
+
+    def getLastMonthTickets(self, category = None):
+        from allura.model.project import TroveCategory
+
+        self.checkOldArtifacts()
+        a = len([el for el in self.lastmonth.assignedtickets
+                 if category in el.categories + [None]])
+        r = len([el for el in self.lastmonth.revokedtickets
+                 if category in el.categories + [None]])
+        s, time = reduce(
+            addtuple, 
+            [(1, el.solvingtime)
+             for el in self.lastmonth.solvedtickets
+             if category in el.categories + [None]],
+            (0,0))
+        if category!=None:
+            category = TroveCategory.query.get(_id=category)
+        if s > 0:
+            time = time / s
+        else:
+            time = None
+        return dict(
+            assigned=a,
+            revoked=r,
+            solved=s, 
+            averagesolvingtime=_convertTimeDiff(time))
+        
+    def getLastMonthTicketsByCategory(self):
+        from allura.model.project import TroveCategory
+
+        self.checkOldArtifacts()
+        seen = set()
+        catlist=[el.category for el in self.general
+                 if el.category not in seen and not seen.add(el.category)]
+        by_cat = {}
+        for cat in catlist:
+            a = len([el for el in self.lastmonth.assignedtickets
+                     if cat in el.categories + [None]])
+            r = len([el for el in self.lastmonth.revokedtickets
+                     if cat in el.categories + [None]])
+            s, time = reduce(addtuple, [(1, el.solvingtime)
+                                        for el in self.lastmonth.solvedtickets
+                                        if cat in el.categories+[None]],(0,0))
+            if cat != None:
+                cat = TroveCategory.query.get(_id = cat)
+            if s > 0: 
+                time = time / s
+            else:
+                time = None
+            by_cat[cat] = dict(
+                assigned=a,
+                revoked=r,
+                solved=s, 
+                averagesolvingtime=_convertTimeDiff(time))
+        return by_cat
+        
+    def checkOldArtifacts(self):
+        now = datetime.utcnow()
+        for m in self.lastmonth.messages:
+            if now - m.datetime > timedelta(30):
+                self.lastmonth.messages.remove(m)
+        for t in self.lastmonth.assignedtickets:
+            if now - t.datetime > timedelta(30):
+                self.lastmonth.assignedtickets.remove(t)
+        for t in self.lastmonth.revokedtickets:
+            if now - t.datetime > timedelta(30):
+                self.lastmonth.revokedtickets.remove(t)
+        for t in self.lastmonth.solvedtickets:
+            if now - t.datetime > timedelta(30):
+                self.lastmonth.solvedtickets.remove(t)
+        for c in self.lastmonth.commits:
+            if now - c.datetime > timedelta(30):
+                self.lastmonth.commits.remove(c)
+
+    def addNewArtifact(self, art_type, art_datetime, project):
+        self._updateArtifactsStats(art_type, art_datetime, project, "created")
+
+    def addModifiedArtifact(self, art_type, art_datetime, project):
+        self._updateArtifactsStats(art_type, art_datetime, project, "modified")
+
+    def addAssignedTicket(self, ticket_datetime, project):
+        topics = [t for t in project.trove_topic if t]
+        self._updateTicketsStats(topics, 'assigned')
+        self.lastmonth.assignedtickets.append(
+            dict(datetime=ticket_datetime, categories=topics))
+
+    def addRevokedTicket(self, ticket_datetime, project):
+        topics = [t for t in project.trove_topic if t]
+        self._updateTicketsStats(topics, 'revoked')
+        self.lastmonth.revokedtickets.append(
+            dict(datetime=ticket_datetime, categories=topics))
+        self.checkOldArtifacts()
+
+    def addClosedTicket(self, open_datetime, close_datetime, project):
+        topics = [t for t in project.trove_topic if t]
+        s_time=int((close_datetime-open_datetime).total_seconds())
+        self._updateTicketsStats(topics, 'solved', s_time = s_time)
+        self.lastmonth.solvedtickets.append(dict(
+            datetime=close_datetime,
+            categories=topics,
+            solvingtime=s_time))
+        self.checkOldArtifacts()
+
+    def addCommit(self, newcommit, commit_datetime, project):
+        def _computeLines(newblob, oldblob = None):
+            if oldblob:
+                listold = list(oldblob)
+            else:
+                listold = []
+            if newblob:
+                listnew = list(newblob)
+            else:
+                listnew = []
+
+            if oldblob is None:
+                lines = len(listnew)
+            elif newblob and newblob.has_html_view:
+                diff = difflib.unified_diff(
+                    listold, listnew,
+                    ('old' + oldblob.path()).encode('utf-8'),
+                    ('new' + newblob.path()).encode('utf-8'))
+                lines = len([l for l in diff if len(l) > 0 and l[0] == '+'])-1
+            else:
+                lines = 0
+            return lines
+
+        def _addCommitData(stats, topics, languages, lines):          
+            lt = topics + [None]
+            ll = languages + [None]
+            for t in lt:
+                i = getElementIndex(stats.general, category=t) 
+                if i is None:
+                    newstats = dict(
+                        category=t,
+                        commits=[],
+                        messages=dict(
+                            assigned=0,
+                            solved=0,
+                            revoked=0,
+                            totsolvingtime=0),
+                        tickets=[])   
+                    stats.general.append(newstats)
+                    i = getElementIndex(stats.general, category=t)
+                for lang in ll:
+                    j = getElementIndex(
+                        stats.general[i]['commits'], language=lang)
+                    if j is None:
+                        stats.general[i]['commits'].append(dict(
+                            language=lang, lines=lines, number=1))
+                    else:
+                        stats.general[i]['commits'][j].lines += lines
+                        stats.general[i]['commits'][j].number += 1
+
+        topics = [t for t in project.trove_topic if t]
+        languages = [l for l in project.trove_language if l]
+
+        d = newcommit.diffs
+        if len(newcommit.parent_ids) > 0:
+            oldcommit = newcommit.repo.commit(newcommit.parent_ids[0])
+
+        totlines = 0
+        for changed in d.changed:
+            newblob = newcommit.tree.get_blob_by_path(changed)
+            oldblob = oldcommit.tree.get_blob_by_path(changed)
+            totlines+=_computeLines(newblob, oldblob)
+
+        for copied in d.copied:
+            newblob = newcommit.tree.get_blob_by_path(copied['new'])
+            oldblob = oldcommit.tree.get_blob_by_path(copied['old'])
+            totlines+=_computeLines(newblob, oldblob)
+
+        for added in d.added:
+            newblob = newcommit.tree.get_blob_by_path(added)
+            totlines+=_computeLines(newblob)
+
+        _addCommitData(self, topics, languages, totlines)
+
+        self.lastmonth.commits.append(dict(
+            datetime=commit_datetime, 
+            categories=topics, 
+            programming_languages=languages,
+            lines=totlines))
+        self.checkOldArtifacts()
+
+    def _updateArtifactsStats(self, art_type, art_datetime, project, action):
+        if action not in ['created', 'modified']: 
+            return
+        topics = [t for t in project.trove_topic if t]
+        lt = [None] + topics
+        for mtype in [None, art_type]:
+            for t in lt:
+                i = getElementIndex(self.general, category = t)
+                if i is None:
+                    msg = dict(
+                        category=t,
+                        commits=[],
+                        tickets=dict(
+                            solved=0,
+                            assigned=0,
+                            revoked=0,
+                            totsolvingtime=0),
+                        messages=[])
+                    self.general.append(msg)
+                    i = getElementIndex(self.general, category = t)
+                j = getElementIndex(
+                    self.general[i]['messages'], messagetype=mtype)
+                if j is None:
+                    entry = dict(messagetype=mtype, created=0, modified=0)
+                    entry[action] += 1
+                    self.general[i]['messages'].append(entry)
+                else:
+                    self.general[i]['messages'][j][action] += 1
+
+        self.lastmonth.messages.append(dict(
+            datetime=art_datetime,
+            created=(action == 'created'),
+            categories=topics,
+            messagetype=art_type))
+        self.checkOldArtifacts() 
+
+    def _updateTicketsStats(self, topics, action, s_time = None):
+        if action not in ['solved', 'assigned', 'revoked']:
+            return
+        lt = topics + [None]
+        for t in lt:
+            i = getElementIndex(self.general, category = t)
+            if i is None:
+                stats = dict(
+                    category=t,
+                    commits=[],
+                    tickets=dict(
+                        solved=0,
+                        assigned=0,
+                        revoked=0,
+                        totsolvingtime=0),
+                    messages=[])
+                self.general.append(stats)
+                i = getElementIndex(self.general, category = t)
+            self.general[i]['tickets'][action] += 1 
+            if action == 'solved': 
+                self.general[i]['tickets']['totsolvingtime']+=s_time
+
+def getElementIndex(el_list, **kw):
+    for i in range(len(el_list)):
+        for k in kw:
+            if el_list[i].get(k) != kw[k]:
+                break
+        else:
+            return i
+    return None
+
+def addtuple(l1, l2):
+    a, b = l1
+    x, y = l2
+    return (a+x, b+y)
+
+def _convertTimeDiff(int_seconds):
+    if int_seconds is None:
+        return None
+    diff = timedelta(seconds = int_seconds)
+    days, seconds = diff.days, diff.seconds
+    hours = seconds / 3600
+    seconds = seconds % 3600
+    minutes = seconds / 60
+    seconds = seconds % 60
+    return dict(
+        days=days, 
+        hours=hours, 
+        minutes=minutes,
+        seconds=seconds)
+
+Mapper.compile_all()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/169e9d98/ForgeUserStats/forgeuserstats/controllers/userstats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/controllers/userstats.py b/ForgeUserStats/forgeuserstats/controllers/userstats.py
index 2bfaf82..fe14449 100644
--- a/ForgeUserStats/forgeuserstats/controllers/userstats.py
+++ b/ForgeUserStats/forgeuserstats/controllers/userstats.py
@@ -228,7 +228,6 @@ def _getDataForCategory(category, stats):
             solved='n/a',
             averagesolvingtime='n/a')
         for key in artifacts_by_type:
-            value = artifacts_by_type[key]
             artifacts_by_type[key]['pmcreated'] = 'n/a'
             artifacts_by_type[key]['pmmodified']= 'n/a'
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/169e9d98/ForgeUserStats/forgeuserstats/main.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/main.py b/ForgeUserStats/forgeuserstats/main.py
index 43ca2f3..8eeb113 100644
--- a/ForgeUserStats/forgeuserstats/main.py
+++ b/ForgeUserStats/forgeuserstats/main.py
@@ -32,25 +32,25 @@ class UserStatsListener(EventsListener):
             stats = UserStats.create(user)
 
         if event_type == "assigned": 
-            stats.addAssignedTicket(ticket, project)
+            stats.addAssignedTicket(ticket.mod_date, project)
         elif event_type == "revoked":
-            stats.addRevokedTicket(ticket, project)
+            stats.addRevokedTicket(ticket.mod_date, project)
         elif event_type == "closed":
-            stats.addClosedTicket(ticket, project)
+            stats.addClosedTicket(ticket.created_date,ticket.mod_date,project)
 
     def newCommit(self, newcommit, project, user):
         stats = user.stats
         if not stats:
             stats = UserStats.create(user)
 
-        stats.addCommit(newcommit, project)
+        stats.addCommit(newcommit, datetime.utcnow(), project)
 
     def addUserLogin(self, user):
         stats = user.stats
         if not stats:
             stats = UserStats.create(user)
 
-        stats.addLogin()
+        stats.addLogin(datetime.utcnow())
 
     def newOrganization(self, organization):
         pass

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/169e9d98/ForgeUserStats/forgeuserstats/model/stats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/stats.py b/ForgeUserStats/forgeuserstats/model/stats.py
index 2f5e1b9..8575e79 100644
--- a/ForgeUserStats/forgeuserstats/model/stats.py
+++ b/ForgeUserStats/forgeuserstats/model/stats.py
@@ -1,67 +1,20 @@
-import pymongo
-from pylons import c, g, request
-
-import bson
+from ming.orm import FieldProperty
 from ming import schema as S
-from ming import Field, Index, collection
-from ming.orm import session, state, Mapper
-from ming.orm import FieldProperty, RelationProperty, ForeignIdProperty
-from ming.orm.declarative import MappedClass
 from datetime import datetime, timedelta
-import difflib
+from ming.orm import session, Mapper
 
 from allura.model.session import main_orm_session
-from allura.lib import helpers as h
+from allura.model.contrib_stats import Stats
 
-class UserStats(MappedClass):
+class UserStats(Stats):
     class __mongometa__:
         name='userstats'
         session = main_orm_session
         unique_indexes = [ '_id', 'user_id']
 
-    _id=FieldProperty(S.ObjectId)
-
-    registration_date = FieldProperty(datetime)
     tot_logins_count = FieldProperty(int, if_missing = 0)
     last_login = FieldProperty(datetime)
-    general = FieldProperty([dict(
-        category = S.ObjectId,
-        messages = [dict(
-            messagetype = str,
-            created = int,
-            modified = int)],
-        tickets = dict(
-            solved = int,
-            assigned = int,
-            revoked = int,
-            totsolvingtime = int),
-        commits = [dict(
-            lines = int,
-            number = int,
-            language = S.ObjectId)])])
-
-    lastmonth=FieldProperty(dict(
-        logins=[datetime],
-            messages=[dict(
-            datetime=datetime,
-            created=bool,
-            categories=[S.ObjectId],
-            messagetype=str)],
-        assignedtickets=[dict(
-            datetime=datetime,
-            categories=[S.ObjectId])],
-        revokedtickets=[dict(
-            datetime=datetime,
-            categories=[S.ObjectId])],
-        solvedtickets=[dict(
-            datetime=datetime,
-            categories=[S.ObjectId],
-            solvingtime=int)],
-        commits=[dict(
-            datetime=datetime,
-            categories=[S.ObjectId],
-            programming_languages=[S.ObjectId],
-            lines=int)]))
+    lastmonthlogins=FieldProperty([datetime])
     user_id = FieldProperty(S.ObjectId)
 
     @classmethod
@@ -76,578 +29,22 @@ class UserStats(MappedClass):
         session(user).flush(user)
         return stats
 
-    def getCodeContribution(self):
-        days=(datetime.today() - self.registration_date).days
-        if not days:
-            days=1
-        for val in self['general']:
-            if val['category'] is None:
-                for commits in val['commits']:
-                    if commits['language'] is None: 
-                        if days > 30:
-                            return round(float(commits.lines)/days*30, 2)
-                        else:
-                            return float(commits.lines)
-        return 0
-
-    def getDiscussionContribution(self):
-        days=(datetime.today() - self.registration_date).days
-        if not days:
-            days=1
-        for val in self['general']:
-            if val['category'] is None:
-                for artifact in val['messages']:
-                    if artifact['messagetype'] is None: 
-                        tot = artifact.created+artifact.modified
-                        if days > 30:
-                            return round(float(tot)/days*30,2)
-                        else:
-                            return float(tot)
-        return 0
-
-    def getTicketsContribution(self):
-        for val in self['general']:
-            if val['category'] is None:
-                tickets = val['tickets']
-                if tickets.assigned == 0:
-                    return 0
-                return float(tickets.solved) / tickets.assigned
-        return 0
-
-    @classmethod
-    def getMaxAndAverageCodeContribution(self):
-        lst = list(self.query.find())
-        n = len(lst)
-        if n == 0:
-            return 0, 0
-        maxcontribution=max([x.getCodeContribution() for x in lst])
-        averagecontribution=sum([x.getCodeContribution() for x in lst]) / n
-        return maxcontribution, round(averagecontribution, 2)
-
-    @classmethod
-    def getMaxAndAverageDiscussionContribution(self):
-        lst = list(self.query.find())
-        n = len(lst)
-        if n == 0:
-            return 0, 0
-        maxcontribution=max([x.getDiscussionContribution() for x in lst])
-        averagecontribution=sum([x.getDiscussionContribution() for x in lst])/n
-        return maxcontribution, round(averagecontribution, 2)
-
-    @classmethod
-    def getMaxAndAverageTicketsSolvingPercentage(self):
-        lst = list(self.query.find())
-        n = len(lst)
-        if n == 0:
-            return 0, 0
-        maxcontribution=max([x.getTicketsContribution() for x in lst])
-        averagecontribution=sum([x.getTicketsContribution() for x in lst])/n
-        return maxcontribution, round(averagecontribution, 2)
-
-    def codeRanking(self):
-        lst = list(self.query.find())
-        totn = len(lst)
-        codcontr = self.getCodeContribution()
-        upper = len([x for x in lst if x.getCodeContribution() > codcontr])
-        return round((totn - upper) * 100.0 / totn, 2)
-
-    def discussionRanking(self):
-        lst = list(self.query.find())
-        totn = len(lst)
-        disccontr = self.getDiscussionContribution()
-        upper=len([x for x in lst if x.getDiscussionContribution()>disccontr])
-        return round((totn - upper) * 100.0 / totn, 2)
-
-    def ticketsRanking(self):
-        lst = list(self.query.find())
-        totn = len(lst)
-        ticketscontr = self.getTicketsContribution()
-        upper=len([x for x in lst if x.getTicketsContribution()>ticketscontr])
-        return round((totn - upper) * 100.0 / totn, 2)
-
-    def getCommits(self, category = None):
-        i = getElementIndex(self.general, category = category)
-        if i is None: 
-            return dict(number=0, lines=0)
-        cat = self.general[i]
-        j = getElementIndex(cat.commits, language = None)
-        if j is None:
-            return dict(number=0, lines=0)
-        return dict(
-            number=cat.commits[j]['number'], 
-            lines=cat.commits[j]['lines'])
-
-    def getArtifacts(self, category = None, art_type = None):
-        i = getElementIndex(self.general, category = category)
-        if i is None:
-            return dict(created=0, modified=0)
-        cat = self.general[i]
-        j = getElementIndex(cat.messages, messagetype = art_type)
-        if j is None:
-            return dict(created=0, modified=0)
-        return dict(created=cat.messages[j].created, modified=cat.messages[j].modified)
-
-    def getTickets(self, category = None):
-        i = getElementIndex(self.general, category = category)
-        if i is None:
-            return dict(
-                assigned=0,
-                solved=0,
-                revoked=0,
-                averagesolvingtime=None)
-        if self.general[i].tickets.solved > 0:
-            tot = self.general[i].tickets.totsolvingtime 
-            number = self.general[i].tickets.solved
-            average = tot / number
-        else: 
-            average = None
-        return dict(
-            assigned=self.general[i].tickets.assigned,
-            solved=self.general[i].tickets.solved,
-            revoked=self.general[i].tickets.revoked,
-            averagesolvingtime=_convertTimeDiff(average))
-
-    def getCommitsByCategory(self):
-        from allura.model.project import TroveCategory
-
-        by_cat = {}
-        for entry in self.general:
-            cat = entry.category
-            i = getElementIndex(entry.commits, language = None)
-            if i is None: 
-                n, lines = 0, 0
-            else: 
-                n, lines = entry.commits[i].number, entry.commits[i].lines
-            if cat != None:
-                cat = TroveCategory.query.get(_id = cat)
-            by_cat[cat] = dict(number=n, lines=lines)
-        return by_cat
-
-    def getCommitsByLanguage(self):
-        langlist = []
-        by_lang = {}
-        i = getElementIndex(self.general, category=None)
-        if i is None: 
-            return dict(number=0, lines=0)
-        return dict([(el.language, dict(lines=el.lines, number=el.number))
-                     for el in self.general[i].commits])
-
-    def getArtifactsByCategory(self, detailed=False):
-        from allura.model.project import TroveCategory
-
-        by_cat = {}
-        for entry in self.general:
-            cat = entry.category
-            if cat != None: 
-                cat = TroveCategory.query.get(_id = cat)
-            if detailed: 
-                by_cat[cat] = entry.messages
-            else:
-                i = getElementIndex(entry.messages, messagetype=None)
-                if i is not None:
-                    by_cat[cat] = entry.messages[i]
-                else: 
-                    by_cat[cat] = dict(created=0, modified=0)
-        return by_cat
-
-    def getArtifactsByType(self, category=None):
-        i = getElementIndex(self.general, category = category)
-        if i is None: 
-            return {}
-        entry = self.general[i].messages
-        by_type = dict([(el.messagetype, dict(created=el.created,
-                                              modified=el.modified))
-                         for el in entry])
-        return by_type
-
-    def getTicketsByCategory(self):
-        from allura.model.project import TroveCategory
-
-        by_cat = {}
-        for entry in self.general:
-            cat = entry.category
-            if cat != None:
-                cat = TroveCategory.query.get(_id = cat)
-            a, s = entry.tickets.assigned, entry.tickets.solved
-            r, time = entry.tickets.solved, entry.tickets.totsolvingtime
-            if s:
-                average = time / s
-            else:
-                average = None
-            by_cat[cat] = dict(
-                assigned=a,
-                solved=s,
-                revoked=r, 
-                averagesolvingtime=_convertTimeDiff(average))
-        return by_cat
-
-    def getLastMonthCommits(self, category = None):
-        self.checkOldArtifacts() 
-        lineslist = [el.lines for el in self.lastmonth.commits
-                     if category in el.categories + [None]]
-        return dict(number=len(lineslist), lines=sum(lineslist))
-
-    def getLastMonthCommitsByCategory(self):
-        from allura.model.project import TroveCategory
-
-        self.checkOldArtifacts() 
-        seen = set()
-        catlist=[el.category for el in self.general
-                 if el.category not in seen and not seen.add(el.category)]
-
-        by_cat = {}
-        for cat in catlist:
-            lineslist = [el.lines for el in self.lastmonth.commits
-                         if cat in el.categories + [None]]
-            n = len(lineslist)
-            lines = sum(lineslist)
-            if cat != None:
-                cat = TroveCategory.query.get(_id = cat)
-            by_cat[cat] = dict(number=n, lines=lines)
-        return by_cat
-
-    def getLastMonthCommitsByLanguage(self):
-        from allura.model.project import TroveCategory
-
-        self.checkOldArtifacts() 
-        seen = set()
-        langlist=[el.language for el in self.general
-                  if el.language not in seen and not seen.add(el.language)]
-
-        by_lang = {}
-        for lang in langlist:
-            lineslist = [el.lines for el in self.lastmonth.commits
-                         if lang in el.programming_languages + [None]]
-            n = len(lineslist)
-            lines = sum(lineslist)
-            if lang != None:
-                lang = TroveCategory.query.get(_id = lang)
-            by_lang[lang] = dict(number=n, lines=lines)
-        return by_lang
-
-    def getLastMonthArtifacts(self, category = None):
-        self.checkOldArtifacts() 
-        cre, mod = reduce(addtuple, [(int(el.created),1-int(el.created))
-                                     for el in self.lastmonth.messages
-                                     if category is None or 
-                                        category in el.categories], (0,0))
-        return dict(created=cre, modified=mod)
-
-    def getLastMonthArtifactsByType(self, category = None):
-        self.checkOldArtifacts()
-        seen = set()
-        types=[el.messagetype for el in self.lastmonth.messages
-               if el.messagetype not in seen and not seen.add(el.messagetype)]
-
-        by_type = {}
-        for t in types:
-            cre, mod = reduce(
-                addtuple, 
-                [(int(el.created),1-int(el.created))
-                 for el in self.lastmonth.messages
-                 if el.messagetype == t and
-                 category in [None]+el.categories],
-                (0,0))
-            by_type[t] = dict(created=cre, modified=mod)
-        return by_type
-
-    def getLastMonthArtifactsByCategory(self):
-        from allura.model.project import TroveCategory
-
-        self.checkOldArtifacts() 
-        seen = set()
-        catlist=[el.category for el in self.general
-                 if el.category not in seen and not seen.add(el.category)]
-
-        by_cat = {}
-        for cat in catlist:
-            cre, mod = reduce(
-                addtuple, 
-                [(int(el.created),1-int(el.created))
-                 for el in self.lastmonth.messages 
-                 if cat in el.categories + [None]], (0,0))
-            if cat != None:
-                cat = TroveCategory.query.get(_id = cat)
-            by_cat[cat] = dict(created=cre, modified=mod)
-        return by_cat
-
-    def getLastMonthTickets(self, category = None):
-        from allura.model.project import TroveCategory
-
-        self.checkOldArtifacts()
-        a = len([el for el in self.lastmonth.assignedtickets
-                 if category in el.categories + [None]])
-        r = len([el for el in self.lastmonth.revokedtickets
-                 if category in el.categories + [None]])
-        s, time = reduce(
-            addtuple, 
-            [(1, el.solvingtime)
-             for el in self.lastmonth.solvedtickets
-             if category in el.categories + [None]],
-            (0,0))
-        if category!=None:
-            category = TroveCategory.query.get(_id=category)
-        if s > 0:
-            time = time / s
-        else:
-            time = None
-        return dict(
-            assigned=a,
-            revoked=r,
-            solved=s, 
-            averagesolvingtime=_convertTimeDiff(time))
-        
-    def getLastMonthTicketsByCategory(self):
-        from allura.model.project import TroveCategory
-
-        self.checkOldArtifacts()
-        seen = set()
-        catlist=[el.category for el in self.general
-                 if el.category not in seen and not seen.add(el.category)]
-        by_cat = {}
-        for cat in catlist:
-            a = len([el for el in self.lastmonth.assignedtickets
-                     if cat in el.categories + [None]])
-            r = len([el for el in self.lastmonth.revokedtickets
-                     if cat in el.categories + [None]])
-            s, time = reduce(addtuple, [(1, el.solvingtime)
-                                        for el in self.lastmonth.solvedtickets
-                                        if cat in el.categories+[None]],(0,0))
-            if cat != None:
-                cat = TroveCategory.query.get(_id = cat)
-            if s > 0: 
-                time = time / s
-            else:
-                time = None
-            by_cat[cat] = dict(
-                assigned=a,
-                revoked=r,
-                solved=s, 
-                averagesolvingtime=_convertTimeDiff(time))
-        return by_cat
-        
     def getLastMonthLogins(self):
         self.checkOldArtifacts()
-        return len(self.lastmonth.logins)
+        return len(self.lastmonthlogins)
 
     def checkOldArtifacts(self):
+        super(UserStats, self).checkOldArtifacts()
         now = datetime.utcnow()
-        for m in self.lastmonth.messages:
-            if now - m.datetime > timedelta(30):
-                self.lastmonth.messages.remove(m)
-        for t in self.lastmonth.assignedtickets:
-            if now - t.datetime > timedelta(30):
-                self.lastmonth.assignedtickets.remove(t)
-        for t in self.lastmonth.revokedtickets:
-            if now - t.datetime > timedelta(30):
-                self.lastmonth.revokedtickets.remove(t)
-        for t in self.lastmonth.solvedtickets:
-            if now - t.datetime > timedelta(30):
-                self.lastmonth.solvedtickets.remove(t)
-
-    def addNewArtifact(self, art_type, art_datetime, project):
-        self._updateArtifactsStats(art_type, art_datetime, project, "created")
-
-    def addModifiedArtifact(self, art_type, art_datetime, project):
-        self._updateArtifactsStats(art_type, art_datetime, project, "modified")
-
-    def addAssignedTicket(self, ticket, project):
-        topics = [t for t in project.trove_topic if t]
-        self._updateTicketsStats(topics, 'assigned')
-        self.lastmonth.assignedtickets.append(
-            dict(datetime=ticket.mod_date, categories=topics))
-
-    def addRevokedTicket(self, ticket, project):
-        topics = [t for t in project.trove_topic if t]
-        self._updateTicketsStats(topics, 'revoked')
-        self.lastmonth.revokedtickets.append(
-            dict(datetime=ticket.mod_date, categories=topics))
-        self.checkOldArtifacts()
-
-    def addClosedTicket(self, ticket, project):
-        topics = [t for t in project.trove_topic if t]
-        s_time=int((datetime.utcnow()-ticket.created_date).total_seconds())
-        self._updateTicketsStats(topics, 'solved', s_time = s_time)
-        self.lastmonth.solvedtickets.append(dict(
-            datetime=ticket.mod_date,
-            categories=topics,
-            solvingtime=s_time))
-        self.checkOldArtifacts()
-
-    def addCommit(self, newcommit, project):
-        def _computeLines(newblob, oldblob = None):
-            if oldblob:
-                listold = list(oldblob)
-            else:
-                listold = []
-            if newblob:
-                listnew = list(newblob)
-            else:
-                listnew = []
-
-            if oldblob is None:
-                lines = len(listnew)
-            elif newblob and newblob.has_html_view:
-                diff = difflib.unified_diff(
-                    listold, listnew,
-                    ('old' + oldblob.path()).encode('utf-8'),
-                    ('new' + newblob.path()).encode('utf-8'))
-                lines = len([l for l in diff if len(l) > 0 and l[0] == '+'])-1
-            else:
-                lines = 0
-            return lines
-
-        def _addCommitData(stats, topics, languages, lines):          
-            lt = topics + [None]
-            ll = languages + [None]
-            for t in lt:
-                i = getElementIndex(stats.general, category=t) 
-                if i is None:
-                    newstats = dict(
-                        category=t,
-                        commits=[],
-                        messages=dict(
-                            assigned=0,
-                            solved=0,
-                            revoked=0,
-                            totsolvingtime=0),
-                        tickets=[])   
-                    stats.general.append(newstats)
-                    i = getElementIndex(stats.general, category=t)
-                for lang in ll:
-                    j = getElementIndex(
-                        stats.general[i]['commits'], language=lang)
-                    if j is None:
-                        stats.general[i]['commits'].append(dict(
-                            language=lang, lines=lines, number=1))
-                    else:
-                        stats.general[i]['commits'][j].lines += lines
-                        stats.general[i]['commits'][j].number += 1
-
-        topics = [t for t in project.trove_topic if t]
-        languages = [l for l in project.trove_language if l]
-        now = datetime.utcnow()
-
-        d = newcommit.diffs
-        if len(newcommit.parent_ids) > 0:
-            oldcommit = newcommit.repo.commit(newcommit.parent_ids[0])
-
-        totlines = 0
-        for changed in d.changed:
-            newblob = newcommit.tree.get_blob_by_path(changed)
-            oldblob = oldcommit.tree.get_blob_by_path(changed)
-            totlines+=_computeLines(newblob, oldblob)
-
-        for copied in d.copied:
-            newblob = newcommit.tree.get_blob_by_path(copied['new'])
-            oldblob = oldcommit.tree.get_blob_by_path(copied['old'])
-            totlines+=_computeLines(newblob, oldblob)
-
-        for added in d.added:
-            newblob = newcommit.tree.get_blob_by_path(added)
-            totlines+=_computeLines(newblob)
-
-        _addCommitData(self, topics, languages, totlines)
+        for l in self.lastmonthlogins:
+            if now - l > timedelta(30):
+                self.lastmonthlogins.remove(l)
 
-        self.lastmonth.commits.append(dict(
-            datetime=now, 
-            categories=topics, 
-            programming_languages=languages,
-            lines=totlines))
-        self.checkOldArtifacts()
-
-    def addLogin(self):
-        now = datetime.utcnow()
-        self.last_login = now
+    def addLogin(self, login_datetime):
+        if (not self.last_login) or (login_datetime > self.last_login):
+            self.last_login = login_datetime
         self.tot_logins_count += 1
-        self.lastmonth.logins.append(now)
+        self.lastmonthlogins.append(login_datetime)
         self.checkOldArtifacts()
         
-    def _updateArtifactsStats(self, art_type, art_datetime, project, action):
-        if action not in ['created', 'modified']: 
-            return
-        topics = [t for t in project.trove_topic if t]
-        lt = [None] + topics
-        for mtype in [None, art_type]:
-            for t in lt:
-                i = getElementIndex(self.general, category = t)
-                if i is None:
-                    msg = dict(
-                        category=t,
-                        commits=[],
-                        tickets=dict(
-                            solved=0,
-                            assigned=0,
-                            revoked=0,
-                            totsolvingtime=0),
-                        messages=[])
-                    self.general.append(msg)
-                    i = getElementIndex(self.general, category = t)
-                j = getElementIndex(
-                    self.general[i]['messages'], messagetype=mtype)
-                if j is None:
-                    entry = dict(messagetype=mtype, created=0, modified=0)
-                    entry[action] += 1
-                    self.general[i]['messages'].append(entry)
-                else:
-                    self.general[i]['messages'][j][action] += 1
-
-        self.lastmonth.messages.append(dict(
-            datetime=art_datetime,
-            created=(action == 'created'),
-            categories=topics,
-            messagetype=art_type))
-        self.checkOldArtifacts() 
-
-    def _updateTicketsStats(self, topics, action, s_time = None):
-        if action not in ['solved', 'assigned', 'revoked']:
-            return
-        lt = topics + [None]
-        for t in lt:
-            i = getElementIndex(self.general, category = t)
-            if i is None:
-                stats = dict(
-                    category=t,
-                    commits=[],
-                    tickets=dict(
-                        solved=0,
-                        assigned=0,
-                        revoked=0,
-                        totsolvingtime=0),
-                    messages=[])
-                self.general.append(stats)
-                i = getElementIndex(self.general, category = t)
-            self.general[i]['tickets'][action] += 1 
-            if action == 'solved': 
-                self.general[i]['tickets']['totsolvingtime']+=s_time
-
-def getElementIndex(el_list, **kw):
-    for i in range(len(el_list)):
-        for k in kw:
-            if el_list[i].get(k) != kw[k]:
-                break
-        else:
-            return i
-    return None
-
-def addtuple(l1, l2):
-    a, b = l1
-    x, y = l2
-    return (a+x, b+y)
-
-def _convertTimeDiff(int_seconds):
-    if int_seconds is None:
-        return None
-    diff = timedelta(seconds = int_seconds)
-    days, seconds = diff.days, diff.seconds
-    hours = seconds / 3600
-    seconds = seconds % 3600
-    minutes = seconds / 60
-    seconds = seconds % 60
-    return dict(
-        days=days, 
-        hours=hours, 
-        minutes=minutes,
-        seconds=seconds)
-
 Mapper.compile_all()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/169e9d98/ForgeUserStats/forgeuserstats/tests/test_model.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/test_model.py b/ForgeUserStats/forgeuserstats/tests/test_model.py
new file mode 100644
index 0000000..3a9fcde
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/tests/test_model.py
@@ -0,0 +1,375 @@
+import pkg_resources
+import unittest
+from datetime import datetime, timedelta
+
+from pylons import tmpl_context as c
+
+from alluratest.controller import setup_basic_test, setup_global_objects
+from allura.tests import decorators as td
+from allura.model import User, Project, TroveCategory
+from allura import model as M
+
+from forgegit.tests import with_git
+
+class TestUserStats(unittest.TestCase):
+
+    def setUp(self):
+        from allura.model import User
+
+        setup_basic_test()
+        setup_global_objects()
+        self.user = User.register(dict(username='test-new-user',
+            display_name='Test Stats'),
+            make_project=False)
+
+    def test_init_values(self):
+        artifacts = self.user.stats.getArtifacts()
+        tickets = self.user.stats.getTickets()
+        commits = self.user.stats.getCommits()
+        assert self.user.stats.tot_logins_count == 0
+        assert artifacts['created'] == 0
+        assert artifacts['modified'] == 0
+        assert tickets['assigned'] == 0
+        assert tickets['solved'] == 0
+        assert tickets['revoked'] == 0
+        assert tickets['averagesolvingtime'] is None
+        assert commits['number'] == 0
+        assert commits['lines'] == 0
+
+        lmartifacts = self.user.stats.getLastMonthArtifacts()
+        lmtickets = self.user.stats.getLastMonthTickets()
+        lmcommits = self.user.stats.getLastMonthCommits()
+        assert self.user.stats.getLastMonthLogins() == 0
+        assert lmartifacts['created'] == 0
+        assert lmartifacts['modified'] == 0
+        assert lmtickets['assigned'] == 0
+        assert lmtickets['solved'] == 0
+        assert lmtickets['revoked'] == 0
+        assert lmtickets['averagesolvingtime'] is None
+        assert lmcommits['number'] == 0
+        assert lmcommits['lines'] == 0
+
+    @td.with_user_project('test-new-user')
+    def test_create_artifact_stats(self):
+        p = Project.query.get(shortname='u/test-new-user')
+        topic = TroveCategory.query.get(shortname='scientific')
+
+        init_lm_art = self.user.stats.getLastMonthArtifacts()
+        init_art = self.user.stats.getArtifacts()
+        init_art_wiki = self.user.stats.getArtifacts(art_type='Wiki')
+        init_art_by_type = self.user.stats.getArtifactsByType()
+        init_lm_art_by_type = self.user.stats.getLastMonthArtifactsByType()
+        init_art_sci = self.user.stats.getArtifacts(category=topic._id)
+
+        self.user.stats.addNewArtifact('Wiki', datetime.utcnow(), p)
+        lm_art = self.user.stats.getLastMonthArtifacts()
+        artifacts = self.user.stats.getArtifacts()
+        art_wiki = self.user.stats.getArtifacts(art_type='Wiki')
+        art_by_type = self.user.stats.getArtifactsByType()
+        lm_art_by_type = self.user.stats.getLastMonthArtifactsByType()
+
+        assert lm_art['created'] == init_lm_art['created'] + 1
+        assert lm_art['modified'] == init_lm_art['modified']
+        assert artifacts['created'] == init_art['created'] + 1
+        assert artifacts['modified'] == init_art['modified']
+        assert art_wiki['created'] == init_art_wiki['created'] + 1
+        assert art_wiki['modified'] == init_art_wiki['modified']
+        assert art_by_type['Wiki']['created'] == init_art_by_type['Wiki']['created'] + 1
+        assert art_by_type['Wiki']['modified'] == init_art_by_type['Wiki']['modified']
+        assert lm_art_by_type['Wiki']['created'] == init_lm_art_by_type['Wiki']['created'] + 1
+        assert lm_art_by_type['Wiki']['modified'] == init_lm_art_by_type['Wiki']['modified']
+        
+        #In that case, last month stats should not be changed
+        new_date = datetime.utcnow() + timedelta(-32)
+        self.user.stats.addNewArtifact('Wiki', new_date, p)
+        lm_art = self.user.stats.getLastMonthArtifacts()
+        artifacts = self.user.stats.getArtifacts()
+        art_wiki = self.user.stats.getArtifacts(art_type='Wiki')
+        art_by_type = self.user.stats.getArtifactsByType()
+        lm_art_by_type = self.user.stats.getLastMonthArtifactsByType()
+
+        assert lm_art['created'] == init_lm_art['created'] + 1
+        assert lm_art['modified'] == init_lm_art['modified']
+        assert artifacts['created'] == init_art['created'] + 2
+        assert artifacts['modified'] == init_art['modified']
+        assert art_wiki['created'] == init_art_wiki['created'] + 2
+        assert art_wiki['modified'] == init_art_wiki['modified']
+        assert art_by_type['Wiki']['created'] == init_art_by_type['Wiki']['created'] + 2
+        assert art_by_type['Wiki']['modified'] == init_art_by_type['Wiki']['modified']
+        assert lm_art_by_type['Wiki']['created'] == init_lm_art_by_type['Wiki']['created'] + 1
+        assert lm_art_by_type['Wiki']['modified'] == init_lm_art_by_type['Wiki']['modified']
+        
+        p.trove_topic = [topic._id]
+
+        self.user.stats.addNewArtifact('Wiki', datetime.utcnow(), p)
+        lm_art = self.user.stats.getLastMonthArtifacts()
+        artifacts = self.user.stats.getArtifacts()
+        art_wiki = self.user.stats.getArtifacts(art_type='Wiki')
+        art_by_type = self.user.stats.getArtifactsByType()
+        lm_art_by_type = self.user.stats.getLastMonthArtifactsByType()
+        art_sci = self.user.stats.getArtifacts(category=topic._id)
+        art_by_cat = self.user.stats.getArtifactsByCategory(detailed=True)
+
+        assert lm_art['created'] == init_lm_art['created'] + 2
+        assert lm_art['modified'] == init_lm_art['modified']
+        assert artifacts['created'] == init_art['created'] + 3
+        assert artifacts['modified'] == init_art['modified']
+        assert art_wiki['created'] == init_art_wiki['created'] + 3
+        assert art_wiki['modified'] == init_art_wiki['modified']
+        assert art_by_type['Wiki']['created'] == init_art_by_type['Wiki']['created'] + 3
+        assert art_by_type['Wiki']['modified'] == init_art_by_type['Wiki']['modified']
+        assert lm_art_by_type['Wiki']['created'] == init_lm_art_by_type['Wiki']['created'] + 2
+        assert lm_art_by_type['Wiki']['modified'] == init_lm_art_by_type['Wiki']['modified']
+        assert art_sci['created'] == init_art_sci['created'] + 1
+        assert art_sci['modified'] == init_art_sci['modified']
+        assert dict(messagetype='Wiki', created= 1, modified = 0) in art_by_cat[topic]
+        art_by_cat = self.user.stats.getArtifactsByCategory(detailed=False)
+        assert art_by_cat[topic]['created'] == 1 and art_by_cat[topic]['modified'] == 0
+
+    @td.with_user_project('test-new-user')
+    def test_modify_artifact_stats(self):
+        p = Project.query.get(shortname='u/test-new-user')
+        topic = TroveCategory.query.get(shortname='scientific')
+
+        init_lm_art = self.user.stats.getLastMonthArtifacts()
+        init_art = self.user.stats.getArtifacts()
+        init_art_wiki = self.user.stats.getArtifacts(art_type='Wiki')
+        init_art_by_type = self.user.stats.getArtifactsByType()
+        init_lm_art_by_type = self.user.stats.getLastMonthArtifactsByType()
+        init_art_sci = self.user.stats.getArtifacts(category=topic._id)
+
+        self.user.stats.addModifiedArtifact('Wiki', datetime.utcnow(), p)
+        lm_art = self.user.stats.getLastMonthArtifacts()
+        artifacts = self.user.stats.getArtifacts()
+        art_wiki = self.user.stats.getArtifacts(art_type='Wiki')
+        art_by_type = self.user.stats.getArtifactsByType()
+        lm_art_by_type = self.user.stats.getLastMonthArtifactsByType()
+
+        assert lm_art['created'] == init_lm_art['created']
+        assert lm_art['modified'] == init_lm_art['modified'] + 1
+        assert artifacts['created'] == init_art['created']
+        assert artifacts['modified'] == init_art['modified'] + 1
+        assert art_wiki['created'] == init_art_wiki['created']
+        assert art_wiki['modified'] == init_art_wiki['modified'] + 1
+        assert art_by_type['Wiki']['created'] == init_art_by_type['Wiki']['created']
+        assert art_by_type['Wiki']['modified'] == init_art_by_type['Wiki']['modified'] + 1
+        assert lm_art_by_type['Wiki']['created'] == init_lm_art_by_type['Wiki']['created']
+        assert lm_art_by_type['Wiki']['modified'] == init_lm_art_by_type['Wiki']['modified'] + 1
+        
+        #In that case, last month stats should not be changed
+        new_date = datetime.utcnow() + timedelta(-32)
+        self.user.stats.addModifiedArtifact('Wiki', new_date, p)
+        lm_art = self.user.stats.getLastMonthArtifacts()
+        artifacts = self.user.stats.getArtifacts()
+        art_wiki = self.user.stats.getArtifacts(art_type='Wiki')
+        art_by_type = self.user.stats.getArtifactsByType()
+        lm_art_by_type = self.user.stats.getLastMonthArtifactsByType()
+
+        assert lm_art['created'] == init_lm_art['created'] 
+        assert lm_art['modified'] == init_lm_art['modified'] + 1
+        assert artifacts['created'] == init_art['created'] 
+        assert artifacts['modified'] == init_art['modified'] + 2
+        assert art_wiki['created'] == init_art_wiki['created']
+        assert art_wiki['modified'] == init_art_wiki['modified'] + 2
+        assert art_by_type['Wiki']['created'] == init_art_by_type['Wiki']['created']
+        assert art_by_type['Wiki']['modified'] == init_art_by_type['Wiki']['modified'] + 2
+        assert lm_art_by_type['Wiki']['created'] == init_lm_art_by_type['Wiki']['created']
+        assert lm_art_by_type['Wiki']['modified'] == init_lm_art_by_type['Wiki']['modified'] + 1
+        
+        p.trove_topic = [topic._id]
+
+        self.user.stats.addModifiedArtifact('Wiki', datetime.utcnow(), p)
+        lm_art = self.user.stats.getLastMonthArtifacts()
+        artifacts = self.user.stats.getArtifacts()
+        art_wiki = self.user.stats.getArtifacts(art_type='Wiki')
+        art_by_type = self.user.stats.getArtifactsByType()
+        lm_art_by_type = self.user.stats.getLastMonthArtifactsByType()
+        art_sci = self.user.stats.getArtifacts(category=topic._id)
+        art_by_cat = self.user.stats.getArtifactsByCategory(detailed=True)
+
+        assert lm_art['created'] == init_lm_art['created'] 
+        assert lm_art['modified'] == init_lm_art['modified'] + 2
+        assert artifacts['created'] == init_art['created']
+        assert artifacts['modified'] == init_art['modified'] + 3
+        assert art_wiki['created'] == init_art_wiki['created']
+        assert art_wiki['modified'] == init_art_wiki['modified'] + 3
+        assert art_by_type['Wiki']['created'] == init_art_by_type['Wiki']['created'] 
+        assert art_by_type['Wiki']['modified'] == init_art_by_type['Wiki']['modified'] + 3
+        assert lm_art_by_type['Wiki']['created'] == init_lm_art_by_type['Wiki']['created']
+        assert lm_art_by_type['Wiki']['modified'] == init_lm_art_by_type['Wiki']['modified'] +2
+        assert art_sci['created'] == init_art_sci['created']
+        assert art_sci['modified'] == init_art_sci['modified'] + 1
+        assert dict(messagetype='Wiki', created=0, modified=1) in art_by_cat[topic]
+        art_by_cat = self.user.stats.getArtifactsByCategory(detailed=False)
+        assert art_by_cat[topic]['created'] == 0 and art_by_cat[topic]['modified'] == 1
+
+    @td.with_user_project('test-new-user')
+    def test_ticket_stats(self):
+        p = Project.query.get(shortname='u/test-new-user')
+        topic = TroveCategory.query.get(shortname='scientific')
+        create_time = datetime.utcnow() + timedelta(-5)
+
+        init_lm_tickets_art = self.user.stats.getLastMonthArtifacts(art_type='Ticket')
+        init_tickets_art = self.user.stats.getArtifacts(art_type='Ticket')
+        init_tickets_sci_art = self.user.stats.getArtifacts(category=topic._id)
+        init_tickets = self.user.stats.getTickets()
+        init_lm_tickets = self.user.stats.getLastMonthTickets()
+
+        self.user.stats.addNewArtifact('Ticket', create_time, p)
+        lm_tickets_art = self.user.stats.getLastMonthArtifacts(art_type='Ticket')
+        tickets_art = self.user.stats.getArtifacts(art_type='Ticket')
+        tickets_sci_art = self.user.stats.getArtifacts(category=topic._id)
+
+        assert lm_tickets_art['created'] == init_lm_tickets_art['created'] + 1
+        assert lm_tickets_art['modified'] == init_lm_tickets_art['modified']
+        assert tickets_art['created'] == init_tickets_art['created'] + 1
+        assert tickets_art['modified'] == init_tickets_art['modified']
+        assert tickets_sci_art['created'] == tickets_sci_art['created']
+        assert tickets_sci_art['modified'] == tickets_sci_art['modified']
+        
+        p.trove_topic = [topic._id]
+
+        self.user.stats.addAssignedTicket(create_time, p)
+        tickets = self.user.stats.getTickets()
+        lm_tickets = self.user.stats.getLastMonthTickets()
+
+        assert tickets['assigned'] == init_tickets['assigned'] + 1 
+        assert tickets['revoked'] == init_tickets['revoked']
+        assert tickets['solved'] == init_tickets['solved'] 
+        assert tickets['averagesolvingtime'] is None 
+        assert lm_tickets['assigned'] == init_lm_tickets['assigned'] + 1 
+        assert lm_tickets['revoked'] == init_lm_tickets['revoked']
+        assert lm_tickets['solved'] == init_lm_tickets['solved'] 
+        assert lm_tickets['averagesolvingtime'] is None 
+
+        self.user.stats.addRevokedTicket(create_time + timedelta(-32), p)
+        tickets = self.user.stats.getTickets()
+
+        assert tickets['assigned'] == init_tickets['assigned'] + 1 
+        assert tickets['revoked'] == init_tickets['revoked'] + 1
+        assert tickets['solved'] == init_tickets['solved'] 
+        assert tickets['averagesolvingtime'] is None 
+        assert lm_tickets['assigned'] == init_lm_tickets['assigned'] + 1 
+        assert lm_tickets['revoked'] == init_lm_tickets['revoked']
+        assert lm_tickets['solved'] == init_lm_tickets['solved'] 
+        assert lm_tickets['averagesolvingtime'] is None 
+
+        self.user.stats.addClosedTicket(create_time, create_time + timedelta(1), p)
+        tickets = self.user.stats.getTickets()
+        lm_tickets = self.user.stats.getLastMonthTickets()
+
+        assert tickets['assigned'] == init_tickets['assigned'] + 1 
+        assert tickets['revoked'] == init_tickets['revoked'] + 1
+        assert tickets['solved'] == init_tickets['solved'] + 1
+
+        solving_time = dict(seconds=0,minutes=0,days=1,hours=0)
+        assert tickets['averagesolvingtime'] == solving_time
+        assert lm_tickets['assigned'] == init_lm_tickets['assigned'] + 1
+        assert lm_tickets['revoked'] == init_lm_tickets['revoked']
+        assert lm_tickets['solved'] == init_lm_tickets['solved'] + 1
+        assert lm_tickets['averagesolvingtime'] == solving_time
+
+        p.trove_topic = []
+        self.user.stats.addClosedTicket(create_time, create_time + timedelta(3), p)
+        tickets = self.user.stats.getTickets()
+        lm_tickets = self.user.stats.getLastMonthTickets()
+
+        solving_time = dict(seconds=0,minutes=0,days=2,hours=0)
+
+        assert tickets['assigned'] == init_tickets['assigned'] + 1 
+        assert tickets['revoked'] == init_tickets['revoked'] + 1
+        assert tickets['solved'] == init_tickets['solved'] + 2
+        assert tickets['averagesolvingtime'] == solving_time
+        assert lm_tickets['assigned'] == init_lm_tickets['assigned'] + 1
+        assert lm_tickets['revoked'] == init_lm_tickets['revoked']
+        assert lm_tickets['solved'] == init_lm_tickets['solved'] + 2
+        assert lm_tickets['averagesolvingtime'] == solving_time
+
+        by_cat = self.user.stats.getTicketsByCategory()
+        lm_by_cat = self.user.stats.getLastMonthTicketsByCategory()
+        solving_time=dict(days=1,hours=0,minutes=0,seconds=0)
+
+        assert by_cat[topic]['assigned'] == 1 
+        assert by_cat[topic]['revoked'] == 1
+        assert by_cat[topic]['solved'] == 1
+        assert by_cat[topic]['averagesolvingtime'] == solving_time
+        assert lm_by_cat[topic]['assigned'] == 1
+        assert lm_by_cat[topic]['revoked'] == 0
+        assert lm_by_cat[topic]['solved'] == 1
+        assert lm_by_cat[topic]['averagesolvingtime'] == solving_time
+
+    @with_git
+    @td.with_user_project('test-new-user')
+    def test_commit_stats(self):
+        p = Project.query.get(shortname='u/test-new-user')
+        topic = TroveCategory.query.get(shortname='scientific')
+        commit_time = datetime.utcnow() + timedelta(-1)
+
+        self.user.set_password('testpassword')
+        addr = M.EmailAddress.upsert('rcopeland@geek.net')
+        self.user.claim_address('rcopeland@geek.net')
+        
+        repo_dir = pkg_resources.resource_filename(
+            'forgeuserstats', 'tests/data')
+
+        c.app.repo.fs_path = repo_dir
+        c.app.repo.name = 'testgit.git'
+        repo = c.app.repo
+        repo.refresh()
+        commit = M.repo.Commit.query.get(_id=repo.heads[0]['object_id'])
+        commit.repo = repo
+
+        init_commits = self.user.stats.getCommits()
+        assert init_commits['number'] == 4
+        init_lmcommits = self.user.stats.getLastMonthCommits()
+        assert init_lmcommits['number'] == 4
+ 
+        p.trove_topic = [topic._id]
+        self.user.stats.addCommit(commit, datetime.utcnow(), p)
+        commits = self.user.stats.getCommits()
+        assert commits['number'] == init_commits['number'] + 1
+        assert commits['lines'] == init_commits['lines'] + 1
+        lmcommits = self.user.stats.getLastMonthCommits()
+        assert lmcommits['number'] == init_lmcommits['number'] + 1
+        assert lmcommits['lines'] == init_lmcommits['lines'] + 1
+        by_cat = self.user.stats.getCommitsByCategory()
+        assert by_cat[topic]['number'] == 1
+        assert by_cat[topic]['lines'] == 1
+        lm_by_cat = self.user.stats.getLastMonthCommitsByCategory()
+        assert lm_by_cat[topic]['number'] == 1
+        assert lm_by_cat[topic]['lines'] == 1
+
+        self.user.stats.addCommit(commit, datetime.utcnow() + timedelta(-40), p)
+        commits = self.user.stats.getCommits()
+        assert commits['number'] == init_commits['number'] + 2
+        assert commits['lines'] == init_commits['lines'] + 2
+        lmcommits = self.user.stats.getLastMonthCommits()
+        assert lmcommits['number'] == init_lmcommits['number'] + 1
+        assert lmcommits['lines'] == init_lmcommits['lines'] + 1
+        by_cat = self.user.stats.getCommitsByCategory()
+        assert by_cat[topic]['number'] == 2
+        assert by_cat[topic]['lines'] == 2
+        lm_by_cat = self.user.stats.getLastMonthCommitsByCategory()
+        assert lm_by_cat[topic]['number'] == 1
+        assert lm_by_cat[topic]['lines'] == 1
+
+    @td.with_user_project('test-new-user')
+    def test_login_stats(self):
+        init_logins = self.user.stats.tot_logins_count
+        init_lm_logins = self.user.stats.getLastMonthLogins()
+        
+        login_datetime = datetime.utcnow()
+        self.user.stats.addLogin(login_datetime)
+        logins = self.user.stats.tot_logins_count
+        lm_logins = self.user.stats.getLastMonthLogins()
+        assert logins == init_logins + 1 
+        assert lm_logins == init_lm_logins + 1 
+        assert abs(self.user.stats.last_login - login_datetime) < timedelta(seconds=1)
+
+        self.user.stats.addLogin(datetime.utcnow() + timedelta(-32))
+        logins = self.user.stats.tot_logins_count
+        lm_logins = self.user.stats.getLastMonthLogins()
+        assert logins == init_logins + 2
+        assert lm_logins == init_lm_logins + 1 
+        assert abs(self.user.stats.last_login - login_datetime) < timedelta(seconds=1)
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/169e9d98/ForgeUserStats/forgeuserstats/tests/test_stats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/test_stats.py b/ForgeUserStats/forgeuserstats/tests/test_stats.py
index a3e3482..e4954b7 100644
--- a/ForgeUserStats/forgeuserstats/tests/test_stats.py
+++ b/ForgeUserStats/forgeuserstats/tests/test_stats.py
@@ -16,38 +16,6 @@ from forgetracker import model as TM
 
 class TestStats(TestController):
 
-    @td.with_user_project('test-user')
-    def test_init_values(self):
-        user = User.register(dict(username='test-new-user',
-            display_name='Test Stats'),
-            make_project=False)
-
-        artifacts = user.stats.getArtifacts()
-        tickets = user.stats.getTickets()
-        commits = user.stats.getCommits()
-        assert user.stats.tot_logins_count == 0
-        assert artifacts['created'] == 0
-        assert artifacts['modified'] == 0
-        assert tickets['assigned'] == 0
-        assert tickets['solved'] == 0
-        assert tickets['revoked'] == 0
-        assert tickets['averagesolvingtime'] is None
-        assert commits['number'] == 0
-        assert commits['lines'] == 0
-
-        lmartifacts = user.stats.getLastMonthArtifacts()
-        lmtickets = user.stats.getLastMonthTickets()
-        lmcommits = user.stats.getLastMonthCommits()
-        assert user.stats.getLastMonthLogins() == 0
-        assert lmartifacts['created'] == 0
-        assert lmartifacts['modified'] == 0
-        assert lmtickets['assigned'] == 0
-        assert lmtickets['solved'] == 0
-        assert lmtickets['revoked'] == 0
-        assert lmtickets['averagesolvingtime'] is None
-        assert lmcommits['number'] == 0
-        assert lmcommits['lines'] == 0
-
     def test_login(self):
         user = User.by_username('test-user')
         init_logins = c.user.stats.tot_logins_count
@@ -198,4 +166,3 @@ class TestGitCommit(unittest.TestCase, TestController):
         assert commits['number'] == 4
         lmcommits = c.user.stats.getLastMonthCommits()
         assert lmcommits['number'] == 4
-


[32/42] git commit: [#5453] Setting userstats tool as anchored for user projects

Posted by br...@apache.org.
[#5453] Setting userstats tool as anchored for user projects


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/116b9ed7
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/116b9ed7
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/116b9ed7

Branch: refs/heads/db/5453
Commit: 116b9ed7bd45a7c3eefb008c3ade80978fdc56f6
Parents: 350e1be
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sun Mar 3 10:30:05 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:33 2013 +0000

----------------------------------------------------------------------
 Allura/allura/lib/app_globals.py    |    4 +---
 Allura/allura/model/auth.py         |    6 ++++--
 Allura/allura/model/project.py      |    2 --
 Allura/allura/websetup/bootstrap.py |    1 +
 Allura/development.ini              |    6 ------
 ForgeUserStats/test.ini             |    1 -
 6 files changed, 6 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/116b9ed7/Allura/allura/lib/app_globals.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index ee2c9b9..1ab9b31 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -172,12 +172,10 @@ class Globals(object):
         # Zarkov logger
         self._zarkov = None
 
-        self.show_userstats = config.get('userstats.enable','false')=='true'
         # Set listeners to update stats
         statslisteners = []
         for name, ep in self.entry_points['stats'].iteritems():
-            if config.get('%s.enable' % name,'false')=='true':
-                statslisteners.append(ep())
+            statslisteners.append(ep())
         self.statsUpdater = PostEvent(statslisteners)
 
     @LazyProperty

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/116b9ed7/Allura/allura/model/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index 23f5366..a0bdb20 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -341,9 +341,11 @@ class User(MappedClass, ActivityNode, ActivityObject):
 
     @property
     def stats(self):
-        if g.show_userstats:
+        if 'userstats' in g.entry_points['stats']:
             from forgeuserstats.model.stats import UserStats
-            return UserStats.query.get(_id=self.stats_id)
+            if self.stats_id:
+                return UserStats.query.get(_id=self.stats_id)
+            return UserStats.create(self)
         else: 
             return None
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/116b9ed7/Allura/allura/model/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index ab47f61..9f07f67 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -715,8 +715,6 @@ class Project(MappedClass, ActivityNode, ActivityObject):
                         ('admin', 'admin', 'Admin'),
                         ('search', 'search', 'Search'),
                         ('activity', 'activity', 'Activity')]
-                if g.show_userstats:
-                    apps = apps + [('userstats', 'userstats', 'Statistics')]
             else:
                 apps = [('admin', 'admin', 'Admin'),
                         ('search', 'search', 'Search'),

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/116b9ed7/Allura/allura/websetup/bootstrap.py
----------------------------------------------------------------------
diff --git a/Allura/allura/websetup/bootstrap.py b/Allura/allura/websetup/bootstrap.py
index 9614b99..747504c 100644
--- a/Allura/allura/websetup/bootstrap.py
+++ b/Allura/allura/websetup/bootstrap.py
@@ -88,6 +88,7 @@ def bootstrap(command, conf, vars):
                                               google_analytics = False))
     n_users = M.Neighborhood(name='Users', url_prefix='/u/',
                              shortname_prefix='u/',
+                             anchored_tools='userstats:Statistics',
                              features=dict(private_projects = True,
                                            max_projects = None,
                                            css = 'none',

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/116b9ed7/Allura/development.ini
----------------------------------------------------------------------
diff --git a/Allura/development.ini b/Allura/development.ini
index 68821e4..3dc76d0 100644
--- a/Allura/development.ini
+++ b/Allura/development.ini
@@ -126,12 +126,6 @@ scm.repos.tarball.url_prefix = http://localhost/
 
 trovecategories.enableediting = true
 
-# If set to false, the stats of the user are not
-# updated and they are not shown to users.
-# Note: the name of the parameter has to be the same
-#       of the entry point, followed by .enable
-userstats.enable = true
-
 # ActivityStream
 activitystream.master = mongodb://127.0.0.1:27017
 activitystream.database = activitystream

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/116b9ed7/ForgeUserStats/test.ini
----------------------------------------------------------------------
diff --git a/ForgeUserStats/test.ini b/ForgeUserStats/test.ini
index 04c1c6e..6753aa4 100644
--- a/ForgeUserStats/test.ini
+++ b/ForgeUserStats/test.ini
@@ -5,7 +5,6 @@
 #
 [DEFAULT]
 debug = true
-userstats.enable = true
 
 [server:main]
 use = egg:Paste#http


[21/42] git commit: [5453] Correct .ini file for tests in ForgeUserStats

Posted by br...@apache.org.
[5453] Correct .ini file for tests in ForgeUserStats


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/e87344b4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/e87344b4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/e87344b4

Branch: refs/heads/db/5453
Commit: e87344b4179213c2003f95f6413962f55a42a33f
Parents: 10a6454
Author: Stefano Invernizzi <st...@apache.org>
Authored: Fri Feb 1 21:25:01 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:32 2013 +0000

----------------------------------------------------------------------
 ForgeUserStats/test.ini |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e87344b4/ForgeUserStats/test.ini
----------------------------------------------------------------------
diff --git a/ForgeUserStats/test.ini b/ForgeUserStats/test.ini
index ca15ecd..04c1c6e 100644
--- a/ForgeUserStats/test.ini
+++ b/ForgeUserStats/test.ini
@@ -5,7 +5,7 @@
 #
 [DEFAULT]
 debug = true
-user.stats.enable = true
+userstats.enable = true
 
 [server:main]
 use = egg:Paste#http


[39/42] git commit: [#5453] only count commits for incremental commmits, not full refreshes

Posted by br...@apache.org.
[#5453] only count commits for incremental commmits, not full refreshes


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/ddbfd02c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/ddbfd02c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/ddbfd02c

Branch: refs/heads/db/5453
Commit: ddbfd02c348824ec77fbc375198bf67b3c9d480d
Parents: c0eb784
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Wed Mar 27 21:04:59 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:34 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/repo_refresh.py |   15 ++++++++-------
 1 files changed, 8 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ddbfd02c/Allura/allura/model/repo_refresh.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repo_refresh.py b/Allura/allura/model/repo_refresh.py
index 63e2bb0..a9c8719 100644
--- a/Allura/allura/model/repo_refresh.py
+++ b/Allura/allura/model/repo_refresh.py
@@ -111,13 +111,14 @@ def refresh_repo(repo, all_commits=False, notify=True):
             if (i+1) % 100 == 0:
                 log.info('Compute last commit info %d: %s', (i+1), ci._id)
 
-    for commit in commit_ids:
-        new = repo.commit(commit)
-        user = User.by_email_address(new.committed.email)
-        if user is None:
-            user = User.by_username(new.committed.name)
-        if user is not None:
-            g.statsUpdater.newCommit(new, repo.app_config.project, user)
+    if not all_commits:
+        for commit in commit_ids:
+            new = repo.commit(commit)
+            user = User.by_email_address(new.committed.email)
+            if user is None:
+                user = User.by_username(new.committed.name)
+            if user is not None:
+                g.statsUpdater.newCommit(new, repo.app_config.project, user)
 
     log.info('Refresh complete for %s', repo.full_fs_path)
     g.post_event(


[30/42] git commit: [#5453] Fix svn test to work with user stats

Posted by br...@apache.org.
[#5453] Fix svn test to work with user stats

Signed-off-by: Tim Van Steenburgh <tv...@gmail.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/5d9da324
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/5d9da324
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/5d9da324

Branch: refs/heads/db/5453
Commit: 5d9da324f1528bdbebadd92eeb3d31518cd6539d
Parents: df9c974
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Mar 12 20:04:01 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:33 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/contrib_stats.py             |    2 +-
 ForgeSVN/forgesvn/tests/model/test_repository.py |   14 +++++++++-----
 2 files changed, 10 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/5d9da324/Allura/allura/model/contrib_stats.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/contrib_stats.py b/Allura/allura/model/contrib_stats.py
index 5a4751a..3799ea5 100644
--- a/Allura/allura/model/contrib_stats.py
+++ b/Allura/allura/model/contrib_stats.py
@@ -16,7 +16,7 @@ from allura.lib import helpers as h
 
 class Stats(MappedClass):
     class __mongometa__:
-        name='userstats'
+        name='basestats'
         session = main_orm_session
         unique_indexes = [ '_id']
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/5d9da324/ForgeSVN/forgesvn/tests/model/test_repository.py
----------------------------------------------------------------------
diff --git a/ForgeSVN/forgesvn/tests/model/test_repository.py b/ForgeSVN/forgesvn/tests/model/test_repository.py
index 43d6cbc..100fa0b 100644
--- a/ForgeSVN/forgesvn/tests/model/test_repository.py
+++ b/ForgeSVN/forgesvn/tests/model/test_repository.py
@@ -518,8 +518,12 @@ class TestRepo(_TestWithRepo):
         assert self.repo.guess_type('.gitignore') == ('text/plain', None)
 
     def test_refresh(self):
+        committer_name = 'Test Committer'
+        committer_email = 'test@example.com'
         ci = mock.Mock()
-        ci.authored.name = 'Test Committer'
+        ci.authored.name = committer_name
+        ci.committed.name = committer_name
+        ci.committed.email = committer_email
         ci.author_url = '/u/test-committer/'
         self.repo.count_revisions=mock.Mock(return_value=100)
         self.repo._impl.commit = mock.Mock(return_value=ci)
@@ -529,8 +533,8 @@ class TestRepo(_TestWithRepo):
         def refresh_commit_info(oid, seen, lazy=False):
             M.repo.CommitDoc(dict(
                     authored=dict(
-                        name='Test Committer',
-                        email='test@test.com'),
+                        name=committer_name,
+                        email=committer_email),
                     _id=oid)).m.insert()
         def set_heads():
             self.repo.heads = [ ming.base.Object(name='head', object_id='foo0', count=100) ]
@@ -544,13 +548,13 @@ class TestRepo(_TestWithRepo):
         notifications = M.Notification.query.find().all()
         for n in notifications:
             if '100 new commits' in n.subject:
-                assert "master,branch:  by Test Committer http://localhost/ci/foo99" in n.text
+                assert "master,branch:  by %s http://localhost/ci/foo99" % committer_name in n.text
                 break
         else:
             assert False, 'Did not find notification'
         assert M.Feed.query.find(dict(
             title='New commit',
-            author_name='Test Committer')).count()
+            author_name=committer_name)).count()
 
     def test_refresh_private(self):
         ci = mock.Mock()


[31/42] git commit: [#5453] Make image assets local to tool

Posted by br...@apache.org.
[#5453] Make image assets local to tool


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/a01a1101
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/a01a1101
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/a01a1101

Branch: refs/heads/db/5453
Commit: a01a11011c74b0ccf1a3498351580bcbe70acfe2
Parents: 116b9ed
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Mar 5 21:44:54 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:33 2013 +0000

----------------------------------------------------------------------
 ForgeUserStats/forgeuserstats/main.py              |   10 +++++-----
 .../nf/userstats/images/stats_24.png               |  Bin 0 -> 654 bytes
 .../nf/userstats/images/stats_32.png               |  Bin 0 -> 715 bytes
 .../nf/userstats/images/stats_48.png               |  Bin 0 -> 925 bytes
 4 files changed, 5 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a01a1101/ForgeUserStats/forgeuserstats/main.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/main.py b/ForgeUserStats/forgeuserstats/main.py
index dcecdb9..acc196d 100644
--- a/ForgeUserStats/forgeuserstats/main.py
+++ b/ForgeUserStats/forgeuserstats/main.py
@@ -71,8 +71,8 @@ class UserStatsListener(EventsListener):
 
 class ForgeUserStatsApp(Application):
     __version__ = version.__version__
-    tool_label='Statistics'
-    default_mount_label='Statistics'
+    tool_label='Stats'
+    default_mount_label='Stats'
     default_mount_point='stats'
     permissions = ['configure', 'read', 'write',
                     'unmoderated_post', 'post', 'moderate', 'admin']
@@ -81,9 +81,9 @@ class ForgeUserStatsApp(Application):
     config_options = Application.config_options
     default_external_feeds = []
     icons={
-        24:'images/stats_24.png',
-        32:'images/stats_32.png',
-        48:'images/stats_48.png'
+        24:'../../tool/userstats/images/stats_24.png',
+        32:'../../tool/userstats/images/stats_32.png',
+        48:'../../tool/userstats/images/stats_48.png'
     }
     root = ForgeUserStatsController()
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a01a1101/ForgeUserStats/forgeuserstats/nf/userstats/images/stats_24.png
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/nf/userstats/images/stats_24.png b/ForgeUserStats/forgeuserstats/nf/userstats/images/stats_24.png
new file mode 100644
index 0000000..e3e9f5e
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/nf/userstats/images/stats_24.png differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a01a1101/ForgeUserStats/forgeuserstats/nf/userstats/images/stats_32.png
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/nf/userstats/images/stats_32.png b/ForgeUserStats/forgeuserstats/nf/userstats/images/stats_32.png
new file mode 100644
index 0000000..ba76a37
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/nf/userstats/images/stats_32.png differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a01a1101/ForgeUserStats/forgeuserstats/nf/userstats/images/stats_48.png
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/nf/userstats/images/stats_48.png b/ForgeUserStats/forgeuserstats/nf/userstats/images/stats_48.png
new file mode 100644
index 0000000..851e122
Binary files /dev/null and b/ForgeUserStats/forgeuserstats/nf/userstats/images/stats_48.png differ


[22/42] git commit: [5453] removed duplicated links

Posted by br...@apache.org.
[5453] removed duplicated links


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/be1b5ab5
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/be1b5ab5
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/be1b5ab5

Branch: refs/heads/db/5453
Commit: be1b5ab549932e2982e506b8d7d2c03f2300ade3
Parents: 3274677
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Jan 30 17:44:14 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:32 2013 +0000

----------------------------------------------------------------------
 .../forgeuserstats/templates/commits.html          |    1 -
 .../forgeuserstats/templates/tickets.html          |    1 -
 2 files changed, 0 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/be1b5ab5/ForgeUserStats/forgeuserstats/templates/commits.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/commits.html b/ForgeUserStats/forgeuserstats/templates/commits.html
index 2bca003..12f9712 100644
--- a/ForgeUserStats/forgeuserstats/templates/commits.html
+++ b/ForgeUserStats/forgeuserstats/templates/commits.html
@@ -37,6 +37,5 @@
       </table>
     </div>
     {% endif %}
-    <div class="grid-20"><a href="/userstats/{{user.username}}">Go back to general statistics</a></div>
   {% endif %}
 {% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/be1b5ab5/ForgeUserStats/forgeuserstats/templates/tickets.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/tickets.html b/ForgeUserStats/forgeuserstats/templates/tickets.html
index 9bf411b..4604c16 100644
--- a/ForgeUserStats/forgeuserstats/templates/tickets.html
+++ b/ForgeUserStats/forgeuserstats/templates/tickets.html
@@ -47,6 +47,5 @@
       </table>
     </div>
     {% endif %}
-    <div class="grid-20"><a href="/userstats/{{user.username}}">Go back to general statistics</a></div>
   {% endif %}
 {% endblock %}


[24/42] git commit: [5453] correct links in userstats

Posted by br...@apache.org.
[5453] correct links in userstats


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/10a6454f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/10a6454f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/10a6454f

Branch: refs/heads/db/5453
Commit: 10a6454fd3bbdcb0027f4f724d738e79f88cb3af
Parents: be1b5ab
Author: Stefano Invernizzi <st...@apache.org>
Authored: Thu Jan 31 15:57:12 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:32 2013 +0000

----------------------------------------------------------------------
 ForgeUserStats/forgeuserstats/templates/index.html |   11 +++++++----
 1 files changed, 7 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/10a6454f/ForgeUserStats/forgeuserstats/templates/index.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/index.html b/ForgeUserStats/forgeuserstats/templates/index.html
index abcb5de..37cd5da 100644
--- a/ForgeUserStats/forgeuserstats/templates/index.html
+++ b/ForgeUserStats/forgeuserstats/templates/index.html
@@ -13,6 +13,11 @@
 {% block content %}
   {% if user %}
 
+    {% if category %}
+       <ul>
+         <li><a href="/userstats/{{user.username}}">Go back to general statistics</a></li>
+       </ul>
+    {% endif %}
     <h2>General statistics</h2>
     <table>
       <thead>
@@ -352,7 +357,7 @@
           <tbody>
             {% for cat, count in categories %}
               <tr>
-                <td><a href="/userstats/{{user.username}}/category/{{cat.fullname}}">{{cat.fullname}}</a></td>
+                <td><a href="/userstats/{{user.username}}/category/{{cat.shortname}}">{{cat.fullname}}</a></td>
                 <td>{{count}}</td>
               </tr>
             {% endfor %}
@@ -368,9 +373,7 @@
           </p>
         {% endif %}
     {% endif %}
-    {% if category %}
-        <div class="grid-20"><a href="/userstats/{{user.username}}">Go back to general statistics</a></div>
-    {% else %}
+    {% if not category %}
       <h2>Overview</h2>
       <table>
         <thead>


[37/42] git commit: [#5453] Correctly initialize user stats

Posted by br...@apache.org.
[#5453] Correctly initialize user stats


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/10ce7f2e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/10ce7f2e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/10ce7f2e

Branch: refs/heads/db/5453
Commit: 10ce7f2e9923b7ef116f4609aa2f343e71287a68
Parents: 8b79011
Author: Stefano Invernizzi <st...@apache.org>
Authored: Thu Mar 7 23:44:50 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:33 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/contrib_stats.py |    6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/10ce7f2e/Allura/allura/model/contrib_stats.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/contrib_stats.py b/Allura/allura/model/contrib_stats.py
index 51121ad..5a4751a 100644
--- a/Allura/allura/model/contrib_stats.py
+++ b/Allura/allura/model/contrib_stats.py
@@ -498,12 +498,12 @@ class Stats(MappedClass):
                     newstats = dict(
                         category=t,
                         commits=[],
-                        messages=[dict(
+                        messages=[],
+                        tickets=dict(
                             assigned=0,
                             solved=0,
                             revoked=0,
-                            totsolvingtime=0)],
-                        tickets={})
+                            totsolvingtime=0))
                     stats.general.append(newstats)
                     i = getElementIndex(stats.general, category=t)
                 for lang in ll:


[26/42] git commit: [5453] Improved code

Posted by br...@apache.org.
[5453] Improved code


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/81fec58d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/81fec58d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/81fec58d

Branch: refs/heads/db/5453
Commit: 81fec58d6ffd25952d8b0efbbdaaeadd19bcd017
Parents: ba64a67
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sat Feb 16 14:33:22 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:32 2013 +0000

----------------------------------------------------------------------
 Allura/allura/templates/user_preferences.html      |    1 +
 .../forgeuserstats/templates/artifacts.html        |    2 +-
 .../forgeuserstats/templates/commits.html          |    2 +-
 ForgeUserStats/forgeuserstats/templates/index.html |    2 +-
 .../forgeuserstats/templates/tickets.html          |    2 +-
 5 files changed, 5 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/81fec58d/Allura/allura/templates/user_preferences.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/user_preferences.html b/Allura/allura/templates/user_preferences.html
index 8159f52..bd4c71e 100644
--- a/Allura/allura/templates/user_preferences.html
+++ b/Allura/allura/templates/user_preferences.html
@@ -133,6 +133,7 @@
     <a name="Statistics"></a>
     <div class="grid-20">
       <h2>Contribution statistics</h2>
+      <ul><li><a href="/userstats/{{c.user.username}}">Click here to check your personal statistics</a></li></ul>
       {{g.theme.statistics_form.display(user=c.user)}}
     </div>
   {% endif %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/81fec58d/ForgeUserStats/forgeuserstats/templates/artifacts.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/artifacts.html b/ForgeUserStats/forgeuserstats/templates/artifacts.html
index d6a01d6..013c108 100644
--- a/ForgeUserStats/forgeuserstats/templates/artifacts.html
+++ b/ForgeUserStats/forgeuserstats/templates/artifacts.html
@@ -48,7 +48,7 @@
     </div>
     {% endif %}
   {% else %}
-    {% if not user.stats.visible %}
+    {% if user %}
       <h2>Statistics not available</h2>
       <div class="grid-20"> 
         This user has set his or her preferences so that personal statistics are not visible

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/81fec58d/ForgeUserStats/forgeuserstats/templates/commits.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/commits.html b/ForgeUserStats/forgeuserstats/templates/commits.html
index cec0ab7..10d1c67 100644
--- a/ForgeUserStats/forgeuserstats/templates/commits.html
+++ b/ForgeUserStats/forgeuserstats/templates/commits.html
@@ -38,7 +38,7 @@
     </div>
     {% endif %}
   {% else %}
-    {% if not user.stats.visible %}
+    {% if user %}
       <h2>Statistics not available</h2>
       <div class="grid-20"> 
         This user has set his or her preferences so that personal statistics are not visible

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/81fec58d/ForgeUserStats/forgeuserstats/templates/index.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/index.html b/ForgeUserStats/forgeuserstats/templates/index.html
index 4a8f504..653cd31 100644
--- a/ForgeUserStats/forgeuserstats/templates/index.html
+++ b/ForgeUserStats/forgeuserstats/templates/index.html
@@ -418,7 +418,7 @@
       </p>
     {% endif %}
   {% else %}
-    {% if not user.stats.visible %}
+    {% if user %}
       <h2>Statistics not available</h2>
       <div class="grid-20"> 
         This user has set his or her preferences so that personal statistics are not visible

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/81fec58d/ForgeUserStats/forgeuserstats/templates/tickets.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/tickets.html b/ForgeUserStats/forgeuserstats/templates/tickets.html
index 6ee66d4..a713b25 100644
--- a/ForgeUserStats/forgeuserstats/templates/tickets.html
+++ b/ForgeUserStats/forgeuserstats/templates/tickets.html
@@ -48,7 +48,7 @@
     </div>
     {% endif %}
   {% else %}
-    {% if not user.stats.visible %}
+    {% if user %}
       <h2>Statistics not available</h2>
       <div class="grid-20"> 
         This user has set his or her preferences so that personal statistics are not visible


[35/42] git commit: [#5453] Fix initialization of the stats object

Posted by br...@apache.org.
[#5453] Fix initialization of the stats object


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/8b790115
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/8b790115
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/8b790115

Branch: refs/heads/db/5453
Commit: 8b790115b688d5cb969bafed6a131a6b82c54a01
Parents: 63db556
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Mar 6 22:39:25 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:33 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/contrib_stats.py |    6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8b790115/Allura/allura/model/contrib_stats.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/contrib_stats.py b/Allura/allura/model/contrib_stats.py
index adc36db..51121ad 100644
--- a/Allura/allura/model/contrib_stats.py
+++ b/Allura/allura/model/contrib_stats.py
@@ -498,12 +498,12 @@ class Stats(MappedClass):
                     newstats = dict(
                         category=t,
                         commits=[],
-                        messages=dict(
+                        messages=[dict(
                             assigned=0,
                             solved=0,
                             revoked=0,
-                            totsolvingtime=0),
-                        tickets=[])   
+                            totsolvingtime=0)],
+                        tickets={})
                     stats.general.append(newstats)
                     i = getElementIndex(stats.general, category=t)
                 for lang in ll:


[25/42] git commit: [5453] Allowing to hide stats

Posted by br...@apache.org.
[5453] Allowing to hide stats


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/ba64a671
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/ba64a671
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/ba64a671

Branch: refs/heads/db/5453
Commit: ba64a671678e2797a7c582187ee7560d5945b72a
Parents: e87344b
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sat Feb 16 13:01:52 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:32 2013 +0000

----------------------------------------------------------------------
 Allura/allura/controllers/auth.py                  |   22 ++
 Allura/allura/ext/user_profile/user_main.py        |    2 +-
 Allura/allura/lib/plugin.py                        |    8 +
 Allura/allura/lib/widgets/forms.py                 |   16 +
 Allura/allura/model/contrib_stats.py               |    1 +
 Allura/allura/templates/user_preferences.html      |  287 +++++++++++++++
 .../forgeuserstats/controllers/userstats.py        |   11 +
 .../forgeuserstats/templates/artifacts.html        |   15 +-
 .../forgeuserstats/templates/commits.html          |   15 +-
 ForgeUserStats/forgeuserstats/templates/index.html |   15 +-
 .../forgeuserstats/templates/tickets.html          |   15 +-
 11 files changed, 401 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ba64a671/Allura/allura/controllers/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index 8e18767..1118d86 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -58,6 +58,7 @@ class F(object):
     remove_inactive_period_form = forms.RemoveInactivePeriodForm()
     save_skill_form = forms.AddUserSkillForm()
     remove_skill_form = forms.RemoveSkillForm()
+    set_statistics = forms.StatsPreferencesForm()
 
 class AuthController(BaseController):
 
@@ -726,6 +727,27 @@ class SubscriptionsController(BaseController):
     @h.vardec
     @expose()
     @require_post()
+    @validate(F.set_statistics, error_handler=index)
+    def set_statistics(self, **kw):
+        require_authenticated()
+        c.user.stats.visible = kw.get('visible', True)
+        flash('Your preferences about statistics were successfully updated!')
+        redirect('.#Statistics')
+
+    @expose()
+    @require_post()
+    def upload_sshkey(self, key=None):
+        ap = plugin.AuthenticationProvider.get(request)
+        try:
+            ap.upload_sshkey(c.user.username, key)
+        except AssertionError, ae:
+            flash('Error uploading key: %s' % ae, 'error')
+        flash('Key uploaded')
+        redirect('.')
+
+    @h.vardec
+    @expose()
+    @require_post()
     @validate(F.subscription_form, error_handler=index)
     def update_subscriptions(self, subscriptions=None, **kw):
         for s in subscriptions:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ba64a671/Allura/allura/ext/user_profile/user_main.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/user_profile/user_main.py b/Allura/allura/ext/user_profile/user_main.py
index 759f3f3..f3c3331 100644
--- a/Allura/allura/ext/user_profile/user_main.py
+++ b/Allura/allura/ext/user_profile/user_main.py
@@ -64,7 +64,7 @@ class UserProfileController(BaseController):
         user = c.project.user_project_of
         if not user:
             raise exc.HTTPNotFound()
-        if g.show_userstats:
+        if g.show_userstats and user.stats.visible:
             from forgeuserstats.main import ForgeUserStatsApp
             link, description = ForgeUserStatsApp.createlink(user)
         else:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ba64a671/Allura/allura/lib/plugin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index ed704d4..59f03e5 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -736,6 +736,14 @@ class ThemeProvider(object):
         return RemoveInactivePeriodForm()
 
     @LazyProperty
+    def statistics_form(self):
+        '''
+        :return: None, or an easywidgets Form to render on the user preferences page
+        '''
+        from allura.lib.widgets.forms import StatsPreferencesForm
+        return StatsPreferencesForm(action='/auth/prefs/set_statistics')
+
+    @LazyProperty
     def add_trove_category(self):
         '''
         :return: None, or an easywidgets Form to render on the page to create a

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ba64a671/Allura/allura/lib/widgets/forms.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/widgets/forms.py b/Allura/allura/lib/widgets/forms.py
index f76eff3..c1a1d75 100644
--- a/Allura/allura/lib/widgets/forms.py
+++ b/Allura/allura/lib/widgets/forms.py
@@ -451,6 +451,22 @@ class RemoveTimeSlotForm(ForgeForm):
         d['endtime'] = V.convertTime(kw.get('endtime',''))
         return d
 
+
+class StatsPreferencesForm(ForgeForm):
+    defaults=dict(ForgeForm.defaults)
+
+    class fields(ew_core.NameList):
+        visible = ew.Checkbox(
+            label='Make my personal statistics visible to other users.')
+            
+    def display(self, **kw):
+        if kw.get('user').stats.visible:
+            self.fields['visible'].attrs = {'checked':'true'}      
+        else:
+            self.fields['visible'].attrs = {}    
+        return super(ForgeForm, self).display(**kw)
+                
+
 class RemoveTroveCategoryForm(ForgeForm):
     defaults=dict(ForgeForm.defaults, submit_text=None, show_errors=False)
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ba64a671/Allura/allura/model/contrib_stats.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/contrib_stats.py b/Allura/allura/model/contrib_stats.py
index 8a71d86..d9cada2 100644
--- a/Allura/allura/model/contrib_stats.py
+++ b/Allura/allura/model/contrib_stats.py
@@ -21,6 +21,7 @@ class Stats(MappedClass):
 
     _id=FieldProperty(S.ObjectId)
 
+    visible = FieldProperty(bool, if_missing = True)
     registration_date = FieldProperty(datetime)
     general = FieldProperty([dict(
         category = S.ObjectId,

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ba64a671/Allura/allura/templates/user_preferences.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/user_preferences.html b/Allura/allura/templates/user_preferences.html
new file mode 100644
index 0000000..8159f52
--- /dev/null
+++ b/Allura/allura/templates/user_preferences.html
@@ -0,0 +1,287 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}{{c.user.username}} / Preferences{% endblock %}
+
+{% block header %}User Preferences for {{c.user.username}}{% endblock %}
+
+{% block content %}
+  <ul id="account-nav-menu" class="b-hornav droppy">
+      {% for item in menu -%}
+      <li id="{{ item.tabid }}">
+      <a href="{{ item.target }}">
+          {{ item.title }}
+          <div class="marker{% if item.target.rstrip('/') == request.path.rstrip('/') %} current{% endif %}"></div>
+      </a>
+      </li>
+      {%- endfor %}
+   </ul>
+
+  <div style="clear:both" class="grid-20">
+    <h2>Personal Settings</h2>
+    {{g.theme.personal_data_form.display(action="/auth/prefs/change_personal_data", user=c.user)}}
+  </div>
+
+  <div style="clear:both" class="grid-20">
+    <a name="Contacts"></a>
+    <h2>Personal Contacts</h2>
+    <h3>Skype account</h3>
+
+    {{g.theme.skype_account_form.display(action="/auth/prefs/skype_account",
+          initial_value=c.user.get_pref('skypeaccount'))}}
+
+    {%if c.user.get_pref('socialnetworks') or c.user.get_pref('telnumbers') or c.user.get_pref('webpages') %}
+      <h3>Other existing contacts</h3>
+        <table>
+          <tr>
+            <thead>
+              <th>Type</th>
+              <th>Contact</th>
+              <th>Actions</th>
+            </thead>
+          </tr>
+          {% for sn in c.user.get_pref('socialnetworks') %}
+             {{g.theme.remove_socialnetwork_form.display(account=sn.accounturl, socialnetwork=sn.socialnetwork)}}
+          {% endfor %}
+
+          {% for tn in c.user.get_pref('telnumbers') %}
+              {{g.theme.remove_textvalue_form.display(action="/auth/prefs/remove_telnumber", value=tn, label="Telephone number")}}
+          {%endfor%}
+
+          {% for ws in c.user.get_pref('webpages') %}
+              {{g.theme.remove_textvalue_form.display(action="/auth/prefs/remove_webpage", value=ws, label="Website url")}}
+          {%endfor%}
+        </table>
+    {% endif %}
+
+    <h3>Add a social network account</h3>
+    {{g.theme.add_socialnetwork_form.display(action="/auth/prefs/add_social_network")}}
+    <h3>Add a telephone number</h3>
+    {{g.theme.add_telnumber_form.display(action="/auth/prefs/add_telnumber")}}
+    <h3>Add a personal website</h3>
+    {{g.theme.add_website_form.display(action="/auth/prefs/add_webpage")}}
+  </div>
+
+  <a name="Availability"></a>
+  <div style="clear:both" class="grid-20">
+    <h2>Availability</h2>
+    <div class="grid-18">
+      If you want, you can set the weekly timeslot during which you are usually available to support other users of the forge.
+      Please, set your time intervals choosing a weekday and entering the time interval according to the timezone specified in your
+      personal data, using the format HH:MM. If you didn't set any timezone, your timeslots could be meaningless to other users,
+      therefore they will be ignored.
+    </div>
+    <div class="grid-18">
+      You can also specify periods of time during which you won't be able to work on the forge, in orther to communicate other users
+      that they can't contact you during those days. Please, do it specifying date intervals in format DD/MM/YYYY.
+    </div>
+  </div>
+  <div class="grid-20">
+    {%if c.user.get_availability_timeslots() %}
+      <h3>Existing availability timeslots</h3>
+      <table>
+        <tr>
+          <thead>
+            <th>Weekday</th>
+            <th>Start time</th>
+            <th>End time</th>
+            <th>Actions</th>
+          </thead>
+        </tr>
+        {% for ts in c.user.get_availability_timeslots() %}
+          {{g.theme.remove_timeslot_form.display(
+                action="/auth/prefs/remove_timeslot",
+                weekday=ts.week_day,
+                starttime=ts.start_time,
+                endtime=ts.end_time)}}
+        {%endfor%}
+      </table>
+    {% endif %}
+    <h3>Add a new availability timeslot</h3>
+    {{g.theme.add_timeslot_form.display(action="/auth/prefs/add_timeslot")}}
+  </div>
+
+  <div class="grid-20">
+    {%if c.user.get_inactive_periods() %}
+      <h3>Existing periods of inactivity on the forge</h3>
+      <table>
+        <tr>
+          <thead>
+            <th>Start date</th>
+            <th>End date</th>
+            <th>Actions</th>
+          </thead>
+        </tr>
+        {% for ip in c.user.get_inactive_periods() %}
+          {{g.theme.remove_inactive_period_form.display(
+                action="/auth/prefs/remove_inactive_period",
+                startdate=ip.start_date,
+                enddate=ip.end_date)}}
+        {%endfor%}
+      </table>
+    {% endif %}
+    <h3>Add a new period of inactivity on the forge</h3>
+    {{g.theme.add_inactive_period_form.display(action="/auth/prefs/add_inactive_period")}}
+  </div>
+
+  <div class="grid-20">
+    <h2>Skills list</h2>
+    <ul><li><a href="/auth/prefs/user_skills">Click here to check and change your skills list</a></li></ul>
+  </div>
+
+  {% if g.show_userstats %}
+    <a name="Statistics"></a>
+    <div class="grid-20">
+      <h2>Contribution statistics</h2>
+      {{g.theme.statistics_form.display(user=c.user)}}
+    </div>
+  {% endif %}
+
+  {% if g.theme.password_change_form %}
+  <div class="grid-20">
+    <h2>Change Password</h2>
+    {{ g.theme.password_change_form.display() }}
+  </div>
+  {% endif %}
+  {% if g.theme.upload_key_form %}
+  <div class="grid-20">
+    <h2>Upload ssh public key</h2>
+    {{ g.theme.upload_key_form.display() }}
+  </div>
+  {% endif %}
+
+  {% if tg.config.get('auth.method', 'local') == 'local' %}
+      <br style="clear:both"/>
+      <h2>API Token</h2>
+      {% if api_token %}
+        <p>
+          <b>API Key:</b><br/>
+          {{api_token.api_key}}<br/>
+          <b>Secret Key:</b><br/>
+          {{api_token.secret_key}}<br/>
+        </p>
+        <form method="POST" action="del_api_token" class="grid-18">
+          <input type="submit" value="Delete API Token">
+        </form>
+      {% else %}
+        <p>No API token generated</p>
+      {% endif %}
+      <form method="POST" action="gen_api_token" class="grid-18">
+        <input type="submit" value="(Re)generate API Token">
+      </form>
+  {% endif %}
+
+  <div style="clear:both"></div>
+
+  <h2>Authorized Third-party Applications</h2>
+  {% for access_tok in authorized_applications %}
+    <div>
+      <h3>{{access_tok.consumer_token.name}}</h3>
+      {{access_tok.consumer_token.description_html}}
+      {{ c.revoke_access.display(value=access_tok) }}
+      <br style="clear:both"/>
+  </div>
+ {% endfor %}
+    {% if not authorized_applications %}<p>No authorized third-party applications</p>{% endif %}
+
+
+  <h2>Subscriptions</h2>
+  {% if subscriptions %}
+    <p><em>Mark tools that you want to subscribe to. Unmark tools that you want to unsubscribe from. Press 'Save' button.</em></p>
+    {{c.form.display(action='update_subscriptions', value=dict(subscriptions=subscriptions))}}
+  {% else%}
+    <p>No subscriptions.</p>
+  {% endif %}
+  <hr/>
+  <div style="clear:both">&nbsp;</div>
+  <form action="update" method="post">
+        {% if tg.config.get('auth.method', 'local') == 'local' %}
+        <label class="grid-4">Display Name</label>
+        <div class="grid-18">
+          <input name="preferences.display_name" value="{{c.user.display_name}}" type="text">
+        </div>
+        {% endif %}
+        <label class="grid-4">Email Format</label>
+        <div class="grid-18">
+          <select name="preferences.email_format">
+            <option value="plain" {{'selected' if c.user.preferences.email_format == 'plain' else ''}}>Plain Text</option>
+            <option value="html" {{'selected' if c.user.preferences.email_format == 'html' else ''}}>HTML</option>
+            <option value="both" {{'selected' if c.user.preferences.email_format == 'both' else ''}}>Combined</option>
+          </select>
+        </div>
+        {% if tg.config.get('auth.method', 'local') == 'local' %}
+        <label class="grid-4">Page Size</label>
+        <div class="grid-18">
+          <select name="preferences.results_per_page">
+            {% for per_page in [25, 50, 100, 250] %}
+                <option {% if per_page == c.user.preferences.results_per_page %}selected="selected"{% endif %}
+                   value="{{per_page}}">{{per_page}}</option>
+            {% endfor %}
+          </select>
+        </div>
+        {% endif %}
+
+    {% if tg.config.get('auth.method', 'local') == 'local' %}
+      {% for a in c.user.email_addresses %}
+        <input name="addr-{{loop.index0}}.ord" value="{{loop.index0}}" type="hidden"/>
+      {% endfor %}
+      {% if c.user.email_addresses %}
+        <h3 class="grid-18">Email Addresses</h3>
+        <table class="grid-18">
+          <tr>
+            <th>Primary?</th>
+            <th>Address</th>
+            <th>Confirmed</th>
+            <th></th>
+          </tr>
+          {% for a in c.user.email_addresses %}
+          <tr>
+            {% set obj = c.user.address_object(a) %}
+            <td>{{lib.radio_button('primary_addr', None, a, c.user.preferences.email_address)}}</td>
+            <td>{{a}}</td>
+            {% if obj %}
+            <td>
+              {% if obj.confirmed %}
+                yes
+              {% else %}
+                no (<a href="{{g.url('/auth/send_verification_link', a=a)}}">verify</a>)
+              {% endif %}
+            </td>
+            {% else %}
+              <td>Unknown addr obj {{a}}</td>
+            {% endif %}
+            <td>{{lib.submit_button('Delete', 'addr-%s.delete' % i)}}</td>
+          </tr>
+          {% endfor %}
+        </table>
+        {% endif %}
+        <div class="grid-18">
+        {{lib.text_field('new_addr.addr', 'New Email Address')}}
+        {{lib.submit_button('Claim Address', name='new_addr.claim')}}
+        </div>
+
+        {% if c.user.open_ids %}
+        <h3 class="grid-18">OpenIDs Claimed</h3>
+        <table class="grid-18">
+          <tr>
+            <th>OpenID</th>
+            <th></th>
+          </tr>
+          {% for oid in c.user.open_ids %}
+            {% set obj = c.user.openid_object(oid) %}
+          <tr>
+            <td>{{oid}}</td>
+            <td>{{lib.submit_button('Delete', 'oid-%s.delete' % loop.index0)}}</td>
+          </tr>
+          {% endfor %}
+        </table>
+        {% endif %}
+        <div class="grid-18">
+        <a href="/auth/claim_oid">Claim New OpenID</a>
+        </div>
+    {% endif %}
+    <div class="grid-18">
+    {{lib.submit_button('Save Changes')}}
+    </div>
+  </form>
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ba64a671/ForgeUserStats/forgeuserstats/controllers/userstats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/controllers/userstats.py b/ForgeUserStats/forgeuserstats/controllers/userstats.py
index b84eb0f..3944557 100644
--- a/ForgeUserStats/forgeuserstats/controllers/userstats.py
+++ b/ForgeUserStats/forgeuserstats/controllers/userstats.py
@@ -5,6 +5,7 @@ from allura.controllers import BaseController
 import allura.model as M
 from allura.lib.graphics.graphic_methods import create_histogram, create_progress_bar
 from forgeuserstats.model.stats import UserStats
+from pylons import c
 
 class ForgeUserStatsController(BaseController):
 
@@ -33,6 +34,8 @@ class ForgeUserStatsController(BaseController):
         if not self.user: 
             return dict(user=None)
         stats = self.user.stats
+        if (not stats.visible) and (c.user != self.user):
+            return dict(user=self.user)
 
         ret_dict = _getDataForCategory(None, stats)
         ret_dict['user'] = self.user
@@ -133,6 +136,8 @@ class ForgeUserStatsCatController(BaseController):
         if not self.user:
             return dict(user=None)
         stats = self.user.stats
+        if (not stats.visible) and (c.user != self.user):
+            return dict(user=self.user)
         
         cat_id = None
         if self.category: 
@@ -156,6 +161,8 @@ class ForgeUserStatsMetricController(BaseController):
         if not self.user:
             return dict(user=None)
         stats = self.user.stats
+        if (not stats.visible) and (c.user != self.user):
+            return dict(user=self.user)
         
         commits = stats.getCommitsByCategory()
         return dict(user = self.user,
@@ -166,6 +173,8 @@ class ForgeUserStatsMetricController(BaseController):
     def artifacts(self, **kw):
         if not self.user:
             return dict(user=None)
+        if (not stats.visible) and (c.user != self.user):
+            return dict(user=self.user)
 
         stats = self.user.stats       
         artifacts = stats.getArtifactsByCategory(detailed=True)
@@ -176,6 +185,8 @@ class ForgeUserStatsMetricController(BaseController):
     def tickets(self, **kw):
         if not self.user: 
             return dict(user=None)
+        if (not stats.visible) and (c.user != self.user):
+            return dict(user=self.user)
 
         artifacts = self.user.stats.getTicketsByCategory()
         return dict(user = self.user, data = artifacts) 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ba64a671/ForgeUserStats/forgeuserstats/templates/artifacts.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/artifacts.html b/ForgeUserStats/forgeuserstats/templates/artifacts.html
index 9492628..d6a01d6 100644
--- a/ForgeUserStats/forgeuserstats/templates/artifacts.html
+++ b/ForgeUserStats/forgeuserstats/templates/artifacts.html
@@ -9,7 +9,7 @@
 
 {% block content %}
 
-  {% if user %}
+  {% if user and (user.stats.visible or (c.user == user)) %}
     <div class="grid-20">
       <ul><li><a href="/userstats/{{user.username}}">Go back to general statistics</a></li></ul>
     </div>
@@ -47,6 +47,19 @@
       </table>
     </div>
     {% endif %}
+  {% else %}
+    {% if not user.stats.visible %}
+      <h2>Statistics not available</h2>
+      <div class="grid-20"> 
+        This user has set his or her preferences so that personal statistics are not visible
+        to other users of the forge.
+      </div>
+    {% else %}
+      <h2>Invalid user</h2>
+      <div class="grid-20"> 
+        You are looking for personal statistics of a user which doesn't exist on this forge. Check your url.
+      </div>
+    {% endif %}
   {% endif %}
 
 {% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ba64a671/ForgeUserStats/forgeuserstats/templates/commits.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/commits.html b/ForgeUserStats/forgeuserstats/templates/commits.html
index 12f9712..cec0ab7 100644
--- a/ForgeUserStats/forgeuserstats/templates/commits.html
+++ b/ForgeUserStats/forgeuserstats/templates/commits.html
@@ -9,7 +9,7 @@
 
 {% block content %}
 
-  {% if user %}
+  {% if user and (user.stats.visible or (c.user == user)) %}
     <div class="grid-20">
       <ul><li><a href="/userstats/{{user.username}}">Go back to general statistics</a></li></ul>
     </div>
@@ -37,5 +37,18 @@
       </table>
     </div>
     {% endif %}
+  {% else %}
+    {% if not user.stats.visible %}
+      <h2>Statistics not available</h2>
+      <div class="grid-20"> 
+        This user has set his or her preferences so that personal statistics are not visible
+        to other users of the forge.
+      </div>
+    {% else %}
+      <h2>Invalid user</h2>
+      <div class="grid-20"> 
+        You are looking for personal statistics of a user which doesn't exist on this forge. Check your url.
+      </div>
+    {% endif %}
   {% endif %}
 {% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ba64a671/ForgeUserStats/forgeuserstats/templates/index.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/index.html b/ForgeUserStats/forgeuserstats/templates/index.html
index 37cd5da..4a8f504 100644
--- a/ForgeUserStats/forgeuserstats/templates/index.html
+++ b/ForgeUserStats/forgeuserstats/templates/index.html
@@ -11,7 +11,7 @@
 {% endblock %}
 
 {% block content %}
-  {% if user %}
+  {% if user and (user.stats.visible or (c.user == user)) %}
 
     {% if category %}
        <ul>
@@ -418,7 +418,18 @@
       </p>
     {% endif %}
   {% else %}
-    Invalid user!
+    {% if not user.stats.visible %}
+      <h2>Statistics not available</h2>
+      <div class="grid-20"> 
+        This user has set his or her preferences so that personal statistics are not visible
+        to other users of the forge.
+      </div>
+    {% else %}
+      <h2>Invalid user</h2>
+      <div class="grid-20"> 
+        You are looking for personal statistics of a user which doesn't exist on this forge. Check your url.
+      </div>
+    {% endif %}
   {% endif %}
 
 {% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ba64a671/ForgeUserStats/forgeuserstats/templates/tickets.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/tickets.html b/ForgeUserStats/forgeuserstats/templates/tickets.html
index 4604c16..6ee66d4 100644
--- a/ForgeUserStats/forgeuserstats/templates/tickets.html
+++ b/ForgeUserStats/forgeuserstats/templates/tickets.html
@@ -9,7 +9,7 @@
 
 {% block content %}
 
-  {% if user %}
+  {% if user and (user.stats.visible or (c.user == user)) %}
     <div class="grid-20">
       <ul><li><a href="/userstats/{{user.username}}">Go back to general statistics</a></li></ul>
     </div>
@@ -47,5 +47,18 @@
       </table>
     </div>
     {% endif %}
+  {% else %}
+    {% if not user.stats.visible %}
+      <h2>Statistics not available</h2>
+      <div class="grid-20"> 
+        This user has set his or her preferences so that personal statistics are not visible
+        to other users of the forge.
+      </div>
+    {% else %}
+      <h2>Invalid user</h2>
+      <div class="grid-20"> 
+        You are looking for personal statistics of a user which doesn't exist on this forge. Check your url.
+      </div>
+    {% endif %}
   {% endif %}
 {% endblock %}


[14/42] git commit: [5453] Moved loop through stats listeners

Posted by br...@apache.org.
[5453] Moved loop through stats listeners


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/dd02cc27
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/dd02cc27
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/dd02cc27

Branch: refs/heads/db/5453
Commit: dd02cc27f78ab2496c06116d97563b35ea986d52
Parents: a972fcb
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sat Jan 19 17:11:42 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:31 2013 +0000

----------------------------------------------------------------------
 Allura/allura/eventslistener.py           |   36 ++++++++++++++++++++++++
 Allura/allura/lib/app_globals.py          |   19 +++++-------
 Allura/allura/lib/plugin.py               |    3 +-
 Allura/allura/model/artifact.py           |   10 ++----
 Allura/allura/model/auth.py               |    3 +-
 Allura/allura/model/repo_refresh.py       |    3 +-
 ForgeTracker/forgetracker/model/ticket.py |   12 +++----
 7 files changed, 56 insertions(+), 30 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/dd02cc27/Allura/allura/eventslistener.py
----------------------------------------------------------------------
diff --git a/Allura/allura/eventslistener.py b/Allura/allura/eventslistener.py
index 15adf00..f508013 100644
--- a/Allura/allura/eventslistener.py
+++ b/Allura/allura/eventslistener.py
@@ -24,4 +24,40 @@ class EventsListener:
     def ticketEvent(self, event_type, ticket, project, user):
         pass
 
+    def addUserToOrganization(self, newMembership):
+        pass
+
+'''This class simply allows to iterate through all the registered listeners,
+so that all of them are called to update statistics.'''
+class PostEvent:
+    def __init__(self, listeners):
+        self.listeners = listeners
+
+    def __iterate(self, event, *d):
+        for l in self.listeners:
+            getattr(l, event)(*d)
+
+    def newArtifact(self, art_type, art_datetime, project, user):
+        self.__iterate('newArtifact', art_type, art_datetime, project, user)
+
+    def modifiedArtifact(self, art_type, art_datetime, project, user):
+        self.__iterate('modifiedArtifact',art_type,art_datetime,project,user)
+
+    def newUser(self, user):
+        self.__iterate('newUser', user)
+
+    def newOrganization(self, organization):
+        self.__iterate('newOrganization', organization)
+
+    def addUserLogin(self, user):
+        self.__iterate('addUserLogin', user)
+
+    def newCommit(self, newcommit, project, user):
+        self.__iterate('newCommit', newcommit, project, user)
+
+    def ticketEvent(self, event_type, ticket, project, user):
+        self.__iterate('ticketEvent', event_type, ticket, project, user)
+
+    def addUserToOrganization(self, organization):
+        self.__iterate('addUserToOrganization', organization)
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/dd02cc27/Allura/allura/lib/app_globals.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index 1e9019c..71426a6 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -30,6 +30,7 @@ from ming.utils import LazyProperty
 import allura.tasks.event_tasks
 from allura import model as M
 from allura.lib.markdown_extensions import ForgeExtension
+from allura.eventslistener import PostEvent
 
 from allura.lib import gravatar, plugin, utils
 from allura.lib import helpers as h
@@ -165,23 +166,19 @@ class Globals(object):
             theme=_cache_eps('allura.theme'),
             user_prefs=_cache_eps('allura.user_prefs'),
             spam=_cache_eps('allura.spam'),
+            stats=_cache_eps('allura.stats'),
             )
 
         # Zarkov logger
         self._zarkov = None
 
-        self.show_userstats = False
+        self.show_userstats = config.get('user.stats.enable','false')=='true'
         # Set listeners to update stats
-        self.statslisteners = []
-        for ep in pkg_resources.iter_entry_points("allura.stats"):
-            if ep.name.lower() == 'userstats':
-                self.show_userstats = config.get(
-                    'user.stats.enable','false')=='true'
-                if self.show_userstats:
-                    self.statslisteners.append(ep.load()().listener)
-            else:
-                self.statslisteners.append(ep.load()().listener)
-
+        statslisteners = []
+        ep = self.entry_points['stats'].get('userstats')
+        if self.show_userstats and ep:
+            statslisteners.append(ep().listener)
+        self.statsUpdater = PostEvent(statslisteners)
 
     @LazyProperty
     def spam_checker(self):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/dd02cc27/Allura/allura/lib/plugin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index 61debc9..ed704d4 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -98,8 +98,7 @@ class AuthenticationProvider(object):
             self.session['userid'] = user._id
             self.session.save()
             g.zarkov_event('login', user=user)
-            for l in g.statslisteners:
-                l.addUserLogin(user)
+            g.statsUpdater.addUserLogin(user)
             return user
         except exc.HTTPUnauthorized:
             self.logout()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/dd02cc27/Allura/allura/model/artifact.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/artifact.py b/Allura/allura/model/artifact.py
index b3760dd..7628620 100644
--- a/Allura/allura/model/artifact.py
+++ b/Allura/allura/model/artifact.py
@@ -374,13 +374,11 @@ class VersionedArtifact(Artifact):
                  self.version, self.__class__)
         if update_stats: 
             if self.version > 1:
-                for l in g.statslisteners:
-                    l.modifiedArtifact(
-                        self.type_s, self.mod_date, self.project, c.user)
+                g.statsUpdater.modifiedArtifact(
+                    self.type_s, self.mod_date, self.project, c.user)
             else :
-                for l in g.statslisteners:
-                    l.newArtifact(
-                        self.type_s, self.mod_date, self.project, c.user)
+                g.statsUpdater.newArtifact(
+                    self.type_s, self.mod_date, self.project, c.user)
         return ss
 
     def get_version(self, n):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/dd02cc27/Allura/allura/model/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index cb9bdfb..9ed7067 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -593,8 +593,7 @@ class User(MappedClass, ActivityNode, ActivityObject):
         if user and 'display_name' in doc:
             user.set_pref('display_name', doc['display_name'])
         if user:
-            for l in g.statslisteners:
-                l.newUser(user)
+            g.statsUpdater.newUser(user)
         if user and make_project:
             n = M.Neighborhood.query.get(name='Users')
             n.register_project(auth_provider.user_project_shortname(user),

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/dd02cc27/Allura/allura/model/repo_refresh.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repo_refresh.py b/Allura/allura/model/repo_refresh.py
index 1993f57..63e2bb0 100644
--- a/Allura/allura/model/repo_refresh.py
+++ b/Allura/allura/model/repo_refresh.py
@@ -117,8 +117,7 @@ def refresh_repo(repo, all_commits=False, notify=True):
         if user is None:
             user = User.by_username(new.committed.name)
         if user is not None:
-            for l in g.statslisteners:
-                l.newCommit(new, repo.app_config.project, user)
+            g.statsUpdater.newCommit(new, repo.app_config.project, user)
 
     log.info('Refresh complete for %s', repo.full_fs_path)
     g.post_event(

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/dd02cc27/ForgeTracker/forgetracker/model/ticket.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
index b97fa44..7d0b7a2 100644
--- a/ForgeTracker/forgetracker/model/ticket.py
+++ b/ForgeTracker/forgetracker/model/ticket.py
@@ -442,8 +442,7 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
                 ('Status', old.status, self.status) ]
             if old.status != self.status and self.status in c.app.globals.set_of_closed_status_names:
                 h.log_action(log, 'closed').info('')
-                for l in g.statslisteners:
-                    l.ticketEvent("closed", self, self.project, self.assigned_to)
+                g.statsUpdater.ticketEvent("closed", self, self.project, self.assigned_to)
             for key in self.custom_fields:
                 fields.append((key, old.custom_fields.get(key, ''), self.custom_fields[key]))
             for title, o, n in fields:
@@ -456,9 +455,9 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
                 changes.append('Owner updated: %r => %r' % (
                         o and o.username, n and n.username))
                 self.subscribe(user=n)
-                for l in g.statslisteners :
-                    l.ticketEvent("assigned", self, self.project, n)
-                    if o: l.ticketEvent("revoked", self, self.project, o)
+                g.statsUpdater.ticketEvent("assigned", self, self.project, n)
+                if o: 
+                    g.statsUpdater.ticketEvent("revoked", self, self.project, o)
             if old.description != self.description:
                 changes.append('Description updated:')
                 changes.append('\n'.join(
@@ -472,8 +471,7 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
             self.subscribe()
             if self.assigned_to_id:
                 user = User.query.get(_id=self.assigned_to_id)
-                for l in g.statslisteners :
-                    l.ticketEvent("assigned", self, self.project, user)
+                g.statsUpdater.ticketEvent("assigned", self, self.project, user)
                 self.subscribe(user=user)
             description = ''
             subject = self.email_subject


[15/42] git commit: [5453] Removed .svn

Posted by br...@apache.org.
[5453] Removed .svn


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/a972fcbd
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/a972fcbd
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/a972fcbd

Branch: refs/heads/db/5453
Commit: a972fcbd4e5c6a9139c0561631eb048529211474
Parents: 60b11fe
Author: Stefano Invernizzi <st...@apache.org>
Authored: Fri Jan 18 18:20:56 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:31 2013 +0000

----------------------------------------------------------------------
 .../forgeuserstats/model/.svn/all-wcprops          |   17 -
 ForgeUserStats/forgeuserstats/model/.svn/entries   |   96 ---
 .../model/.svn/text-base/stats.py.svn-base         |  534 ---------------
 .../forgeuserstats/templates/.svn/all-wcprops      |   29 -
 .../forgeuserstats/templates/.svn/entries          |  164 -----
 .../.svn/text-base/artifacts.html.svn-base         |   48 --
 .../templates/.svn/text-base/commits.html.svn-base |   37 -
 .../templates/.svn/text-base/index.html.svn-base   |  341 ---------
 .../templates/.svn/text-base/tickets.html.svn-base |   47 --
 9 files changed, 0 insertions(+), 1313 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a972fcbd/ForgeUserStats/forgeuserstats/model/.svn/all-wcprops
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/.svn/all-wcprops b/ForgeUserStats/forgeuserstats/model/.svn/all-wcprops
deleted file mode 100644
index a5d5661..0000000
--- a/ForgeUserStats/forgeuserstats/model/.svn/all-wcprops
+++ /dev/null
@@ -1,17 +0,0 @@
-K 25
-svn:wc:ra_dav:version-url
-V 58
-/svn/allura/!svn/ver/3/ForgeUserStats/forgeuserstats/model
-END
-stats.py
-K 25
-svn:wc:ra_dav:version-url
-V 67
-/svn/allura/!svn/ver/3/ForgeUserStats/forgeuserstats/model/stats.py
-END
-__init__.py
-K 25
-svn:wc:ra_dav:version-url
-V 70
-/svn/allura/!svn/ver/1/ForgeUserStats/forgeuserstats/model/__init__.py
-END

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a972fcbd/ForgeUserStats/forgeuserstats/model/.svn/entries
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/.svn/entries b/ForgeUserStats/forgeuserstats/model/.svn/entries
deleted file mode 100644
index c26dfd9..0000000
--- a/ForgeUserStats/forgeuserstats/model/.svn/entries
+++ /dev/null
@@ -1,96 +0,0 @@
-10
-
-dir
-4
-https://xp-dev.com/svn/allura/ForgeUserStats/forgeuserstats/model
-https://xp-dev.com/svn/allura
-
-
-
-2012-10-19T08:28:36.749162Z
-3
-stefanoinvernizzi
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-46ed536d-f66c-413e-a53e-834384f708db
-
-stats.py
-file
-
-
-
-
-2012-11-05T14:43:25.729756Z
-21591047edf4fabfb1b70150af5bd0c2
-2012-10-19T08:28:36.749162Z
-3
-stefanoinvernizzi
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-23647
-
-__init__.py
-file
-
-
-
-
-2012-11-05T14:43:25.729756Z
-d41d8cd98f00b204e9800998ecf8427e
-2012-10-17T19:55:53.450112Z
-1
-stefanoinvernizzi
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-0
-

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a972fcbd/ForgeUserStats/forgeuserstats/model/.svn/text-base/__init__.py.svn-base
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/.svn/text-base/__init__.py.svn-base b/ForgeUserStats/forgeuserstats/model/.svn/text-base/__init__.py.svn-base
deleted file mode 100644
index e69de29..0000000

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a972fcbd/ForgeUserStats/forgeuserstats/model/.svn/text-base/stats.py.svn-base
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/.svn/text-base/stats.py.svn-base b/ForgeUserStats/forgeuserstats/model/.svn/text-base/stats.py.svn-base
deleted file mode 100644
index f434e4e..0000000
--- a/ForgeUserStats/forgeuserstats/model/.svn/text-base/stats.py.svn-base
+++ /dev/null
@@ -1,534 +0,0 @@
-import pymongo
-from pylons import c, g, request
-
-import bson
-from ming import schema as S
-from ming import Field, Index, collection
-from ming.orm import session, state, Mapper
-from ming.orm import FieldProperty, RelationProperty, ForeignIdProperty
-from ming.orm.declarative import MappedClass
-from datetime import datetime, timedelta
-import difflib
-
-from allura.model.session import main_orm_session, main_doc_session
-from allura.model.session import project_orm_session
-from allura.model import User
-import allura.model as M
-from allura.lib import helpers as h
-
-class UserStats(MappedClass):
-    SALT_LEN=8
-    class __mongometa__:
-        name='userstats'
-        session = main_orm_session
-        unique_indexes = [ 'userid' ]
-
-    _id=FieldProperty(S.ObjectId)
-    userid = ForeignIdProperty('User')
-
-    registration_date = FieldProperty(datetime)
-    tot_logins_count = FieldProperty(int, if_missing = 0)
-    last_login = FieldProperty(datetime)
-    general = FieldProperty([dict(category = S.ObjectId,
-                                  messages = [dict(messagetype = str,
-                                                   created = int,
-                                                   modified = int)],
-                                  tickets = dict(solved = int,
-                                                 assigned = int,
-                                                 revoked = int,
-                                                 totsolvingtime = int),
-                                  commits = [dict(lines = int,
-                                                  number = int,
-                                                  language = S.ObjectId)])])
-
-    lastmonth= FieldProperty(dict(logins=[datetime],
-                                  messages=[dict(datetime=datetime,
-                                                 created=bool,
-                                                 categories=[S.ObjectId],
-                                                 messagetype=str)],
-                                  assignedtickets=[dict(datetime=datetime,
-                                                        categories=[S.ObjectId])],
-                                  revokedtickets=[dict(datetime=datetime,
-                                                       categories=[S.ObjectId])],
-                                  solvedtickets=[dict(datetime=datetime,
-                                                      categories=[S.ObjectId],
-                                                      solvingtime=int)],
-                                  commits=[dict(datetime=datetime,
-                                                categories=[S.ObjectId],
-                                                programming_languages=[S.ObjectId],
-                                                lines=int)]))
-    reluser = RelationProperty('User')
-
-
-    def codeRanking(self) :
-        def _getCodeContribution(stats) :
-            for val in stats['general'] :
-                if val['category'] is None :
-                    for commits in val['commits'] :
-                         if commits['language'] is None : 
-                             return (commits.lines, commits.number)
-            return (0,0) 
-
-        lst = list(self.query.find())
-        totn = len(lst)
-        codcontr = _getCodeContribution(self)
-        upper = len([x for x in lst if _getCodeContribution(x) > codcontr])
-        percentage = upper * 100.0 / totn
-        if percentage < 1 / 6.0 : return 5
-        if percentage < 2 / 6.0 : return 4
-        if percentage < 3 / 6.0 : return 3
-        if percentage < 4 / 6.0 : return 2
-        if percentage < 5 / 6.0 : return 1
-        return 0
-
-    def discussionRanking(self) :
-        def _getDiscussionContribution(stats) :
-            for val in stats['general'] :
-                if val['category'] is None :
-                    for artifact in val['messages'] :
-                         if artifact['messagetype'] is None : 
-                             return artifact.created + artifact.modified
-            return 0
-
-        lst = list(self.query.find())
-        totn = len(lst)
-        disccontr = _getDiscussionContribution(self)
-        upper = len([x for x in lst if _getDiscussionContribution(x) > disccontr])
-        percentage = upper * 100.0 / totn
-        if percentage < 1 / 6.0 : return 5
-        if percentage < 2 / 6.0 : return 4
-        if percentage < 3 / 6.0 : return 3
-        if percentage < 4 / 6.0 : return 2
-        if percentage < 5 / 6.0 : return 1
-        return 0
-
-    def ticketsRanking(self) :
-
-        def _getTicketsPercentage(stats) :
-            for val in stats['general'] :
-                if val['category'] is None :
-                    if val['tickets']['assigned'] == 0 : percentage = 0
-                    else :
-                        percentage = val['tickets']['solved'] \
-                                    / val['tickets']['assigned']
-            return 0
-
-        percentage = _getTicketsPercentage(self)
-        if percentage > 1 / 6.0 : return 5
-        if percentage > 2 / 6.0 : return 4
-        if percentage > 3 / 6.0 : return 3
-        if percentage > 4 / 6.0 : return 2
-        if percentage > 5 / 6.0 : return 1
-        return 0
-
-    def getCommits(self, category = None) :
-        i = getElementIndex(self.general, category = category)
-        if i is None : return {'number' : 0, 'lines': 0}
-        cat = self.general[i]
-        j = getElementIndex(cat.commits, language = None)
-        if j is None : return {'number' : 0, 'lines': 0}
-        return {'number': cat.commits[j]['number'], 
-                'lines' : cat.commits[j]['lines']}
-
-    def getArtifacts(self, category = None, art_type = None) :
-        i = getElementIndex(self.general, category = category)
-        if i is None : return {'created' : 0, 'modified': 0}
-        cat = self.general[i]
-        j = getElementIndex(cat.messages, art_type = art_type)
-        if j is None : return {'created' : 0, 'modified': 0}
-        return {'created'  : cat[j].created, 
-                'modified' : cat[j].modified}
-
-    def getTickets(self, category = None) :
-        i = getElementIndex(self.general, category = category)
-        if i is None : return {'assigned'           : 0,
-                               'solved'             : 0,
-                               'revoked'            : 0,
-                               'averagesolvingtime' : None}
-        if self.general[i].tickets.solved > 0 :
-           tot = self.general[i].tickets.totsolvingtime 
-           number = self.general[i].tickets.solved
-           average = tot / number
-
-        else : average = None
-        return {'assigned'           : self.general[i].tickets.assigned,
-                'solved'             : self.general[i].tickets.solved,
-                'revoked'            : self.general[i].tickets.revoked,
-                'averagesolvingtime' : _convertTimeDiff(average)}
-
-    def getCommitsByCategory(self) :
-        by_cat = {}
-        for entry in self.general :
-            cat = entry.category
-            i = getElementIndex(entry.commits, language = None)
-            if i is None : n, lines = 0, 0
-            else : n, lines = entry.commits[i].number, entry.commits[i].lines
-            if cat != None : cat = M.TroveCategory.query.get(_id = cat)
-            by_cat[cat] = {'number' : n, 'lines' : lines}
-        return by_cat
-
-    def getCommitsByLanguage(self) :
-        langlist = []
-        by_lang = {}
-        i = getElementIndex(self.general, category=None)
-        if i is None : return {'number' : 0, 'lines' : 0}
-        return dict([(el.language, {'lines' : el.lines, 'number':el.number})
-                     for el in self.general[i].commits])
-
-    def getArtifactsByCategory(self, detailed=False) :
-        by_cat = {}
-        for entry in self.general :
-            cat = entry.category
-            if cat != None : cat = M.TroveCategory.query.get(_id = cat)
-            if detailed : 
-                by_cat[cat] = entry.messages
-            else : 
-                i = getElementIndex(entry.messages, messagetype=None)
-                if i is not None : by_cat[cat] = entry.messages[i]
-                else : by_cat[cat] = {'created' : 0, 'modified' : 0}
-        return by_cat
-
-    def getArtifactsByType(self, category=None) :
-        i = getElementIndex(self.general, category = category)
-        if i is None : return {}
-        entry = self.general[i].messages
-        by_type = dict([(el.messagetype, {'created' : el.created, 
-                                          'modified': el.modified})
-                         for el in entry])
-        return by_type
-
-    def getTicketsByCategory(self) :
-        by_cat = {}
-        for entry in self.general :
-            cat = entry.category
-            if cat != None : cat = M.TroveCategory.query.get(_id = cat)
-            a, s = entry.tickets.assigned, entry.tickets.solved
-            r, time = entry.tickets.solved, entry.tickets.totsolvingtime
-            if s : average = time / s
-            else : average = None
-            by_cat[cat] = {'assigned'           : a,
-                           'solved'             : s,
-                           'revoked'            : r, 
-                           'averagesolvingtime' : _convertTimeDiff(average)}
-        return by_cat
-
-    def getLastMonthCommits(self, category = None) :
-        self.checkOldArtifacts() 
-        lineslist = [el.lines for el in self.lastmonth.commits
-                     if category in el.categories + [None]]
-        return {'number': len(lineslist), 'lines':sum(lineslist)}
-
-    def getLastMonthCommitsByCategory(self) :
-        self.checkOldArtifacts() 
-        seen = set()
-        catlist=[el.category for el in self.general
-                 if el.category not in seen and not seen.add(el.category)]
-
-        by_cat = {}
-        for cat in catlist :
-            lineslist = [el.lines for el in self.lastmonth.commits
-                         if cat in el.categories + [None]]
-            n = len(lineslist)
-            lines = sum(lineslist)
-            if cat != None : cat = M.TroveCategory.query.get(_id = cat)
-            by_cat[cat] = {'number' : n, 'lines' : lines}
-        return by_cat
-
-    def getLastMonthCommitsByLanguage(self) :
-        self.checkOldArtifacts() 
-        seen = set()
-        langlist=[el.language for el in self.general
-                  if el.language not in seen and not seen.add(el.language)]
-
-        by_lang = {}
-        for lang in langlist :
-            lineslist = [el.lines for el in self.lastmonth.commits
-                         if lang in el.programming_languages + [None]]
-            n = len(lineslist)
-            lines = sum(lineslist)
-            if lang != None : lang = M.TroveCategory.query.get(_id = lang)
-            by_lang[lang] = {'number' : n, 'lines' : lines}
-        return by_lang
-
-    def getLastMonthArtifacts(self, category = None) :
-        self.checkOldArtifacts() 
-        cre, mod = reduce(addtuple, [(int(el.created),1-int(el.created))
-                                     for el in self.lastmonth.messages
-                                     if category is None or 
-                                        category in el.categories], (0,0))
-        return {'created': cre, 'modified' : mod}
-
-    def getLastMonthArtifactsByType(self, category = None) :
-        self.checkOldArtifacts() 
-        seen = set()
-        types=[el.messagetype for el in self.lastmonth.messages
-               if el.messagetype not in seen and not seen.add(el.messagetype)]
-
-        by_type = {}
-        for t in types :
-            cre, mod = reduce(addtuple, 
-                              [(int(el.created),1-int(el.created))
-                               for el in self.lastmonth.messages
-                               if el.messagetype == t and
-                                  category in [None]+el.categories],
-                              (0,0))
-            by_type[t] = {'created': cre, 'modified' : mod}
-        return by_type
-
-    def getLastMonthArtifactsByCategory(self) :
-        self.checkOldArtifacts() 
-        seen = set()
-        catlist=[el.category for el in self.general
-                 if el.category not in seen and not seen.add(el.category)]
-
-        by_cat = {}
-        for cat in catlist :
-            cre, mod = reduce(addtuple, [(int(el.created),1-int(el.created))
-                                         for el in self.lastmonth.messages 
-                                         if cat in el.categories + [None]], (0,0))
-            if cat != None : cat = M.TroveCategory.query.get(_id = cat)
-            by_cat[cat] = {'created' : cre, 'modified' : mod}
-        return by_cat
-
-    def getLastMonthTickets(self, category = None) :
-        self.checkOldArtifacts()
-        a = len([el for el in self.lastmonth.assignedtickets
-                 if category in el.categories + [None]])
-        r = len([el for el in self.lastmonth.revokedtickets
-                 if category in el.categories + [None]])
-        s, time = reduce(addtuple, 
-                         [(1, el.solvingtime)
-                          for el in self.lastmonth.solvedtickets
-                          if category in el.categories + [None]],
-                         (0,0))
-        if category!=None : category = M.TroveCategory.query.get(_id=category)
-        if s > 0 : time = time / s
-        else : time = None
-        return {'assigned'           : a,
-                'revoked'            : r,
-                'solved'             : s, 
-                'averagesolvingtime' : _convertTimeDiff(time)}
-        
-    def getLastMonthTicketsByCategory(self) :
-        self.checkOldArtifacts()
-        seen = set()
-        catlist=[el.category for el in self.general
-                 if el.category not in seen and not seen.add(el.category)]
-        by_cat = {}
-        for cat in catlist :
-            a = len([el for el in self.lastmonth.assignedtickets
-                     if cat in el.categories + [None]])
-            r = len([el for el in self.lastmonth.revokedtickets
-                     if cat in el.categories + [None]])
-            s, time = reduce(addtuple, [(1, el.solvingtime)
-                                        for el in self.lastmonth.solvedtickets
-                                        if cat in el.categories + [None]],(0,0))
-            if cat != None : cat = M.TroveCategory.query.get(_id = cat)
-            if s > 0 : time = time / s
-            else : time = None
-            by_cat[cat] = {'assigned'           : a,
-                           'revoked'            : r,
-                           'solved'             : s, 
-                           'averagesolvingtime' : _convertTimeDiff(time)}
-        return by_cat
-        
-    def getLastMonthLogins(self) :
-        self.checkOldArtifacts()
-        return len(self.lastmonth.logins)
-
-    def checkOldArtifacts(self) :
-        now = datetime.now()
-        for m in self.lastmonth.messages :
-            if now - m.datetime > timedelta(30) :
-               self.lastmonth.messages.remove(m)
-        for t in self.lastmonth.assignedtickets :
-            if now - t.datetime > timedelta(30) :
-               self.lastmonth.assignedtickets.remove(t)
-        for t in self.lastmonth.revokedtickets :
-            if now - t.datetime > timedelta(30) :
-               self.lastmonth.revokedtickets.remove(t)
-        for t in self.lastmonth.solvedtickets :
-            if now - t.datetime > timedelta(30) :
-               self.lastmonth.solvedtickets.remove(t)
-
-    def addNewArtifact(self, art_type, art_datetime, project) :
-        self._updateArtifactsStats(art_type, art_datetime, project, "created")
-
-    def addModifiedArtifact(self, art_type, art_datetime, project) :
-        self._updateArtifactsStats(art_type, art_datetime, project, "modified")
-
-    def addAssignedTicket(self, ticket, project) :
-        topics = [t for t in project.trove_topic if t]
-        self._updateTicketsStats(topics, 'assigned')
-        self.lastmonth.assignedtickets.append({'datetime'   : ticket.mod_date,
-                                               'categories' : topics})
-
-    def addRevokedTicket(self, ticket, project) :
-        topics = [t for t in project.trove_topic if t]
-        self._updateTicketsStats(topics, 'revoked')
-        self.lastmonth.revokedtickets.append({'datetime'   : ticket.mod_date,
-                                              'categories' : topics})
-        self.checkOldArtifacts()
-
-    def addClosedTicket(self, ticket, project) :
-        topics = [t for t in project.trove_topic if t]
-        s_time=int((datetime.utcnow()-ticket.created_date).total_seconds())
-        self._updateTicketsStats(topics, 'solved', s_time = s_time)
-        self.lastmonth.solvedtickets.append({'datetime'   : ticket.mod_date,
-                                             'categories' : topics,
-                                             'solvingtime': s_time})
-        self.checkOldArtifacts()
-
-    def addCommit(self, newcommit, project) :
-        def _addCommitData(stats, topics, languages, newblob, oldblob = None) :
-            if oldblob : listold = list(oldblob)
-            else : listold = []
-            listnew = list(newblob)
-
-            if oldblob is None : lines = len(listnew)
-            elif newblob.has_html_view :
-                diff = difflib.unified_diff(listold, listnew,
-                         ('old' + oldblob.path()).encode('utf-8'),
-                         ('new' + newblob.path()).encode('utf-8'))
-                lines = len([l for l in diff if len(l) > 0 and l[0] == '+']) - 1
-            else : lines = 0
-            
-            lt = topics + [None]
-            ll = languages + [None]
-            for t in lt :
-                i = getElementIndex(stats.general, category=t) 
-                if i is None :
-                    newstats = {'category' : t,
-                                'commits'  : [],
-                                'tickets'  : {'assigned'       : 0,
-                                              'solved'         : 0,
-                                              'revoked'        : 0,
-                                              'totsolvingtime' : 0},
-                                'messages' : []}   
-                    stats.general.append(newstats)
-                    i = getElementIndex(stats.general, category=t)
-                for lang in ll :
-                    j = getElementIndex(stats.general[i]['commits'], 
-                                        language=lang)
-                    if j is None :
-                        stats.general[i]['commits'].append({'language': lang,
-                                                            'lines'   : lines,
-                                                            'number'  : 1})
-                    else :
-                        stats.general[i]['commits'][j].lines += lines
-                        stats.general[i]['commits'][j].number += 1
-            return lines
-
-        topics = [t for t in project.trove_topic if t]
-        languages = [l for l in project.trove_language if l]
-        now = datetime.utcnow()
-
-        d = newcommit.diffs
-        if len(newcommit.parent_ids) > 0 :
-            oldcommit = newcommit.repo.commit(newcommit.parent_ids[0])
-
-        totlines = 0
-        for changed in d.changed :
-            newblob = newcommit.tree.get_blob_by_path(changed)
-            oldblob = oldcommit.tree.get_blob_by_path(changed)
-            totlines+=_addCommitData(self, topics, languages, newblob, oldblob)
-
-        for copied in d.copied :
-            newblob = newcommit.tree.get_blob_by_path(copied['new'])
-            oldblob = oldcommit.tree.get_blob_by_path(copied['old'])
-            totlines+=_addCommitData(self, topics, languages, newblob, oldblob)
-
-        for added in d.added :
-            newblob = newcommit.tree.get_blob_by_path(added)
-            totlines+=_addCommitData(self, topics, languages, newblob)
-
-        self.lastmonth.commits.append({'datetime' : now,
-                                       'categories' : topics,
-                                       'programming_languages' : languages,
-                                       'lines' : totlines})
-        self.checkOldArtifacts()
-
-    def addLogin(self) :
-        now = datetime.utcnow()
-        self.last_login = now
-        self.tot_logins_count += 1
-        self.lastmonth.logins.append(now)
-        self.checkOldArtifacts()
-        
-    def _updateArtifactsStats(self, art_type, art_datetime, project, action) :
-        if action not in ['created', 'modified'] : return
-        topics = [t for t in project.trove_topic if t]
-        lt = [None] + topics
-        for mtype in [None, art_type] :
-            for t in lt :
-                i = getElementIndex(self.general, category = t)
-                if i is None :
-                    msg = {'category' : t,
-                           'commits'  : [],
-                           'tickets'  : {'solved'         : 0,
-                                         'assigned'       : 0,
-                                         'revoked'        : 0,
-                                         'totsolvingtime' : 0},
-                           'messages' : []}
-                    self.general.append(msg)
-                    i = getElementIndex(self.general, category = t)
-                j = getElementIndex(self.general[i]['messages'], messagetype = mtype)
-                if j is None : 
-                    entry = {'messagetype' : mtype,
-                             'created'     : 0,
-                             'modified'    : 0}
-                    entry[action] += 1
-                    self.general[i]['messages'].append(entry)
-                else : self.general[i]['messages'][j][action] += 1
-
-        self.lastmonth.messages.append({'datetime'   : art_datetime,
-                                        'created'    : action == 'created',
-                                        'categories' : topics,
-                                        'messagetype': art_type})
-        self.checkOldArtifacts() 
-
-    def _updateTicketsStats(self, topics, action, s_time = None) :
-        if action not in ['solved', 'assigned', 'revoked'] : return
-        lt = topics + [None]
-        for t in lt :
-            i = getElementIndex(self.general, category = t)
-            if i is None :
-                stats = {'category' : t,
-                         'commits'  : [],
-                         'tickets'  : {'solved'         : 0,
-                                       'assigned'       : 0,
-                                       'revoked'        : 0,
-                                       'totsolvingtime' : 0},
-                         'messages' : [] }
-                self.general.append(stats)
-                i = getElementIndex(self.general, category = t)
-            self.general[i]['tickets'][action] += 1 
-            if action == 'solved' : 
-                self.general[i]['tickets']['totsolvingtime']+=s_time
-
-def getElementIndex(el_list, **kw) :
-    for i in range(len(el_list)) :
-        for k in kw : 
-            if el_list[i].get(k) != kw[k] : break
-        else : return i
-    return None
-
-def addtuple(l1, l2) :
-    a, b = l1
-    x, y = l2
-    return (a+x, b+y)
-
-def _convertTimeDiff(int_seconds) :
-    if int_seconds is None : return None
-    diff = timedelta(seconds = int_seconds)
-    days, seconds = diff.days, diff.seconds
-    hours = seconds / 3600
-    seconds = seconds % 3600
-    minutes = seconds / 60
-    seconds = seconds % 60
-    return {'days'    : days, 
-            'hours'   : hours, 
-            'minutes' : minutes,
-            'seconds' : seconds}
-
-Mapper.compile_all()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a972fcbd/ForgeUserStats/forgeuserstats/templates/.svn/all-wcprops
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/.svn/all-wcprops b/ForgeUserStats/forgeuserstats/templates/.svn/all-wcprops
deleted file mode 100644
index efae2aa..0000000
--- a/ForgeUserStats/forgeuserstats/templates/.svn/all-wcprops
+++ /dev/null
@@ -1,29 +0,0 @@
-K 25
-svn:wc:ra_dav:version-url
-V 62
-/svn/allura/!svn/ver/1/ForgeUserStats/forgeuserstats/templates
-END
-commits.html
-K 25
-svn:wc:ra_dav:version-url
-V 75
-/svn/allura/!svn/ver/1/ForgeUserStats/forgeuserstats/templates/commits.html
-END
-artifacts.html
-K 25
-svn:wc:ra_dav:version-url
-V 77
-/svn/allura/!svn/ver/1/ForgeUserStats/forgeuserstats/templates/artifacts.html
-END
-tickets.html
-K 25
-svn:wc:ra_dav:version-url
-V 75
-/svn/allura/!svn/ver/1/ForgeUserStats/forgeuserstats/templates/tickets.html
-END
-index.html
-K 25
-svn:wc:ra_dav:version-url
-V 73
-/svn/allura/!svn/ver/1/ForgeUserStats/forgeuserstats/templates/index.html
-END

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a972fcbd/ForgeUserStats/forgeuserstats/templates/.svn/entries
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/.svn/entries b/ForgeUserStats/forgeuserstats/templates/.svn/entries
deleted file mode 100644
index ef7dfdb..0000000
--- a/ForgeUserStats/forgeuserstats/templates/.svn/entries
+++ /dev/null
@@ -1,164 +0,0 @@
-10
-
-dir
-4
-https://xp-dev.com/svn/allura/ForgeUserStats/forgeuserstats/templates
-https://xp-dev.com/svn/allura
-
-
-
-2012-10-17T19:55:53.450112Z
-1
-stefanoinvernizzi
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-46ed536d-f66c-413e-a53e-834384f708db
-
-tickets.html
-file
-
-
-
-
-2012-11-05T14:43:25.725756Z
-4bac229c573965dbfd312e65cc7313a2
-2012-10-17T19:55:53.450112Z
-1
-stefanoinvernizzi
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1361
-
-index.html
-file
-
-
-
-
-2012-11-05T14:43:25.725756Z
-036136344f0b3099f212c6c749431996
-2012-10-17T19:55:53.450112Z
-1
-stefanoinvernizzi
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-11126
-
-commits.html
-file
-
-
-
-
-2012-11-05T14:43:25.725756Z
-cbfcdaeb670c8896e31071077c51eb23
-2012-10-17T19:55:53.450112Z
-1
-stefanoinvernizzi
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-955
-
-artifacts.html
-file
-
-
-
-
-2012-11-05T14:43:25.725756Z
-bb6c7ceabf56de25d177ee5cd52451ab
-2012-10-17T19:55:53.450112Z
-1
-stefanoinvernizzi
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1386
-

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a972fcbd/ForgeUserStats/forgeuserstats/templates/.svn/text-base/artifacts.html.svn-base
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/.svn/text-base/artifacts.html.svn-base b/ForgeUserStats/forgeuserstats/templates/.svn/text-base/artifacts.html.svn-base
deleted file mode 100644
index 0b3cfb8..0000000
--- a/ForgeUserStats/forgeuserstats/templates/.svn/text-base/artifacts.html.svn-base
+++ /dev/null
@@ -1,48 +0,0 @@
-{% set hide_left_bar = True %}
-{% extends g.theme.master %}
-
-{% block title %}User stats{% endblock %}
-
-{% block header %}
-    Statistics about {{user.display_name}}'s contribution – Artifacts
-{% endblock %}
-
-{% block content %}
-
-  {% if user %}
-
-    {% if data %}
-      <h2>Statistics by category</h2>
-      <table>
-        <thead>
-          <tr>
-            <th>Category</th>
-            <th>Created artifacts</th>
-            <th>Modified artifacts</th>
-          </tr>
-        </thead>
-        <tbody> 
-          {% for cat, row in data.items() %}
-            <tr>
-              <td>{% if cat %}{{cat.fullname}}{% else %}All categories{% endif %}</td>
-              <td>
-                {% for details in row %}
-                  {% if details.messagetype %} {{details.messagetype}}:
-                  {% else %}Total:{% endif %} {{details.created}}<br/>
-                {% endfor %}
-              </td>
-              <td>
-                {% for details in row %}
-                  {% if details.messagetype %} {{details.messagetype}}:
-                  {% else %}Total:{% endif %} {{details.modified}}<br/>
-                {% endfor %}
-              </td>
-            </tr>
-          {% endfor %}
-        </tbody>
-      </table>
-    {% endif %}
-    <div class="grid-20"><a href="/userstats/{{user.username}}">Go back to general statistics</a></div>
-  {% endif %}
-
-{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a972fcbd/ForgeUserStats/forgeuserstats/templates/.svn/text-base/commits.html.svn-base
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/.svn/text-base/commits.html.svn-base b/ForgeUserStats/forgeuserstats/templates/.svn/text-base/commits.html.svn-base
deleted file mode 100644
index c574c9f..0000000
--- a/ForgeUserStats/forgeuserstats/templates/.svn/text-base/commits.html.svn-base
+++ /dev/null
@@ -1,37 +0,0 @@
-{% set hide_left_bar = True %}
-{% extends g.theme.master %}
-
-{% block title %}User stats{% endblock %}
-
-{% block header %}
-    Statistics about {{user.display_name}}'s contribution – Code contribution
-{% endblock %}
-
-{% block content %}
-
-  {% if user %}
-
-    {% if data %}
-      <h2>Statistics by category</h2>
-      <table>
-        <thead>
-          <tr>
-            <th>Category</th>
-            <th>Number of commits</th>
-            <th>Lines of code</th>
-          </tr>
-        </thead>
-        <tbody> 
-          {% for cat, el in data.items() %}
-            <tr>
-              <td>{% if cat %}{{cat.fullname}}{% else %}All categories{% endif %}</td>
-              <td>{{el.number}}</td>
-              <td>{{el.lines}}</td>
-            {% endfor %}
-          </tr>
-        </tbody>
-      </table>
-    {% endif %}
-    <div class="grid-20"><a href="/userstats/{{user.username}}">Go back to general statistics</a></div>
-  {% endif %}
-{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a972fcbd/ForgeUserStats/forgeuserstats/templates/.svn/text-base/index.html.svn-base
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/.svn/text-base/index.html.svn-base b/ForgeUserStats/forgeuserstats/templates/.svn/text-base/index.html.svn-base
deleted file mode 100644
index b53e596..0000000
--- a/ForgeUserStats/forgeuserstats/templates/.svn/text-base/index.html.svn-base
+++ /dev/null
@@ -1,341 +0,0 @@
-{% set hide_left_bar = True %}
-{% extends g.theme.master %}
-
-{% block title %}User stats{% endblock %}
-
-{% block header %}
-    Statistics about {{user.display_name}}'s contribution
-    {% if category %}
-        in projects of category {{category.fullname}}
-    {% endif %}
-{% endblock %}
-
-{% block content %}
-  {% if user %}
-
-    <h2>General statistics</h2>
-    <table>
-      <thead>
-        <tr>
-          <th>Parameter</th>
-          <th>Value</th>
-        </tr>
-      </thead>
-      <tbody> 
-        <tr>
-          <td>Registration date</td>
-          <td>
-            {{registration_date.strftime("%d %b %Y, %H:%M:%S (UTC)")}}, 
-            {{days}} day{% if days != 1 %}s{% endif %} ago</td>
-        </tr>
-        {% if last_login %}
-          <tr>
-            <td>Last login</td>
-            <td>
-              {{last_login.strftime("%d %b %Y, %H:%M:%S (UTC)")}},
-              {{last_login_days}} day{% if last_login_days != 1 %}s{% endif %} ago</td>
-            </td>
-          </tr>
-        {% endif %}
-      </tbody>
-    </table>
-
-    <h2>Contribution statistics</h2>
-
-    <table>
-      <thead>
-        <tr>
-          <th>Parameter</th>
-          <th>Total value</th>
-          <th>Average per-month value</th>
-          <th>Last 30 days</th>
-          {% if days >= 30 %}
-            <th>Trend</th>
-          {% endif %}
-        </tr>
-      </thead>
-      <tbody>
-        {% if not category %}
-          <tr>
-            <td>Logins</td>
-            <td>{{totlogins}}</td>
-            <td>{{permonthlogins}}</td>
-            <td>{{lastmonth_logins}}</td>
-            {% if days >= 30 %}
-              <td>
-                {% if lastmonth_logins > permonthlogins %}
-                  Up
-                {% elif lastmonth_logins == permonthlogins %}
-                  =
-                {% else %}
-                  Down
-                {%endif%}
-              </td>
-            {% endif %}
-          </tr>
-        {% endif %}
-        <tr>
-          <td><a href="/userstats/{{user.username}}/metric/commits/">Commits number</a></td>
-          <td>{{totcommits.number}}</td>
-          <td>{{permonthcommits.number}}</td>
-          <td>{{lastmonthcommits.number}}</td>
-          {% if days >= 30 %}
-            <td>
-              {% if permonthcommits.number > permonthcommits.number %}
-                Up
-              {% elif permonthcommits.number == permonthcommits.number %}
-                =
-              {% else %}
-                Down
-              {%endif%}
-            </td>
-          {% endif %}
-        </tr>
-        <tr>
-          <td><a href="/userstats/{{user.username}}/metric/commits/">Added/modified LOCs</a></td>
-          <td>{{totcommits.lines}}</td>
-          <td>{{permonthcommits.lines}}</td>
-          <td>{{lastmonthcommits.lines}}</td>
-          {% if days >= 30 %}
-            <td>
-              {% if permonthcommits.lines > permonthcommits.lines %}
-                Up
-              {% elif permonthcommits.lines == permonthcommits.lines %}
-                =
-              {% else %}
-                Down
-              {%endif%}
-            </td>
-          {% endif %}
-        </tr>
-        <tr>
-          <td><a href="/userstats/{{user.username}}/metric/artifacts/">Total number of created artifacts</a></td>
-          <td>{{totartifacts.created}}</td>
-          <td>{{permonthartifacts.created}}</td>
-          <td>{{lastmonthartifacts.created}}</td>
-          {% if days >= 30 %}
-            <td>
-              {% if lastmonthartifacts.created > permonthartifacts.created %}
-                Up
-              {% elif lastmonthartifacts.created == permonthartifacts.created %}
-                =
-              {% else %}
-                Down
-              {%endif%}
-            </td>
-          {% endif %}
-        </tr>
-        <tr>
-          <td><a href="/userstats/{{user.username}}/metric/commits/">Total number of edited artifacts</a></td>
-          <td>{{totartifacts.modified}}</td>
-          <td>{{permonthartifacts.modified}}</td>
-          <td>{{lastmonthartifacts.modified}}</td>
-          {% if days >= 30 %}
-            <td>
-              {% if lastmonthartifacts.modified > permonthartifacts.modified %}
-                Up
-              {% elif lastmonthartifacts.modified == permonthartifacts.modified %}
-                =
-              {% else %}
-                Down
-              {%endif%}
-            </td>
-          {% endif %}
-        </tr>
-        
-        {% for key, value in artifacts_by_type.items() %}
-          <tr>
-            <td><a href="/userstats/{{user.username}}/metric/artifacts/">Created {{key}} artifacts</a></td>
-            <td>{{value.created}}</td>
-            <td>{{value.pmcreated}}</td>
-            <td>
-              {% if lastmonth_artifacts_by_type.get(key) %}
-                 {{lastmonth_artifacts_by_type[key].created}}
-              {% else %}
-                 0
-              {% endif %}
-            </td>
-            {% if days >= 30 %}
-              <td>
-                {% if lastmonth_artifacts_by_type.get(key) %}
-                  {% if lastmonth_artifacts_by_type[key].created > value.pmcreated %}
-                    Up
-                  {% elif lastmonth_artifacts_by_type[key].created == value.pmcreated %}
-                    =
-                  {% else %}
-                    Down
-                  {%endif%}
-                {%else%} Down {%endif%}
-              </td>
-            {% endif %}
-          </tr>
-          <tr>
-            <td><a href="/userstats/{{user.username}}/metric/artifacts/">Edited {{key}} artifacts</a></td>
-            <td>{{value.modified}}</td>
-            <td>{{value.pmmodified}}</td>
-            <td>
-              {% if lastmonth_artifacts_by_type.get(key) %}
-                 {{lastmonth_artifacts_by_type[key].modified}}
-              {% else %}
-                 0
-              {% endif %}
-            </td>
-            {% if days >= 30 %}
-              <td>
-                {% if lastmonth_artifacts_by_type.get(key) %}
-                  {% if lastmonth_artifacts_by_type[key].modified > value.pmmodified %}
-                    Up
-                  {% elif lastmonth_artifacts_by_type[key].modified == value.pmmodified %}
-                    =
-                  {% else %}
-                    Down
-                  {%endif%}
-                {%else%} Down {%endif%}
-              </td>
-            {% endif %}
-          </tr>
-        {% endfor %}
-
-        <tr>
-          <td><a href="/userstats/{{user.username}}/metric/tickets/">Assigned tickets</a></td>
-          <td>{{tottickets.assigned}}</td>
-          <td>{{permonthtickets.assigned}}</td>
-          <td>{{lastmonthtickets.assigned}}</td>
-          {% if days >= 30 %}
-            <td>
-              {% if lastmonthtickets.assigned > permonthtickets.assigned %}
-                Up
-              {% elif lastmonthtickets.assigned == permonthtickets.assigned %}
-                =
-              {% else %}
-                Down
-              {%endif%}
-            </td>
-          {% endif %}
-        </tr>
-        <tr>
-          <td><a href="/userstats/{{user.username}}/metric/tickets/">Revoked tickets</a></td>
-          <td>{{tottickets.revoked}}</td>
-          <td>{{permonthtickets.revoked}}</td>
-          <td>{{lastmonthtickets.revoked}}</td>
-          {% if days >= 30 %}
-            <td>
-              {% if lastmonthtickets.revoked > permonthtickets.revoked %}
-                Up
-              {% elif lastmonthtickets.revoked == permonthtickets.revoked %}
-                =
-              {% else %}
-                Down
-              {%endif%}
-            </td>
-          {% endif %}
-        </tr>
-        <tr>
-          <td><a href="/userstats/{{user.username}}/metric/tickets/">Solved tickets</a></td>
-          <td>{{tottickets.solved}}</td>
-          <td>{{permonthtickets.solved}}</td>
-          <td>{{lastmonthtickets.solved}}</td>
-          {% if days >= 30 %}
-            <td>
-              {% if lastmonthtickets.solved > permonthtickets.solved %}
-                Up
-              {% elif lastmonthtickets.solved == permonthtickets.solved %}
-                =
-              {% else %}
-                Down
-              {%endif%}
-            </td>
-          {% endif %}
-        </tr>
-        <tr>
-          <td><a href="/userstats/{{user.username}}/metric/tickets/">Average tickets solving time</a></td>
-          <td>
-            {% if tottickets.averagesolvingtime %}
-              {{tottickets.averagesolvingtime.days}} days, 
-              {{tottickets.averagesolvingtime.hours}} hours,
-              {{tottickets.averagesolvingtime.minutes}} min
-            {% else %}n/a{% endif %}
-          </td>
-          <td>n/a</td>
-          <td>
-            {% if lastmonthtickets.averagesolvingtime %}
-              {{lastmonthtickets.averagesolvingtime.days}} days, 
-              {{lastmonthtickets.averagesolvingtime.hours}} hours,
-              {{lastmonthtickets.averagesolvingtime.minutes}} min
-            {% else %}n/a{% endif %}
-          </td>
-          {% if days >= 30 %}
-            <td>
-              {% if lastmonthtickets.averagesolvingtime > tottickets.averagesolvingtime %}
-                Up
-              {% elif lastmonthtickets.averagesolvingtime == tottickets.averagesolvingtime %}
-                =
-              {% else %}
-                Down
-              {%endif%}
-            </td>
-          {% endif %}
-        </tr>
-      </tbody>
-    </table>
-
-    {% if categories %}
-        <h2>Prefered categories</h2>
-        <table>
-          <thead>
-            <tr>
-              <th>Category name</th>
-              <th>Number of projects</th>
-            </tr>
-          </thead>
-          <tbody>
-            {% for cat, count in categories %}
-              <tr>
-                <td><a href="/userstats/{{user.username}}/category/{{cat.fullname}}">{{cat.fullname}}</a></td>
-                <td>{{count}}</td>
-              </tr>
-            {% endfor %}
-          </tbody>
-        </table>
-    {% endif %}
-    {% if category %}
-        <div class="grid-20"><a href="/userstats/{{user.username}}">Go back to general statistics</a></div>
-    {% else %}
-      <h2>Overall evaluation</h2>
-      <table>
-        <thead>
-          <tr>
-            <th>Field</th>
-            <th>Evaluation</th>
-          </tr>
-        </thead>
-        <tbody>
-          <tr>
-            <td>Code contribution</td>
-            <td>
-               {% for i in range(codestars) %}★{% endfor %}
-               {% for i in range(5 - codestars) %}☆{% endfor %}
-            </td>
-          </tr>
-          <tr>
-            <td>Contribution to discussions on the forge</td>
-            <td>
-               {% for i in range(discussionstars) %}★{% endfor %}
-               {% for i in range(5 - discussionstars) %}☆{% endfor %}
-            </td>
-          </tr>
-          <tr>
-            <td>Contribution to issues solving</td>
-            <td>
-              {% for i in range(ticketsstars) %}★{% endfor %}
-              {% for i in range(5 - ticketsstars) %}☆{% endfor %}
-            </td>
-          </tr>
-        </tbody>
-      </table>
-    {% endif %}
-  {% else %}
-    Invalid user!
-  {% endif %}
-
-{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/a972fcbd/ForgeUserStats/forgeuserstats/templates/.svn/text-base/tickets.html.svn-base
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/.svn/text-base/tickets.html.svn-base b/ForgeUserStats/forgeuserstats/templates/.svn/text-base/tickets.html.svn-base
deleted file mode 100644
index 148cfa8..0000000
--- a/ForgeUserStats/forgeuserstats/templates/.svn/text-base/tickets.html.svn-base
+++ /dev/null
@@ -1,47 +0,0 @@
-{% set hide_left_bar = True %}
-{% extends g.theme.master %}
-
-{% block title %}User stats{% endblock %}
-
-{% block header %}
-    Statistics about {{user.display_name}}'s contribution – Tickets
-{% endblock %}
-
-{% block content %}
-
-  {% if user %}
-
-    {% if data %}
-      <h2>Statistics by category</h2>
-      <table>
-        <thead>
-          <tr>
-            <th>Category</th>
-            <th>Assigned tickets</th>
-            <th>Solved tickets</th>
-            <th>Revoked tickets</th>
-            <th>Average solving time</th>
-          </tr>
-        </thead>
-        <tbody> 
-          {% for cat, el in data.items() %}
-            <tr>
-              <td>{% if cat %}{{cat.fullname}}{% else %}All categories{% endif %}</td>
-              <td>{{el.assigned}}</td>
-              <td>{{el.solved}}</td>
-              <td>{{el.revoked}}</td>
-              <td>
-                {% if el.averagesolvingtime %}
-                  {{el.averagesolvingtime.days}} days, 
-                  {{el.averagesolvingtime.hours}} hours,
-                  {{el.averagesolvingtime.minutes}} min
-                {% else %}n/a{% endif %}
-              </td>
-            {% endfor %}
-          </tr>
-        </tbody>
-      </table>
-    {% endif %}
-    <div class="grid-20"><a href="/userstats/{{user.username}}">Go back to general statistics</a></div>
-  {% endif %}
-{% endblock %}


[02/42] git commit: [5453] adding support for user stats

Posted by br...@apache.org.
[5453] adding support for user stats


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/91bac4a4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/91bac4a4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/91bac4a4

Branch: refs/heads/db/5453
Commit: 91bac4a49d5a280ab7ea85ec9fea89da73fa9052
Parents: 6f44f8a
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Dec 12 22:06:15 2012 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:29 2013 +0000

----------------------------------------------------------------------
 Allura/allura/controllers/discuss.py               |    1 +
 Allura/allura/controllers/root.py                  |    3 +
 Allura/allura/eventslistener.py                    |   27 +
 .../ext/user_profile/templates/user_index.html     |    9 +
 Allura/allura/ext/user_profile/user_main.py        |    9 +-
 Allura/allura/lib/app_globals.py                   |   13 +
 Allura/allura/lib/graphics/graphic_methods.py      |   69 ++
 Allura/allura/model/artifact.py                    |   11 +-
 Allura/allura/model/auth.py                        |   18 +
 Allura/allura/model/discuss.py                     |    2 +-
 Allura/allura/model/repo_refresh.py                |    9 +
 Allura/allura/public/nf/images/down.png            |  Bin 0 -> 2993 bytes
 Allura/allura/public/nf/images/equal.png           |  Bin 0 -> 343 bytes
 Allura/allura/public/nf/images/up.png              |  Bin 0 -> 2974 bytes
 Allura/development.ini                             |    4 +
 ForgeTracker/forgetracker/model/ticket.py          |   10 +-
 .../forgeuserstats/controllers/userstats.py        |  248 ++++++
 ForgeUserStats/forgeuserstats/main.py              |   66 ++
 .../forgeuserstats/model/.svn/all-wcprops          |   17 +
 ForgeUserStats/forgeuserstats/model/.svn/entries   |   96 +++
 .../model/.svn/text-base/stats.py.svn-base         |  534 ++++++++++++
 ForgeUserStats/forgeuserstats/model/stats.py       |  647 +++++++++++++++
 .../forgeuserstats/templates/.svn/all-wcprops      |   29 +
 .../forgeuserstats/templates/.svn/entries          |  164 ++++
 .../.svn/text-base/artifacts.html.svn-base         |   48 ++
 .../templates/.svn/text-base/commits.html.svn-base |   37 +
 .../templates/.svn/text-base/index.html.svn-base   |  341 ++++++++
 .../templates/.svn/text-base/tickets.html.svn-base |   47 +
 .../forgeuserstats/templates/artifacts.html        |   52 ++
 .../forgeuserstats/templates/commits.html          |   42 +
 ForgeUserStats/forgeuserstats/templates/index.html |  423 ++++++++++
 .../forgeuserstats/templates/tickets.html          |   52 ++
 ForgeUserStats/forgeuserstats/version.py           |    2 +
 ForgeUserStats/setup.py                            |   29 +
 requirements-common.txt                            |    2 +
 35 files changed, 3057 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/allura/controllers/discuss.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/discuss.py b/Allura/allura/controllers/discuss.py
index 4d7b3ec..f785be2 100644
--- a/Allura/allura/controllers/discuss.py
+++ b/Allura/allura/controllers/discuss.py
@@ -296,6 +296,7 @@ class PostController(BaseController):
             self.post.edit_count = self.post.edit_count + 1
             self.post.last_edit_date = datetime.utcnow()
             self.post.last_edit_by_id = c.user._id
+            self.post.commit()
             g.director.create_activity(c.user, 'modified', self.post,
                     target=self.post.thread.artifact or self.post.thread,
                     related_nodes=[self.post.app_config.project])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/allura/controllers/root.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/root.py b/Allura/allura/controllers/root.py
index 69c80ec..859394b 100644
--- a/Allura/allura/controllers/root.py
+++ b/Allura/allura/controllers/root.py
@@ -69,6 +69,9 @@ class RootController(WsgiDispatchController):
         if n and not n.url_prefix.startswith('//'):
             n.bind_controller(self)
         self.browse = ProjectBrowseController()
+        for ep in pkg_resources.iter_entry_points("allura.stats"):
+            if ep.name.lower() == 'userstats' and g.show_userstats:
+                setattr(self, ep.name.lower(), ep.load()().root)
         super(RootController, self).__init__()
 
     def _setup_request(self):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/allura/eventslistener.py
----------------------------------------------------------------------
diff --git a/Allura/allura/eventslistener.py b/Allura/allura/eventslistener.py
new file mode 100644
index 0000000..15adf00
--- /dev/null
+++ b/Allura/allura/eventslistener.py
@@ -0,0 +1,27 @@
+'''This class is supposed to be extended in order to support statistics for
+a specific entity (e.g. user, project, ...). To do so, the new classes should
+overwrite the methods defined here, which will be called when the related
+event happens, so that the statistics for the given entity are updated.'''
+class EventsListener:
+    def newArtifact(self, art_type, art_datetime, project, user):
+        pass
+
+    def modifiedArtifact(self, art_type, art_datetime, project, user):
+        pass
+
+    def newUser(self, user):
+        pass
+
+    def newOrganization(self, organization):
+        pass
+
+    def addUserLogin(self, user):
+        pass
+
+    def newCommit(self, newcommit, project, user):
+        pass
+
+    def ticketEvent(self, event_type, ticket, project, user):
+        pass
+
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/allura/ext/user_profile/templates/user_index.html
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/user_profile/templates/user_index.html b/Allura/allura/ext/user_profile/templates/user_index.html
index 2614953..7371eb6 100644
--- a/Allura/allura/ext/user_profile/templates/user_index.html
+++ b/Allura/allura/ext/user_profile/templates/user_index.html
@@ -236,6 +236,15 @@
     </div>
   </div>
 
+  {% if statslinkurl %}
+    <div class="grid-24">
+      <div class="grid-24" style="margin:0;"><b>User statistics</b></div>
+      <div class="grid-24" style="margin-top:5px;margin-bottom:5px;">
+        <div><a href="{{statslinkurl}}"/>{{statslinkdescription}}</a></div>
+      </div>
+    </div>
+  {% endif %}
+
   {% if c.user.username == user.username %}
       <div class="address-list grid-18">
         <b>Email Addresses</b>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/allura/ext/user_profile/user_main.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/user_profile/user_main.py b/Allura/allura/ext/user_profile/user_main.py
index 7754228..759f3f3 100644
--- a/Allura/allura/ext/user_profile/user_main.py
+++ b/Allura/allura/ext/user_profile/user_main.py
@@ -64,7 +64,14 @@ class UserProfileController(BaseController):
         user = c.project.user_project_of
         if not user:
             raise exc.HTTPNotFound()
-        return dict(user=user)
+        if g.show_userstats:
+            from forgeuserstats.main import ForgeUserStatsApp
+            link, description = ForgeUserStatsApp.createlink(user)
+        else:
+            link, description = None, None
+        return dict(user=user,
+                    statslinkurl = link,
+                    statslinkdescription = description)
     # This will be fully implemented in a future iteration
     # @expose('jinja:allura.ext.user_profile:templates/user_subscriptions.html')
     # def subscriptions(self):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/allura/lib/app_globals.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index 14522aa..1e9019c 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -170,6 +170,19 @@ class Globals(object):
         # Zarkov logger
         self._zarkov = None
 
+        self.show_userstats = False
+        # Set listeners to update stats
+        self.statslisteners = []
+        for ep in pkg_resources.iter_entry_points("allura.stats"):
+            if ep.name.lower() == 'userstats':
+                self.show_userstats = config.get(
+                    'user.stats.enable','false')=='true'
+                if self.show_userstats:
+                    self.statslisteners.append(ep.load()().listener)
+            else:
+                self.statslisteners.append(ep.load()().listener)
+
+
     @LazyProperty
     def spam_checker(self):
         """Return a SpamFilter implementation.

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/allura/lib/graphics/__init__.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/graphics/__init__.py b/Allura/allura/lib/graphics/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/allura/lib/graphics/graphic_methods.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/graphics/graphic_methods.py b/Allura/allura/lib/graphics/graphic_methods.py
new file mode 100644
index 0000000..cd3151b
--- /dev/null
+++ b/Allura/allura/lib/graphics/graphic_methods.py
@@ -0,0 +1,69 @@
+from matplotlib.backends.backend_agg import FigureCanvasAgg
+from matplotlib.figure import Figure
+from matplotlib.text import Annotation
+from PIL import Image
+import StringIO
+
+def create_histogram(data, tick_labels, y_label, title):
+    fig = Figure(figsize=(10,5), dpi=80, facecolor='white')
+    ax = fig.add_subplot(111, axisbg='#EEEEFF')
+
+    canvas = FigureCanvasAgg(fig)
+    n, bins, patches = ax.hist(data, facecolor='#330099', edgecolor='white')
+    ax.set_ylabel(y_label)
+    ax.set_title(title)
+
+    ax.set_xticks(range(len(tick_labels)+1))
+    ax.get_xaxis().set_ticklabels(tick_labels, rotation=45, va='top', ha='right')
+    ax.get_xaxis().set_ticks_position('none')
+    ax.set_autoscalex_on(False)
+
+    ax.set_xlim((-1, len(tick_labels)))
+    ax.set_ylim((0, 1+max([data.count(el) for el in data])))
+    fig.subplots_adjust(bottom=0.3)
+
+    canvas.draw()
+        
+    s = canvas.tostring_rgb()
+    l,b,w,h = fig.bbox.bounds
+    w, h = int(w), int(h)
+
+    output = StringIO.StringIO()
+    im = Image.fromstring( "RGB", (w,h), s)
+    im.save(output, 'PNG')
+
+    return output.getvalue()
+
+def create_progress_bar(value):
+    value = value / 100.0
+    if value < 1 / 5.0:
+        color = 'red'
+    elif value < 2 / 5.0:
+        color = 'orange'
+    elif value < 3 / 5.0:
+        color = 'yellow'
+    elif value < 4 / 5.0:
+        color = 'lightgreen'
+    else:
+        color = 'green'
+
+    fig = Figure(figsize=(3,0.5), dpi=40, facecolor='gray')
+    canvas = FigureCanvasAgg(fig)
+    canvas.draw()
+
+    from matplotlib.patches import Rectangle
+    from matplotlib.axes import Axes
+
+    fig.draw_artist(Rectangle((0,0), int(value * 120), 20, color=color))
+    fig.draw_artist(Rectangle((1,0), 119, 19, fill=False, ec='black'))
+
+    l,b,w,h = fig.bbox.bounds
+    s = canvas.tostring_rgb()
+    w, h = int(w), int(h)
+
+    output = StringIO.StringIO()
+    im = Image.fromstring( "RGB", (w,h), s)
+    im.save(output, 'PNG')
+
+    return output.getvalue()
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/allura/model/artifact.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/artifact.py b/Allura/allura/model/artifact.py
index aa3ddfa..b3760dd 100644
--- a/Allura/allura/model/artifact.py
+++ b/Allura/allura/model/artifact.py
@@ -347,7 +347,7 @@ class VersionedArtifact(Artifact):
 
     version = FieldProperty(S.Int, if_missing=0)
 
-    def commit(self):
+    def commit(self, update_stats=True):
         '''Save off a snapshot of the artifact and increment the version #'''
         self.version += 1
         try:
@@ -372,6 +372,15 @@ class VersionedArtifact(Artifact):
         session(ss).insert_now(ss, state(ss))
         log.info('Snapshot version %s of %s',
                  self.version, self.__class__)
+        if update_stats: 
+            if self.version > 1:
+                for l in g.statslisteners:
+                    l.modifiedArtifact(
+                        self.type_s, self.mod_date, self.project, c.user)
+            else :
+                for l in g.statslisteners:
+                    l.newArtifact(
+                        self.type_s, self.mod_date, self.project, c.user)
         return ss
 
     def get_version(self, n):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/allura/model/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index 8df4a58..cb9bdfb 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -11,6 +11,7 @@ from hashlib import sha256
 import uuid
 from pytz import timezone
 from datetime import timedelta, date, datetime, time
+from pkg_resources import iter_entry_points
 
 import iso8601
 import pymongo
@@ -35,6 +36,13 @@ from .timeline import ActivityNode, ActivityObject
 
 log = logging.getLogger(__name__)
 
+#This is just to keep the UserStats module completely optional
+has_user_stats_module = False
+for ep in iter_entry_points("allura.stats"):
+    if ep.name.lower() == 'userstats':
+        from forgeuserstats.model.stats import UserStats
+        has_user_stats_module = True
+
 def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
     """
     Returns a bytestring version of 's', encoded as specified in 'encoding'.
@@ -331,6 +339,13 @@ class User(MappedClass, ActivityNode, ActivityObject):
         level = S.OneOf('low', 'high', 'medium'),
         comment=str)])
 
+    #Statistics
+    if has_user_stats_module:
+        stats_id = ForeignIdProperty('UserStats', if_missing=None)
+        stats = RelationProperty('UserStats', via='stats_id')
+    else:
+        stats_id = FieldProperty(S.ObjectId, if_missing=None)
+
     @property
     def activity_name(self):
         return self.display_name or self.username
@@ -577,6 +592,9 @@ class User(MappedClass, ActivityNode, ActivityObject):
         user = auth_provider.register_user(doc)
         if user and 'display_name' in doc:
             user.set_pref('display_name', doc['display_name'])
+        if user:
+            for l in g.statslisteners:
+                l.newUser(user)
         if user and make_project:
             n = M.Neighborhood.query.get(name='Users')
             n.register_project(auth_provider.user_project_shortname(user),

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/allura/model/discuss.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/discuss.py b/Allura/allura/model/discuss.py
index 2ea6f4c..b9bad98 100644
--- a/Allura/allura/model/discuss.py
+++ b/Allura/allura/model/discuss.py
@@ -203,7 +203,7 @@ class Thread(Artifact, ActivityObject):
     def add_post(self, **kw):
         """Helper function to avoid code duplication."""
         p = self.post(**kw)
-        p.commit()
+        p.commit(update_stats=False)
         self.num_replies += 1
         if not self.first_post:
             self.first_post_id = p._id

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/allura/model/repo_refresh.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repo_refresh.py b/Allura/allura/model/repo_refresh.py
index 539df2a..5e4f21f 100644
--- a/Allura/allura/model/repo_refresh.py
+++ b/Allura/allura/model/repo_refresh.py
@@ -110,12 +110,21 @@ def refresh_repo(repo, all_commits=False, notify=True):
             if (i+1) % 100 == 0:
                 log.info('Compute last commit info %d: %s', (i+1), ci._id)
 
+    for commit in commit_ids:
+        new = repo.commit(commit)
+        user = User.by_email_address(new.committed.email)
+        if user is None:
+            user = User.by_username(new.committed.name)
+        if user is not None:
+            for l in g.statslisteners:
+                l.newCommit(new, repo.app_config.project, user)
 
     log.info('Refresh complete for %s', repo.full_fs_path)
     g.post_event(
             'repo_refreshed',
             commit_number=len(commit_ids),
             new=bool(new_commit_ids))
+
     # Send notifications
     if notify:
         send_notifications(repo, commit_ids)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/allura/public/nf/images/down.png
----------------------------------------------------------------------
diff --git a/Allura/allura/public/nf/images/down.png b/Allura/allura/public/nf/images/down.png
new file mode 100644
index 0000000..7ecfe70
Binary files /dev/null and b/Allura/allura/public/nf/images/down.png differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/allura/public/nf/images/equal.png
----------------------------------------------------------------------
diff --git a/Allura/allura/public/nf/images/equal.png b/Allura/allura/public/nf/images/equal.png
new file mode 100644
index 0000000..c08136a
Binary files /dev/null and b/Allura/allura/public/nf/images/equal.png differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/allura/public/nf/images/up.png
----------------------------------------------------------------------
diff --git a/Allura/allura/public/nf/images/up.png b/Allura/allura/public/nf/images/up.png
new file mode 100644
index 0000000..f044a67
Binary files /dev/null and b/Allura/allura/public/nf/images/up.png differ

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/Allura/development.ini
----------------------------------------------------------------------
diff --git a/Allura/development.ini b/Allura/development.ini
index 3dc76d0..7d2bbe0 100644
--- a/Allura/development.ini
+++ b/Allura/development.ini
@@ -126,6 +126,10 @@ scm.repos.tarball.url_prefix = http://localhost/
 
 trovecategories.enableediting = true
 
+# If set to false, the stats of the user are not
+# updated and they are not shown to users.
+user.stats.enable = true
+
 # ActivityStream
 activitystream.master = mongodb://127.0.0.1:27017
 activitystream.database = activitystream

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeTracker/forgetracker/model/ticket.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
index 1abeaac..b97fa44 100644
--- a/ForgeTracker/forgetracker/model/ticket.py
+++ b/ForgeTracker/forgetracker/model/ticket.py
@@ -442,6 +442,8 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
                 ('Status', old.status, self.status) ]
             if old.status != self.status and self.status in c.app.globals.set_of_closed_status_names:
                 h.log_action(log, 'closed').info('')
+                for l in g.statslisteners:
+                    l.ticketEvent("closed", self, self.project, self.assigned_to)
             for key in self.custom_fields:
                 fields.append((key, old.custom_fields.get(key, ''), self.custom_fields[key]))
             for title, o, n in fields:
@@ -454,6 +456,9 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
                 changes.append('Owner updated: %r => %r' % (
                         o and o.username, n and n.username))
                 self.subscribe(user=n)
+                for l in g.statslisteners :
+                    l.ticketEvent("assigned", self, self.project, n)
+                    if o: l.ticketEvent("revoked", self, self.project, o)
             if old.description != self.description:
                 changes.append('Description updated:')
                 changes.append('\n'.join(
@@ -466,7 +471,10 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
         else:
             self.subscribe()
             if self.assigned_to_id:
-                self.subscribe(user=User.query.get(_id=self.assigned_to_id))
+                user = User.query.get(_id=self.assigned_to_id)
+                for l in g.statslisteners :
+                    l.ticketEvent("assigned", self, self.project, user)
+                self.subscribe(user=user)
             description = ''
             subject = self.email_subject
             Thread.new(discussion_id=self.app_config.discussion_id,

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/__init__.py b/ForgeUserStats/forgeuserstats/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/controllers/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/controllers/__init__.py b/ForgeUserStats/forgeuserstats/controllers/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/controllers/userstats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/controllers/userstats.py b/ForgeUserStats/forgeuserstats/controllers/userstats.py
new file mode 100644
index 0000000..2bfaf82
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/controllers/userstats.py
@@ -0,0 +1,248 @@
+from tg import expose
+from tg.decorators import with_trailing_slash
+from datetime import datetime
+from allura.controllers import BaseController
+import allura.model as M
+from allura.lib.graphics.graphic_methods import create_histogram, create_progress_bar
+from forgeuserstats.model.stats import UserStats
+
+class ForgeUserStatsController(BaseController):
+
+    @expose()
+    def _lookup(self, part, *remainder):
+        user = M.User.query.get(username=part)
+
+        if not self.user:
+            return ForgeUserStatsController(user=user), remainder
+        if part == "category":
+            return ForgeUserStatsCatController(self.user, self.stats, None), remainder
+        if part == "metric":
+            return ForgeUserStatsMetricController(self.user, self.stats), remainder
+
+    def __init__(self, user=None):
+        self.user = user
+        if self.user:
+            self.stats = self.user.stats
+            if not self.stats:
+                self.stats = UserStats.create(self.user)
+
+        super(ForgeUserStatsController, self).__init__()
+
+    @expose('jinja:forgeuserstats:templates/index.html')
+    @with_trailing_slash
+    def index(self, **kw):
+        if not self.user: 
+            return dict(user=None)
+        stats = self.stats
+
+        ret_dict = _getDataForCategory(None, stats)
+        ret_dict['user'] = self.user
+
+        ret_dict['registration_date'] = stats.registration_date
+
+        ret_dict['totlogins'] = stats.tot_logins_count
+        ret_dict['last_login'] = stats.last_login
+        if stats.last_login:
+            ret_dict['last_login_days'] = \
+                (datetime.utcnow()-stats.last_login).days
+
+        categories = {}
+        for p in self.user.my_projects():
+            for cat in p.trove_topic:
+                cat = M.TroveCategory.query.get(_id = cat)
+                if categories.get(cat):
+                    categories[cat] += 1
+                else:
+                    categories[cat] = 1
+        categories = sorted(categories.items(), key=lambda (x,y): y,reverse=True)
+
+        ret_dict['lastmonth_logins'] = stats.getLastMonthLogins()
+        ret_dict['categories'] = categories
+        days = ret_dict['days']
+        if days >= 30: 
+            ret_dict['permonthlogins'] = \
+                round(stats.tot_logins_count*30.0/days,2)
+        else:
+            ret_dict['permonthlogins'] = 'n/a'
+
+        ret_dict['codepercentage'] = stats.codeRanking()
+        ret_dict['discussionpercentage'] = stats.discussionRanking()
+        ret_dict['ticketspercentage'] = stats.ticketsRanking()
+        ret_dict['codecontribution'] = stats.getCodeContribution()
+        ret_dict['discussioncontribution'] = stats.getDiscussionContribution()
+        ret_dict['ticketcontribution'] = stats.getTicketsContribution()
+        ret_dict['maxcodecontrib'], ret_dict['averagecodecontrib'] =\
+            stats.getMaxAndAverageCodeContribution()
+        ret_dict['maxdisccontrib'], ret_dict['averagedisccontrib'] =\
+            stats.getMaxAndAverageDiscussionContribution()
+        ret_dict['maxticketcontrib'], ret_dict['averageticketcontrib'] =\
+            stats.getMaxAndAverageTicketsSolvingPercentage()
+
+        return ret_dict
+
+    @expose()
+    def categories_graph(self):
+        categories = {}
+        for p in self.user.my_projects():
+            for cat in p.trove_topic:
+                cat = M.TroveCategory.query.get(_id = cat)
+                if categories.get(cat):
+                    categories[cat] += 1
+                else:
+                    categories[cat] = 1
+        data = []
+        labels = []
+        i = 0
+        for cat in sorted(categories.keys(), key=lambda x:x.fullname):
+            n = categories[cat]
+            data = data + [i] * n
+            label = cat.fullname
+            if len(label) > 15:
+                label = label[:15] + "..."
+            labels.append(label)
+            i += 1
+
+        return create_histogram(data, labels, 
+            'Number of projects', 'Projects by category')
+
+    @expose()
+    def code_ranking_bar(self):
+        return create_progress_bar(self.stats.codeRanking())
+
+    @expose()
+    def discussion_ranking_bar(self):
+        return create_progress_bar(self.stats.discussionRanking())
+
+    @expose()
+    def tickets_ranking_bar(self):
+        return create_progress_bar(self.stats.ticketsRanking())
+
+class ForgeUserStatsCatController(BaseController):
+    @expose()
+    def _lookup(self, category, *remainder):
+        cat = M.TroveCategory.query.get(fullname=category)
+        return ForgeUserStatsCatController(self.user, cat), remainder
+
+    def __init__(self, user, stats, category):
+        self.user = user
+        self.stats = stats
+        self.category = category
+        super(ForgeUserStatsCatController, self).__init__()
+
+    @expose('jinja:forgeuserstats:templates/index.html')
+    @with_trailing_slash
+    def index(self, **kw):
+        if not self.user:
+            return dict(user=None)
+        stats = self.stats
+        
+        cat_id = None
+        if self.category: 
+            cat_id = self.category._id
+        ret_dict = _getDataForCategory(cat_id, stats)
+        ret_dict['user'] = self.user
+        ret_dict['registration_date'] = stats.registration_date
+        ret_dict['category'] = self.category
+        
+        return ret_dict
+
+class ForgeUserStatsMetricController(BaseController):
+
+    def __init__(self, user, stats):
+        self.user = user
+        self.stats = stats
+        super(ForgeUserStatsMetricController, self).__init__()
+
+    @expose('jinja:forgeuserstats:templates/commits.html')
+    @with_trailing_slash
+    def commits(self, **kw):
+        if not self.user:
+            return dict(user=None)
+        stats = self.stats
+        
+        commits = stats.getCommitsByCategory()
+        return dict(user = self.user,
+                    data = commits) 
+
+    @expose('jinja:forgeuserstats:templates/artifacts.html')
+    @with_trailing_slash
+    def artifacts(self, **kw):
+        if not self.user:
+            return dict(user=None)
+
+        stats = self.stats       
+        artifacts = stats.getArtifactsByCategory(detailed=True)
+        return dict(user = self.user, data = artifacts) 
+
+    @expose('jinja:forgeuserstats:templates/tickets.html')
+    @with_trailing_slash
+    def tickets(self, **kw):
+        if not self.user: 
+            return dict(user=None)
+
+        artifacts = self.stats.getTicketsByCategory()
+        return dict(user = self.user, data = artifacts) 
+
+def _getDataForCategory(category, stats):
+    totcommits = stats.getCommits(category)
+    tottickets = stats.getTickets(category)
+    averagetime = tottickets.get('averagesolvingtime')
+    artifacts_by_type = stats.getArtifactsByType(category)
+    totartifacts = artifacts_by_type.get(None) 
+    if totartifacts:
+        del artifacts_by_type[None]
+    else:
+        totartifacts = dict(created=0, modified=0)
+    lmcommits = stats.getLastMonthCommits(category)
+    lm_artifacts_by_type = stats.getLastMonthArtifactsByType(category)
+    lm_totartifacts = stats.getLastMonthArtifacts(category)
+    lm_tickets = stats.getLastMonthTickets(category)
+
+    averagetime = lm_tickets.get('averagesolvingtime')
+
+    days = (datetime.utcnow() - stats.registration_date).days
+    if days >= 30: 
+        pmartifacts = dict(
+            created = round(totartifacts['created']*30.0/days,2),
+            modified=round(totartifacts['modified']*30.0/days,2))
+        pmcommits = dict(
+            number=round(totcommits['number']*30.0/days,2),
+            lines=round(totcommits['lines']*30.0/days,2))
+        pmtickets = dict(
+            assigned=round(tottickets['assigned']*30.0/days,2),
+            revoked=round(tottickets['revoked']*30.0/days,2),
+            solved=round(tottickets['solved']*30.0/days,2),
+            averagesolvingtime='n/a')
+        for key in artifacts_by_type:
+            value = artifacts_by_type[key]
+            artifacts_by_type[key]['pmcreated'] = \
+                round(value['created']*30.0/days,2)
+            artifacts_by_type[key]['pmmodified']= \
+                round(value['modified']*30.0/days,2)
+    else: 
+        pmartifacts = dict(created='n/a', modified='n/a')
+        pmcommits = dict(number='n/a', lines='n/a')
+        pmtickets = dict(
+            assigned='n/a',
+            revoked='n/a',
+            solved='n/a',
+            averagesolvingtime='n/a')
+        for key in artifacts_by_type:
+            value = artifacts_by_type[key]
+            artifacts_by_type[key]['pmcreated'] = 'n/a'
+            artifacts_by_type[key]['pmmodified']= 'n/a'
+
+    return dict(
+        days = days,
+        totcommits = totcommits,
+        lastmonthcommits = lmcommits,
+        lastmonthtickets = lm_tickets,
+        tottickets = tottickets,
+        permonthcommits = pmcommits,
+        totartifacts = totartifacts,
+        lastmonthartifacts = lm_totartifacts,
+        permonthartifacts = pmartifacts,
+        artifacts_by_type = artifacts_by_type,
+        lastmonth_artifacts_by_type = lm_artifacts_by_type,
+        permonthtickets = pmtickets)
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/main.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/main.py b/ForgeUserStats/forgeuserstats/main.py
new file mode 100644
index 0000000..43ca2f3
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/main.py
@@ -0,0 +1,66 @@
+import logging
+from datetime import datetime
+
+from allura.eventslistener import EventsListener
+from model.stats import UserStats
+from controllers.userstats import ForgeUserStatsController
+
+log = logging.getLogger(__name__)
+
+class UserStatsListener(EventsListener):
+    def newArtifact(self, art_type, art_datetime, project, user):
+        stats = user.stats
+        if not stats:
+            stats = UserStats.create(user)
+        stats.addNewArtifact(art_type, art_datetime, project)
+
+    def modifiedArtifact(self, art_type, art_datetime, project, user):
+        stats = user.stats
+        if not stats:
+            stats = UserStats.create(user)
+
+        stats.addModifiedArtifact(art_type, art_datetime, project)
+
+    def newUser(self, user):
+        stats = UserStats.create(user)
+
+    def ticketEvent(self, event_type, ticket, project, user):
+        if user is None:
+            return
+        stats = user.stats
+        if not stats:
+            stats = UserStats.create(user)
+
+        if event_type == "assigned": 
+            stats.addAssignedTicket(ticket, project)
+        elif event_type == "revoked":
+            stats.addRevokedTicket(ticket, project)
+        elif event_type == "closed":
+            stats.addClosedTicket(ticket, project)
+
+    def newCommit(self, newcommit, project, user):
+        stats = user.stats
+        if not stats:
+            stats = UserStats.create(user)
+
+        stats.addCommit(newcommit, project)
+
+    def addUserLogin(self, user):
+        stats = user.stats
+        if not stats:
+            stats = UserStats.create(user)
+
+        stats.addLogin()
+
+    def newOrganization(self, organization):
+        pass
+
+class ForgeUserStatsApp:
+    root = ForgeUserStatsController()
+    listener = UserStatsListener()
+
+    @classmethod
+    def createlink(cls, user):
+        return (
+            "/userstats/%s/" % user.username, 
+            "%s personal statistcs" % user.display_name)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/model/.svn/all-wcprops
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/.svn/all-wcprops b/ForgeUserStats/forgeuserstats/model/.svn/all-wcprops
new file mode 100644
index 0000000..a5d5661
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/model/.svn/all-wcprops
@@ -0,0 +1,17 @@
+K 25
+svn:wc:ra_dav:version-url
+V 58
+/svn/allura/!svn/ver/3/ForgeUserStats/forgeuserstats/model
+END
+stats.py
+K 25
+svn:wc:ra_dav:version-url
+V 67
+/svn/allura/!svn/ver/3/ForgeUserStats/forgeuserstats/model/stats.py
+END
+__init__.py
+K 25
+svn:wc:ra_dav:version-url
+V 70
+/svn/allura/!svn/ver/1/ForgeUserStats/forgeuserstats/model/__init__.py
+END

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/model/.svn/entries
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/.svn/entries b/ForgeUserStats/forgeuserstats/model/.svn/entries
new file mode 100644
index 0000000..c26dfd9
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/model/.svn/entries
@@ -0,0 +1,96 @@
+10
+
+dir
+4
+https://xp-dev.com/svn/allura/ForgeUserStats/forgeuserstats/model
+https://xp-dev.com/svn/allura
+
+
+
+2012-10-19T08:28:36.749162Z
+3
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+46ed536d-f66c-413e-a53e-834384f708db
+
+stats.py
+file
+
+
+
+
+2012-11-05T14:43:25.729756Z
+21591047edf4fabfb1b70150af5bd0c2
+2012-10-19T08:28:36.749162Z
+3
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+23647
+
+__init__.py
+file
+
+
+
+
+2012-11-05T14:43:25.729756Z
+d41d8cd98f00b204e9800998ecf8427e
+2012-10-17T19:55:53.450112Z
+1
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/model/.svn/text-base/__init__.py.svn-base
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/.svn/text-base/__init__.py.svn-base b/ForgeUserStats/forgeuserstats/model/.svn/text-base/__init__.py.svn-base
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/model/.svn/text-base/stats.py.svn-base
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/.svn/text-base/stats.py.svn-base b/ForgeUserStats/forgeuserstats/model/.svn/text-base/stats.py.svn-base
new file mode 100644
index 0000000..f434e4e
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/model/.svn/text-base/stats.py.svn-base
@@ -0,0 +1,534 @@
+import pymongo
+from pylons import c, g, request
+
+import bson
+from ming import schema as S
+from ming import Field, Index, collection
+from ming.orm import session, state, Mapper
+from ming.orm import FieldProperty, RelationProperty, ForeignIdProperty
+from ming.orm.declarative import MappedClass
+from datetime import datetime, timedelta
+import difflib
+
+from allura.model.session import main_orm_session, main_doc_session
+from allura.model.session import project_orm_session
+from allura.model import User
+import allura.model as M
+from allura.lib import helpers as h
+
+class UserStats(MappedClass):
+    SALT_LEN=8
+    class __mongometa__:
+        name='userstats'
+        session = main_orm_session
+        unique_indexes = [ 'userid' ]
+
+    _id=FieldProperty(S.ObjectId)
+    userid = ForeignIdProperty('User')
+
+    registration_date = FieldProperty(datetime)
+    tot_logins_count = FieldProperty(int, if_missing = 0)
+    last_login = FieldProperty(datetime)
+    general = FieldProperty([dict(category = S.ObjectId,
+                                  messages = [dict(messagetype = str,
+                                                   created = int,
+                                                   modified = int)],
+                                  tickets = dict(solved = int,
+                                                 assigned = int,
+                                                 revoked = int,
+                                                 totsolvingtime = int),
+                                  commits = [dict(lines = int,
+                                                  number = int,
+                                                  language = S.ObjectId)])])
+
+    lastmonth= FieldProperty(dict(logins=[datetime],
+                                  messages=[dict(datetime=datetime,
+                                                 created=bool,
+                                                 categories=[S.ObjectId],
+                                                 messagetype=str)],
+                                  assignedtickets=[dict(datetime=datetime,
+                                                        categories=[S.ObjectId])],
+                                  revokedtickets=[dict(datetime=datetime,
+                                                       categories=[S.ObjectId])],
+                                  solvedtickets=[dict(datetime=datetime,
+                                                      categories=[S.ObjectId],
+                                                      solvingtime=int)],
+                                  commits=[dict(datetime=datetime,
+                                                categories=[S.ObjectId],
+                                                programming_languages=[S.ObjectId],
+                                                lines=int)]))
+    reluser = RelationProperty('User')
+
+
+    def codeRanking(self) :
+        def _getCodeContribution(stats) :
+            for val in stats['general'] :
+                if val['category'] is None :
+                    for commits in val['commits'] :
+                         if commits['language'] is None : 
+                             return (commits.lines, commits.number)
+            return (0,0) 
+
+        lst = list(self.query.find())
+        totn = len(lst)
+        codcontr = _getCodeContribution(self)
+        upper = len([x for x in lst if _getCodeContribution(x) > codcontr])
+        percentage = upper * 100.0 / totn
+        if percentage < 1 / 6.0 : return 5
+        if percentage < 2 / 6.0 : return 4
+        if percentage < 3 / 6.0 : return 3
+        if percentage < 4 / 6.0 : return 2
+        if percentage < 5 / 6.0 : return 1
+        return 0
+
+    def discussionRanking(self) :
+        def _getDiscussionContribution(stats) :
+            for val in stats['general'] :
+                if val['category'] is None :
+                    for artifact in val['messages'] :
+                         if artifact['messagetype'] is None : 
+                             return artifact.created + artifact.modified
+            return 0
+
+        lst = list(self.query.find())
+        totn = len(lst)
+        disccontr = _getDiscussionContribution(self)
+        upper = len([x for x in lst if _getDiscussionContribution(x) > disccontr])
+        percentage = upper * 100.0 / totn
+        if percentage < 1 / 6.0 : return 5
+        if percentage < 2 / 6.0 : return 4
+        if percentage < 3 / 6.0 : return 3
+        if percentage < 4 / 6.0 : return 2
+        if percentage < 5 / 6.0 : return 1
+        return 0
+
+    def ticketsRanking(self) :
+
+        def _getTicketsPercentage(stats) :
+            for val in stats['general'] :
+                if val['category'] is None :
+                    if val['tickets']['assigned'] == 0 : percentage = 0
+                    else :
+                        percentage = val['tickets']['solved'] \
+                                    / val['tickets']['assigned']
+            return 0
+
+        percentage = _getTicketsPercentage(self)
+        if percentage > 1 / 6.0 : return 5
+        if percentage > 2 / 6.0 : return 4
+        if percentage > 3 / 6.0 : return 3
+        if percentage > 4 / 6.0 : return 2
+        if percentage > 5 / 6.0 : return 1
+        return 0
+
+    def getCommits(self, category = None) :
+        i = getElementIndex(self.general, category = category)
+        if i is None : return {'number' : 0, 'lines': 0}
+        cat = self.general[i]
+        j = getElementIndex(cat.commits, language = None)
+        if j is None : return {'number' : 0, 'lines': 0}
+        return {'number': cat.commits[j]['number'], 
+                'lines' : cat.commits[j]['lines']}
+
+    def getArtifacts(self, category = None, art_type = None) :
+        i = getElementIndex(self.general, category = category)
+        if i is None : return {'created' : 0, 'modified': 0}
+        cat = self.general[i]
+        j = getElementIndex(cat.messages, art_type = art_type)
+        if j is None : return {'created' : 0, 'modified': 0}
+        return {'created'  : cat[j].created, 
+                'modified' : cat[j].modified}
+
+    def getTickets(self, category = None) :
+        i = getElementIndex(self.general, category = category)
+        if i is None : return {'assigned'           : 0,
+                               'solved'             : 0,
+                               'revoked'            : 0,
+                               'averagesolvingtime' : None}
+        if self.general[i].tickets.solved > 0 :
+           tot = self.general[i].tickets.totsolvingtime 
+           number = self.general[i].tickets.solved
+           average = tot / number
+
+        else : average = None
+        return {'assigned'           : self.general[i].tickets.assigned,
+                'solved'             : self.general[i].tickets.solved,
+                'revoked'            : self.general[i].tickets.revoked,
+                'averagesolvingtime' : _convertTimeDiff(average)}
+
+    def getCommitsByCategory(self) :
+        by_cat = {}
+        for entry in self.general :
+            cat = entry.category
+            i = getElementIndex(entry.commits, language = None)
+            if i is None : n, lines = 0, 0
+            else : n, lines = entry.commits[i].number, entry.commits[i].lines
+            if cat != None : cat = M.TroveCategory.query.get(_id = cat)
+            by_cat[cat] = {'number' : n, 'lines' : lines}
+        return by_cat
+
+    def getCommitsByLanguage(self) :
+        langlist = []
+        by_lang = {}
+        i = getElementIndex(self.general, category=None)
+        if i is None : return {'number' : 0, 'lines' : 0}
+        return dict([(el.language, {'lines' : el.lines, 'number':el.number})
+                     for el in self.general[i].commits])
+
+    def getArtifactsByCategory(self, detailed=False) :
+        by_cat = {}
+        for entry in self.general :
+            cat = entry.category
+            if cat != None : cat = M.TroveCategory.query.get(_id = cat)
+            if detailed : 
+                by_cat[cat] = entry.messages
+            else : 
+                i = getElementIndex(entry.messages, messagetype=None)
+                if i is not None : by_cat[cat] = entry.messages[i]
+                else : by_cat[cat] = {'created' : 0, 'modified' : 0}
+        return by_cat
+
+    def getArtifactsByType(self, category=None) :
+        i = getElementIndex(self.general, category = category)
+        if i is None : return {}
+        entry = self.general[i].messages
+        by_type = dict([(el.messagetype, {'created' : el.created, 
+                                          'modified': el.modified})
+                         for el in entry])
+        return by_type
+
+    def getTicketsByCategory(self) :
+        by_cat = {}
+        for entry in self.general :
+            cat = entry.category
+            if cat != None : cat = M.TroveCategory.query.get(_id = cat)
+            a, s = entry.tickets.assigned, entry.tickets.solved
+            r, time = entry.tickets.solved, entry.tickets.totsolvingtime
+            if s : average = time / s
+            else : average = None
+            by_cat[cat] = {'assigned'           : a,
+                           'solved'             : s,
+                           'revoked'            : r, 
+                           'averagesolvingtime' : _convertTimeDiff(average)}
+        return by_cat
+
+    def getLastMonthCommits(self, category = None) :
+        self.checkOldArtifacts() 
+        lineslist = [el.lines for el in self.lastmonth.commits
+                     if category in el.categories + [None]]
+        return {'number': len(lineslist), 'lines':sum(lineslist)}
+
+    def getLastMonthCommitsByCategory(self) :
+        self.checkOldArtifacts() 
+        seen = set()
+        catlist=[el.category for el in self.general
+                 if el.category not in seen and not seen.add(el.category)]
+
+        by_cat = {}
+        for cat in catlist :
+            lineslist = [el.lines for el in self.lastmonth.commits
+                         if cat in el.categories + [None]]
+            n = len(lineslist)
+            lines = sum(lineslist)
+            if cat != None : cat = M.TroveCategory.query.get(_id = cat)
+            by_cat[cat] = {'number' : n, 'lines' : lines}
+        return by_cat
+
+    def getLastMonthCommitsByLanguage(self) :
+        self.checkOldArtifacts() 
+        seen = set()
+        langlist=[el.language for el in self.general
+                  if el.language not in seen and not seen.add(el.language)]
+
+        by_lang = {}
+        for lang in langlist :
+            lineslist = [el.lines for el in self.lastmonth.commits
+                         if lang in el.programming_languages + [None]]
+            n = len(lineslist)
+            lines = sum(lineslist)
+            if lang != None : lang = M.TroveCategory.query.get(_id = lang)
+            by_lang[lang] = {'number' : n, 'lines' : lines}
+        return by_lang
+
+    def getLastMonthArtifacts(self, category = None) :
+        self.checkOldArtifacts() 
+        cre, mod = reduce(addtuple, [(int(el.created),1-int(el.created))
+                                     for el in self.lastmonth.messages
+                                     if category is None or 
+                                        category in el.categories], (0,0))
+        return {'created': cre, 'modified' : mod}
+
+    def getLastMonthArtifactsByType(self, category = None) :
+        self.checkOldArtifacts() 
+        seen = set()
+        types=[el.messagetype for el in self.lastmonth.messages
+               if el.messagetype not in seen and not seen.add(el.messagetype)]
+
+        by_type = {}
+        for t in types :
+            cre, mod = reduce(addtuple, 
+                              [(int(el.created),1-int(el.created))
+                               for el in self.lastmonth.messages
+                               if el.messagetype == t and
+                                  category in [None]+el.categories],
+                              (0,0))
+            by_type[t] = {'created': cre, 'modified' : mod}
+        return by_type
+
+    def getLastMonthArtifactsByCategory(self) :
+        self.checkOldArtifacts() 
+        seen = set()
+        catlist=[el.category for el in self.general
+                 if el.category not in seen and not seen.add(el.category)]
+
+        by_cat = {}
+        for cat in catlist :
+            cre, mod = reduce(addtuple, [(int(el.created),1-int(el.created))
+                                         for el in self.lastmonth.messages 
+                                         if cat in el.categories + [None]], (0,0))
+            if cat != None : cat = M.TroveCategory.query.get(_id = cat)
+            by_cat[cat] = {'created' : cre, 'modified' : mod}
+        return by_cat
+
+    def getLastMonthTickets(self, category = None) :
+        self.checkOldArtifacts()
+        a = len([el for el in self.lastmonth.assignedtickets
+                 if category in el.categories + [None]])
+        r = len([el for el in self.lastmonth.revokedtickets
+                 if category in el.categories + [None]])
+        s, time = reduce(addtuple, 
+                         [(1, el.solvingtime)
+                          for el in self.lastmonth.solvedtickets
+                          if category in el.categories + [None]],
+                         (0,0))
+        if category!=None : category = M.TroveCategory.query.get(_id=category)
+        if s > 0 : time = time / s
+        else : time = None
+        return {'assigned'           : a,
+                'revoked'            : r,
+                'solved'             : s, 
+                'averagesolvingtime' : _convertTimeDiff(time)}
+        
+    def getLastMonthTicketsByCategory(self) :
+        self.checkOldArtifacts()
+        seen = set()
+        catlist=[el.category for el in self.general
+                 if el.category not in seen and not seen.add(el.category)]
+        by_cat = {}
+        for cat in catlist :
+            a = len([el for el in self.lastmonth.assignedtickets
+                     if cat in el.categories + [None]])
+            r = len([el for el in self.lastmonth.revokedtickets
+                     if cat in el.categories + [None]])
+            s, time = reduce(addtuple, [(1, el.solvingtime)
+                                        for el in self.lastmonth.solvedtickets
+                                        if cat in el.categories + [None]],(0,0))
+            if cat != None : cat = M.TroveCategory.query.get(_id = cat)
+            if s > 0 : time = time / s
+            else : time = None
+            by_cat[cat] = {'assigned'           : a,
+                           'revoked'            : r,
+                           'solved'             : s, 
+                           'averagesolvingtime' : _convertTimeDiff(time)}
+        return by_cat
+        
+    def getLastMonthLogins(self) :
+        self.checkOldArtifacts()
+        return len(self.lastmonth.logins)
+
+    def checkOldArtifacts(self) :
+        now = datetime.now()
+        for m in self.lastmonth.messages :
+            if now - m.datetime > timedelta(30) :
+               self.lastmonth.messages.remove(m)
+        for t in self.lastmonth.assignedtickets :
+            if now - t.datetime > timedelta(30) :
+               self.lastmonth.assignedtickets.remove(t)
+        for t in self.lastmonth.revokedtickets :
+            if now - t.datetime > timedelta(30) :
+               self.lastmonth.revokedtickets.remove(t)
+        for t in self.lastmonth.solvedtickets :
+            if now - t.datetime > timedelta(30) :
+               self.lastmonth.solvedtickets.remove(t)
+
+    def addNewArtifact(self, art_type, art_datetime, project) :
+        self._updateArtifactsStats(art_type, art_datetime, project, "created")
+
+    def addModifiedArtifact(self, art_type, art_datetime, project) :
+        self._updateArtifactsStats(art_type, art_datetime, project, "modified")
+
+    def addAssignedTicket(self, ticket, project) :
+        topics = [t for t in project.trove_topic if t]
+        self._updateTicketsStats(topics, 'assigned')
+        self.lastmonth.assignedtickets.append({'datetime'   : ticket.mod_date,
+                                               'categories' : topics})
+
+    def addRevokedTicket(self, ticket, project) :
+        topics = [t for t in project.trove_topic if t]
+        self._updateTicketsStats(topics, 'revoked')
+        self.lastmonth.revokedtickets.append({'datetime'   : ticket.mod_date,
+                                              'categories' : topics})
+        self.checkOldArtifacts()
+
+    def addClosedTicket(self, ticket, project) :
+        topics = [t for t in project.trove_topic if t]
+        s_time=int((datetime.utcnow()-ticket.created_date).total_seconds())
+        self._updateTicketsStats(topics, 'solved', s_time = s_time)
+        self.lastmonth.solvedtickets.append({'datetime'   : ticket.mod_date,
+                                             'categories' : topics,
+                                             'solvingtime': s_time})
+        self.checkOldArtifacts()
+
+    def addCommit(self, newcommit, project) :
+        def _addCommitData(stats, topics, languages, newblob, oldblob = None) :
+            if oldblob : listold = list(oldblob)
+            else : listold = []
+            listnew = list(newblob)
+
+            if oldblob is None : lines = len(listnew)
+            elif newblob.has_html_view :
+                diff = difflib.unified_diff(listold, listnew,
+                         ('old' + oldblob.path()).encode('utf-8'),
+                         ('new' + newblob.path()).encode('utf-8'))
+                lines = len([l for l in diff if len(l) > 0 and l[0] == '+']) - 1
+            else : lines = 0
+            
+            lt = topics + [None]
+            ll = languages + [None]
+            for t in lt :
+                i = getElementIndex(stats.general, category=t) 
+                if i is None :
+                    newstats = {'category' : t,
+                                'commits'  : [],
+                                'tickets'  : {'assigned'       : 0,
+                                              'solved'         : 0,
+                                              'revoked'        : 0,
+                                              'totsolvingtime' : 0},
+                                'messages' : []}   
+                    stats.general.append(newstats)
+                    i = getElementIndex(stats.general, category=t)
+                for lang in ll :
+                    j = getElementIndex(stats.general[i]['commits'], 
+                                        language=lang)
+                    if j is None :
+                        stats.general[i]['commits'].append({'language': lang,
+                                                            'lines'   : lines,
+                                                            'number'  : 1})
+                    else :
+                        stats.general[i]['commits'][j].lines += lines
+                        stats.general[i]['commits'][j].number += 1
+            return lines
+
+        topics = [t for t in project.trove_topic if t]
+        languages = [l for l in project.trove_language if l]
+        now = datetime.utcnow()
+
+        d = newcommit.diffs
+        if len(newcommit.parent_ids) > 0 :
+            oldcommit = newcommit.repo.commit(newcommit.parent_ids[0])
+
+        totlines = 0
+        for changed in d.changed :
+            newblob = newcommit.tree.get_blob_by_path(changed)
+            oldblob = oldcommit.tree.get_blob_by_path(changed)
+            totlines+=_addCommitData(self, topics, languages, newblob, oldblob)
+
+        for copied in d.copied :
+            newblob = newcommit.tree.get_blob_by_path(copied['new'])
+            oldblob = oldcommit.tree.get_blob_by_path(copied['old'])
+            totlines+=_addCommitData(self, topics, languages, newblob, oldblob)
+
+        for added in d.added :
+            newblob = newcommit.tree.get_blob_by_path(added)
+            totlines+=_addCommitData(self, topics, languages, newblob)
+
+        self.lastmonth.commits.append({'datetime' : now,
+                                       'categories' : topics,
+                                       'programming_languages' : languages,
+                                       'lines' : totlines})
+        self.checkOldArtifacts()
+
+    def addLogin(self) :
+        now = datetime.utcnow()
+        self.last_login = now
+        self.tot_logins_count += 1
+        self.lastmonth.logins.append(now)
+        self.checkOldArtifacts()
+        
+    def _updateArtifactsStats(self, art_type, art_datetime, project, action) :
+        if action not in ['created', 'modified'] : return
+        topics = [t for t in project.trove_topic if t]
+        lt = [None] + topics
+        for mtype in [None, art_type] :
+            for t in lt :
+                i = getElementIndex(self.general, category = t)
+                if i is None :
+                    msg = {'category' : t,
+                           'commits'  : [],
+                           'tickets'  : {'solved'         : 0,
+                                         'assigned'       : 0,
+                                         'revoked'        : 0,
+                                         'totsolvingtime' : 0},
+                           'messages' : []}
+                    self.general.append(msg)
+                    i = getElementIndex(self.general, category = t)
+                j = getElementIndex(self.general[i]['messages'], messagetype = mtype)
+                if j is None : 
+                    entry = {'messagetype' : mtype,
+                             'created'     : 0,
+                             'modified'    : 0}
+                    entry[action] += 1
+                    self.general[i]['messages'].append(entry)
+                else : self.general[i]['messages'][j][action] += 1
+
+        self.lastmonth.messages.append({'datetime'   : art_datetime,
+                                        'created'    : action == 'created',
+                                        'categories' : topics,
+                                        'messagetype': art_type})
+        self.checkOldArtifacts() 
+
+    def _updateTicketsStats(self, topics, action, s_time = None) :
+        if action not in ['solved', 'assigned', 'revoked'] : return
+        lt = topics + [None]
+        for t in lt :
+            i = getElementIndex(self.general, category = t)
+            if i is None :
+                stats = {'category' : t,
+                         'commits'  : [],
+                         'tickets'  : {'solved'         : 0,
+                                       'assigned'       : 0,
+                                       'revoked'        : 0,
+                                       'totsolvingtime' : 0},
+                         'messages' : [] }
+                self.general.append(stats)
+                i = getElementIndex(self.general, category = t)
+            self.general[i]['tickets'][action] += 1 
+            if action == 'solved' : 
+                self.general[i]['tickets']['totsolvingtime']+=s_time
+
+def getElementIndex(el_list, **kw) :
+    for i in range(len(el_list)) :
+        for k in kw : 
+            if el_list[i].get(k) != kw[k] : break
+        else : return i
+    return None
+
+def addtuple(l1, l2) :
+    a, b = l1
+    x, y = l2
+    return (a+x, b+y)
+
+def _convertTimeDiff(int_seconds) :
+    if int_seconds is None : return None
+    diff = timedelta(seconds = int_seconds)
+    days, seconds = diff.days, diff.seconds
+    hours = seconds / 3600
+    seconds = seconds % 3600
+    minutes = seconds / 60
+    seconds = seconds % 60
+    return {'days'    : days, 
+            'hours'   : hours, 
+            'minutes' : minutes,
+            'seconds' : seconds}
+
+Mapper.compile_all()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/model/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/__init__.py b/ForgeUserStats/forgeuserstats/model/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/model/stats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/stats.py b/ForgeUserStats/forgeuserstats/model/stats.py
new file mode 100644
index 0000000..6e9d063
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/model/stats.py
@@ -0,0 +1,647 @@
+import pymongo
+from pylons import c, g, request
+
+import bson
+from ming import schema as S
+from ming import Field, Index, collection
+from ming.orm import session, state, Mapper
+from ming.orm import FieldProperty, RelationProperty, ForeignIdProperty
+from ming.orm.declarative import MappedClass
+from datetime import datetime, timedelta
+import difflib
+
+from allura.model.session import main_orm_session
+from allura.lib import helpers as h
+
+class UserStats(MappedClass):
+    class __mongometa__:
+        name='userstats'
+        session = main_orm_session
+        unique_indexes = [ '_id', 'user_id']
+
+    _id=FieldProperty(S.ObjectId)
+
+    registration_date = FieldProperty(datetime)
+    tot_logins_count = FieldProperty(int, if_missing = 0)
+    last_login = FieldProperty(datetime)
+    general = FieldProperty([dict(
+        category = S.ObjectId,
+        messages = [dict(
+            messagetype = str,
+            created = int,
+            modified = int)],
+        tickets = dict(
+            solved = int,
+            assigned = int,
+            revoked = int,
+            totsolvingtime = int),
+        commits = [dict(
+            lines = int,
+            number = int,
+            language = S.ObjectId)])])
+
+    lastmonth=FieldProperty(dict(
+        logins=[datetime],
+            messages=[dict(
+            datetime=datetime,
+            created=bool,
+            categories=[S.ObjectId],
+            messagetype=str)],
+        assignedtickets=[dict(
+            datetime=datetime,
+            categories=[S.ObjectId])],
+        revokedtickets=[dict(
+            datetime=datetime,
+            categories=[S.ObjectId])],
+        solvedtickets=[dict(
+            datetime=datetime,
+            categories=[S.ObjectId],
+            solvingtime=int)],
+        commits=[dict(
+            datetime=datetime,
+            categories=[S.ObjectId],
+            programming_languages=[S.ObjectId],
+            lines=int)]))
+    user_id = FieldProperty(S.ObjectId)
+
+    @classmethod
+    def create(cls, user):
+        stats = cls.query.get(user_id = user._id)
+        if stats:
+            return stats
+        stats = cls(user_id=user._id,
+            registration_date = datetime.utcnow())
+        user.stats_id = stats._id
+        session(stats).flush(stats)
+        session(user).flush(user)
+        return stats
+
+    def getCodeContribution(self):
+        days=(datetime.today() - self.registration_date).days
+        if not days:
+            days=1
+        for val in self['general']:
+            if val['category'] is None:
+                for commits in val['commits']:
+                    if commits['language'] is None: 
+                        if days > 30:
+                            return round(float(commits.lines)/days*30, 2)
+                        else:
+                            return float(commits.lines)
+        return 0
+
+    def getDiscussionContribution(self):
+        days=(datetime.today() - self.registration_date).days
+        if not days:
+            days=1
+        for val in self['general']:
+            if val['category'] is None:
+                for artifact in val['messages']:
+                    if artifact['messagetype'] is None: 
+                        tot = artifact.created+artifact.modified
+                        if days > 30:
+                            return round(float(tot)/days*30,2)
+                        else:
+                            return float(tot)
+        return 0
+
+    def getTicketsContribution(self):
+        for val in self['general']:
+            if val['category'] is None:
+                tickets = val['tickets']
+                if tickets.assigned == 0:
+                    return 0
+                return float(tickets.solved) / tickets.assigned
+        return 0
+
+    @classmethod
+    def getMaxAndAverageCodeContribution(self):
+        lst = list(self.query.find())
+        n = len(lst)
+        if n == 0:
+            return 0, 0
+        maxcontribution=max([x.getCodeContribution() for x in lst])
+        averagecontribution=sum([x.getCodeContribution() for x in lst]) / n
+        return maxcontribution, round(averagecontribution, 2)
+
+    @classmethod
+    def getMaxAndAverageDiscussionContribution(self):
+        lst = list(self.query.find())
+        n = len(lst)
+        if n == 0:
+            return 0, 0
+        maxcontribution=max([x.getDiscussionContribution() for x in lst])
+        averagecontribution=sum([x.getDiscussionContribution() for x in lst])/n
+        return maxcontribution, round(averagecontribution, 2)
+
+    @classmethod
+    def getMaxAndAverageTicketsSolvingPercentage(self):
+        lst = list(self.query.find())
+        n = len(lst)
+        if n == 0:
+            return 0, 0
+        maxcontribution=max([x.getTicketsContribution() for x in lst])
+        averagecontribution=sum([x.getTicketsContribution() for x in lst])/n
+        return maxcontribution, round(averagecontribution, 2)
+
+    def codeRanking(self):
+        lst = list(self.query.find())
+        totn = len(lst)
+        codcontr = self.getCodeContribution()
+        upper = len([x for x in lst if x.getCodeContribution() > codcontr])
+        return round((totn - upper) * 100.0 / totn, 2)
+
+    def discussionRanking(self):
+        lst = list(self.query.find())
+        totn = len(lst)
+        disccontr = self.getDiscussionContribution()
+        upper=len([x for x in lst if x.getDiscussionContribution()>disccontr])
+        return round((totn - upper) * 100.0 / totn, 2)
+
+    def ticketsRanking(self):
+        lst = list(self.query.find())
+        totn = len(lst)
+        ticketscontr = self.getTicketsContribution()
+        upper=len([x for x in lst if x.getTicketsContribution()>ticketscontr])
+        return round((totn - upper) * 100.0 / totn, 2)
+
+    def getCommits(self, category = None):
+        i = getElementIndex(self.general, category = category)
+        if i is None: 
+            return dict(number=0, lines=0)
+        cat = self.general[i]
+        j = getElementIndex(cat.commits, language = None)
+        if j is None:
+            return dict(number=0, lines=0)
+        return dict(
+            number=cat.commits[j]['number'], 
+            lines=cat.commits[j]['lines'])
+
+    def getArtifacts(self, category = None, art_type = None):
+        i = getElementIndex(self.general, category = category)
+        if i is None:
+            return dict(created=0, modified=0)
+        cat = self.general[i]
+        j = getElementIndex(cat.messages, art_type = art_type)
+        if j is None:
+            return dict(created=0, modified=0)
+        return dict(created=cat[j].created, modified=cat[j].modified)
+
+    def getTickets(self, category = None):
+        i = getElementIndex(self.general, category = category)
+        if i is None:
+            return dict(
+                assigned=0,
+                solved=0,
+                revoked=0,
+                averagesolvingtime=None)
+        if self.general[i].tickets.solved > 0:
+            tot = self.general[i].tickets.totsolvingtime 
+            number = self.general[i].tickets.solved
+            average = tot / number
+        else: 
+            average = None
+        return dict(
+            assigned=self.general[i].tickets.assigned,
+            solved=self.general[i].tickets.solved,
+            revoked=self.general[i].tickets.revoked,
+            averagesolvingtime=_convertTimeDiff(average))
+
+    def getCommitsByCategory(self):
+        from allura.model.project import TroveCategory
+
+        by_cat = {}
+        for entry in self.general:
+            cat = entry.category
+            i = getElementIndex(entry.commits, language = None)
+            if i is None: 
+                n, lines = 0, 0
+            else: 
+                n, lines = entry.commits[i].number, entry.commits[i].lines
+            if cat != None:
+                cat = TroveCategory.query.get(_id = cat)
+            by_cat[cat] = dict(number=n, lines=lines)
+        return by_cat
+
+    def getCommitsByLanguage(self):
+        langlist = []
+        by_lang = {}
+        i = getElementIndex(self.general, category=None)
+        if i is None: 
+            return dict(number=0, lines=0)
+        return dict([(el.language, dict(lines=el.lines, number=el.number))
+                     for el in self.general[i].commits])
+
+    def getArtifactsByCategory(self, detailed=False):
+        from allura.model.project import TroveCategory
+
+        by_cat = {}
+        for entry in self.general:
+            cat = entry.category
+            if cat != None: 
+                cat = TroveCategory.query.get(_id = cat)
+            if detailed: 
+                by_cat[cat] = entry.messages
+            else:
+                i = getElementIndex(entry.messages, messagetype=None)
+                if i is not None:
+                    by_cat[cat] = entry.messages[i]
+                else: 
+                    by_cat[cat] = dict(created=0, modified=0)
+        return by_cat
+
+    def getArtifactsByType(self, category=None):
+        i = getElementIndex(self.general, category = category)
+        if i is None: 
+            return {}
+        entry = self.general[i].messages
+        by_type = dict([(el.messagetype, dict(created=el.created,
+                                              modified=el.modified))
+                         for el in entry])
+        return by_type
+
+    def getTicketsByCategory(self):
+        from allura.model.project import TroveCategory
+
+        by_cat = {}
+        for entry in self.general:
+            cat = entry.category
+            if cat != None:
+                cat = TroveCategory.query.get(_id = cat)
+            a, s = entry.tickets.assigned, entry.tickets.solved
+            r, time = entry.tickets.solved, entry.tickets.totsolvingtime
+            if s:
+                average = time / s
+            else:
+                average = None
+            by_cat[cat] = dict(
+                assigned=a,
+                solved=s,
+                revoked=r, 
+                averagesolvingtime=_convertTimeDiff(average))
+        return by_cat
+
+    def getLastMonthCommits(self, category = None):
+        self.checkOldArtifacts() 
+        lineslist = [el.lines for el in self.lastmonth.commits
+                     if category in el.categories + [None]]
+        return dict(number=len(lineslist), lines=sum(lineslist))
+
+    def getLastMonthCommitsByCategory(self):
+        from allura.model.project import TroveCategory
+
+        self.checkOldArtifacts() 
+        seen = set()
+        catlist=[el.category for el in self.general
+                 if el.category not in seen and not seen.add(el.category)]
+
+        by_cat = {}
+        for cat in catlist:
+            lineslist = [el.lines for el in self.lastmonth.commits
+                         if cat in el.categories + [None]]
+            n = len(lineslist)
+            lines = sum(lineslist)
+            if cat != None:
+                cat = TroveCategory.query.get(_id = cat)
+            by_cat[cat] = dict(number=n, lines=lines)
+        return by_cat
+
+    def getLastMonthCommitsByLanguage(self):
+        from allura.model.project import TroveCategory
+
+        self.checkOldArtifacts() 
+        seen = set()
+        langlist=[el.language for el in self.general
+                  if el.language not in seen and not seen.add(el.language)]
+
+        by_lang = {}
+        for lang in langlist:
+            lineslist = [el.lines for el in self.lastmonth.commits
+                         if lang in el.programming_languages + [None]]
+            n = len(lineslist)
+            lines = sum(lineslist)
+            if lang != None:
+                lang = TroveCategory.query.get(_id = lang)
+            by_lang[lang] = dict(number=n, lines=lines)
+        return by_lang
+
+    def getLastMonthArtifacts(self, category = None):
+        self.checkOldArtifacts() 
+        cre, mod = reduce(addtuple, [(int(el.created),1-int(el.created))
+                                     for el in self.lastmonth.messages
+                                     if category is None or 
+                                        category in el.categories], (0,0))
+        return dict(created=cre, modified=mod)
+
+    def getLastMonthArtifactsByType(self, category = None):
+        self.checkOldArtifacts()
+        seen = set()
+        types=[el.messagetype for el in self.lastmonth.messages
+               if el.messagetype not in seen and not seen.add(el.messagetype)]
+
+        by_type = {}
+        for t in types:
+            cre, mod = reduce(
+                addtuple, 
+                [(int(el.created),1-int(el.created))
+                 for el in self.lastmonth.messages
+                 if el.messagetype == t and
+                 category in [None]+el.categories],
+                (0,0))
+            by_type[t] = dict(created=cre, modified=mod)
+        return by_type
+
+    def getLastMonthArtifactsByCategory(self):
+        from allura.model.project import TroveCategory
+
+        self.checkOldArtifacts() 
+        seen = set()
+        catlist=[el.category for el in self.general
+                 if el.category not in seen and not seen.add(el.category)]
+
+        by_cat = {}
+        for cat in catlist:
+            cre, mod = reduce(
+                addtuple, 
+                [(int(el.created),1-int(el.created))
+                 for el in self.lastmonth.messages 
+                 if cat in el.categories + [None]], (0,0))
+            if cat != None:
+                cat = TroveCategory.query.get(_id = cat)
+            by_cat[cat] = dict(created=cre, modified=mod)
+        return by_cat
+
+    def getLastMonthTickets(self, category = None):
+        from allura.model.project import TroveCategory
+
+        self.checkOldArtifacts()
+        a = len([el for el in self.lastmonth.assignedtickets
+                 if category in el.categories + [None]])
+        r = len([el for el in self.lastmonth.revokedtickets
+                 if category in el.categories + [None]])
+        s, time = reduce(
+            addtuple, 
+            [(1, el.solvingtime)
+             for el in self.lastmonth.solvedtickets
+             if category in el.categories + [None]],
+            (0,0))
+        if category!=None:
+            category = TroveCategory.query.get(_id=category)
+        if s > 0:
+            time = time / s
+        else:
+            time = None
+        return dict(
+            assigned=a,
+            revoked=r,
+            solved=s, 
+            averagesolvingtime=_convertTimeDiff(time))
+        
+    def getLastMonthTicketsByCategory(self):
+        from allura.model.project import TroveCategory
+
+        self.checkOldArtifacts()
+        seen = set()
+        catlist=[el.category for el in self.general
+                 if el.category not in seen and not seen.add(el.category)]
+        by_cat = {}
+        for cat in catlist:
+            a = len([el for el in self.lastmonth.assignedtickets
+                     if cat in el.categories + [None]])
+            r = len([el for el in self.lastmonth.revokedtickets
+                     if cat in el.categories + [None]])
+            s, time = reduce(addtuple, [(1, el.solvingtime)
+                                        for el in self.lastmonth.solvedtickets
+                                        if cat in el.categories+[None]],(0,0))
+            if cat != None:
+                cat = TroveCategory.query.get(_id = cat)
+            if s > 0: 
+                time = time / s
+            else:
+                time = None
+            by_cat[cat] = dict(
+                assigned=a,
+                revoked=r,
+                solved=s, 
+                averagesolvingtime=_convertTimeDiff(time))
+        return by_cat
+        
+    def getLastMonthLogins(self):
+        self.checkOldArtifacts()
+        return len(self.lastmonth.logins)
+
+    def checkOldArtifacts(self):
+        now = datetime.now()
+        for m in self.lastmonth.messages:
+            if now - m.datetime > timedelta(30):
+                self.lastmonth.messages.remove(m)
+        for t in self.lastmonth.assignedtickets:
+            if now - t.datetime > timedelta(30):
+                self.lastmonth.assignedtickets.remove(t)
+        for t in self.lastmonth.revokedtickets:
+            if now - t.datetime > timedelta(30):
+                self.lastmonth.revokedtickets.remove(t)
+        for t in self.lastmonth.solvedtickets:
+            if now - t.datetime > timedelta(30):
+                self.lastmonth.solvedtickets.remove(t)
+
+    def addNewArtifact(self, art_type, art_datetime, project):
+        self._updateArtifactsStats(art_type, art_datetime, project, "created")
+
+    def addModifiedArtifact(self, art_type, art_datetime, project):
+        self._updateArtifactsStats(art_type, art_datetime, project, "modified")
+
+    def addAssignedTicket(self, ticket, project):
+        topics = [t for t in project.trove_topic if t]
+        self._updateTicketsStats(topics, 'assigned')
+        self.lastmonth.assignedtickets.append(
+            dict(datetime=ticket.mod_date, categories=topics))
+
+    def addRevokedTicket(self, ticket, project):
+        topics = [t for t in project.trove_topic if t]
+        self._updateTicketsStats(topics, 'revoked')
+        self.lastmonth.revokedtickets.append(
+            dict(datetime=ticket.mod_date, categories=topics))
+        self.checkOldArtifacts()
+
+    def addClosedTicket(self, ticket, project):
+        topics = [t for t in project.trove_topic if t]
+        s_time=int((datetime.utcnow()-ticket.created_date).total_seconds())
+        self._updateTicketsStats(topics, 'solved', s_time = s_time)
+        self.lastmonth.solvedtickets.append(dict(
+            datetime=ticket.mod_date,
+            categories=topics,
+            solvingtime=s_time))
+        self.checkOldArtifacts()
+
+    def addCommit(self, newcommit, project):
+        def _addCommitData(stats, topics, languages, newblob, oldblob = None):
+            if oldblob:
+                listold = list(oldblob)
+            else:
+                listold = []
+            listnew = list(newblob)
+
+            if oldblob is None:
+                lines = len(listnew)
+            elif newblob.has_html_view:
+                diff = difflib.unified_diff(
+                    listold, listnew,
+                    ('old' + oldblob.path()).encode('utf-8'),
+                    ('new' + newblob.path()).encode('utf-8'))
+                lines = len([l for l in diff if len(l) > 0 and l[0] == '+'])-1
+            else:
+                lines = 0
+            
+            lt = topics + [None]
+            ll = languages + [None]
+            for t in lt:
+                i = getElementIndex(stats.general, category=t) 
+                if i is None:
+                    newstats = dict(
+                        category=t,
+                        commits=[],
+                        messages=dict(
+                            assigned=0,
+                            solved=0,
+                            revoked=0,
+                            totsolvingtime=0),
+                        tickets=[])   
+                    stats.general.append(newstats)
+                    i = getElementIndex(stats.general, category=t)
+                for lang in ll:
+                    j = getElementIndex(
+                        stats.general[i]['commits'], language=lang)
+                    if j is None:
+                        stats.general[i]['commits'].append(dict(
+                            language=lang, lines=lines, number=1))
+                    else:
+                        stats.general[i]['commits'][j].lines += lines
+                        stats.general[i]['commits'][j].number += 1
+            return lines
+
+        topics = [t for t in project.trove_topic if t]
+        languages = [l for l in project.trove_language if l]
+        now = datetime.utcnow()
+
+        d = newcommit.diffs
+        if len(newcommit.parent_ids) > 0:
+            oldcommit = newcommit.repo.commit(newcommit.parent_ids[0])
+
+        totlines = 0
+        for changed in d.changed:
+            newblob = newcommit.tree.get_blob_by_path(changed)
+            oldblob = oldcommit.tree.get_blob_by_path(changed)
+            totlines+=_addCommitData(self, topics, languages, newblob, oldblob)
+
+        for copied in d.copied:
+            newblob = newcommit.tree.get_blob_by_path(copied['new'])
+            oldblob = oldcommit.tree.get_blob_by_path(copied['old'])
+            totlines+=_addCommitData(self, topics, languages, newblob, oldblob)
+
+        for added in d.added:
+            newblob = newcommit.tree.get_blob_by_path(added)
+            totlines+=_addCommitData(self, topics, languages, newblob)
+
+        self.lastmonth.commits.append(dict(
+            datetime=now, 
+            categories=topics, 
+            programming_languages=languages,
+            lines=totlines))
+        self.checkOldArtifacts()
+
+    def addLogin(self):
+        now = datetime.utcnow()
+        self.last_login = now
+        self.tot_logins_count += 1
+        self.lastmonth.logins.append(now)
+        self.checkOldArtifacts()
+        
+    def _updateArtifactsStats(self, art_type, art_datetime, project, action):
+        if action not in ['created', 'modified']: 
+            return
+        topics = [t for t in project.trove_topic if t]
+        lt = [None] + topics
+        for mtype in [None, art_type]:
+            for t in lt:
+                i = getElementIndex(self.general, category = t)
+                if i is None:
+                    msg = dict(
+                        category=t,
+                        commits=[],
+                        tickets=dict(
+                            solved=0,
+                            assigned=0,
+                            revoked=0,
+                            totsolvingtime=0),
+                        messages=[])
+                    self.general.append(msg)
+                    i = getElementIndex(self.general, category = t)
+                j = getElementIndex(
+                    self.general[i]['messages'], messagetype=mtype)
+                if j is None:
+                    entry = dict(messagetype=mtype, created=0, modified=0)
+                    entry[action] += 1
+                    self.general[i]['messages'].append(entry)
+                else:
+                    self.general[i]['messages'][j][action] += 1
+
+        self.lastmonth.messages.append(dict(
+            datetime=art_datetime,
+            created=(action == 'created'),
+            categories=topics,
+            messagetype=art_type))
+        self.checkOldArtifacts() 
+
+    def _updateTicketsStats(self, topics, action, s_time = None):
+        if action not in ['solved', 'assigned', 'revoked']:
+            return
+        lt = topics + [None]
+        for t in lt:
+            i = getElementIndex(self.general, category = t)
+            if i is None:
+                stats = dict(
+                    category=t,
+                    commits=[],
+                    tickets=dict(
+                        solved=0,
+                        assigned=0,
+                        revoked=0,
+                        totsolvingtime=0),
+                    messages=[])
+                self.general.append(stats)
+                i = getElementIndex(self.general, category = t)
+            self.general[i]['tickets'][action] += 1 
+            if action == 'solved': 
+                self.general[i]['tickets']['totsolvingtime']+=s_time
+
+def getElementIndex(el_list, **kw):
+    for i in range(len(el_list)):
+        for k in kw:
+            if el_list[i].get(k) != kw[k]:
+                break
+        else:
+            return i
+    return None
+
+def addtuple(l1, l2):
+    a, b = l1
+    x, y = l2
+    return (a+x, b+y)
+
+def _convertTimeDiff(int_seconds):
+    if int_seconds is None:
+        return None
+    diff = timedelta(seconds = int_seconds)
+    days, seconds = diff.days, diff.seconds
+    hours = seconds / 3600
+    seconds = seconds % 3600
+    minutes = seconds / 60
+    seconds = seconds % 60
+    return dict(
+        days=days, 
+        hours=hours, 
+        minutes=minutes,
+        seconds=seconds)
+
+Mapper.compile_all()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/templates/.svn/all-wcprops
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/.svn/all-wcprops b/ForgeUserStats/forgeuserstats/templates/.svn/all-wcprops
new file mode 100644
index 0000000..efae2aa
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/templates/.svn/all-wcprops
@@ -0,0 +1,29 @@
+K 25
+svn:wc:ra_dav:version-url
+V 62
+/svn/allura/!svn/ver/1/ForgeUserStats/forgeuserstats/templates
+END
+commits.html
+K 25
+svn:wc:ra_dav:version-url
+V 75
+/svn/allura/!svn/ver/1/ForgeUserStats/forgeuserstats/templates/commits.html
+END
+artifacts.html
+K 25
+svn:wc:ra_dav:version-url
+V 77
+/svn/allura/!svn/ver/1/ForgeUserStats/forgeuserstats/templates/artifacts.html
+END
+tickets.html
+K 25
+svn:wc:ra_dav:version-url
+V 75
+/svn/allura/!svn/ver/1/ForgeUserStats/forgeuserstats/templates/tickets.html
+END
+index.html
+K 25
+svn:wc:ra_dav:version-url
+V 73
+/svn/allura/!svn/ver/1/ForgeUserStats/forgeuserstats/templates/index.html
+END

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/91bac4a4/ForgeUserStats/forgeuserstats/templates/.svn/entries
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/.svn/entries b/ForgeUserStats/forgeuserstats/templates/.svn/entries
new file mode 100644
index 0000000..ef7dfdb
--- /dev/null
+++ b/ForgeUserStats/forgeuserstats/templates/.svn/entries
@@ -0,0 +1,164 @@
+10
+
+dir
+4
+https://xp-dev.com/svn/allura/ForgeUserStats/forgeuserstats/templates
+https://xp-dev.com/svn/allura
+
+
+
+2012-10-17T19:55:53.450112Z
+1
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+46ed536d-f66c-413e-a53e-834384f708db
+
+tickets.html
+file
+
+
+
+
+2012-11-05T14:43:25.725756Z
+4bac229c573965dbfd312e65cc7313a2
+2012-10-17T19:55:53.450112Z
+1
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1361
+
+index.html
+file
+
+
+
+
+2012-11-05T14:43:25.725756Z
+036136344f0b3099f212c6c749431996
+2012-10-17T19:55:53.450112Z
+1
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+11126
+
+commits.html
+file
+
+
+
+
+2012-11-05T14:43:25.725756Z
+cbfcdaeb670c8896e31071077c51eb23
+2012-10-17T19:55:53.450112Z
+1
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+955
+
+artifacts.html
+file
+
+
+
+
+2012-11-05T14:43:25.725756Z
+bb6c7ceabf56de25d177ee5cd52451ab
+2012-10-17T19:55:53.450112Z
+1
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1386
+


[08/42] git commit: [5453] replace datetime.now with datetime.utcnow

Posted by br...@apache.org.
[5453] replace datetime.now with datetime.utcnow


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/b77d3383
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/b77d3383
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/b77d3383

Branch: refs/heads/db/5453
Commit: b77d33837ecfe34528d8049b5eeb3740e40ada3b
Parents: b9c46c6
Author: Stefano Invernizzi <st...@apache.org>
Authored: Thu Jan 10 15:41:14 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:30 2013 +0000

----------------------------------------------------------------------
 ForgeUserStats/forgeuserstats/model/stats.py |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/b77d3383/ForgeUserStats/forgeuserstats/model/stats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/model/stats.py b/ForgeUserStats/forgeuserstats/model/stats.py
index 6e9d063..c622888 100644
--- a/ForgeUserStats/forgeuserstats/model/stats.py
+++ b/ForgeUserStats/forgeuserstats/model/stats.py
@@ -431,7 +431,7 @@ class UserStats(MappedClass):
         return len(self.lastmonth.logins)
 
     def checkOldArtifacts(self):
-        now = datetime.now()
+        now = datetime.utcnow()
         for m in self.lastmonth.messages:
             if now - m.datetime > timedelta(30):
                 self.lastmonth.messages.remove(m)


[18/42] git commit: [5453] Removed wrong message in userstats Web page

Posted by br...@apache.org.
[5453] Removed wrong message in userstats Web page


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/bb4a3397
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/bb4a3397
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/bb4a3397

Branch: refs/heads/db/5453
Commit: bb4a339701ffcb53bcc867aab6ee72c145487848
Parents: 8cc0f25
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Jan 30 15:10:59 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:31 2013 +0000

----------------------------------------------------------------------
 ForgeUserStats/forgeuserstats/templates/index.html |    2 --
 1 files changed, 0 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bb4a3397/ForgeUserStats/forgeuserstats/templates/index.html
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/templates/index.html b/ForgeUserStats/forgeuserstats/templates/index.html
index 502cb25..f5a71c6 100644
--- a/ForgeUserStats/forgeuserstats/templates/index.html
+++ b/ForgeUserStats/forgeuserstats/templates/index.html
@@ -366,8 +366,6 @@
           <p>
             <img src="/userstats/{{user.username}}/categories_graph"/>
           </p>
-        {% else %}
-          The following table shows the number projects tagged as belonging to each single category in which this user is involved.
         {% endif %}
     {% endif %}
     {% if category %}


[42/42] git commit: [#5453] make Profile tool be anchored before userstats, and fixes:

Posted by br...@apache.org.
[#5453] make Profile tool be anchored before userstats, and fixes:

* make profile icon show up properly everywhere
* make profile tool show up in menus properly
* remove special-casing for the profile tool being first; relies
  on anchored feature now
* update first_mount() to remove simple case (no auth check), to check
  is_visible_to so it is consistent with other menu checks, and to
  remove invalid old ForgeWikiApp type check


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/114ac9c0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/114ac9c0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/114ac9c0

Branch: refs/heads/db/5453
Commit: 114ac9c02a189867317c328d4e78374570b75ecc
Parents: eaa4420
Author: Dave Brondsema <db...@geek.net>
Authored: Mon Mar 25 16:09:11 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:56:51 2013 +0000

----------------------------------------------------------------------
 Allura/allura/controllers/project.py               |    6 +--
 Allura/allura/ext/user_profile/user_main.py        |   17 ++++++---
 Allura/allura/model/project.py                     |   28 ++++-----------
 Allura/allura/nf/allura/css/allura.css             |    6 ++--
 Allura/allura/nf/allura/css/site_style.css         |    2 +-
 .../allura/tests/functional/test_neighborhood.py   |    2 +-
 Allura/allura/websetup/bootstrap.py                |    2 +-
 7 files changed, 27 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/114ac9c0/Allura/allura/controllers/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/project.py b/Allura/allura/controllers/project.py
index ebeba77..8a63f21 100644
--- a/Allura/allura/controllers/project.py
+++ b/Allura/allura/controllers/project.py
@@ -100,7 +100,7 @@ class NeighborhoodController(object):
         if self.neighborhood.redirect:
             redirect(self.neighborhood.redirect)
         if not self.neighborhood.has_home_tool:
-            mount = c.project.first_mount()
+            mount = c.project.ordered_mounts()[0]
             if mount is not None:
                 if 'ac' in mount:
                     redirect(mount['ac'].options.mount_point + '/')
@@ -318,7 +318,7 @@ class ProjectController(object):
     @expose()
     @with_trailing_slash
     def index(self, **kw):
-        mount = c.project.first_mount('read')
+        mount = c.project.first_mount_visible(c.user)
         activity_enabled = config.get('activitystream.enabled', False)
         activity_enabled = request.cookies.get('activitystream.enabled', activity_enabled)
         activity_enabled = asbool(activity_enabled)
@@ -329,8 +329,6 @@ class ProjectController(object):
                 redirect(mount['ac'].options.mount_point + '/')
             elif 'sub' in mount:
                 redirect(mount['sub'].url())
-        elif c.project.app_instance('profile'):
-            redirect('profile/')
         else:
             redirect(c.project.app_configs[0].options.mount_point + '/')
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/114ac9c0/Allura/allura/ext/user_profile/user_main.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/user_profile/user_main.py b/Allura/allura/ext/user_profile/user_main.py
index 7754228..907271a 100644
--- a/Allura/allura/ext/user_profile/user_main.py
+++ b/Allura/allura/ext/user_profile/user_main.py
@@ -9,7 +9,7 @@ from tg import expose, redirect, validate, response
 from webob import exc
 
 from allura import version
-from allura.app import Application
+from allura.app import Application, SitemapEntry
 from allura.lib import helpers as h
 from allura.lib.helpers import DateTimeConverter
 from allura.lib.security import require_access
@@ -23,10 +23,11 @@ log = logging.getLogger(__name__)
 class UserProfileApp(Application):
     __version__ = version.__version__
     installable = False
+    tool_label = 'Profile'
     icons={
-        24:'images/sftheme/24x24/home_24.png',
-        32:'images/sftheme/32x32/home_32.png',
-        48:'images/sftheme/48x48/home_48.png'
+        24:'images/home_24.png',
+        32:'images/home_32.png',
+        48:'images/home_48.png'
     }
 
     def __init__(self, user, config):
@@ -38,11 +39,17 @@ class UserProfileApp(Application):
     @property
     @h.exceptionless([], log)
     def sitemap(self):
-        return []
+        return [SitemapEntry('Profile', '.')]
 
     def admin_menu(self):
         return []
 
+    def main_menu(self):
+        return [SitemapEntry('Profile', '.')]
+
+    def is_visible_to(self, user):
+        return True
+
     def install(self, project):
         pr = c.user.project_role()
         if pr:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/114ac9c0/Allura/allura/model/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 9f07f67..4272345 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -401,11 +401,6 @@ class Project(MappedClass, ActivityNode, ActivityObject):
         delta_ordinal = i
         max_ordinal = i
 
-        if self.is_user_project:
-            entries.append({'ordinal': delta_ordinal, 'entry':SitemapEntry('Profile', "%sprofile/" % self.url(), ui_icon="tool-home")})
-            max_ordinal = delta_ordinal
-            delta_ordinal = delta_ordinal + 1
-
         for sub in self.direct_subprojects:
             ordinal = sub.ordinal + delta_ordinal
             if ordinal > max_ordinal:
@@ -619,26 +614,17 @@ class Project(MappedClass, ActivityNode, ActivityObject):
                 result.append({'ordinal': int(ordinal), 'ac': ac, 'rank': rank})
         return sorted(result, key=lambda e: (e['ordinal'], e['rank']))
 
-    def first_mount(self, required_access=None):
-        '''Returns the first (toolbar order) mount, or the first mount to
-        which the user has the required access.'''
-        from forgewiki.wiki_main import ForgeWikiApp
+    def first_mount_visible(self, user):
         mounts = self.ordered_mounts()
-        if self.is_user_project:
-            for mount in mounts:
-                if 'ac' in mount and mount['ac'].tool_name == 'profile':
-                    return mount
-        if mounts and required_access is None:
-            return mounts[0]
         for mount in mounts:
             if 'sub' in mount:
-                obj = mount['sub']
+                sub = mount['sub']
+                if has_access(sub, 'read', user):
+                    return mount
             elif 'ac' in mount:
-                obj = self.app_instance(mount['ac'])
-            else:
-                continue
-            if has_access(obj, required_access) or isinstance(obj, ForgeWikiApp):
-                return mount
+                app = self.app_instance(mount['ac'])
+                if app.is_visible_to(user):
+                    return mount
         return None
 
     def next_mount_point(self, include_hidden=False):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/114ac9c0/Allura/allura/nf/allura/css/allura.css
----------------------------------------------------------------------
diff --git a/Allura/allura/nf/allura/css/allura.css b/Allura/allura/nf/allura/css/allura.css
index f3863b8..613caa7 100644
--- a/Allura/allura/nf/allura/css/allura.css
+++ b/Allura/allura/nf/allura/css/allura.css
@@ -36,7 +36,7 @@ b.ico.ico-vote-up { background-image: url('../images/vote_up.png'); }
 b.ico.ico-vote-down { background-image: url('../images/vote_down.png'); }
 
 
-.ui-icon-tool-home {
+.ui-icon-tool-home, .ui-icon-tool-profile {
   background-image: url("../images/home_24.png");
   background-repeat: no-repeat;
 }
@@ -106,7 +106,7 @@ b.ico.ico-vote-down { background-image: url('../images/vote_down.png'); }
   background-repeat: no-repeat;
 }
 
-#top_nav .ui-icon-tool-home {
+#top_nav .ui-icon-tool-home, #top_nav .ui-icon-tool-profile {
   background-image: url("../images/home_32.png");
 }
 #top_nav .ui-icon-tool-wiki {
@@ -149,7 +149,7 @@ b.ico.ico-vote-down { background-image: url('../images/vote_down.png'); }
   background-image: url("../images/chat_32.png");
 }
 
-.big_icon.ui-icon-tool-home {
+.big_icon.ui-icon-tool-home, .big_icon.ui-icon-tool-profile {
   background-image: url("../images/home_48.png");
 }
 .big_icon.ui-icon-tool-wiki {

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/114ac9c0/Allura/allura/nf/allura/css/site_style.css
----------------------------------------------------------------------
diff --git a/Allura/allura/nf/allura/css/site_style.css b/Allura/allura/nf/allura/css/site_style.css
index cc503f4..2f96715 100644
--- a/Allura/allura/nf/allura/css/site_style.css
+++ b/Allura/allura/nf/allura/css/site_style.css
@@ -2216,7 +2216,7 @@ div.attachment_thumb .file_type span {
 }
 
 /* forge tool icons */
-.ui-icon-tool-home {
+.ui-icon-tool-home, .ui-icon-tool-profile {
   background-repeat: no-repeat;
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/114ac9c0/Allura/allura/tests/functional/test_neighborhood.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_neighborhood.py b/Allura/allura/tests/functional/test_neighborhood.py
index 9c86a67..23e14ff 100644
--- a/Allura/allura/tests/functional/test_neighborhood.py
+++ b/Allura/allura/tests/functional/test_neighborhood.py
@@ -806,7 +806,7 @@ class TestNeighborhood(TestController):
     @td.with_user_project('test-user')
     def test_profile_topnav_menu(self):
         r = self.app.get('/u/test-user/', extra_environ=dict(username='test-user')).follow()
-        assert '<a href="/u/test-user/profile/" class="ui-icon-tool-home">' in r
+        assert '<a href="/u/test-user/profile/" class="ui-icon-tool-profile">' in r, r
 
     def test_user_project_creates_on_demand(self):
         M.User.register(dict(username='donald-duck'), make_project=False)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/114ac9c0/Allura/allura/websetup/bootstrap.py
----------------------------------------------------------------------
diff --git a/Allura/allura/websetup/bootstrap.py b/Allura/allura/websetup/bootstrap.py
index 747504c..90c8e62 100644
--- a/Allura/allura/websetup/bootstrap.py
+++ b/Allura/allura/websetup/bootstrap.py
@@ -88,7 +88,7 @@ def bootstrap(command, conf, vars):
                                               google_analytics = False))
     n_users = M.Neighborhood(name='Users', url_prefix='/u/',
                              shortname_prefix='u/',
-                             anchored_tools='userstats:Statistics',
+                             anchored_tools='profile:Profile,userstats:Statistics',
                              features=dict(private_projects = True,
                                            max_projects = None,
                                            css = 'none',


[05/42] git commit: [5453] added missing import

Posted by br...@apache.org.
[5453] added missing import


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/af203729
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/af203729
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/af203729

Branch: refs/heads/db/5453
Commit: af2037297840a7d611e0e7b448032df3043494b8
Parents: 91bac4a
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Dec 12 22:32:29 2012 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:30 2013 +0000

----------------------------------------------------------------------
 Allura/allura/model/repo_refresh.py |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/af203729/Allura/allura/model/repo_refresh.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repo_refresh.py b/Allura/allura/model/repo_refresh.py
index 5e4f21f..1993f57 100644
--- a/Allura/allura/model/repo_refresh.py
+++ b/Allura/allura/model/repo_refresh.py
@@ -19,6 +19,7 @@ from allura.model.repo import CommitDoc, TreeDoc, TreesDoc, DiffInfoDoc
 from allura.model.repo import LastCommitDoc, CommitRunDoc
 from allura.model.repo import Commit, Tree, LastCommit, ModelCache
 from allura.model.index import ArtifactReferenceDoc, ShortlinkDoc
+from allura.model.auth import User
 
 log = logging.getLogger(__name__)
 


[12/42] git commit: [5453] Implemented loop to load other listeners updating stats

Posted by br...@apache.org.
[5453] Implemented loop to load other listeners updating stats


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/04bc59fa
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/04bc59fa
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/04bc59fa

Branch: refs/heads/db/5453
Commit: 04bc59fabce17351c92aa5a1467c7462cfe182a0
Parents: cd82457
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Jan 30 17:03:56 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:31 2013 +0000

----------------------------------------------------------------------
 Allura/allura/lib/app_globals.py |    8 ++++----
 Allura/development.ini           |    4 +++-
 2 files changed, 7 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/04bc59fa/Allura/allura/lib/app_globals.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index 71426a6..632a29f 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -172,12 +172,12 @@ class Globals(object):
         # Zarkov logger
         self._zarkov = None
 
-        self.show_userstats = config.get('user.stats.enable','false')=='true'
+        self.show_userstats = config.get('userstats.enable','false')=='true'
         # Set listeners to update stats
         statslisteners = []
-        ep = self.entry_points['stats'].get('userstats')
-        if self.show_userstats and ep:
-            statslisteners.append(ep().listener)
+        for name, ep in self.entry_points['stats'].iteritems():
+            if config.get('%s.enable' % name,'false')=='true':
+                statslisteners.append(ep().listener)
         self.statsUpdater = PostEvent(statslisteners)
 
     @LazyProperty

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/04bc59fa/Allura/development.ini
----------------------------------------------------------------------
diff --git a/Allura/development.ini b/Allura/development.ini
index 7d2bbe0..68821e4 100644
--- a/Allura/development.ini
+++ b/Allura/development.ini
@@ -128,7 +128,9 @@ trovecategories.enableediting = true
 
 # If set to false, the stats of the user are not
 # updated and they are not shown to users.
-user.stats.enable = true
+# Note: the name of the parameter has to be the same
+#       of the entry point, followed by .enable
+userstats.enable = true
 
 # ActivityStream
 activitystream.master = mongodb://127.0.0.1:27017


[11/42] git commit: [5453] Added some tests

Posted by br...@apache.org.
[5453] Added some tests


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/61f53eb5
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/61f53eb5
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/61f53eb5

Branch: refs/heads/db/5453
Commit: 61f53eb51111bfa39371417b2b739f21348fff61
Parents: 102d0e7
Author: Stefano Invernizzi <st...@apache.org>
Authored: Fri Jan 11 19:07:48 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:30 2013 +0000

----------------------------------------------------------------------
 run_tests |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/61f53eb5/run_tests
----------------------------------------------------------------------
diff --git a/run_tests b/run_tests
index eb2bf3b..80a3079 100755
--- a/run_tests
+++ b/run_tests
@@ -22,6 +22,7 @@ if [ "$TEST_MODULES"  == "" ]; then
     ForgeWiki \
     ForgeActivity \
     ForgeShortUrl \
+    ForgeUserStats \
     "
 fi
 


[23/42] git commit: Fix matplotlib version; 1.1.1rc no longer on pypi

Posted by br...@apache.org.
Fix matplotlib version; 1.1.1rc no longer on pypi


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/597b1b3c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/597b1b3c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/597b1b3c

Branch: refs/heads/db/5453
Commit: 597b1b3c5986cdfdbf40b1b44d52c7e3b1a04e7f
Parents: d2cbb4b
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Mon Feb 25 21:55:16 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:32 2013 +0000

----------------------------------------------------------------------
 requirements-common.txt |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/597b1b3c/requirements-common.txt
----------------------------------------------------------------------
diff --git a/requirements-common.txt b/requirements-common.txt
index 5656d40..f374286 100644
--- a/requirements-common.txt
+++ b/requirements-common.txt
@@ -48,7 +48,7 @@ WebOb==1.0.8
 # part of the stdlib, but with a version number.  see http://guide.python-distribute.org/pip.html#listing-installed-packages
 wsgiref==0.1.2
 numpy==1.6.1
-matplotlib==1.1.1rc
+matplotlib==1.1.1
 
 # tg2 deps (not used directly)
 Babel==0.9.6


[28/42] git commit: [5453] Fixed bug in code to hide stats

Posted by br...@apache.org.
[5453] Fixed bug in code to hide stats


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/d2cbb4bb
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/d2cbb4bb
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/d2cbb4bb

Branch: refs/heads/db/5453
Commit: d2cbb4bb7a675739a58d5be26314ba73eeb75411
Parents: 81fec58
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sat Feb 23 12:06:17 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:32 2013 +0000

----------------------------------------------------------------------
 .../forgeuserstats/controllers/userstats.py        |    2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/d2cbb4bb/ForgeUserStats/forgeuserstats/controllers/userstats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/controllers/userstats.py b/ForgeUserStats/forgeuserstats/controllers/userstats.py
index 3944557..c77dded 100644
--- a/ForgeUserStats/forgeuserstats/controllers/userstats.py
+++ b/ForgeUserStats/forgeuserstats/controllers/userstats.py
@@ -173,6 +173,7 @@ class ForgeUserStatsMetricController(BaseController):
     def artifacts(self, **kw):
         if not self.user:
             return dict(user=None)
+        stats = self.user.stats
         if (not stats.visible) and (c.user != self.user):
             return dict(user=self.user)
 
@@ -185,6 +186,7 @@ class ForgeUserStatsMetricController(BaseController):
     def tickets(self, **kw):
         if not self.user: 
             return dict(user=None)
+        stats = self.user.stats
         if (not stats.visible) and (c.user != self.user):
             return dict(user=self.user)
 


[19/42] git commit: [5453] Improved functional tests

Posted by br...@apache.org.
[5453] Improved functional tests


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/8cc0f252
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/8cc0f252
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/8cc0f252

Branch: refs/heads/db/5453
Commit: 8cc0f252f73bc80cda9f4605a591b11c968055fb
Parents: 169e9d9
Author: Stefano Invernizzi <st...@apache.org>
Authored: Tue Jan 22 11:20:26 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Mar 28 16:53:31 2013 +0000

----------------------------------------------------------------------
 ForgeUserStats/forgeuserstats/tests/test_stats.py |   67 ++++++++++------
 1 files changed, 42 insertions(+), 25 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8cc0f252/ForgeUserStats/forgeuserstats/tests/test_stats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/tests/test_stats.py b/ForgeUserStats/forgeuserstats/tests/test_stats.py
index e4954b7..4389cd7 100644
--- a/ForgeUserStats/forgeuserstats/tests/test_stats.py
+++ b/ForgeUserStats/forgeuserstats/tests/test_stats.py
@@ -16,6 +16,11 @@ from forgetracker import model as TM
 
 class TestStats(TestController):
 
+    def setUp(self):
+        super(TestStats, self).setUp()
+        p = M.Project.query.get(shortname='test')
+        p.add_user(M.User.by_username('test-user'), ['Admin'])
+
     def test_login(self):
         user = User.by_username('test-user')
         init_logins = c.user.stats.tot_logins_count
@@ -25,15 +30,14 @@ class TestStats(TestController):
         assert user.stats.tot_logins_count == 1 + init_logins
         assert user.stats.getLastMonthLogins() == 1 + init_logins
 
-    @td.with_user_project('test-admin')
-    @td.with_wiki
+    @td.with_tool('test', 'wiki', mount_point='wiki', mount_label='wiki', username='test-admin')
     def test_wiki_stats(self):
         initial_artifacts = c.user.stats.getArtifacts()
         initial_wiki = c.user.stats.getArtifacts(art_type="Wiki")
 
-        h.set_context('test', 'wiki', neighborhood='Projects')
-        page = WM.Page(title="TestPage", text="some text")
-        page.commit()
+        self.app.post('/wiki/TestPage/update', 
+            params=dict(title='TestPage', text='some text'),
+            extra_environ=dict(username=str(c.user.username)))
 
         artifacts = c.user.stats.getArtifacts()
         wiki = c.user.stats.getArtifacts(art_type="Wiki")
@@ -43,8 +47,9 @@ class TestStats(TestController):
         assert wiki['created'] == 1 + initial_wiki['created']
         assert wiki['modified'] == initial_wiki['modified']
 
-        page = WM.Page(title="TestPage2", text="some different text")
-        page.commit()
+        self.app.post('/wiki/TestPage2/update', 
+            params=dict(title='TestPage2', text='some text'),
+            extra_environ=dict(username=str(c.user.username)))
 
         artifacts = c.user.stats.getArtifacts()
         wiki = c.user.stats.getArtifacts(art_type="Wiki")
@@ -54,9 +59,9 @@ class TestStats(TestController):
         assert wiki['created'] == 2 + initial_wiki['created']
         assert wiki['modified'] == initial_wiki['modified']
 
-
-        page.text="some modified text"
-        page.commit()
+        self.app.post('/wiki/TestPage2/update', 
+            params=dict(title='TestPage2', text='some modified text'),
+            extra_environ=dict(username=str(c.user.username)))
 
         artifacts = c.user.stats.getArtifacts()
         wiki = c.user.stats.getArtifacts(art_type="Wiki")
@@ -66,16 +71,17 @@ class TestStats(TestController):
         assert wiki['created'] == 2 + initial_wiki['created']
         assert wiki['modified'] == 1 + initial_wiki['modified']
 
-
-    @td.with_user_project('test-admin')
-    @td.with_tracker
+    @td.with_tool('test', 'tickets', mount_point='tickets', mount_label='tickets', username='test-admin')
     def test_tracker_stats(self):
         initial_tickets = c.user.stats.getTickets()
         initial_tickets_artifacts = c.user.stats.getArtifacts(art_type="Ticket")
 
-        h.set_context('test', 'bugs', neighborhood='Projects')
-        ticket = TM.Ticket(ticket_num=12, summary="test", assigned_to_id = c.user._id)
-        ticket.commit()
+        r = self.app.post('/tickets/save_ticket', 
+            params={'ticket_form.summary':'test',
+                    'ticket_form.assigned_to' : str(c.user.username)},
+            extra_environ=dict(username=str(c.user.username)))
+
+        ticketnum = str(TM.Ticket.query.get(summary='test').ticket_num)
 
         tickets = c.user.stats.getTickets()
         tickets_artifacts = c.user.stats.getArtifacts(art_type="Ticket")
@@ -86,8 +92,11 @@ class TestStats(TestController):
         assert tickets_artifacts['created'] == initial_tickets_artifacts['created'] + 1
         assert tickets_artifacts['modified'] == initial_tickets_artifacts['modified']
 
-        ticket.status = 'closed'
-        ticket.commit()
+        r = self.app.post('/tickets/%s/update_ticket_from_widget' % ticketnum, 
+            params={'ticket_form.ticket_num' : ticketnum,
+                    'ticket_form.summary':'footext3',
+                    'ticket_form.status' : 'closed'},
+            extra_environ=dict(username=str(c.user.username)))
 
         tickets = c.user.stats.getTickets()
         tickets_artifacts = c.user.stats.getArtifacts(art_type="Ticket")
@@ -98,9 +107,11 @@ class TestStats(TestController):
         assert tickets_artifacts['created'] == initial_tickets_artifacts['created'] + 1
         assert tickets_artifacts['modified'] == initial_tickets_artifacts['modified'] + 1
 
-        h.set_context('test', 'bugs', neighborhood='Projects')
-        ticket = TM.Ticket(ticket_num=13, summary="test")
-        ticket.commit()
+        r = self.app.post('/tickets/save_ticket', 
+            params={'ticket_form.summary':'test2'},
+            extra_environ=dict(username=str(c.user.username)))
+
+        ticketnum = str(TM.Ticket.query.get(summary='test2').ticket_num)
         
         tickets = c.user.stats.getTickets()
         tickets_artifacts = c.user.stats.getArtifacts(art_type="Ticket")
@@ -111,8 +122,11 @@ class TestStats(TestController):
         assert tickets_artifacts['created'] == initial_tickets_artifacts['created'] + 2
         assert tickets_artifacts['modified'] == initial_tickets_artifacts['modified'] + 1
 
-        ticket.assigned_to_id = c.user._id
-        ticket.commit()
+        r = self.app.post('/tickets/%s/update_ticket_from_widget' % ticketnum, 
+            params={'ticket_form.ticket_num' : ticketnum,
+                    'ticket_form.summary':'test2',
+                    'ticket_form.assigned_to' : str(c.user.username)},
+            extra_environ=dict(username=str(c.user.username)))
 
         tickets = c.user.stats.getTickets()
         tickets_artifacts = c.user.stats.getArtifacts(art_type="Ticket")
@@ -123,8 +137,11 @@ class TestStats(TestController):
         assert tickets_artifacts['created'] == initial_tickets_artifacts['created'] + 2
         assert tickets_artifacts['modified'] == initial_tickets_artifacts['modified'] + 2
 
-        ticket.assigned_to_id = User.by_username('test-user')._id
-        ticket.commit()
+        r = self.app.post('/tickets/%s/update_ticket_from_widget' % ticketnum, 
+            params={'ticket_form.ticket_num' : ticketnum,
+                    'ticket_form.summary':'test2',
+                    'ticket_form.assigned_to' : 'test-user'},
+            extra_environ=dict(username=str(c.user.username)))
 
         tickets = c.user.stats.getTickets()
         tickets_artifacts = c.user.stats.getArtifacts(art_type="Ticket")