1from github import Github, Repository 2import os 3import json 4from datetime import datetime 5from collections import Counter 6from pybars import Compiler 7import matplotlib 8 9try: 10 import tqdm 11 12 progress = tqdm.tqdm 13except Exception: 14 progress = lambda x: x 15 16matplotlib.use("Agg") 17import matplotlib.pyplot as plt 18 19 20g = Github(os.environ["GITHUB_TOKEN"]) 21sources = json.load(open("fontrepos.json")) 22 23this = datetime.now() 24 25closed_per_month = Counter() 26opened_per_month = Counter() 27open_per_repo = Counter() 28open_per_tier = Counter() 29releases_per_month = {} 30totals_per_month = {} 31issues_by_age = {} 32 33total = 0 34for repo_name in progress(list(sources.keys())): 35 # if repo_name not in org_names: 36 # continue 37 repo = g.get_repo("notofonts/" + repo_name) 38 issues = repo.get_issues(state="all") 39 tier = sources[repo_name].get("tier", 3) 40 for i in issues: 41 if i.state == "open": 42 total += 1 43 open_per_repo[repo_name] += 1 44 open_per_tier[tier] += 1 45 if i.created_at.year == this.year: 46 opened_per_month[i.created_at.month] += 1 47 elif i.closed_at.year == this.year: 48 closed_per_month[i.closed_at.month] += 1 49 50 releases = repo.get_releases() 51 for release in sorted( 52 releases, key=lambda r: r.published_at.isoformat() if r.published_at else "" 53 ): 54 if release.draft: 55 continue 56 releases_per_month.setdefault(release.published_at.month, []).append( 57 {"tag": release.tag_name, "url": release.html_url} 58 ) 59 60# Back-compute totals per month 61totals_per_month[this.month] = total 62for m in range(this.month, 0, -1): 63 total += closed_per_month[m] - opened_per_month[m] 64 totals_per_month[m] = total 65 66# Save it 67json.dump( 68 { 69 "opened_per_month": opened_per_month, 70 "closed_per_month": closed_per_month, 71 "totals_per_month": totals_per_month, 72 "open_per_repo": open_per_repo, 73 "open_per_tier": open_per_tier, 74 "releases_per_month": releases_per_month, 75 }, 76 open("docs/issues.json", "w"), 77 indent=True, 78 sort_keys=True, 79) 80 81year_to_date = range(1, this.month + 1) 82 83months = [ 84 "Jan", 85 "Feb", 86 "Mar", 87 "Apr", 88 "May", 89 "Jun", 90 "Jul", 91 "Aug", 92 "Sep", 93 "Oct", 94 "Nov", 95 "Dec", 96][: this.month] 97totals = [totals_per_month[i] for i in year_to_date] 98 99fig, ax1 = plt.subplots() 100ax2 = ax1.twinx() 101 102bars = ax1.bar( 103 months, 104 [totals_per_month[i] for i in year_to_date], 105 label="Total", 106 color="#aaaaffaa", 107) 108ax1.bar_label(bars) 109ax1.axes.get_yaxis().set_visible(False) 110 111lns1 = ax2.plot( 112 months, 113 [opened_per_month[i] for i in year_to_date], 114 marker=".", 115 label="Opened", 116 color="red", 117 linewidth=3, 118) 119lns2 = ax2.plot( 120 months, 121 [closed_per_month[i] for i in year_to_date], 122 marker="+", 123 label="Closed", 124 color="green", 125 linewidth=3, 126) 127 128lines, labels = ax1.get_legend_handles_labels() 129lines2, labels2 = ax2.get_legend_handles_labels() 130ax2.legend(lines + lines2, labels + labels2, loc="lower left") 131plt.title("Issues opened, closed, and open") 132plt.savefig("docs/open-closed.png") 133 134 135## Top 10 scripts 136top_10 = sorted(open_per_repo.most_common(10), key=lambda x: -x[1]) 137labels, values = list(zip(*top_10)) 138fig, ax = plt.subplots() 139bars = ax.bar(labels, values) 140ax.bar_label(bars) 141plt.title("Repositories with most open issues") 142plt.xticks(rotation=60) 143plt.tight_layout() 144plt.savefig("docs/top-10.png") 145 146 147# Low hanging fruit and tiers 148low_hanging = {} 149tiers = {1: [], 2: [], 3: [], 4: [], 5: []} 150for k, v in open_per_repo.items(): 151 if v == 0: 152 continue 153 tier = sources[k].get("tier", 3) 154 tiers[tier].append({"repo": k, "issues": v}) 155 if v > 10: 156 continue 157 low_hanging.setdefault(v, []).append(k) 158low_hanging = [ 159 {"issues": k, "repos": low_hanging[k]} for k in sorted(low_hanging.keys()) 160] 161 162tiers = {k: sorted(v, key=lambda i: -i["issues"]) for k, v in tiers.items()} 163 164labels = [1, 2, 3, 4, 5] 165values = [open_per_tier.get(l, 0) for l in labels] 166fig, ax = plt.subplots() 167bars = ax.bar(labels, values) 168ax.bar_label(bars) 169plt.title("Open issues per tier") 170plt.tight_layout() 171plt.savefig("docs/per-tier.png") 172 173## Releases per month 174release_count_per_month = [len(releases_per_month.get(i, [])) for i in year_to_date] 175fig, ax = plt.subplots() 176bars = ax.bar(months, release_count_per_month) 177ax.bar_label(bars) 178plt.title("Releases per month") 179plt.savefig("docs/releases.png") 180 181monthly_stats = [ 182 { 183 "month": months[i - 1], 184 "opened": opened_per_month.get(i, 0), 185 "closed": closed_per_month.get(i, 0), 186 "releases": releases_per_month.get(i, []), 187 "releases_count": len(releases_per_month.get(i, [])), 188 } 189 for i in year_to_date 190] 191 192compiler = Compiler() 193template = open("scripts/analytics-template.html", "r").read() 194template = compiler.compile(template) 195output = template( 196 { 197 "monthly_stats": monthly_stats, 198 "top_10": [{"repo": k, "count": v} for k, v in top_10], 199 "low_hanging": low_hanging, 200 "tiers": tiers, 201 } 202) 203 204with open("docs/analytics.html", "w") as fh: 205 fh.write(output) 206