utils.ts 8.2 KB

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