Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ This project follows the structure from Keep a Changelog and intends to use Sema

### Fixed

- **SonarCloud reliability (S6466):** `get_key_insights` in `report_data.py` adds an explicit `None`-guard (`or []`) on `by_minutes` before indexing, satisfying SonarCloud's S6466 check; new test covers `by_minutes: None`.
- **CodeRabbit follow-ups (PR #10):** Soft-fail `workflow_breakdown` fetch on `RuntimeError` (email + legacy paths) so partial reports still render; skip redundant private concentration recommendations when private top-2 matches overall top-2; share `repo_label` / `WORKFLOW_MINUTES_REQUEST_HEADROOM` / public `parse_iso_datetime`; narrow workflow-name-map soft-fail to `RuntimeError`; normalize non-UTC ISO offsets to UTC in `parse_iso_datetime` so expiry/retention day math stays calendar-stable.
- **SonarCloud quality gate (PR #10):** Safer list indexing for private consumer findings and workflow breakdown (`S6466`); reduced cognitive complexity in `_format_consumers_section` and `_repo_rows`; deduplicated HTML `<table>` literals; consolidated repeated consumer test fixtures into `tests/_consumer_fixtures.py`.
- **Calendar-dependent forecast export/email tests:** Pin `report_forecast_data.date.today` to a mid-month date in CSV/PDF/XLSX section-presence tests and the cached email-report CLI forecast assertion. Forecast is intentionally omitted when `day_of_month < 3`, so those tests failed on the 1st–2nd of each month without a date pin.
Expand Down
18 changes: 10 additions & 8 deletions src/github_usage/report_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,14 +211,16 @@ def get_key_insights(report_data: dict) -> list[str]:
f"Private repos used {pct:.0f}% of the 2,000 free Actions minutes this month."
)
consumers = report_data.get("repo_consumers")
if actions and consumers and consumers.get("by_minutes"):
top = consumers["by_minutes"][0]
minutes = float(actions.get("minutes", 0.0))
if minutes:
vis = visibility_label(repo_visibility(top))
insights.append(
f"{top['repo']}{vis} accounts for {top['minutes'] / minutes * 100:.0f}% of Actions minutes."
)
if actions and consumers:
by_minutes = consumers.get("by_minutes") or []
if by_minutes:
top = by_minutes[0]
minutes = float(actions.get("minutes", 0.0))
if minutes:
vis = visibility_label(repo_visibility(top))
insights.append(
f"{top['repo']}{vis} accounts for {top['minutes'] / minutes * 100:.0f}% of Actions minutes."
)
if actions and float(actions.get("storage_percent", 0.0)) < 100:
insights.append("Actions storage is below the free-tier limit.")
return insights[:3]
Expand Down
12 changes: 12 additions & 0 deletions tests/test_report_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,18 @@ def test_get_key_insights_reports_storage_below_free_tier(self):

self.assertEqual(insights, ["Actions storage is below the free-tier limit."])

def test_get_key_insights_handles_none_by_minutes_without_index_error(self):
from github_usage.report_data import get_key_insights

report = {
"actions": {"minutes": 100.0, "storage_percent": 50.0},
"repo_consumers": {"by_minutes": None},
}

insights = get_key_insights(report)

self.assertEqual(insights, ["Actions storage is below the free-tier limit."])

def test_get_key_insights_caps_at_three(self):
from github_usage.report_data import get_key_insights

Expand Down