Jira Automation (WIP - don't use yet)
Automate creating Jira Tickets
Section titled “Automate creating Jira Tickets”# note untestedimport 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))Automate closing of Jira Tickets
Section titled “Automate closing of Jira Tickets”# this has extra code in itimport osimport 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"])