Summary
A dataset measure declared with format: '0.0%' renders correctly for every ratio below 1, but not for a ratio of exactly 1. 1 (i.e. 100 %) is printed as 1.0% instead of 100.0%. Both the dashboard metric card and the dataset-bound table widget are affected.
For an SLA / pass-rate / completion-rate dashboard this is the single worst value to get wrong: "everything met the SLA" is shown to management as "1 % met the SLA".
Repro
Dataset (ObjectStack app metadata):
measures: [
{ name: 'sla_base_count', label: 'SLA applicable', aggregate: 'count' },
{ name: 'met_count', label: 'SLA met', aggregate: 'count', filter: { sla_met: true } },
{ name: 'sla_rate', label: 'SLA rate',
derived: { op: 'ratio', of: ['met_count', 'sla_base_count'] }, format: '0.0%' },
]
Dashboard widgets: one type: 'metric' bound to sla_rate, one type: 'table' with dimensions: ['status'], values: ['total_count','sla_rate', …].
| records in the base object |
ratio value |
metric card shows |
table cell shows |
expected |
| 2 of 2 met |
1 |
1.0% ❌ |
1.0% ❌ |
100.0% |
| 2 of 3 met |
0.6667 |
66.7% ✅ |
66.7% ✅ |
66.7% |
| 1 of 3 escalated |
0.3333 |
33.3% ✅ |
— |
33.3% |
| 0 of 2 escalated |
0 |
0.0% ✅ |
— (blank, see note) |
0.0% |
Same dataset, same format string, same dashboard — only the value differs. Verified on a clean database, @objectstack/*@17.0.0-rc.1, Console dashboard, Chromium.
Cause
The percent path scales by 100 only when the value is strictly inside (-1, 1), so a ratio of exactly 1 falls through unscaled and then gets a % appended. From the shipped console bundle (assets/ui-components-*.js):
o = a ? i : (i > -1 && i < 1 ? i * 100 : i)
and the sibling implementation in assets/plugin-dashboard-*.js carries the same "already a percentage?" heuristic:
return o ? `${(e > 1 ? e : e * 100).toFixed(c)}%` : …
The heuristic is trying to accept both scales (0–1 ratios and 0–100 percentage points) on one format string. That is inherently ambiguous exactly at 1, which is both "100 %" and "1 %". Today the ambiguity is resolved in favour of the reading that is almost never intended — a ratio measure can legitimately reach 1 (full compliance), while a percentage-point measure of 1 (one percent) is rare, and sla_rate in the example above can only ever be in [0, 1].
Suggestion
Make the scale explicit rather than guessed — the declaration already carries enough information to know which one it is (derived: { op: 'ratio' } is by definition 0–1). Options, roughly in order of preference:
- Honour the ratio semantics: a
% format always multiplies by 100 (numeral.js / Excel convention, which '0.0%' is borrowed from). Anything already in percentage points would then declare '0.0' + a literal % suffix, or a new explicit format.
- Keep the heuristic but let the author override it — e.g.
scale: 'ratio' | 'percent' on the measure.
- At minimum, scale on
i <= 1 rather than i < 1, so full compliance renders as 100.0%. (Narrow fix; still ambiguous for percentage-point data whose value is ≤ 1.)
Related, same widget family
In the dataset-bound table widget a 0 ratio renders as — (empty), while the metric card renders 0.0%. A measured zero and "no data" are different facts; the table treating 0 as blank hides real information (e.g. "0 % of this status met the SLA").
Summary
A dataset measure declared with
format: '0.0%'renders correctly for every ratio below 1, but not for a ratio of exactly 1.1(i.e. 100 %) is printed as1.0%instead of100.0%. Both the dashboard metric card and the dataset-bound table widget are affected.For an SLA / pass-rate / completion-rate dashboard this is the single worst value to get wrong: "everything met the SLA" is shown to management as "1 % met the SLA".
Repro
Dataset (ObjectStack app metadata):
Dashboard widgets: one
type: 'metric'bound tosla_rate, onetype: 'table'withdimensions: ['status'],values: ['total_count','sla_rate', …].11.0%❌1.0%❌100.0%0.666766.7%✅66.7%✅66.7%0.333333.3%✅33.3%00.0%✅—(blank, see note)0.0%Same dataset, same
formatstring, same dashboard — only the value differs. Verified on a clean database,@objectstack/*@17.0.0-rc.1, Console dashboard, Chromium.Cause
The percent path scales by 100 only when the value is strictly inside
(-1, 1), so a ratio of exactly1falls through unscaled and then gets a%appended. From the shipped console bundle (assets/ui-components-*.js):and the sibling implementation in
assets/plugin-dashboard-*.jscarries the same "already a percentage?" heuristic:The heuristic is trying to accept both scales (0–1 ratios and 0–100 percentage points) on one
formatstring. That is inherently ambiguous exactly at1, which is both "100 %" and "1 %". Today the ambiguity is resolved in favour of the reading that is almost never intended — a ratio measure can legitimately reach 1 (full compliance), while a percentage-point measure of1(one percent) is rare, andsla_ratein the example above can only ever be in[0, 1].Suggestion
Make the scale explicit rather than guessed — the declaration already carries enough information to know which one it is (
derived: { op: 'ratio' }is by definition 0–1). Options, roughly in order of preference:%format always multiplies by 100 (numeral.js / Excel convention, which'0.0%'is borrowed from). Anything already in percentage points would then declare'0.0'+ a literal%suffix, or a new explicit format.scale: 'ratio' | 'percent'on the measure.i <= 1rather thani < 1, so full compliance renders as100.0%. (Narrow fix; still ambiguous for percentage-point data whose value is ≤ 1.)Related, same widget family
In the dataset-bound table widget a
0ratio renders as—(empty), while the metric card renders0.0%. A measured zero and "no data" are different facts; the table treating0as blank hides real information (e.g. "0 % of this status met the SLA").