Skip to content

Jira Automation (WIP - don't use yet)

# note untested
import os, requests
BASE = os.environ["JIRA_BASE_URL"]
EMAIL = os.environ["JIRA_EMAIL"]
TOKEN = os.environ["JIRA_API_TOKEN"]
PROJECT = os.environ["JIRA_PROJECT_KEY"]
s = requests.Session()
s.auth = (EMAIL, TOKEN)
s.headers = {"Accept": "application/json", "Content-Type": "application/json"}
def issue(summary, desc, parent=None):
p = {
"fields": {
"project": {"key": PROJECT},
"summary": summary,
"description": desc or "",
"issuetype": {"name": "Sub-task" if parent else "Task"},
}
}
if parent:
p["fields"]["parent"] = {"key": parent}
r = s.post(f"{BASE}/rest/api/3/issue", json=p)
r.raise_for_status()
return r.json()["key"]
def create_tree(data):
stack = [(None, data)]
created = {}
idx = 0
while idx < len(stack):
parent_key, node = stack[idx]
idx += 1
key = issue(node["summary"], node.get("description"), parent_key)
created[id(node)] = key
for c in node.get("children", []):
stack.append((key, c))
return created[id(data)]
if __name__ == "__main__":
tasks = {
"summary": "Parent",
"children": [
{"summary": "A", "children": [{"summary": "A1"}, {"summary": "A2"}]},
{"summary": "B"},
],
}
print("Root:", create_tree(tasks))
# this has extra code in it
import os
import requests
# ---- Jira config ----
BASE = os.environ["JIRA_BASE_URL"]
EMAIL = os.environ["JIRA_EMAIL"]
TOKEN = os.environ["JIRA_API_TOKEN"]
PROJECT = os.environ.get("JIRA_PROJECT_KEY", "")
DONE_TRANSITION_NAME = os.environ.get("JIRA_DONE_TRANSITION_NAME", "Done")
s = requests.Session()
s.auth = (EMAIL, TOKEN)
s.headers = {"Accept": "application/json", "Content-Type": "application/json"}
# ---- Basic helpers ----
def issue(summary, desc=None, parent=None):
payload = {
"fields": {
"project": {"key": PROJECT} if PROJECT else None,
"summary": summary,
"description": desc or "",
"issuetype": {"name": "Sub-task" if parent else "Task"},
}
}
if not PROJECT:
payload["fields"].pop("project")
if parent:
payload["fields"]["parent"] = {"key": parent}
r = s.post(f"{BASE}/rest/api/3/issue", json=payload)
r.raise_for_status()
return r.json()["key"]
def get_issue(issue_key):
r = s.get(f"{BASE}/rest/api/3/issue/{issue_key}")
r.raise_for_status()
return r.json()
# ---- Transition helpers (closing issues) ----
def get_transitions(issue_key):
r = s.get(f"{BASE}/rest/api/3/issue/{issue_key}/transitions")
r.raise_for_status()
return r.json().get("transitions", [])
def transition_issue(issue_key, transition_name=DONE_TRANSITION_NAME):
transitions = get_transitions(issue_key)
transition_id = None
for t in transitions:
if t["name"].lower() == transition_name.lower():
transition_id = t["id"]
break
if not transition_id:
raise RuntimeError(f"No transition named '{transition_name}' for {issue_key}")
payload = {"transition": {"id": transition_id}}
r = s.post(f"{BASE}/rest/api/3/issue/{issue_key}/transitions", json=payload)
r.raise_for_status()
# ---- Subtask search + unresolved count ----
def get_all_subtasks(parent_key):
all_issues = []
start_at = 0
max_results = 100
jql = f'parent = "{parent_key}"'
while True:
params = {
"jql": jql,
"startAt": start_at,
"maxResults": max_results,
"fields": "summary,status,resolution",
}
r = s.get(f"{BASE}/rest/api/3/search", params=params)
r.raise_for_status()
data = r.json()
issues = data.get("issues", [])
all_issues.extend(issues)
if start_at + len(issues) >= data.get("total", 0):
break
start_at += len(issues)
return all_issues
def count_unresolved_subtasks(parent_key):
subtasks = get_all_subtasks(parent_key)
unresolved = []
for i in subtasks:
fields = i.get("fields", {})
resolution = fields.get("resolution")
status_cat = (fields.get("status") or {}).get("statusCategory") or {}
is_done_category = status_cat.get("key") == "done"
if resolution is None and not is_done_category:
unresolved.append(i)
return len(unresolved), unresolved
# ---- Close parent + subtasks, then report unresolved ----
def close_parent_and_subtasks(parent_key, done_transition_name=DONE_TRANSITION_NAME):
subtasks = get_all_subtasks(parent_key)
for st in subtasks:
key = st["key"]
try:
transition_issue(key, done_transition_name)
except Exception as e:
print(f"Failed to transition subtask {key}: {e}")
try:
transition_issue(parent_key, done_transition_name)
except Exception as e:
print(f"Failed to transition parent {parent_key}: {e}")
count, unresolved = count_unresolved_subtasks(parent_key)
return {
"parent": parent_key,
"unresolved_subtask_count": count,
"unresolved_subtasks": [i["key"] for i in unresolved],
}
# ---- Example: create tree, close, then report ----
def create_tree(data):
stack = [(None, data)]
created = {}
idx = 0
while idx < len(stack):
parent_key, node = stack[idx]
idx += 1
key = issue(node["summary"], node.get("description"), parent_key)
created[id(node)] = key
for c in node.get("children", []):
stack.append((key, c))
return created[id(data)]
if __name__ == "__main__":
tasks = {
"summary": "Parent",
"description": "Example parent",
"children": [
{"summary": "A", "children": [{"summary": "A1"}, {"summary": "A2"}]},
{"summary": "B"},
],
}
parent_key = create_tree(tasks)
print("Created parent:", parent_key)
result = close_parent_and_subtasks(parent_key)
print("Unresolved subtasks:", result["unresolved_subtask_count"])
print("Unresolved subtask keys:", result["unresolved_subtasks"])