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/04/04 20:38:02 UTC
[01/43] git commit: [5453] Fixed bug in user stats
Updated Branches:
refs/heads/master 709f4bf4d -> 17728a59f
[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/15130fe9
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/15130fe9
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/15130fe9
Branch: refs/heads/master
Commit: 15130fe9742332ea96b0c5af2cd18a491782ee45
Parents: e92943f
Author: Stefano Invernizzi <st...@apache.org>
Authored: Fri Jan 11 17:19:53 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:33 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/15130fe9/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)
[38/43] 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/43731d37
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/43731d37
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/43731d37
Branch: refs/heads/master
Commit: 43731d379ffeec3a65155cd652187cd6f6e67f67
Parents: 719ec68
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Mar 12 20:04:01 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:37 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/43731d37/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/43731d37/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 1613cf9..676a887 100644
--- a/ForgeSVN/forgesvn/tests/model/test_repository.py
+++ b/ForgeSVN/forgesvn/tests/model/test_repository.py
@@ -519,8 +519,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)
@@ -530,8 +534,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) ]
@@ -545,13 +549,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()
[39/43] 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/719ec685
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/719ec685
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/719ec685
Branch: refs/heads/master
Commit: 719ec6859e47600913ad8f9e4ade4b2c9a2591a0
Parents: cfb3805
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sat Mar 9 16:34:02 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:37 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/719ec685/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):
[02/43] 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/e92943f7
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/e92943f7
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/e92943f7
Branch: refs/heads/master
Commit: e92943f7fa52e5478e5bd77205e30eceec53eb54
Parents: b860d55
Author: Stefano Invernizzi <st...@apache.org>
Authored: Thu Jan 10 19:25:44 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:33 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/e92943f7/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/43] 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/bf6ebff4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/bf6ebff4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/bf6ebff4
Branch: refs/heads/master
Commit: bf6ebff44aef04b2e1ccde38aad1bfe949784705
Parents: 8eea428
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sat Jan 19 17:11:42 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:34 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/bf6ebff4/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/bf6ebff4/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/bf6ebff4/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/bf6ebff4/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/bf6ebff4/Allura/allura/model/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index 8bcf950..ed62dfb 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -594,8 +594,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/bf6ebff4/Allura/allura/model/repo_refresh.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repo_refresh.py b/Allura/allura/model/repo_refresh.py
index 46e952a..799d3cc 100644
--- a/Allura/allura/model/repo_refresh.py
+++ b/Allura/allura/model/repo_refresh.py
@@ -118,8 +118,7 @@ def refresh_repo(repo, all_commits=False, notify=True, new_clone=False):
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('repo_refreshed', len(commit_ids), all_commits, new_clone)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/bf6ebff4/ForgeTracker/forgetracker/model/ticket.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
index 0bd6a43..c491d7b 100644
--- a/ForgeTracker/forgetracker/model/ticket.py
+++ b/ForgeTracker/forgetracker/model/ticket.py
@@ -444,8 +444,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:
@@ -458,9 +457,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(
@@ -474,8 +473,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
[18/43] 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/82dae8ca
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/82dae8ca
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/82dae8ca
Branch: refs/heads/master
Commit: 82dae8caa771172bfd05737ab70800c3d09a9756
Parents: 5ae3f7f
Author: Stefano Invernizzi <st...@apache.org>
Authored: Tue Jan 22 11:20:26 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:35 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/82dae8ca/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")
[23/43] 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/3c59bd20
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/3c59bd20
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/3c59bd20
Branch: refs/heads/master
Commit: 3c59bd208c73b966bd734a2a65114d78fec079c1
Parents: 123a732
Author: Stefano Invernizzi <st...@apache.org>
Authored: Thu Jan 31 15:57:12 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:35 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/3c59bd20/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/43] 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/8eeb3ae0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/8eeb3ae0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/8eeb3ae0
Branch: refs/heads/master
Commit: 8eeb3ae0db7dd9bfde2e8fb5cfdf0f5da6a384d8
Parents: faf7812
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Mar 6 22:39:25 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:37 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/8eeb3ae0/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:
[07/43] 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/b860d552
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/b860d552
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/b860d552
Branch: refs/heads/master
Commit: b860d552b15fc43e07d797ef9079874822e20d4a
Parents: 634a68d
Author: Stefano Invernizzi <st...@apache.org>
Authored: Thu Jan 10 15:41:14 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:33 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/b860d552/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)
[17/43] 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/1de6fe14
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/1de6fe14
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/1de6fe14
Branch: refs/heads/master
Commit: 1de6fe145f20b2b56c191ad7e07694f0706adfd5
Parents: 4772ddd
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Jan 30 17:25:36 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:35 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/1de6fe14/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>
[29/43] 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/21b3465f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/21b3465f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/21b3465f
Branch: refs/heads/master
Commit: 21b3465f8498ea94af3acd8bfdd80d262bce3d16
Parents: ebd49bd
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sat Feb 16 13:01:52 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:36 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/21b3465f/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/21b3465f/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/21b3465f/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/21b3465f/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/21b3465f/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/21b3465f/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"> </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/21b3465f/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/21b3465f/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/21b3465f/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/21b3465f/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/21b3465f/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 %}
[40/43] 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/9d126701
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/9d126701
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/9d126701
Branch: refs/heads/master
Commit: 9d126701fc8855930aeda63ee3cdcbf754d0064a
Parents: 672ff3a
Author: Dave Brondsema <db...@geek.net>
Authored: Mon Mar 25 16:09:11 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:38 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/9d126701/Allura/allura/controllers/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/project.py b/Allura/allura/controllers/project.py
index 7a1551f..891e18f 100644
--- a/Allura/allura/controllers/project.py
+++ b/Allura/allura/controllers/project.py
@@ -105,7 +105,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 + '/')
@@ -323,7 +323,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)
@@ -334,8 +334,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/9d126701/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/9d126701/Allura/allura/model/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 39509ff..b0b45df 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -402,11 +402,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:
@@ -620,26 +615,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/9d126701/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/9d126701/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/9d126701/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 815f557..f1d7e33 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/9d126701/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',
[11/43] 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/8eea4289
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/8eea4289
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/8eea4289
Branch: refs/heads/master
Commit: 8eea4289f20271049bd2f9b37c8da74afbde41a0
Parents: 8dc99c3
Author: Stefano Invernizzi <st...@apache.org>
Authored: Fri Jan 18 18:20:56 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:34 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/8eea4289/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/8eea4289/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/8eea4289/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/8eea4289/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/8eea4289/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/8eea4289/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/8eea4289/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/8eea4289/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/8eea4289/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/8eea4289/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 %}
[10/43] 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/5ae3f7f1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/5ae3f7f1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/5ae3f7f1
Branch: refs/heads/master
Commit: 5ae3f7f1bbb776a1065434151b876ff584c500af
Parents: 126b56c
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sun Jan 20 16:33:25 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:34 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/5ae3f7f1/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/5ae3f7f1/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/5ae3f7f1/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/5ae3f7f1/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/5ae3f7f1/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/5ae3f7f1/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
-
[16/43] 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/ebd49bdc
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/ebd49bdc
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/ebd49bdc
Branch: refs/heads/master
Commit: ebd49bdc3d377be68e66f3fa09a286dc9fee581e
Parents: 3c59bd2
Author: Stefano Invernizzi <st...@apache.org>
Authored: Fri Feb 1 21:25:01 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:35 2013 +0000
----------------------------------------------------------------------
ForgeUserStats/test.ini | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ebd49bdc/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
[35/43] 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/96f7e9a8
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/96f7e9a8
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/96f7e9a8
Branch: refs/heads/master
Commit: 96f7e9a8024881a12150be5d6f4eb1909d0a61f1
Parents: 91774b5
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sun Mar 24 11:00:39 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:37 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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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> </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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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/96f7e9a8/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
[20/43] 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/428297eb
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/428297eb
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/428297eb
Branch: refs/heads/master
Commit: 428297eb3fb8c9e333c5738525799b58efa523d3
Parents: 4bb3b6c
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Jan 30 16:21:49 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:35 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/428297eb/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/428297eb/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>
[36/43] 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/2e6c61f8
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/2e6c61f8
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/2e6c61f8
Branch: refs/heads/master
Commit: 2e6c61f8624f31165eb6ab6092b350918540eeab
Parents: 96f7e9a
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sun Mar 24 11:36:25 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:37 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/2e6c61f8/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"> </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 %}
[03/43] 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/634a68d1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/634a68d1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/634a68d1
Branch: refs/heads/master
Commit: 634a68d14601425007c82fa249f52f6fb2885a81
Parents: b0a56a5
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Jan 9 09:19:56 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:33 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/634a68d1/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()
[27/43] 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/e5abf088
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/e5abf088
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/e5abf088
Branch: refs/heads/master
Commit: e5abf08843e214267cf87ae6a5e76f19995f4941
Parents: d250390
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Mar 5 21:44:54 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:36 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/e5abf088/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/e5abf088/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/e5abf088/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/e5abf088/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
[05/43] [5453] adding support for user stats
Posted by br...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/requirements-common.txt
----------------------------------------------------------------------
diff --git a/requirements-common.txt b/requirements-common.txt
index eded3e8..55697b2 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
[28/43] 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/86799b5f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/86799b5f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/86799b5f
Branch: refs/heads/master
Commit: 86799b5fd68e38a8c349ff50a9db02bd500123d2
Parents: 656a7b3
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Mon Feb 25 21:55:33 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:36 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/86799b5f/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/86799b5f/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):
[13/43] 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/d148f390
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/d148f390
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/d148f390
Branch: refs/heads/master
Commit: d148f390bb8d220165c5ba440fb304782f905641
Parents: 15130fe
Author: Stefano Invernizzi <st...@apache.org>
Authored: Fri Jan 11 19:07:48 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:34 2013 +0000
----------------------------------------------------------------------
run_tests | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/d148f390/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
[31/43] 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/656a7b36
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/656a7b36
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/656a7b36
Branch: refs/heads/master
Commit: 656a7b36e026b4aaf315d336ad46ea85938073af
Parents: 0cf61cb
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Mon Feb 25 21:55:16 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:36 2013 +0000
----------------------------------------------------------------------
requirements-common.txt | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/656a7b36/requirements-common.txt
----------------------------------------------------------------------
diff --git a/requirements-common.txt b/requirements-common.txt
index 55697b2..e072ad9 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
[06/43] 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/68b8dfe2
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/68b8dfe2
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/68b8dfe2
Branch: refs/heads/master
Commit: 68b8dfe21f772e5ee3f677ae58369af599911a81
Parents: 709f4bf
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Dec 12 22:06:15 2012 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:33 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 | 8 +
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, 3056 insertions(+), 4 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/68b8dfe2/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/68b8dfe2/Allura/allura/controllers/root.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/root.py b/Allura/allura/controllers/root.py
index 5ac4767..beaddb6 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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/Allura/allura/model/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index 5ccfd60..8bcf950 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
@@ -36,6 +37,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'.
@@ -332,6 +340,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
@@ -578,6 +593,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/68b8dfe2/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/68b8dfe2/Allura/allura/model/repo_refresh.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repo_refresh.py b/Allura/allura/model/repo_refresh.py
index f15ec81..7087edf 100644
--- a/Allura/allura/model/repo_refresh.py
+++ b/Allura/allura/model/repo_refresh.py
@@ -111,6 +111,14 @@ def refresh_repo(repo, all_commits=False, notify=True, new_clone=False):
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', len(commit_ids), all_commits, new_clone)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/ForgeTracker/forgetracker/model/ticket.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
index 686fd7d..0bd6a43 100644
--- a/ForgeTracker/forgetracker/model/ticket.py
+++ b/ForgeTracker/forgetracker/model/ticket.py
@@ -444,6 +444,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:
@@ -456,6 +458,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(
@@ -468,7 +473,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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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/68b8dfe2/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
+
[34/43] 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/faf78125
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/faf78125
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/faf78125
Branch: refs/heads/master
Commit: faf781252ed34639cd795eeffe615f0af0c60710
Parents: e5abf08
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Tue Mar 5 21:45:38 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:37 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/faf78125/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']
[22/43] 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/123a7320
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/123a7320
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/123a7320
Branch: refs/heads/master
Commit: 123a73207c785df99776b4b8ed1ec8e7667dd056
Parents: 1de6fe1
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Jan 30 17:44:14 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:35 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/123a7320/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/123a7320/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 %}
[14/43] 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/a9df922e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/a9df922e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/a9df922e
Branch: refs/heads/master
Commit: a9df922eaeca96ad47a510381ed8be51179fe5dd
Parents: 25bdc07
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sat Jan 12 18:37:26 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:34 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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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/a9df922e/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
[24/43] 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/0cf61cb1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/0cf61cb1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/0cf61cb1
Branch: refs/heads/master
Commit: 0cf61cb1563c8eb3cfc353edcb9a58434f806ada
Parents: 9ba8e4a
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sat Feb 23 12:06:17 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:36 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/0cf61cb1/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)
[42/43] git commit: [#5453] only count commits for incremental
commits, not full refreshes
Posted by br...@apache.org.
[#5453] only count commits for incremental commits, 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/e81542a2
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/e81542a2
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/e81542a2
Branch: refs/heads/master
Commit: e81542a2cd132895e549019e1f517f7d0111b4ac
Parents: 2e6c61f
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Wed Mar 27 21:04:59 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:38 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/e81542a2/Allura/allura/model/repo_refresh.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repo_refresh.py b/Allura/allura/model/repo_refresh.py
index 799d3cc..d06e879 100644
--- a/Allura/allura/model/repo_refresh.py
+++ b/Allura/allura/model/repo_refresh.py
@@ -112,13 +112,14 @@ def refresh_repo(repo, all_commits=False, notify=True, new_clone=False):
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('repo_refreshed', len(commit_ids), all_commits, new_clone)
[04/43] 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/b0a56a58
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/b0a56a58
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/b0a56a58
Branch: refs/heads/master
Commit: b0a56a58c61a8883cadfe7e33184205be23cadd5
Parents: 68b8dfe
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Dec 12 22:32:29 2012 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:33 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/b0a56a58/Allura/allura/model/repo_refresh.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repo_refresh.py b/Allura/allura/model/repo_refresh.py
index 7087edf..46e952a 100644
--- a/Allura/allura/model/repo_refresh.py
+++ b/Allura/allura/model/repo_refresh.py
@@ -18,6 +18,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__)
[21/43] 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/4bb3b6ca
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/4bb3b6ca
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/4bb3b6ca
Branch: refs/heads/master
Commit: 4bb3b6ca336e1dcc79d22e282c7c193d588e7d5a
Parents: 82dae8c
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Jan 30 15:10:59 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:35 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/4bb3b6ca/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 %}
[26/43] 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/d2503902
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/d2503902
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/d2503902
Branch: refs/heads/master
Commit: d2503902c8b346bc92f726c3ac7e24287778995f
Parents: c99abb4
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sun Mar 3 10:30:05 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:36 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/d2503902/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/d2503902/Allura/allura/model/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index b8240b9..a6904f2 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -342,9 +342,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/d2503902/Allura/allura/model/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 0f46a11..39509ff 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -715,8 +715,6 @@ class Project(MappedClass, ActivityNode, ActivityObject):
apps += [('Wiki', 'wiki', 'Wiki'),
('profile', 'profile', 'Profile'),
]
- if g.show_userstats:
- apps = apps + [('userstats', 'userstats', 'Statistics')]
apps += [
('admin', 'admin', 'Admin'),
('search', 'search', 'Search'),
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/d2503902/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/d2503902/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/d2503902/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
[08/43] 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/126b56c4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/126b56c4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/126b56c4
Branch: refs/heads/master
Commit: 126b56c4c077ca360895d55fc631281a44483c4b
Parents: bf6ebff
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sat Jan 19 17:29:34 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:34 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/126b56c4/Allura/allura/controllers/root.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/root.py b/Allura/allura/controllers/root.py
index beaddb6..d23891c 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/126b56c4/Allura/allura/model/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index ed62dfb..b8240b9 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -37,13 +37,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'.
@@ -341,16 +334,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)
[15/43] 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/25bdc079
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/25bdc079
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/25bdc079
Branch: refs/heads/master
Commit: 25bdc079c45ff4c82d45c25ebf201d7e8c54e53c
Parents: d148f39
Author: Stefano Invernizzi <st...@apache.org>
Authored: Fri Jan 11 19:08:03 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:34 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/25bdc079/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/25bdc079/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
[33/43] 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/cfb38058
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/cfb38058
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/cfb38058
Branch: refs/heads/master
Commit: cfb38058afb37e14064d4b420e5c3f146e4979c7
Parents: 8eeb3ae
Author: Stefano Invernizzi <st...@apache.org>
Authored: Thu Mar 7 23:44:50 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:37 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/cfb38058/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:
[30/43] 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/9ba8e4a1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/9ba8e4a1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/9ba8e4a1
Branch: refs/heads/master
Commit: 9ba8e4a1de3da18e3edf998829bd647774ec7393
Parents: 21b3465
Author: Stefano Invernizzi <st...@apache.org>
Authored: Sat Feb 16 14:33:22 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:36 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/9ba8e4a1/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/9ba8e4a1/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/9ba8e4a1/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/9ba8e4a1/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/9ba8e4a1/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
[19/43] 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/4772ddda
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/4772ddda
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/4772ddda
Branch: refs/heads/master
Commit: 4772ddda2f2010b7fd87e55cdfdc5c8d575a1c87
Parents: 428297e
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Jan 30 17:03:56 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:35 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/4772ddda/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/4772ddda/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
[32/43] 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/91774b52
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/91774b52
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/91774b52
Branch: refs/heads/master
Commit: 91774b525612211c2424a720ec9a182c6351d85f
Parents: 43731d3
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Mar 13 01:25:10 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:37 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/91774b52/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/91774b52/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()
[12/43] 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/8dc99c3e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/8dc99c3e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/8dc99c3e
Branch: refs/heads/master
Commit: 8dc99c3e7275cc6e394aae9e3fd5507c02f7aaa7
Parents: a9df922
Author: Stefano Invernizzi <st...@apache.org>
Authored: Mon Jan 14 14:43:56 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:34 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/8dc99c3e/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/8dc99c3e/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
[43/43] git commit: [#5453] don't count svn imports either
Posted by br...@apache.org.
[#5453] don't count svn imports either
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/17728a59
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/17728a59
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/17728a59
Branch: refs/heads/master
Commit: 17728a59faa947c3a0a450b620781b00d595fc9b
Parents: 9d12670
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Thu Apr 4 18:36:39 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:38 2013 +0000
----------------------------------------------------------------------
Allura/allura/model/repo_refresh.py | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/17728a59/Allura/allura/model/repo_refresh.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repo_refresh.py b/Allura/allura/model/repo_refresh.py
index d06e879..20383e9 100644
--- a/Allura/allura/model/repo_refresh.py
+++ b/Allura/allura/model/repo_refresh.py
@@ -112,7 +112,7 @@ def refresh_repo(repo, all_commits=False, notify=True, new_clone=False):
if (i+1) % 100 == 0:
log.info('Compute last commit info %d: %s', (i+1), ci._id)
- if not all_commits:
+ if not all_commits and not new_clone:
for commit in commit_ids:
new = repo.commit(commit)
user = User.by_email_address(new.committed.email)
[41/43] 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/672ff3a5
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/672ff3a5
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/672ff3a5
Branch: refs/heads/master
Commit: 672ff3a5e71a39b359ed0b8b530bbf05fe5cd315
Parents: e81542a
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Thu Mar 28 14:07:28 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:38 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/672ff3a5/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:
[25/43] 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/c99abb4f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/c99abb4f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/c99abb4f
Branch: refs/heads/master
Commit: c99abb4f36d091fd4449acd8164be99927df3d64
Parents: 86799b5
Author: Stefano Invernizzi <st...@apache.org>
Authored: Wed Feb 27 23:47:24 2013 +0100
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu Apr 4 18:37:36 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/c99abb4f/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/c99abb4f/Allura/allura/controllers/root.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/root.py b/Allura/allura/controllers/root.py
index d23891c..c56e373 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/c99abb4f/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/c99abb4f/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/c99abb4f/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/c99abb4f/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/c99abb4f/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/c99abb4f/Allura/allura/model/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 39509ff..0f46a11 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -715,6 +715,8 @@ class Project(MappedClass, ActivityNode, ActivityObject):
apps += [('Wiki', 'wiki', 'Wiki'),
('profile', 'profile', 'Profile'),
]
+ if g.show_userstats:
+ apps = apps + [('userstats', 'userstats', 'Statistics')]
apps += [
('admin', 'admin', 'Admin'),
('search', 'search', 'Search'),
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c99abb4f/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/c99abb4f/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/c99abb4f/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/c99abb4f/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/c99abb4f/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/c99abb4f/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/c99abb4f/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/c99abb4f/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/c99abb4f/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/c99abb4f/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/c99abb4f/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/c99abb4f/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
+
""",
)