diff --git a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap index 9c4f04936c8..408c3d92222 100644 --- a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap +++ b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap @@ -79,7 +79,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 22, + "schemaVersion": 23, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -194,7 +194,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 22, + "schemaVersion": 23, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -289,7 +289,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 22, + "schemaVersion": 23, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -414,7 +414,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti ], "refresh": undefined, "revision": undefined, - "schemaVersion": 22, + "schemaVersion": 23, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -529,7 +529,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti ], "refresh": undefined, "revision": undefined, - "schemaVersion": 22, + "schemaVersion": 23, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -624,7 +624,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti ], "refresh": undefined, "revision": undefined, - "schemaVersion": 22, + "schemaVersion": 23, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -725,7 +725,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti ], "refresh": undefined, "revision": undefined, - "schemaVersion": 22, + "schemaVersion": 23, "snapshot": undefined, "style": "dark", "tags": Array [], diff --git a/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap b/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap index 14d160e80e1..5cccb2cdc66 100644 --- a/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap +++ b/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap @@ -241,7 +241,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 22, + "schemaVersion": 23, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -489,7 +489,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 22, + "schemaVersion": 23, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -737,7 +737,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 22, + "schemaVersion": 23, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -985,7 +985,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 22, + "schemaVersion": 23, "snapshot": undefined, "style": "dark", "tags": Array [], diff --git a/public/app/features/dashboard/state/DashboardMigrator.test.ts b/public/app/features/dashboard/state/DashboardMigrator.test.ts index 1e26c477ec1..7a5876a3f1b 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.test.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.test.ts @@ -128,7 +128,7 @@ describe('DashboardModel', () => { }); it('dashboard schema version should be set to latest', () => { - expect(model.schemaVersion).toBe(22); + expect(model.schemaVersion).toBe(23); }); it('graph thresholds should be migrated', () => { @@ -575,6 +575,53 @@ describe('DashboardModel', () => { }); }); }); + + describe('when migrating variables with multi support', () => { + let model: DashboardModel; + + beforeEach(() => { + model = new DashboardModel({ + templating: { + list: [ + { + multi: false, + current: { + value: ['value'], + text: ['text'], + }, + }, + { + multi: true, + current: { + value: ['value'], + text: ['text'], + }, + }, + ], + }, + }); + }); + + it('should have two variables after migration', () => { + expect(model.templating.list.length).toBe(2); + }); + + it('should be migrated if being out of sync', () => { + expect(model.templating.list[0].multi).toBe(false); + expect(model.templating.list[0].current).toEqual({ + text: 'text', + value: 'value', + }); + }); + + it('should not be migrated if being in sync', () => { + expect(model.templating.list[1].multi).toBe(true); + expect(model.templating.list[1].current).toEqual({ + text: ['text'], + value: ['value'], + }); + }); + }); }); function createRow(options: any, panelDescriptions: any[]) { diff --git a/public/app/features/dashboard/state/DashboardMigrator.ts b/public/app/features/dashboard/state/DashboardMigrator.ts index 0ac880e122a..282ae890073 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.ts @@ -18,6 +18,8 @@ import { MIN_PANEL_HEIGHT, } from 'app/core/constants'; import { DataLinkBuiltInVars } from '@grafana/ui'; +import { isMulti } from 'app/features/variables/guard'; +import { alignCurrentWithMulti } from 'app/features/variables/shared/multiOptions'; export class DashboardMigrator { dashboard: DashboardModel; @@ -30,7 +32,7 @@ export class DashboardMigrator { let i, j, k, n; const oldVersion = this.dashboard.schemaVersion; const panelUpgrades = []; - this.dashboard.schemaVersion = 22; + this.dashboard.schemaVersion = 23; if (oldVersion === this.dashboard.schemaVersion) { return; @@ -497,6 +499,16 @@ export class DashboardMigrator { }); } + if (oldVersion < 23) { + for (const variable of this.dashboard.templating.list) { + if (!isMulti(variable)) { + continue; + } + const { multi, current } = variable; + variable.current = alignCurrentWithMulti(current, multi); + } + } + if (panelUpgrades.length === 0) { return; } diff --git a/public/app/features/variables/custom/CustomVariableEditor.tsx b/public/app/features/variables/custom/CustomVariableEditor.tsx index d08f0589e92..d9eb27fd295 100644 --- a/public/app/features/variables/custom/CustomVariableEditor.tsx +++ b/public/app/features/variables/custom/CustomVariableEditor.tsx @@ -2,10 +2,22 @@ import React, { ChangeEvent, FocusEvent, PureComponent } from 'react'; import { CustomVariableModel, VariableWithMultiSupport } from '../../templating/types'; import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor'; import { OnPropChangeArguments, VariableEditorProps } from '../editor/types'; +import { connectWithStore } from 'app/core/utils/connectWithReduxStore'; +import { MapDispatchToProps, MapStateToProps } from 'react-redux'; +import { StoreState } from 'app/types'; +import { changeVariableMultiValue } from '../state/actions'; -export interface Props extends VariableEditorProps {} +interface OwnProps extends VariableEditorProps {} -export class CustomVariableEditor extends PureComponent { +interface ConnectedProps {} + +interface DispatchProps { + changeVariableMultiValue: typeof changeVariableMultiValue; +} + +export type Props = OwnProps & ConnectedProps & DispatchProps; + +class CustomVariableEditorUnconnected extends PureComponent { onChange = (event: ChangeEvent) => { this.props.onPropChange({ propName: 'query', @@ -44,8 +56,24 @@ export class CustomVariableEditor extends PureComponent { /> - + ); } } + +const mapStateToProps: MapStateToProps = (state, ownProps) => ({}); + +const mapDispatchToProps: MapDispatchToProps = { + changeVariableMultiValue, +}; + +export const CustomVariableEditor = connectWithStore( + CustomVariableEditorUnconnected, + mapStateToProps, + mapDispatchToProps +); diff --git a/public/app/features/variables/datasource/DataSourceVariableEditor.tsx b/public/app/features/variables/datasource/DataSourceVariableEditor.tsx index f24968f85ee..803d2456b4a 100644 --- a/public/app/features/variables/datasource/DataSourceVariableEditor.tsx +++ b/public/app/features/variables/datasource/DataSourceVariableEditor.tsx @@ -10,6 +10,7 @@ import { initDataSourceVariableEditor } from './actions'; import { MapDispatchToProps, MapStateToProps } from 'react-redux'; import { StoreState } from '../../../types'; import { connectWithStore } from '../../../core/utils/connectWithReduxStore'; +import { changeVariableMultiValue } from '../state/actions'; export interface OwnProps extends VariableEditorProps {} @@ -19,6 +20,7 @@ interface ConnectedProps { interface DispatchProps { initDataSourceVariableEditor: typeof initDataSourceVariableEditor; + changeVariableMultiValue: typeof changeVariableMultiValue; } type Props = OwnProps & ConnectedProps & DispatchProps; @@ -110,7 +112,11 @@ export class DataSourceVariableEditorUnConnected extends PureComponent { - + ); } @@ -122,6 +128,7 @@ const mapStateToProps: MapStateToProps = ( const mapDispatchToProps: MapDispatchToProps = { initDataSourceVariableEditor, + changeVariableMultiValue, }; export const DataSourceVariableEditor = connectWithStore( diff --git a/public/app/features/variables/datasource/reducer.ts b/public/app/features/variables/datasource/reducer.ts index 4b6a25d6afc..7d8045249bc 100644 --- a/public/app/features/variables/datasource/reducer.ts +++ b/public/app/features/variables/datasource/reducer.ts @@ -72,5 +72,4 @@ export const dataSourceVariableSlice = createSlice({ }); export const dataSourceVariableReducer = dataSourceVariableSlice.reducer; - export const { createDataSourceOptions } = dataSourceVariableSlice.actions; diff --git a/public/app/features/variables/editor/SelectionOptionsEditor.tsx b/public/app/features/variables/editor/SelectionOptionsEditor.tsx index 563fb452eac..adfe5cb2afc 100644 --- a/public/app/features/variables/editor/SelectionOptionsEditor.tsx +++ b/public/app/features/variables/editor/SelectionOptionsEditor.tsx @@ -4,16 +4,19 @@ import { e2e } from '@grafana/e2e'; import { VariableWithMultiSupport } from '../../templating/types'; import { VariableEditorProps } from './types'; +import { VariableIdentifier, toVariableIdentifier } from '../state/types'; export interface SelectionOptionsEditorProps - extends VariableEditorProps {} + extends VariableEditorProps { + onMultiChanged: (identifier: VariableIdentifier, value: boolean) => void; +} export const SelectionOptionsEditor: FunctionComponent = props => { const onMultiChanged = useCallback( (event: React.ChangeEvent) => { - props.onPropChange({ propName: 'multi', propValue: event.target.checked }); + props.onMultiChanged(toVariableIdentifier(props.variable), event.target.checked); }, - [props.onPropChange] + [props.onMultiChanged] ); const onIncludeAllChanged = useCallback( diff --git a/public/app/features/variables/guard.ts b/public/app/features/variables/guard.ts index 767b84e125a..abf3e45e681 100644 --- a/public/app/features/variables/guard.ts +++ b/public/app/features/variables/guard.ts @@ -1,4 +1,4 @@ -import { QueryVariableModel, VariableModel, AdHocVariableModel } from '../templating/types'; +import { QueryVariableModel, VariableModel, AdHocVariableModel, VariableWithMultiSupport } from '../templating/types'; export const isQuery = (model: VariableModel): model is QueryVariableModel => { return model.type === 'query'; @@ -7,3 +7,8 @@ export const isQuery = (model: VariableModel): model is QueryVariableModel => { export const isAdHoc = (model: VariableModel): model is AdHocVariableModel => { return model.type === 'adhoc'; }; + +export const isMulti = (model: VariableModel): model is VariableWithMultiSupport => { + const withMulti = model as VariableWithMultiSupport; + return withMulti.hasOwnProperty('multi') && typeof withMulti.multi === 'boolean'; +}; diff --git a/public/app/features/variables/query/QueryVariableEditor.tsx b/public/app/features/variables/query/QueryVariableEditor.tsx index 500abe86d0a..cc6261ead30 100644 --- a/public/app/features/variables/query/QueryVariableEditor.tsx +++ b/public/app/features/variables/query/QueryVariableEditor.tsx @@ -13,6 +13,7 @@ import { MapDispatchToProps, MapStateToProps } from 'react-redux'; import { StoreState } from '../../../types'; import { connectWithStore } from '../../../core/utils/connectWithReduxStore'; import { toVariableIdentifier } from '../state/types'; +import { changeVariableMultiValue } from '../state/actions'; export interface OwnProps extends VariableEditorProps {} @@ -24,6 +25,7 @@ interface DispatchProps { initQueryVariableEditor: typeof initQueryVariableEditor; changeQueryVariableDataSource: typeof changeQueryVariableDataSource; changeQueryVariableQuery: typeof changeQueryVariableQuery; + changeVariableMultiValue: typeof changeVariableMultiValue; } type Props = OwnProps & ConnectedProps & DispatchProps; @@ -237,7 +239,11 @@ export class QueryVariableEditorUnConnected extends PureComponent - +
Value groups/tags (Experimental feature)
@@ -300,6 +306,7 @@ const mapDispatchToProps: MapDispatchToProps = { initQueryVariableEditor, changeQueryVariableDataSource, changeQueryVariableQuery, + changeVariableMultiValue, }; export const QueryVariableEditor = connectWithStore( diff --git a/public/app/features/variables/shared/multiOptions.test.ts b/public/app/features/variables/shared/multiOptions.test.ts new file mode 100644 index 00000000000..9a9bb4bc60b --- /dev/null +++ b/public/app/features/variables/shared/multiOptions.test.ts @@ -0,0 +1,76 @@ +import { VariableOption } from 'app/features/templating/types'; +import { alignCurrentWithMulti } from './multiOptions'; + +describe('alignCurrentWithMulti', () => { + describe('when current has string array values and multi is false', () => { + it('should return current without string arrays', () => { + const current: VariableOption = { + value: ['A'], + text: ['A'], + selected: false, + }; + + const next = alignCurrentWithMulti(current, false); + + expect(next).toEqual({ + value: 'A', + text: 'A', + selected: false, + }); + }); + }); + + describe('when current has string values and multi is true', () => { + it('should return current with string arrays', () => { + const current: VariableOption = { + value: 'A', + text: 'A', + selected: false, + }; + + const next = alignCurrentWithMulti(current, true); + + expect(next).toEqual({ + value: ['A'], + text: ['A'], + selected: false, + }); + }); + }); + + describe('when current has string values and multi is false', () => { + it('should return current without string arrays', () => { + const current: VariableOption = { + value: 'A', + text: 'A', + selected: false, + }; + + const next = alignCurrentWithMulti(current, false); + + expect(next).toEqual({ + value: 'A', + text: 'A', + selected: false, + }); + }); + }); + + describe('when current has string array values and multi is true', () => { + it('should return current with string arrays', () => { + const current: VariableOption = { + value: ['A'], + text: ['A'], + selected: false, + }; + + const next = alignCurrentWithMulti(current, true); + + expect(next).toEqual({ + value: ['A'], + text: ['A'], + selected: false, + }); + }); + }); +}); diff --git a/public/app/features/variables/shared/multiOptions.ts b/public/app/features/variables/shared/multiOptions.ts new file mode 100644 index 00000000000..cd34e92ac69 --- /dev/null +++ b/public/app/features/variables/shared/multiOptions.ts @@ -0,0 +1,44 @@ +import { VariableOption } from 'app/features/templating/types'; + +export const alignCurrentWithMulti = (current: VariableOption, value: boolean): VariableOption => { + if (!current) { + return current; + } + + if (value && !Array.isArray(current.value)) { + return { + ...current, + value: convertToMulti(current.value), + text: convertToMulti(current.text), + }; + } + + if (!value && Array.isArray(current.value)) { + return { + ...current, + value: convertToSingle(current.value), + text: convertToSingle(current.text), + }; + } + + return current; +}; + +const convertToSingle = (value: string | string[]): string => { + if (!Array.isArray(value)) { + return value; + } + + if (value.length > 0) { + return value[0]; + } + + return ''; +}; + +const convertToMulti = (value: string | string[]): string[] => { + if (Array.isArray(value)) { + return value; + } + return [value]; +}; diff --git a/public/app/features/variables/state/actions.test.ts b/public/app/features/variables/state/actions.test.ts index 6c4ba6e095e..df1440553ce 100644 --- a/public/app/features/variables/state/actions.test.ts +++ b/public/app/features/variables/state/actions.test.ts @@ -9,7 +9,13 @@ import { createTextBoxVariableAdapter } from '../textbox/adapter'; import { createConstantVariableAdapter } from '../constant/adapter'; import { reduxTester } from '../../../../test/core/redux/reduxTester'; import { TemplatingState } from 'app/features/variables/state/reducers'; -import { initDashboardTemplating, processVariables, setOptionFromUrl, validateVariableSelectionState } from './actions'; +import { + initDashboardTemplating, + processVariables, + setOptionFromUrl, + validateVariableSelectionState, + changeVariableMultiValue, +} from './actions'; import { addInitLock, addVariable, @@ -17,6 +23,7 @@ import { removeVariable, resolveInitLock, setCurrentVariableValue, + changeVariableProp, } from './sharedReducer'; import { NEW_VARIABLE_ID, toVariableIdentifier, toVariablePayload } from './types'; import { @@ -136,17 +143,24 @@ describe('shared actions', () => { describe('when setOptionFromUrl is dispatched with a custom variable (no refresh property)', () => { it.each` - urlValue | expected - ${'B'} | ${['B']} - ${['B']} | ${['B']} - ${'X'} | ${['X']} - ${''} | ${['']} - ${['A', 'B']} | ${['A', 'B']} - ${null} | ${[null]} - ${undefined} | ${[undefined]} - `('and urlValue is $urlValue then correct actions are dispatched', async ({ urlValue, expected }) => { + urlValue | isMulti | expected + ${'B'} | ${false} | ${'B'} + ${['B']} | ${false} | ${'B'} + ${'X'} | ${false} | ${'X'} + ${''} | ${false} | ${''} + ${null} | ${false} | ${null} + ${undefined} | ${false} | ${undefined} + ${'B'} | ${true} | ${['B']} + ${['B']} | ${true} | ${['B']} + ${'X'} | ${true} | ${['X']} + ${''} | ${true} | ${['']} + ${['A', 'B']} | ${true} | ${['A', 'B']} + ${null} | ${true} | ${[null]} + ${undefined} | ${true} | ${[undefined]} + `('and urlValue is $urlValue then correct actions are dispatched', async ({ urlValue, expected, isMulti }) => { const custom = customBuilder() .withId('0') + .withMulti(isMulti) .withOptions('A', 'B', 'C') .withCurrent('A') .build(); @@ -439,4 +453,72 @@ describe('shared actions', () => { }); }); }); + + describe('changeVariableMultiValue', () => { + describe('when changeVariableMultiValue is dispatched for variable with multi enabled', () => { + it('then correct actions are dispatched', () => { + const custom = customBuilder() + .withId('custom') + .withMulti(true) + .withCurrent(['A'], ['A']) + .build(); + + reduxTester<{ templating: TemplatingState }>() + .givenRootReducer(getTemplatingRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(custom, { global: false, index: 0, model: custom }))) + .whenActionIsDispatched(changeVariableMultiValue(toVariableIdentifier(custom), false), true) + .thenDispatchedActionsShouldEqual( + changeVariableProp( + toVariablePayload(custom, { + propName: 'multi', + propValue: false, + }) + ), + changeVariableProp( + toVariablePayload(custom, { + propName: 'current', + propValue: { + value: 'A', + text: 'A', + selected: true, + }, + }) + ) + ); + }); + }); + + describe('when changeVariableMultiValue is dispatched for variable with multi disabled', () => { + it('then correct actions are dispatched', () => { + const custom = customBuilder() + .withId('custom') + .withMulti(false) + .withCurrent(['A'], ['A']) + .build(); + + reduxTester<{ templating: TemplatingState }>() + .givenRootReducer(getTemplatingRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(custom, { global: false, index: 0, model: custom }))) + .whenActionIsDispatched(changeVariableMultiValue(toVariableIdentifier(custom), true), true) + .thenDispatchedActionsShouldEqual( + changeVariableProp( + toVariablePayload(custom, { + propName: 'multi', + propValue: true, + }) + ), + changeVariableProp( + toVariablePayload(custom, { + propName: 'current', + propValue: { + value: ['A'], + text: ['A'], + selected: true, + }, + }) + ) + ); + }); + }); + }); }); diff --git a/public/app/features/variables/state/actions.ts b/public/app/features/variables/state/actions.ts index abad3d4fe8d..631f984f51e 100644 --- a/public/app/features/variables/state/actions.ts +++ b/public/app/features/variables/state/actions.ts @@ -9,16 +9,26 @@ import { VariableOption, VariableRefresh, VariableWithOptions, + VariableWithMultiSupport, } from '../../templating/types'; import { StoreState, ThunkResult } from '../../../types'; import { getVariable, getVariables } from './selectors'; import { variableAdapters } from '../adapters'; import { Graph } from '../../../core/utils/dag'; import { updateLocation } from 'app/core/actions'; -import { addInitLock, addVariable, removeInitLock, resolveInitLock, setCurrentVariableValue } from './sharedReducer'; +import { + addInitLock, + addVariable, + removeInitLock, + resolveInitLock, + setCurrentVariableValue, + changeVariableProp, +} from './sharedReducer'; import { toVariableIdentifier, toVariablePayload, VariableIdentifier } from './types'; import { appEvents } from '../../../core/core'; import templateSrv from '../../templating/template_srv'; +import { alignCurrentWithMulti } from '../shared/multiOptions'; +import { isMulti } from '../guard'; // process flow queryVariable // thunk => processVariables @@ -68,6 +78,16 @@ export const initDashboardTemplating = (list: VariableModel[]): ThunkResult => { + return (dispatch, getState) => { + const variable = getVariable(identifier.id!, getState()); + const current = alignCurrentWithMulti(variable.current, multi); + + dispatch(changeVariableProp(toVariablePayload(identifier, { propName: 'multi', propValue: multi }))); + dispatch(changeVariableProp(toVariablePayload(identifier, { propName: 'current', propValue: current }))); + }; +}; + export const processVariableDependencies = async (variable: VariableModel, state: StoreState) => { let dependencies: Array> = []; @@ -173,10 +193,13 @@ export const setOptionFromUrl = (identifier: VariableIdentifier, urlValue: UrlQu option = { text: defaultText, value: defaultValue, selected: false }; } - if (variableFromState.hasOwnProperty('multi')) { + if (isMulti(variableFromState)) { // In case variable is multiple choice, we cast to array to preserve the same behaviour as when selecting // the option directly, which will return even single value in an array. - option = { text: castArray(option.text), value: castArray(option.value), selected: false }; + option = alignCurrentWithMulti( + { text: castArray(option.text), value: castArray(option.value), selected: false }, + variableFromState.multi + ); } await variableAdapters.get(variable.type).setValue(variableFromState, option); diff --git a/public/app/features/variables/state/processVariable.test.ts b/public/app/features/variables/state/processVariable.test.ts index 186a77d4292..88ab43b2189 100644 --- a/public/app/features/variables/state/processVariable.test.ts +++ b/public/app/features/variables/state/processVariable.test.ts @@ -132,10 +132,7 @@ describe('processVariable', () => { await tester.thenDispatchedActionsShouldEqual( setCurrentVariableValue( - toVariablePayload( - { type: 'custom', id: 'custom' }, - { option: { text: ['B'], value: ['B'], selected: false } } - ) + toVariablePayload({ type: 'custom', id: 'custom' }, { option: { text: 'B', value: 'B', selected: false } }) ), resolveInitLock(toVariablePayload({ type: 'custom', id: 'custom' })) ); @@ -218,7 +215,7 @@ describe('processVariable', () => { setCurrentVariableValue( toVariablePayload( { type: 'query', id: 'queryNoDepends' }, - { option: { text: ['B'], value: ['B'], selected: false } } + { option: { text: 'B', value: 'B', selected: false } } ) ), resolveInitLock(toVariablePayload({ type: 'query', id: 'queryNoDepends' })) @@ -263,7 +260,7 @@ describe('processVariable', () => { setCurrentVariableValue( toVariablePayload( { type: 'query', id: 'queryNoDepends' }, - { option: { text: ['B'], value: ['B'], selected: false } } + { option: { text: 'B', value: 'B', selected: false } } ) ), resolveInitLock(toVariablePayload({ type: 'query', id: 'queryNoDepends' })) @@ -364,7 +361,7 @@ describe('processVariable', () => { setCurrentVariableValue( toVariablePayload( { type: 'query', id: 'queryDependsOnCustom' }, - { option: { text: ['AB'], value: ['AB'], selected: false } } + { option: { text: 'AB', value: 'AB', selected: false } } ) ), resolveInitLock(toVariablePayload({ type: 'query', id: 'queryDependsOnCustom' })) @@ -414,7 +411,7 @@ describe('processVariable', () => { setCurrentVariableValue( toVariablePayload( { type: 'query', id: 'queryDependsOnCustom' }, - { option: { text: ['AB'], value: ['AB'], selected: false } } + { option: { text: 'AB', value: 'AB', selected: false } } ) ), resolveInitLock(toVariablePayload({ type: 'query', id: 'queryDependsOnCustom' })) diff --git a/public/app/features/variables/state/sharedReducer.ts b/public/app/features/variables/state/sharedReducer.ts index ba42e965432..5baee615e17 100644 --- a/public/app/features/variables/state/sharedReducer.ts +++ b/public/app/features/variables/state/sharedReducer.ts @@ -15,13 +15,14 @@ const sharedReducerSlice = createSlice({ reducers: { addVariable: (state: VariablesState, action: PayloadAction>) => { const id = action.payload.id ?? action.payload.data.model.name; // for testing purposes we can call this with an id - state[id] = { + const variable = { ...cloneDeep(variableAdapters.get(action.payload.type).initialState), ...action.payload.data.model, + id: id, + index: action.payload.data.index, + global: action.payload.data.global, }; - state[id].id = id; - state[id].index = action.payload.data.index; - state[id].global = action.payload.data.global; + state[id] = variable; }, addInitLock: (state: VariablesState, action: PayloadAction) => { const instanceState = getInstanceState(state, action.payload.id!);