Dashboard: Variable without a current value in json model causes crash on load (#24261)

* Variables: Fixes variable initilization and default values

* fixed failing tests.

Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>
This commit is contained in:
Torkel Ödegaard 2020-05-05 15:47:48 +02:00 committed by GitHub
parent a2363f4d0c
commit cdc5203d8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 19 additions and 7 deletions

View File

@ -196,7 +196,7 @@ describe('shared actions', () => {
${['A', 'B', 'C']} | ${'B'} | ${'C'} | ${'B'} ${['A', 'B', 'C']} | ${'B'} | ${'C'} | ${'B'}
${['A', 'B', 'C']} | ${'X'} | ${undefined} | ${'A'} ${['A', 'B', 'C']} | ${'X'} | ${undefined} | ${'A'}
${['A', 'B', 'C']} | ${'X'} | ${'C'} | ${'C'} ${['A', 'B', 'C']} | ${'X'} | ${'C'} | ${'C'}
${undefined} | ${'B'} | ${undefined} | ${'A'} ${undefined} | ${'B'} | ${undefined} | ${'should not dispatch setCurrentVariableValue'}
`('then correct actions are dispatched', async ({ withOptions, withCurrent, defaultValue, expected }) => { `('then correct actions are dispatched', async ({ withOptions, withCurrent, defaultValue, expected }) => {
let custom; let custom;

View File

@ -308,8 +308,10 @@ export const validateVariableSelectionState = (
// 3. use the first value // 3. use the first value
if (variableInState.options) { if (variableInState.options) {
const option = variableInState.options[0]; const option = variableInState.options[0];
if (option) {
return setValue(variableInState, option); return setValue(variableInState, option);
} }
}
// 4... give up // 4... give up
return Promise.resolve(); return Promise.resolve();

View File

@ -1,4 +1,5 @@
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import { default as lodashDefaults } from 'lodash/defaults';
import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { reducerTester } from '../../../../test/core/redux/reducerTester';
import { import {
@ -29,15 +30,20 @@ variableAdapters.setInit(() => [createQueryVariableAdapter()]);
describe('sharedReducer', () => { describe('sharedReducer', () => {
describe('when addVariable is dispatched', () => { describe('when addVariable is dispatched', () => {
it('then state should be correct', () => { it('then state should be correct', () => {
const model = ({ name: 'name from model', type: 'type from model' } as unknown) as QueryVariableModel; const model = ({
name: 'name from model',
type: 'type from model',
current: undefined,
} as unknown) as QueryVariableModel;
const payload = toVariablePayload({ id: '0', type: 'query' }, { global: true, index: 0, model }); const payload = toVariablePayload({ id: '0', type: 'query' }, { global: true, index: 0, model });
reducerTester<VariablesState>() reducerTester<VariablesState>()
.givenReducer(sharedReducer, { ...initialVariablesState }) .givenReducer(sharedReducer, { ...initialVariablesState })
.whenActionIsDispatched(addVariable(payload)) .whenActionIsDispatched(addVariable(payload))
.thenStateShouldEqual({ .thenStateShouldEqual({
[0]: { [0]: {
...initialQueryVariableModelState, ...lodashDefaults({}, model, initialQueryVariableModelState),
...model,
id: '0', id: '0',
global: true, global: true,
index: 0, index: 0,

View File

@ -1,5 +1,6 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import { default as lodashDefaults } from 'lodash/defaults';
import { VariableType } from '@grafana/data'; import { VariableType } from '@grafana/data';
import { VariableModel, VariableOption, VariableWithOptions } from '../../templating/types'; import { VariableModel, VariableOption, VariableWithOptions } from '../../templating/types';
@ -16,13 +17,16 @@ const sharedReducerSlice = createSlice({
reducers: { reducers: {
addVariable: (state: VariablesState, action: PayloadAction<VariablePayload<AddVariable>>) => { addVariable: (state: VariablesState, action: PayloadAction<VariablePayload<AddVariable>>) => {
const id = action.payload.id ?? action.payload.data.model.name; // for testing purposes we can call this with an id const id = action.payload.id ?? action.payload.data.model.name; // for testing purposes we can call this with an id
const initialState = cloneDeep(variableAdapters.get(action.payload.type).initialState);
const model = cloneDeep(action.payload.data.model);
const variable = { const variable = {
...cloneDeep(variableAdapters.get(action.payload.type).initialState), ...lodashDefaults({}, model, initialState),
...action.payload.data.model,
id: id, id: id,
index: action.payload.data.index, index: action.payload.data.index,
global: action.payload.data.global, global: action.payload.data.global,
}; };
state[id] = variable; state[id] = variable;
}, },
addInitLock: (state: VariablesState, action: PayloadAction<VariablePayload>) => { addInitLock: (state: VariablesState, action: PayloadAction<VariablePayload>) => {