Commit 13c7e332 authored by Thomas Lotze's avatar Thomas Lotze

refactoring: fetch and report in chunks by project

We didn't do this at first but it has become clear that projects are
completely independent in Gitlab. The downside is that we now need to
make assumptions about project ordering in the data passed from fetch to
report. Reporting has become much more readable, though, so that's
probably worth it.
parent 92bd843a
......@@ -3,11 +3,12 @@ from gitlab import Gitlab
def fetch(url, token, filter):
"""Yield all relevant time tracking entries, in no particular order.
"""Yield unordered sequences of time tracking entries, by project.
"""
gitlab = Gitlab(url, token)
projects = gitlab.projects.list(all=True)
projects.sort(key=lambda p: p.path_with_namespace)
# We iterate over all projects regardless of our filter settings. When
# filtering for a project name, we have no better way of searching a
......@@ -25,28 +26,31 @@ def fetch(url, token, filter):
None):
continue
issues = project.issues.list(all=True)
merge_requests = project.mergerequests.list(all=True)
yield project, fetch_project(project, filter)
for item in issues + merge_requests:
entries = defaultdict(float)
for author, date, seconds in fetch_times(item):
if filter['username'] not in (author, None):
continue
if not date.startswith(filter['month'] or ''):
continue
def fetch_project(project, filter):
issues = project.issues.list(all=True)
merge_requests = project.mergerequests.list(all=True)
entries[author, date] += seconds
for item in issues + merge_requests:
entries = defaultdict(float)
for (author, date), seconds in entries.items():
yield dict(
project=project,
item=item,
author=author,
date=date,
hours=round(seconds/3600, 2),
)
for author, date, seconds in fetch_times(item):
if filter['username'] not in (author, None):
continue
if not date.startswith(filter['month'] or ''):
continue
entries[author, date] += seconds
for (author, date), seconds in entries.items():
yield dict(
item=item,
author=author,
date=date,
hours=round(seconds/3600, 2),
)
def fetch_times(item):
......
......@@ -3,43 +3,41 @@ from gitlab.v4.objects import ProjectIssue
from gitlab.v4.objects import ProjectMergeRequest
def report(entries):
"""Aggregate an unordered sequence of time tracking entries into a report.
def report(entries_by_project):
"""Aggregate unordered sequences of time tracking entries into a report.
"""
total = sum(report_project(project, entries)
for project, entries in entries_by_project)
print('total: {:.2f}'.format(total))
def report_project(project, entries):
# When aggregating things by item, we cannot simply use the item as a dict
# key: Both issues and merge requests just use their iid as a hash and
# those number sets do overlap, so mixing them as keys in the same dict
# potentially loses data. Therefore, we use a tuple as item key, including
# an indicator of the item's type. At the same time we take a short cut
# and choose the tuple such that it makes for a meaningful sort key.
# Projects don't have this problem but treating them similarly rather than
# using a key parameter when sorting makes for simpler sorting of dict
# items and some kind of consistency. Not sure this is good style, though.
times = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
totals_by_project = defaultdict(float)
times = defaultdict(lambda: defaultdict(list))
totals_by_item = defaultdict(float)
for entry in entries:
project = entry['project']
project = (project.path_with_namespace, project)
item = entry['item']
item = ([ProjectIssue, ProjectMergeRequest].index(type(item)),
item.iid,
item.id,
item)
author = entry['author']
times[project][item][author].append((entry['date'], entry['hours']))
totals_by_project[project] += entry['hours']
times[item][author].append((entry['date'], entry['hours']))
totals_by_item[item] += entry['hours']
total = sum(totals_by_project.values())
total = sum(totals_by_item.values())
for project_key, times_by_item in sorted(times.items()):
project = project_key[-1]
print('{}: {:.2f}'.format(
project.name_with_namespace, totals_by_project[project_key]))
if total:
print('{}: {:.2f}'.format(project.name_with_namespace, total))
for item_key, times_by_author in sorted(times_by_item.items()):
for item_key, times_by_author in sorted(times.items()):
item = item_key[-1]
print('{}{}: {:.2f} ({})'.format(
4*' ',
......@@ -54,4 +52,4 @@ def report(entries):
print()
print('total: {:.2f}'.format(total))
return total
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment