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