Update sprint_scrum.py
Changed cycles to modules, functionality is working, looking for feedback on how Nick would like this to run
This commit is contained in:
449
sprint_scrum.py
449
sprint_scrum.py
@@ -1,255 +1,194 @@
|
||||
#----------------------------------Imports-----------------------------------
|
||||
import json
|
||||
import requests
|
||||
from datetime import timedelta, datetime, timezone
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
|
||||
#---------------------------Set Up--------------------------------------------
|
||||
PLANE_URL = "https://project-management.cui-secure.us/api/v1/workspaces/midwatch"
|
||||
load_dotenv()
|
||||
TOKEN = os.getenv("TOKEN")
|
||||
PROJECT_ID = "2a3b613e-a182-4278-8547-9bd5250bf67f"
|
||||
WORKSPACE_ID = "midwatch"
|
||||
TESTING_PROJECT = "a0c36f9c-65a5-4f50-beae-b9badc8cf11d"
|
||||
SPRINT_LENGTH = 7
|
||||
|
||||
HEADER = {
|
||||
"X-API-Key": f"{TOKEN}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
|
||||
#---------------------------Testing Grounds------------------------------------
|
||||
# test = requests.get(f"{PLANE_URL}/projects/{TESTING_PROJECT}/cycles", headers=HEADER)
|
||||
|
||||
# if test.status_code == 200:
|
||||
# print("Connected to Plane API successfully.")
|
||||
# print(test.json())
|
||||
|
||||
# else:
|
||||
# print(f"API connection failed. Status code: {test.status_code}")
|
||||
# print("Response:", test.text)
|
||||
# exit()
|
||||
|
||||
#-----------------------Functionality-----------------------------------------------
|
||||
|
||||
def get_last_sprint():
|
||||
url = f"{PLANE_URL}/projects/{TESTING_PROJECT}/cycles"
|
||||
response = requests.get(url, headers=HEADER)
|
||||
data = response.json()
|
||||
if response.status_code != 200:
|
||||
print(f"Failed to fetch sprints: {response.status_code}")
|
||||
print("Response:", data)
|
||||
return None
|
||||
|
||||
sprints = data if isinstance(data, list) else data.get("results", [])
|
||||
if not sprints:
|
||||
print("No sprints found.")
|
||||
return None
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
completed = []
|
||||
active = []
|
||||
upcoming = []
|
||||
|
||||
for s in sprints:
|
||||
start = datetime.fromisoformat(s["start_date"].replace("Z", "+00:00"))
|
||||
end = datetime.fromisoformat(s["end_date"].replace("Z", "+00:00"))
|
||||
|
||||
if end < now:
|
||||
completed.append(s)
|
||||
elif start <= now <= end:
|
||||
active.append(s)
|
||||
else:
|
||||
upcoming.append(s)
|
||||
|
||||
completed.sort(key=lambda s: s["end_date"], reverse=True)
|
||||
|
||||
if completed:
|
||||
last = completed[0]
|
||||
print(f"Last completed sprint: {last['name']} (ended {last['end_date']})")
|
||||
return last
|
||||
elif active:
|
||||
current = active[0]
|
||||
print(f"Current sprint in progress: {current['name']} (ends {current['end_date']})")
|
||||
return current
|
||||
else:
|
||||
print("No active or completed sprints found.")
|
||||
return None
|
||||
|
||||
|
||||
def get_all_issues():
|
||||
response = requests.get(
|
||||
f"{PLANE_URL}/projects/{TESTING_PROJECT}/issues?expand=state",
|
||||
headers=HEADER
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
all_issues = data.get('results', data)
|
||||
uncompleted = []
|
||||
|
||||
for issue in all_issues:
|
||||
state = issue.get('state', {})
|
||||
group = state.get('group', 'unknown')
|
||||
|
||||
if group.lower() != 'completed':
|
||||
uncompleted.append(issue)
|
||||
|
||||
print(f"Retrieved {len(uncompleted)} uncompleted issues from project {TESTING_PROJECT}")
|
||||
return uncompleted
|
||||
|
||||
|
||||
def get_unfinished_issues(sprint, all_issues):
|
||||
sprint_id = sprint['id']
|
||||
|
||||
response = requests.get(
|
||||
f"{PLANE_URL}/projects/{TESTING_PROJECT}/cycles/{sprint_id}/cycle-issues/",
|
||||
headers=HEADER
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
sprint_data = response.json()
|
||||
sprint_issues = sprint_data.get('results', sprint_data)
|
||||
|
||||
sprint_issue_ids = {i.get('id') for i in sprint_issues if i.get('id')}
|
||||
|
||||
print(f"Found {len(sprint_issue_ids)} issues linked to sprint {sprint_id}")
|
||||
|
||||
unfinished_ids = [
|
||||
issue.get('id')
|
||||
for issue in all_issues
|
||||
if issue.get('id') in sprint_issue_ids
|
||||
]
|
||||
|
||||
print(f"Found {len(unfinished_ids)} unfinished issues in sprint {sprint_id}")
|
||||
return unfinished_ids
|
||||
|
||||
|
||||
def create_cycle_minimal(name="Auto-Sprint"):
|
||||
url = f"{PLANE_URL}/projects/{TESTING_PROJECT}/cycles/"
|
||||
payload = {"name": name}
|
||||
|
||||
print("Creating new cycle:", payload)
|
||||
res = requests.post(url, headers=HEADER, json=payload)
|
||||
print("Create response:", res.text)
|
||||
res.raise_for_status()
|
||||
|
||||
cycle = res.json()
|
||||
return cycle
|
||||
|
||||
|
||||
def update_cycle_details(cycle_id, start_date, end_date, description):
|
||||
url = f"{PLANE_URL}/projects/{TESTING_PROJECT}/cycles/{cycle_id}"
|
||||
payload = {
|
||||
"start_date": start_date,
|
||||
"end_date": end_date,
|
||||
"description": description,
|
||||
"name": f"Auto-Sprint {start_date}"
|
||||
}
|
||||
|
||||
print(f"Updating cycle {cycle_id}:", payload)
|
||||
res = requests.patch(url, headers=HEADER, json=payload)
|
||||
print("Patch response:", res.text)
|
||||
res.raise_for_status()
|
||||
return res.json()
|
||||
|
||||
|
||||
def attach_issues_to_cycle(cycle_id, issue_ids):
|
||||
url = f"{PLANE_URL}/projects/{TESTING_PROJECT}/cycles/{cycle_id}/cycle-issues/"
|
||||
payload = {"issues": issue_ids}
|
||||
|
||||
print(f"Attaching issues to cycle {cycle_id}:", payload)
|
||||
res = requests.post(url, headers=HEADER, json=payload)
|
||||
print("Attach response:", res.text)
|
||||
res.raise_for_status()
|
||||
return res.json()
|
||||
|
||||
|
||||
def create_new_sprint(last_sprint, unresolved_issue_ids):
|
||||
last_end = datetime.fromisoformat(last_sprint["end_date"].replace("Z", "+00:00"))
|
||||
new_start = last_end + timedelta(days=1)
|
||||
new_end = new_start + timedelta(days=SPRINT_LENGTH)
|
||||
|
||||
start_str = new_start.strftime("%Y-%m-%dT00:00:00Z")
|
||||
end_str = new_end.strftime("%Y-%m-%dT23:59:59Z")
|
||||
|
||||
|
||||
print("Creating cycle with name only...")
|
||||
|
||||
create_payload = {
|
||||
"name": f"Auto-Sprint {new_start}"
|
||||
}
|
||||
|
||||
create_res = requests.post(
|
||||
f"{PLANE_URL}/projects/{TESTING_PROJECT}/cycles/",
|
||||
headers=HEADER,
|
||||
json=create_payload
|
||||
)
|
||||
|
||||
print("Create response:", create_res.text)
|
||||
create_res.raise_for_status()
|
||||
|
||||
cycle = create_res.json()
|
||||
cycle_id = cycle["id"]
|
||||
print("Cycle created:", cycle_id)
|
||||
|
||||
patch_payload = {
|
||||
"start_date": "2025-11-15",
|
||||
"end_date": "2025-11-16",
|
||||
"description": f"Automated sprint starting {start_str}"
|
||||
}
|
||||
|
||||
print("➡ Patching cycle with:", patch_payload)
|
||||
patch_res = requests.patch(
|
||||
f"{PLANE_URL}/projects/{TESTING_PROJECT}/cycles/{cycle_id}",
|
||||
headers=HEADER,
|
||||
json=patch_payload
|
||||
)
|
||||
print("Patch response:", patch_res.text)
|
||||
patch_res.raise_for_status()
|
||||
patched_cycle = patch_res.json()
|
||||
|
||||
if unresolved_issue_ids:
|
||||
attach_payload = {"issues": unresolved_issue_ids}
|
||||
print("Attaching issues:", attach_payload)
|
||||
attach_res = requests.post(
|
||||
f"{PLANE_URL}/projects/{TESTING_PROJECT}/cycles/{cycle_id}/cycle-issues/",
|
||||
headers=HEADER,
|
||||
json=attach_payload
|
||||
)
|
||||
print("Attach response:", attach_res.text)
|
||||
attach_res.raise_for_status()
|
||||
else:
|
||||
print("No unresolved issues to attach.")
|
||||
|
||||
return {
|
||||
"cycle_id": cycle_id,
|
||||
"updated": patched_cycle,
|
||||
"attached_issues": unresolved_issue_ids
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
all_uncompleted = get_all_issues()
|
||||
|
||||
last_sprint = get_last_sprint()
|
||||
|
||||
get_unfinished_issues(last_sprint, all_uncompleted)
|
||||
|
||||
print(create_new_sprint(last_sprint, get_unfinished_issues(last_sprint, all_uncompleted)))
|
||||
|
||||
# patch_try = requests.patch(f"{PLANE_URL}/projects/{TESTING_PROJECT}/cycles/52261070-73ae-4ac6-aeed-805dfa5fb3d0",
|
||||
# headers=HEADER,
|
||||
# json= {
|
||||
# "name": "Auto-Sprint 2025-11-15",
|
||||
# "start_date": "2015-11-17",
|
||||
# "end_date": "2025-11-19"
|
||||
# })
|
||||
|
||||
# print(f"Status: {patch_try.status_code}")
|
||||
# print(f"Response: {patch_try.text}")
|
||||
#----------------------------------Imports-----------------------------------
|
||||
import json
|
||||
import requests
|
||||
from datetime import timedelta, datetime, timezone
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
|
||||
#---------------------------Set Up--------------------------------------------
|
||||
PLANE_URL = "https://project-management.cui-secure.us/api/v1"
|
||||
load_dotenv()
|
||||
TOKEN = os.getenv("TOKEN")
|
||||
PROJECT_ID = "2a3b613e-a182-4278-8547-9bd5250bf67f"
|
||||
WORKSPACE_ID = "midwatch"
|
||||
TESTING_PROJECT = "a0c36f9c-65a5-4f50-beae-b9badc8cf11d"
|
||||
SPRINT_LENGTH = 7
|
||||
|
||||
HEADER = {
|
||||
"X-API-Key": f"{TOKEN}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
|
||||
#---------------------------Testing Grounds------------------------------------
|
||||
# test = requests.get(f"{PLANE_URL}/workspaces/midwatch/projects/{TESTING_PROJECT}/modules", headers=HEADER)
|
||||
|
||||
# if test.status_code == 200:
|
||||
# print("Connected to Plane API successfully.")
|
||||
# print(test.json())
|
||||
|
||||
# else:
|
||||
# print(f"API connection failed. Status code: {test.status_code}")
|
||||
# print("Response:", test.text)
|
||||
# exit()
|
||||
|
||||
#-----------------------Functionality-----------------------------------------------
|
||||
|
||||
def get_last_sprint():
|
||||
url = f"{PLANE_URL}/workspaces/midwatch/projects/{TESTING_PROJECT}/modules"
|
||||
response = requests.get(url, headers=HEADER)
|
||||
data = response.json()
|
||||
if response.status_code != 200:
|
||||
print(f"Failed to fetch sprints: {response.status_code}")
|
||||
print("Response:", data)
|
||||
return None
|
||||
|
||||
sprints = data if isinstance(data, list) else data.get("results", [])
|
||||
if not sprints:
|
||||
print("No sprints found.")
|
||||
return None
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
completed = []
|
||||
upcoming = []
|
||||
|
||||
for s in sprints:
|
||||
raw = s["target_date"]
|
||||
|
||||
end = datetime.strptime(raw, "%Y-%m-%d").replace(tzinfo=timezone.utc)
|
||||
|
||||
|
||||
if end < now:
|
||||
completed.append(s)
|
||||
else:
|
||||
upcoming.append(s)
|
||||
|
||||
completed.sort(key=lambda s: s["target_date"], reverse=True)
|
||||
|
||||
if completed:
|
||||
last = completed[0]
|
||||
print(f"Last completed sprint: {last['name']} (ended {last['target_date']})")
|
||||
return last
|
||||
else:
|
||||
print("No active or completed sprints found.")
|
||||
return None
|
||||
|
||||
|
||||
def get_all_issues():
|
||||
response = requests.get(
|
||||
f"{PLANE_URL}/workspaces/midwatch/projects/{TESTING_PROJECT}/issues?expand=state",
|
||||
headers=HEADER
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
all_issues = data.get('results', data)
|
||||
uncompleted = []
|
||||
|
||||
for issue in all_issues:
|
||||
state = issue.get('state', {})
|
||||
group = state.get('group', 'unknown')
|
||||
|
||||
if group.lower() != 'completed':
|
||||
uncompleted.append(issue)
|
||||
|
||||
print(f"Retrieved {len(uncompleted)} uncompleted issues from project {TESTING_PROJECT}")
|
||||
return uncompleted
|
||||
|
||||
def get_unfinished_issues(sprint, all_issues):
|
||||
sprint_id = sprint['id']
|
||||
|
||||
response = requests.get(
|
||||
f"{PLANE_URL}/workspaces/midwatch/projects/{TESTING_PROJECT}/modules/{sprint_id}/module-issues/",
|
||||
headers=HEADER
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
sprint_data = response.json()
|
||||
sprint_issues = sprint_data.get('results', sprint_data)
|
||||
|
||||
sprint_issue_ids = {i.get('id') for i in sprint_issues if i.get('id')}
|
||||
|
||||
print(f"Found {len(sprint_issue_ids)} issues linked to sprint {sprint_id}")
|
||||
|
||||
unfinished_ids = [
|
||||
issue.get('id')
|
||||
for issue in all_issues
|
||||
if issue.get('id') in sprint_issue_ids
|
||||
]
|
||||
|
||||
print(f"Found {len(unfinished_ids)} unfinished issues in sprint {sprint_id}")
|
||||
|
||||
archive_last_sprint = requests.patch(
|
||||
f"{PLANE_URL}/workspaces/midwatch/projects/{TESTING_PROJECT}/modules/{sprint_id}/",
|
||||
headers=HEADER,
|
||||
json={"status": "completed"}
|
||||
)
|
||||
print(f"Patch Last Sprint: {archive_last_sprint.status_code}")
|
||||
|
||||
return unfinished_ids
|
||||
|
||||
|
||||
def create_new_sprint(last_sprint, unresolved_issue_ids):
|
||||
last_end = datetime.fromisoformat(last_sprint["target_date"])
|
||||
new_start = last_end + timedelta(days=1)
|
||||
new_end = new_start + timedelta(days=SPRINT_LENGTH)
|
||||
|
||||
starting_str = new_start.strftime("%Y-%m-%d")
|
||||
ending_str = new_end.strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
print("Creating cycle with name only...")
|
||||
|
||||
create_payload = {
|
||||
"name": f"Auto-Sprint {starting_str}-{ending_str}",
|
||||
"description": f"This is an Automated Sprint for the dates {starting_str} to {ending_str}",
|
||||
"start_date": starting_str,
|
||||
"target_date": ending_str,
|
||||
"status":"in-progress"
|
||||
}
|
||||
|
||||
create_res = requests.post(
|
||||
f"{PLANE_URL}/workspaces/midwatch/projects/{TESTING_PROJECT}/modules/",
|
||||
headers=HEADER,
|
||||
json=create_payload
|
||||
)
|
||||
|
||||
print("Create response:", create_res.text)
|
||||
create_res.raise_for_status()
|
||||
|
||||
cycle = create_res.json()
|
||||
cycle_id = cycle["id"]
|
||||
print("Cycle created:", cycle_id)
|
||||
|
||||
|
||||
if unresolved_issue_ids:
|
||||
attach_payload = {"issues": unresolved_issue_ids}
|
||||
print("Attaching issues:", attach_payload)
|
||||
attach_res = requests.post(
|
||||
f"{PLANE_URL}/workspaces/midwatch/projects/{TESTING_PROJECT}/modules/{cycle_id}/module-issues/",
|
||||
headers=HEADER,
|
||||
json=attach_payload
|
||||
)
|
||||
print("Attach response:", attach_res.text)
|
||||
attach_res.raise_for_status()
|
||||
else:
|
||||
print("No unresolved issues to attach.")
|
||||
|
||||
return {
|
||||
"cycle_id": cycle_id,
|
||||
"attached_issues": unresolved_issue_ids
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
all_uncompleted = get_all_issues()
|
||||
|
||||
last_sprint = get_last_sprint()
|
||||
|
||||
get_unfinished_issues(last_sprint, all_uncompleted)
|
||||
|
||||
print(create_new_sprint(last_sprint, get_unfinished_issues(last_sprint, all_uncompleted)))
|
||||
|
||||
Reference in New Issue
Block a user