diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index dde91c87a1f..dc44658ef6c 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -71,7 +71,7 @@ export class GrafanaBootConfig { expressions: false, newEdit: false, meta: false, - newVariables: false, + newVariables: true, tracingIntegration: false, }; licenseInfo: LicenseInfo = {} as LicenseInfo; 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 408c3d92222..3b45648dca4 100644 --- a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap +++ b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap @@ -29,6 +29,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` }, }, "getVariables": [Function], + "getVariablesFromState": [Function], "gnetId": null, "graphTooltip": 0, "id": null, @@ -118,7 +119,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
- - ({})); @@ -31,7 +32,7 @@ describe('given dashboard with panel repeat', () => { ], }, }; - dashboard = new DashboardModel(dashboardJSON); + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(); }); @@ -59,7 +60,7 @@ describe('given dashboard with panel repeat in horizontal direction', () => { let dashboard: any; beforeEach(() => { - dashboard = new DashboardModel({ + const dashboardJSON = { panels: [ { id: 2, @@ -85,7 +86,8 @@ describe('given dashboard with panel repeat in horizontal direction', () => { }, ], }, - }); + }; + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(); }); @@ -191,7 +193,7 @@ describe('given dashboard with panel repeat in vertical direction', () => { let dashboard: any; beforeEach(() => { - dashboard = new DashboardModel({ + const dashboardJSON = { panels: [ { id: 1, type: 'row', gridPos: { x: 0, y: 0, h: 1, w: 24 } }, { id: 2, repeat: 'apps', repeatDirection: 'v', gridPos: { x: 5, y: 1, h: 2, w: 8 } }, @@ -214,7 +216,8 @@ describe('given dashboard with panel repeat in vertical direction', () => { }, ], }, - }); + }; + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(); }); @@ -230,7 +233,7 @@ describe('given dashboard with panel repeat in vertical direction', () => { }); describe('given dashboard with row repeat and panel repeat in horizontal direction', () => { - let dashboard: any, dashboardJSON; + let dashboard: any, dashboardJSON: any; beforeEach(() => { dashboardJSON = { @@ -269,7 +272,7 @@ describe('given dashboard with row repeat and panel repeat in horizontal directi ], }, }; - dashboard = new DashboardModel(dashboardJSON); + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(false); }); @@ -348,7 +351,7 @@ describe('given dashboard with row repeat', () => { ], }, }; - dashboard = new DashboardModel(dashboardJSON); + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(); }); @@ -359,7 +362,7 @@ describe('given dashboard with row repeat', () => { it('should set scopedVars for each panel', () => { dashboardJSON.templating.list[0].options[2].selected = true; - dashboard = new DashboardModel(dashboardJSON); + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(); expect(dashboard.panels[1].scopedVars).toMatchObject({ @@ -399,7 +402,7 @@ describe('given dashboard with row repeat', () => { { id: 4, type: 'row', gridPos: { x: 0, y: 1, h: 1, w: 24 } }, { id: 5, type: 'graph', gridPos: { x: 0, y: 2, h: 1, w: 12 } }, ]; - dashboard = new DashboardModel(dashboardJSON); + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(); const panelTypes = _.map(dashboard.panels, 'type'); @@ -441,7 +444,7 @@ describe('given dashboard with row repeat', () => { { text: 'backend03', value: 'backend03', selected: false }, ], }); - dashboard = new DashboardModel(dashboardJSON); + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(); const panelTypes = _.map(dashboard.panels, 'type'); @@ -488,7 +491,7 @@ describe('given dashboard with row repeat', () => { { id: 4, type: 'row', gridPos: { x: 0, y: 1, h: 1, w: 24 } }, { id: 5, type: 'graph', gridPos: { x: 0, y: 2, h: 1, w: 12 } }, ]; - dashboard = new DashboardModel(dashboardJSON); + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(); const panelIds = _.flattenDeep( @@ -511,7 +514,7 @@ describe('given dashboard with row repeat', () => { { id: 3, type: 'graph', gridPos: { x: 6, y: 1, h: 4, w: 12 } }, { id: 4, type: 'graph', gridPos: { x: 0, y: 5, h: 2, w: 12 } }, ]; - dashboard = new DashboardModel(dashboardJSON); + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(); const panelTypes = _.map(dashboard.panels, 'type'); @@ -564,7 +567,7 @@ describe('given dashboard with row and panel repeat', () => { ], }, }; - dashboard = new DashboardModel(dashboardJSON); + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(); }); @@ -592,7 +595,7 @@ describe('given dashboard with row and panel repeat', () => { }, { id: 12, type: 'graph', repeatPanelId: 2, repeatIteration: 101, gridPos: { x: 0, y: 3, h: 1, w: 6 } }, ]; - dashboard = new DashboardModel(dashboardJSON); + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(); const panelTypes = _.map(dashboard.panels, 'type'); @@ -600,7 +603,7 @@ describe('given dashboard with row and panel repeat', () => { }); it('should set scopedVars for each row', () => { - dashboard = new DashboardModel(dashboardJSON); + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(); expect(dashboard.panels[0].scopedVars).toMatchObject({ @@ -612,7 +615,7 @@ describe('given dashboard with row and panel repeat', () => { }); it('should set panel-repeat variable for each panel', () => { - dashboard = new DashboardModel(dashboardJSON); + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(); expect(dashboard.panels[1].scopedVars).toMatchObject({ @@ -631,7 +634,7 @@ describe('given dashboard with row and panel repeat', () => { }); it('should set row-repeat variable for each panel', () => { - dashboard = new DashboardModel(dashboardJSON); + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(); expect(dashboard.panels[1].scopedVars).toMatchObject({ @@ -650,7 +653,7 @@ describe('given dashboard with row and panel repeat', () => { }); it('should repeat panels when row is expanding', () => { - dashboard = new DashboardModel(dashboardJSON); + dashboard = getDashboardModel(dashboardJSON); dashboard.processRepeats(); expect(dashboard.panels.length).toBe(6); diff --git a/public/app/features/dashboard/state/DashboardModel.test.ts b/public/app/features/dashboard/state/DashboardModel.test.ts index 042c99923ab..c647d8cb350 100644 --- a/public/app/features/dashboard/state/DashboardModel.test.ts +++ b/public/app/features/dashboard/state/DashboardModel.test.ts @@ -1,8 +1,13 @@ import _ from 'lodash'; import { DashboardModel } from '../state/DashboardModel'; import { PanelModel } from '../state/PanelModel'; +import { getDashboardModel } from '../../../../test/helpers/getDashboardModel'; +import { variableAdapters } from '../../variables/adapters'; +import { createAdHocVariableAdapter } from '../../variables/adhoc/adapter'; +import { createQueryVariableAdapter } from '../../variables/query/adapter'; jest.mock('app/core/services/context_srv', () => ({})); +variableAdapters.setInit(() => [createQueryVariableAdapter(), createAdHocVariableAdapter()]); describe('DashboardModel', () => { describe('when creating new dashboard model defaults only', () => { @@ -498,7 +503,7 @@ describe('DashboardModel', () => { let model: DashboardModel; beforeEach(() => { - model = new DashboardModel({ + const json = { templating: { list: [ { @@ -512,7 +517,8 @@ describe('DashboardModel', () => { }, ], }, - }); + }; + model = getDashboardModel(json); expect(model.hasVariableValuesChanged()).toBeFalsy(); }); @@ -562,7 +568,7 @@ describe('DashboardModel', () => { let model: DashboardModel; beforeEach(() => { - model = new DashboardModel({ + const json = { templating: { list: [ { @@ -578,7 +584,8 @@ describe('DashboardModel', () => { }, ], }, - }); + }; + model = getDashboardModel(json); expect(model.hasVariableValuesChanged()).toBeFalsy(); }); diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index 6af16e6ee40..65703152229 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -14,7 +14,7 @@ import { AppEvent, dateTime, DateTimeInput, isDateTime, PanelEvents, TimeRange, import { UrlQueryValue } from '@grafana/runtime'; import { CoreEvents, DashboardMeta, KIOSK_MODE_TV } from 'app/types'; import { getConfig } from '../../../core/config'; -import { getVariables } from 'app/features/variables/state/selectors'; +import { GetVariables, getVariables } from 'app/features/variables/state/selectors'; import { variableAdapters } from 'app/features/variables/adapters'; import { onTimeRangeUpdated } from 'app/features/variables/state/actions'; import { dispatch } from '../../../store/store'; @@ -69,9 +69,10 @@ export class DashboardModel { originalTime: true, originalTemplating: true, panelInEdit: true, + getVariablesFromState: true, }; - constructor(data: any, meta?: DashboardMeta) { + constructor(data: any, meta?: DashboardMeta, private getVariablesFromState: GetVariables = getVariables) { if (!data) { data = {}; } @@ -238,7 +239,7 @@ export class DashboardModel { defaults: { saveTimerange: boolean; saveVariables: boolean } & CloneOptions ) { const originalVariables = this.originalTemplating; - const currentVariables = getVariables(); + const currentVariables = this.getVariablesFromState(); copy.templating = { list: currentVariables.map(variable => variableAdapters.get(variable.type).getSaveModel(variable)), @@ -940,12 +941,12 @@ export class DashboardModel { return; } - this.originalTemplating = this.cloneVariablesFrom(getVariables()); + this.originalTemplating = this.cloneVariablesFrom(this.getVariablesFromState()); } hasVariableValuesChanged() { if (getConfig().featureToggles.newVariables) { - return this.hasVariablesChanged(this.originalTemplating, getVariables()); + return this.hasVariablesChanged(this.originalTemplating, this.getVariablesFromState()); } return this.hasVariablesChanged(this.originalTemplating, this.templating.list); @@ -1019,7 +1020,7 @@ export class DashboardModel { getVariables = () => { if (getConfig().featureToggles.newVariables) { - return getVariables(); + return this.getVariablesFromState(); } return this.templating.list; }; @@ -1029,7 +1030,7 @@ export class DashboardModel { return _.find(this.templating.list, { name: panel.repeat } as any); } - return getVariables().find(variable => variable.name === panel.repeat); + return this.getVariablesFromState().find(variable => variable.name === panel.repeat); } private isSnapshotTruthy() { @@ -1038,7 +1039,7 @@ export class DashboardModel { private hasVariables() { if (getConfig().featureToggles.newVariables) { - return getVariables().length > 0; + return this.getVariablesFromState().length > 0; } return this.templating.list.length > 0; } diff --git a/public/app/features/dashboard/state/initDashboard.test.ts b/public/app/features/dashboard/state/initDashboard.test.ts index adb0f395ec4..6cce0b21662 100644 --- a/public/app/features/dashboard/state/initDashboard.test.ts +++ b/public/app/features/dashboard/state/initDashboard.test.ts @@ -7,9 +7,30 @@ import { dashboardInitCompleted, dashboardInitFetching, dashboardInitServices } import { updateLocation } from '../../../core/actions'; import { setEchoSrv } from '@grafana/runtime'; import { Echo } from '../../../core/services/echo/Echo'; +import { getConfig } from 'app/core/config'; +import { variableAdapters } from 'app/features/variables/adapters'; +import { createConstantVariableAdapter } from 'app/features/variables/constant/adapter'; +import { addVariable } from 'app/features/variables/state/sharedReducer'; +import { constantBuilder } from 'app/features/variables/shared/testing/builders'; jest.mock('app/core/services/backend_srv'); +jest.mock('app/features/dashboard/services/TimeSrv', () => { + const original = jest.requireActual('app/features/dashboard/services/TimeSrv'); + return { + ...original, + getTimeSrv: () => ({ + ...original.getTimeSrv(), + timeRange: jest.fn().mockReturnValue(undefined), + }), + }; +}); +jest.mock('app/core/services/context_srv', () => ({ + contextSrv: { + user: { orgId: 1, orgName: 'TestOrg' }, + }, +})); +variableAdapters.register(createConstantVariableAdapter()); const mockStore = configureMockStore([thunk]); interface ScenarioContext { @@ -61,6 +82,9 @@ function describeInitScenario(description: string, scenarioFn: ScenarioFn) { ], }, ], + templating: { + list: [constantBuilder().build()], + }, }, })), }; @@ -118,6 +142,9 @@ function describeInitScenario(description: string, scenarioFn: ScenarioFn) { queries: [], }, }, + templating: { + variables: {}, + }, }, setup: (fn: () => void) => { setupFn = fn; @@ -166,11 +193,17 @@ describeInitScenario('Initializing new dashboard', ctx => { it('Should initialize services', () => { expect(ctx.timeSrv.init).toBeCalled(); expect(ctx.annotationsSrv.init).toBeCalled(); - expect(ctx.variableSrv.init).toBeCalled(); expect(ctx.unsavedChangesSrv.init).toBeCalled(); expect(ctx.keybindingSrv.setupDashboardBindings).toBeCalled(); expect(ctx.dashboardSrv.setCurrent).toBeCalled(); }); + + it('Should initialize variableSrv if newVariables is disabled', () => { + if (getConfig().featureToggles.newVariables) { + return expect.assertions(0); + } + expect(ctx.variableSrv.init).toBeCalled(); + }); }); describeInitScenario('Initializing home dashboard', ctx => { @@ -224,16 +257,30 @@ describeInitScenario('Initializing existing dashboard', ctx => { }); it('Should send action dashboardInitCompleted', () => { - expect(ctx.actions[3].type).toBe(dashboardInitCompleted.type); - expect(ctx.actions[3].payload.title).toBe('My cool dashboard'); + const index = getConfig().featureToggles.newVariables ? 4 : 3; + expect(ctx.actions[index].type).toBe(dashboardInitCompleted.type); + expect(ctx.actions[index].payload.title).toBe('My cool dashboard'); }); it('Should initialize services', () => { expect(ctx.timeSrv.init).toBeCalled(); expect(ctx.annotationsSrv.init).toBeCalled(); - expect(ctx.variableSrv.init).toBeCalled(); expect(ctx.unsavedChangesSrv.init).toBeCalled(); expect(ctx.keybindingSrv.setupDashboardBindings).toBeCalled(); expect(ctx.dashboardSrv.setCurrent).toBeCalled(); }); + + it('Should initialize variableSrv if newVariables is disabled', () => { + if (getConfig().featureToggles.newVariables) { + return expect.assertions(0); + } + expect(ctx.variableSrv.init).toBeCalled(); + }); + + it('Should initialize redux variables if newVariables is enabled', () => { + if (!getConfig().featureToggles.newVariables) { + return expect.assertions(0); + } + expect(ctx.actions[3].type).toBe(addVariable.type); + }); }); diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts index 9909002795e..9dc010a2cca 100644 --- a/public/app/features/dashboard/state/initDashboard.ts +++ b/public/app/features/dashboard/state/initDashboard.ts @@ -23,7 +23,7 @@ import { DashboardDTO, DashboardRouteInfo, StoreState, ThunkDispatch, ThunkResul import { DashboardModel } from './DashboardModel'; import { DataQuery } from '@grafana/data'; import { getConfig } from '../../../core/config'; -import { initDashboardTemplating, processVariables } from '../../variables/state/actions'; +import { initDashboardTemplating, processVariables, completeDashboardTemplating } from '../../variables/state/actions'; import { emitDashboardViewEvent } from './analyticsProcessor'; export interface InitDashboardArgs { @@ -185,8 +185,9 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult { await variableSrv.init(dashboard); } if (getConfig().featureToggles.newVariables) { - await dispatch(initDashboardTemplating(dashboard.templating.list)); + dispatch(initDashboardTemplating(dashboard.templating.list)); await dispatch(processVariables()); + dispatch(completeDashboardTemplating(dashboard)); } } catch (err) { dispatch(notifyApp(createErrorNotification('Templating init failed', err))); diff --git a/public/app/features/templating/specs/template_srv.test.ts b/public/app/features/templating/specs/template_srv.test.ts index f3fb61873f6..635c97b5443 100644 --- a/public/app/features/templating/specs/template_srv.test.ts +++ b/public/app/features/templating/specs/template_srv.test.ts @@ -1,10 +1,14 @@ import { TemplateSrv } from '../template_srv'; +import { convertToStoreState } from 'test/helpers/convertToStoreState'; +import { getTemplateSrvDependencies } from '../../../../test/helpers/getTemplateSrvDependencies'; describe('templateSrv', () => { let _templateSrv: any; - function initTemplateSrv(variables: any) { - _templateSrv = new TemplateSrv(); + function initTemplateSrv(variables: any[]) { + const state = convertToStoreState(variables); + + _templateSrv = new TemplateSrv(getTemplateSrvDependencies(state)); _templateSrv.init(variables); } diff --git a/public/app/features/templating/template_srv.ts b/public/app/features/templating/template_srv.ts index 22db8b38ddd..7d6d7402604 100644 --- a/public/app/features/templating/template_srv.ts +++ b/public/app/features/templating/template_srv.ts @@ -16,6 +16,18 @@ interface FieldAccessorCache { [key: string]: (obj: any) => any; } +export interface TemplateSrvDependencies { + getFilteredVariables: typeof getFilteredVariables; + getVariables: typeof getVariables; + getVariableWithName: typeof getVariableWithName; +} + +const runtimeDependencies: TemplateSrvDependencies = { + getFilteredVariables, + getVariables, + getVariableWithName, +}; + export class TemplateSrv { private _variables: any[]; private regex = variableRegex; @@ -25,7 +37,7 @@ export class TemplateSrv { private timeRange?: TimeRange | null = null; private fieldAccessorCache: FieldAccessorCache = {}; - constructor() { + constructor(private dependencies: TemplateSrvDependencies = runtimeDependencies) { this.builtIns['__interval'] = { text: '1s', value: '1s' }; this.builtIns['__interval_ms'] = { text: '100', value: '100' }; this._variables = []; @@ -53,7 +65,7 @@ export class TemplateSrv { getVariables(): VariableModel[] { if (getConfig().featureToggles.newVariables) { - return getVariables(); + return this.dependencies.getVariables(); } return this._variables; @@ -419,7 +431,7 @@ export class TemplateSrv { } if (getConfig().featureToggles.newVariables && !this.index[name]) { - return getVariableWithName(name); + return this.dependencies.getVariableWithName(name); } return this.index[name]; @@ -427,7 +439,7 @@ export class TemplateSrv { private getAdHocVariables = (): any[] => { if (getConfig().featureToggles.newVariables) { - return getFilteredVariables(isAdHoc); + return this.dependencies.getFilteredVariables(isAdHoc); } if (Array.isArray(this._variables)) { return this._variables.filter(isAdHoc); diff --git a/public/app/features/variables/constant/actions.test.ts b/public/app/features/variables/constant/actions.test.ts index 9df353157ce..75af63c31d3 100644 --- a/public/app/features/variables/constant/actions.test.ts +++ b/public/app/features/variables/constant/actions.test.ts @@ -3,12 +3,11 @@ import { createConstantVariableAdapter } from './adapter'; import { reduxTester } from '../../../../test/core/redux/reduxTester'; import { TemplatingState } from 'app/features/variables/state/reducers'; import { updateConstantVariableOptions } from './actions'; -import { getTemplatingRootReducer } from '../state/helpers'; +import { getRootReducer } from '../state/helpers'; import { ConstantVariableModel, VariableHide, VariableOption } from '../../templating/types'; import { toVariablePayload } from '../state/types'; import { createConstantOptionsFromQuery } from './reducer'; -import { setCurrentVariableValue } from '../state/sharedReducer'; -import { initDashboardTemplating } from '../state/actions'; +import { setCurrentVariableValue, addVariable } from '../state/sharedReducer'; describe('constant actions', () => { variableAdapters.setInit(() => [createConstantVariableAdapter()]); @@ -40,8 +39,8 @@ describe('constant actions', () => { }; const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(updateConstantVariableOptions(toVariablePayload(variable)), true); tester.thenDispatchedActionsPredicateShouldEqual(actions => { diff --git a/public/app/features/variables/custom/actions.test.ts b/public/app/features/variables/custom/actions.test.ts index 78ce61214e4..47363b4b9df 100644 --- a/public/app/features/variables/custom/actions.test.ts +++ b/public/app/features/variables/custom/actions.test.ts @@ -2,11 +2,10 @@ import { variableAdapters } from '../adapters'; import { updateCustomVariableOptions } from './actions'; import { createCustomVariableAdapter } from './adapter'; import { reduxTester } from '../../../../test/core/redux/reduxTester'; -import { getTemplatingRootReducer } from '../state/helpers'; +import { getRootReducer } from '../state/helpers'; import { CustomVariableModel, VariableHide, VariableOption } from '../../templating/types'; import { toVariablePayload } from '../state/types'; -import { setCurrentVariableValue } from '../state/sharedReducer'; -import { initDashboardTemplating } from '../state/actions'; +import { setCurrentVariableValue, addVariable } from '../state/sharedReducer'; import { TemplatingState } from '../state/reducers'; import { createCustomOptionsFromQuery } from './reducer'; @@ -53,8 +52,8 @@ describe('custom actions', () => { }; const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(updateCustomVariableOptions(toVariablePayload(variable)), true); tester.thenDispatchedActionsPredicateShouldEqual(actions => { diff --git a/public/app/features/variables/datasource/actions.test.ts b/public/app/features/variables/datasource/actions.test.ts index f7e68ec24f8..f652545aefe 100644 --- a/public/app/features/variables/datasource/actions.test.ts +++ b/public/app/features/variables/datasource/actions.test.ts @@ -1,7 +1,6 @@ import { reduxTester } from '../../../../test/core/redux/reduxTester'; import { TemplatingState } from '../state/reducers'; -import { getTemplatingRootReducer } from '../state/helpers'; -import { initDashboardTemplating } from '../state/actions'; +import { getRootReducer } from '../state/helpers'; import { toVariableIdentifier, toVariablePayload } from '../state/types'; import { variableAdapters } from '../adapters'; import { createDataSourceVariableAdapter } from './adapter'; @@ -13,7 +12,7 @@ import { import { DataSourcePluginMeta, DataSourceSelectItem } from '@grafana/data'; import { getMockPlugin } from '../../plugins/__mocks__/pluginMocks'; import { createDataSourceOptions } from './reducer'; -import { setCurrentVariableValue } from '../state/sharedReducer'; +import { setCurrentVariableValue, addVariable } from '../state/sharedReducer'; import { changeVariableEditorExtended } from '../editor/reducer'; import { datasourceBuilder } from '../shared/testing/builders'; @@ -47,8 +46,10 @@ describe('data source actions', () => { .build(); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([datasource])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched( + addVariable(toVariablePayload(datasource, { global: false, index: 0, model: datasource })) + ) .whenAsyncActionIsDispatched( updateDataSourceVariableOptions(toVariableIdentifier(datasource), dependencies), true @@ -98,8 +99,10 @@ describe('data source actions', () => { .withRegEx('/.*(second-name).*/') .build(); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([datasource])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched( + addVariable(toVariablePayload(datasource, { global: false, index: 0, model: datasource })) + ) .whenAsyncActionIsDispatched( updateDataSourceVariableOptions(toVariableIdentifier(datasource), dependencies), true @@ -156,7 +159,7 @@ describe('data source actions', () => { const dependencies: DataSourceVariableActionDependencies = { getDatasourceSrv: getDatasourceSrvMock }; const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) + .givenRootReducer(getRootReducer()) .whenAsyncActionIsDispatched(initDataSourceVariableEditor(dependencies)); await tester.thenDispatchedActionsShouldEqual( diff --git a/public/app/features/variables/interval/actions.test.ts b/public/app/features/variables/interval/actions.test.ts index c47d391f213..356c980c33e 100644 --- a/public/app/features/variables/interval/actions.test.ts +++ b/public/app/features/variables/interval/actions.test.ts @@ -1,8 +1,7 @@ -import { getTemplatingRootReducer } from '../state/helpers'; +import { getRootReducer } from '../state/helpers'; import { reduxTester } from '../../../../test/core/redux/reduxTester'; import { TemplatingState } from '../state/reducers'; -import { initDashboardTemplating } from '../state/actions'; -import { toVariableIdentifier } from '../state/types'; +import { toVariableIdentifier, toVariablePayload } from '../state/types'; import { updateAutoValue, UpdateAutoValueDependencies, @@ -10,7 +9,7 @@ import { UpdateIntervalVariableOptionsDependencies, } from './actions'; import { createIntervalOptions } from './reducer'; -import { setCurrentVariableValue } from '../state/sharedReducer'; +import { setCurrentVariableValue, addVariable } from '../state/sharedReducer'; import { variableAdapters } from '../adapters'; import { createIntervalVariableAdapter } from './adapter'; import { Emitter } from 'app/core/core'; @@ -31,8 +30,8 @@ describe('interval actions', () => { .build(); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([interval])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(interval, { global: false, index: 0, model: interval }))) .whenAsyncActionIsDispatched(updateIntervalVariableOptions(toVariableIdentifier(interval)), true); tester.thenDispatchedActionsShouldEqual( @@ -74,8 +73,8 @@ describe('interval actions', () => { const dependencies: UpdateIntervalVariableOptionsDependencies = { appEvents: appEventMock }; await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([interval])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(interval, { global: false, index: 0, model: interval }))) .whenAsyncActionIsDispatched(updateIntervalVariableOptions(toVariableIdentifier(interval), dependencies), true); expect(appEventMock.emit).toHaveBeenCalledTimes(1); @@ -119,8 +118,10 @@ describe('interval actions', () => { }; await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([interval])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched( + addVariable(toVariablePayload(interval, { global: false, index: 0, model: interval })) + ) .whenAsyncActionIsDispatched(updateAutoValue(toVariableIdentifier(interval), dependencies), true); expect(dependencies.kbn.calculateInterval).toHaveBeenCalledTimes(0); @@ -163,8 +164,10 @@ describe('interval actions', () => { }; await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([interval])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched( + addVariable(toVariablePayload(interval, { global: false, index: 0, model: interval })) + ) .whenAsyncActionIsDispatched(updateAutoValue(toVariableIdentifier(interval), dependencies), true); expect(dependencies.kbn.calculateInterval).toHaveBeenCalledTimes(1); diff --git a/public/app/features/variables/pickers/OptionsPicker/actions.test.ts b/public/app/features/variables/pickers/OptionsPicker/actions.test.ts index 4d15d8cda8f..2f39de386e3 100644 --- a/public/app/features/variables/pickers/OptionsPicker/actions.test.ts +++ b/public/app/features/variables/pickers/OptionsPicker/actions.test.ts @@ -1,6 +1,5 @@ import { reduxTester } from '../../../../../test/core/redux/reduxTester'; -import { getTemplatingRootReducer } from '../../state/helpers'; -import { initDashboardTemplating } from '../../state/actions'; +import { getRootReducer } from '../../state/helpers'; import { TemplatingState } from '../../state/reducers'; import { QueryVariableModel, VariableHide, VariableRefresh, VariableSort } from '../../../templating/types'; import { @@ -20,9 +19,10 @@ import { } from './actions'; import { NavigationKey } from '../types'; import { toVariablePayload } from '../../state/types'; -import { changeVariableProp, setCurrentVariableValue } from '../../state/sharedReducer'; +import { changeVariableProp, setCurrentVariableValue, addVariable } from '../../state/sharedReducer'; import { variableAdapters } from '../../adapters'; import { createQueryVariableAdapter } from '../../query/adapter'; +import { updateLocation } from 'app/core/actions'; const datasource = { metricFindQuery: jest.fn(() => Promise.resolve([])), @@ -50,8 +50,8 @@ describe('options picker actions', () => { const key = NavigationKey.cancel; const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenActionIsDispatched(showOptions(variable)) .whenAsyncActionIsDispatched(navigateOptions(key, clearOthers), true); @@ -63,14 +63,15 @@ describe('options picker actions', () => { }; tester.thenDispatchedActionsPredicateShouldEqual(actions => { - const [setCurrentValue, changeQueryValue, updateOption, hideAction] = actions; - const expectedNumberOfActions = 4; + const [setCurrentValue, changeQueryValue, updateOption, locationAction, hideAction] = actions; + const expectedNumberOfActions = 5; expect(setCurrentValue).toEqual(setCurrentVariableValue(toVariablePayload(variable, { option }))); expect(changeQueryValue).toEqual( changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' })) ); expect(updateOption).toEqual(setCurrentVariableValue(toVariablePayload(variable, { option }))); + expect(locationAction).toEqual(updateLocation({ query: { 'var-Constant': ['A'] } })); expect(hideAction).toEqual(hideOptions()); return actions.length === expectedNumberOfActions; @@ -87,8 +88,8 @@ describe('options picker actions', () => { const key = NavigationKey.select; const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenActionIsDispatched(showOptions(variable)) .whenActionIsDispatched(navigateOptions(NavigationKey.moveDown, false)) .whenAsyncActionIsDispatched(navigateOptions(key, clearOthers), true); @@ -112,8 +113,8 @@ describe('options picker actions', () => { const key = NavigationKey.select; const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenActionIsDispatched(showOptions(variable)) .whenActionIsDispatched(navigateOptions(NavigationKey.moveDown, clearOthers)) .whenAsyncActionIsDispatched(navigateOptions(key, clearOthers), true); @@ -137,8 +138,8 @@ describe('options picker actions', () => { const key = NavigationKey.select; const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenActionIsDispatched(showOptions(variable)) .whenActionIsDispatched(navigateOptions(NavigationKey.moveDown, clearOthers)) .whenActionIsDispatched(navigateOptions(NavigationKey.moveDown, clearOthers)) @@ -164,8 +165,8 @@ describe('options picker actions', () => { const key = NavigationKey.select; const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenActionIsDispatched(showOptions(variable)) .whenActionIsDispatched(navigateOptions(NavigationKey.moveDown, clearOthers)) .whenActionIsDispatched(navigateOptions(NavigationKey.moveDown, clearOthers)) @@ -192,8 +193,8 @@ describe('options picker actions', () => { const key = NavigationKey.selectAndClose; const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenActionIsDispatched(showOptions(variable)) .whenActionIsDispatched(navigateOptions(NavigationKey.moveDown, clearOthers)) .whenActionIsDispatched(navigateOptions(NavigationKey.moveDown, clearOthers)) @@ -209,8 +210,15 @@ describe('options picker actions', () => { }; tester.thenDispatchedActionsPredicateShouldEqual(actions => { - const [toggleOptionAction, setCurrentValue, changeQueryValue, updateOption, hideAction] = actions; - const expectedNumberOfActions = 5; + const [ + toggleOptionAction, + setCurrentValue, + changeQueryValue, + updateOption, + locationAction, + hideAction, + ] = actions; + const expectedNumberOfActions = 6; expect(toggleOptionAction).toEqual(toggleOption({ option: options[1], forceSelect: false, clearOthers })); expect(setCurrentValue).toEqual(setCurrentVariableValue(toVariablePayload(variable, { option }))); @@ -218,6 +226,7 @@ describe('options picker actions', () => { changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' })) ); expect(updateOption).toEqual(setCurrentVariableValue(toVariablePayload(variable, { option }))); + expect(locationAction).toEqual(updateLocation({ query: { 'var-Constant': ['B'] } })); expect(hideAction).toEqual(hideOptions()); return actions.length === expectedNumberOfActions; @@ -232,8 +241,8 @@ describe('options picker actions', () => { const filter = 'A'; const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenActionIsDispatched(showOptions(variable)) .whenAsyncActionIsDispatched(filterOrSearchOptions(filter), true); @@ -255,8 +264,8 @@ describe('options picker actions', () => { const variable = createVariable({ options, includeAll: false }); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenActionIsDispatched(showOptions(variable)) .whenAsyncActionIsDispatched(commitChangesToVariable(), true); @@ -289,8 +298,8 @@ describe('options picker actions', () => { const clearOthers = false; const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenActionIsDispatched(showOptions(variable)) .whenActionIsDispatched(navigateOptions(NavigationKey.moveDown, clearOthers)) .whenActionIsDispatched(toggleOptionByHighlight(clearOthers)) @@ -304,14 +313,15 @@ describe('options picker actions', () => { }; tester.thenDispatchedActionsPredicateShouldEqual(actions => { - const [setCurrentValue, changeQueryValue, updateOption, hideAction] = actions; - const expectedNumberOfActions = 4; + const [setCurrentValue, changeQueryValue, updateOption, locationAction, hideAction] = actions; + const expectedNumberOfActions = 5; expect(setCurrentValue).toEqual(setCurrentVariableValue(toVariablePayload(variable, { option }))); expect(changeQueryValue).toEqual( changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' })) ); expect(updateOption).toEqual(setCurrentVariableValue(toVariablePayload(variable, { option }))); + expect(locationAction).toEqual(updateLocation({ query: { 'var-Constant': ['A'] } })); expect(hideAction).toEqual(hideOptions()); return actions.length === expectedNumberOfActions; @@ -326,8 +336,8 @@ describe('options picker actions', () => { const clearOthers = false; const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenActionIsDispatched(showOptions(variable)) .whenActionIsDispatched(navigateOptions(NavigationKey.moveDown, clearOthers)) .whenActionIsDispatched(toggleOptionByHighlight(clearOthers), true); @@ -351,8 +361,8 @@ describe('options picker actions', () => { const variable = createVariable({ options, includeAll: false, tags: [tag] }); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenActionIsDispatched(showOptions(variable)) .whenAsyncActionIsDispatched(toggleAndFetchTag(tag), true); @@ -378,8 +388,8 @@ describe('options picker actions', () => { datasource.metricFindQuery.mockImplementation(() => Promise.resolve(values)); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenActionIsDispatched(showOptions(variable)) .whenAsyncActionIsDispatched(toggleAndFetchTag(tag), true); diff --git a/public/app/features/variables/query/actions.test.ts b/public/app/features/variables/query/actions.test.ts index 26748a1dc3f..e585e23b385 100644 --- a/public/app/features/variables/query/actions.test.ts +++ b/public/app/features/variables/query/actions.test.ts @@ -1,11 +1,10 @@ import { variableAdapters } from '../adapters'; import { createQueryVariableAdapter } from './adapter'; import { reduxTester } from '../../../../test/core/redux/reduxTester'; -import { getTemplatingRootReducer } from '../state/helpers'; +import { getRootReducer } from '../state/helpers'; import { QueryVariableModel, VariableHide, VariableRefresh, VariableSort } from '../../templating/types'; import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE, toVariablePayload } from '../state/types'; -import { changeVariableProp, setCurrentVariableValue } from '../state/sharedReducer'; -import { initDashboardTemplating } from '../state/actions'; +import { changeVariableProp, setCurrentVariableValue, addVariable } from '../state/sharedReducer'; import { TemplatingState } from '../state/reducers'; import { changeQueryVariableDataSource, @@ -62,8 +61,8 @@ describe('query actions', () => { mockDatasourceMetrics(variable, optionsMetrics, tagsMetrics); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(updateQueryVariableOptions(toVariablePayload(variable)), true); const option = createOption(ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE); @@ -90,8 +89,8 @@ describe('query actions', () => { mockDatasourceMetrics(variable, optionsMetrics, tagsMetrics); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(updateQueryVariableOptions(toVariablePayload(variable)), true); const option = createOption('A'); @@ -117,8 +116,8 @@ describe('query actions', () => { mockDatasourceMetrics(variable, optionsMetrics, []); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(updateQueryVariableOptions(toVariablePayload(variable)), true); const option = createOption('A'); @@ -143,8 +142,8 @@ describe('query actions', () => { mockDatasourceMetrics(variable, optionsMetrics, []); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(updateQueryVariableOptions(toVariablePayload(variable)), true); const option = createOption(ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE); @@ -169,8 +168,8 @@ describe('query actions', () => { mockDatasourceMetrics(variable, optionsMetrics, []); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenActionIsDispatched(setIdInEditor({ id: variable.id! })) .whenAsyncActionIsDispatched(updateQueryVariableOptions(toVariablePayload(variable)), true); @@ -197,8 +196,8 @@ describe('query actions', () => { mocks[variable.datasource!].metricFindQuery = jest.fn(() => Promise.reject(error)); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenActionIsDispatched(setIdInEditor({ id: variable.id! })) .whenAsyncActionIsDispatched(updateQueryVariableOptions(toVariablePayload(variable)), true); @@ -226,8 +225,8 @@ describe('query actions', () => { }); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(initQueryVariableEditor(toVariablePayload(variable)), true); tester.thenDispatchedActionsPredicateShouldEqual(actions => { @@ -259,8 +258,8 @@ describe('query actions', () => { }); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(initQueryVariableEditor(toVariablePayload(variable)), true); tester.thenDispatchedActionsPredicateShouldEqual(actions => { @@ -291,8 +290,8 @@ describe('query actions', () => { }); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(initQueryVariableEditor(toVariablePayload(variable)), true); tester.thenDispatchedActionsPredicateShouldEqual(actions => { @@ -317,8 +316,8 @@ describe('query actions', () => { const ds = { name: '', value: '', meta: {}, sort: '' }; const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(initQueryVariableEditor(toVariablePayload(variable)), true); tester.thenDispatchedActionsPredicateShouldEqual(actions => { @@ -341,8 +340,8 @@ describe('query actions', () => { }); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(changeQueryVariableDataSource(toVariablePayload(variable), 'datasource'), true); tester.thenDispatchedActionsPredicateShouldEqual(actions => { @@ -371,8 +370,8 @@ describe('query actions', () => { }); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(changeQueryVariableDataSource(toVariablePayload(variable), 'datasource'), true); tester.thenDispatchedActionsPredicateShouldEqual(actions => { @@ -403,8 +402,8 @@ describe('query actions', () => { mockDatasourceMetrics({ ...variable, query }, optionsMetrics, tagsMetrics); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(changeQueryVariableQuery(toVariablePayload(variable), query, definition), true); const option = createOption(ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE); @@ -441,8 +440,8 @@ describe('query actions', () => { mockDatasourceMetrics({ ...variable, query }, optionsMetrics, []); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(changeQueryVariableQuery(toVariablePayload(variable), query, definition), true); const option = createOption(ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE); @@ -477,8 +476,8 @@ describe('query actions', () => { mockDatasourceMetrics({ ...variable, query }, optionsMetrics, []); const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(changeQueryVariableQuery(toVariablePayload(variable), query, definition), true); const option = createOption('A'); @@ -510,8 +509,8 @@ describe('query actions', () => { const definition = 'depends on datasource variable'; const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(changeQueryVariableQuery(toVariablePayload(variable), query, definition), true); const errorText = 'Query cannot contain a reference to itself. Variable: $' + variable.name; diff --git a/public/app/features/variables/state/actions.test.ts b/public/app/features/variables/state/actions.test.ts index df1440553ce..27e22d4db5e 100644 --- a/public/app/features/variables/state/actions.test.ts +++ b/public/app/features/variables/state/actions.test.ts @@ -43,6 +43,12 @@ variableAdapters.setInit(() => [ createConstantVariableAdapter(), ]); +jest.mock('app/features/dashboard/services/TimeSrv', () => ({ + getTimeSrv: () => ({ + timeRange: jest.fn().mockReturnValue(undefined), + }), +})); + describe('shared actions', () => { describe('when initDashboardTemplating is dispatched', () => { it('then correct actions are dispatched', () => { diff --git a/public/app/features/variables/state/actions.ts b/public/app/features/variables/state/actions.ts index 631f984f51e..5fe5c51dd01 100644 --- a/public/app/features/variables/state/actions.ts +++ b/public/app/features/variables/state/actions.ts @@ -25,10 +25,13 @@ import { changeVariableProp, } from './sharedReducer'; import { toVariableIdentifier, toVariablePayload, VariableIdentifier } from './types'; -import { appEvents } from '../../../core/core'; +import { appEvents } from 'app/core/core'; +import { contextSrv } from 'app/core/services/context_srv'; import templateSrv from '../../templating/template_srv'; import { alignCurrentWithMulti } from '../shared/multiOptions'; import { isMulti } from '../guard'; +import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { DashboardModel } from 'app/features/dashboard/state'; // process flow queryVariable // thunk => processVariables @@ -72,12 +75,37 @@ export const initDashboardTemplating = (list: VariableModel[]): ThunkResult => { + return (dispatch, getState) => { + templateSrv.setGlobalVariable('__dashboard', { + value: { + name: dashboard.title, + uid: dashboard.uid, + toString: function() { + return this.uid; + }, + }, + }); + templateSrv.setGlobalVariable('__org', { + value: { + name: contextSrv.user.orgName, + id: contextSrv.user.id, + toString: function() { + return this.id; + }, + }, + }); + }; +}; + export const changeVariableMultiValue = (identifier: VariableIdentifier, multi: boolean): ThunkResult => { return (dispatch, getState) => { const variable = getVariable(identifier.id!, getState()); @@ -106,7 +134,10 @@ export const processVariableDependencies = async (variable: VariableModel, state await Promise.all(dependencies); }; -export const processVariable = (identifier: VariableIdentifier, queryParams: UrlQueryMap): ThunkResult => { +export const processVariable = ( + identifier: VariableIdentifier, + queryParams: UrlQueryMap +): ThunkResult> => { return async (dispatch, getState) => { const variable = getVariable(identifier.id!, getState()); await processVariableDependencies(variable, getState()); @@ -114,7 +145,7 @@ export const processVariable = (identifier: VariableIdentifier, queryParams: Url const urlValue = queryParams['var-' + variable.name]; if (urlValue !== void 0) { await variableAdapters.get(variable.type).setValueFromUrl(variable, urlValue ?? ''); - await dispatch(resolveInitLock(toVariablePayload(variable))); + dispatch(resolveInitLock(toVariablePayload(variable))); return; } @@ -125,16 +156,16 @@ export const processVariable = (identifier: VariableIdentifier, queryParams: Url refreshableVariable.refresh === VariableRefresh.onTimeRangeChanged ) { await variableAdapters.get(variable.type).updateOptions(refreshableVariable); - await dispatch(resolveInitLock(toVariablePayload(variable))); + dispatch(resolveInitLock(toVariablePayload(variable))); return; } } - await dispatch(resolveInitLock(toVariablePayload(variable))); + dispatch(resolveInitLock(toVariablePayload(variable))); }; }; -export const processVariables = (): ThunkResult => { +export const processVariables = (): ThunkResult> => { return async (dispatch, getState) => { const queryParams = getState().location.query; const promises = getVariables(getState()).map( @@ -144,12 +175,15 @@ export const processVariables = (): ThunkResult => { await Promise.all(promises); for (let index = 0; index < getVariables(getState()).length; index++) { - await dispatch(removeInitLock(toVariablePayload(getVariables(getState())[index]))); + dispatch(removeInitLock(toVariablePayload(getVariables(getState())[index]))); } }; }; -export const setOptionFromUrl = (identifier: VariableIdentifier, urlValue: UrlQueryValue): ThunkResult => { +export const setOptionFromUrl = ( + identifier: VariableIdentifier, + urlValue: UrlQueryValue +): ThunkResult> => { return async (dispatch, getState) => { const variable = getVariable(identifier.id!, getState()); if (variable.hasOwnProperty('refresh') && (variable as QueryVariableModel).refresh !== VariableRefresh.never) { @@ -233,8 +267,8 @@ export const selectOptionsForCurrentValue = (variable: VariableWithOptions): Var export const validateVariableSelectionState = ( identifier: VariableIdentifier, defaultValue?: string -): ThunkResult => { - return async (dispatch, getState) => { +): ThunkResult> => { + return (dispatch, getState) => { const variableInState = getVariable(identifier.id!, getState()); const current = variableInState.current || (({} as unknown) as VariableOption); const setValue = variableAdapters.get(variableInState.type).setValue; @@ -287,8 +321,8 @@ export const setOptionAsCurrent = ( identifier: VariableIdentifier, current: VariableOption, emitChanges: boolean -): ThunkResult => { - return async dispatch => { +): ThunkResult> => { + return dispatch => { dispatch(setCurrentVariableValue(toVariablePayload(identifier, { option: current }))); return dispatch(variableUpdated(identifier, emitChanges)); }; @@ -316,7 +350,10 @@ const createGraph = (variables: VariableModel[]) => { return g; }; -export const variableUpdated = (identifier: VariableIdentifier, emitChangeEvents: boolean): ThunkResult => { +export const variableUpdated = ( + identifier: VariableIdentifier, + emitChangeEvents: boolean +): ThunkResult> => { return (dispatch, getState) => { // if there is a variable lock ignore cascading update because we are in a boot up scenario const variable = getVariable(identifier.id!, getState()); @@ -358,7 +395,7 @@ export interface OnTimeRangeUpdatedDependencies { export const onTimeRangeUpdated = ( timeRange: TimeRange, dependencies: OnTimeRangeUpdatedDependencies = { templateSrv: templateSrv, appEvents: appEvents } -): ThunkResult => async (dispatch, getState) => { +): ThunkResult> => async (dispatch, getState) => { dependencies.templateSrv.updateTimeRange(timeRange); const variablesThatNeedRefresh = getVariables(getState()).filter(variable => { if (variable.hasOwnProperty('refresh') && variable.hasOwnProperty('options')) { diff --git a/public/app/features/variables/state/selectors.ts b/public/app/features/variables/state/selectors.ts index 5f387edaf2d..583508ed5c3 100644 --- a/public/app/features/variables/state/selectors.ts +++ b/public/app/features/variables/state/selectors.ts @@ -19,21 +19,21 @@ export const getVariable = ( }; export const getFilteredVariables = (filter: (model: VariableModel) => boolean, state: StoreState = getState()) => { - return Object.values(state.templating.variables).filter(filter); + return Object.values(state.templating.variables) + .filter(filter) + .sort((s1, s2) => s1.index! - s2.index!); }; -export const getVariableWithName = (name: string) => { - return getVariable(name, getState(), false); +export const getVariableWithName = (name: string, state: StoreState = getState()) => { + return getVariable(name, state, false); }; export const getVariables = (state: StoreState = getState(), includeNewVariable = false): VariableModel[] => { - const variables = getFilteredVariables( - variable => (includeNewVariable ? true : variable.id! !== NEW_VARIABLE_ID), - state - ); - return variables.sort((s1, s2) => s1.index! - s2.index!); + return getFilteredVariables(variable => (includeNewVariable ? true : variable.id! !== NEW_VARIABLE_ID), state); }; +export type GetVariables = typeof getVariables; + export const getNewVariabelIndex = (state: StoreState = getState()): number => { return Object.values(state.templating.variables).length; }; diff --git a/public/app/features/variables/textbox/actions.test.ts b/public/app/features/variables/textbox/actions.test.ts index 1c3555c7a1f..bfa9f8a76d6 100644 --- a/public/app/features/variables/textbox/actions.test.ts +++ b/public/app/features/variables/textbox/actions.test.ts @@ -3,12 +3,12 @@ import { createTextBoxVariableAdapter } from './adapter'; import { reduxTester } from '../../../../test/core/redux/reduxTester'; import { TemplatingState } from 'app/features/variables/state/reducers'; import { updateTextBoxVariableOptions } from './actions'; -import { getTemplatingRootReducer } from '../state/helpers'; +import { getRootReducer } from '../state/helpers'; import { TextBoxVariableModel, VariableHide, VariableOption } from '../../templating/types'; import { toVariablePayload } from '../state/types'; import { createTextBoxOptions } from './reducer'; -import { setCurrentVariableValue } from '../state/sharedReducer'; -import { initDashboardTemplating } from '../state/actions'; +import { setCurrentVariableValue, addVariable } from '../state/sharedReducer'; +import { updateLocation } from 'app/core/actions'; describe('textbox actions', () => { variableAdapters.setInit(() => [createTextBoxVariableAdapter()]); @@ -40,16 +40,18 @@ describe('textbox actions', () => { }; const tester = await reduxTester<{ templating: TemplatingState }>() - .givenRootReducer(getTemplatingRootReducer()) - .whenActionIsDispatched(initDashboardTemplating([variable])) + .givenRootReducer(getRootReducer()) + .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) .whenAsyncActionIsDispatched(updateTextBoxVariableOptions(toVariablePayload(variable)), true); tester.thenDispatchedActionsPredicateShouldEqual(actions => { - const [createAction, setCurrentAction] = actions; - const expectedNumberOfActions = 2; + const [createAction, setCurrentAction, locationAction] = actions; + const expectedNumberOfActions = 3; expect(createAction).toEqual(createTextBoxOptions(toVariablePayload(variable))); expect(setCurrentAction).toEqual(setCurrentVariableValue(toVariablePayload(variable, { option }))); + expect(locationAction).toEqual(updateLocation({ query: { 'var-textbox': 'A' } })); + return actions.length === expectedNumberOfActions; }); }); diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts index bff7304c9ae..fd69052b11a 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts @@ -1,14 +1,14 @@ import '../datasource'; import CloudWatchDatasource from '../datasource'; import * as redux from 'app/store/store'; -import { dateMath } from '@grafana/data'; +import { DataSourceInstanceSettings, dateMath } from '@grafana/data'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { CustomVariable } from 'app/features/templating/all'; -import _ from 'lodash'; import { CloudWatchQuery } from '../types'; -import { DataSourceInstanceSettings } from '@grafana/data'; import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { convertToStoreState } from '../../../../../test/helpers/convertToStoreState'; +import { getTemplateSrvDependencies } from 'test/helpers/getTemplateSrvDependencies'; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), @@ -23,7 +23,7 @@ describe('CloudWatchDatasource', () => { name: 'TestDatasource', } as DataSourceInstanceSettings; - const templateSrv = new TemplateSrv(); + let templateSrv = new TemplateSrv(); const start = 1483196400 * 1000; const defaultTimeRange = { from: new Date(start), to: new Date(start + 3600 * 1000) }; @@ -465,7 +465,7 @@ describe('CloudWatchDatasource', () => { describe('When performing CloudWatch query with template variables', () => { let requestParams: { queries: CloudWatchQuery[] }; beforeEach(() => { - templateSrv.init([ + const variables = [ new CustomVariable( { name: 'var1', @@ -516,7 +516,11 @@ describe('CloudWatchDatasource', () => { }, {} as any ), - ]); + ]; + const state = convertToStoreState(variables); + const _templateSrv = new TemplateSrv(getTemplateSrvDependencies(state)); + _templateSrv.init(variables); + ctx.ds = new CloudWatchDatasource(instanceSettings, _templateSrv, timeSrv); datasourceRequestMock.mockImplementation(params => { requestParams = params.data; diff --git a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx index b7c3ef66df8..1f4c2949903 100644 --- a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx +++ b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx @@ -11,6 +11,21 @@ jest.mock('../functions', () => ({ extractServicesFromMetricDescriptors: (): any[] => [], })); +jest.mock('../../../../core/config', () => { + console.warn('[This test uses old variable system, needs a rewrite]'); + const original = jest.requireActual('../../../../core/config'); + const config = original.getConfig(); + return { + getConfig: () => ({ + ...config, + featureToggles: { + ...config.featureToggles, + newVariables: false, + }, + }), + }; +}); + const props: VariableQueryProps = { onChange: (query, definition) => {}, query: {}, diff --git a/public/app/store/store.ts b/public/app/store/store.ts index c133cff1d5f..a00442011fa 100644 --- a/public/app/store/store.ts +++ b/public/app/store/store.ts @@ -9,7 +9,11 @@ export function setStore(newStore: Store) { export function getState(): StoreState { if (!store || !store.getState) { - return {} as StoreState; // used by tests + return { + templating: { + variables: {}, + }, + } as StoreState; // used by tests } return store.getState(); diff --git a/public/test/helpers/convertToStoreState.ts b/public/test/helpers/convertToStoreState.ts new file mode 100644 index 00000000000..c8c51dc6b9d --- /dev/null +++ b/public/test/helpers/convertToStoreState.ts @@ -0,0 +1,12 @@ +import { StoreState } from '../../app/types'; + +export const convertToStoreState = (variables: any[]): StoreState => { + return { + templating: { + variables: variables.reduce((byName, variable) => { + byName[variable.name] = variable; + return byName; + }, {}), + }, + } as StoreState; +}; diff --git a/public/test/helpers/getDashboardModel.ts b/public/test/helpers/getDashboardModel.ts new file mode 100644 index 00000000000..f7c0b54cdee --- /dev/null +++ b/public/test/helpers/getDashboardModel.ts @@ -0,0 +1,6 @@ +import { DashboardModel } from '../../app/features/dashboard/state'; + +export const getDashboardModel = (json: any, meta: any = {}) => { + const getVariablesFromState = () => json.templating.list; + return new DashboardModel(json, meta, getVariablesFromState); +}; diff --git a/public/test/helpers/getTemplateSrvDependencies.ts b/public/test/helpers/getTemplateSrvDependencies.ts new file mode 100644 index 00000000000..13f30777e8a --- /dev/null +++ b/public/test/helpers/getTemplateSrvDependencies.ts @@ -0,0 +1,9 @@ +import { getFilteredVariables, getVariables, getVariableWithName } from '../../app/features/variables/state/selectors'; +import { StoreState } from '../../app/types'; +import { TemplateSrvDependencies } from 'app/features/templating/template_srv'; + +export const getTemplateSrvDependencies = (state: StoreState): TemplateSrvDependencies => ({ + getFilteredVariables: filter => getFilteredVariables(filter, state), + getVariableWithName: name => getVariableWithName(name, state), + getVariables: () => getVariables(state), +});