bug-chart/build.py

127 lines
3.6 KiB
Python
Raw Normal View History

2023-12-21 18:49:15 +00:00
#!/usr/bin/python3
"""Grab list of bus from Forgejo and use it to make a line graph."""
import configparser
import json
import os
import sys
import typing
2023-12-21 18:59:28 +00:00
from collections import Counter
2023-12-21 18:49:15 +00:00
from datetime import date, datetime, timedelta
import pytz
import requests
config = configparser.ConfigParser()
2023-12-21 19:13:29 +00:00
config_file_path = os.path.expanduser(
os.path.join(os.getenv("XDG_CONFIG_HOME", "~/.config"), "bug-chart", "config")
)
assert os.path.exists(config_file_path)
config.read(os.path.expanduser(config_file_path))
2023-12-21 18:49:15 +00:00
Bug = dict[str, typing.Any]
CallParams = dict[str, str | int]
hostname = config.get("forgejo", "hostname")
token = config.get("forgejo", "token")
url = f"https://{hostname}/api/v1/repos/issues/search"
def download_bugs(state: str) -> list[Bug]:
"""Get all bugs from forgejo."""
all_bugs: list[Bug] = []
page = 0
2023-12-21 18:59:28 +00:00
headers = {"Authorization": "token " + token}
2023-12-21 18:49:15 +00:00
while bugs := requests.get(
url,
2023-12-21 18:59:28 +00:00
headers=headers,
2023-12-21 18:49:15 +00:00
params=typing.cast(CallParams, {"state": state, "page": page}),
).json():
if isinstance(bugs, dict):
print(bugs)
sys.exit(1)
2023-12-21 18:59:28 +00:00
2023-12-21 18:49:15 +00:00
assert isinstance(bugs, list)
all_bugs += bugs
page += 1
return all_bugs
def get_all_bugs() -> list[Bug]:
"""Get all bugs."""
2023-12-21 19:13:29 +00:00
filename = config.get("output", "cache")
2023-12-21 18:49:15 +00:00
if os.path.exists(filename):
with open(filename) as fh:
return typing.cast(list[Bug], json.load(fh))
all_bugs = download_bugs("open") + download_bugs("closed")
with open(filename, "w") as out:
json.dump(all_bugs, out, indent=2)
return all_bugs
def parse_date(date_str: str | None) -> date | None:
"""Parse a date string with timezone information."""
if date_str is None:
return None
fmt = "%Y-%m-%dT%H:%M:%SZ" if date_str.endswith("Z") else "%Y-%m-%dT%H:%M:%S%z"
return datetime.strptime(date_str, fmt).astimezone(pytz.utc).date()
def count_open_bugs(bug_reports: list[Bug]) -> list[tuple[str, int]]:
"""Count the number of open bugs for each date based on a list of bug reports."""
2023-12-21 18:59:28 +00:00
open_dates: Counter[date] = Counter()
close_dates: Counter[date] = Counter()
2023-12-21 18:49:15 +00:00
# Process each bug report
seen: set[int] = set()
for report in bug_reports:
if report["id"] in seen:
continue
seen.add(report["id"])
open_date = parse_date(report["created_at"])
2023-12-21 18:59:28 +00:00
assert open_date
open_dates[open_date] += 1
2023-12-21 18:49:15 +00:00
close_date = parse_date(report["closed_at"])
if close_date:
2023-12-21 18:59:28 +00:00
close_dates[close_date] += 1
2023-12-21 18:49:15 +00:00
# Create a date range from the earliest open date to the latest close date
start_date = min(open_dates.keys())
end_date = max(close_dates.keys(), default=start_date)
delta = end_date - start_date
# Count open bugs for each date
open_bug_count = 0
open_bugs_over_time = []
for i in range(delta.days + 1):
current_date = start_date + timedelta(days=i)
open_bug_count += open_dates.get(current_date, 0)
open_bug_count -= close_dates.get(current_date, 0)
open_bugs_over_time.append((current_date.isoformat(), open_bug_count))
return open_bugs_over_time
def main() -> None:
"""Grab bug reports and generate chart."""
bug_reports = get_all_bugs()
open_bugs_over_time = count_open_bugs(bug_reports)
json_data = json.dumps(open_bugs_over_time)
template_html = open("template.html").read()
2023-12-21 18:59:28 +00:00
dest = config.get("output", "dest")
with open(dest, "w") as out:
2023-12-21 18:49:15 +00:00
out.write(template_html.replace("jsonData = []", "jsonData = " + json_data))
if __name__ == "__main__":
main()