From 4571f756b88cc4ca50d884aadba90ca43c21acad Mon Sep 17 00:00:00 2001 From: Utkarsh Bhatnagar Date: Wed, 12 Aug 2026 07:21:25 -0700 Subject: [PATCH] fix(ui): tolerate non-JSON AnalysisRun measurement values Argo Rollouts' web provider passes a plain text response body through unchanged, so a measurement value is not guaranteed to be JSON. transformMeasurementValue called JSON.parse without a guard, which threw a SyntaxError and stopped the analysis modal from rendering at all -- clicking an AnalysisRun under a Stage's Verifications tab failed with "SyntaxError: Unexpected token 'P', \"PASS\" is not valid JSON". Treat an unparseable value as a non-chartable table value, consistent with how the function already handles unsupported formats. Signed-off-by: Utkarsh Bhatnagar --- .../common/analysis-modal/transforms.test.ts | 42 ++++++++++++++++++- .../common/analysis-modal/transforms.ts | 14 ++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/ui/src/features/common/analysis-modal/transforms.test.ts b/ui/src/features/common/analysis-modal/transforms.test.ts index e3750a498f..f97428bc27 100644 --- a/ui/src/features/common/analysis-modal/transforms.test.ts +++ b/ui/src/features/common/analysis-modal/transforms.test.ts @@ -24,7 +24,8 @@ import { metricStatusLabel, metricSubstatus, printableCloudWatchQuery, - printableDatadogQuery + printableDatadogQuery, + transformMeasurements } from './transforms'; import { AnalysisStatus, FunctionalStatus } from './types'; @@ -559,4 +560,43 @@ describe('analysis modal transforms', () => { tableValue: { latency: null, cpuUsage: null } }); }); + + // Regression: a provider that returns plain text (the web provider passes a + // text/plain body through unchanged) used to throw a SyntaxError out of + // JSON.parse and take the whole analysis modal down with it. + test('transformMeasurements() with a plain text measurement value', () => { + expect(transformMeasurements([], [{ value: 'PASS' }])).toEqual({ + chartable: false, + min: 0, + max: null, + measurements: [{ value: 'PASS', chartValue: null, tableValue: 'PASS' }] + }); + }); + test('transformMeasurements() with a malformed JSON measurement value', () => { + expect(transformMeasurements([], [{ value: '{"cpuUsage":' }])).toEqual({ + chartable: false, + min: 0, + max: null, + measurements: [{ value: '{"cpuUsage":', chartValue: null, tableValue: '{"cpuUsage":' }] + }); + }); + test('transformMeasurements() still parses a valid JSON measurement value', () => { + expect(transformMeasurements([], [{ value: '500' }])).toEqual({ + chartable: true, + min: 0, + max: 500, + measurements: [{ value: '500', chartValue: 500, tableValue: 500 }] + }); + }); + test('transformMeasurements() with both parseable and unparseable values', () => { + expect(transformMeasurements([], [{ value: '500' }, { value: 'PASS' }])).toEqual({ + chartable: false, + min: 0, + max: 500, + measurements: [ + { value: '500', chartValue: 500, tableValue: 500 }, + { value: 'PASS', chartValue: null, tableValue: 'PASS' } + ] + }); + }); }); diff --git a/ui/src/features/common/analysis-modal/transforms.ts b/ui/src/features/common/analysis-modal/transforms.ts index 046ef4f86e..9bec6b9f6a 100644 --- a/ui/src/features/common/analysis-modal/transforms.ts +++ b/ui/src/features/common/analysis-modal/transforms.ts @@ -783,7 +783,19 @@ const transformMeasurementValue = ( }; } - const parsedValue = JSON.parse(value); + let parsedValue; + try { + parsedValue = JSON.parse(value); + } catch { + // Providers are not obliged to return JSON. The web provider, for one, + // passes a plain text response through untouched. Such a value cannot be + // charted, but it is still worth surfacing in the table as-is. + return { + canChart: false, + chartValue: null, + tableValue: value + }; + } // single number measurement value if (isFiniteNumber(parsedValue)) {