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.

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