Commit c7ffc119 by Hugo Häggmark Committed by GitHub

Variables: turns on newVariables as a new default (#23272)

* Variables: turns on newVariables as default

* Chore: adds default templating state

* Some small refactorings to get the template_srv tests to get green when toggle is enabled by default.

* Refactor: adds getVariables dependency to DashboardModel

* Tests: fixes StackDriver tests

* Tests: updates snapshots

* Tests: updates snapshot for DashboardGrid.test.tsx

* Tests: fixes DashboardModel.test.ts

* fixed initDashboard tests.

* renamed variable.

* changed so we use the templating.list when running the migration work.

* changed so we always returns the variables in sorted order.

* Tests: fixed cloudwatch tests

* added so we set the global template variable props.

* Fixed tests and added moved logic to complete templateSrv variables.

* removed unneccesary updateIndex.

Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>
parent bf046d75
......@@ -71,7 +71,7 @@ export class GrafanaBootConfig {
expressions: false,
newEdit: false,
meta: false,
newVariables: false,
newVariables: true,
tracingIntegration: false,
};
licenseInfo: LicenseInfo = {} as LicenseInfo;
......
......@@ -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`
<div
className="dashboard-container"
>
<AngularSubMenu
<SubMenu
dashboard={
DashboardModel {
"annotations": Object {
......@@ -144,6 +145,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
},
},
"getVariables": [Function],
"getVariablesFromState": [Function],
"gnetId": null,
"graphTooltip": 0,
"id": null,
......@@ -239,6 +241,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
},
},
"getVariables": [Function],
"getVariablesFromState": [Function],
"gnetId": null,
"graphTooltip": 0,
"id": null,
......@@ -364,6 +367,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
},
},
"getVariables": [Function],
"getVariablesFromState": [Function],
"gnetId": null,
"graphTooltip": 0,
"id": null,
......@@ -453,7 +457,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
<div
className="dashboard-container"
>
<AngularSubMenu
<SubMenu
dashboard={
DashboardModel {
"annotations": Object {
......@@ -479,6 +483,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
},
},
"getVariables": [Function],
"getVariablesFromState": [Function],
"gnetId": null,
"graphTooltip": 0,
"id": null,
......@@ -574,6 +579,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
},
},
"getVariables": [Function],
"getVariablesFromState": [Function],
"gnetId": null,
"graphTooltip": 0,
"id": null,
......@@ -675,6 +681,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
},
},
"getVariables": [Function],
"getVariablesFromState": [Function],
"gnetId": null,
"graphTooltip": 0,
"id": null,
......
......@@ -106,6 +106,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
},
},
"getVariables": [Function],
"getVariablesFromState": [Function],
"gnetId": null,
"graphTooltip": 0,
"id": null,
......@@ -354,6 +355,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
},
},
"getVariables": [Function],
"getVariablesFromState": [Function],
"gnetId": null,
"graphTooltip": 0,
"id": null,
......@@ -602,6 +604,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
},
},
"getVariables": [Function],
"getVariablesFromState": [Function],
"gnetId": null,
"graphTooltip": 0,
"id": null,
......@@ -850,6 +853,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
},
},
"getVariables": [Function],
"getVariablesFromState": [Function],
"gnetId": null,
"graphTooltip": 0,
"id": null,
......
......@@ -127,9 +127,8 @@ export class DashboardMigrator {
}
// update template variables
const variables = this.dashboard.getVariables();
for (i = 0; i < variables.length; i++) {
const variable = variables[i];
for (i = 0; i < this.dashboard.templating.list.length; i++) {
const variable = this.dashboard.templating.list[i];
if (variable.datasource === void 0) {
variable.datasource = null;
}
......
import _ from 'lodash';
import { DashboardModel } from '../state/DashboardModel';
import { expect } from 'test/lib/common';
import { getDashboardModel } from '../../../../test/helpers/getDashboardModel';
jest.mock('app/core/services/context_srv', () => ({}));
......@@ -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);
......
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();
});
......
......@@ -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;
}
......
......@@ -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);
});
});
......@@ -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<void> {
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)));
......
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);
}
......
......@@ -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);
......
......@@ -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 => {
......
......@@ -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 => {
......
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(
......
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);
......
......@@ -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', () => {
......
......@@ -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<void
dispatch(addVariable(toVariablePayload(model, { global: false, index: orderIndex++, model })));
}
templateSrv.updateTimeRange(getTimeSrv().timeRange());
for (let index = 0; index < getVariables(getState()).length; index++) {
dispatch(addInitLock(toVariablePayload(getVariables(getState())[index])));
}
};
};
export const completeDashboardTemplating = (dashboard: DashboardModel): ThunkResult<void> => {
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<void> => {
return (dispatch, getState) => {
const variable = getVariable<VariableWithMultiSupport>(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<void> => {
export const processVariable = (
identifier: VariableIdentifier,
queryParams: UrlQueryMap
): ThunkResult<Promise<void>> => {
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<void> => {
export const processVariables = (): ThunkResult<Promise<void>> => {
return async (dispatch, getState) => {
const queryParams = getState().location.query;
const promises = getVariables(getState()).map(
......@@ -144,12 +175,15 @@ export const processVariables = (): ThunkResult<void> => {
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<void> => {
export const setOptionFromUrl = (
identifier: VariableIdentifier,
urlValue: UrlQueryValue
): ThunkResult<Promise<void>> => {
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<void> => {
return async (dispatch, getState) => {
): ThunkResult<Promise<void>> => {
return (dispatch, getState) => {
const variableInState = getVariable<VariableWithOptions>(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<void> => {
return async dispatch => {
): ThunkResult<Promise<void>> => {
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<void> => {
export const variableUpdated = (
identifier: VariableIdentifier,
emitChangeEvents: boolean
): ThunkResult<Promise<void>> => {
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<void> => async (dispatch, getState) => {
): ThunkResult<Promise<void>> => async (dispatch, getState) => {
dependencies.templateSrv.updateTimeRange(timeRange);
const variablesThatNeedRefresh = getVariables(getState()).filter(variable => {
if (variable.hasOwnProperty('refresh') && variable.hasOwnProperty('options')) {
......
......@@ -19,21 +19,21 @@ export const getVariable = <T extends VariableModel = VariableModel>(
};
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;
};
......@@ -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;
});
});
......
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;
......
......@@ -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: {},
......
......@@ -9,7 +9,11 @@ export function setStore(newStore: Store<StoreState>) {
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();
......
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;
};
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);
};
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),
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment