diff --git a/.github/scripts/pull-request-dashboard/dashboard.py b/.github/scripts/pull-request-dashboard/dashboard.py index 336b9e10..52f401fe 100644 --- a/.github/scripts/pull-request-dashboard/dashboard.py +++ b/.github/scripts/pull-request-dashboard/dashboard.py @@ -1073,6 +1073,8 @@ def update_dashboard(args: argparse.Namespace) -> int: md = render_pr_tables( prs, calculation.results, + max_rows_per_section=args.max_rows_per_section or None, + skip_drafts=args.skip_drafts, ) output_path = dashboard_markdown_path() output_path.parent.mkdir(parents=True, exist_ok=True) @@ -1121,9 +1123,22 @@ def main() -> int: help="minimum non-bot approvals needed before a PR can route to maintainers", ) parser.add_argument("--model", default=DEFAULT_MODEL, help=f"copilot model (default: {DEFAULT_MODEL})") + parser.add_argument( + "--max-rows-per-section", + type=int, + default=0, + help="cap rows per section in the rendered dashboard (0 = no limit)", + ) + parser.add_argument( + "--skip-drafts", + action="store_true", + help="omit the Draft pull requests section from the rendered dashboard", + ) args = parser.parse_args() if args.required_approvals < 1: parser.error("--required-approvals must be at least 1") + if args.max_rows_per_section < 0: + parser.error("--max-rows-per-section must be zero or positive") with state_branch.temporary_state_dir() as state_dir: repo_key = repo_state_key(args.repo) if args.repo else repo_state_key(detect_repo()) set_state_dir(state_dir / repo_key) diff --git a/.github/scripts/pull-request-dashboard/render.py b/.github/scripts/pull-request-dashboard/render.py index 12466576..19bd216e 100644 --- a/.github/scripts/pull-request-dashboard/render.py +++ b/.github/scripts/pull-request-dashboard/render.py @@ -33,11 +33,26 @@ def _md_escape(s: str) -> str: ) -def render_draft_pr_section(prs: list[dict[str, Any]]) -> list[str]: +def _limit_rows(rows: list[Any], max_rows: int | None) -> tuple[list[Any], int]: + if max_rows is None or max_rows <= 0 or len(rows) <= max_rows: + return rows, 0 + return rows[:max_rows], len(rows) - max_rows + + +def _truncation_note(count: int) -> str: + plural = "PR" if count == 1 else "PRs" + return f"_More {count} {plural} not shown_" + + +def render_draft_pr_section( + prs: list[dict[str, Any]], + max_rows_per_section: int | None = None, +) -> list[str]: drafts = [p for p in prs if p.get("isDraft")] if not drafts: return [] drafts.sort(key=lambda p: p.get("updatedAt") or "") + drafts, truncated = _limit_rows(drafts, max_rows_per_section) lines = ["## Draft pull requests", ""] lines.append("| PR | Author | Updated |") lines.append("|---|---|:---:|") @@ -49,6 +64,9 @@ def render_draft_pr_section(prs: list[dict[str, Any]]) -> list[str]: updated = activity_age(parse_ts(pr.get("updatedAt") or "")) lines.append(f"| [{title} (#{number})]({url}) | {author} | {updated} |") lines.append("") + if truncated: + lines.append(_truncation_note(truncated)) + lines.append("") return lines @@ -159,7 +177,12 @@ def render_diagnostics_section(results: dict[int, dict[str, Any]]) -> list[str]: ] -def render_pr_tables(prs: list[dict[str, Any]], results: dict[int, dict[str, Any]]) -> str: +def render_pr_tables( + prs: list[dict[str, Any]], + results: dict[int, dict[str, Any]], + max_rows_per_section: int | None = None, + skip_drafts: bool = False, +) -> str: source_url = "https://github.com/open-telemetry/shared-workflows/blob/main/.github/scripts/pull-request-dashboard/dashboard.py" refresh_url = "https://github.com/open-telemetry/shared-workflows/actions/workflows/pull-request-dashboard.yml" grouping_note = ( @@ -200,6 +223,7 @@ def row_sort_key(pr: dict[str, Any]) -> tuple[int, int]: if not rows: continue rows.sort(key=row_sort_key, reverse=True) + rows, truncated = _limit_rows(rows, max_rows_per_section) out.append(f"## {ROUTE_LABELS.get(route, route)}") out.append("") out.append("| PR | Author | Reviewers | CI | Conflicts | Age |") @@ -219,8 +243,12 @@ def row_sort_key(pr: dict[str, Any]) -> tuple[int, int]: f"{conflicts_cell(facts)} | {activity_cell} |" ) out.append("") + if truncated: + out.append(_truncation_note(truncated)) + out.append("") - out.extend(render_draft_pr_section(prs)) + if not skip_drafts: + out.extend(render_draft_pr_section(prs, max_rows_per_section)) out.extend(render_diagnostics_section(results)) out.append(f"_Approvers may [force a refresh]({refresh_url})._") out.append("") diff --git a/.github/scripts/pull-request-dashboard/repositories.json b/.github/scripts/pull-request-dashboard/repositories.json index b03bd15f..02aad753 100644 --- a/.github/scripts/pull-request-dashboard/repositories.json +++ b/.github/scripts/pull-request-dashboard/repositories.json @@ -3,7 +3,9 @@ "name": "opentelemetry-collector-contrib", "approver_teams": ["collector-contrib-approvers"], "slack_channel": "", - "slack_user_mapping": {} + "slack_user_mapping": {}, + "max_rows_per_section": 50, + "skip_drafts": true }, { "name": "opentelemetry-java", diff --git a/.github/workflows/pull-request-dashboard-repo.yml b/.github/workflows/pull-request-dashboard-repo.yml index 2284f43f..4e489508 100644 --- a/.github/workflows/pull-request-dashboard-repo.yml +++ b/.github/workflows/pull-request-dashboard-repo.yml @@ -38,6 +38,14 @@ on: required: false default: "{}" type: string + max_rows_per_section: + required: false + default: 0 + type: number + skip_drafts: + required: false + default: false + type: boolean secrets: PR_DASHBOARD_PRIVATE_KEY: required: true @@ -189,6 +197,8 @@ jobs: REPO_NAME: ${{ inputs.repository }} REQUIRED_APPROVALS: ${{ inputs.required_approvals }} APPROVER_TEAMS_JSON: ${{ inputs.approver_teams_json }} + MAX_ROWS_PER_SECTION: ${{ inputs.max_rows_per_section }} + SKIP_DRAFTS: ${{ inputs.skip_drafts }} run: | set -euo pipefail dashboard_args=(--state-branch "$DASHBOARD_STATE_BRANCH" --repo "$REPO_NAME") @@ -199,6 +209,12 @@ jobs: if [[ -n "${TRIGGER_PR_NUMBER:-}" ]]; then dashboard_args+=(--pr-number "$TRIGGER_PR_NUMBER") fi + if [[ "${MAX_ROWS_PER_SECTION:-0}" -gt 0 ]]; then + dashboard_args+=(--max-rows-per-section "$MAX_ROWS_PER_SECTION") + fi + if [[ "${SKIP_DRAFTS:-false}" == "true" ]]; then + dashboard_args+=(--skip-drafts) + fi python3 .github/scripts/pull-request-dashboard/dashboard.py "${dashboard_args[@]}" notify-slack: diff --git a/.github/workflows/pull-request-dashboard.yml b/.github/workflows/pull-request-dashboard.yml index 49163ebf..6f87c4bf 100644 --- a/.github/workflows/pull-request-dashboard.yml +++ b/.github/workflows/pull-request-dashboard.yml @@ -177,6 +177,8 @@ jobs: approver_teams_json: ${{ toJSON(matrix.approver_teams || fromJSON('[]')) }} slack_channel: ${{ matrix.slack_channel }} slack_user_mapping_json: ${{ toJSON(matrix.slack_user_mapping || fromJSON('{}')) }} + max_rows_per_section: ${{ matrix.max_rows_per_section || 0 }} + skip_drafts: ${{ matrix.skip_drafts || false }} secrets: PR_DASHBOARD_PRIVATE_KEY: ${{ secrets.PR_DASHBOARD_PRIVATE_KEY }} COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} diff --git a/pull-request-dashboard/README.md b/pull-request-dashboard/README.md index b67706fb..e31e8cfd 100644 --- a/pull-request-dashboard/README.md +++ b/pull-request-dashboard/README.md @@ -64,6 +64,8 @@ Fields: | `required_approvals` | yes | Number of approvals required for an open PR to be marked ready to merge. | | `slack_channel` | no | Slack channel for notifications. Omit to skip Slack processing for this repository. | | `slack_user_mapping` | no | Map of GitHub login to Slack user ID for at-mentions. | +| `max_rows_per_section` | no | Cap on rows rendered per group section (and for drafts) in the dashboard issue body. Rows past the cap are dropped and replaced with a `_More X PRs not shown_` note. Omit for no cap. Needed for very large repos where the full dashboard exceeds GitHub's 65,536-character issue-body limit. | +| `skip_drafts` | no | If `true`, omit the *Draft pull requests* section from the dashboard body entirely. Defaults to `false` (drafts are shown). Useful for very large repos where drafts add significant size without triage value. | Ask a maintainer or admin to add the repository under [Repository access](https://github.com/organizations/open-telemetry/settings/installations/133550497).