1import { isArray, isEqual } from 'lodash'; 2import { ScopedVars, UrlQueryMap, UrlQueryValue, VariableType } from '@grafana/data'; 3import { getTemplateSrv } from '@grafana/runtime'; 4 5import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from './state/types'; 6import { QueryVariableModel, VariableModel, VariableRefresh } from './types'; 7import { getTimeSrv } from '../dashboard/services/TimeSrv'; 8import { variableAdapters } from './adapters'; 9import { safeStringifyValue } from 'app/core/utils/explore'; 10import { StoreState } from '../../types'; 11import { getState } from '../../store/store'; 12import { TransactionStatus } from './state/transactionReducer'; 13 14/* 15 * This regex matches 3 types of variable reference with an optional format specifier 16 * \$(\w+) $var1 17 * \[\[([\s\S]+?)(?::(\w+))?\]\] [[var2]] or [[var2:fmt2]] 18 * \${(\w+)(?::(\w+))?} ${var3} or ${var3:fmt3} 19 */ 20export const variableRegex = /\$(\w+)|\[\[([\s\S]+?)(?::(\w+))?\]\]|\${(\w+)(?:\.([^:^\}]+))?(?::([^\}]+))?}/g; 21 22// Helper function since lastIndex is not reset 23export const variableRegexExec = (variableString: string) => { 24 variableRegex.lastIndex = 0; 25 return variableRegex.exec(variableString); 26}; 27 28export const SEARCH_FILTER_VARIABLE = '__searchFilter'; 29 30export const containsSearchFilter = (query: string | unknown): boolean => 31 query && typeof query === 'string' ? query.indexOf(SEARCH_FILTER_VARIABLE) !== -1 : false; 32 33export const getSearchFilterScopedVar = (args: { 34 query: string; 35 wildcardChar: string; 36 options: { searchFilter?: string }; 37}): ScopedVars => { 38 const { query, wildcardChar } = args; 39 if (!containsSearchFilter(query)) { 40 return {}; 41 } 42 43 let { options } = args; 44 45 options = options || { searchFilter: '' }; 46 const value = options.searchFilter ? `${options.searchFilter}${wildcardChar}` : `${wildcardChar}`; 47 48 return { 49 __searchFilter: { 50 value, 51 text: '', 52 }, 53 }; 54}; 55 56export function containsVariable(...args: any[]) { 57 const variableName = args[args.length - 1]; 58 args[0] = typeof args[0] === 'string' ? args[0] : safeStringifyValue(args[0]); 59 const variableString = args.slice(0, -1).join(' '); 60 const matches = variableString.match(variableRegex); 61 const isMatchingVariable = 62 matches !== null 63 ? matches.find((match) => { 64 const varMatch = variableRegexExec(match); 65 return varMatch !== null && varMatch.indexOf(variableName) > -1; 66 }) 67 : false; 68 69 return !!isMatchingVariable; 70} 71 72export const isAllVariable = (variable: any): boolean => { 73 if (!variable) { 74 return false; 75 } 76 77 if (!variable.current) { 78 return false; 79 } 80 81 if (variable.current.value) { 82 const isArray = Array.isArray(variable.current.value); 83 if (isArray && variable.current.value.length && variable.current.value[0] === ALL_VARIABLE_VALUE) { 84 return true; 85 } 86 87 if (!isArray && variable.current.value === ALL_VARIABLE_VALUE) { 88 return true; 89 } 90 } 91 92 if (variable.current.text) { 93 const isArray = Array.isArray(variable.current.text); 94 if (isArray && variable.current.text.length && variable.current.text[0] === ALL_VARIABLE_TEXT) { 95 return true; 96 } 97 98 if (!isArray && variable.current.text === ALL_VARIABLE_TEXT) { 99 return true; 100 } 101 } 102 103 return false; 104}; 105 106export const getCurrentText = (variable: any): string => { 107 if (!variable) { 108 return ''; 109 } 110 111 if (!variable.current) { 112 return ''; 113 } 114 115 if (!variable.current.text) { 116 return ''; 117 } 118 119 if (Array.isArray(variable.current.text)) { 120 return variable.current.text.toString(); 121 } 122 123 if (typeof variable.current.text !== 'string') { 124 return ''; 125 } 126 127 return variable.current.text; 128}; 129 130export function getTemplatedRegex(variable: QueryVariableModel, templateSrv = getTemplateSrv()): string { 131 if (!variable) { 132 return ''; 133 } 134 135 if (!variable.regex) { 136 return ''; 137 } 138 139 return templateSrv.replace(variable.regex, {}, 'regex'); 140} 141 142export function getLegacyQueryOptions(variable: QueryVariableModel, searchFilter?: string, timeSrv = getTimeSrv()) { 143 const queryOptions: any = { range: undefined, variable, searchFilter }; 144 if (variable.refresh === VariableRefresh.onTimeRangeChanged || variable.refresh === VariableRefresh.onDashboardLoad) { 145 queryOptions.range = timeSrv.timeRange(); 146 } 147 148 return queryOptions; 149} 150 151export function getVariableRefresh(variable: VariableModel): VariableRefresh { 152 if (!variable || !variable.hasOwnProperty('refresh')) { 153 return VariableRefresh.never; 154 } 155 156 const queryVariable = variable as QueryVariableModel; 157 158 if ( 159 queryVariable.refresh !== VariableRefresh.onTimeRangeChanged && 160 queryVariable.refresh !== VariableRefresh.onDashboardLoad && 161 queryVariable.refresh !== VariableRefresh.never 162 ) { 163 return VariableRefresh.never; 164 } 165 166 return queryVariable.refresh; 167} 168 169export function getVariableTypes(): Array<{ label: string; value: VariableType }> { 170 return variableAdapters 171 .list() 172 .filter((v) => v.id !== 'system') 173 .map(({ id, name }) => ({ 174 label: name, 175 value: id, 176 })); 177} 178 179function getUrlValueForComparison(value: any): any { 180 if (isArray(value)) { 181 if (value.length === 0) { 182 value = undefined; 183 } else if (value.length === 1) { 184 value = value[0]; 185 } 186 } 187 188 return value; 189} 190 191export interface UrlQueryType { 192 value: UrlQueryValue; 193 removed?: boolean; 194} 195 196export interface ExtendedUrlQueryMap extends Record<string, UrlQueryType> {} 197 198export function findTemplateVarChanges(query: UrlQueryMap, old: UrlQueryMap): ExtendedUrlQueryMap | undefined { 199 let count = 0; 200 const changes: ExtendedUrlQueryMap = {}; 201 202 for (const key in query) { 203 if (!key.startsWith('var-')) { 204 continue; 205 } 206 207 let oldValue = getUrlValueForComparison(old[key]); 208 let newValue = getUrlValueForComparison(query[key]); 209 210 if (!isEqual(newValue, oldValue)) { 211 changes[key] = { value: query[key] }; 212 count++; 213 } 214 } 215 216 for (const key in old) { 217 if (!key.startsWith('var-')) { 218 continue; 219 } 220 221 const value = old[key]; 222 223 // ignore empty array values 224 if (isArray(value) && value.length === 0) { 225 continue; 226 } 227 228 if (!query.hasOwnProperty(key)) { 229 changes[key] = { value: '', removed: true }; // removed 230 count++; 231 } 232 } 233 return count ? changes : undefined; 234} 235 236export function ensureStringValues(value: any | any[]): string | string[] { 237 if (Array.isArray(value)) { 238 return value.map(String); 239 } 240 241 if (value === null || value === undefined) { 242 return ''; 243 } 244 245 if (typeof value === 'number') { 246 return value.toString(10); 247 } 248 249 if (typeof value === 'string') { 250 return value; 251 } 252 253 if (typeof value === 'boolean') { 254 return value.toString(); 255 } 256 257 return ''; 258} 259 260export function hasOngoingTransaction(state: StoreState = getState()): boolean { 261 return state.templating.transaction.status !== TransactionStatus.NotStarted; 262} 263