From 3bd7dcc189d26d6a576b17d325d4d43e87fbe21d Mon Sep 17 00:00:00 2001 From: Angus Salkeld Date: Wed, 13 May 2015 16:03:26 +1000 Subject: [PATCH] Automatically generate a dash based on "In Progress" bugs This adds a script to get the "In Progress" bugs for a particular project and builds prints a URL for the user. $ ./gerrit-bug-dash --milestone kilo-rc1 --tag kilo-rc-potential heat Some notes: - it uses launchpadlib to talk to launchpad - The presentation of the dashboard could be improved but this is just a starting point. - The caching could be better. What I wanted was to know was "what do I need to review for rc1" (i.e. what are the bugs that are targeted for kilo-rc1 that are in progress and have reviews up). This scratches my itch, but will happily do some work on it if others want it. Change-Id: I4a97d59631ac9cd344206c6cc48164d6a0d7e57c --- gerrit-bug-dash | 1 + gerrit_dash_creator/cmd/bugs.py | 176 ++++++++++++++++++++++++++++++++ requirements.txt | 1 + setup.cfg | 1 + 4 files changed, 179 insertions(+) create mode 120000 gerrit-bug-dash create mode 100755 gerrit_dash_creator/cmd/bugs.py diff --git a/gerrit-bug-dash b/gerrit-bug-dash new file mode 120000 index 0000000..9cdf9d5 --- /dev/null +++ b/gerrit-bug-dash @@ -0,0 +1 @@ +gerrit_dash_creator/cmd/bugs.py \ No newline at end of file diff --git a/gerrit_dash_creator/cmd/bugs.py b/gerrit_dash_creator/cmd/bugs.py new file mode 100755 index 0000000..e567f96 --- /dev/null +++ b/gerrit_dash_creator/cmd/bugs.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import argparse +import os +import sys + +from launchpadlib import launchpad +import six +from six.moves import configparser + +from gerrit_dash_creator.cmd import creator + +CACHE_DIR = os.path.expanduser('~/.cache/launchpadlib/') +SERVICE_ROOT = 'production' + + +def print_dash_url(opts, bugs): + config = configparser.ConfigParser() + config.add_section('dashboard') + title = ','.join(opts.projects) + if opts.milestone: + title += ' milestone:%s' % opts.milestone + if opts.tag: + title += ' AND' + if opts.tag: + title += ' tag:%s' % opts.tag + + config.set('dashboard', 'title', title) + config.set('dashboard', 'description', 'Bug Fix Inbox') + + proj_q = ['project:openstack/%s' % proj for proj in opts.projects] + config.set('dashboard', 'foreach', + '(%s) status:open ' % ' OR '.join(proj_q)) + + for label in bugs: + for prio in bugs[label]: + if len(bugs[label][prio]) == 0: + continue + sect = 'section "%s Importance %s"' % (label, prio) + if prio == 'None': + sect = 'section "%s"' % label + config.add_section(sect) + config.set(sect, 'query', + ' OR '.join(['change:%s' % bug + for bug in bugs[label][prio]])) + + print(creator.generate_dashboard_url(config)) + + +def pretty_milestone(milestone_url): + if milestone_url is None: + return 'Unassigned' + # https://api.launchpad.net/1.0/heat/+milestone/next: + return str(milestone_url).split('/')[-1] + + +def review_id_from_bug(bug, project_name): + reviews = set() + reviews_ignored = set() + for msg in bug.bug.messages: + try: + lines = six.text_type(msg.content).split('\n') + except UnicodeEncodeError: + print('non-ascii in bug %s' % bug.web_link) + continue + + proposed = 'ix proposed to %s' % project_name + merged = 'ix merged to %s' % project_name + abandoned = 'Change abandoned on %s' % project_name + if proposed in msg.subject: + for line in lines: + if 'Review: ' in line: + reviews.add(line.split('/')[-1]) + if merged in msg.subject or abandoned in msg.subject: + for line in lines: + if 'Review' in line: + reviews_ignored.add(line.split('/')[-1]) + live_reviews = (reviews - reviews_ignored) + if len(live_reviews) == 0: + print('bug %s has no reviews set to Triaged state' % bug.web_link) + return live_reviews + + +def get_options(): + """Parse command line arguments and options.""" + parser = argparse.ArgumentParser( + description='Create a Gerrit dashboard URL from launchpad ' + '"In Progress bugs') + parser.add_argument('projects', nargs='+', + metavar='projects', + help='Launchpad Projects') + parser.add_argument('--milestone', default=None, + help='Project Milestone') + parser.add_argument('--tag', default=None, + help='Project Tag') + return parser.parse_args() + + +def process_project(lp, opts, project_name, bugs): + project = lp.projects[project_name] + + if opts.tag is None and opts.milestone is not None: + from_milestone = project.getMilestone(name=opts.milestone) + if not from_milestone: + print('Origin milestone %s does not exist' % + opts.milestone) + sys.exit(1) + + review_bugtasks = from_milestone.searchTasks(status=['In Progress']) + else: + review_bugtasks = project.searchTasks(status=['In Progress']) + + for bug in review_bugtasks: + importance = bug.importance + milestone = pretty_milestone(bug.milestone) + tags = bug.bug.tags + + label = None + if opts.tag is not None: + if opts.tag in tags: + label = 'Tag:%s' % opts.tag + + if opts.milestone is not None: + if milestone == opts.milestone: + label = 'Milestone:%s' % milestone + + if opts.tag is None and opts.milestone is None: + # just place by milestone + label = 'Milestone:%s' % milestone + importance = 'None' + + if label is None: + continue + + if label not in bugs: + bugs[label] = {} + + if importance not in bugs[label]: + bugs[label][importance] = [] + + for rev_no in review_id_from_bug(bug, project_name): + bugs[label][importance].append(rev_no) + print('[%s] %s -> %s' % (importance, + bug.web_link, rev_no)) + + +def main(): + """Entrypoint.""" + + opts = get_options() + lpad = launchpad.Launchpad.login_anonymously(sys.argv[0], + SERVICE_ROOT, + CACHE_DIR) + bugs = {} + for proj in opts.projects: + process_project(lpad, opts, proj, bugs) + + print('') + print_dash_url(opts, bugs) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/requirements.txt b/requirements.txt index ffa4e7d..7dc0454 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ argparse jinja2 pbr>=0.6,!=0.7,<1.0 six>=1.7.0 +launchpadlib diff --git a/setup.cfg b/setup.cfg index 7e2276c..e78fa5d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,6 +33,7 @@ data_files = [entry_points] console_scripts = gerrit-dash-creator = gerrit_dash_creator.cmd.creator:main + gerrit-bug-dash = gerrit_dash_creator.cmd.bugs:main [build_sphinx] source-dir = doc/source