[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