Skip to content

Commit 9b9ef03

Browse files
committed
use jinja for long text outputs
1 parent 96c68e8 commit 9b9ef03

1 file changed

Lines changed: 134 additions & 56 deletions

File tree

scripts/tooling/cli/release/check_approvals.py

Lines changed: 134 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,40 @@
2525
from urllib.request import urlopen
2626

2727
from github import Auth, Github # type: ignore[import-untyped]
28+
from jinja2 import Template
2829

2930
from scripts.tooling.lib.known_good import load_known_good
3031

32+
# Jinja2 templates for markdown generation
33+
MODULE_STATUS_TEMPLATE = Template(
34+
"""{% if status == 'disapproved' -%}
35+
- 🚫 **{{ module_name }}**: Changes requested by {{ disapproved_usernames|join(', ') }}
36+
{%- if approved_usernames %} (approved by {{ approved_usernames|join(', ') }}){% endif %}
37+
{% elif status == 'approved' -%}
38+
- ✅ **{{ module_name }}**: Approved by {{ approved_usernames|join(', ') }}
39+
{% else -%}
40+
- ❌ **{{ module_name }}**: Awaiting approval (requires one of: {{ required_approvers|join(', ') }})
41+
{% endif -%}
42+
"""
43+
)
44+
45+
SUMMARY_TEMPLATE = Template(
46+
"""### Release Approval Check Results
47+
48+
**Target Branch:** {{ base_branch }}
49+
50+
{% if all_approved -%}
51+
✅ **Status:** All modules have required approvals
52+
{% else -%}
53+
❌ **Status:** Some modules are missing required approvals
54+
{% endif %}
55+
#### Modules:
56+
{% for module in modules -%}
57+
{{ module }}
58+
{% endfor -%}
59+
"""
60+
)
61+
3162

3263
@dataclass
3364
class ModuleResult:
@@ -264,6 +295,106 @@ def check_pr_reviews(
264295
}
265296

266297

298+
def _format_module_status(module_name: str, result: dict[str, Any]) -> str:
299+
"""Format a single module's approval status as markdown.
300+
301+
Args:
302+
module_name: Name of the module
303+
result: Module result dictionary containing status and approver information
304+
305+
Returns:
306+
Markdown-formatted status line
307+
"""
308+
required_approvers = [m["github"] if isinstance(m, dict) else str(m) for m in result["maintainers"]]
309+
310+
return MODULE_STATUS_TEMPLATE.render(
311+
module_name=module_name,
312+
status=result["status"],
313+
approved_usernames=result["approvedUsernames"],
314+
disapproved_usernames=result["disapprovedUsernames"],
315+
required_approvers=required_approvers,
316+
)
317+
318+
319+
def _write_github_actions_summary(summary: str) -> None:
320+
"""Write summary to GitHub Actions step summary if available.
321+
322+
Args:
323+
summary: Markdown-formatted summary text
324+
"""
325+
if "GITHUB_STEP_SUMMARY" in os.environ:
326+
with open(os.environ["GITHUB_STEP_SUMMARY"], "a") as f:
327+
f.write(summary)
328+
f.write("\n")
329+
330+
331+
def _post_pr_comment(
332+
summary: str,
333+
repo_owner: str,
334+
repo_name: str,
335+
pr_number: int,
336+
github_token: str,
337+
) -> None:
338+
"""Post or update PR comment with approval summary.
339+
340+
Args:
341+
summary: Markdown-formatted summary text
342+
repo_owner: Repository owner
343+
repo_name: Repository name
344+
pr_number: Pull request number
345+
github_token: GitHub authentication token
346+
"""
347+
try:
348+
auth = Auth.Token(github_token)
349+
github = Github(auth=auth)
350+
repo = github.get_repo(f"{repo_owner}/{repo_name}")
351+
pr = repo.get_pull(pr_number)
352+
353+
comment_marker = "<!-- release-approval-check -->"
354+
full_comment = f"{comment_marker}\n{summary}"
355+
356+
# Find existing comment from this workflow
357+
existing_comment = None
358+
for comment in pr.get_issue_comments():
359+
if comment_marker in comment.body:
360+
existing_comment = comment
361+
break
362+
363+
# Update existing or create new comment
364+
if existing_comment:
365+
existing_comment.edit(full_comment)
366+
print("Updated existing PR comment", file=sys.stderr)
367+
else:
368+
pr.create_issue_comment(full_comment)
369+
print("Created new PR comment", file=sys.stderr)
370+
except Exception as e:
371+
print(f"Warning: Failed to post PR comment: {e}", file=sys.stderr)
372+
373+
374+
def _build_summary_markdown(
375+
module_results: dict[str, dict[str, Any]],
376+
all_approved: bool,
377+
base_branch: str,
378+
) -> str:
379+
"""Build the markdown summary text.
380+
381+
Args:
382+
module_results: Dictionary of module approval results
383+
all_approved: Whether all modules are approved
384+
base_branch: Target branch of the PR
385+
386+
Returns:
387+
Markdown-formatted summary string
388+
"""
389+
modules = [_format_module_status(module_name, result) for module_name, result in module_results.items()]
390+
391+
return SUMMARY_TEMPLATE.render(
392+
base_branch=base_branch,
393+
all_approved=all_approved,
394+
modules=modules,
395+
)
396+
397+
267398
def generate_summary(
268399
module_results: dict[str, dict[str, Any]],
269400
all_approved: bool,
@@ -287,65 +418,12 @@ def generate_summary(
287418
Returns:
288419
Markdown-formatted summary string
289420
"""
290-
summary = "### Release Approval Check Results\n\n"
291-
summary += f"**Target Branch:** {base_branch}\n\n"
421+
summary = _build_summary_markdown(module_results, all_approved, base_branch)
292422

293-
if all_approved:
294-
summary += "✅ **Status:** All modules have required approvals\n\n"
295-
else:
296-
summary += "❌ **Status:** Some modules are missing required approvals\n\n"
297-
298-
summary += "#### Modules:\n"
299-
300-
for module_name, result in module_results.items():
301-
if result["status"] == "disapproved":
302-
disapprovers = ", ".join(result["disapprovedUsernames"])
303-
approvers_text = ""
304-
if result["approvedUsernames"]:
305-
approvers_text = f" (approved by {', '.join(result['approvedUsernames'])})"
306-
summary += f"- 🚫 **{module_name}**: Changes requested by {disapprovers}{approvers_text}\n"
307-
elif result["status"] == "approved":
308-
approvers = ", ".join(result["approvedUsernames"])
309-
summary += f"- ✅ **{module_name}**: Approved by {approvers}\n"
310-
else: # pending
311-
required_approvers = ", ".join(
312-
[m["github"] if isinstance(m, dict) else str(m) for m in result["maintainers"]]
313-
)
314-
summary += f"- ❌ **{module_name}**: Awaiting approval (requires one of: {required_approvers})\n"
315-
316-
# Write to GitHub Actions step summary if available
317-
if "GITHUB_STEP_SUMMARY" in os.environ:
318-
with open(os.environ["GITHUB_STEP_SUMMARY"], "a") as f:
319-
f.write(summary)
320-
f.write("\n")
423+
_write_github_actions_summary(summary)
321424

322-
# Post as PR comment if credentials provided
323425
if all([repo_owner, repo_name, pr_number, github_token]):
324-
try:
325-
auth = Auth.Token(github_token)
326-
github = Github(auth=auth)
327-
repo = github.get_repo(f"{repo_owner}/{repo_name}")
328-
pr = repo.get_pull(pr_number)
329-
330-
# Check if there's already a comment from this workflow
331-
comment_marker = "<!-- release-approval-check -->"
332-
existing_comment = None
333-
334-
for comment in pr.get_issue_comments():
335-
if comment_marker in comment.body:
336-
existing_comment = comment
337-
break
338-
339-
full_comment = f"{comment_marker}\n{summary}"
340-
341-
if existing_comment:
342-
existing_comment.edit(full_comment)
343-
print("Updated existing PR comment", file=sys.stderr)
344-
else:
345-
pr.create_issue_comment(full_comment)
346-
print("Created new PR comment", file=sys.stderr)
347-
except Exception as e:
348-
print(f"Warning: Failed to post PR comment: {e}", file=sys.stderr)
426+
_post_pr_comment(summary, repo_owner, repo_name, pr_number, github_token)
349427

350428
return summary
351429

0 commit comments

Comments
 (0)