From 5912e14f2849fbf366bcf80ecb21a73c94fd9c1e Mon Sep 17 00:00:00 2001 From: Melissa DeLucchi Date: Mon, 2 Mar 2026 16:51:44 -0500 Subject: [PATCH] Add last updated time. Break out GSOC PRs. --- src/lf_workflow_dash/data_types.py | 9 +- .../lsdb_interrupts/external_issues.py | 20 +++- .../lsdb_interrupts/open_prs.py | 95 +++++++++++++------ src/lf_workflow_dash/string_helpers.py | 8 ++ templates/issue_list.jinja | 6 ++ templates/pr_list.jinja | 37 ++++++++ 6 files changed, 138 insertions(+), 37 deletions(-) diff --git a/src/lf_workflow_dash/data_types.py b/src/lf_workflow_dash/data_types.py index d564773148c..7f8c7b8fd4e 100644 --- a/src/lf_workflow_dash/data_types.py +++ b/src/lf_workflow_dash/data_types.py @@ -1,10 +1,10 @@ from dataclasses import dataclass, field -from datetime import datetime from typing import List -import pytz import yaml +from lf_workflow_dash.string_helpers import now_time_formatted + @dataclass class WorkflowElemData: @@ -161,9 +161,6 @@ def read_yaml_file(file_path): all_projects.append(project_data) - timezone = pytz.timezone("America/New_York") - last_updated = datetime.now(timezone).strftime("%H:%M %B %d, %Y (US-NYC)") - return { "page_title": page_title, "all_projects": all_projects, @@ -174,7 +171,7 @@ def read_yaml_file(file_path): "contains_live": contains_live, "dash_name": "LINCC Frameworks Builds", "dash_repo": "lf-workflow-dash", - "last_updated": last_updated, + "last_updated": now_time_formatted(), "extra_links": extra_links, "copier_project": copier_project, } diff --git a/src/lf_workflow_dash/lsdb_interrupts/external_issues.py b/src/lf_workflow_dash/lsdb_interrupts/external_issues.py index 2d652a77fc4..3959779b9dd 100644 --- a/src/lf_workflow_dash/lsdb_interrupts/external_issues.py +++ b/src/lf_workflow_dash/lsdb_interrupts/external_issues.py @@ -20,6 +20,7 @@ get_lsdb_repos, paginate_github_api, ) +from lf_workflow_dash.string_helpers import now_time_formatted def get_open_issues(repos: List[str], org_members: Set[str], token: str) -> List[Dict]: @@ -99,7 +100,24 @@ def write_html_issues(external_issues: List[Dict], html_file: str): environment = Environment(loader=FileSystemLoader("templates/")) template = environment.get_template("issue_list.jinja") with open(html_file, mode="w", encoding="utf-8") as results: - results.write(template.render({"external_issues": issue_summaries})) + results.write( + template.render( + { + "external_issues": issue_summaries, + "last_updated": now_time_formatted(), + "extra_links": [ + { + "text": "LSDB Workflows", + "url": "https://lincc-frameworks.github.io/lf-workflow-dash/lsdb.html", + }, + { + "text": "LSDB PRs", + "url": "https://lincc-frameworks.github.io/lf-workflow-dash/lsdb_issues.html", + }, + ], + } + ) + ) print(f"HTML output written to {html_file}") diff --git a/src/lf_workflow_dash/lsdb_interrupts/open_prs.py b/src/lf_workflow_dash/lsdb_interrupts/open_prs.py index 76ff0a2d467..3bdb428d35f 100644 --- a/src/lf_workflow_dash/lsdb_interrupts/open_prs.py +++ b/src/lf_workflow_dash/lsdb_interrupts/open_prs.py @@ -13,6 +13,7 @@ get_org_repos, paginate_github_api, ) +from lf_workflow_dash.string_helpers import now_time_formatted def get_open_prs(repos: List[str], token: str) -> List[Dict]: @@ -63,53 +64,59 @@ def get_open_prs(repos: List[str], token: str) -> List[Dict]: return all_prs -def write_html_prs(prs: List[Dict], html_file: str, page_title: str = "Pull Requests"): +def write_html_prs(prs: List[Dict], html_file: str, attributes: dict): """Fill in the jinja template, using the prs found""" print(f"Writing HTML output to {html_file} ...") prs.sort(key=lambda x: x["updatedAt"], reverse=True) now = datetime.now(timezone.utc) pr_summaries = [] + gsoc_summaries = [] reviewer_summaries = [] for pr in prs: author = pr["author"]["login"] if pr["author"] else "unknown" updated_at = get_humanized_updated_at(pr["updatedAt"], now) tidy_title = pr["title"].replace("&", "&").replace("<", "<").replace(">", ">") - pr_summaries.append( - { - "updated_human": updated_at, - "author": author, - "title": tidy_title, - "url": pr["url"], - "repo": pr["repo"], - "is_draft": "DRAFT" if pr["is_draft"] else "", - "is_bot": author in ["dependabot[bot]", "Copilot"], - "reviewers": ", ".join(pr["reviewers"]), - } - ) - reviewer_summaries.extend( - [ - { - "updated_human": updated_at, - "author": author, - "title": tidy_title, - "url": pr["url"], - "repo": pr["repo"], - "reviewer": reviewer, - } - for reviewer in pr["reviewers"] - ] - ) + pr_summary = { + "updated_human": updated_at, + "author": author, + "title": tidy_title, + "url": pr["url"], + "repo": pr["repo"], + "is_draft": "DRAFT" if pr["is_draft"] else "", + "is_bot": author in ["dependabot[bot]", "Copilot"], + "reviewers": ", ".join(pr["reviewers"]), + } + + if "GSOC26" in tidy_title: + gsoc_summaries.append(pr_summary) + else: + pr_summaries.append(pr_summary) + reviewer_summaries.extend( + [ + { + "updated_human": updated_at, + "author": author, + "title": tidy_title, + "url": pr["url"], + "repo": pr["repo"], + "reviewer": reviewer, + } + for reviewer in pr["reviewers"] + ] + ) reviewer_summaries.sort(key=lambda x: x["reviewer"]) environment = Environment(loader=FileSystemLoader("templates/")) template = environment.get_template("pr_list.jinja") - attributes = { - "page_title": page_title, + attributes = attributes | { "all_prs": pr_summaries, + "gsoc_prs": gsoc_summaries, "reviewer_prs": reviewer_summaries, "num_results": len(pr_summaries), + "num_gsoc_results": len(gsoc_summaries), + "last_updated": now_time_formatted(), } with open(html_file, mode="w", encoding="utf-8") as results: results.write(template.render(attributes)) @@ -120,11 +127,39 @@ def main(token): """Convenience method to do the work.""" repos = get_lsdb_repos(token) prs = get_open_prs(repos, token) - write_html_prs(prs, "html/lsdb_prs.html", "LSDB PRs") + write_html_prs( + prs, + "html/lsdb_prs.html", + { + "page_title": "LSDB PRs", + "extra_links": [ + { + "text": "LSDB Workflows", + "url": "https://lincc-frameworks.github.io/lf-workflow-dash/lsdb.html", + }, + { + "text": "LSDB External Issues", + "url": "https://lincc-frameworks.github.io/lf-workflow-dash/lsdb_issues.html", + }, + ], + }, + ) repos = get_org_repos("lincc-frameworks", token) prs = get_open_prs(repos, token) - write_html_prs(prs, "html/lincc_prs.html", "lincc-frameworks PRs") + write_html_prs( + prs, + "html/lincc_prs.html", + { + "page_title": "lincc-frameworks PRs", + "extra_links": [ + { + "text": "LINCC Frameworks Dash", + "url": "https://lincc-frameworks.github.io/lf-workflow-dash/", + } + ], + }, + ) if __name__ == "__main__": diff --git a/src/lf_workflow_dash/string_helpers.py b/src/lf_workflow_dash/string_helpers.py index 46d6c8267ec..576a1707d6f 100644 --- a/src/lf_workflow_dash/string_helpers.py +++ b/src/lf_workflow_dash/string_helpers.py @@ -35,6 +35,14 @@ def get_conclusion_time(last_run): return (formatted_timestamp, is_stale) +def now_time_formatted(): + """Format the current time for America/New York timezone. + + Used in `last_updated` field in rendered HTML.""" + timezone = pytz.timezone("America/New_York") + return datetime.now(timezone).strftime("%H:%M %B %d, %Y (US-NYC)") + + BASEVERSION = re.compile( r"""[vV]? (?P0|[1-9]\d*) diff --git a/templates/issue_list.jinja b/templates/issue_list.jinja index 7ae0428cd18..48644d8ee3a 100644 --- a/templates/issue_list.jinja +++ b/templates/issue_list.jinja @@ -14,6 +14,12 @@ a:hover { text-decoration: underline; } +

+ Last Updated {{last_updated}} + {% for link in extra_links %} + | {{link.text}} + {% endfor %} +

External Issues (most recent activity first)

diff --git a/templates/pr_list.jinja b/templates/pr_list.jinja index d7be2219247..e106c9a87df 100644 --- a/templates/pr_list.jinja +++ b/templates/pr_list.jinja @@ -15,6 +15,12 @@ td.bot {color: gray; font-style:italic} +

+ Last Updated {{last_updated}} + {% for link in extra_links %} + | {{link.text}} + {% endfor %} +

{{page_title}} ({{num_results}}) (most recent activity first)

@@ -42,6 +48,37 @@ td.bot {color: gray; font-style:italic}
+{% if num_gsoc_results > 0%} +
+
+

GSOC PRs ({{num_gsoc_results}}) (most recent activity first)

+ + + + + + + + + + + + + +{% for pr in gsoc_prs %} + + + {{pr.author}} + + + + + +{% endfor %} + +
Last ActivityAuthorReviewersRepoDraft?Title
{{pr.updated_human}}{{pr.reviewers}}{{pr.repo}}{{pr.is_draft}}{{pr.title}}
+{% endif %} +

This time, by reviewer