Skip to content

fix(dashboard): defensive guards prevent 'is not iterable' on partial backend responses (closes #304)#311

Merged
cristim merged 1 commit into
feat/multicloud-web-frontendfrom
fix/issue-304-dashboard-iterable
May 5, 2026
Merged

fix(dashboard): defensive guards prevent 'is not iterable' on partial backend responses (closes #304)#311
cristim merged 1 commit into
feat/multicloud-web-frontendfrom
fix/issue-304-dashboard-iterable

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented May 5, 2026

Summary

  • getRecommendations() can resolve to null (when apiRequest's JSON-parse catch fires on a 2xx with empty/non-JSON body) or an unexpected object shape — both reach groupRecsByCell() which uses for...of, throwing "X is not iterable" and replacing the entire dashboard with an error banner.
  • Added Array.isArray() guard in loadDashboard() to coerce any non-array fulfilled value to [] before it reaches groupRecsByCell().
  • Added try/catch soft-fail in renderDashboardSummary() around the savings-range computation so the Potential Monthly Savings card degrades to $0 rather than blanking the dashboard if the guard is ever bypassed.

Fault site (PR #295 — commit e4a52f9)

// Before: no guard — null/object from apiRequest lands directly in groupRecsByCell
const recs = recsResult.status === 'fulfilled'
  ? (recsResult.value as unknown as LocalRecommendation[])
  : [];

Regression tests added

  • #304: getRecommendations returning null does not throw "is not iterable"
  • #304: getRecommendations returning a non-array object does not throw "is not iterable"
  • #304: summaryData missing by_service field does not throw

All 24 dashboard tests pass.

Test plan

  • Run cd frontend && npm test -- --testPathPattern=dashboard — all 24 tests pass
  • Verify the three new #304 tests are green
  • Manually test with a backend returning 5xx for /recommendations — other cards still render, savings card shows $0

Closes #304

Summary by CodeRabbit

Bug Fixes

  • Dashboard now gracefully handles unexpected recommendation data formats, preventing application crashes when the API returns null or other malformed response data
  • The savings calculation automatically falls back to displaying $0 when encountering unexpected data structures, instead of causing the interface to crash
  • Dashboard rendering continues to work correctly even when summary data is missing optional fields

… backend responses (closes #304)

The 'Failed to load dashboard: e is not iterable' error was triggered when
api.getRecommendations() resolved to a non-array value. The apiRequest()
catch block returns `null as T` when response.json() fails on a 2xx response
with an empty or non-JSON body; a non-array envelope shape from the backend
produces the same symptom. Both land in groupRecsByCell() which iterates via
`for...of`, throwing "null/X is not iterable" and replacing the dashboard
with an error banner.

Two layered guards added to dashboard.ts:
- Array.isArray() check in loadDashboard() coerces any non-array recsResult
  value to [] before it reaches groupRecsByCell().
- try/catch soft-fail in renderDashboardSummary() around the
  groupRecsByCell/pageLevelRange computation so the savings card degrades
  to $0 rather than blanking the entire dashboard if the guard above is
  ever bypassed.

Regression tests added for:
  (a) getRecommendations() returning null
  (b) getRecommendations() returning a non-array object ({recommendations:[]})
  (c) summaryData missing by_service field entirely
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c26afe6-759e-47c3-8304-86443550487f

📥 Commits

Reviewing files that changed from the base of the PR and between f83c914 and 46bf10e.

📒 Files selected for processing (2)
  • frontend/src/__tests__/dashboard.test.ts
  • frontend/src/dashboard.ts

📝 Walkthrough

Walkthrough

This PR adds defensive error handling to dashboard data loading to prevent crashes when API endpoints return unexpected data shapes (null, non-array objects, missing fields). It guards against iteration errors in recommendations processing and wraps savings calculations in a try/catch fallback to zero.

Changes

Dashboard Defensive Guards

Layer / File(s) Summary
Core Implementation
frontend/src/dashboard.ts
Added Array.isArray guard in loadDashboard (lines 110–121) to handle null or non-array recommendations. Wrapped savings-range computation in renderDashboardSummary (lines 156–170) with try/catch to degrade "Potential Monthly Savings" to $0 on error instead of crashing the entire dashboard.
Regression Tests
frontend/src/__tests__/dashboard.test.ts
Added three tests (lines 554–637) covering: getRecommendations returning null, getRecommendations returning a non-array object, and getDashboardSummary missing the by_service field—all verifying safe fallback rendering without error banners.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related issues

Possibly related PRs

  • LeanerCloud/CUDly#295: Adds recommendations fetch to dashboard; this PR adds defensive guards around that new recommendations handling.
  • LeanerCloud/CUDly#276: Introduces page-level savings range computation; this PR wraps that computation in defensive try/catch.
  • LeanerCloud/CUDly#283: Exports groupRecsByCell and pageLevelRange helpers; this PR defensively calls those helpers with error boundaries.

Suggested labels

impact/many

Poem

🐰 When data shapes are wild and strange,
And nulls and objects rearrange,
We guard with care, we catch with grace,
And fallback $0 takes its place!
No more crashes from the sky—
Just safe dashboards, you and I! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: defensive guards to prevent 'is not iterable' errors on partial backend responses, fixing issue #304.
Linked Issues check ✅ Passed The PR addresses all coding requirements from issue #304: adds Array.isArray() guards in loadDashboard() and try/catch in renderDashboardSummary(), includes regression tests covering null/non-array recommendations and missing by_service field.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the dashboard iterable error as specified in issue #304; no unrelated modifications detected in test or implementation files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-304-dashboard-iterable

Comment @coderabbitai help to get the list of available commands and usage tips.

@cristim cristim added priority/p1 Next up; this sprint severity/high Significant harm urgency/this-sprint Within the current sprint impact/all-users Affects every user effort/s Hours type/bug Defect triaged Item has been triaged labels May 5, 2026
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 5, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim cristim merged commit 0b61fcb into feat/multicloud-web-frontend May 5, 2026
5 checks passed
@cristim cristim deleted the fix/issue-304-dashboard-iterable branch May 5, 2026 23:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/all-users Affects every user priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant