From 8869243d49e58848c25d014ae44de1a6d8b96314 Mon Sep 17 00:00:00 2001 From: buddhisthead Date: Fri, 24 Oct 2025 11:33:20 -0700 Subject: [PATCH 1/8] Update the updater script to use the status field --- .github/workflows/weekly-update-dev.yml | 97 +++++ .github/workflows/weekly-update.yml | 52 +++ scripts/weekly_status_markdown.py | 533 ++++++++++++++++++++++++ 3 files changed, 682 insertions(+) create mode 100644 .github/workflows/weekly-update-dev.yml create mode 100644 .github/workflows/weekly-update.yml create mode 100644 scripts/weekly_status_markdown.py diff --git a/.github/workflows/weekly-update-dev.yml b/.github/workflows/weekly-update-dev.yml new file mode 100644 index 00000000..b9aac2cd --- /dev/null +++ b/.github/workflows/weekly-update-dev.yml @@ -0,0 +1,97 @@ +name: Weekly Status (dev) + +on: + # run on every push to your dev branch + push: + branches: + - weekly-status-dev + paths: + - "scripts/weekly_confluence_markdown.py" + - ".github/workflows/weekly-update-dev.yml" + # run on any PR that targets your default branch and originates from same repo + pull_request: + branches: + - main + paths: + - "scripts/weekly_confluence_markdown.py" + - ".github/workflows/weekly-update-dev.yml" + # allow manual runs + workflow_dispatch: + +permissions: + contents: read + issues: read + pull-requests: write # needed to post/update a PR comment + +concurrency: + group: weekly-status-dev-${{ github.ref }} + cancel-in-progress: true + +jobs: + dev-run: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install deps + run: pip install requests + + - name: Generate weekly markdown + id: gen + env: + # Prefer the built-in token during dev + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: input-output-hk/acropolis + + # ---- If you have a Projects (v2) board, uncomment and set these: + # PROJECT_OWNER: input-output-hk + # PROJECT_NUMBER: 7 + # STATUS_DONE_VALUE: Done + # STATUS_INPROGRESS_VALUE: In Progress + + # ---- Or fall back to labels during dev: + # DONE_LABELS: "status: done,done" + # INPROGRESS_LABELS: "status: in progress,in progress" + + OUTPUT_PATH: artifacts/weekly_status.md + run: | + python scripts/weekly_confluence_markdown.py | tee /tmp/out.md + echo "md<> $GITHUB_OUTPUT + cat /tmp/out.md >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Upload artifact (dev) + uses: actions/upload-artifact@v4 + with: + name: weekly-status-dev + path: artifacts/weekly_status.md + if-no-files-found: warn + + # Post or update a sticky PR comment with the Markdown (only on PR events) + - name: Create/Update PR comment + if: ${{ github.event_name == 'pull_request' }} + uses: actions/github-script@v7 + with: + script: | + const header = "### πŸ“‹ Weekly Status (auto-generated DEV)"; + const body = `${header}\n\n${process.env.MD}`; + const { owner, repo, number } = context.issue; + // Find an existing bot comment to update + const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number: number, per_page: 100 }); + const mine = comments.find(c => + c.user.type === "Bot" && + c.user.login.endsWith("[bot]") && + c.body && c.body.startsWith(header) + ); + if (mine) { + await github.rest.issues.updateComment({ owner, repo, comment_id: mine.id, body }); + } else { + await github.rest.issues.createComment({ owner, repo, issue_number: number, body }); + } + env: + MD: ${{ steps.gen.outputs.md }} diff --git a/.github/workflows/weekly-update.yml b/.github/workflows/weekly-update.yml new file mode 100644 index 00000000..fed78943 --- /dev/null +++ b/.github/workflows/weekly-update.yml @@ -0,0 +1,52 @@ +name: Weekly Status Markdown + +on: + schedule: + - cron: "0 14 * * MON" # Mondays 07:00 PT (14:00 UTC) + workflow_dispatch: + +jobs: + build-status: + runs-on: ubuntu-latest + permissions: + contents: read + issues: read + pull-requests: read + # if you use org/user Projects v2, you may need: project: read + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install deps + run: pip install requests + + - name: Generate weekly status markdown + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: input-output-hk/acropolis + # --- Use ONE of the two approaches below --- + + # A) Projects v2 (recommended): + PROJECT_OWNER: input-output-hk + PROJECT_NUMBER: 7 + STATUS_DONE_VALUE: Done + STATUS_INPROGRESS_VALUE: In Progress + + # B) Fallback via labels: + # DONE_LABELS: "status: done,done" + # INPROGRESS_LABELS: "status: in progress,in progress" + + OUTPUT_PATH: artifacts/weekly_status.md + run: | + python scripts/weekly_status_markdown.py + + - name: Upload markdown artifact + uses: actions/upload-artifact@v4 + with: + name: weekly-status + path: artifacts/weekly_status.md + if-no-files-found: warn diff --git a/scripts/weekly_status_markdown.py b/scripts/weekly_status_markdown.py new file mode 100644 index 00000000..b8ff4ae9 --- /dev/null +++ b/scripts/weekly_status_markdown.py @@ -0,0 +1,533 @@ +#!/usr/bin/env python3 +""" +Generates a 2-column Markdown snippet for weekly status: +- Last week's achievements (Status == Done) +- Plans for next week (Status == In Progress) + +Data sources (in priority order): + A) GitHub Projects v2 Status field (org-level or user-level project) + B) Issue labels (DONE_LABELS / INPROGRESS_LABELS, comma-separated) + +Environment: + GH_TOKEN (required) GitHub token with repo read + project read + REPO (required) e.g. "input-output-hk/acropolis" + # --- For Projects v2 (preferred) --- + PROJECT_OWNER (optional) e.g. "input-output-hk" (org login) or user login + PROJECT_NUMBER (optional) e.g. "7" + STATUS_DONE_VALUE (optional) defaults to "Done" + STATUS_INPROGRESS_VALUE (optional) defaults to "In Progress" + # --- Fallback via labels --- + DONE_LABELS (optional) comma-separated (default: "status: done,done") + INPROGRESS_LABELS (optional) comma-separated (default: "status: in progress,in progress") + +Behavior: + - "Last week" time window is the previous Mon–Sun in repo default timezone (UTC). + - "Achievements" are items with Status == Done and (closed/merged/updated) in last week. + - "Plans" are items currently Status == In Progress and updated within last 14 days. + +Output: + - Prints Markdown to stdout + - If GITHUB_STEP_SUMMARY is set, also writes to it. + - If OUTPUT_PATH is set, also writes file to that path. +""" + +# Suppress urllib3 warnings early +import warnings +warnings.filterwarnings("ignore", message="urllib3 v2 only supports OpenSSL 1.1.1+") +warnings.filterwarnings("ignore", category=UserWarning, module="urllib3") + +import os +import sys +import json +import datetime as dt +from typing import List, Dict, Optional, Tuple +import requests + +GITHUB_API = "https://api.github.com/graphql" +REST_API = "https://api.github.com" + +def iso(d: dt.datetime) -> str: + return d.replace(microsecond=0, tzinfo=dt.timezone.utc).isoformat().replace("+00:00", "Z") + +def previous_monday(d: dt.date) -> dt.date: + return d - dt.timedelta(days=(d.weekday())) # Monday is 0 + +def last_week_window(today: dt.date) -> Tuple[dt.datetime, dt.datetime]: + # Define "last week" as the Monday..Sunday immediately before the current week. + this_monday = previous_monday(today) + last_monday = this_monday - dt.timedelta(days=7) + last_sunday = this_monday - dt.timedelta(seconds=1) + start = dt.datetime.combine(last_monday, dt.time(0,0,0), tzinfo=dt.timezone.utc) + end = dt.datetime.combine(last_sunday, dt.time(23,59,59), tzinfo=dt.timezone.utc) + return start, end + +def gh_graphql(token: str, query: str, variables: dict) -> dict: + r = requests.post( + GITHUB_API, + headers={"Authorization": f"bearer {token}", "Accept": "application/vnd.github+json"}, + json={"query": query, "variables": variables}, + timeout=60, + ) + r.raise_for_status() + data = r.json() + if "errors" in data: + raise RuntimeError(f"GitHub GraphQL errors: {data['errors']}") + return data["data"] + +def gh_rest(token: str, url: str, params: dict=None) -> dict: + r = requests.get( + url, + headers={"Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json"}, + params=params or {}, + timeout=60, + ) + r.raise_for_status() + return r.json() + +def discover_projects(token: str, owner: str) -> List[dict]: + """ + Discover available GitHub Projects v2 for the given owner (org or user). + Returns list of {number, title} dicts. + """ + query = """ + query($owner: String!) { + organization(login: $owner) { + projectsV2(first: 20) { + nodes { number title } + } + } + user(login: $owner) { + projectsV2(first: 20) { + nodes { number title } + } + } + } + """ + try: + data = gh_graphql(token, query, {"owner": owner}) + projects = [] + + # Try org projects first + org_projects = data.get("organization", {}) + if org_projects and org_projects.get("projectsV2"): + projects.extend(org_projects["projectsV2"]["nodes"] or []) + + # Try user projects + user_projects = data.get("user", {}) + if user_projects and user_projects.get("projectsV2"): + projects.extend(user_projects["projectsV2"]["nodes"] or []) + + return [p for p in projects if p] # filter out nulls + except Exception as e: + print(f"[warn] Failed to discover projects for {owner}: {e}", file=sys.stderr) + return [] + +def get_project_and_status_field(token: str, owner: str, number: int) -> Tuple[str, str, Dict[str,str]]: + """ + Returns (projectId, statusFieldId, statusOptionsMap{name->optionId}) + """ + query = """ + query($owner: String!, $number: Int!) { + organization(login: $owner) { + projectV2(number: $number) { + id + fields(first: 50) { + nodes { + ... on ProjectV2FieldCommon { + id + name + dataType + } + ... on ProjectV2SingleSelectField { + id + name + dataType + options { id name } + } + } + } + } + } + user(login: $owner) { + projectV2(number: $number) { + id + fields(first: 50) { + nodes { + ... on ProjectV2FieldCommon { + id + name + dataType + } + ... on ProjectV2SingleSelectField { + id + name + dataType + options { id name } + } + } + } + } + } + } + """ + data = gh_graphql(token, query, {"owner": owner, "number": number}) + proj = data.get("organization", {}).get("projectV2") or data.get("user", {}).get("projectV2") + if not proj: + raise RuntimeError("Project not found (check PROJECT_OWNER/PROJECT_NUMBER).") + project_id = proj["id"] + + status_field = None + status_options = {} + for f in proj["fields"]["nodes"]: + if f and f.get("name") == "Status": + status_field = f["id"] + opts = f.get("options") or [] + status_options = {o["name"]: o["id"] for o in opts} + break + if not status_field: + raise RuntimeError("Status field not found on the project.") + return project_id, status_field, status_options + +def items_by_status_from_project(token: str, owner: str, number: int, + wanted_status_names: List[str], + repo_fullname: str) -> Dict[str, List[dict]]: + """ + Returns map {statusName: [ {title,url,number,updatedAt,closedAt,type} ]} + filtered to the specified repo. + """ + project_id, status_field_id, status_options = get_project_and_status_field(token, owner, number) + wanted_option_ids = [status_options[n] for n in wanted_status_names if n in status_options] + + results = {n: [] for n in wanted_status_names} + + # Paginate through project items + query = """ + query($projectId: ID!, $after: String) { + node(id: $projectId) { + ... on ProjectV2 { + items(first: 100, after: $after) { + pageInfo { hasNextPage endCursor } + nodes { + updatedAt + content { + __typename + ... on Issue { + number + title + url + repository { nameWithOwner } + closedAt + } + ... on PullRequest { + number + title + url + repository { nameWithOwner } + mergedAt + closedAt + } + } + fieldValues(first: 20) { + nodes { + __typename + ... on ProjectV2ItemFieldSingleSelectValue { + field { ... on ProjectV2SingleSelectField { id name } } + optionId + } + } + } + } + } + } + } + } + """ + after = None + while True: + data = gh_graphql(token, query, {"projectId": project_id, "after": after}) + items = data["node"]["items"]["nodes"] + for it in items: + # find the Status value (optionId) + option_id = None + for fv in it["fieldValues"]["nodes"]: + if fv.get("__typename") == "ProjectV2ItemFieldSingleSelectValue" and \ + fv.get("field", {}).get("id") == status_field_id: + option_id = fv.get("optionId") + break + if option_id not in wanted_option_ids: + continue + + content = it.get("content") or {} + typename = content.get("__typename") + if typename not in ("Issue", "PullRequest"): + continue + if content["repository"]["nameWithOwner"].lower() != repo_fullname.lower(): + continue + + closedAt = content.get("closedAt") + mergedAt = content.get("mergedAt") + updatedAt = it["updatedAt"] + + results_key = None + for name, oid in status_options.items(): + if oid == option_id: + results_key = name + break + if not results_key: + continue + + results[results_key].append({ + "type": typename, + "title": content["title"], + "url": content["url"], + "number": content["number"], + "closedAt": closedAt, + "mergedAt": mergedAt, + "updatedAt": updatedAt, + }) + + pi = data["node"]["items"]["pageInfo"] + if not pi["hasNextPage"]: + break + after = pi["endCursor"] + + return results + +def search_by_labels(token: str, repo: str, labels: List[str]) -> List[dict]: + """ + Simple REST search for issues with any of the labels. + """ + items = [] + for lab in labels: + page = 1 + while True: + res = gh_rest(token, f"{REST_API}/repos/{repo}/issues", + params={"state": "all", "labels": lab, "per_page": 100, "page": page}) + if not res: + break + for it in res: + # Skip pull-request-only stubs unless desired; GitHub REST issues may include PRs + title = it["title"] + url = it["html_url"] + num = it["number"] + closedAt = it.get("closed_at") + updatedAt = it.get("updated_at") + is_pr = "pull_request" in it + items.append({ + "type": "PullRequest" if is_pr else "Issue", + "title": title, "url": url, "number": num, + "closedAt": closedAt, "mergedAt": None, "updatedAt": updatedAt + }) + if len(res) < 100: + break + page += 1 + # de-dup by number + seen = {} + for x in items: + seen[x["number"]] = x + return list(seen.values()) + +def get_recent_issues_by_state(token: str, repo: str, state: str = "all", days: int = 7) -> List[dict]: + """ + Fetch recent issues/PRs from the repo by state (open/closed/all). + More targeted approach when labels aren't well organized. + """ + items = [] + page = 1 + + # Calculate the date threshold + since_date = dt.datetime.now(dt.timezone.utc) - dt.timedelta(days=days) + since_iso = since_date.isoformat().replace('+00:00', 'Z') + + while True: + params = { + "state": state, + "per_page": 100, + "page": page, + "sort": "updated", + "direction": "desc", + "since": since_iso + } + + res = gh_rest(token, f"{REST_API}/repos/{repo}/issues", params=params) + if not res: + break + + for it in res: + title = it["title"] + url = it["html_url"] + num = it["number"] + closedAt = it.get("closed_at") + updatedAt = it.get("updated_at") + createdAt = it.get("created_at") + state_val = it.get("state") + is_pr = "pull_request" in it + + items.append({ + "type": "PullRequest" if is_pr else "Issue", + "title": title, + "url": url, + "number": num, + "closedAt": closedAt, + "mergedAt": None, + "updatedAt": updatedAt, + "createdAt": createdAt, + "state": state_val + }) + + if len(res) < 100: + break + page += 1 + + return items + +def format_markdown(done_items: List[dict], inprog_items: List[dict]) -> str: + def fmt(items: List[dict]) -> str: + if not items: + return "_(none)_" + # sort by updatedAt desc + items = sorted(items, key=lambda x: x.get("updatedAt") or "", reverse=True) + lines = [] + for it in items: + t = "PR" if it["type"] == "PullRequest" else "Issue" + lines.append(f"- [{t} #{it['number']}]({it['url']}) β€” {it['title']}") + return "\n".join(lines) + + left = fmt(done_items) + right = fmt(inprog_items) + + # 2-column Markdown table (works well when pasted into Confluence that supports markdown) + md = [] + md.append("| Last week’s achievements | Plans for next week |") + md.append("|---|---|") + # replace newlines with
so lists render in table cells + md.append(f"| {left.replace(chr(10), '
')} | {right.replace(chr(10), '
')} |") + return "\n".join(md) + +def main(): + token = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN") + repo = os.environ.get("REPO") + if not token or not repo: + print("Missing GH_TOKEN/GITHUB_TOKEN or REPO (e.g., 'input-output-hk/acropolis')", file=sys.stderr) + sys.exit(2) + + # time windows + today = dt.datetime.now(dt.timezone.utc).date() + last_start, last_end = last_week_window(today) + + status_done_val = os.environ.get("STATUS_DONE_VALUE", "Done") + status_ip_val = os.environ.get("STATUS_INPROGRESS_VALUE", "In Progress") + + done_items: List[dict] = [] + inprog_items: List[dict] = [] + + proj_owner = os.environ.get("PROJECT_OWNER") + proj_number = os.environ.get("PROJECT_NUMBER") + + # Auto-discover projects if not specified + if not proj_owner or not proj_number: + # Try the repo owner first + repo_owner = repo.split('/')[0] + print(f"[info] No project specified, discovering projects for {repo_owner}...", file=sys.stderr) + projects = discover_projects(token, repo_owner) + + if projects: + print(f"[info] Found {len(projects)} projects:", file=sys.stderr) + for p in projects: + print(f" - Project {p['number']}: {p['title']}", file=sys.stderr) + + # Use the first project as default + if not proj_owner: + proj_owner = repo_owner + if not proj_number: + proj_number = str(projects[0]['number']) + print(f"[info] Auto-selecting project {proj_number}: {projects[0]['title']}", file=sys.stderr) + else: + print(f"[info] No projects found for {repo_owner}", file=sys.stderr) + + try_projects = bool(proj_owner and proj_number) + + if try_projects: + try: + print(f"[info] Attempting to use GitHub Project: owner={proj_owner}, number={proj_number}", file=sys.stderr) + buckets = items_by_status_from_project( + token, proj_owner, int(proj_number), + [status_done_val, status_ip_val], + repo_fullname=repo + ) + done_items = buckets.get(status_done_val, []) + inprog_items = buckets.get(status_ip_val, []) + print(f"[info] Found {len(done_items)} done items, {len(inprog_items)} in-progress items from project", file=sys.stderr) + except Exception as e: + print(f"[warn] Project v2 lookup failed, falling back to state-based filtering: {e}", file=sys.stderr) + try_projects = False + + if not try_projects: + # Try label-based approach first + done_labels = os.environ.get("DONE_LABELS", "").split(",") + ip_labels = os.environ.get("INPROGRESS_LABELS", "").split(",") + + # Filter out empty labels + done_labels = [s.strip() for s in done_labels if s.strip()] + ip_labels = [s.strip() for s in ip_labels if s.strip()] + + if done_labels or ip_labels: + # Use label-based approach if labels are specified + done_items = search_by_labels(token, repo, done_labels) if done_labels else [] + inprog_items= search_by_labels(token, repo, ip_labels) if ip_labels else [] + else: + # Fallback: use state-based approach + print("[info] No specific labels configured, using state-based filtering", file=sys.stderr) + + # Get all recent issues from the last 14 days for broader context + all_recent = get_recent_issues_by_state(token, repo, state="all", days=14) + + # Split into done (closed) and in-progress (open) items + done_items = [it for it in all_recent if it.get("state") == "closed"] + inprog_items = [it for it in all_recent if it.get("state") == "open"] + + # Filter by time windows + def closed_last_week(it: dict) -> bool: + # For achievements: closed/merged in the last week + for k in ("mergedAt", "closedAt"): + v = it.get(k) + if not v: + continue + t = dt.datetime.fromisoformat(v.replace("Z","+00:00")) + if last_start <= t <= last_end: + return True + return False + + def updated_recent(it: dict, days=7) -> bool: + # For in-progress: updated in the last week + v = it.get("updatedAt") + if not v: + return False + t = dt.datetime.fromisoformat(v.replace("Z","+00:00")) + return (dt.datetime.now(dt.timezone.utc) - t).days <= days + + # Achievements: items that were closed in the last week + achievements = [it for it in done_items if closed_last_week(it)] + + # Plans: open items that were updated in the last week (showing active work) + plans = [it for it in inprog_items if it.get("state") == "open" and updated_recent(it, days=7)] + + md = format_markdown(achievements, plans) + + # Print and optionally write outputs + print(md) + step_summary = os.environ.get("GITHUB_STEP_SUMMARY") + if step_summary: + with open(step_summary, "a", encoding="utf-8") as f: + f.write("\n\n## Weekly Status (auto-generated)\n") + f.write(md) + f.write("\n") + + out_path = os.environ.get("OUTPUT_PATH") + if out_path: + os.makedirs(os.path.dirname(out_path), exist_ok=True) + with open(out_path, "w", encoding="utf-8") as f: + f.write(md) + +if __name__ == "__main__": + main() From 48b25d155c924a00aa0317a09db1a1961ddf1249 Mon Sep 17 00:00:00 2001 From: buddhisthead Date: Fri, 24 Oct 2025 11:40:09 -0700 Subject: [PATCH 2/8] Break into two separate lists for easier copy/paste into confluence --- scripts/weekly_status_markdown.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/scripts/weekly_status_markdown.py b/scripts/weekly_status_markdown.py index b8ff4ae9..9377ebcd 100644 --- a/scripts/weekly_status_markdown.py +++ b/scripts/weekly_status_markdown.py @@ -9,7 +9,6 @@ B) Issue labels (DONE_LABELS / INPROGRESS_LABELS, comma-separated) Environment: - GH_TOKEN (required) GitHub token with repo read + project read REPO (required) e.g. "input-output-hk/acropolis" # --- For Projects v2 (preferred) --- PROJECT_OWNER (optional) e.g. "input-output-hk" (org login) or user login @@ -396,12 +395,16 @@ def fmt(items: List[dict]) -> str: left = fmt(done_items) right = fmt(inprog_items) - # 2-column Markdown table (works well when pasted into Confluence that supports markdown) + # Two separate sections for easy copying to Confluence md = [] - md.append("| Last week’s achievements | Plans for next week |") - md.append("|---|---|") - # replace newlines with
so lists render in table cells - md.append(f"| {left.replace(chr(10), '
')} | {right.replace(chr(10), '
')} |") + md.append("## Last week's achievements") + md.append("") + md.append(left) + md.append("") + md.append("## Plans for next week") + md.append("") + md.append(right) + return "\n".join(md) def main(): From 15a3732c8994eae10e7c6b944b14972a21569834 Mon Sep 17 00:00:00 2001 From: buddhisthead Date: Fri, 24 Oct 2025 11:45:01 -0700 Subject: [PATCH 3/8] Update spelling of updater script --- .github/workflows/weekly-update-dev.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/weekly-update-dev.yml b/.github/workflows/weekly-update-dev.yml index b9aac2cd..ccbca439 100644 --- a/.github/workflows/weekly-update-dev.yml +++ b/.github/workflows/weekly-update-dev.yml @@ -6,14 +6,14 @@ on: branches: - weekly-status-dev paths: - - "scripts/weekly_confluence_markdown.py" + - "scripts/weekly_status_markdown.py" - ".github/workflows/weekly-update-dev.yml" # run on any PR that targets your default branch and originates from same repo pull_request: branches: - main paths: - - "scripts/weekly_confluence_markdown.py" + - "scripts/weekly_status_markdown.py" - ".github/workflows/weekly-update-dev.yml" # allow manual runs workflow_dispatch: @@ -60,7 +60,7 @@ jobs: OUTPUT_PATH: artifacts/weekly_status.md run: | - python scripts/weekly_confluence_markdown.py | tee /tmp/out.md + python scripts/weekly_status_markdown.py | tee /tmp/out.md echo "md<> $GITHUB_OUTPUT cat /tmp/out.md >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT From 7ee90611e283615575f7f10e3ee6db9ba493a9db Mon Sep 17 00:00:00 2001 From: buddhisthead Date: Fri, 24 Oct 2025 11:56:51 -0700 Subject: [PATCH 4/8] Auto-create an issue after the workflow runs, with the results --- .github/workflows/weekly-update-dev.yml | 34 ++++++++++++++++++++----- .github/workflows/weekly-update.yml | 27 +++++++++++++++++++- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/.github/workflows/weekly-update-dev.yml b/.github/workflows/weekly-update-dev.yml index ccbca439..81a33537 100644 --- a/.github/workflows/weekly-update-dev.yml +++ b/.github/workflows/weekly-update-dev.yml @@ -20,7 +20,7 @@ on: permissions: contents: read - issues: read + issues: write # needed to create/update issues pull-requests: write # needed to post/update a PR comment concurrency: @@ -41,6 +41,10 @@ jobs: - name: Install deps run: pip install requests + - name: Get current date + id: date + run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + - name: Generate weekly markdown id: gen env: @@ -48,11 +52,11 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: input-output-hk/acropolis - # ---- If you have a Projects (v2) board, uncomment and set these: - # PROJECT_OWNER: input-output-hk - # PROJECT_NUMBER: 7 - # STATUS_DONE_VALUE: Done - # STATUS_INPROGRESS_VALUE: In Progress + # ---- Projects (v2) board integration: + PROJECT_OWNER: input-output-hk + PROJECT_NUMBER: 7 + STATUS_DONE_VALUE: Done + STATUS_INPROGRESS_VALUE: In Progress # ---- Or fall back to labels during dev: # DONE_LABELS: "status: done,done" @@ -60,6 +64,7 @@ jobs: OUTPUT_PATH: artifacts/weekly_status.md run: | + mkdir -p artifacts python scripts/weekly_status_markdown.py | tee /tmp/out.md echo "md<> $GITHUB_OUTPUT cat /tmp/out.md >> $GITHUB_OUTPUT @@ -72,6 +77,23 @@ jobs: path: artifacts/weekly_status.md if-no-files-found: warn + # Create GitHub Issue with weekly status (for testing) + - name: Create Weekly Status Issue (DEV) + if: ${{ github.event_name == 'workflow_dispatch' }} + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const content = fs.readFileSync('artifacts/weekly_status.md', 'utf8'); + + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `πŸ“Š Weekly Status (DEV TEST) - ${{ steps.date.outputs.date }}`, + body: content, + labels: ['weekly-status', 'dev-test'] + }); + # Post or update a sticky PR comment with the Markdown (only on PR events) - name: Create/Update PR comment if: ${{ github.event_name == 'pull_request' }} diff --git a/.github/workflows/weekly-update.yml b/.github/workflows/weekly-update.yml index fed78943..2e9ea198 100644 --- a/.github/workflows/weekly-update.yml +++ b/.github/workflows/weekly-update.yml @@ -24,7 +24,12 @@ jobs: - name: Install deps run: pip install requests + - name: Get current date + id: date + run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + - name: Generate weekly status markdown + id: gen env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: input-output-hk/acropolis @@ -42,7 +47,11 @@ jobs: OUTPUT_PATH: artifacts/weekly_status.md run: | - python scripts/weekly_status_markdown.py + mkdir -p artifacts + python scripts/weekly_status_markdown.py | tee /tmp/weekly_status.md + echo "md<> $GITHUB_OUTPUT + cat /tmp/weekly_status.md >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT - name: Upload markdown artifact uses: actions/upload-artifact@v4 @@ -50,3 +59,19 @@ jobs: name: weekly-status path: artifacts/weekly_status.md if-no-files-found: warn + + # Create GitHub Issue with weekly status + - name: Create Weekly Status Issue + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const content = fs.readFileSync('artifacts/weekly_status.md', 'utf8'); + + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `πŸ“Š Weekly Status - Week of ${{ steps.date.outputs.date }}`, + body: content, + labels: ['weekly-status', 'automated'] + }); From 252257b7f8f4e01b172dac873ea06f2938a5817a Mon Sep 17 00:00:00 2001 From: buddhisthead Date: Fri, 24 Oct 2025 12:01:17 -0700 Subject: [PATCH 5/8] Attempt to resolve why the issue didn't get created --- .github/workflows/weekly-update-dev.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/weekly-update-dev.yml b/.github/workflows/weekly-update-dev.yml index 81a33537..b99f3411 100644 --- a/.github/workflows/weekly-update-dev.yml +++ b/.github/workflows/weekly-update-dev.yml @@ -79,20 +79,31 @@ jobs: # Create GitHub Issue with weekly status (for testing) - name: Create Weekly Status Issue (DEV) - if: ${{ github.event_name == 'workflow_dispatch' }} + if: ${{ github.event_name != 'pull_request' }} uses: actions/github-script@v7 with: script: | + console.log('Event name:', context.eventName); + console.log('Checking if artifacts/weekly_status.md exists...'); + const fs = require('fs'); + if (!fs.existsSync('artifacts/weekly_status.md')) { + console.log('❌ artifacts/weekly_status.md not found!'); + throw new Error('Weekly status file not found'); + } + const content = fs.readFileSync('artifacts/weekly_status.md', 'utf8'); + console.log('βœ… File read successfully, length:', content.length); - await github.rest.issues.create({ + const issue = await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title: `πŸ“Š Weekly Status (DEV TEST) - ${{ steps.date.outputs.date }}`, body: content, labels: ['weekly-status', 'dev-test'] }); + + console.log('βœ… Issue created successfully:', issue.data.html_url); # Post or update a sticky PR comment with the Markdown (only on PR events) - name: Create/Update PR comment From f4da4cbb7828e76f473d89d3df7993df36165653 Mon Sep 17 00:00:00 2001 From: buddhisthead Date: Fri, 24 Oct 2025 12:04:08 -0700 Subject: [PATCH 6/8] Attempt to resolve why the issue didn't get created --- .github/workflows/weekly-update-dev.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/weekly-update-dev.yml b/.github/workflows/weekly-update-dev.yml index b99f3411..945eb349 100644 --- a/.github/workflows/weekly-update-dev.yml +++ b/.github/workflows/weekly-update-dev.yml @@ -20,7 +20,7 @@ on: permissions: contents: read - issues: write # needed to create/update issues + issues: write # needed to create/update issues pull-requests: write # needed to post/update a PR comment concurrency: @@ -79,22 +79,22 @@ jobs: # Create GitHub Issue with weekly status (for testing) - name: Create Weekly Status Issue (DEV) - if: ${{ github.event_name != 'pull_request' }} + # if: ${{ github.event_name != 'pull_request' }} uses: actions/github-script@v7 with: script: | console.log('Event name:', context.eventName); console.log('Checking if artifacts/weekly_status.md exists...'); - + const fs = require('fs'); if (!fs.existsSync('artifacts/weekly_status.md')) { console.log('❌ artifacts/weekly_status.md not found!'); throw new Error('Weekly status file not found'); } - + const content = fs.readFileSync('artifacts/weekly_status.md', 'utf8'); console.log('βœ… File read successfully, length:', content.length); - + const issue = await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, @@ -102,7 +102,7 @@ jobs: body: content, labels: ['weekly-status', 'dev-test'] }); - + console.log('βœ… Issue created successfully:', issue.data.html_url); # Post or update a sticky PR comment with the Markdown (only on PR events) From 25e8ff3646c388757f526e2e48e1dc01af25f093 Mon Sep 17 00:00:00 2001 From: buddhisthead Date: Fri, 24 Oct 2025 12:13:24 -0700 Subject: [PATCH 7/8] Apply dev version changes to cron version --- .github/workflows/weekly-update-dev.yml | 2 +- .github/workflows/weekly-update.yml | 25 ++++++++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/.github/workflows/weekly-update-dev.yml b/.github/workflows/weekly-update-dev.yml index 945eb349..9be61233 100644 --- a/.github/workflows/weekly-update-dev.yml +++ b/.github/workflows/weekly-update-dev.yml @@ -79,7 +79,7 @@ jobs: # Create GitHub Issue with weekly status (for testing) - name: Create Weekly Status Issue (DEV) - # if: ${{ github.event_name != 'pull_request' }} + if: ${{ github.event_name != 'pull_request' }} uses: actions/github-script@v7 with: script: | diff --git a/.github/workflows/weekly-update.yml b/.github/workflows/weekly-update.yml index 2e9ea198..e4471ef6 100644 --- a/.github/workflows/weekly-update.yml +++ b/.github/workflows/weekly-update.yml @@ -10,9 +10,8 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - issues: read - pull-requests: read - # if you use org/user Projects v2, you may need: project: read + issues: write # needed to create/update issues + pull-requests: write # needed to post/update a PR comment steps: - uses: actions/checkout@v4 @@ -48,9 +47,9 @@ jobs: OUTPUT_PATH: artifacts/weekly_status.md run: | mkdir -p artifacts - python scripts/weekly_status_markdown.py | tee /tmp/weekly_status.md + python scripts/weekly_status_markdown.py | tee /tmp/out.md echo "md<> $GITHUB_OUTPUT - cat /tmp/weekly_status.md >> $GITHUB_OUTPUT + cat /tmp/out.md >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Upload markdown artifact @@ -62,16 +61,28 @@ jobs: # Create GitHub Issue with weekly status - name: Create Weekly Status Issue + if: ${{ github.event_name != 'pull_request' }} uses: actions/github-script@v7 with: script: | + console.log('Event name:', context.eventName); + console.log('Checking if artifacts/weekly_status.md exists...'); + const fs = require('fs'); + if (!fs.existsSync('artifacts/weekly_status.md')) { + console.log('❌ artifacts/weekly_status.md not found!'); + throw new Error('Weekly status file not found'); + } + const content = fs.readFileSync('artifacts/weekly_status.md', 'utf8'); - - await github.rest.issues.create({ + console.log('βœ… File read successfully, length:', content.length); + + const issue = await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title: `πŸ“Š Weekly Status - Week of ${{ steps.date.outputs.date }}`, body: content, labels: ['weekly-status', 'automated'] }); + + console.log('βœ… Issue created successfully:', issue.data.html_url); From 6965eee665977a11f66a33b02e9d24557223cd4e Mon Sep 17 00:00:00 2001 From: buddhisthead Date: Fri, 24 Oct 2025 12:16:49 -0700 Subject: [PATCH 8/8] Remove dev version --- .github/workflows/weekly-update-dev.yml | 130 ------------------------ 1 file changed, 130 deletions(-) delete mode 100644 .github/workflows/weekly-update-dev.yml diff --git a/.github/workflows/weekly-update-dev.yml b/.github/workflows/weekly-update-dev.yml deleted file mode 100644 index 9be61233..00000000 --- a/.github/workflows/weekly-update-dev.yml +++ /dev/null @@ -1,130 +0,0 @@ -name: Weekly Status (dev) - -on: - # run on every push to your dev branch - push: - branches: - - weekly-status-dev - paths: - - "scripts/weekly_status_markdown.py" - - ".github/workflows/weekly-update-dev.yml" - # run on any PR that targets your default branch and originates from same repo - pull_request: - branches: - - main - paths: - - "scripts/weekly_status_markdown.py" - - ".github/workflows/weekly-update-dev.yml" - # allow manual runs - workflow_dispatch: - -permissions: - contents: read - issues: write # needed to create/update issues - pull-requests: write # needed to post/update a PR comment - -concurrency: - group: weekly-status-dev-${{ github.ref }} - cancel-in-progress: true - -jobs: - dev-run: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Install deps - run: pip install requests - - - name: Get current date - id: date - run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT - - - name: Generate weekly markdown - id: gen - env: - # Prefer the built-in token during dev - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: input-output-hk/acropolis - - # ---- Projects (v2) board integration: - PROJECT_OWNER: input-output-hk - PROJECT_NUMBER: 7 - STATUS_DONE_VALUE: Done - STATUS_INPROGRESS_VALUE: In Progress - - # ---- Or fall back to labels during dev: - # DONE_LABELS: "status: done,done" - # INPROGRESS_LABELS: "status: in progress,in progress" - - OUTPUT_PATH: artifacts/weekly_status.md - run: | - mkdir -p artifacts - python scripts/weekly_status_markdown.py | tee /tmp/out.md - echo "md<> $GITHUB_OUTPUT - cat /tmp/out.md >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - - name: Upload artifact (dev) - uses: actions/upload-artifact@v4 - with: - name: weekly-status-dev - path: artifacts/weekly_status.md - if-no-files-found: warn - - # Create GitHub Issue with weekly status (for testing) - - name: Create Weekly Status Issue (DEV) - if: ${{ github.event_name != 'pull_request' }} - uses: actions/github-script@v7 - with: - script: | - console.log('Event name:', context.eventName); - console.log('Checking if artifacts/weekly_status.md exists...'); - - const fs = require('fs'); - if (!fs.existsSync('artifacts/weekly_status.md')) { - console.log('❌ artifacts/weekly_status.md not found!'); - throw new Error('Weekly status file not found'); - } - - const content = fs.readFileSync('artifacts/weekly_status.md', 'utf8'); - console.log('βœ… File read successfully, length:', content.length); - - const issue = await github.rest.issues.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: `πŸ“Š Weekly Status (DEV TEST) - ${{ steps.date.outputs.date }}`, - body: content, - labels: ['weekly-status', 'dev-test'] - }); - - console.log('βœ… Issue created successfully:', issue.data.html_url); - - # Post or update a sticky PR comment with the Markdown (only on PR events) - - name: Create/Update PR comment - if: ${{ github.event_name == 'pull_request' }} - uses: actions/github-script@v7 - with: - script: | - const header = "### πŸ“‹ Weekly Status (auto-generated DEV)"; - const body = `${header}\n\n${process.env.MD}`; - const { owner, repo, number } = context.issue; - // Find an existing bot comment to update - const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number: number, per_page: 100 }); - const mine = comments.find(c => - c.user.type === "Bot" && - c.user.login.endsWith("[bot]") && - c.body && c.body.startsWith(header) - ); - if (mine) { - await github.rest.issues.updateComment({ owner, repo, comment_id: mine.id, body }); - } else { - await github.rest.issues.createComment({ owner, repo, issue_number: number, body }); - } - env: - MD: ${{ steps.gen.outputs.md }}