1import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2import { isNumber, sortBy, toLower, uniqBy } from 'lodash'; 3import { DataSourceApi, MetricFindValue, stringToJsRegex } from '@grafana/data'; 4 5import { 6 initialVariableModelState, 7 QueryVariableModel, 8 VariableOption, 9 VariableQueryEditorType, 10 VariableRefresh, 11 VariableSort, 12} from '../types'; 13 14import { 15 ALL_VARIABLE_TEXT, 16 ALL_VARIABLE_VALUE, 17 getInstanceState, 18 initialVariablesState, 19 NONE_VARIABLE_TEXT, 20 NONE_VARIABLE_VALUE, 21 VariablePayload, 22 VariablesState, 23} from '../state/types'; 24 25interface VariableOptionsUpdate { 26 templatedRegex: string; 27 results: MetricFindValue[]; 28} 29 30export interface QueryVariableEditorState { 31 VariableQueryEditor: VariableQueryEditorType; 32 dataSource: DataSourceApi | null; 33} 34 35export const initialQueryVariableModelState: QueryVariableModel = { 36 ...initialVariableModelState, 37 type: 'query', 38 datasource: null, 39 query: '', 40 regex: '', 41 sort: VariableSort.disabled, 42 refresh: VariableRefresh.onDashboardLoad, 43 multi: false, 44 includeAll: false, 45 allValue: null, 46 options: [], 47 current: {} as VariableOption, 48 definition: '', 49}; 50 51export const sortVariableValues = (options: any[], sortOrder: VariableSort) => { 52 if (sortOrder === VariableSort.disabled) { 53 return options; 54 } 55 56 const sortType = Math.ceil(sortOrder / 2); 57 const reverseSort = sortOrder % 2 === 0; 58 59 if (sortType === 1) { 60 options = sortBy(options, 'text'); 61 } else if (sortType === 2) { 62 options = sortBy(options, (opt) => { 63 if (!opt.text) { 64 return -1; 65 } 66 67 const matches = opt.text.match(/.*?(\d+).*/); 68 if (!matches || matches.length < 2) { 69 return -1; 70 } else { 71 return parseInt(matches[1], 10); 72 } 73 }); 74 } else if (sortType === 3) { 75 options = sortBy(options, (opt) => { 76 return toLower(opt.text); 77 }); 78 } 79 80 if (reverseSort) { 81 options = options.reverse(); 82 } 83 84 return options; 85}; 86 87const getAllMatches = (str: string, regex: RegExp): RegExpExecArray[] => { 88 const results: RegExpExecArray[] = []; 89 let matches = null; 90 91 regex.lastIndex = 0; 92 93 do { 94 matches = regex.exec(str); 95 if (matches) { 96 results.push(matches); 97 } 98 } while (regex.global && matches && matches[0] !== '' && matches[0] !== undefined); 99 100 return results; 101}; 102 103export const metricNamesToVariableValues = (variableRegEx: string, sort: VariableSort, metricNames: any[]) => { 104 let regex; 105 let options: VariableOption[] = []; 106 107 if (variableRegEx) { 108 regex = stringToJsRegex(variableRegEx); 109 } 110 111 for (let i = 0; i < metricNames.length; i++) { 112 const item = metricNames[i]; 113 let text = item.text === undefined || item.text === null ? item.value : item.text; 114 let value = item.value === undefined || item.value === null ? item.text : item.value; 115 116 if (isNumber(value)) { 117 value = value.toString(); 118 } 119 120 if (isNumber(text)) { 121 text = text.toString(); 122 } 123 124 if (regex) { 125 const matches = getAllMatches(value, regex); 126 if (!matches.length) { 127 continue; 128 } 129 130 const valueGroup = matches.find((m) => m.groups && m.groups.value); 131 const textGroup = matches.find((m) => m.groups && m.groups.text); 132 const firstMatch = matches.find((m) => m.length > 1); 133 const manyMatches = matches.length > 1 && firstMatch; 134 135 if (valueGroup || textGroup) { 136 value = valueGroup?.groups?.value ?? textGroup?.groups?.text; 137 text = textGroup?.groups?.text ?? valueGroup?.groups?.value; 138 } else if (manyMatches) { 139 for (let j = 0; j < matches.length; j++) { 140 const match = matches[j]; 141 options.push({ text: match[1], value: match[1], selected: false }); 142 } 143 continue; 144 } else if (firstMatch) { 145 text = firstMatch[1]; 146 value = firstMatch[1]; 147 } 148 } 149 150 options.push({ text: text, value: value, selected: false }); 151 } 152 153 options = uniqBy(options, 'value'); 154 return sortVariableValues(options, sort); 155}; 156 157export const queryVariableSlice = createSlice({ 158 name: 'templating/query', 159 initialState: initialVariablesState, 160 reducers: { 161 updateVariableOptions: (state: VariablesState, action: PayloadAction<VariablePayload<VariableOptionsUpdate>>) => { 162 const { results, templatedRegex } = action.payload.data; 163 const instanceState = getInstanceState<QueryVariableModel>(state, action.payload.id); 164 const { includeAll, sort } = instanceState; 165 const options = metricNamesToVariableValues(templatedRegex, sort, results); 166 167 if (includeAll) { 168 options.unshift({ text: ALL_VARIABLE_TEXT, value: ALL_VARIABLE_VALUE, selected: false }); 169 } 170 171 if (!options.length) { 172 options.push({ text: NONE_VARIABLE_TEXT, value: NONE_VARIABLE_VALUE, isNone: true, selected: false }); 173 } 174 175 instanceState.options = options; 176 }, 177 }, 178}); 179 180export const queryVariableReducer = queryVariableSlice.reducer; 181 182export const { updateVariableOptions } = queryVariableSlice.actions; 183