diff --git a/src/generate_repo_overview/_policy_sync_html.py b/src/generate_repo_overview/_policy_sync_html.py index d08f110..054343a 100644 --- a/src/generate_repo_overview/_policy_sync_html.py +++ b/src/generate_repo_overview/_policy_sync_html.py @@ -61,14 +61,14 @@ def render_policy_sync_section( '
Evaluation status
\n' '
\n' + _policy_stat(summary.compliant, "Compliant", "compliant", "✓") - + _policy_stat(summary.drifted, "Changes Needed", "changes-required", "!") + + _policy_stat(summary.drifted, "Changes Needed", "changes-required", "X") + _policy_stat( summary.not_applicable, "Not Applicable", "not-applicable", "N/A" ) + _optional_policy_stat( - summary.evaluation_failures, "Evaluation Errors", "error", "!" + summary.evaluation_failures, "Evaluation Errors", "error", "X" ) - + _optional_policy_stat(summary.sync_failures, "Sync Failures", "error", "!") + + _optional_policy_stat(summary.sync_failures, "Sync Failures", "error", "X") + _optional_policy_stat(summary.skipped, "Skipped", "not-evaluated", "—") + "
\n" " \n" @@ -254,6 +254,10 @@ def _matrix_cell(outcome: PolicySyncOutcome | None) -> str: if automated: label = "Compliant (automated PR)" marker = "✓✓" + if status_class == "changes-required" and outcome.policy_pr_status == "open": + link = _safe_external_url(outcome.pull_request_url or "") + if link: + return _policy_pr_badge(outcome.policy_pr_status, href=link) content = ( f'{marker}' @@ -306,13 +310,13 @@ def _status_display(outcome: PolicySyncOutcome) -> tuple[str, str, str]: "pull-request-recreated", "pull-request-recreated-no-changes", }: - return "changes-required", "Changes required", "!" + return "changes-required", "Changes required", "X" if outcome.status == "not-applicable": return "not-applicable", "Not applicable", "N/A" if outcome.status in {"skipped", "sync-error"}: return "not-evaluated", "Not evaluated", "—" if outcome.status == "error": - return "error", "Error", "!" + return "error", "Error", "X" return "unknown", outcome.status or "Unknown", "?" diff --git a/src/generate_repo_overview/templates/styles.css b/src/generate_repo_overview/templates/styles.css index 12f0b41..7371ba2 100644 --- a/src/generate_repo_overview/templates/styles.css +++ b/src/generate_repo_overview/templates/styles.css @@ -504,7 +504,8 @@ th[data-tooltip]:focus::after, td[data-tooltip]:focus::after { opacity: 1; } .policy-status.compliant { color: var(--green); background: #23863633; } .policy-status.changes-required { color: var(--red); background: #da363333; } .policy-status.not-applicable, .policy-status.not-evaluated { color: var(--muted); background: #8b949e22; } -.policy-status.error, .policy-status.unknown { color: var(--orange); background: #d2992233; } +.policy-status.error { color: var(--red); background: #da363333; } +.policy-status.unknown { color: var(--orange); background: #d2992233; } .policy-pr-badge { display: inline-flex; align-items: center; @@ -518,7 +519,7 @@ th[data-tooltip]:focus::after, td[data-tooltip]:focus::after { opacity: 1; } } .policy-pr-badge:hover { text-decoration: none; filter: brightness(1.15); } .policy-pr-badge svg { width: 12px; height: 12px; } -.policy-pr-open { color: #3fb950; background: #2ea04333; border-color: #2ea04399; } +.policy-pr-open { color: var(--orange); background: #e3702d33; border-color: #e3702d99; } .policy-pr-merged { color: #a371f7; background: #8250df33; border-color: #8250df99; } .policy-pr-closed { color: #8b949e; background: #6e778133; border-color: #6e778199; } .policy-pr-none, .policy-pr-unknown { color: var(--muted); background: #8b949e22; border-color: #8b949e66; } diff --git a/tests/test_policy_sync.py b/tests/test_policy_sync.py index 4569aa8..64037e9 100644 --- a/tests/test_policy_sync.py +++ b/tests/test_policy_sync.py @@ -350,7 +350,7 @@ def test_policy_sync_tab_uses_repository_groups_and_pr_states() -> None: '' in page ) assert ( - '!' + 'X' in page ) assert ( @@ -371,6 +371,11 @@ def test_policy_sync_tab_uses_repository_groups_and_pr_states() -> None: assert "Actions in this run" not in page assert 'class="policy-pr-badge policy-pr-open"' in page assert ">Open" in page + assert ( + 'X ✓✓' in page @@ -382,6 +387,35 @@ def test_policy_sync_tab_uses_repository_groups_and_pr_states() -> None: assert 'href="https://github.com/org/score/pull/3"' not in page +def test_policy_sync_error_outcomes_render_red_x() -> None: + """Error outcomes use a red X in both the summary and matrix.""" + report = PolicySyncReport( + schema_version=2, + summary=PolicySyncSummary(evaluation_failures=1), + outcomes=( + PolicySyncOutcome( + policy_id="policy-error", + repository="repo-error", + applicable="yes", + status="error", + error="evaluation failed", + ), + ), + ) + + page = render_index_page(_minimal_snapshot(), report) + + assert ( + 'X' + in page + ) + assert ( + 'X' + in page + ) + assert ".policy-status.error { color: var(--red);" in page + + def test_render_details_discovers_configured_report_and_publishes_raw_json( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: