diff --git a/.gitignore b/.gitignore index 0d20b64..361ab6c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *.pyc +venv +foo.md diff --git a/Makefile.factory b/Makefile.factory new file mode 100644 index 0000000..507d86c --- /dev/null +++ b/Makefile.factory @@ -0,0 +1,16 @@ +install: + test -d ~/node_modules/md2gslides/bin/ || npm install md2gslides + test -d venv || virtualenv venv + venv/bin/pip install -Ur requirements.txt + +build: install + venv/bin/python finishline.py \ + --insecure \ + --server https://projects.engineering.redhat.com \ + --project FACTORY \ + --title "Factory 2.0, Sprint Review" \ + --subtitle "Sprint #39" \ + --template threebean/slides.md > foo.md + +upload: build + ~/node_modules/md2gslides/bin/md2gslides.js foo.md -e -a 1Riz9-XeqElrXlnu_vB5JS0nV-FoDv_oTmTAcl3emyuw diff --git a/finishline.py b/finishline.py index f3be80a..f4907a3 100644 --- a/finishline.py +++ b/finishline.py @@ -12,9 +12,11 @@ import bs4 import docutils.examples -import requests import jinja2 +import jira + +date_format = '%Y-%m-%d' custom_filters = { 'slugify': lambda x: x.lower().replace(' ', '-'), @@ -24,19 +26,31 @@ def scrape_links(session, args): - response = session.get(args.url) + response = session.get(args.server) soup = bs4.BeautifulSoup(response.text, 'html5lib') return [l.text for l in soup.find(id='content').findAll('a')[5:]] def parse_arguments(): + two_weeks = datetime.timedelta(days=14) + default_since = (datetime.date.today() - two_weeks).strftime(date_format) parser = argparse.ArgumentParser() - parser.add_argument('--url', help='HTTP-accessible directory where sprint videos and descriptions are stored.') + parser.add_argument('--server', help='server to your JIRA instance.') + parser.add_argument('--insecure', help='Do not verify SSL certs.', + default=False, action='store_true') + parser.add_argument('--project', help='JIRA project to report on.') + parser.add_argument('--since', help='Past date from which to pull data.', + default=default_since) parser.add_argument('--title', help='Title of the report.') + parser.add_argument('--subtitle', help='Subtitle of the report.') parser.add_argument('--template', help='Path to a template for output.') + parser.add_argument('--epicfield', help='Epic customfield key.', + default='customfield_10006') args = parser.parse_args() - if not args.url: - raise ValueError('--url is required') + if not args.server: + raise ValueError('--server is required') + if not args.project: + raise ValueError('--project is required') if not args.title: raise ValueError('--title is required') if not args.template: @@ -44,61 +58,61 @@ def parse_arguments(): return args -def prepare(session, args, links): - get = lambda filename: session.get(args.url + '/' + filename).text +def render(args, data): + env = jinja2.Environment( + loader=jinja2.FileSystemLoader('templates'), + ) + env.filters.update(custom_filters) + template = env.get_template(args.template) - try: - links.remove('readme.rst') - summary = get('readme.rst') - except ValueError: - summary = '' + data = data.copy() - collated = collections.defaultdict(dict) - for link in links: - key, extension = link.rsplit('.') - collated[key][extension] = link + data['today'] = datetime.datetime.utcnow().strftime(date_format) - entries = [] - for key, items in collated.items(): - try: - username, rest = key.split('-', 1) - except ValueError: - username, rest = key.split('_', 1) + data.update(args._get_kwargs()) - heading = "%s, by %s" % (rest, username) + return template.render(**data) - extensions = items.keys() - try: - extensions.remove('rst') - body = get(items['rst']) - except ValueError: - body = None +def pull_issues(client, args): + tmpl = ( + 'project = %s' + ' AND resolution is not EMPTY' + ' AND resolutiondate >= %s' + ' AND status != Dropped' + ) + query = tmpl % (args.project, args.since) + issues = client.search_issues(query) + for issue in issues: + yield issue - assert(len(extensions) == 1) - link = items[extensions[0]] - entries.append((heading, body, args.url + '/' + link,)) +def get_epic_details(client, key): + if not key: + return None + epic = client.issue(key) - entries.sort() + epic.image_url = 'https://placekitten.com/1600/900' - return dict(summary=summary, entries=entries) + epic.status_update = 'foo bar' + epic.status_update_date = '2017-01-01' + return epic -def render(args, data): - env = jinja2.Environment( - loader=jinja2.FileSystemLoader('templates'), - ) - env.filters.update(custom_filters) - template = env.get_template(args.template) - data = data.copy() +def collate_issues(client, args, issues): + epics = {} + by_epic = collections.defaultdict(set) + for issue in issues: + epic = issue.raw['fields'][args.epicfield] - fmt = "%Y/%m/%d %H:%M:%S" - data['current_date'] = datetime.datetime.now().strftime(fmt) + # Enrich with details + if not epic in epics: + epics[epic] = get_epic_details(client, epic) - data.update(args._get_kwargs()) + # Associate the issue with the enriched epic. + by_epic[epic].add(issue) - return template.render(**data) + return dict(epics=epics, by_epic=by_epic) if __name__ == '__main__': @@ -107,11 +121,14 @@ def render(args, data): args = parse_arguments() - session = requests.session() - - links = scrape_links(session, args) + client = jira.client.JIRA( + server=args.server, + options=dict(verify=not args.insecure), + kerberos=True, + ) - data = prepare(session, args, links) + issues = pull_issues(client, args) + data = collate_issues(client, args, issues) output = render(args, data) print output.encode('utf-8') diff --git a/requirements.txt b/requirements.txt index a7a2f73..7d382a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,5 @@ docutils html5lib requests jinja2 +jira +requests_kerberos diff --git a/templates/threebean/slides.md b/templates/threebean/slides.md new file mode 100644 index 0000000..5d8c3a3 --- /dev/null +++ b/templates/threebean/slides.md @@ -0,0 +1,22 @@ +# {{title}} +{% if subtitle %} +## {{subtitle}} +## {{today}} +### PnT DevOps +{% endif %} +--- +{% for key, epic in epics.items() if epic %} +# [{{ epic.raw['fields']['customfield_10007'] }}]({{ server }}/browse/{{ key }}) + +{{ epic.raw['fields']['summary'] }} + +{% if epic.status_update %}**Status**: (*{{epic.status_update_date}}*) {{epic.status_update}} {% endif %} +{% for issue in by_epic[key] %} +* {{ issue.raw['fields']['summary'].replace('[', '').replace(']', ':') }} + [{{ issue.key }}]({{ server }}/browse/{{ issue.key }}) +{% endfor %} +{% if epic.image_url %}![]({{epic.image_url}}){.background} +{% endif %} +--- +{% endfor %} +# Thank you