diff --git a/public/app/features/explore/utils/links.test.ts b/public/app/features/explore/utils/links.test.ts index 587a62baa81..113e97c8dee 100644 --- a/public/app/features/explore/utils/links.test.ts +++ b/public/app/features/explore/utils/links.test.ts @@ -644,86 +644,47 @@ describe('explore links utils', () => { }); describe('getVariableUsageInfo', () => { - it('returns true when query contains variables and all variables are used', () => { - const dataLink = { + function makeDataLinkWithQuery(query: string): DataLink { + return { url: '', title: '', internal: { datasourceUid: 'uid', datasourceName: 'dsName', - query: { query: 'test ${testVal}' }, + query: { query }, }, }; + } + + function allVariablesDefinedInQuery(query: string) { const scopedVars = { testVal: { text: '', value: 'val1' }, }; - const dataLinkRtnVal = getVariableUsageInfo(dataLink, scopedVars).allVariablesDefined; + return getVariableUsageInfo(makeDataLinkWithQuery(query), scopedVars).allVariablesDefined; + } - expect(dataLinkRtnVal).toBe(true); + it('returns true when query contains variables and all variables are used', () => { + expect(allVariablesDefinedInQuery('test ${testVal}')).toBe(true); + }); + + it('ignores global variables', () => { + expect(allVariablesDefinedInQuery('test ${__rate_interval} $__from $__to')).toBe(true); }); it('returns false when query contains variables and no variables are used', () => { - const dataLink = { - url: '', - title: '', - internal: { - datasourceUid: 'uid', - datasourceName: 'dsName', - query: { query: 'test ${diffVar}' }, - }, - }; - const scopedVars = { - testVal: { text: '', value: 'val1' }, - }; - const dataLinkRtnVal = getVariableUsageInfo(dataLink, scopedVars).allVariablesDefined; - - expect(dataLinkRtnVal).toBe(false); + expect(allVariablesDefinedInQuery('test ${diffVar}')).toBe(false); }); it('returns false when query contains variables and some variables are used', () => { - const dataLink = { - url: '', - title: '', - internal: { - datasourceUid: 'uid', - datasourceName: 'dsName', - query: { query: 'test ${testVal} ${diffVar}' }, - }, - }; - const scopedVars = { - testVal: { text: '', value: 'val1' }, - }; - const dataLinkRtnVal = getVariableUsageInfo(dataLink, scopedVars).allVariablesDefined; - expect(dataLinkRtnVal).toBe(false); + expect(allVariablesDefinedInQuery('test ${testVal} ${diffVar}')).toBe(false); }); it('returns true when query contains no variables', () => { - const dataLink = { - url: '', - title: '', - internal: { - datasourceUid: 'uid', - datasourceName: 'dsName', - query: { query: 'test' }, - }, - }; - const scopedVars = { - testVal: { text: '', value: 'val1' }, - }; - const dataLinkRtnVal = getVariableUsageInfo(dataLink, scopedVars).allVariablesDefined; - expect(dataLinkRtnVal).toBe(true); + expect(allVariablesDefinedInQuery('test')).toBe(true); }); it('returns deduplicated list of variables', () => { - const dataLink = { - url: '', - title: '', - internal: { - datasourceUid: 'uid', - datasourceName: 'dsName', - query: { query: 'test ${test} ${foo} ${test:raw} $test' }, - }, - }; + const dataLink = makeDataLinkWithQuery('test ${test} ${foo} ${test:raw} $test'); const scopedVars = { testVal: { text: '', value: 'val1' }, }; diff --git a/public/app/features/explore/utils/links.ts b/public/app/features/explore/utils/links.ts index b1572c5064d..c308b882484 100644 --- a/public/app/features/explore/utils/links.ts +++ b/public/app/features/explore/utils/links.ts @@ -233,6 +233,23 @@ export function useLinks(range: TimeRange, splitOpenFn?: SplitOpen) { ); } +// See https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables +const builtInVariables = [ + '__from', + '__to', + '__interval', + '__interval_ms', + '__org', + '__user', + '__range', + '__rate_interval', + '__timeFilter', + 'timeFilter', + // These are only applicable in dashboards so should not affect this for Explore + // '__dashboard', + //'__name', +]; + /** * Use variable map from templateSrv to determine if all variables have values * @param query @@ -244,14 +261,20 @@ export function getVariableUsageInfo( ): { variables: VariableInterpolation[]; allVariablesDefined: boolean } { let variables: VariableInterpolation[] = []; const replaceFn = getTemplateSrv().replace.bind(getTemplateSrv()); + // This adds info to the variables array while interpolating replaceFn(getStringsFromObject(query), scopedVars, undefined, variables); variables = uniqBy(variables, 'variableName'); return { variables: variables, - allVariablesDefined: variables.every((variable) => variable.found), + allVariablesDefined: variables + // We filter out builtin variables as they should be always defined but sometimes only later, like + // __range_interval which is defined in prometheus at query time. + .filter((v) => !builtInVariables.includes(v.variableName)) + .every((variable) => variable.found), }; } +// Recursively get all strings from an object into a simple list with space as separator. function getStringsFromObject(obj: Object): string { let acc = ''; let k: keyof typeof obj;