Skip to content

feat: configurable thousands/decimal separators for number formatting#7749

Open
eran132 wants to merge 2 commits into
getredash:masterfrom
eran132:feature/number-separators
Open

feat: configurable thousands/decimal separators for number formatting#7749
eran132 wants to merge 2 commits into
getredash:masterfrom
eran132:feature/number-separators

Conversation

@eran132
Copy link
Copy Markdown

@eran132 eran132 commented Jun 3, 2026

What & why

Fixes #7689. Setting REDASH_INTEGER_FORMAT="0 0" (or a space separator via the org settings) doesn't produce space-grouped numbers — and on closer inspection Redash isn't stripping the space at all.

The root cause is numeral.js semantics: , and . in numeral format strings are locale markers, not literal characters. The rendered separators come from the active locale's delimiters (en, / .), and Redash never overrode them — so a space/NBSP/apostrophe thousands separator was simply unreachable, and an invalid pattern like "0 0" silently dropped the space (no grouping).

Per the discussion in #7689 with @arikfr, this implements the explicit-separator approach (the locale option can follow as a thin layer on top):

  • New org settings thousands_separator / decimal_separator, env-defaulted via REDASH_THOUSANDS_SEPARATOR / REDASH_DECIMAL_SEPARATOR. Defaults , / . — zero behavior change for existing installs.
  • Exposed through number_format_config() → clientConfig and applied once to numeral.localeData().delimiters whenever visualization settings update.
  • Editable in the organization General settings page, alongside Date/Time format.

This makes full continental-European formatting reachable, e.g. 1 234 567,89.

Screenshot

New fields on the General settings page:

Thousands/Decimal Separator settings

Testing

All verified locally by execution (Docker, real Postgres + Redis):

  • Backend (tests/handlers/test_settings.py) — round-trip + defaults: 5 passed.
  • viz-lib unit (value-format.test.ts) — delimiter override incl. the reported space case: 4 passed.
  • Cypress E2E (organization_settings_spec.js) — set separator in the UI → reload (backend round-trip) → table renders 1 234 567: 3 passing on a clean run.

numeral.js treats `,` and `.` in format strings as locale markers, not
literal characters, so the rendered separators always came from the active
`en` locale's delimiters. There was no Redash setting to override them, so a
space (or NBSP/apostrophe) thousands separator was unreachable — formats like
"0 0" silently dropped the space and produced no grouping at all.

Add `REDASH_THOUSANDS_SEPARATOR` / `REDASH_DECIMAL_SEPARATOR` (org settings,
defaults `,` / `.` so existing installs are unchanged), exposed via
`number_format_config()` and applied once to `numeral.localeData().delimiters`
when visualization settings update. Also surface both as editable fields in the
organization General settings page.

Fixes getredash#7689

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 3, 2026 22:19
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds organization-configurable thousands and decimal separators so numerals can be rendered with non-default delimiters (e.g., spaces for grouping and commas for decimals).

Changes:

  • Introduces thousands_separator / decimal_separator org settings in the backend and exposes them to the frontend format config.
  • Updates frontend visualization settings to override numeral.js locale delimiters based on configured separators.
  • Adds unit, backend, and Cypress coverage for separator customization and persistence.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
viz-lib/src/visualizations/visualizationsSettings.tsx Adds separator settings and applies them by mutating numeral.js locale delimiters during settings updates
viz-lib/src/lib/value-format.test.ts Adds unit tests validating default and custom delimiter rendering
tests/handlers/test_settings.py Adds API tests for updating and defaulting separator settings
redash/settings/organization.py Adds env-backed defaults and includes separators in organization settings defaults
redash/handlers/authentication.py Exposes separator settings to frontend number format config
client/cypress/integration/settings/organization_settings_spec.js Adds E2E flow verifying separator setting persistence and table rendering
client/app/pages/settings/components/GeneralSettings/FormatSettings.jsx Adds UI inputs for thousands/decimal separator settings
client/app/components/visualizations/visualizationComponents.jsx Includes new visualization settings keys for propagation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 56 to 69
export function updateVisualizationsSettings(options: any) {
extend(visualizationsSettings, options);

// `,` and `.` in numeral format strings are locale markers, not literal characters —
// the rendered separators come from the active locale's delimiters. Override them so
// settings like a space thousands separator (e.g. "1 234 567") are reachable.
const { thousandsSeparator, decimalSeparator } = visualizationsSettings;
if (isString(thousandsSeparator) && isString(decimalSeparator)) {
extend(numeral.localeData().delimiters, {
thousands: thousandsSeparator,
decimal: decimalSeparator,
});
}
}
Comment thread viz-lib/src/lib/value-format.test.ts Outdated
Comment on lines +4 to +8
// numeral keeps a single global locale, so restore the defaults after each test
// to avoid leaking separator overrides into unrelated specs.
afterEach(() => {
updateVisualizationsSettings({ thousandsSeparator: ",", decimalSeparator: "." });
});
Comment on lines +51 to +56
<Input
style={{ width: 300 }}
value={values.thousands_separator}
onChange={e => onChange({ thousands_separator: e.target.value })}
data-test="ThousandsSeparatorInput"
/>
Comment on lines +50 to +54
cy.getByTestId("OrganizationSettingsSaveButton").click();

// round-trips through the backend
cy.reload();
cy.getByTestId("ThousandsSeparatorInput").should("have.value", " ");
Comment on lines +48 to +49
// capture the new separator fields for the PR description
cy.getByTestId("OrganizationSettings").screenshot("format-settings-separators");
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 8 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread client/cypress/integration/settings/organization_settings_spec.js
Comment thread viz-lib/src/visualizations/visualizationsSettings.tsx Outdated
- apply each numeral delimiter independently with a default fallback, so a
  non-string override never leaves numeral stuck on stale delimiters
- reset separators in beforeEach too, making the unit suite order-independent
- cap the separator inputs at maxLength=1 to match the "Character" copy
- wait on the save request before reloading in the e2e spec (avoids a race)
  and drop the one-off screenshot capture

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

If you set the variables REDASH_INTEGER_FORMAT="0 0" and REDASH_FLOAT_FORMAT="0 0.00", it does not work.

2 participants