Skip to content

fix(ui): add bigint display support - #394

Merged
rapids-bot[bot] merged 16 commits into
rapidsai:mainfrom
cmatzenbach:bigint-display-support
Jul 28, 2026
Merged

fix(ui): add bigint display support#394
rapids-bot[bot] merged 16 commits into
rapidsai:mainfrom
cmatzenbach:bigint-display-support

Conversation

@cmatzenbach

@cmatzenbach cmatzenbach commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

Previously the DAG stats panel and tooltips did not support BigInt, so they would be coerced into strings and not go through the formatting functions. This adds BigInt support to the formatting functions (and opens up pathways so BigInt values can access them) so that they will be treated like other numeric values.

Other changes:

  • Moved isNumericValue into @quent/utils to use as a general utility function, avoiding repeated logic
  • Fix pivot to include BigInt (coerced into numbers) rather than dropping them
  • Fix DAG node/edge coloring so BigInt fields keep continuous coloring rather than falling back to categorical
  • Added tests covering BigInt paths

Note: Since we are coercing BigInts to numbers in several areas, I had claude do an analysis on the safety of each of these coercions in terms of what it affects in the application. Here is a chart showing each place and its risk factor:

Site Purpose Precision loss impact
dagFieldProcessing (node/edge coloring) value normalized to (v-min)/(max-min) ∈ [0,1] for a gradient None — a color gradient can't show 15-digit precision anyway
getSortValue (pivot sort) ordering only Negligible — only mis-orders values within ~1 of each other above 2⁵³; cosmetic
pivot aggregation (sum/mean/min/max/stdev) float math on number[] Approximate — sums of huge values drift in low digits, but results are displayed scaled/rounded
formatWithPrefix Si/Iec, formatDuration, _mbs, ratios scaled display ("8.0 PiB", "1.5 M") Invisible — output is ~3 sig figs, so lost low digits never appear
formatNumber / formatNumberWithMaxFractionDigits integer branch exact full-digit display with grouping Negligable - unless a displayed stat can carry an exact value above 9 quadrillion

Related Issues

Closes: #351

Testing

A set of unit tests were added to ensure all new BigInt paths are verified.

If you have a ndjson file with BigInt stats (I do not), that would be the best test scenario

Screenshots

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

BigInt support is added to statistic types and numeric formatters. DAG, query-plan, and pivot-table paths now format or aggregate bigint values, with tests covering formatter behavior, pivot aggregation, edge coloring, and edge widths.

Changes

BigInt formatting

Layer / File(s) Summary
BigInt formatter support
ui/packages/@quent/utils/src/dagTypes.ts, ui/packages/@quent/utils/src/formatters.ts, ui/packages/@quent/utils/src/formatters.test.ts
StatValue and formatter functions accept bigint values, coerce them for numeric formatting, and test prefix, inferred-field, and numeric-value behavior.
Pivot-table BigInt handling
ui/packages/@quent/components/src/pivot-table/utils.ts, ui/packages/@quent/components/src/pivot-table/buildPivotedRows.test.ts, ui/packages/@quent/components/src/pivot-table/utils.test.ts
Pivot-table numeric checks and formatting accept bigint values, with aggregation and formatting coverage for bigint statistics.
BigInt display integration
ui/packages/@quent/components/src/dag/DAGNodeInfoPanel.tsx, ui/packages/@quent/components/src/query-plan/QueryPlanNode.tsx, ui/packages/@quent/components/src/services/query-plan/dagFieldProcessing.test.ts
DAG statistics and query-plan color and width calculations handle bigint values as numeric data.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: johanpel

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning BigInt formatting is added, but the issue also requested replacing StatValue with the bindings Value type, which this PR does not do. Update the affected code paths to use ts-bindings Value where applicable, or document why StatValue remains and align the implementation with #351.
✅ Passed checks (4 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The changes stay focused on BigInt formatting support and related tests across DAG, query-plan, and pivot-table code.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding bigint display support.
Description check ✅ Passed The description follows the template and covers purpose, related issue, testing, and screenshots, with only the screenshot section left empty.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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: 1

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
ui/packages/@quent/utils/src/formatters.ts-121-126 (1)

121-126: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid coercing large BigInts through Number. formatWithPrefix and inferFieldFormatter round values above Number.MAX_SAFE_INTEGER, so large U64/I64 labels can drift. Add a bigint-safe fallback for unscaled output and cover >safe-integer cases in formatters.test.ts.

🤖 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 `@ui/packages/`@quent/utils/src/formatters.ts around lines 121 - 126, Update
formatWithPrefix and inferFieldFormatter in
ui/packages/@quent/utils/src/formatters.ts at lines 121-126 and 291-293 to
preserve bigint values without coercing them through Number, using a bigint-safe
path for unscaled output while retaining existing formatting for safe values.
Add regression coverage in ui/packages/@quent/utils/src/formatters.test.ts at
lines 259-277 and 451-455 for values exceeding Number.MAX_SAFE_INTEGER,
including large U64/I64 labels, and verify exact output without rounding.
🧹 Nitpick comments (1)
ui/packages/@quent/utils/src/formatters.test.ts (1)

259-277: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a bigint regression past Number.MAX_SAFE_INTEGER. The current cases only use exactly representable values, so they won’t catch the bigint -> number coercion in formatWithPrefix/inferFieldFormatter. Add one positive and one negative input above the safe-integer boundary and assert the expected display.

🤖 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 `@ui/packages/`@quent/utils/src/formatters.test.ts around lines 259 - 277,
Extend the formatWithPrefix bigint tests with one positive and one negative
value greater than Number.MAX_SAFE_INTEGER, asserting exact expected displays
that preserve bigint precision. Place the regression cases in the existing
bigint test coverage and ensure they exercise the
formatWithPrefix/inferFieldFormatter conversion path.
🤖 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 `@ui/packages/`@quent/utils/src/formatters.test.ts:
- Around line 255-257: Rename the colocated test file from formatters.test.ts to
formatters.test.tsx, preserving its existing Vitest test content and behavior.

---

Other comments:
In `@ui/packages/`@quent/utils/src/formatters.ts:
- Around line 121-126: Update formatWithPrefix and inferFieldFormatter in
ui/packages/@quent/utils/src/formatters.ts at lines 121-126 and 291-293 to
preserve bigint values without coercing them through Number, using a bigint-safe
path for unscaled output while retaining existing formatting for safe values.
Add regression coverage in ui/packages/@quent/utils/src/formatters.test.ts at
lines 259-277 and 451-455 for values exceeding Number.MAX_SAFE_INTEGER,
including large U64/I64 labels, and verify exact output without rounding.

---

Nitpick comments:
In `@ui/packages/`@quent/utils/src/formatters.test.ts:
- Around line 259-277: Extend the formatWithPrefix bigint tests with one
positive and one negative value greater than Number.MAX_SAFE_INTEGER, asserting
exact expected displays that preserve bigint precision. Place the regression
cases in the existing bigint test coverage and ensure they exercise the
formatWithPrefix/inferFieldFormatter conversion path.
🪄 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: QUIET

Plan: Enterprise

Run ID: ff47ed72-187c-484d-82bd-3588f5a8aec2

📥 Commits

Reviewing files that changed from the base of the PR and between b68dab1 and 2b656ed.

📒 Files selected for processing (5)
  • ui/packages/@quent/components/src/dag/DAGNodeInfoPanel.tsx
  • ui/packages/@quent/components/src/query-plan/QueryPlanNode.tsx
  • ui/packages/@quent/utils/src/dagTypes.ts
  • ui/packages/@quent/utils/src/formatters.test.ts
  • ui/packages/@quent/utils/src/formatters.ts

Comment thread ui/packages/@quent/utils/src/formatters.test.ts

@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: 1

🤖 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 `@ui/packages/`@quent/components/src/pivot-table/utils.ts:
- Around line 52-53: Update isNumericValue to compare typeof v against the
"bigint" type string, and retain the type predicate narrowing StatValue to
number | bigint so both numeric variants pass the guard.
🪄 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: QUIET

Plan: Enterprise

Run ID: 0062d330-9048-4c8f-bff3-223cae38310f

📥 Commits

Reviewing files that changed from the base of the PR and between 2b656ed and d2988be.

📒 Files selected for processing (1)
  • ui/packages/@quent/components/src/pivot-table/utils.ts

Comment thread ui/packages/@quent/components/src/pivot-table/utils.ts Outdated

@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: 3

🤖 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 `@ui/packages/`@quent/components/src/pivot-table/buildPivotedRows.test.ts:
- Line 159: Rename buildPivotedRows.test.ts to buildPivotedRows.test.tsx and
dagFieldProcessing.test.ts to dagFieldProcessing.test.tsx. Apply the same
dagFieldProcessing.test.tsx rename for both referenced tests, including the
width regression test; no test logic changes are required.
- Around line 160-176: Update isNumericValue in the aggregation path to detect
bigint values using the valid typeof string comparison, and update the
bucket.nums push logic in buildPivotedRows to coerce bigint row values to Number
before numeric reduction. Preserve existing handling for number values and
ensure bigint inputs continue through sum, mean, min, and max aggregation
without mixing numeric types.

In
`@ui/packages/`@quent/components/src/services/query-plan/dagFieldProcessing.test.ts:
- Around line 160-176: Update the numeric guards used by computeEdgeColoring and
computeEdgeWidthConfig in dagFieldProcessing.ts to accept values whose type is
either number or bigint. Preserve the existing numeric processing by coercing
accepted bigint values to number before calculating continuous coloring or
edge-width configuration.
🪄 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: QUIET

Plan: Enterprise

Run ID: b9dfd39c-4202-4cb7-b84d-5cdd5b92fafb

📥 Commits

Reviewing files that changed from the base of the PR and between d2988be and b96e14b.

📒 Files selected for processing (2)
  • ui/packages/@quent/components/src/pivot-table/buildPivotedRows.test.ts
  • ui/packages/@quent/components/src/services/query-plan/dagFieldProcessing.test.ts

Comment thread ui/packages/@quent/components/src/pivot-table/buildPivotedRows.test.ts Outdated
Comment thread ui/packages/@quent/components/src/services/query-plan/dagFieldProcessing.test.ts Outdated

@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: 3

🤖 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 `@ui/packages/`@quent/components/src/pivot-table/utils.test.ts:
- Around line 164-169: Rename the test file containing the formatStatValue
Vitest cases from utils.test.ts to utils.test.tsx, preserving all existing test
content and coverage.
- Around line 179-182: Fix the bigint handling introduced by the isNumericValue
test: correct the bigint guard in isNumericValue, then update its callers such
as getSortValue and aggregation paths to convert or otherwise widen bigint
values before number-only operations, including gradientBg and range
calculations. Preserve numeric behavior for existing number inputs and ensure
accepted bigint values cannot reach incompatible math unchanged.

In `@ui/packages/`@quent/utils/src/formatters.test.ts:
- Line 20: Fix the isNumericValue import in formatters.test.ts by locating the
module that defines and exports the predicate, then move the related checks
there or update the import to that module. Ensure the test no longer imports
isNumericValue from formatters.ts, which does not export it.
🪄 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: QUIET

Plan: Enterprise

Run ID: 290053fa-6376-41e1-834b-01c7fbf97f5c

📥 Commits

Reviewing files that changed from the base of the PR and between b96e14b and c7e14bc.

📒 Files selected for processing (2)
  • ui/packages/@quent/components/src/pivot-table/utils.test.ts
  • ui/packages/@quent/utils/src/formatters.test.ts

Comment thread ui/packages/@quent/components/src/pivot-table/utils.test.ts
Comment thread ui/packages/@quent/components/src/pivot-table/utils.test.ts
Comment thread ui/packages/@quent/utils/src/formatters.test.ts

@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: 1

🤖 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 `@ui/packages/`@quent/components/src/pivot-table/utils.ts:
- Line 176: Update getSortValue and the aggregation bucket’s row.value handling
to preserve bigint values through sorting and aggregation instead of coercing
them with Number. Add bigint-aware comparisons and sum/mean/min/max
calculations, while retaining existing behavior for numeric non-bigint values
and returning the appropriate StatValue type.
🪄 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: QUIET

Plan: Enterprise

Run ID: 1f16c92c-a5fd-490f-b0ee-61d7061ccdf8

📥 Commits

Reviewing files that changed from the base of the PR and between c7e14bc and 21a1624.

📒 Files selected for processing (6)
  • ui/packages/@quent/components/src/dag/DAGNodeInfoPanel.tsx
  • ui/packages/@quent/components/src/pivot-table/buildPivotedRows.test.ts
  • ui/packages/@quent/components/src/pivot-table/utils.test.ts
  • ui/packages/@quent/components/src/pivot-table/utils.ts
  • ui/packages/@quent/components/src/query-plan/QueryPlanNode.tsx
  • ui/packages/@quent/components/src/services/query-plan/dagFieldProcessing.test.ts
💤 Files with no reviewable changes (2)
  • ui/packages/@quent/components/src/pivot-table/utils.test.ts
  • ui/packages/@quent/components/src/pivot-table/buildPivotedRows.test.ts

const v = row.values.get(stat);
if (v === undefined) return null;
return isNumericValue(v) ? v : null;
return isNumericValue(v) ? Number(v) : null;

@coderabbitai coderabbitai Bot Jul 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file outline =="
ast-grep outline ui/packages/@quent/components/src/pivot-table/utils.ts --view expanded || true

echo
echo "== relevant lines =="
sed -n '140,310p' ui/packages/@quent/components/src/pivot-table/utils.ts

echo
echo "== search for isNumericValue and related callers =="
rg -n "isNumericValue|parseJsonWithBigInt|Number\\(v\\)|pivot-table" ui/packages/@quent/components/src -S

Repository: rapidsai/quent

Length of output: 10680


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== utils.ts around numeric formatting and aggregation =="
sed -n '1,140p' ui/packages/@quent/components/src/pivot-table/utils.ts
sed -n '240,290p' ui/packages/@quent/components/src/pivot-table/utils.ts

echo
echo "== pivot-table types =="
sed -n '1,220p' ui/packages/@quent/components/src/pivot-table/types.ts

echo
echo "== search for StatValue and isNumericValue definitions =="
rg -n "type StatValue|interface StatValue|export .*isNumericValue|function isNumericValue|const isNumericValue" ui/packages/@quent -S

Repository: rapidsai/quent

Length of output: 11009


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== isNumericValue / formatter definitions in `@quent/utils` =="
rg -n "export .*isNumericValue|function isNumericValue|const isNumericValue|inferFieldFormatter|format.*bigint|StatValue" ui/packages/@quent/utils/src -S

echo
echo "== candidate files =="
fd -a "dagTypes.ts|.*utils.*" ui/packages/@quent/utils/src

echo
echo "== relevant source snippets =="
sed -n '1,140p' ui/packages/@quent/utils/src/dagTypes.ts
sed -n '1,220p' ui/packages/@quent/utils/src/index.ts

Repository: rapidsai/quent

Length of output: 8520


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== getSortValue usages =="
rg -n "getSortValue\\(" ui/packages/@quent/components/src -S

echo
echo "== sort logic around pivoted rows =="
rg -n "sortInfo|compare|localeCompare|getSortValue|aggMode" ui/packages/@quent/components/src/pivot-table -S

echo
echo "== relevant chunks from pivot-table components =="
sed -n '1,260p' ui/packages/@quent/components/src/pivot-table/GroupedDataTable.tsx
sed -n '1,260p' ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx

Repository: rapidsai/quent

Length of output: 26159


Keep pivot-table stats as bigint through sort and aggregation
StatValue already includes bigint, but both Number(v) in getSortValue and Number(row.value) in the aggregation bucket silently narrow large u64 values. That can mis-order rows and produce wrong sums/means/min/max once values exceed Number.MAX_SAFE_INTEGER; use bigint-aware handling instead.

🤖 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 `@ui/packages/`@quent/components/src/pivot-table/utils.ts at line 176, Update
getSortValue and the aggregation bucket’s row.value handling to preserve bigint
values through sorting and aggregation instead of coercing them with Number. Add
bigint-aware comparisons and sum/mean/min/max calculations, while retaining
existing behavior for numeric non-bigint values and returning the appropriate
StatValue type.

Source: Path instructions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This too, but if you want I can go back and do the pivot table stuff

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@johallar, yes please—updating the pivot-table path in this PR would be best, since it is part of the BigInt-support objective. In particular, preserve exact bigint values for sorting and aggregate operations so large u64 statistics remain correctly ordered and calculated, and add regression coverage for values beyond Number.MAX_SAFE_INTEGER.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lol, comment intended for @cmatzenbach

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@johallar Ok, just pushed up a fix for this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice, I see the agg fixes, but the line that this comment is on will still affect sorting won't it?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@cmatzenbach cmatzenbach Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah right, should've mentioned it - I was thinking we could address this in a follow-up PR, as that would require cascading changes to gradientBg and the column range comparisons in PivotedStatsTable.tsx. Here would be the three areas where we are currently accepting precision loss, which would require that refactor:

  1. getSortValue line 176 - sort order for non-aggregated rows with large bigints
  2. getSortValue agg path - agg.sum, agg.min, agg.max come from PivotedRowAgg which stores number | null, so even though we now compute them more accurately as bigints, they're converted to number before being stored and returned here
  3. gradientBg color scaling - uses the number sort value to position a value on a color gradient, so large bigints that lost precision in getSortValue get slightly wrong colors

I'm not quite sure how large those changes would be, so I thought keeping it separate might make sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍 that works! Let's just track in an issue so we don't lose track of it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done - logged in #475

@cmatzenbach cmatzenbach added bug Something isn't working non-breaking Introduces a non-breaking change ui Pertains to the UI labels Jul 16, 2026
@cmatzenbach cmatzenbach changed the title bug(ui): add bigint display support fix(ui): add bigint display support Jul 16, 2026
@johallar

Copy link
Copy Markdown
Contributor

One change I want to make for this issue is to use the typescript binding for Value instead of defining our own StatValue. This should keep the UI type safe + bound to the API contract in the future

@cmatzenbach
cmatzenbach requested a review from johallar as a code owner July 21, 2026 17:34

const abs = value < 0 ? -value : value;
const sign = value < 0 ? '-' : '';
if (num === 0) return symbol ? `0 ${symbol}` : '0';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

bigint 0 does not triple equal number 0 😒 0 === 0n -> false

loose equality or another check for num === 0n would fix here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Probably outdated now based on your following comment, but yes, BigInt is coerced to ensure all values are number types here

decimals: number = 1
): string {
if (value === 0) return symbol ? `0 ${symbol}` : '0';
const num = typeof value === 'bigint' ? Number(value) : value;

@johallar johallar Jul 21, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Converting BigInt to Number is going to be a problem if above the max safe integer value

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That was acknowledged and addressed that in the table in the PR description above; the precision loss doesn't affect the output here. The rounding done by formatWithPrefix already throws away far more precision than the BigInt -> number conversion would, so the precision loss is invisible in the final output.

The place where the precision loss would matter is in formatNumber, which is handled differently. Instead we are passing BigInt to Intl.NumberFormat to ensure no conversion happens.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah I see, I think the case where this matters most is prefixSystem === "None", which would call toFixed on the coerced Number. If there was precision loss on the BigInt conversion to Number that would still be shown here.

Since we have the BigInt already, I think we want to do the scaling for units using the full precision if possible. Here's a failing unit test for the "None" case:

  it('preserves unprefixed bigint precision above Number.MAX_SAFE_INTEGER', () => {
    expect(formatWithPrefix(9007199254740993n, 'Hz', 'None', 0)).toBe(
      '9007199254740993 Hz'
    );
  });

There's one other case where losing the precision can cause it to roll into another prefix. Less concerned about this, but an edge case that could be fixed:

  it('normalizes a rounded mantissa into the next prefix', () => {
    expect(formatWithPrefix(999999999999999n, 'Hz', 'Si', 1)).toBe('1.0 PHz');
  });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah that's a great catch. Added a new path for BigInt values when prefixSystem is disabled so they are no longer coerced.

Also included one other small change AI had found earlier related to the Terra prefix - when the divided mantissa rounds up to 1000, we now step up to the next prefix instead of displaying 1000.0 THz.

@cmatzenbach

Copy link
Copy Markdown
Contributor Author

One change I want to make for this issue is to use the typescript binding for Value instead of defining our own StatValue. This should keep the UI type safe + bound to the API contract in the future

Agreed - went ahead and derived that from the BE-generated DynamicValue binding. Kept StatValue itself as it's a domain term, already used in multiple places (pre-PR) and includes the optional null value.

if (fieldName.endsWith('_mbs')) return v => `${v.toFixed(1)} MB/s`;
if (
export function inferFieldFormatter(fieldName: string): (value: number | bigint) => string {
let format: (value: number) => string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should be number | bigint shouldn't it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Assuming you're talking about the format variable, no - the BigInt conversion is handled at line 368 in the returned wrapper: value => format(typeof value === 'bigint' ? Number(value) : value)

if (fieldName.endsWith('_ns')) format = v => formatDuration(v / 1e6);
else if (isBytesStat(fieldName)) format = v => formatBytes(v, 2);
else if (isCountStat(fieldName)) format = v => formatSiCount(v, 2);
else if (fieldName.endsWith('_mbs')) format = v => `${v.toFixed(1)} MB/s`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this + the ratio/fraction block below is problematic for BigInts, they don't have a toFixed method

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These lambdas take number - the conversion from BigInt to number happens at line 368 before format is ever called. By the time any of these run, v is already a number, so .toFixed() works fine.

format = v => `${(v * 100).toFixed(1)}%`;
else format = v => formatNumberWithMaxFractionDigits(v, 4);

return value => format(typeof value === 'bigint' ? Number(value) : value);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This coercion is saving the formatters above from type errors, but prevents the bigint from ever reaching formatWithPrefix

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So I had looked at this before, but the formatters in this function - durations, ratios, percentages, counts - all do float arithmetic that doesn't make sense on a BigInt anyway. The only formatter where preserving BigInt precision matters is formatBytes, which calls formatWithPrefix. However, byte counts from the API are unlikely to exceed Number.MAX_SAFE_INTEGER in practice (that's what, 9 petabytes?), and even if they did, formatBytes applies IEC prefix scaling which rounds to 1 decimal place, so any precision loss in the Number() conversion would be invisible in the output.

@johallar johallar Jul 21, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This makes sense, but we are making a big assumption based on the field name as to what its probable values are at this point. The fact is that any of these DynamicValue type statistics can be 64 bit numbers, and it's going to depend on the engine developer who is implementing the instrumentation what these values are.

It would feel better to really cover our bases and handle all the (numeric) types instead of leaning on the Number coercion IMO.

eg. fields with _ns that are nanosecond epoch timestamps are going to exceed MAX_SAFE_INTEGER and display incorrect values when coerced to Number, but also if any of the other name based bucketing is not correct we could end up in that same situation

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Or actually...maybe if serde serializes these values with the type we can just have two code paths?

@cmatzenbach cmatzenbach Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok, here's another idea that addresses some values that could be valid bigints:

 export function inferFieldFormatter(fieldName: string): (value: number | bigint) => string {
 return (value: number | bigint): string => {                                                                                                                                                                                                                                                      
    const num = typeof value === 'bigint' ? Number(value) : value;                                                                                                                                                                                                                                  
    if (fieldName.endsWith('_ns')) return formatDuration(num / 1e6);                                                                                                                                                                                                                                       
    if (isBytesStat(fieldName)) return formatBytes(value, 2);                                                                                                                                                                                                                                       
    if (isCountStat(fieldName)) return formatSiCount(num, 2);                                                                                                                                                                                                                                       
    if (fieldName.endsWith('_mbs')) return `${num.toFixed(1)} MB/s`;                                                                                                                                                                                                                                
    if (                                                                                                                                                                                                                                                                                            
       fieldName.endsWith('_ratio') ||                                                                                                                                                                                                                                                               
       fieldName.endsWith('_fraction') ||                                                                                                                                                                                                                                                            
       fieldName.endsWith('_fpr') ||                                                                                                                                                                                                                                                                 
       fieldName.endsWith('_selectivity') ||                                                                                                                                                                                                                                                         
       fieldName.endsWith('_rate')                                                                                                                                                                                                                                                                   
     )                                                                                                                                                                                                                                                                                               
       return `${(num * 100).toFixed(1)}%`;                                                                                                                                                                                                                                                          
    return typeof value === 'bigint' ? formatNumber(value) : formatNumberWithMaxFractionDigits(num, 4);                                                                                                                                                                                             
 };

The inferFieldFormatter function now handles bigints with two explicit paths rather than blindly coercing everything to Number upfront:

  • formatBytes receives the original bigint, which formatWithPrefix already handles natively
  • The default case uses formatNumber(value) for bigints, which formats via Intl.NumberFormat
  • All other branches (_ns, counts, ratios, _mbs) still convert to Number since their math is float-only

Now, for the paths do still coerce to Number, here's my thought - there's really a limit to how far you can go here. For the ratio/percentage branch, if a value arrives as a bigint and you multiply it by 100 and call toFixed, the output is almost certainly wrong semantically too - a bigint named _ratio is a misuse of the field naming convention, and no amount of bigint-safe math will make the display meaningful. If the engine developer names a field _ratio and emits a u64, the display is wrong regardless - bigint safety doesn't fix that semantic mismatch. Let me know what you think.

@cmatzenbach

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit bc10bc8 into rapidsai:main Jul 28, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working non-breaking Introduces a non-breaking change ui Pertains to the UI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: DAG detail panel + entities in tooltip do not handle BigInt

2 participants