mirror of
https://github.com/grafana/grafana.git
synced 2025-02-15 01:53:33 -06:00
215 lines
6.7 KiB
TypeScript
215 lines
6.7 KiB
TypeScript
import debounce from 'lodash/debounce';
|
|
import trim from 'lodash/trim';
|
|
import { StoreState, ThunkDispatch, ThunkResult } from 'app/types';
|
|
import {
|
|
QueryVariableModel,
|
|
VariableOption,
|
|
VariableRefresh,
|
|
VariableTag,
|
|
VariableWithMultiSupport,
|
|
VariableWithOptions,
|
|
} from '../../types';
|
|
import { variableAdapters } from '../../adapters';
|
|
import { getVariable } from '../../state/selectors';
|
|
import { NavigationKey } from '../types';
|
|
import {
|
|
hideOptions,
|
|
moveOptionsHighlight,
|
|
OptionsPickerState,
|
|
showOptions,
|
|
toggleOption,
|
|
toggleTag,
|
|
updateOptionsAndFilter,
|
|
updateOptionsFromSearch,
|
|
updateSearchQuery,
|
|
} from './reducer';
|
|
import { getDataSourceSrv } from '@grafana/runtime';
|
|
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
|
import { changeVariableProp, setCurrentVariableValue } from '../../state/sharedReducer';
|
|
import { toVariablePayload, VariableIdentifier } from '../../state/types';
|
|
import { containsSearchFilter, getCurrentText } from '../../utils';
|
|
|
|
export const navigateOptions = (key: NavigationKey, clearOthers: boolean): ThunkResult<void> => {
|
|
return async (dispatch, getState) => {
|
|
if (key === NavigationKey.cancel) {
|
|
return await dispatch(commitChangesToVariable());
|
|
}
|
|
|
|
if (key === NavigationKey.select) {
|
|
return dispatch(toggleOptionByHighlight(clearOthers));
|
|
}
|
|
|
|
if (key === NavigationKey.selectAndClose) {
|
|
dispatch(toggleOptionByHighlight(clearOthers, true));
|
|
return await dispatch(commitChangesToVariable());
|
|
}
|
|
|
|
if (key === NavigationKey.moveDown) {
|
|
return dispatch(moveOptionsHighlight(1));
|
|
}
|
|
|
|
if (key === NavigationKey.moveUp) {
|
|
return dispatch(moveOptionsHighlight(-1));
|
|
}
|
|
|
|
return undefined;
|
|
};
|
|
};
|
|
|
|
export const filterOrSearchOptions = (searchQuery = ''): ThunkResult<void> => {
|
|
return async (dispatch, getState) => {
|
|
const { id, queryValue } = getState().templating.optionsPicker;
|
|
const { query, options } = getVariable<VariableWithOptions>(id, getState());
|
|
dispatch(updateSearchQuery(searchQuery));
|
|
|
|
if (trim(queryValue) === trim(searchQuery)) {
|
|
return;
|
|
}
|
|
|
|
if (containsSearchFilter(query)) {
|
|
return searchForOptionsWithDebounce(dispatch, getState, searchQuery);
|
|
}
|
|
return dispatch(updateOptionsAndFilter(options));
|
|
};
|
|
};
|
|
|
|
const setVariable = async (updated: VariableWithMultiSupport) => {
|
|
const adapter = variableAdapters.get(updated.type);
|
|
await adapter.setValue(updated, updated.current, true);
|
|
return;
|
|
};
|
|
|
|
export const commitChangesToVariable = (callback?: (updated: any) => void): ThunkResult<void> => {
|
|
return async (dispatch, getState) => {
|
|
const picker = getState().templating.optionsPicker;
|
|
const existing = getVariable<VariableWithMultiSupport>(picker.id, getState());
|
|
const currentPayload = { option: mapToCurrent(picker) };
|
|
const searchQueryPayload = { propName: 'queryValue', propValue: picker.queryValue };
|
|
|
|
dispatch(setCurrentVariableValue(toVariablePayload(existing, currentPayload)));
|
|
dispatch(changeVariableProp(toVariablePayload(existing, searchQueryPayload)));
|
|
const updated = getVariable<VariableWithMultiSupport>(picker.id, getState());
|
|
dispatch(hideOptions());
|
|
|
|
if (getCurrentText(existing) === getCurrentText(updated)) {
|
|
return;
|
|
}
|
|
|
|
if (callback) {
|
|
return callback(updated);
|
|
}
|
|
|
|
return await setVariable(updated);
|
|
};
|
|
};
|
|
|
|
export const openOptions = ({ id }: VariableIdentifier, callback?: (updated: any) => void): ThunkResult<void> => async (
|
|
dispatch,
|
|
getState
|
|
) => {
|
|
const picker = getState().templating.optionsPicker;
|
|
|
|
if (picker.id && picker.id !== id) {
|
|
await dispatch(commitChangesToVariable(callback));
|
|
}
|
|
|
|
const variable = getVariable<VariableWithMultiSupport>(id, getState());
|
|
dispatch(showOptions(variable));
|
|
};
|
|
|
|
export const toggleOptionByHighlight = (clearOthers: boolean, forceSelect = false): ThunkResult<void> => {
|
|
return (dispatch, getState) => {
|
|
const { highlightIndex, options } = getState().templating.optionsPicker;
|
|
const option = options[highlightIndex];
|
|
dispatch(toggleOption({ option, forceSelect, clearOthers }));
|
|
};
|
|
};
|
|
|
|
export const toggleAndFetchTag = (tag: VariableTag): ThunkResult<void> => {
|
|
return async (dispatch, getState) => {
|
|
if (Array.isArray(tag.values)) {
|
|
return dispatch(toggleTag(tag));
|
|
}
|
|
|
|
const values = await dispatch(fetchTagValues(tag.text.toString()));
|
|
return dispatch(toggleTag({ ...tag, values }));
|
|
};
|
|
};
|
|
|
|
const fetchTagValues = (tagText: string): ThunkResult<Promise<string[]>> => {
|
|
return async (dispatch, getState) => {
|
|
const picker = getState().templating.optionsPicker;
|
|
const variable = getVariable<QueryVariableModel>(picker.id, getState());
|
|
|
|
const datasource = await getDataSourceSrv().get(variable.datasource ?? '');
|
|
const query = variable.tagValuesQuery.replace(/\$tag/g, tagText);
|
|
const options = { range: getTimeRange(variable), variable };
|
|
|
|
if (!datasource.metricFindQuery) {
|
|
return [];
|
|
}
|
|
|
|
const results = await datasource.metricFindQuery(query, options);
|
|
|
|
if (!Array.isArray(results)) {
|
|
return [];
|
|
}
|
|
return results.map((value) => value.text);
|
|
};
|
|
};
|
|
|
|
const getTimeRange = (variable: QueryVariableModel) => {
|
|
if (variable.refresh === VariableRefresh.onTimeRangeChanged || variable.refresh === VariableRefresh.onDashboardLoad) {
|
|
return getTimeSrv().timeRange();
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const searchForOptions = async (dispatch: ThunkDispatch, getState: () => StoreState, searchQuery: string) => {
|
|
try {
|
|
const { id } = getState().templating.optionsPicker;
|
|
const existing = getVariable<VariableWithOptions>(id, getState());
|
|
|
|
const adapter = variableAdapters.get(existing.type);
|
|
await adapter.updateOptions(existing, searchQuery);
|
|
|
|
const updated = getVariable<VariableWithOptions>(id, getState());
|
|
dispatch(updateOptionsFromSearch(updated.options));
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
};
|
|
|
|
const searchForOptionsWithDebounce = debounce(searchForOptions, 500);
|
|
|
|
export function mapToCurrent(picker: OptionsPickerState): VariableOption | undefined {
|
|
const { options, selectedValues, queryValue: searchQuery, multi } = picker;
|
|
|
|
if (options.length === 0 && searchQuery && searchQuery.length > 0) {
|
|
return { text: searchQuery, value: searchQuery, selected: false };
|
|
}
|
|
|
|
if (!multi) {
|
|
return selectedValues.find((o) => o.selected);
|
|
}
|
|
|
|
const texts: string[] = [];
|
|
const values: string[] = [];
|
|
|
|
for (const option of selectedValues) {
|
|
if (!option.selected) {
|
|
continue;
|
|
}
|
|
|
|
texts.push(option.text.toString());
|
|
values.push(option.value.toString());
|
|
}
|
|
|
|
return {
|
|
value: values,
|
|
text: texts,
|
|
tags: picker.tags.filter((t) => t.selected),
|
|
selected: true,
|
|
};
|
|
}
|