[Python-checkins] r53028 - tracker/instances/python-dev/scripts/roundup-summary
paul.dubois
python-checkins at python.org
Thu Dec 14 00:57:56 CET 2006
Author: paul.dubois
Date: Thu Dec 14 00:57:56 2006
New Revision: 53028
Modified:
tracker/instances/python-dev/scripts/roundup-summary
Log:
ready for testing again
Modified: tracker/instances/python-dev/scripts/roundup-summary
==============================================================================
--- tracker/instances/python-dev/scripts/roundup-summary (original)
+++ tracker/instances/python-dev/scripts/roundup-summary Thu Dec 14 00:57:56 2006
@@ -9,11 +9,10 @@
import optparse , datetime
import cStringIO, MimeWriter, smtplib
from roundup.mailer import SMTPConnection
-today = roundup.date.Date('.')
### CONFIGURATION
# List of statuses to treat as closed -- these don't have to all exist.
-resolvedStatusDefault = 'resolved,done,done-cbb,closed'
+resolvedStatusDefault = 'resolved,done,done-cbb,closed,faq'
# Period of time for report. Uses roundup syntax for ranges.
defaultDates = '-1w;'
@@ -50,9 +49,9 @@
'-1w;' -- the last week (the default)
'-3m;' -- the last 3 months
'from 2006-10-25 to 2006-12-25' -- Thanksgiving to Christmas, 2006
-
- resolved is a list of statuses to treat as 'closed'.
+ mailTo is one or more email addresses, comma delimited.
+ resolved is a list of statuses to treat as 'closed'.
statuses are names of statuses to ignore.
Be sure to protect commas and semicolons from the shell with quotes!
@@ -77,7 +76,7 @@
'from 2006-11-1 to 2006-12-1' -- exact period""")
parser.add_option('-m','--mail', dest='mailTo',
default=defaultMailTo,
- help='Mail the report to the address mailTo; if not given, print report.')
+ help='Mail the report to the address(es) mailTo; if not given, print report.')
parser.add_option('-s','--status', dest='ignore', metavar="STATUSES_TO_IGNORE",
default=ignoredStatusesDefault,
help="Comma-delimited list of statuses to ignore in report.")
@@ -107,15 +106,14 @@
db = instance.open('admin')
# CUSTOMIZATION
-dateWidth = len(roundup.date.Date('30 September 2006').pretty(format=dateFormat))
+dateWidth = len(roundup.date.Date('2006-09-30').pretty(format=dateFormat))
titleWidth = 72 #zero means no limit
durationWidth = len('9999 days')
+tableStyle = 'border="1" cellspacing="0" cellpadding="3"'
def formatDuration(duration):
- "Change a roundup duration object into text."
- s = duration.as_seconds()
- delta = datetime.timedelta(seconds=float(s))
- return str(delta.days) + ' days'
+ "Change a duration into text."
+ return "%4.0f days" % (duration.as_seconds()/(3600.*24),)
def closedOrBlank(statusID):
"CLOSED or a blank"
@@ -125,22 +123,20 @@
return ""
# Additional filter(s) to use in picking out issues.
-# This sort of thing should maybe be command line options
-# but it depends on the schema.
-# Configuration file also a possibility for this and ignored statuses
+# Depends on the schema.
+notThese = ('notices', 'faq', 'todo', 'feature', 'wish')
+try:
+ priorityIDS = db.priority.getnodeids(None, retired=None)
+ priorityNames = [db.priority.get(p, 'name') for p in priorityIDS]
+ acceptablePriorities = [priorityIDS[i] for i in range(len(priorityIDS)) if \
+ priorityNames[i] not in notThese]
+except: #for example, priority is not in the schema
+ acceptablePriorities = []
+
def F(**args):
"args is the dictionary to return, with additions if desired."
- try:
- priorityIDS = db.priority.getnodeids(None, retired=None)
- priorityNames = [db.priority.get(p, 'name') for p in priorityIDS]
- notThese = ('notices', 'faq', 'todo')
- acceptable = [priorityIDS[i] for i in range(len(priorityIDS)) if \
- priorityNames[i] not in notThese]
- if options.debug:
- print 'acceptable priorities', acceptable
- args['priority'] = acceptable
- except:
- pass
+ if acceptablePriorities:
+ args['priority'] = acceptablePriorities
return args
@@ -158,54 +154,55 @@
# The 'Created or Repopened' table, text mode.
createdTable = """
titles:
- 'Title', 0
+ 'Title', titleWidth - dateWidth
+ 'Date', dateWidth
'<br>', 0
'Status', len('CLOSED')
- 'issueRef', issueRefWidth
+ 'Link', linkWidth
'Action', actionWidth
'By', userNameWidth
records:
title
- closedOrBlank(statusID)
- link(issueID)
(reopenedDate or creation).pretty(format=dateFormat)
+ closedOrBlank(statusID)
+ link(issueNumber)
+ reopened or 'created'
reopener or creator
"""
# The table of closed issues, text mode.
closedTable = """
titles:
- 'Title', 0
+ 'Title', titleWidth - durationWidth
+ 'Duration', durationWidth
'<br>', len('CLOSED')
- 'Issue', issueRefWidth
+ 'Link', linkWidth
'By', userNameWidth
- 'Duration', durationWidth
records:
title
- issueRef
- actor
formatDuration(duration)
+ link(issueNumber)
+ actor
"""
# The table of most discussed issues, text mode.
discussedTable = """
titles:
- 'N', 3
- 'Title', 0
+ 'N', -3
+ 'Title', titleWidth - durationWidth
+ 'Duration', durationWidth
'<br>', 0
'Status', statusWidth
- 'Issue', issueRefWidth
- 'By', userNameWidth
- 'Duration', durationWidth
+ 'Link', linkWidth
records:
numberOfMessages
title
- status
- issueRef
- actor
formatDuration(duration)
+ status
+ link(issueNumber)
"""
### HTML Tables
# The 'Created or Repopened' table, html mode.
+# Titles are links, use zero on width; limit is applied in hlink
htmlCreatedTable = """
titles:
'Title', 0
@@ -214,7 +211,7 @@
'Action', actionWidth
'By', userNameWidth
records:
- hlink(issueID, title)
+ hlink(issueNumber, title)
closedOrBlank(statusID)
(reopenedDate or creation).pretty(format=dateFormat)
reopened or 'created'
@@ -227,20 +224,20 @@
'By', userNameWidth
'Duration', durationWidth
records:
- hlink(title, issueID)
+ hlink(issueNumber, title)
actor
formatDuration(duration)
"""
# The table of most discussed issues, text mode.
htmlDiscussedTable = """
titles:
- 'N', 3
+ 'N', -3
'Title', 0
'Status', statusWidth
'Duration', durationWidth
records:
numberOfMessages
- hlink(title, issueID)
+ hlink(issueNumber, title)
status
formatDuration(duration)
"""
@@ -252,6 +249,7 @@
"""Return dictionaries with the issue's title, etc.
Assumes issue was created no later than to_value
"""
+ issueNumber = issueID
reopened = ''
reopener = ''
reopenedDate = ''
@@ -278,26 +276,22 @@
activity = activity or activity2
for x,ts,userid,act,data in journal:
- if ts < from_value:
- break
- if ts < to_value:
+ inPeriod = ts >= from_value and ts < to_value
+ if inPeriod:
statusID = statusID or status2
actorID = actorID or actor2
activity = activity or activity2
if act == 'set':
if data.has_key('messages'):
- if ts < to_value:
+ if inPeriod:
numberOfMessages += 1
if data.has_key('status'):
status1 = data['status']
- if status1 is None:
- status1 = createdStatusID
if ts < to_value:
if (status1 in resolvedStatusIDS) and \
(status2 not in resolvedStatusIDS):
- if not reopened:
- # want the latest reopener
- if issueID not in recentlyCreatedIssueIDS:
+ if not reopened: # want the latest reopener only
+ if inPeriod and issueID not in recentlyCreatedIssueIDS:
reopenedIssueIDS.append(issueID)
reopened = 'reopened'
reopener = userMap[userid]
@@ -311,18 +305,23 @@
statusID = statusID or status2
activity = activity or activity2
actorID = actorID or actor2
-# calculate the fields for the reports (consider delaying this part
-# so as to do it only for reported-upon issues)
+# calculate the fields for the reports
status = statusMap[statusID]
actor = userMap[actorID]
- duration = activity - (reopenedDate or creation)
title = db.issue.get(issueID, 'title')
creation = db.issue.get(issueID, 'creation')
creatorID = db.issue.get(issueID, 'creator')
creator = userMap[creatorID]
+ if statusID in resolvedStatusIDS:
+ duration = activity - (reopenedDate or creation)
+ else:
+ duration = to_value - (reopenedDate or creation)
+ if options.debug:
+ print >>bugfile, issueID, status, creator, actor, "%4.0f" %(duration.as_seconds()/(3600.24)), \
+ activity.pretty(dateFormat), reopenedDate or 'created', creation.pretty(dateFormat)
# out of a sense of neatness
del status1, status2, actor2, activity2
- del x, ts, userid, act, data, t, journal
+ del x, ts, userid, act, data, journal
# return the dictionary of local values for use in evals of table values.
return locals()
@@ -333,7 +332,7 @@
def hlink (issueID, title):
"html link to url/title of issue whose ID is issueID"
- return '''<a href="%s">%s</a>''' %(link(issueID), title)
+ return '''<a href="%s">%s</a>''' %(link(issueID), title[:titleWidth])
def statusCompare (x, y):
"""Compare two statusIDs by order."""
@@ -509,14 +508,13 @@
super(htmlTable, self).__init__(caption, issueIDS, tableSpec)
def prologue(self, device):
- print >>device, """<br><table border="1">
-<caption>%s</caption>""" % self.caption
+ print >>device, """<br><table %s>
+<caption>%s</caption>""" % (tableStyle, self.caption)
self.writeTitles(device, self.titles, self.fws)
def epilogue(self, device):
print >>device, '</table>'
-
def startTitles(self, device, titles, fws):
print >>device, ' <tr>'
@@ -584,10 +582,18 @@
(nOpen, nOpen-nOpenOld, nClosed, nClosed-nClosedOld, (nOpen+nClosed),
(nOpen+nClosed)-(nOpenOld+nClosedOld))
print >>body, '</p>'
+ print >>body, "<p>"
+ print >>body, '<p></p>'
+ print >>body, "Average duration of open issues:", "%3.0f" % averageDuration, "days."
+ print >>body, '</p>'
+ print >>body, "<p>"
+ print >>body, "Median duration of open issues:", "%3.0f" % medianDuration, "days."
+ print >>body, '</p>'
print >>body, '<p>'
- print >>body, '''<table border="1"><caption>%s</caption>
+ print >>body, '''<table %s>
+<caption>%s</caption>
<tr><th>STATUS</th> <th>Number</th><th>Change</th></tr>
-''' % caption
+''' % (tableStyle, caption)
for sid in statusIDS:
if sid in resolvedStatusIDS:
continue
@@ -615,6 +621,9 @@
(nOpen, nOpen-nOpenOld, nClosed, nClosed-nClosedOld, (nOpen+nClosed),
(nOpen+nClosed)-(nOpenOld+nClosedOld))
print >>body
+ print >>body, "Average duration of open issues:", "%3.0f" % averageDuration, "days."
+ print >>body, "Median duration of open issues:", "%3.0f" % medianDuration, "days."
+ print >>body
print >>body, caption
for sid in statusIDS:
if sid in resolvedStatusIDS:
@@ -627,7 +636,7 @@
def writeTextReport(device):
"Write the report in text mode to device."
- textSummary(device, "Open Issues Statistics")
+ textSummary(device, "Open Issues Breakdown")
if not options.brief:
messages.sort()
@@ -644,7 +653,7 @@
def writeHtmlReport(device):
"Write the report in html mode to device."
- htmlSummary(device, 'Open Issues Statistics')
+ htmlSummary(device, 'Open Issues Breakdown')
if not options.brief:
t = htmlTable('Issues Created Or Reopened', createdOrReopenedIDS,
htmlCreatedTable)
@@ -671,15 +680,24 @@
# Options processing
if options.debug:
- print options
+ bugfile = open('bugfile', 'w')
+ print >>bugfile, options
# ...dates
dates = roundup.date.Range(options.dates, roundup.date.Date)
from_value = dates.from_value
to_value = dates.to_value
if to_value is None:
to_value = roundup.date.Date('.')
+if from_value is None:
+ from_value = roundup.date.Date('1980-1-1')
early_period = ';%s'%from_value
whole_period = ';%s'%to_value
+# ... statuses
+ignoredStatuses = [s.strip() for s in options.ignore.split(',')]
+if options.debug:
+ print >>bugfile, "from,to", from_value, to_value
+ print >>bugfile, 'acceptablePriorities', acceptablePriorities
+ print >>bugfile, 'ignoredStatuses', ignoredStatuses
# ...audit
if options.audit:
@@ -698,13 +716,8 @@
audit.close()
sys.exit(0)
-# ... statuses
-ignoredStatuses = [s.strip() for s in options.ignore.split(',')]
# MAIN
-# Find the id of the status an issue gets when created
-createdStatusID = db.status.lookup('unread')
-
# Make lists and maps of things
# Users
adminUserID = db.user.lookup('admin')
@@ -721,10 +734,8 @@
# Make map of statuses / names, including retired ones.
# Just doing lookup on names will miss retired ones.
statusIDS_ALL = db.status.getnodeids(retired=None)
-statusLookup = {}
-statusMap = {None: createdStatusID}
-statusMap = {None: createdStatusID}
-
+statusLookup = {'None': None}
+statusMap = {None: 'None'}
for sid in statusIDS_ALL:
name = db.status.get(sid, 'name')
statusLookup[name] = sid
@@ -741,8 +752,8 @@
pass
if options.debug:
- print 'statusLookup', statusLookup
- print 'resolvedStatusIDS', resolvedStatusIDS
+ print >>bugfile, 'statusLookup', statusLookup
+ print >>bugfile, 'resolvedStatusIDS', resolvedStatusIDS
statusIDS = [sid for sid in statusIDS_ALL if statusUsable(sid)]
statusIDS.sort(statusCompare)
@@ -776,7 +787,7 @@
maxIssueID = allIssueIDS[-1]
else:
maxIssueID = 1000
-issueRefWidth = len(link(maxIssueID))
+linkWidth = len(link(maxIssueID))
messages = []
statusReport = {}
issueAttributes = {}
@@ -787,6 +798,17 @@
createdOrReopenedIDS = reopenedIssueIDS + recentlyCreatedIssueIDS
createdOrReopenedIDS.sort(issueIdCompare)
+durations = [issueAttributes[id]['duration'].as_seconds()/(3600*24.) for id in allIssueIDS if \
+ id not in closedIssueIDS]
+if durations:
+ if options.debug:
+ print >>bugfile, 'Min duration:', durations[-1], '; max', durations[0]
+ averageDuration = sum(durations)/len(durations)
+ medianDuration = durations[len(durations)/2]
+else:
+ averageDuration = "N/A"
+ medianDuration = "N/A"
+
messages.sort()
messages.reverse()
discussedIDS = [id for (num, id) in messages[:discussionMax] \
@@ -806,19 +828,19 @@
statusReport[sid] = (nNow, nThen)
-# Now make the text report
+# Now make the reports
textReport = cStringIO.StringIO()
writeTextReport(textReport)
-# Email? Or just print it.
-if not options.mailTo:
- print textReport.getvalue()
-else:
- # generate the email message
+htmlReport = cStringIO.StringIO()
+writeHtmlReport(htmlReport)
+
+def sendReport (recipient):
+ "Send the email message."
message = cStringIO.StringIO()
writer = MimeWriter.MimeWriter(message)
writer.addheader('Subject', 'Summary of %s Issues'%db.config.TRACKER_NAME)
- writer.addheader('To', options.mailTo)
+ writer.addheader('To', recipient)
writer.addheader('From', '%s <%s>'%(db.config.TRACKER_NAME, db.config.ADMIN_EMAIL))
writer.addheader('Reply-To', '%s <%s>'%(db.config.TRACKER_NAME, 'DONOTREPLY at NOWHERE.ORG'))
writer.addheader('MIME-Version', '1.0')
@@ -835,7 +857,7 @@
# now the HTML one
part = writer.nextpart()
body = part.startbody('text/html')
- writeHtmlReport(body)
+ print >>body, htmlReport.getvalue()
# finish off the multipart
writer.lastpart()
# all done, send!
@@ -845,3 +867,10 @@
smtp = SMTPConnection(db.config)
smtp.sendmail(db.config.ADMIN_EMAIL, options.mailTo, message.getvalue())
+# Email? Or just print it.
+if not options.mailTo:
+ print textReport.getvalue()
+else:
+ for recipient in options.mailTo.split(','):
+ sendReport(recipient)
+
More information about the Python-checkins
mailing list