From d5039394cb4fff2067da6240abccc62fe274241f Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Mon, 21 Oct 2024 08:25:20 +0200 Subject: [PATCH] fix: variable flickers when changing value from right sidebar panel --- src/components/debouncedVariableCheck.ts | 9 ++--- src/components/utils/extractRange.ts | 32 +++++++++++++++++ .../utils/findMinAndMaxValues.test.ts | 1 - src/components/utils/findMinAndMaxValues.ts | 25 ++++--------- src/components/utils/isValidVariable.test.ts | 35 +++++++++++++++++++ src/components/utils/isValidVariable.ts | 31 ++++++++++++++++ 6 files changed, 109 insertions(+), 24 deletions(-) create mode 100644 src/components/utils/extractRange.ts create mode 100644 src/components/utils/isValidVariable.test.ts create mode 100644 src/components/utils/isValidVariable.ts diff --git a/src/components/debouncedVariableCheck.ts b/src/components/debouncedVariableCheck.ts index 4e09d96..9df448f 100644 --- a/src/components/debouncedVariableCheck.ts +++ b/src/components/debouncedVariableCheck.ts @@ -1,21 +1,22 @@ import { getTemplateSrv } from '@grafana/runtime'; +import { isValidVariable } from './utils/isValidVariable'; export const debouncedVariableCheck = ( variableName: string, onSuccess: (variableValue: string) => void, onError: () => void, interval = 1000, - maxDuration = 5000 + maxDuration = 3000 ) => { let elapsed = 0; const checkVariable = () => { const variable = getTemplateSrv() .getVariables() - .find((v) => v.name === variableName && (v as any).current?.value); + .find((v) => v.name === variableName); - if (variable && 'current' in variable) { - onSuccess(variable.current.value as string); + if (isValidVariable(variable)) { + onSuccess(variable.current.value); } else { elapsed += interval; if (elapsed < maxDuration) { diff --git a/src/components/utils/extractRange.ts b/src/components/utils/extractRange.ts new file mode 100644 index 0000000..3c4a3fe --- /dev/null +++ b/src/components/utils/extractRange.ts @@ -0,0 +1,32 @@ +interface ExtractedRange { + minValue: string; + delimiter: string; + maxValue: string; +} + +const extractRange = (value: string): ExtractedRange | null => { + const numberRegex = /(-?\d+)/; + const firstNumberMatch = numberRegex.exec(value); + + if (!firstNumberMatch) { + return null; + } + + const firstNumber = firstNumberMatch[1]; + const restOfString = value.slice(firstNumberMatch.index + firstNumber.length); + + const delimiterRegex = /(.+?)(-?\d+)([^\d]*)$/; + const delimiterMatch = delimiterRegex.exec(restOfString); + + if (!delimiterMatch || delimiterMatch.length < 3) { + return null; + } + + return { + minValue: firstNumber, + delimiter: delimiterMatch[1].trim(), + maxValue: delimiterMatch[2], + }; +}; + +export default extractRange; diff --git a/src/components/utils/findMinAndMaxValues.test.ts b/src/components/utils/findMinAndMaxValues.test.ts index 2233d78..1bab75b 100644 --- a/src/components/utils/findMinAndMaxValues.test.ts +++ b/src/components/utils/findMinAndMaxValues.test.ts @@ -36,7 +36,6 @@ describe('findMinAndMaxValues', () => { }); it('should throw an error when no min and max values are found', () => { - expect(() => findMinAndMaxValues('no numbers here')).toThrow('Could not find any number'); expect(() => findMinAndMaxValues('[10]')).toThrow('Could not find min and max values'); expect(() => findMinAndMaxValues('[10-to]')).toThrow('Could not find min and max values'); }); diff --git a/src/components/utils/findMinAndMaxValues.ts b/src/components/utils/findMinAndMaxValues.ts index 05e95a8..1102c05 100644 --- a/src/components/utils/findMinAndMaxValues.ts +++ b/src/components/utils/findMinAndMaxValues.ts @@ -1,24 +1,11 @@ -export default function findMinAndMaxValues(variableValue: string) { - const numberRegex = /(-?\d+)/; - const firstNumberMatch = numberRegex.exec(variableValue); - - if (!firstNumberMatch) { - throw new Error('Could not find any number'); - } +import extractRange from './extractRange'; - const firstNumber = firstNumberMatch[1]; - const restOfString = variableValue.slice(firstNumberMatch.index + firstNumber.length); - - const delimiterRegex = /(.+?)(-?\d+)([^\d]*)$/; - const delimiterMatch = delimiterRegex.exec(restOfString); +export default function findMinAndMaxValues(variableValue: string) { + const extracted = extractRange(variableValue); - if (delimiterMatch && delimiterMatch.length >= 3) { - return { - minValue: firstNumber, - delimiter: delimiterMatch[1].trim(), - maxValue: delimiterMatch[2], - }; - } else { + if (!extracted) { throw new Error('Could not find min and max values'); } + + return extracted; } diff --git a/src/components/utils/isValidVariable.test.ts b/src/components/utils/isValidVariable.test.ts new file mode 100644 index 0000000..c5e7579 --- /dev/null +++ b/src/components/utils/isValidVariable.test.ts @@ -0,0 +1,35 @@ +import { isValidVariable } from './isValidVariable'; + +describe('isValidVariable', () => { + it('should return true for valid RangeVariable with valid range format', () => { + const validVariable = { current: { value: '10-90' } }; + expect(isValidVariable(validVariable)).toBe(true); + }); + + it('should return false for RangeVariable with invalid range format', () => { + const invalidVariable = { current: { value: 'invalid range' } }; + expect(isValidVariable(invalidVariable)).toBe(false); + }); + + it('should return false for RangeVariable with no value property', () => { + const invalidVariable = { current: {} }; + expect(isValidVariable(invalidVariable)).toBe(false); + }); + + it('should return false for non-object input', () => { + expect(isValidVariable(null)).toBe(false); + expect(isValidVariable(undefined)).toBe(false); + expect(isValidVariable('not an object')).toBe(false); + expect(isValidVariable(123)).toBe(false); + }); + + it('should return false for RangeVariable where current is null', () => { + const invalidVariable = { current: null }; + expect(isValidVariable(invalidVariable)).toBe(false); + }); + + it('should return false for RangeVariable where value is not a string', () => { + const invalidVariable = { current: { value: 123 } }; + expect(isValidVariable(invalidVariable)).toBe(false); + }); +}); diff --git a/src/components/utils/isValidVariable.ts b/src/components/utils/isValidVariable.ts new file mode 100644 index 0000000..befb8c9 --- /dev/null +++ b/src/components/utils/isValidVariable.ts @@ -0,0 +1,31 @@ +import extractRange from './extractRange'; + +const isValidRangeFormat = (value: string): boolean => { + try { + return !!extractRange(value); + } catch { + return false; + } +}; + +export interface RangeVariable { + current: { + value: string; + }; +} + +export const isValidVariable = (v: any): v is RangeVariable => { + if (v === null || v === undefined) { + return false; + } + + if (!v.current || v.current === null) { + return false; + } + + if (typeof v.current.value !== 'string') { + return false; + } + + return isValidRangeFormat(v.current.value); +};