← Back to team overview

launchpad-dev team mailing list archive

Hacky script to get critical bug breakdown for Launchpad

 

Hello Launchpadders,

I've attached a hacky script that gets the by-project breakdown of
critical bugs on launchpad-project.

Probably mostly useful for whoever is chairing the team lead meeting
and needs a quick way to get the data, as well as folk currently in
maintenance squads.

Current output:

$ python critical-bugs.py
launchpad         238
loggerhead         10
launchpad-buildd    2
lazr.delegates      1
lazr.restful        1
oops-tools          1
qa-tagger           1

Total: 254 (+34, -30 since 2011-03-09)

cheers,
jml

PS. Although the number of critical bugs has increased, that's because
we've been making great progress in reporting critical bugs that are
already there. i.e. it's an increase in reports, not defects. We're
making great progress in actually *fixing* the defects.

PPS. If you want to improve the script, please find a home for it &
share it with the list.
from datetime import datetime, timedelta, tzinfo
from operator import attrgetter
import os
import re
from StringIO import StringIO

from launchpadlib.launchpad import Launchpad
from launchpadlib import uris

CACHE_DIR = os.path.expanduser('~/.launchpadlib/cache')
SERVICE_ROOT = uris.LPNET_SERVICE_ROOT

CRITICAL = u'Critical'

CLOSED_STATUSES = [
    u"Invalid",
    u"Opinion",
    u"Expired",
    u"Won't Fix",
    u'Fix Released',
    ]


OPEN_STATUSES = [
    u"New",
    u"Incomplete",
    u"Confirmed",
    u"Triaged",
    u"In Progress",
    u"Fix Committed"]


ALL_STATUSES = OPEN_STATUSES + CLOSED_STATUSES

_title_re = re.compile(r'^.*: "(.*)"$')


class BugTask(object):

    def __init__(self, lp_task):
        self._lp_task = lp_task
        self.title = _title_re.match(lp_task.title).group(1)

    def __repr__(self):
        return '<BugTask %s/%s>' % (self.bug_id, self.target_name)

    def __str__(self):
        return '#%s: %s <%s:%s>' % (
            self.bug_id,
            self.title,
            self.target_name,
            self._lp_task.status,
            )

    @property
    def bug_id(self):
        return int(self._lp_task.bug_link.split('/')[-1])

    @property
    def date_created(self):
        return self._lp_task.date_created

    @property
    def target_name(self):
        return self._lp_task.bug_target_name

    def as_dict(self):
        return self._lp_task._wadl_resource.representation


class _UTC(tzinfo):
    """UTC"""

    def utcoffset(self, dt):
        return timedelta(0)

    def tzname(self, dt):
        return "UTC"

    def dst(self, dt):
        return timedelta(0)

UTC = _UTC()


def get_bug_stats(project, since):
    open_tasks = map(
        BugTask,
        project.searchTasks(
            importance=CRITICAL,
            status=OPEN_STATUSES,
        ))
    new_tasks = [t for t in open_tasks if t.date_created >= since]
    closed_tasks = [
        BugTask(t) for t in project.searchTasks(
            importance=CRITICAL,
            status=CLOSED_STATUSES,
            modified_since=since,
            )
        if t.date_closed >= since]
    return open_tasks, new_tasks, closed_tasks


def groupby(sequence, key):
    output = {}
    for thing in sequence:
        k = key(thing)
        if k in output:
            output[k].append(thing)
        else:
            output[k] = [thing]
    return output


def per_target(tasks):
    by_target = groupby(tasks, attrgetter('target_name')).items()
    return sorted(by_target, key=lambda (x, y): (-len(y), x))


def format_table(table):
    table = [map(str, row) for row in table]
    widths = [max([len(cell) for cell in column]) for column in zip(*table)]
    output = StringIO()
    for row in table:
        for column, cell in enumerate(row):
            width = widths[column] + 1
            try:
                float(cell)
            except ValueError:
                output.write(cell.ljust(width))
            else:
                output.write(cell.rjust(width))
        output.write('\n')
    return output.getvalue()


def main(args):
    launchpad = Launchpad.login_anonymously(
        'jml-crit-bug', SERVICE_ROOT, CACHE_DIR)
    lpp = launchpad.projects['launchpad-project']
    last_week = (datetime.now() - timedelta(days=7)).replace(tzinfo=UTC)
    open_tasks, new_tasks, closed_tasks = get_bug_stats(lpp, last_week)
    by_target = per_target(open_tasks)
    target_counts = [(target, len(tasks)) for target, tasks in by_target]
    print format_table(target_counts)
    print 'Total: %s (+%s, -%s since %s)' % (
        len(open_tasks), len(new_tasks), len(closed_tasks), last_week.date())


if __name__ == '__main__':
    main([])

Follow ups