Skip to content

Commit 9666935

Browse files
authored
fix: Chart not displayed when formula references an undefined field (#3360)
1 parent 5f0c479 commit 9666935

3 files changed

Lines changed: 71 additions & 3 deletions

File tree

src/lib/FormulaEvaluator.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,19 @@ export function evaluateFormula(formula, variables) {
140140
try {
141141
const processedFormula = preprocessFormula(formula);
142142
const expr = parser.parse(processedFormula);
143-
let result = expr.evaluate(variables);
143+
144+
// Default any variables referenced by the formula but missing from the
145+
// provided variables to 0. This keeps charts rendering when a referenced
146+
// field has no value on a row (consistent with the "sum" operator, which
147+
// already treats missing fields as 0).
148+
const safeVariables = { ...(variables || {}) };
149+
for (const varName of expr.variables()) {
150+
if (!(varName in safeVariables)) {
151+
safeVariables[varName] = 0;
152+
}
153+
}
154+
155+
let result = expr.evaluate(safeVariables);
144156

145157
// Convert boolean results to numbers (for comparison operators)
146158
if (typeof result === 'boolean') {

src/lib/tests/FormulaEvaluator.test.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,15 @@ describe('FormulaEvaluator', () => {
169169
expect(evaluateFormula('invalid syntax !!!', { x: 10 })).toBe(null);
170170
});
171171

172-
it('should return null for undefined variables', () => {
173-
expect(evaluateFormula('unknownVar * 2', {})).toBe(null);
172+
it('should treat undefined variables as 0', () => {
173+
// Undefined variables should default to 0 so charts still render
174+
// when a referenced field has no value on a row (consistent with the
175+
// behavior of the "sum" operator, which already treats missing fields
176+
// as 0).
177+
expect(evaluateFormula('unknownVar * 2', {})).toBe(0);
178+
expect(evaluateFormula('x + unknownVar', { x: 5 })).toBe(5);
179+
expect(evaluateFormula('x * y', { x: 10 })).toBe(0);
180+
expect(evaluateFormula('a + b + c', { b: 7 })).toBe(7);
174181
});
175182

176183
it('should return null for NaN results', () => {

src/lib/tests/GraphDataUtils.test.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,55 @@ describe('GraphDataUtils', () => {
332332
// Should not throw, chart should render with regular values
333333
expect(result).toHaveProperty('datasets');
334334
});
335+
336+
it('should render chart when a referenced field is undefined on some rows', () => {
337+
// Reproduces the bug where a formula referencing a field that is
338+
// undefined on a row caused the entire chart to be empty. Undefined
339+
// fields should be treated as 0 instead.
340+
const dataWithMissingField = [
341+
{ attributes: { month: 'Jan', price: 10 } }, // quantity undefined
342+
{ attributes: { month: 'Feb', price: 20, quantity: 3 } },
343+
{ attributes: { month: 'Mar', price: 15 } }, // quantity undefined
344+
];
345+
346+
const calculatedValues = [{
347+
name: 'Total',
348+
operator: 'formula',
349+
formula: 'price * quantity',
350+
}];
351+
352+
const result = processBarLineData(dataWithMissingField, 'month', [], null, calculatedValues);
353+
354+
expect(result).toHaveProperty('datasets');
355+
expect(result.datasets.length).toBe(1);
356+
expect(result.datasets[0].label).toBe('Total');
357+
// Jan: 10 * 0 = 0, Feb: 20 * 3 = 60, Mar: 15 * 0 = 0
358+
expect(result.datasets[0].data).toContain(60);
359+
});
360+
361+
it('should render chart when the referenced field is undefined on every row', () => {
362+
// Even more extreme case: the field exists in the schema but no row
363+
// has a value for it. The chart should still render (using 0 for the
364+
// missing field) rather than disappearing entirely.
365+
const dataWithAllMissing = [
366+
{ attributes: { month: 'Jan', price: 10 } },
367+
{ attributes: { month: 'Feb', price: 20 } },
368+
];
369+
370+
const calculatedValues = [{
371+
name: 'Total',
372+
operator: 'formula',
373+
formula: 'price + quantity',
374+
}];
375+
376+
const result = processBarLineData(dataWithAllMissing, 'month', [], null, calculatedValues);
377+
378+
expect(result).toHaveProperty('datasets');
379+
expect(result.datasets.length).toBe(1);
380+
// Jan: 10 + 0 = 10, Feb: 20 + 0 = 20
381+
expect(result.datasets[0].data).toContain(10);
382+
expect(result.datasets[0].data).toContain(20);
383+
});
335384
});
336385

337386
describe('processPieData with formula', () => {

0 commit comments

Comments
 (0)