|
7 | 7 | "# Nombre de PR fusionnées par personne agrégées par semaine\n", |
8 | 8 | "\n", |
9 | 9 | "Ce notebook récupère, via l'API GitHub, le nombre de *pull requests* (PR) fusionnées\n", |
10 | | - "pour un dépôt donné, les regroupe par auteur et par semaine sur l'année écoulée,\n", |
| 10 | + "pour **un ou plusieurs dépôts**, les regroupe par auteur et par semaine sur l'année écoulée,\n", |
11 | 11 | "puis affiche le résultat sous forme de graphique.\n", |
12 | 12 | "\n", |
13 | 13 | "**Dépendances :** `requests`, `pandas`, `matplotlib`.\n", |
|
43 | 43 | "source": [ |
44 | 44 | "## Paramètres\n", |
45 | 45 | "\n", |
46 | | - "Modifiez `OWNER` et `REPO` pour pointer vers le dépôt de votre choix." |
| 46 | + "Modifiez `REPOS` pour lister les dépôts à analyser sous la forme\n", |
| 47 | + "`[(owner, repo), ...]`. Vous pouvez ajouter autant de dépôts que vous le souhaitez." |
47 | 48 | ] |
48 | 49 | }, |
49 | 50 | { |
|
52 | 53 | "metadata": {}, |
53 | 54 | "outputs": [], |
54 | 55 | "source": [ |
55 | | - "OWNER = \"sdpython\"\n", |
56 | | - "REPO = \"teachpyx\"\n", |
| 56 | + "REPOS = [\n", |
| 57 | + " (\"sdpython\", \"teachpyx\"),\n", |
| 58 | + " # (\"sdpython\", \"onnx-extended\"), # ajoutez d'autres dépôts ici\n", |
| 59 | + "]\n", |
57 | 60 | "\n", |
58 | 61 | "# Jeton d'authentification GitHub (optionnel mais recommandé)\n", |
59 | 62 | "GITHUB_TOKEN = os.environ.get(\"GITHUB_TOKEN\", \"\")" |
|
69 | 72 | "avec `state=closed`. On filtre ensuite les PR dont le champ `merged_at` est renseigné\n", |
70 | 73 | "et dont la date de fusion est dans les 12 derniers mois.\n", |
71 | 74 | "\n", |
72 | | - "La pagination est gérée via le paramètre `page`." |
| 75 | + "La pagination est gérée via le paramètre `page`.\n", |
| 76 | + "La boucle principale itère sur chaque dépôt listé dans `REPOS`." |
73 | 77 | ] |
74 | 78 | }, |
75 | 79 | { |
|
79 | 83 | "outputs": [], |
80 | 84 | "source": [ |
81 | 85 | "def fetch_merged_prs(owner: str, repo: str, token: str = \"\") -> list[dict]:\n", |
82 | | - " \"\"\"Récupère toutes les PR fusionnées au cours de l'année écoulée.\n", |
| 86 | + " \"\"\"Récupère toutes les PR fusionnées au cours de l'année écoulée pour un dépôt.\n", |
83 | 87 | "\n", |
84 | 88 | " :param owner: propriétaire du dépôt GitHub\n", |
85 | 89 | " :param repo: nom du dépôt GitHub\n", |
86 | 90 | " :param token: jeton d'authentification GitHub (optionnel)\n", |
87 | | - " :return: liste de dictionnaires avec les champs ``author``, ``merged_at``\n", |
| 91 | + " :return: liste de dictionnaires avec les champs ``author``, ``merged_at``, ``repo``\n", |
88 | 92 | " \"\"\"\n", |
89 | 93 | " headers = {\"Accept\": \"application/vnd.github+json\"}\n", |
90 | 94 | " if token:\n", |
|
135 | 139 | " stop = True\n", |
136 | 140 | " break\n", |
137 | 141 | " author = (pr.get(\"user\") or {}).get(\"login\", \"unknown\")\n", |
138 | | - " results.append({\"author\": author, \"merged_at\": merged_dt})\n", |
| 142 | + " results.append({\"author\": author, \"merged_at\": merged_dt, \"repo\": f\"{owner}/{repo}\"})\n", |
139 | 143 | "\n", |
140 | 144 | " if stop:\n", |
141 | 145 | " break\n", |
|
145 | 149 | " return results\n", |
146 | 150 | "\n", |
147 | 151 | "\n", |
148 | | - "merged_prs = fetch_merged_prs(OWNER, REPO, GITHUB_TOKEN)\n", |
149 | | - "print(f\"{len(merged_prs)} PR(s) fusionnée(s) trouvée(s) au cours de l'année écoulée.\")" |
| 152 | + "merged_prs = []\n", |
| 153 | + "for owner, repo in REPOS:\n", |
| 154 | + " prs = fetch_merged_prs(owner, repo, GITHUB_TOKEN)\n", |
| 155 | + " print(f\" {owner}/{repo} : {len(prs)} PR(s) fusionnée(s)\")\n", |
| 156 | + " merged_prs.extend(prs)\n", |
| 157 | + "\n", |
| 158 | + "print(f\"Total : {len(merged_prs)} PR(s) fusionnée(s) sur l'ensemble des dépôts.\")" |
150 | 159 | ] |
151 | 160 | }, |
152 | 161 | { |
|
171 | 180 | " df[\"week\"] = df[\"merged_at\"].dt.to_period(\"W\").dt.start_time\n", |
172 | 181 | "\n", |
173 | 182 | " weekly = (\n", |
174 | | - " df.groupby([\"author\", \"week\"])\n", |
| 183 | + " df.groupby([\"repo\", \"author\", \"week\"])\n", |
175 | 184 | " .size()\n", |
176 | 185 | " .reset_index(name=\"pr_count\")\n", |
177 | 186 | " )\n", |
|
182 | 191 | "cell_type": "markdown", |
183 | 192 | "metadata": {}, |
184 | 193 | "source": [ |
185 | | - "## Tableau croisé (auteur × semaine)" |
| 194 | + "## Tableau croisé (auteur × semaine, agrégé sur tous les dépôts)" |
186 | 195 | ] |
187 | 196 | }, |
188 | 197 | { |
|
192 | 201 | "outputs": [], |
193 | 202 | "source": [ |
194 | 203 | "if not df.empty:\n", |
| 204 | + " # Agrégation sur tous les dépôts\n", |
195 | 205 | " pivot = weekly.pivot_table(\n", |
196 | 206 | " index=\"author\", columns=\"week\", values=\"pr_count\", aggfunc=\"sum\", fill_value=0\n", |
197 | 207 | " )\n", |
|
200 | 210 | " pivot" |
201 | 211 | ] |
202 | 212 | }, |
| 213 | + { |
| 214 | + "cell_type": "markdown", |
| 215 | + "metadata": {}, |
| 216 | + "source": [ |
| 217 | + "## Tableau croisé par dépôt" |
| 218 | + ] |
| 219 | + }, |
| 220 | + { |
| 221 | + "cell_type": "code", |
| 222 | + "execution_count": null, |
| 223 | + "metadata": {}, |
| 224 | + "outputs": [], |
| 225 | + "source": [ |
| 226 | + "if not df.empty and len(REPOS) > 1:\n", |
| 227 | + " for repo_name, grp in weekly.groupby(\"repo\"):\n", |
| 228 | + " pvt = grp.pivot_table(\n", |
| 229 | + " index=\"author\", columns=\"week\", values=\"pr_count\", aggfunc=\"sum\", fill_value=0\n", |
| 230 | + " )\n", |
| 231 | + " pvt = pvt.loc[pvt.sum(axis=1).sort_values(ascending=False).index]\n", |
| 232 | + " print(f\"\\n=== {repo_name} ===\")\n", |
| 233 | + " display(pvt)" |
| 234 | + ] |
| 235 | + }, |
203 | 236 | { |
204 | 237 | "cell_type": "markdown", |
205 | 238 | "metadata": {}, |
|
234 | 267 | " plt.xticks(rotation=45, ha=\"right\")\n", |
235 | 268 | " ax.set_xlabel(\"Semaine\")\n", |
236 | 269 | " ax.set_ylabel(\"Nombre de PR fusionnées\")\n", |
237 | | - " ax.set_title(f\"PR fusionnées par semaine — {OWNER}/{REPO}\")\n", |
| 270 | + " repos_label = \", \".join(f\"{o}/{r}\" for o, r in REPOS)\n ax.set_title(f\"PR fusionnées par semaine — {repos_label}\")\n", |
238 | 271 | " ax.legend(loc=\"upper left\", bbox_to_anchor=(1, 1), title=\"Auteur\")\n", |
239 | 272 | " plt.tight_layout()\n", |
240 | 273 | " plt.show()" |
|
269 | 302 | " [str(d)[:10] for d in pivot.columns[::step]], rotation=45, ha=\"right\"\n", |
270 | 303 | " )\n", |
271 | 304 | "\n", |
272 | | - " ax.set_title(f\"Heatmap des PR fusionnées — {OWNER}/{REPO}\")\n", |
| 305 | + " repos_label = \", \".join(f\"{o}/{r}\" for o, r in REPOS)\n ax.set_title(f\"Heatmap des PR fusionnées — {repos_label}\")\n", |
273 | 306 | " ax.set_xlabel(\"Semaine\")\n", |
274 | 307 | " ax.set_ylabel(\"Auteur\")\n", |
275 | 308 | " plt.tight_layout()\n", |
|
0 commit comments