You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by st...@apache.org on 2013/04/04 00:22:24 UTC
[2/5] organization and organization stats
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/model/.svn/text-base/stats.py.svn-base
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/model/.svn/text-base/stats.py.svn-base b/ForgeOrganizationStats/forgeorganizationstats/model/.svn/text-base/stats.py.svn-base
new file mode 100644
index 0000000..f434e4e
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/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/787ef235/ForgeOrganizationStats/forgeorganizationstats/model/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/model/__init__.py b/ForgeOrganizationStats/forgeorganizationstats/model/__init__.py
new file mode 100644
index 0000000..0a92f1e
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/model/__init__.py
@@ -0,0 +1 @@
+from orgstats import OrganizationStats
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/model/orgstats.py
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/model/orgstats.py b/ForgeOrganizationStats/forgeorganizationstats/model/orgstats.py
new file mode 100644
index 0000000..f9c0cca
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/model/orgstats.py
@@ -0,0 +1,62 @@
+from ming.orm import FieldProperty
+from ming import schema as S
+from datetime import datetime, timedelta
+from ming.orm import session, Mapper
+
+from allura.model.session import main_orm_session
+
+from allura.model import Stats
+
+class OrganizationStats(Stats):
+ class __mongometa__:
+ name='organizationstats'
+ session = main_orm_session
+ unique_indexes = [ '_id', 'organization_id']
+
+ organization_id = FieldProperty(S.ObjectId)
+
+ @classmethod
+ def create(cls, organization):
+ stats = cls(organization_id=organization._id,
+ registration_date = datetime.utcnow())
+ organization.stats_id = stats._id
+ return stats
+
+ def getLastMonthCommitsPerMember(self, category = None):
+ from forgeorganization.organization.model import Organization
+
+ org = Organization.query.get(_id=self.organization_id)
+ members = len(org.getEnrolledUsers())
+ if not members:
+ return dict(number=0.0, lines=0.0)
+ commits = self.getLastMonthCommits(category=category)
+ return dict(
+ number=round(float(commits['number'])/members,2),
+ lines=round(float(commits['lines'])/members,2))
+
+ def getLastMonthArtifactsPerMember(self, category = None, art_type = None):
+ from forgeorganization.organization.model import Organization
+
+ org = Organization.query.get(_id=self.organization_id)
+ members = len(org.getEnrolledUsers())
+ if not members:
+ return dict(number=0.0, lines=0.0)
+ artifacts = self.getLastMonthArtifacts(category=category)
+ return dict(
+ created=round(float(artifacts['created'])/members,2),
+ modified=round(float(artifacts['modified'])/members,2))
+
+ def getLastMonthTicketsPerMember(self, category = None):
+ from forgeorganization.organization.model import Organization
+
+ org = Organization.query.get(_id=self.organization_id)
+ members = len(org.getEnrolledUsers())
+ if not members:
+ return dict(number=0.0, lines=0.0)
+ tickets = self.getLastMonthTickets(category=category)
+ return dict(
+ assigned=round(float(tickets['assigned'])/members,2),
+ solved=round(float(tickets['solved'])/members,2),
+ revoked=round(float(tickets['revoked'])/members,2))
+
+Mapper.compile_all()
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/all-wcprops
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/all-wcprops b/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/all-wcprops
new file mode 100644
index 0000000..efae2aa
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/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/787ef235/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/entries
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/entries b/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/entries
new file mode 100644
index 0000000..ef7dfdb
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/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
+
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/text-base/artifacts.html.svn-base
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/text-base/artifacts.html.svn-base b/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/text-base/artifacts.html.svn-base
new file mode 100644
index 0000000..0b3cfb8
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/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/787ef235/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/text-base/commits.html.svn-base
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/text-base/commits.html.svn-base b/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/text-base/commits.html.svn-base
new file mode 100644
index 0000000..c574c9f
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/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/787ef235/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/text-base/index.html.svn-base
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/text-base/index.html.svn-base b/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/text-base/index.html.svn-base
new file mode 100644
index 0000000..b53e596
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/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/787ef235/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/text-base/tickets.html.svn-base
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/text-base/tickets.html.svn-base b/ForgeOrganizationStats/forgeorganizationstats/templates/.svn/text-base/tickets.html.svn-base
new file mode 100644
index 0000000..148cfa8
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/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/787ef235/ForgeOrganizationStats/forgeorganizationstats/templates/artifacts.html
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/templates/artifacts.html b/ForgeOrganizationStats/forgeorganizationstats/templates/artifacts.html
new file mode 100644
index 0000000..d3edf6f
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/templates/artifacts.html
@@ -0,0 +1,67 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}Organization stats{% endblock %}
+
+{% block header %}
+ Statistics about {{organization.fullname}}'s contribution – Artifacts
+{% endblock %}
+
+{% block content %}
+
+ {% if organization and (organization.stats.visible or (c.user.username in c.project.admins())) %}
+ <div class="grid-20">
+ <ul>
+ <li><a href="{{c.project.url()}}organizationstats">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 %}
+ {% else %}
+ {% if not organization %}
+ <h2>Invalid organization</h2>
+ <div class="grid-20">
+ You are looking for the statistics of an organization which doesn't exist on this forge. Check your url.
+ </div>
+ {% else %}
+ <h2>Statistics not available</h2>
+ <div class="grid-20">
+ The administrator of this organization has set the preferences so that statistics are not visible
+ to other users of the forge.
+ </div>
+ {% endif %}
+ {% endif %}
+
+{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/templates/commits.html
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/templates/commits.html b/ForgeOrganizationStats/forgeorganizationstats/templates/commits.html
new file mode 100644
index 0000000..8368e45
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/templates/commits.html
@@ -0,0 +1,56 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}Organization stats{% endblock %}
+
+{% block header %}
+ Statistics about {{organization.fullname}}'s contribution – Code contribution
+{% endblock %}
+
+{% block content %}
+
+ {% if organization and (organization.stats.visible or (c.user.username in c.project.admins())) %}
+ <div class="grid-20">
+ <ul>
+ <li><a href="{{c.project.url()}}organizationstats">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 %}
+ {% else %}
+ {% if not organization %}
+ <h2>Invalid organization</h2>
+ <div class="grid-20">
+ You are looking for the statistics of an organization which doesn't exist on this forge. Check your url.
+ </div>
+ {% else %}
+ <h2>Statistics not available</h2>
+ <div class="grid-20">
+ The administrator of this organization has set the preferences so that statistics are not visible
+ to other users of the forge.
+ </div>
+ {% endif %}
+ {% endif %}
+{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/templates/index.html
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/templates/index.html b/ForgeOrganizationStats/forgeorganizationstats/templates/index.html
new file mode 100644
index 0000000..087ac58
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/templates/index.html
@@ -0,0 +1,509 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}Organization stats{% endblock %}
+
+{% block header %}
+ Statistics about {{organization.fullname}}'s contribution
+ {% if category %}
+ in projects of category {{category.fullname}}
+ {% endif %}
+{% endblock %}
+
+{% block content %}
+ {% if organization and (organization.stats.visible or (c.user.username in c.project.admins())) %}
+
+ {% if category %}
+ <ul>
+ <li><a href="{{c.project.url()}}organizationstats">Go back to general statistics</a></li>
+ </ul>
+ {% endif %}
+
+ <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>
+ </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>
+ <tr>
+ <td>
+ {%if totcommits.number > 0 %}
+ <a href="{{c.project.url()}}organizationstats/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 lastmonthcommits.number > permonthcommits.number %}
+ <img src="{{g.forge_static('images/up.png')}}"/>
+ {% elif lastmonthcommits.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="{{c.project.url()}}organizationstats/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 lastmonthcommits.lines > permonthcommits.lines %}
+ <img src="{{g.forge_static('images/up.png')}}"/>
+ {% elif lastmonthcommits.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="{{c.project.url()}}organizationstats/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="{{c.project.url()}}organizationstats/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="{{c.project.url()}}organizationstats/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%} <img src="{{g.forge_static('images/down.png')}}"/> {%endif%}
+ </td>
+ {% endif %}
+ </tr>
+ <tr>
+ <td>
+ {%if value.modified > 0 %}
+ <a href="{{c.project.url()}}organizationstats/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%} <img src="{{g.forge_static('images/down.png')}}"/> {%endif%}
+ </td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+
+ <tr>
+ <td>
+ {%if tottickets.assigned > 0 %}
+ <a href="{{c.project.url()}}organizationstats/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="{{c.project.url()}}organizationstats/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="{{c.project.url()}}organizationstats/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="{{c.project.url()}}organizationstats/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 not category %}
+ <h2>Members</h2>
+ <table>
+ <thead>
+ <tr>
+ <th>Parameter</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Current number of registered members</td>
+ <td>{{membersnumber}}</td>
+ </tr>
+ <tr>
+ <td>Members joining the organization during the last 30 days</td>
+ <td>{{newmembers}}</td>
+ </tr>
+ <tr>
+ <td>Members leaving the organization during the last 30 days</td>
+ <td>{{leftmembers}}</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <h2>Per-member contributions</h2>
+ <table>
+ <thead>
+ <tr>
+ <th>Parameter</th>
+ <th>Last 30 days</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Created artifacts</td>
+ <td>{{permemberartifacts.created}}</td>
+ </tr>
+ <tr>
+ <td>Modified artifacts</td>
+ <td>{{permemberartifacts.modified}}</td>
+ </tr>
+ <tr>
+ <td>Number of commits</td>
+ <td>{{permembercommits.number}}</td>
+ </tr>
+ <tr>
+ <td>Number of committed lines of code</td>
+ <td>{{permembercommits.lines}}</td>
+ </tr>
+ <tr>
+ <td>Number of assigned tickets</td>
+ <td>{{permembertickets.assigned}}</td>
+ </tr>
+ <tr>
+ <td>Number of solved tickets</td>
+ <td>{{permembertickets.solved}}</td>
+ </tr>
+ <tr>
+ <td>Number of revoked tickets</td>
+ <td>{{permembertickets.revoked}}</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <h2>Projects</h2>
+ <table>
+ <thead>
+ <tr>
+ <th>Type</th>
+ <th>Current number</th>
+ <th>Joined in the last month</th>
+ <th>Left in the last month</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Cooperations</td>
+ <td>{{coopnumber}}</td>
+ <td>{{newcooperations}}</td>
+ <td>{{oldcooperations}}</td>
+ </tr>
+ <tr>
+ <td>Participations</td>
+ <td>{{participnumber}}</td>
+ <td>{{newparticipations}}</td>
+ <td>{{oldparticipations}}</td>
+ </tr>
+ </tbody>
+ </table>
+
+ {% if categories %}
+ <h2>Preferred categories</h2>
+ <p>
+ The following table shows the number projects tagged as belonging to each single category in which this organization is
+ currently 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="{{c.project.url()}}organizationstats/category/{{cat.shortname}}">{{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="{{c.project.url()}}organizationstats/categories_graph"/>
+ </p>
+ {% endif %}
+ {% endif %}
+ {% if not category %}
+ <h2>Overall evaluation</h2>
+ <table>
+ <thead>
+ <tr>
+ <th>Field</th>
+ <th>Value</th>
+ <th>Average</th>
+ <th>Maximum</th>
+ <th>Progressbar</th>
+ <th>Percentage</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="{{c.project.url()}}organizationstats/code_ranking_bar"/></td>
+ <td>{{codepercentage}} %</td>
+ </tr>
+ <tr>
+ <td>Discussion</td>
+ <td>{{discussioncontribution}} contribution{% if discussioncontribution != 1 %}s{% endif %}/month</td>
+ <td>{{averagedisccontrib}} contribution{% if averagedisccontrib != 1 %}s{% endif %}/month</td>
+ <td>{{maxdisccontrib}} contribution{% if maxdisccontrib != 1 %}s{% endif %}/month</td>
+ <td><img src="{{c.project.url()}}organizationstats/discussion_ranking_bar"/></td>
+ <td>{{discussionpercentage}} %</td>
+ </tr>
+ <tr>
+ <td>Solved issues</td>
+ <td>{{ticketcontribution}} %</td>
+ <td>{{averageticketcontrib}} %</td>
+ <td>{{maxticketcontrib}} %</td>
+ <td><img src="{{c.project.url()}}organizationstats/tickets_ranking_bar"/></td>
+ <td>{{ticketspercentage}} %</td>
+ </tr>
+ </tbody>
+ </table>
+ <h3>Note</h3>
+ <p>
+ The above table compares the average monthly contribution of this organization with the average monthly contributions of
+ the other organization of the forge. The progressbar and the percentage refer to the organization's position in an overall
+ ranking of the organization of this forge. For example, a value of 100% in the field "Code" is associated to the
+ organization whose users have generate the highest total average number of committed LOCs per month.
+ Of course, this doesn't consider the quality of the contributions.
+ </p>
+ {% endif %}
+ {% endif %}
+ {% else %}
+ {% if not organization %}
+ <h2>Invalid organization</h2>
+ <div class="grid-20">
+ You are looking for the statistics of an organization which doesn't exist on this forge. Check your url.
+ </div>
+ {% else %}
+ <h2>Statistics not available</h2>
+ <div class="grid-20">
+ The administrator of this organization has set the preferences so that statistics are not visible
+ to other users of the forge.
+ </div>
+ {% endif %}
+ {% endif %}
+
+{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/templates/settings.html
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/templates/settings.html b/ForgeOrganizationStats/forgeorganizationstats/templates/settings.html
new file mode 100644
index 0000000..8f4f532
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/templates/settings.html
@@ -0,0 +1,19 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}Organization stats – Settings{% endblock %}
+
+{% block header %}
+ Statistics about {{organization.fullname}}'s contribution – Settings
+{% endblock %}
+
+{% block content %}
+
+ <div class="grid-20">
+ In this page you can set the visibility of the statistics related to your organization. If you decide to hide your
+ organization's statistics to other users, data collected about contributions of your organization to projects hosted on this
+ forge will be available only to users with admin access to the profile of your organization.
+ {{form.display(organization = organization)}}
+ </div>
+
+{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/templates/tickets.html
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/templates/tickets.html b/ForgeOrganizationStats/forgeorganizationstats/templates/tickets.html
new file mode 100644
index 0000000..8f3f050
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/templates/tickets.html
@@ -0,0 +1,66 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}Organization stats{% endblock %}
+
+{% block header %}
+ Statistics about {{organization.fullname}}'s contribution – Tickets
+{% endblock %}
+
+{% block content %}
+
+ {% if organization and (organization.stats.visible or (c.user.username in c.project.admins())) %}
+ <div class="grid-20">
+ <ul>
+ <li><a href="{{c.project.url()}}organizationstats">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 %}
+ {% else %}
+ {% if not organization %}
+ <h2>Invalid organization</h2>
+ <div class="grid-20">
+ You are looking for the statistics of an organization which doesn't exist on this forge. Check your url.
+ </div>
+ {% else %}
+ <h2>Statistics not available</h2>
+ <div class="grid-20">
+ The administrator of this organization has set the preferences so that statistics are not visible
+ to other users of the forge.
+ </div>
+ {% endif %}
+ {% endif %}
+{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/tests/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/tests/__init__.py b/ForgeOrganizationStats/forgeorganizationstats/tests/__init__.py
new file mode 100644
index 0000000..e69de29