Repository: nilearn/nilearn

Issue:
update `maint_tools/check_gha_workflow.py` to also monitor the doc build on main
https://github.com/nilearn/nilearn/blob/main/maint_tools/check_gha_workflow.py


Discussion:

Resolving patch:
diff --git a/maint_tools/check_gha_workflow.py b/maint_tools/check_gha_workflow.py
--- a/maint_tools/check_gha_workflow.py
+++ b/maint_tools/check_gha_workflow.py
@@ -61,13 +61,11 @@
 # file containing your github token
 TOKEN_FILE = Path("/home/remi/Documents/tokens/gh_read_repo_for_orga.txt")
 
-# can be found out at
-# "https://api.github.com/repos/{USER}/{REPO}/actions/workflows"
-WORKFLOW_ID = "71549417"
-
 BRANCH = "main"
 
-INCLUDE_FAILED_RUNS = True
+# Set to True if yu want to also include the runs of a CI workflow
+# that did not complete successfully.
+INCLUDE_FAILED_RUNS = False
 
 # Pages of runs to collect
 # 100 per page
@@ -79,41 +77,52 @@
 # used by set_python_version to filter jobs by their python version
 EXPECTED_PYTHON_VERSIONS = ["3.8", "3.9", "3.10", "3.11", "3.12"]
 
-OUTPUT_FILE = Path(__file__).parent / "test_runs_timing.tsv"
-
 
 def main(args=sys.argv) -> None:
     """Collect duration of each job and plots them."""
-    update_tsv = UPDATE_TSV if OUTPUT_FILE.exists() else True
+    # %%
+    # can be found out at
+    # "https://api.github.com/repos/{USER}/{REPO}/actions/workflows"
+    TEST_WORKFLOW_ID = "71549417"
+    output_file = Path(__file__).parent / "test_runs_timing.tsv"
+
+    _update_tsv(
+        args,
+        update=UPDATE_TSV,
+        output_file=output_file,
+        workflow_id=TEST_WORKFLOW_ID,
+    )
 
-    if update_tsv:
+    df = pd.read_csv(
+        output_file,
+        sep="\t",
+        parse_dates=["started_at", "completed_at"],
+    )
 
-        if len(args) > 1:
-            TOKEN = args[1]
-            auth = {"Authorization": "token " + TOKEN}
-        else:
-            auth = get_auth(USERNAME, TOKEN_FILE)
-
-        jobs_data = {"name": [], "started_at": [], "completed_at": []}
-
-        for page in PAGES_TO_COLLECT:
-            runs = get_runs(
-                WORKFLOW_ID,
-                auth,
-                page=page,
-                include_failed_runs=INCLUDE_FAILED_RUNS,
-            )
-            if len(runs) > 0:
-                print(f" found {len(runs)} runs")
-                jobs_data = update_jobs_data(jobs_data, runs, auth)
-            else:
-                break
-
-        df = pd.DataFrame(jobs_data)
-        df.to_csv(OUTPUT_FILE, sep="\t", index=False)
+    df["duration"] = (df["completed_at"] - df["started_at"]) / pd.Timedelta(
+        minutes=1
+    )
+    df["python"] = df["name"].apply(_set_python_version)
+    df["OS"] = df["name"].apply(_set_os)
+    df["dependencies"] = df["name"].apply(_set_dependencies)
+
+    print(df)
+
+    _plot_test_job_durations(df, output_file)
+
+    # %%
+    DOC_WORKFLOW_ID = "37349438"
+    output_file = Path(__file__).parent / "doc_runs_timing.tsv"
+
+    _update_tsv(
+        args,
+        update=UPDATE_TSV,
+        output_file=output_file,
+        workflow_id=DOC_WORKFLOW_ID,
+    )
 
     df = pd.read_csv(
-        OUTPUT_FILE,
+        output_file,
         sep="\t",
         parse_dates=["started_at", "completed_at"],
     )
@@ -121,16 +130,50 @@ def main(args=sys.argv) -> None:
     df["duration"] = (df["completed_at"] - df["started_at"]) / pd.Timedelta(
         minutes=1
     )
-    df["python"] = df["name"].apply(set_python_version)
-    df["OS"] = df["name"].apply(set_os)
-    df["dependencies"] = df["name"].apply(set_dependencies)
+
+    df = df[df["name"] == "build_docs"]
+    df = df[df["duration"] < 360]
 
     print(df)
 
-    plot_job_durations(df)
+    _plot_doc_job_durations(df, output_file)
+
+
+def _update_tsv(
+    args, update: bool, output_file: Path, workflow_id: str
+) -> None:
+    """Update TSV containing run time of every workflow."""
+    update_tsv = update if output_file.exists() else True
 
+    if not update_tsv:
+        return
 
-def plot_job_durations(df: pd.DataFrame) -> None:
+    if len(args) > 1:
+        TOKEN = args[1]
+        auth = {"Authorization": "token " + TOKEN}
+    else:
+        auth = _get_auth(USERNAME, TOKEN_FILE)
+
+    jobs_data = {"name": [], "started_at": [], "completed_at": []}
+
+    for page in PAGES_TO_COLLECT:
+        runs = _get_runs(
+            workflow_id,
+            auth,
+            page=page,
+            include_failed_runs=INCLUDE_FAILED_RUNS,
+        )
+        if len(runs) > 0:
+            print(f" found {len(runs)} runs")
+            jobs_data = _update_jobs_data(jobs_data, runs, auth)
+        else:
+            break
+
+    df = pd.DataFrame(jobs_data)
+    df.to_csv(output_file, sep="\t", index=False)
+
+
+def _plot_test_job_durations(df: pd.DataFrame, output_file: Path) -> None:
     """Plot and save."""
     fig = px.line(
         df,
@@ -151,11 +194,27 @@ def plot_job_durations(df: pd.DataFrame) -> None:
     fig.update_xaxes(dtick="M1", tickformat="%b\n%Y")
     fig.update_layout(autosize=True, width=1000, height=700)
 
-    fig.write_image(OUTPUT_FILE.with_suffix(".png"), engine="kaleido")
-    fig.write_html(OUTPUT_FILE.with_suffix(".html"))
+    fig.write_image(output_file.with_suffix(".png"), engine="kaleido")
+    fig.write_html(output_file.with_suffix(".html"))
+
+
+def _plot_doc_job_durations(df: pd.DataFrame, output_file: Path) -> None:
+    """Plot and save."""
+    fig = px.line(
+        df,
+        x="started_at",
+        y="duration",
+        title="Duration of nilearn doc build",
+    )
+
+    fig.update_xaxes(dtick="M1", tickformat="%b\n%Y")
+    fig.update_layout(autosize=True, width=1000, height=700)
+
+    fig.write_image(output_file.with_suffix(".png"), engine="kaleido")
+    fig.write_html(output_file.with_suffix(".html"))
 
 
-def set_os(x: str) -> str:
+def _set_os(x: str) -> str:
     """Detect which OS the job was run on.
 
     .. note::
@@ -175,7 +234,7 @@ def set_os(x: str) -> str:
         return "n/a"
 
 
-def set_python_version(x: str) -> str:
+def _set_python_version(x: str) -> str:
     """Detect which python version the job was run on.
 
     .. note::
@@ -191,7 +250,7 @@ def set_python_version(x: str) -> str:
     )
 
 
-def set_dependencies(x: str) -> str:
+def _set_dependencies(x: str) -> str:
     """Detect which set of dependencies was used for the run.
 
     .. note::
@@ -211,7 +270,7 @@ def set_dependencies(x: str) -> str:
     )
 
 
-def get_auth(username: str, token_file: Path) -> None | tuple[str, str]:
+def _get_auth(username: str, token_file: Path) -> None | tuple[str, str]:
     """Get authentication with token."""
     token = None
 
@@ -224,7 +283,7 @@ def get_auth(username: str, token_file: Path) -> None | tuple[str, str]:
     return None if username is None or token is None else (username, token)
 
 
-def get_runs(
+def _get_runs(
     workflow_id: str,
     auth: None | tuple[str, str] = None,
     page: int = 1,
@@ -243,7 +302,7 @@ def get_runs(
 
     print(f"pinging: {url}")
 
-    content = handle_request(url, auth)
+    content = _handle_request(url, auth)
 
     if not content.get("workflow_runs"):
         return []
@@ -258,7 +317,7 @@ def get_runs(
     ]
 
 
-def handle_request(url: str, auth: None | tuple[str, str]):
+def _handle_request(url: str, auth: None | tuple[str, str]):
     """Wrap request."""
     if isinstance(auth, tuple):
         response = requests.get(url, auth=auth)
@@ -274,7 +333,7 @@ def handle_request(url: str, auth: None | tuple[str, str]):
     return response.json()
 
 
-def update_jobs_data(
+def _update_jobs_data(
     jobs_data: dict[str, list[str]],
     runs: list[dict[str, Any]],
     auth: None | tuple[str, str] = None,
@@ -283,7 +342,7 @@ def update_jobs_data(
     for run in runs:
         print(f'{run["id"]}: {run["display_title"]}')
 
-        content = handle_request(run["jobs_url"], auth)
+        content = _handle_request(run["jobs_url"], auth)
 
         for job in content.get("jobs", {}):
             for key in jobs_data:


Test patch: