mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Variables: Fixes issue with All variable not being resolved (#27151)
* Variables: Fixes issue with All variable not being resolved * Tests: update tests according to changes
This commit is contained in:
parent
69df8b424c
commit
09a1af3f91
@ -25,6 +25,7 @@ import { GetVariables, getVariables } from 'app/features/variables/state/selecto
|
|||||||
import { variableAdapters } from 'app/features/variables/adapters';
|
import { variableAdapters } from 'app/features/variables/adapters';
|
||||||
import { onTimeRangeUpdated } from 'app/features/variables/state/actions';
|
import { onTimeRangeUpdated } from 'app/features/variables/state/actions';
|
||||||
import { dispatch } from '../../../store/store';
|
import { dispatch } from '../../../store/store';
|
||||||
|
import { isAllVariable } from '../../variables/utils';
|
||||||
|
|
||||||
export interface CloneOptions {
|
export interface CloneOptions {
|
||||||
saveVariables?: boolean;
|
saveVariables?: boolean;
|
||||||
@ -651,7 +652,7 @@ export class DashboardModel {
|
|||||||
|
|
||||||
getSelectedVariableOptions(variable: any) {
|
getSelectedVariableOptions(variable: any) {
|
||||||
let selectedOptions: any[];
|
let selectedOptions: any[];
|
||||||
if (variable.current.text === 'All') {
|
if (isAllVariable(variable)) {
|
||||||
selectedOptions = variable.options.slice(1, variable.options.length);
|
selectedOptions = variable.options.slice(1, variable.options.length);
|
||||||
} else {
|
} else {
|
||||||
selectedOptions = _.filter(variable.options, { selected: true });
|
selectedOptions = _.filter(variable.options, { selected: true });
|
||||||
|
@ -8,6 +8,7 @@ import { OptionsPicker } from '../pickers';
|
|||||||
import { CustomVariableEditor } from './CustomVariableEditor';
|
import { CustomVariableEditor } from './CustomVariableEditor';
|
||||||
import { updateCustomVariableOptions } from './actions';
|
import { updateCustomVariableOptions } from './actions';
|
||||||
import { ALL_VARIABLE_TEXT, toVariableIdentifier } from '../state/types';
|
import { ALL_VARIABLE_TEXT, toVariableIdentifier } from '../state/types';
|
||||||
|
import { isAllVariable } from '../utils';
|
||||||
|
|
||||||
export const createCustomVariableAdapter = (): VariableAdapter<CustomVariableModel> => {
|
export const createCustomVariableAdapter = (): VariableAdapter<CustomVariableModel> => {
|
||||||
return {
|
return {
|
||||||
@ -35,7 +36,7 @@ export const createCustomVariableAdapter = (): VariableAdapter<CustomVariableMod
|
|||||||
return rest;
|
return rest;
|
||||||
},
|
},
|
||||||
getValueForUrl: variable => {
|
getValueForUrl: variable => {
|
||||||
if (variable.current.text === ALL_VARIABLE_TEXT) {
|
if (isAllVariable(variable)) {
|
||||||
return ALL_VARIABLE_TEXT;
|
return ALL_VARIABLE_TEXT;
|
||||||
}
|
}
|
||||||
return variable.current.value;
|
return variable.current.value;
|
||||||
|
@ -8,7 +8,7 @@ import { OptionsPicker } from '../pickers';
|
|||||||
import { ALL_VARIABLE_TEXT, toVariableIdentifier } from '../state/types';
|
import { ALL_VARIABLE_TEXT, toVariableIdentifier } from '../state/types';
|
||||||
import { DataSourceVariableEditor } from './DataSourceVariableEditor';
|
import { DataSourceVariableEditor } from './DataSourceVariableEditor';
|
||||||
import { updateDataSourceVariableOptions } from './actions';
|
import { updateDataSourceVariableOptions } from './actions';
|
||||||
import { containsVariable } from '../utils';
|
import { containsVariable, isAllVariable } from '../utils';
|
||||||
|
|
||||||
export const createDataSourceVariableAdapter = (): VariableAdapter<DataSourceVariableModel> => {
|
export const createDataSourceVariableAdapter = (): VariableAdapter<DataSourceVariableModel> => {
|
||||||
return {
|
return {
|
||||||
@ -39,7 +39,7 @@ export const createDataSourceVariableAdapter = (): VariableAdapter<DataSourceVar
|
|||||||
return { ...rest, options: [] };
|
return { ...rest, options: [] };
|
||||||
},
|
},
|
||||||
getValueForUrl: variable => {
|
getValueForUrl: variable => {
|
||||||
if (variable.current.text === ALL_VARIABLE_TEXT) {
|
if (isAllVariable(variable)) {
|
||||||
return ALL_VARIABLE_TEXT;
|
return ALL_VARIABLE_TEXT;
|
||||||
}
|
}
|
||||||
return variable.current.value;
|
return variable.current.value;
|
||||||
|
@ -69,8 +69,6 @@ describe('options picker actions', () => {
|
|||||||
tester.thenDispatchedActionsShouldEqual(
|
tester.thenDispatchedActionsShouldEqual(
|
||||||
setCurrentVariableValue(toVariablePayload(variable, { option })),
|
setCurrentVariableValue(toVariablePayload(variable, { option })),
|
||||||
changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' })),
|
changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' })),
|
||||||
setCurrentVariableValue(toVariablePayload(variable, { option })),
|
|
||||||
updateLocation({ query: { 'var-Constant': ['A'] } }),
|
|
||||||
hideOptions()
|
hideOptions()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -219,7 +217,7 @@ describe('options picker actions', () => {
|
|||||||
|
|
||||||
describe('when commitChangesToVariable is dispatched with no changes', () => {
|
describe('when commitChangesToVariable is dispatched with no changes', () => {
|
||||||
it('then correct actions are dispatched', async () => {
|
it('then correct actions are dispatched', async () => {
|
||||||
const options = [createOption('A'), createOption('B'), createOption('C')];
|
const options = [createOption('A', 'A', true), createOption('B'), createOption('C')];
|
||||||
const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
|
const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
|
||||||
|
|
||||||
const tester = await reduxTester<{ templating: TemplatingState }>()
|
const tester = await reduxTester<{ templating: TemplatingState }>()
|
||||||
@ -228,10 +226,39 @@ describe('options picker actions', () => {
|
|||||||
.whenActionIsDispatched(showOptions(variable))
|
.whenActionIsDispatched(showOptions(variable))
|
||||||
.whenAsyncActionIsDispatched(commitChangesToVariable(), true);
|
.whenAsyncActionIsDispatched(commitChangesToVariable(), true);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
...createOption(['A']),
|
||||||
|
selected: true,
|
||||||
|
value: ['A'] as any[],
|
||||||
|
tags: [] as any[],
|
||||||
|
};
|
||||||
|
|
||||||
|
tester.thenDispatchedActionsShouldEqual(
|
||||||
|
setCurrentVariableValue(toVariablePayload(variable, { option })),
|
||||||
|
changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' })),
|
||||||
|
hideOptions()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when commitChangesToVariable is dispatched with changes', () => {
|
||||||
|
it('then correct actions are dispatched', async () => {
|
||||||
|
const options = [createOption('A', 'A', true), createOption('B'), createOption('C')];
|
||||||
|
const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
|
||||||
|
const clearOthers = false;
|
||||||
|
|
||||||
|
const tester = await reduxTester<{ templating: TemplatingState }>()
|
||||||
|
.givenRootReducer(getRootReducer())
|
||||||
|
.whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
|
||||||
|
.whenActionIsDispatched(showOptions(variable))
|
||||||
|
.whenActionIsDispatched(navigateOptions(NavigationKey.moveDown, clearOthers))
|
||||||
|
.whenActionIsDispatched(toggleOptionByHighlight(clearOthers))
|
||||||
|
.whenAsyncActionIsDispatched(commitChangesToVariable(), true);
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
...createOption([]),
|
...createOption([]),
|
||||||
selected: true,
|
selected: true,
|
||||||
value: [] as any[],
|
value: [],
|
||||||
tags: [] as any[],
|
tags: [] as any[],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -245,40 +272,9 @@ describe('options picker actions', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when commitChangesToVariable is dispatched with changes', () => {
|
|
||||||
it('then correct actions are dispatched', async () => {
|
|
||||||
const options = [createOption('A'), createOption('B'), createOption('C')];
|
|
||||||
const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
|
|
||||||
const clearOthers = false;
|
|
||||||
|
|
||||||
const tester = await reduxTester<{ templating: TemplatingState }>()
|
|
||||||
.givenRootReducer(getRootReducer())
|
|
||||||
.whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
|
|
||||||
.whenActionIsDispatched(showOptions(variable))
|
|
||||||
.whenActionIsDispatched(navigateOptions(NavigationKey.moveDown, clearOthers))
|
|
||||||
.whenActionIsDispatched(toggleOptionByHighlight(clearOthers))
|
|
||||||
.whenAsyncActionIsDispatched(commitChangesToVariable(), true);
|
|
||||||
|
|
||||||
const option = {
|
|
||||||
...createOption(['A']),
|
|
||||||
selected: true,
|
|
||||||
value: ['A'],
|
|
||||||
tags: [] as any[],
|
|
||||||
};
|
|
||||||
|
|
||||||
tester.thenDispatchedActionsShouldEqual(
|
|
||||||
setCurrentVariableValue(toVariablePayload(variable, { option })),
|
|
||||||
changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' })),
|
|
||||||
setCurrentVariableValue(toVariablePayload(variable, { option })),
|
|
||||||
updateLocation({ query: { 'var-Constant': ['A'] } }),
|
|
||||||
hideOptions()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when commitChangesToVariable is dispatched with changes and list of options is filtered', () => {
|
describe('when commitChangesToVariable is dispatched with changes and list of options is filtered', () => {
|
||||||
it('then correct actions are dispatched', async () => {
|
it('then correct actions are dispatched', async () => {
|
||||||
const options = [createOption('A'), createOption('B'), createOption('C')];
|
const options = [createOption('A', 'A', true), createOption('B'), createOption('C')];
|
||||||
const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
|
const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
|
||||||
const clearOthers = false;
|
const clearOthers = false;
|
||||||
|
|
||||||
@ -292,9 +288,9 @@ describe('options picker actions', () => {
|
|||||||
.whenAsyncActionIsDispatched(commitChangesToVariable(), true);
|
.whenAsyncActionIsDispatched(commitChangesToVariable(), true);
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
...createOption(['A']),
|
...createOption([]),
|
||||||
selected: true,
|
selected: true,
|
||||||
value: ['A'],
|
value: [],
|
||||||
tags: [] as any[],
|
tags: [] as any[],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -302,7 +298,7 @@ describe('options picker actions', () => {
|
|||||||
setCurrentVariableValue(toVariablePayload(variable, { option })),
|
setCurrentVariableValue(toVariablePayload(variable, { option })),
|
||||||
changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: 'C' })),
|
changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: 'C' })),
|
||||||
setCurrentVariableValue(toVariablePayload(variable, { option })),
|
setCurrentVariableValue(toVariablePayload(variable, { option })),
|
||||||
updateLocation({ query: { 'var-Constant': ['A'] } }),
|
updateLocation({ query: { 'var-Constant': [] } }),
|
||||||
hideOptions()
|
hideOptions()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -26,7 +26,7 @@ import { getDataSourceSrv } from '@grafana/runtime';
|
|||||||
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
import { changeVariableProp, setCurrentVariableValue } from '../../state/sharedReducer';
|
import { changeVariableProp, setCurrentVariableValue } from '../../state/sharedReducer';
|
||||||
import { toVariablePayload } from '../../state/types';
|
import { toVariablePayload } from '../../state/types';
|
||||||
import { containsSearchFilter } from '../../utils';
|
import { containsSearchFilter, getCurrentText } from '../../utils';
|
||||||
|
|
||||||
export const navigateOptions = (key: NavigationKey, clearOthers: boolean): ThunkResult<void> => {
|
export const navigateOptions = (key: NavigationKey, clearOthers: boolean): ThunkResult<void> => {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
@ -83,7 +83,7 @@ export const commitChangesToVariable = (): ThunkResult<void> => {
|
|||||||
dispatch(changeVariableProp(toVariablePayload(existing, searchQueryPayload)));
|
dispatch(changeVariableProp(toVariablePayload(existing, searchQueryPayload)));
|
||||||
const updated = getVariable<VariableWithMultiSupport>(picker.id, getState());
|
const updated = getVariable<VariableWithMultiSupport>(picker.id, getState());
|
||||||
|
|
||||||
if (existing.current.text === updated.current.text) {
|
if (getCurrentText(existing) === getCurrentText(updated)) {
|
||||||
return dispatch(hideOptions());
|
return dispatch(hideOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import { OptionsPicker } from '../pickers';
|
|||||||
import { QueryVariableEditor } from './QueryVariableEditor';
|
import { QueryVariableEditor } from './QueryVariableEditor';
|
||||||
import { updateQueryVariableOptions } from './actions';
|
import { updateQueryVariableOptions } from './actions';
|
||||||
import { ALL_VARIABLE_TEXT, toVariableIdentifier } from '../state/types';
|
import { ALL_VARIABLE_TEXT, toVariableIdentifier } from '../state/types';
|
||||||
import { containsVariable } from '../utils';
|
import { containsVariable, isAllVariable } from '../utils';
|
||||||
|
|
||||||
export const createQueryVariableAdapter = (): VariableAdapter<QueryVariableModel> => {
|
export const createQueryVariableAdapter = (): VariableAdapter<QueryVariableModel> => {
|
||||||
return {
|
return {
|
||||||
@ -42,7 +42,7 @@ export const createQueryVariableAdapter = (): VariableAdapter<QueryVariableModel
|
|||||||
return rest;
|
return rest;
|
||||||
},
|
},
|
||||||
getValueForUrl: variable => {
|
getValueForUrl: variable => {
|
||||||
if (variable.current.text === ALL_VARIABLE_TEXT) {
|
if (isAllVariable(variable)) {
|
||||||
return ALL_VARIABLE_TEXT;
|
return ALL_VARIABLE_TEXT;
|
||||||
}
|
}
|
||||||
return variable.current.value;
|
return variable.current.value;
|
||||||
|
@ -45,6 +45,7 @@ import {
|
|||||||
import { getBackendSrv } from '../../../core/services/backend_srv';
|
import { getBackendSrv } from '../../../core/services/backend_srv';
|
||||||
import { cleanVariables } from './variablesReducer';
|
import { cleanVariables } from './variablesReducer';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
|
import { getCurrentText } from '../utils';
|
||||||
|
|
||||||
// process flow queryVariable
|
// process flow queryVariable
|
||||||
// thunk => processVariables
|
// thunk => processVariables
|
||||||
@ -359,7 +360,8 @@ export const validateVariableSelectionState = (
|
|||||||
let option: VariableOption | undefined | null = null;
|
let option: VariableOption | undefined | null = null;
|
||||||
|
|
||||||
// 1. find the current value
|
// 1. find the current value
|
||||||
option = variableInState.options?.find(v => v.text === current.text);
|
const text = getCurrentText(variableInState);
|
||||||
|
option = variableInState.options?.find(v => v.text === text);
|
||||||
if (option) {
|
if (option) {
|
||||||
return setValue(variableInState, option);
|
return setValue(variableInState, option);
|
||||||
}
|
}
|
||||||
|
49
public/app/features/variables/utils.test.ts
Normal file
49
public/app/features/variables/utils.test.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { getCurrentText, isAllVariable } from './utils';
|
||||||
|
|
||||||
|
describe('isAllVariable', () => {
|
||||||
|
it.each`
|
||||||
|
variable | expected
|
||||||
|
${null} | ${false}
|
||||||
|
${undefined} | ${false}
|
||||||
|
${{}} | ${false}
|
||||||
|
${{ current: {} }} | ${false}
|
||||||
|
${{ current: { text: '' } }} | ${false}
|
||||||
|
${{ current: { text: null } }} | ${false}
|
||||||
|
${{ current: { text: undefined } }} | ${false}
|
||||||
|
${{ current: { text: 'Alll' } }} | ${false}
|
||||||
|
${{ current: { text: 'All' } }} | ${true}
|
||||||
|
${{ current: { text: [] } }} | ${false}
|
||||||
|
${{ current: { text: [null] } }} | ${false}
|
||||||
|
${{ current: { text: [undefined] } }} | ${false}
|
||||||
|
${{ current: { text: ['Alll'] } }} | ${false}
|
||||||
|
${{ current: { text: ['Alll', 'All'] } }} | ${false}
|
||||||
|
${{ current: { text: ['All'] } }} | ${true}
|
||||||
|
${{ current: { text: { prop1: 'test' } } }} | ${false}
|
||||||
|
`("when called with params: 'variable': '$variable' then result should be '$expected'", ({ variable, expected }) => {
|
||||||
|
expect(isAllVariable(variable)).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCurrentText', () => {
|
||||||
|
it.each`
|
||||||
|
variable | expected
|
||||||
|
${null} | ${''}
|
||||||
|
${undefined} | ${''}
|
||||||
|
${{}} | ${''}
|
||||||
|
${{ current: {} }} | ${''}
|
||||||
|
${{ current: { text: '' } }} | ${''}
|
||||||
|
${{ current: { text: null } }} | ${''}
|
||||||
|
${{ current: { text: undefined } }} | ${''}
|
||||||
|
${{ current: { text: 'A' } }} | ${'A'}
|
||||||
|
${{ current: { text: 'All' } }} | ${'All'}
|
||||||
|
${{ current: { text: [] } }} | ${''}
|
||||||
|
${{ current: { text: [null] } }} | ${''}
|
||||||
|
${{ current: { text: [undefined] } }} | ${''}
|
||||||
|
${{ current: { text: ['A'] } }} | ${'A'}
|
||||||
|
${{ current: { text: ['A', 'All'] } }} | ${'A,All'}
|
||||||
|
${{ current: { text: ['All'] } }} | ${'All'}
|
||||||
|
${{ current: { text: { prop1: 'test' } } }} | ${''}
|
||||||
|
`("when called with params: 'variable': '$variable' then result should be '$expected'", ({ variable, expected }) => {
|
||||||
|
expect(getCurrentText(variable)).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
@ -1,5 +1,6 @@
|
|||||||
import isString from 'lodash/isString';
|
import isString from 'lodash/isString';
|
||||||
import { ScopedVars } from '@grafana/data';
|
import { ScopedVars } from '@grafana/data';
|
||||||
|
import { ALL_VARIABLE_TEXT } from './state/types';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This regex matches 3 types of variable reference with an optional format specifier
|
* This regex matches 3 types of variable reference with an optional format specifier
|
||||||
@ -58,3 +59,47 @@ export function containsVariable(...args: any[]) {
|
|||||||
|
|
||||||
return !!isMatchingVariable;
|
return !!isMatchingVariable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isAllVariable = (variable: any): boolean => {
|
||||||
|
if (!variable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!variable.current) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!variable.current.text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(variable.current.text)) {
|
||||||
|
return variable.current.text.length ? variable.current.text[0] === ALL_VARIABLE_TEXT : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return variable.current.text === ALL_VARIABLE_TEXT;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCurrentText = (variable: any): string => {
|
||||||
|
if (!variable) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!variable.current) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!variable.current.text) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(variable.current.text)) {
|
||||||
|
return variable.current.text.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof variable.current.text !== 'string') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return variable.current.text;
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user