Skip to content

fix: chart review HIGH + MEDIUM bugs (bar, line, table) - #870

Merged
alfredo1996 merged 6 commits into
release/1.0from
fix/v1.0-chart-review-high-severity
May 20, 2026
Merged

fix: chart review HIGH + MEDIUM bugs (bar, line, table)#870
alfredo1996 merged 6 commits into
release/1.0from
fix/v1.0-chart-review-high-severity

Conversation

@alfredo1996

@alfredo1996 alfredo1996 commented May 19, 2026

Copy link
Copy Markdown
Owner

Summary

Five HIGH-severity and two MEDIUM-severity findings from the post-merge
chart code review (line / bar / table), all fixed in this PR.

HIGH

# Area Bug Fix
1 bar/line transform `Number(r[k])
2 bar/line transform Object.keys(records[0]) drops series absent from the first row (sparse data) collectAllKeys unions keys across every row; mirrored in bar-chart.tsx / line-chart.tsx
3 table + single-value duplicated contrastTextColor crashed / produced invisible text on rgb() / named colors Single shared util in chart-utils with defensive hex/rgb parser + safe fallback to #000000
4 table widget pagination "flash" on first render — table mounts with default pageSize=10 then snaps to dynamic size when ResizeObserver fires Gate DataGrid mount until containerHeight is measured (only when pagination is enabled)
5 table widget dead enableGlobalFilter UI — schema advertised "Global Search" but no input was ever rendered Removed the dead toggle from the table chart-options schema

MEDIUM

# Area Bug Fix
6 bar/line chart a11y Default ARIA label was the unhelpful generic "Chart visualization" Auto-derive a description from data shape ("Bar chart with N categories and M series: …"); explicit ariaDescription still wins
7 table widget a11y Scroll wrapper had no role/label; underlying <table> had no top-level aria-label, leaving AT users with no context Added role="region" + auto-derived aria-label ("Table with N rows and M columns"); overridable via settings.ariaLabel

False positives (verified, no change needed)

  • Bar chart "empty data placeholder" — already exists via buildEmptyDataOption() (theme-aware centered "No data" title) used at bar-chart.tsx:92.
  • Line chart "escape HTML in tooltip series names"chart-utils.ts:191 already calls escapeHtml(p.seriesName); numeric values + header + abs values in the percent formatter are all escaped too. The endLabel: { formatter: "{a}" } token renders to canvas, not HTML.

Test plan

  • Unit: component/ — 476 chart tests pass (added 8 ARIA-description cases + 9 contrast-text-color cases)
  • Unit: app/ — passes (added 8 new shared-utils cases, updated 2 transform tests)
  • Type check: app/ + component/ clean
  • E2E focused: charts.spec.ts + styling-rules.spec.ts + widget-states.spec.ts — 43 passed, 4 flaky retry-pass (pre-existing graph-login flake, unrelated), 3 skipped, 0 failures
  • CI: full Playwright sharded suite (will run on push)
  • CodeRabbit + SonarCloud comments reviewed before merge

Notes

  • Bar/line transform tests previously asserted "not-a-number"0. Updated to assert null (with comments explaining why) — this is the intentional behavior change.
  • enableGlobalFilter is removed from the schema only; the DataGrid prop stays in case downstream callers wire it up via a custom toolbar.
  • The auto-derived ARIA labels list series/column counts and names — for charts with many series this can produce a long label. That's intentional; screen readers benefit from explicit enumeration over generic phrases.

Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Bar and line charts now auto-generate accessibility labels based on data shape.
  • Bug Fixes

    • Improved handling of incomplete/sparse data rows—series that appear later are preserved and gaps are retained (missing vs zero).
    • Fixed initial table pagination flash by deferring grid render until container height is known.
  • Improvements

    • Added a color-contrast helper for reliable text color selection.
    • Removed the non-functional global search option for tables and enhanced table ARIA labeling.
  • Tests

    • Expanded unit and e2e tests covering transforms, charts, and contrast behavior.

Review Change Stack

Fixes five high-severity findings from the post-merge chart code review:

1. Bar/line transforms: silent Number() coercion. Replaced
   `Number(r[k]) || 0` with a `toSeriesNumber` helper that returns null
   for null/undefined/non-numeric values and preserves numeric zero.
   ECharts renders nulls as gaps, restoring the missing-vs-zero
   distinction so bad data isn't silently masquerading as 0.

2. Bar/line transforms: `Object.keys(records[0])` dropped sparse series.
   Added `collectAllKeys` that unions keys across every row, and mirrored
   the fix in the bar/line chart components for direct callers.

3. `contrastTextColor` crashed / produced invisible text on non-hex
   colors. Extracted to a single shared util in chart-utils with a
   defensive parser that accepts `#rgb`, `#rrggbb`, `rgb()`, `rgba()`
   (including percentage components), and falls back to `#000000` for
   anything unparseable. Removed the duplicate copies in
   `table-renderer.tsx` and `single-value-chart.tsx`.

4. Table widget: pagination flash on first render. When pagination is
   enabled, gate the DataGrid render until the container ResizeObserver
   has measured a height. Previously the table briefly mounted with the
   default `pageSize=10`, then immediately re-rendered with the dynamic
   page size, visibly snapping the row count.

5. Table widget: dead `enableGlobalFilter` UI. Removed the "Global
   Search" toggle from the table chart-options schema; the toggle wired
   `enableGlobalFilter` into TanStack Table's filter row model but no
   search input was ever rendered, so toggling it had no observable
   effect. Per-column filters cover the filtering need.

Tests: 9 new contrast-text-color cases, 8 new shared-utils helper cases
(collectAllKeys, toSeriesNumber), updated bar/line transform tests
(now assert null for non-numeric input, added zero-preservation and
sparse-series regression cases), updated chart-options-schema tests
to assert the dead toggle is gone.

All 1628 component tests + 2756 app tests pass; type checks clean;
focused E2E (charts, styling-rules, widget-states) green.
@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@alfredo1996 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 8 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 306dccea-10e4-4670-9eec-c61a168bce90

📥 Commits

Reviewing files that changed from the base of the PR and between 09319f5 and e4e826f.

📒 Files selected for processing (6)
  • app/src/components/table-renderer.tsx
  • app/src/plugins/transforms/__tests__/shared.test.ts
  • app/src/plugins/transforms/shared-utils.ts
  • component/src/charts/__tests__/contrast-text-color.test.ts
  • component/src/charts/chart-utils.ts
  • component/src/charts/line-chart.tsx

Walkthrough

This PR refactors chart data handling to support sparse rows, adds accessibility labels to bar and line charts, consolidates the color-contrast utility across components, and improves table rendering behavior. Key changes include shared transform utilities (collectAllKeys, toSeriesNumber), auto-derived ARIA descriptions in BarChart and LineChart, a new centralized contrastTextColor function, and ResizeObserver-gated DataGrid mounting in TableRenderer.

Changes

Data transforms, chart accessibility, and table rendering refinements

Layer / File(s) Summary
Shared transform utilities for sparse data handling
app/src/plugins/transforms/shared-utils.ts, app/src/plugins/transforms/__tests__/shared.test.ts
New collectAllKeys() and toSeriesNumber() utilities enable transforms to union keys across all rows (not just the first) and preserve missing vs. zero values by returning null for non-finite/unparseable inputs.
Bar chart transform and sparse-data tests
app/src/plugins/bar/transform.ts, app/src/plugins/transforms/__tests__/bar.test.ts
transformToBarData and validateBarData use union-based series key collection and toSeriesNumber() conversion. Tests verify non-numeric-to-null mapping, zero preservation, and sparse series (missing values become null).
Line chart transform and sparse-data tests
app/src/plugins/line/transform.ts, app/src/plugins/transforms/__tests__/line.test.ts
transformToLineData and validateLineData adopt the same union key and missing-vs-zero semantics. Tests confirm null handling and sparse row inclusion.
Auto-derived ARIA descriptions for BarChart and LineChart
component/src/charts/bar-chart.tsx, component/src/charts/line-chart.tsx, component/src/charts/__tests__/bar-chart.test.tsx, component/src/charts/__tests__/line-chart.test.tsx
BarChart and LineChart auto-generate ariaDescription from data shape (category/point count + series names); explicit ariaDescription prop overrides. Tests validate single-series, multi-series, and empty-data labeling via RTL accessibility queries.
Shared color-contrast utility and consolidation
component/src/charts/chart-utils.ts, component/src/charts/index.ts, component/src/charts/__tests__/contrast-text-color.test.ts, component/src/charts/single-value-chart.tsx, app/src/components/table-renderer.tsx
New contrastTextColor() and parseColorToRgb() in chart-utils support hex (#rgb, #rrggbb) and rgb()/rgba() parsing; returns black or white based on WCAG luminance. SingleValueChart and TableRenderer now import instead of duplicating. Tests verify correct color selection and robustness to malformed inputs.
Table DataGrid rendering and option cleanup
app/src/components/table-renderer.tsx, component/src/components/composed/chart-options/table.ts, component/src/components/composed/__tests__/chart-options-schema.test.ts
TableRenderer gates DataGrid mounting on measured containerHeight (via ResizeObserver) to avoid pagination-flash. Wrapper adds ARIA role="region" and computed aria-label. Unused enableGlobalFilter prop removed from DataGrid and option definitions; per-column filters remain.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

bug, pkg:app, pkg:component, area:charts, area:table

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary changes: fixing HIGH and MEDIUM severity bugs across bar, line, and table chart components.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/v1.0-chart-review-high-severity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Two MEDIUM findings from the chart code review (the third — line tooltip
HTML escaping — was a false positive; chart-utils.ts:191 already calls
escapeHtml on series names, and the bar empty-data placeholder also
already exists via buildEmptyDataOption()).

- BarChart / LineChart: when no explicit ariaDescription is provided,
  derive one from the data shape ("Bar chart with N categories and M
  series: ..."). The previous default of "Chart visualization" was
  uninformative for screen-reader users.
- TableRenderer: add role="region" + aria-label to the scroll wrapper
  ("Table with N rows and M columns") — the underlying <table> had no
  top-level label, so AT users hit the widget with no context. Callers
  can override via settings.ariaLabel.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
@alfredo1996 alfredo1996 changed the title fix: chart review high-severity bugs (bar, line, table) fix: chart review HIGH + MEDIUM bugs (bar, line, table) May 19, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/components/table-renderer.tsx`:
- Around line 295-296: The gate that prevents early DataGrid mounting should
treat non-positive heights as "not ready": update the awaitingHeight check (the
const awaitingHeight that currently tests enablePagination && containerHeight
=== undefined) to consider null/undefined and <= 0 (e.g., enablePagination &&
(containerHeight == null || containerHeight <= 0)); apply the same change to the
other similar check around the DataGrid mount (the second occurrence mentioned
at the other check) so DataGrid won't mount when the initial measured height is
0 or negative.

In `@app/src/plugins/transforms/shared-utils.ts`:
- Around line 76-79: toSeriesNumber currently treats whitespace-only strings as
0 because Number("   ") === 0; update the toSeriesNumber function to treat
blank-ish strings as missing by checking for string inputs and returning null
when raw is a string and raw.trim() === "". Keep existing checks
(null/undefined/""), then coerce non-blank strings/numbers as before (use
Number(raw) and Number.isFinite(n)) so behavior for valid numbers is unchanged.

In `@component/src/charts/chart-utils.ts`:
- Around line 120-136: The rgb/rgba parser currently only validates the first
three components (variables: rgb regex match, parts array) and thus accepts
malformed inputs; update the logic in chart-utils.ts to enforce exact arity and
valid alpha: when the input is an rgb(...) string require parts.length === 3,
and when it's rgba(...) require parts.length === 4, parse and clamp the first
three channels as before, then parse the fourth component as alpha (numeric,
allow percentage or 0–1 decimal) and reject (return null) if alpha is not a
finite number or out of range; ensure any extra channels cause rejection so only
properly formed rgb() or rgba() values are accepted.

In `@component/src/charts/line-chart.tsx`:
- Around line 98-109: The union-of-series-key logic (iterating data, skipping
"x", building seenKeys/seriesKeys) is duplicated in the options useMemo and the
autoAria useMemo; extract it into a single shared helper or a useMemo (e.g.,
create getSeriesKeys(data) or const seriesKeysMemo = useMemo(() => { ... },
[data])), return the array of series keys and reuse that symbol in both the
options and autoAria useMemos (replace the local seenKeys/seriesKeys blocks with
a reference to the shared seriesKeys), ensuring the dependency arrays reference
the shared memo if you use useMemo so behavior remains stable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8674c617-0d37-4a03-b6fe-fcb220a3baa0

📥 Commits

Reviewing files that changed from the base of the PR and between 95f2474 and 93661e7.

📒 Files selected for processing (17)
  • app/src/components/table-renderer.tsx
  • app/src/plugins/bar/transform.ts
  • app/src/plugins/line/transform.ts
  • app/src/plugins/transforms/__tests__/bar.test.ts
  • app/src/plugins/transforms/__tests__/line.test.ts
  • app/src/plugins/transforms/__tests__/shared.test.ts
  • app/src/plugins/transforms/shared-utils.ts
  • component/src/charts/__tests__/bar-chart.test.tsx
  • component/src/charts/__tests__/contrast-text-color.test.ts
  • component/src/charts/__tests__/line-chart.test.tsx
  • component/src/charts/bar-chart.tsx
  • component/src/charts/chart-utils.ts
  • component/src/charts/index.ts
  • component/src/charts/line-chart.tsx
  • component/src/charts/single-value-chart.tsx
  • component/src/components/composed/__tests__/chart-options-schema.test.ts
  • component/src/components/composed/chart-options/table.ts

Comment thread app/src/components/table-renderer.tsx Outdated
Comment thread app/src/plugins/transforms/shared-utils.ts
Comment thread component/src/charts/chart-utils.ts Outdated
Comment thread component/src/charts/line-chart.tsx Outdated
CI surfaced two issues after the MEDIUM push:

1. SonarCloud — 8% duplication on new code (>3% gate). The autoAria
   useMemo block was duplicated 21 lines × 2 between bar-chart.tsx and
   line-chart.tsx. Extracted to `buildAutoAriaDescription(chartType,
   data, labelKey, rowNoun)` in chart-utils.ts.

2. E2E — design-system.spec.ts "chart container has role='img' and
   auto-generated aria-label" asserted the old ECharts AriaComponent
   template (/This is a chart/). Our new BarChart now passes its own
   data-shape description through, so the aria-label is "Bar chart with
   5 categories and 1 series: value" — strictly better for AT users.
   Updated the assertion to match the new format.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
component/src/charts/chart-utils.ts (1)

120-136: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate rgb()/rgba() arity and alpha before accepting the color.

The parser accepts malformed values (e.g., extra channels or invalid alpha in rgba()) because it only validates parts.length < 3. This undermines the "unparseable input falls back safely" contract.

🔧 Proposed fix (enforces exact arity and validates alpha)
-  const rgb = /^rgba?\(([^)]+)\)$/i.exec(s);
+  const rgb = /^(rgb|rgba)\(([^)]+)\)$/i.exec(s);
   if (rgb) {
-    const parts = rgb[1].split(",").map((p) => p.trim());
-    if (parts.length < 3) return null;
+    const fn = rgb[1].toLowerCase();
+    const parts = rgb[2].split(",").map((p) => p.trim());
+    if (
+      (fn === "rgb" && parts.length !== 3) ||
+      (fn === "rgba" && parts.length !== 4)
+    ) {
+      return null;
+    }
     const out: number[] = [];
     for (let i = 0; i < 3; i++) {
       const p = parts[i];
       let n: number;
       if (p.endsWith("%")) {
         n = (Number(p.slice(0, -1)) / 100) * 255;
       } else {
         n = Number(p);
       }
       if (!Number.isFinite(n)) return null;
       out.push(Math.max(0, Math.min(255, n)));
     }
+    if (fn === "rgba") {
+      const alpha = Number(parts[3]);
+      if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) return null;
+    }
     return [out[0], out[1], out[2]];
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@component/src/charts/chart-utils.ts` around lines 120 - 136, The rgb/rgba
parser currently only checks parts.length < 3 and thus accepts extra channels or
bad alpha; update the logic around the regex match (variable rgb), the parts
array, and the out construction to enforce exact arity: require parts.length ===
3 for rgb inputs and parts.length === 4 for rgba inputs, and when parts.length
=== 4 validate the alpha channel (parts[3]) is a finite number between 0 and 1
(reject otherwise). Keep existing numeric clamping for RGB channels (the out
array push logic) but return null for any invalid alpha or wrong number of
channels so malformed inputs are rejected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@component/src/charts/chart-utils.ts`:
- Around line 120-136: The rgb/rgba parser currently only checks parts.length <
3 and thus accepts extra channels or bad alpha; update the logic around the
regex match (variable rgb), the parts array, and the out construction to enforce
exact arity: require parts.length === 3 for rgb inputs and parts.length === 4
for rgba inputs, and when parts.length === 4 validate the alpha channel
(parts[3]) is a finite number between 0 and 1 (reject otherwise). Keep existing
numeric clamping for RGB channels (the out array push logic) but return null for
any invalid alpha or wrong number of channels so malformed inputs are rejected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 17a1efab-b19e-4824-ac91-5295a571bac6

📥 Commits

Reviewing files that changed from the base of the PR and between 93661e7 and 09319f5.

📒 Files selected for processing (4)
  • app/e2e/design-system.spec.ts
  • component/src/charts/bar-chart.tsx
  • component/src/charts/chart-utils.ts
  • component/src/charts/line-chart.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • component/src/charts/bar-chart.tsx

alfredorubin96 and others added 3 commits May 19, 2026 14:02
Four inline comments on the original push, three still valid; addressed
all three:

- table-renderer.tsx (MAJOR): the pagination "flash" gate only checked
  `containerHeight === undefined`. A momentary 0-height measurement
  before layout settled could still slip through and trigger the snap.
  Treat 0/negative heights as "not ready" too; pass containerHeight
  to DataGrid only when it is strictly positive.
- shared-utils.ts toSeriesNumber (MINOR): `Number("   ")` returns 0,
  so whitespace-only strings were silently masquerading as real zeros.
  Trim-check string inputs and return null for blanks. New test cases
  cover spaces, tabs, newlines.
- chart-utils.ts parseColorToRgb (MINOR): `rgb(1,2,3,4,5)` was parsed
  as `(1,2,3)` because we only validated the first three components.
  Enforce strict arity (rgb=3, rgba=4) and validate alpha is in [0,1].
  New tests cover wrong arity + out-of-range alpha + valid rgba.

The fourth comment (line-chart.tsx key-collection duplication) was
already addressed by 09319f5 — the autoAria copy of the loop was
removed when we extracted `buildAutoAriaDescription` into chart-utils.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
- chart-utils: split parseColorToRgb into parseHexColor + parseRgbFunctionColor
  helpers (Cognitive Complexity 25 -> below gate)
- chart-utils: replace parseInt with Number.parseInt (6 minor smells)
- line-chart: extract collectSeriesKeys + findLastNumericValue + buildSeries
  out of the options useMemo (Cognitive Complexity 18 -> below gate)
- table-renderer: use <section aria-label> instead of <div role="region"> for
  native landmark semantics

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
SonarCloud flagged `seriesKeys.map(buildSeries)` because Array.map passes
(element, index, array) and a direct function reference can pick up the
third arg unintentionally. Wrap explicitly to make the (key, idx) contract
the only thing buildSeries sees.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants