Skip to content

Commit

Permalink
followup actual time report
Browse files Browse the repository at this point in the history
  • Loading branch information
peterbe committed Feb 4, 2011
1 parent d727d7b commit 15d8a66
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 65 deletions.
9 changes: 7 additions & 2 deletions IssueTrackerProduct/IssueTracker.py
Expand Up @@ -5834,7 +5834,7 @@ def InitZCatalog(self, t={}):
extras.lexicon_id = 'Lexicon'
zcatalog.addIndex(idx, 'ZCTextIndex', extras)

dateindexes = ['modifydate','issuedate']
dateindexes = ['modifydate','issuedate','threaddate']
if self.EnableDueDate():
dateindexes.append('due_date')

Expand Down Expand Up @@ -7596,7 +7596,12 @@ def sendEmail(self, msg, to, fr, subject, swallowerrors=False, headers={}):
mailhost = self._findMailHost()
# We like to do our own (more unicode sensitive) munging of headers and
# stuff but like to use the mailhost to do the actual network sending.
mailhost._send(fr, to, message.as_string())
t0 = time()
message_as_string = message.as_string()
mailhost._send(fr, to, message_as_string)
t1 = time()
logger.info("Took %s seconds to send %d bytes email to %s" %\
(t1-t0, len(message_as_string), to))

return True

Expand Down
138 changes: 96 additions & 42 deletions IssueTrackerProduct/Summary.py
Expand Up @@ -12,9 +12,9 @@
except ImportError:
# < Zope 2.12
from Globals import InitializeClass

from TemplateAdder import addTemplates2Class

from TemplateAdder import addTemplates2Class
from Constants import ISSUETHREAD_METATYPE

try:
from collections import defaultdict
Expand Down Expand Up @@ -53,59 +53,61 @@ def __deepcopy__(self, memo):
def __repr__(self):
return 'defaultdict(%s, %s)' % (self.default_factory,
dict.__repr__(self))


MONTH_NAMES = [DateTime(2010, i+1, 1).strftime('%B')
for i in range(12)]

#----------------------------------------------------------------------------

class ConfigurationError(Exception):
pass

class SummaryBase(object):

security = ClassSecurityInfo()

def getSummaryPageTitle(self, month=None, year=None):
month, year = self._get_and_check_month_and_year(month, year)
return "Summary for %s, %s" % (month, year)

def getSummaryPageURL(self, month=None, year=None):
month, year = self._get_and_check_month_and_year(month, year)
return self.getRootURL() + '/%s/%s' % (year, month)

def getIssueSummaryByMonth(self, month=None, year=None):
"""return a list of issues grouped by statuses"""
month, year = self._get_and_check_month_and_year(month, year)

month_nr = MONTH_NAMES.index(month)+1
start_date = DateTime(year, month_nr, 1)
if month_nr == 12:
end_date = DateTime(year + 1, 1, 1)
else:
end_date = DateTime(year, month_nr + 1, 1)
end_date -= 1


trackers = [self] + self._getBrothers()


base_search = {'meta_type':'Issue Tracker Issue'}

added_issues = []

# keys status, values list of issue objects
modified_issues = defaultdict(list)

for tracker in trackers:
catalog = tracker.getCatalog()

_has_issuedate_index = catalog._catalog.indexes.has_key('issuedate')

if _has_issuedate_index:
search = dict(base_search,
issuedate={'query': [start_date, end_date],
'range':'min:max'})
else:
search = dict(base_search)

for brain in catalog(sort_on=_has_issuedate_index and 'issuedate' or 'modifydate',
**search):
try:
Expand All @@ -120,9 +122,9 @@ def getIssueSummaryByMonth(self, month=None, year=None):
continue
if issue.getIssueDate() > end_date:
continue

added_issues.append(issue)

search = dict(base_search,
modifydate={'query': [start_date, end_date],
'range':'min:max'})
Expand All @@ -135,22 +137,22 @@ def getIssueSummaryByMonth(self, month=None, year=None):
continue
if issue.getIssueDate() == issue.getModifyDate():
continue

# now we need to find the thread that made this status change
for thread in sorted(issue.getThreadObjects(),
lambda x,y: cmp(y.threaddate, x.threaddate)):
# XXX:
# A thought would be to include all threads where there is a change
# Food for thought

if thread.getTitle().lower().startswith('changed status'):
break
else:
# the issue doesn't have any threads!
continue

modified_issues[issue.getStatus()].append((issue, thread))

# next we need to flatten the dict modified_issues by the order statuses
# are defined
statuses = [x[0] for x in self.getStatusesMerged(aslist=True)]
Expand All @@ -164,28 +166,28 @@ def sorter_by_status(x, y):
except ValueError:
j = -1
return cmp(i, j)

modified_issues = sorted(modified_issues.items(), sorter_by_status)

# clear some memory
trackers = None
return dict(added_issues=added_issues,
modified_issues=modified_issues)


def getPrevNextMonthURLs(self, month=None, year=None):
month, year = self._get_and_check_month_and_year(month, year)

month_nr = MONTH_NAMES.index(month)+1
date = datetime.date(year, month_nr, 1)
prev_date = date - datetime.timedelta(days=1)
next_date = date + datetime.timedelta(days=31) # e.g. Jan 1 + 31 days is some time in Feb

if self.absolute_url_path() == '/':
prev_url = prev_date.strftime('/%Y/%B')
else:
prev_url = self.absolute_url_path() + prev_date.strftime('/%Y/%B')

if next_date > datetime.date.today():
next_url = None
else:
Expand All @@ -194,8 +196,8 @@ def getPrevNextMonthURLs(self, month=None, year=None):
else:
next_url = self.absolute_url_path() + next_date.strftime('/%Y/%B')
return prev_url, next_url


def _get_and_check_month_and_year(self, month, year):
if month is None:
month = DateTime().strftime('%B')
Expand All @@ -207,41 +209,93 @@ def _get_and_check_month_and_year(self, month, year):
year = int(year)
except ValueError:
raise NotFound("Unrecognized year number")

return month, year

def getUniqueIssueClassname(self, issue):
"""the reason this method is necessary is because we can't construct a
class name (for the HTML) based on just the issue ID since that's only
unique per issuetracker. If you use brother issuetrackers your page
might eventually feature both issue foo/010 and bar/020.
I could make this unique class name depend on the parent the issue is
in but if the issue is in a BTreeFolder I have to use aq_parent more
than once and it gets messy.
"""
return "i%s-%s" % (issue.getId(), int(issue.getIssueDate()))

def isFirstStatusOption(self, status):
for option in [x[0] for x in self.getStatusesMerged(aslist=True)]:
if option.lower() == status.lower():
return True
return False

def isLastStatusOption(self, status):
for option in reversed([x[0] for x in self.getStatusesMerged(aslist=True)]):
if option.lower() == status.lower():
return True
return False




def getTotalActualFollowupHours(self, month=None, year=None):
assert self.UseFollowupActualTime()
month, year = self._get_and_check_month_and_year(month, year)
month_nr = MONTH_NAMES.index(month)+1
start_date = DateTime(year, month_nr, 1)
if month_nr == 12:
end_date = DateTime(year + 1, 1, 1)
else:
end_date = DateTime(year, month_nr + 1, 1)

trackers = [self] + self._getBrothers()
base_search = {'meta_type': ISSUETHREAD_METATYPE}

total = 0.0
threads = list()

for tracker in trackers:
catalog = tracker.getCatalog()

_has_threaddate_index = catalog._catalog.indexes.has_key('threaddate')

if _has_threaddate_index:
search = dict(base_search,
threaddate={'query': [start_date, end_date],
'range':'min:max'})
else:
search = dict(base_search)
raise ConfigurationError(
"Can't sum followup actual time without threaddate. Run UpdateEverything()")

for brain in catalog(**search):
try:
thread = brain.getObject()
except KeyError:
warnings.warn("ZCatalog (%s) out of date. Press Update Everything" %\
catalog.absolute_url_path())
continue
if not thread.getActualTimeHours():
continue

if not _has_threaddate_index:
# we have to manually filter it
if thread.getThreadDate() < start_date:
continue
if issue.getThreadDate() > end_date:
continue

total += thread.getActualTimeHours()
threads.append(thread)

count_threads = len(threads)
return total


zpts = (
'zpt/show_summary',
)



addTemplates2Class(SummaryBase, zpts, extension='zpt')

InitializeClass(SummaryBase)
4 changes: 1 addition & 3 deletions IssueTrackerProduct/Thread.py
Expand Up @@ -277,7 +277,7 @@ def index_object(self, idxs=None):
# because I don't want to put mutable defaults in
# the keyword arguments
idxs = ['comment','meta_type','fromname','email','path',
'modifydate', 'filenames']
'modifydate', 'threaddate', 'filenames']
else:
# No matter what, when indexing you must always include 'path'
# otherwise you might update indexes without putting the object
Expand All @@ -287,8 +287,6 @@ def index_object(self, idxs=None):
if 'path' not in idxs:
idxs.append('path')



indexes = catalog._catalog.indexes
if 'filenames' not in idxs and indexes.has_key('filenames'):
idxs.append('filenames')
Expand Down

0 comments on commit 15d8a66

Please sign in to comment.