Last active
August 2, 2024 14:42
-
-
Save noandrea/734aec5cbfd94d1debcb2ead213fd14d to your computer and use it in GitHub Desktop.
Grooming for issues
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/home/andrea/.local/share/miniconda3/bin/python | |
# Installation | |
# | |
# python dependencies | |
# pip3 install jira questionary | |
# | |
# create the env var to connect to jira | |
# export [email protected] | |
# export JIRA_TOKEN=*** | |
# export JIRA_HOST=https://MYORG.atlassian.net | |
# https://jira.readthedocs.io/ | |
from jira import JIRA | |
import os | |
import datetime | |
import questionary | |
import json | |
def get_client(): | |
user = os.getenv("JIRA_USER") | |
token = os.getenv("JIRA_TOKEN") | |
host = os.getenv("JIRA_HOST") | |
return JIRA(server=host, basic_auth=(user, token)) | |
def to_date(date_string): | |
return datetime.datetime.fromisoformat(date_string) | |
def main(): | |
client = get_client() | |
priority_weight = { | |
"Lowest": 1000, | |
"Low": 500, | |
"Medium": 100, | |
"High": 50, | |
"Highest": 10 | |
} | |
# the query is to get all open issues and re-prioritize them | |
q = 'project = "MOON" and status in (Triage) ORDER BY created DESC' | |
print(f"JQL:\n {q}") | |
# do_update = questionary.confirm("Do you want to update the issues?", default=False).ask() | |
do_update = False | |
options = {"Top 5": 5, "Top 10": 10, "Top 20": 20, "All": None} | |
howMany = questionary.select( | |
"How many issues do you want to see?", | |
choices=options, | |
use_shortcuts=True | |
).ask() | |
issues = [] | |
for issue in client.search_issues(q, maxResults=None): | |
age = (datetime.datetime.now(tz=datetime.timezone.utc) - to_date(issue.fields.updated)).days | |
weight = priority_weight[issue.fields.priority.name] | |
score = weight * age | |
issues.append({"key": issue.key, "score": score, "_age": age, "_weight": weight, "_priority": issue.fields.priority.name, "_updated": issue.fields.updated}) | |
if do_update: | |
client.update(issue.key, fields={"priority": {"name": "High"}}) | |
# sort the issues by score | |
issues.sort(key=lambda x: x.get("score", 0), reverse=False) | |
print(f"{'Key':10} | {'Score':10} | {'URL'}") | |
for record in issues[-options[howMany]:]: | |
print(f'{record["key"]:10} | {record["score"]:10.0f} | {os.getenv("JIRA_HOST")}/browse/{record["key"]}') | |
#print(f'{record["key"]:10} [{record["score"]:10.0f}] ({record["_priority"]} {record["_age"]} {record["_weight"]} {record["_updated"]})') | |
print(f"{'Total':10} | {issues.__len__():10.0f}") | |
# client.update(fields={"priority": {"name": "High"}}) | |
# Story points per release per user | |
def get_story_points_per_release_per_user(): | |
STORY_POINT_ESTIMATE_FIELD = "customfield_10016" | |
client = get_client() | |
# list the releases | |
releases = client.project_versions("MOON") | |
releases.sort(key=lambda x: x.raw.get("releaseDate", "5000-01-01"), reverse=True) | |
do_select_release = True | |
while do_select_release: | |
# select the release | |
selected = questionary.checkbox("Select the release", choices=[release.name for release in releases]).ask() | |
if selected.__len__() == 0: | |
print("at least one release must be selected") | |
continue | |
# get the issue for the release | |
q = f'project = "MOON" and fixVersion in ({",".join(selected)}) and type != Epic' | |
print(f"JQL:\n {q}") | |
# "user": {"story_points": 0, "issues": 0} | |
metrics_by_user = {} | |
for issue in client.search_issues(q): | |
sp = issue.get_field(STORY_POINT_ESTIMATE_FIELD) or 0 | |
if sp == 0: | |
print(f'⚠️ issue {os.getenv("JIRA_HOST")}/browse/{issue.key} has no story points {issue.fields.creator.displayName}') | |
assignee = issue.get_field("assignee") | |
if assignee is None: | |
print(f'⚠️ issue {os.getenv("JIRA_HOST")}/browse/{issue.key} has no assignee') | |
assignee = "Unassigned" | |
else: | |
assignee = assignee.displayName | |
data = metrics_by_user.get(assignee, {"story_points": 0, "issues": 0}) | |
data["story_points"] += sp | |
data["issues"] += 1 | |
metrics_by_user[assignee] = data | |
# transform the dictionary into a list | |
# sort the list by story points | |
summary = sorted([{'n': k, 'p': v['story_points'], 'i': v['issues']} for k,v in metrics_by_user.items()], key=lambda x: x["p"], reverse=True) | |
print(f'Summary for version ({",".join(selected)})') | |
print(f"{'User':20s} | {'Story Points':15s} | {'Issues':10s}") | |
print("-" * 50) | |
sum_story_points = 0 | |
sum_issues = 0 | |
for record in summary: | |
sum_story_points += record["p"] | |
sum_issues += record["i"] | |
print(f'{record["n"]:20s} | {record["p"]:15.0f} | {record["i"]:10.0f}') | |
print("-" * 50) | |
print(f"{'Total':20s} | {sum_story_points:15.0f} | {sum_issues:10.0f}") | |
do_select_release = questionary.confirm("Do you want to select another release?").ask() | |
if __name__ == '__main__': | |
opt1 = "Re-prioritize issues" | |
opt2 = "Analyze release story points" | |
answer = questionary.select( | |
"Select the action", | |
choices=[opt1, opt2], | |
use_shortcuts=True | |
).ask() | |
if answer == opt1: | |
main() | |
elif answer == opt2: | |
get_story_points_per_release_per_user() | |
else: | |
print("Invalid option") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment