Skip to content

Instantly share code, notes, and snippets.

@qrkourier
Last active December 4, 2020 08:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save qrkourier/b33af7fc9825b2d694d8f2d9fb0a075c to your computer and use it in GitHub Desktop.
Save qrkourier/b33af7fc9825b2d694d8f2d9fb0a075c to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
#
# blame(@qrkourier)
#
from jira import JIRA
import os
import sys
from argparse import ArgumentParser
import webbrowser
def unblocked(project, issues):
"""
issues that need attention because they are new or were blocked
"""
interesting = list()
for i in issues:
# nested try/except here is used as a control structure to exclude uninteresting issues that
# are blocked by an issue that is not done because I didn't know of another way to break out
# of a particular level of a nested loop
try:
if i['fields']['issuetype']['name'] not in ['Epic', 'Sub-task']:
issuelinks = i['fields']['issuelinks']
if len(issuelinks) > 0:
try:
for l in issuelinks:
for d in ['inward','outward']:
if l['type'][d] == "is blocked by" \
and d+'Issue' in l \
and l[d+'Issue']['fields']['status']['statusCategory']['name'] != "Done":
raise Exception("linked issue not done")
except Exception as e:
raise Exception("Not interesting: ", e)
# interesting if no links or previously blocked by a linked issue that is now done
interesting += [i['key']]
except Exception as e:
#print("SKIP "+i['key'])
pass
return(interesting)
def blocking(project, issues):
"""
issues that need attention because they are blocking another project
"""
interesting = list()
for i in issues:
try:
issuelinks = i['fields']['issuelinks']
if len(issuelinks) > 0:
for l in issuelinks:
for d in ['inward','outward']:
if l['type'][d] == "blocks" \
and d+'Issue' in l \
and l[d+'Issue']['fields']['status']['statusCategory']['name'] != "Done" \
and l[d+'Issue']['key'].split('-')[0] != project.upper():
interesting += [i['key']]
else:
raise Exception("Not interesting: no issue links")
except Exception as e:
#print("SKIP "+i['key'])
pass
return(interesting)
def blocked(project, issues):
"""
issues that need attention because they are blocked
"""
interesting = list()
for i in issues:
issuelinks = i['fields']['issuelinks']
if len(issuelinks) > 0:
for l in issuelinks:
for d in ['inward','outward']:
if l['type'][d] == "is blocked by" \
and d+'Issue' in l \
and l[d+'Issue']['fields']['status']['statusCategory']['name'] != "Done":
interesting += [i['key']]
return(interesting)
def makeFilterQuery(keys):
if len(keys) > 0:
query = "issuekey in ("+keys[0]
for i in keys[1:]:
query += ","+i
query += ")"
else:
query = "project is EMPTY"
return(query)
def excludeLabels(labels, issues):
uninteresting = list()
for i in issues:
for l in labels:
if l in i['fields']['labels']:
uninteresting += [i['key']]
interesting = list()
for i in issues:
if i['key'] not in uninteresting:
interesting += [i]
return(interesting)
PARSER = ArgumentParser()
PARSER.add_argument(
'jira_url',
help='JIRA URL e.g. "https://example.atlassian.net"'
)
PARSER.add_argument(
'project',
help='JIRA project e.g. "TOW"'
)
PARSER.add_argument(
'policy',
help='one policy that decides which issues are interesting',
choices=['unblocked','blocked','blocking']
)
PARSER.add_argument(
'--exclude-labels',
dest='exclude_labels',
nargs='+',
help='Exclude issues with one or more labels'
)
ACTIONS.add_argument(
'--no-list',
dest='list_results',
action='store_false',
default=True,
help='suppress printing the URL for each issue'
)
ACTIONS = PARSER.add_argument_group(title='ACTIONS')
ACTIONS.add_argument(
'--filter-id',
dest='filter_id',
type=int,
help='Numeric ID of the JIRA issue filter to update with the list of issues'
)
ACTIONS.add_argument(
'--open',
dest='open_browser',
action='store_true',
default=False,
help='open JIRA in a web browser for each issue'
)
ARGS = PARSER.parse_args()
JIRA_URL = ARGS.jira_url
J = JIRA(
{'server': JIRA_URL},
basic_auth=(
os.environ['ATLASSIAN_API_USER'],
os.environ['ATLASSIAN_API_TOKEN']
)
)
JQL = 'project = "'+ARGS.project.upper()+'" AND resolution = Unresolved AND Sprint is EMPTY'
DATA = J.search_issues(JQL, json_result=True)
ISSUES = DATA['issues']
if ARGS.exclude_labels:
ISSUES = excludeLabels(ARGS.exclude_labels, ISSUES)
# call the eponymous function for the given policy e.g. "unblocked",
# passing in the dict of potentially interesting ISSUES
INTERESTING = globals()[ARGS.policy](ARGS.project, ISSUES)
# we'll set False if we do anything interesting so we can complain if nothing was done
NOOP=True
if ARGS.list_results:
# print the policy as a heading with issue count
print('\nfound '+str(len(INTERESTING))+' for '+ARGS.policy.upper())
# slice the list of issues into batches
for i in INTERESTING:
u = '\t'+JIRA_URL+'/browse/'+i
print('\t'+u)
NOOP=False
if ARGS.filter_id:
# build a query like "issuekey in (TOW-123, TOW-456)" from the INTERESTING results
filter_query = makeFilterQuery(INTERESTING)
try:
# update the filter with the new query which is a list of issue keys
filter_result = J.update_filter(
filter_id=ARGS.filter_id,
name=ARGS.project.upper()+' '+ARGS.policy,
description="updated by /systems-tools/scripts/backlog.py",
jql=filter_query
)
print('\nsent '+str(len(INTERESTING))+' to '+JIRA_URL+'/issues/?filter='+str(ARGS.filter_id))
except:
raise
NOOP=False
if ARGS.open_browser:
# print the policy as a heading with issue count
print('\nopening '+str(len(INTERESTING))+' for '+ARGS.policy.upper())
# slice the list of issues into batches
batches = [INTERESTING[i:i + 4] for i in range(0, len(INTERESTING), 4)]
for b in batches:
for i in b:
u = '\t'+JIRA_URL+'/browse/'+i
print('\t'+u)
webbrowser.open(u, new = 2)
input("Press Enter to continue...")
NOOP=False
if NOOP:
print("\nNothing was done.\n")
PARSER.print_help()
@qrkourier
Copy link
Author

