From 21e400079f293479bde6e0381babb943e0c18b2c Mon Sep 17 00:00:00 2001 From: Martin Barisits Date: Mon, 24 Aug 2026 14:51:54 +0200 Subject: [PATCH 1/4] docs: show wishlist labelling date in wishlist tables Assisted-by: Claude --- tools/generate_wishlist.py | 77 +++++++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/tools/generate_wishlist.py b/tools/generate_wishlist.py index 44e3e64b045..ce407a9cc01 100755 --- a/tools/generate_wishlist.py +++ b/tools/generate_wishlist.py @@ -3,7 +3,18 @@ from collections import defaultdict from dataclasses import dataclass from itertools import count -from typing import Any, Dict, FrozenSet, Iterator, List, Set, TextIO, Tuple, Type +from typing import ( + Any, + Dict, + FrozenSet, + Iterator, + List, + Optional, + Set, + TextIO, + Tuple, + Type, +) import requests @@ -38,7 +49,8 @@ [issue management guideline](issue_management.md), these are issues of potential value that are not planned for the next twelve months. Wishlisted issues are revisited regularly and can be re-opened when there is both interest -and capacity from component leads. +and capacity from component leads. The *Wishlisted* column shows when the +`wishlist` label was last added to an issue. This list is refreshed automatically whenever the documentation is built. You can also view it @@ -51,16 +63,16 @@ class GitHubIssue: number: int title: str html_url: str - closed_at: str + wishlisted_at: str labels: Tuple[str, ...] @classmethod def from_github_issue_api_json_obj( - cls: "Type[GitHubIssue]", obj: Any + cls: "Type[GitHubIssue]", obj: Any, wishlisted_at: str ) -> "GitHubIssue": labels = tuple(label["name"] for label in obj["labels"]) return cls( - obj["number"], obj["title"], obj["html_url"], obj["closed_at"], labels + obj["number"], obj["title"], obj["html_url"], wishlisted_at, labels ) @@ -109,6 +121,39 @@ def get_component_labels(owner: str, repo: str) -> FrozenSet[str]: page += 1 +def get_wishlisted_at(owner: str, repo: str, issue_number: int) -> Optional[str]: + """ + Get the timestamp of the last addition of the wishlist label to an issue. + + The label name is compared case-insensitively, matching the behaviour of + the label filter of the issues API. + + :param owner: The owner of the GitHub repository. + :param repo: The GitHub repository name. + :param issue_number: The issue number. + :returns: The timestamp of the last wishlist labelling, or None if the + events of the issue do not record one. + """ + labelled_at = None + for page in count(1): + request = requests.get( + f"https://api.github.com/repos/{owner}/{repo}" + f"/issues/{issue_number}/events", + params={"per_page": str(ITEMS_PER_PAGE), "page": str(page)}, + headers=github_api_headers(), + ) + assert request.status_code == 200, "The request should be successful!" + + events = request.json() + + for event in events: + if event["event"] == "labeled" and event["label"]["name"].lower() == LABEL: + labelled_at = event["created_at"] + + if len(events) != ITEMS_PER_PAGE: + return labelled_at + + def iter_github_wishlist_issues(owner: str, repo: str) -> Iterator[GitHubIssue]: """ Iterate the closed issues labelled wishlist in the given GitHub repository. @@ -136,7 +181,11 @@ def iter_github_wishlist_issues(owner: str, repo: str) -> Iterator[GitHubIssue]: # The issues API also returns pull requests; skip them. if "pull_request" in issue: continue - yield GitHubIssue.from_github_issue_api_json_obj(issue) + # Fall back to the closing date if the labelling is not recorded. + wishlisted_at = ( + get_wishlisted_at(owner, repo, issue["number"]) or issue["closed_at"] + ) + yield GitHubIssue.from_github_issue_api_json_obj(issue, wishlisted_at) if len(issues) != ITEMS_PER_PAGE: return @@ -144,15 +193,15 @@ def iter_github_wishlist_issues(owner: str, repo: str) -> Iterator[GitHubIssue]: def get_sorted_wishlist_issues(owner: str, repo: str) -> List[GitHubIssue]: """ - Get the closed wishlist issues of a repository, newest-closed first. + Get the closed wishlist issues of a repository, newest-wishlisted first. :param owner: The owner of the GitHub repository. :param repo: The GitHub repository name. - :returns: The closed wishlist issues, newest-closed first. + :returns: The closed wishlist issues, newest-wishlisted first. """ return sorted( iter_github_wishlist_issues(owner, repo), - key=lambda issue: issue.closed_at, + key=lambda issue: issue.wishlisted_at, reverse=True, ) @@ -178,12 +227,14 @@ def write_issue_table(fp: TextIO, issues: List[GitHubIssue]) -> None: :param fp: The file to write to. :param issues: The issues to write. """ - fp.write("| Issue | Title | Closed |\n") - fp.write("|-------|-------|--------|\n") + fp.write("| Issue | Title | Wishlisted |\n") + fp.write("|-------|-------|------------|\n") for issue in issues: title = issue.title.replace("|", "\\|") - closed_on = issue.closed_at[:10] - fp.write(f"| [#{issue.number}]({issue.html_url}) | {title} | {closed_on} |\n") + wishlisted_on = issue.wishlisted_at[:10] + fp.write( + f"| [#{issue.number}]({issue.html_url}) | {title} | {wishlisted_on} |\n" + ) def write_core_repo_section(fp: TextIO) -> None: From b91ff1f51a478e79d547c8c4e9a24755f4928060 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 12:53:09 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tools/generate_wishlist.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/generate_wishlist.py b/tools/generate_wishlist.py index ce407a9cc01..bde2ccbf801 100755 --- a/tools/generate_wishlist.py +++ b/tools/generate_wishlist.py @@ -71,9 +71,7 @@ def from_github_issue_api_json_obj( cls: "Type[GitHubIssue]", obj: Any, wishlisted_at: str ) -> "GitHubIssue": labels = tuple(label["name"] for label in obj["labels"]) - return cls( - obj["number"], obj["title"], obj["html_url"], wishlisted_at, labels - ) + return cls(obj["number"], obj["title"], obj["html_url"], wishlisted_at, labels) def github_api_headers() -> Dict[str, str]: From 7ef3e9ce6b283fad143bb0e194b0340b69d2d9a7 Mon Sep 17 00:00:00 2001 From: Martin Barisits Date: Wed, 26 Aug 2026 17:50:10 +0200 Subject: [PATCH 3/4] docs: use while loop for event pagination to satisfy mypy mypy cannot prove that iterating itertools.count never completes, so get_wishlisted_at was flagged with a missing return statement. Use the same explicit while loop as get_component_labels instead. Assisted-by: Claude --- tools/generate_wishlist.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/generate_wishlist.py b/tools/generate_wishlist.py index bde2ccbf801..cd750068f14 100755 --- a/tools/generate_wishlist.py +++ b/tools/generate_wishlist.py @@ -133,7 +133,8 @@ def get_wishlisted_at(owner: str, repo: str, issue_number: int) -> Optional[str] events of the issue do not record one. """ labelled_at = None - for page in count(1): + page = 1 + while True: request = requests.get( f"https://api.github.com/repos/{owner}/{repo}" f"/issues/{issue_number}/events", @@ -150,6 +151,7 @@ def get_wishlisted_at(owner: str, repo: str, issue_number: int) -> Optional[str] if len(events) != ITEMS_PER_PAGE: return labelled_at + page += 1 def iter_github_wishlist_issues(owner: str, repo: str) -> Iterator[GitHubIssue]: From 1f5515b67ce7076bd14232752077cf0357236603 Mon Sep 17 00:00:00 2001 From: Martin Barisits Date: Wed, 26 Aug 2026 18:17:28 +0200 Subject: [PATCH 4/4] docs: authenticate wishlist generation in the build job The wishlist page generation now requests the label events of every closed wishlist issue, which exceeds the unauthenticated GitHub API rate limit of 60 requests per hour. Pass GITHUB_TOKEN to the build step, which generate_wishlist.py already picks up from the environment, to use the authenticated rate limit instead. Assisted-by: Claude --- .github/workflows/update_documentation.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/update_documentation.yml b/.github/workflows/update_documentation.yml index 8f7163a3024..95375076bd3 100644 --- a/.github/workflows/update_documentation.yml +++ b/.github/workflows/update_documentation.yml @@ -78,6 +78,8 @@ jobs: with: node-version: 24 - name: Install rucio-api generation dependencies and build markdown sites for the API + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | python3 -m pip install -U pip setuptools python3 -m pip install -U -r tools/requirements.txt