qrkourier commented Sep 4, 2019

python3 backlog.py https://example.atlassian.net TOW unblocked --filter-id 19628 --exclude-labels groomed

found 5 for UNBLOCKED
                https://example.atlassian.net/browse/TOW-624
                https://example.atlassian.net/browse/TOW-623
                https://example.atlassian.net/browse/TOW-622
                https://example.atlassian.net/browse/TOW-620
                https://example.atlassian.net/browse/TOW-619

sent 5 to https://example.atlassian.net/issues/?filter=19628

@qrkourier
Copy link
Author

./backlog.py --help
usage: backlog.py [-h] [--exclude-labels EXCLUDE_LABELS [EXCLUDE_LABELS ...]]
                  [--filter-id FILTER_ID] [--open] [--no-list]
                  jira_url project {unblocked,blocked,blocking}

positional arguments:
  jira_url              JIRA URL e.g. "https://example.atlassian.net"
  project               JIRA project e.g. "TOW"
  {unblocked,blocked,blocking}
                        one policy that decides which issues are interesting

optional arguments:
  -h, --help            show this help message and exit
  --exclude-labels EXCLUDE_LABELS [EXCLUDE_LABELS ...]
                        Exclude issues with one or more labels
  --no-list             suppress printing the URL for each issue

ACTIONS:
  --filter-id FILTER_ID
                        Numeric ID of the JIRA issue filter to update with the
                        list of issues
  --open                open JIRA in a web browser for each issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment