Variables: migrates interval variable type to React/Redux (#22797)

* Feature: migrates interval variable type to React/Redux
This commit is contained in:
Hugo Häggmark 2020-03-16 13:45:51 +01:00 committed by GitHub
parent fd99bfab1d
commit fb789e8398
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 663 additions and 58 deletions

View File

@ -16,6 +16,7 @@ import { createCustomVariableAdapter } from '../variables/custom/adapter';
import { createTextBoxVariableAdapter } from '../variables/textbox/adapter';
import { createConstantVariableAdapter } from '../variables/constant/adapter';
import { createDataSourceVariableAdapter } from '../variables/datasource/adapter';
import { createIntervalVariableAdapter } from '../variables/interval/adapter';
coreModule.factory('templateSrv', () => templateSrv);
@ -35,3 +36,4 @@ variableAdapters.set('custom', createCustomVariableAdapter());
variableAdapters.set('textbox', createTextBoxVariableAdapter());
variableAdapters.set('constant', createConstantVariableAdapter());
variableAdapters.set('datasource', createDataSourceVariableAdapter());
variableAdapters.set('interval', createIntervalVariableAdapter());

View File

@ -23,13 +23,13 @@ export interface VariableAdapter<Model extends VariableModel> {
}
const allVariableAdapters: Record<VariableType, VariableAdapter<any> | null> = {
interval: null,
query: null,
textbox: null,
constant: null,
datasource: null,
custom: null,
interval: null,
constant: null,
adhoc: null,
textbox: null,
};
export interface VariableAdapters {

View File

@ -44,7 +44,7 @@ describe('constant actions', () => {
.whenActionIsDispatched(initDashboardTemplating([variable]))
.whenAsyncActionIsDispatched(updateConstantVariableOptions(toVariablePayload(variable)), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [createAction, setCurrentAction] = actions;
const expectedNumberOfActions = 2;

View File

@ -57,7 +57,7 @@ describe('custom actions', () => {
.whenActionIsDispatched(initDashboardTemplating([variable]))
.whenAsyncActionIsDispatched(updateCustomVariableOptions(toVariablePayload(variable)), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [createAction, setCurrentAction] = actions;
const expectedNumberOfActions = 2;

View File

@ -52,7 +52,7 @@ describe('data source actions', () => {
true
);
await tester.thenDispatchedActionShouldEqual(
await tester.thenDispatchedActionsShouldEqual(
createDataSourceOptions(
toVariablePayload({ type: 'datasource', uuid: '0' }, { sources, regex: (undefined as unknown) as RegExp })
),
@ -103,7 +103,7 @@ describe('data source actions', () => {
true
);
await tester.thenDispatchedActionShouldEqual(
await tester.thenDispatchedActionsShouldEqual(
createDataSourceOptions(
toVariablePayload({ type: 'datasource', uuid: '0' }, { sources, regex: /.*(second-name).*/ })
),
@ -157,7 +157,7 @@ describe('data source actions', () => {
.givenRootReducer(getTemplatingRootReducer())
.whenAsyncActionIsDispatched(initDataSourceVariableEditor(dependencies));
await tester.thenDispatchedActionShouldEqual(
await tester.thenDispatchedActionsShouldEqual(
changeVariableEditorExtended({
propName: 'dataSourceTypes',
propValue: [

View File

@ -0,0 +1,119 @@
import React, { ChangeEvent, FocusEvent, PureComponent } from 'react';
import { IntervalVariableModel } from '../../templating/variable';
import { VariableEditorProps } from '../editor/types';
import { FormLabel, Switch } from '@grafana/ui';
export interface Props extends VariableEditorProps<IntervalVariableModel> {}
export class IntervalVariableEditor extends PureComponent<Props> {
onAutoChange = (event: ChangeEvent<HTMLInputElement>) => {
this.props.onPropChange({
propName: 'auto',
propValue: event.target.checked,
updateOptions: true,
});
};
onQueryChanged = (event: ChangeEvent<HTMLInputElement>) => {
this.props.onPropChange({
propName: 'query',
propValue: event.target.value,
});
};
onQueryBlur = (event: FocusEvent<HTMLInputElement>) => {
this.props.onPropChange({
propName: 'query',
propValue: event.target.value,
updateOptions: true,
});
};
onAutoCountChanged = (event: ChangeEvent<HTMLSelectElement>) => {
this.props.onPropChange({
propName: 'auto_count',
propValue: event.target.value,
updateOptions: true,
});
};
onAutoMinChanged = (event: ChangeEvent<HTMLInputElement>) => {
this.props.onPropChange({
propName: 'auto_min',
propValue: event.target.value,
updateOptions: true,
});
};
render() {
return (
<>
<div className="gf-form-group">
<h5 className="section-heading">Interval Options</h5>
<div className="gf-form">
<span className="gf-form-label width-9">Values</span>
<input
type="text"
className="gf-form-input"
value={this.props.variable.query}
placeholder="1m,10m,1h,6h,1d,7d"
onChange={this.onQueryChanged}
onBlur={this.onQueryBlur}
required
/>
</div>
<div className="gf-form-inline">
<Switch
label="Auto Option"
labelClass="width-9"
checked={this.props.variable.auto}
onChange={this.onAutoChange}
tooltip={'Enables multiple values to be selected at the same time'}
/>
{this.props.variable.auto && (
<>
<div className="gf-form">
<FormLabel
width={9}
tooltip={'How many times should the current time range be divided to calculate the value'}
>
Step count
</FormLabel>
<div className="gf-form-select-wrapper max-width-10">
<select
className="gf-form-input"
value={this.props.variable.auto_count}
onChange={this.onAutoCountChanged}
>
{[1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500].map(count => (
<option key={`auto_count_key-${count}`} label={`${count}`}>
{count}
</option>
))}
</select>
</div>
</div>
<div className="gf-form">
<FormLabel width={9} tooltip={'The calculated value will not go below this threshold'}>
Min interval
</FormLabel>
<input
type="text"
className="gf-form-input max-width-10"
value={this.props.variable.auto_min}
onChange={this.onAutoMinChanged}
placeholder="10s"
/>
</div>
</>
)}
</div>
</div>
</>
);
}
}

View File

@ -0,0 +1,188 @@
import { getTemplatingRootReducer, variableMockBuilder } 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 {
updateAutoValue,
UpdateAutoValueDependencies,
updateIntervalVariableOptions,
UpdateIntervalVariableOptionsDependencies,
} from './actions';
import { createIntervalOptions } from './reducer';
import { setCurrentVariableValue } from '../state/sharedReducer';
import { variableAdapters } from '../adapters';
import { createIntervalVariableAdapter } from './adapter';
import { Emitter } from 'app/core/core';
import { AppEvents, dateTime } from '@grafana/data';
import { getTimeSrv, setTimeSrv, TimeSrv } from '../../dashboard/services/TimeSrv';
import { TemplateSrv } from '../../templating/template_srv';
describe('interval actions', () => {
variableAdapters.set('interval', createIntervalVariableAdapter());
describe('when updateIntervalVariableOptions is dispatched', () => {
it('then correct actions are dispatched', async () => {
const interval = variableMockBuilder('interval')
.withUuid('0')
.withQuery('1s,1m,1h,1d')
.withAuto(false)
.create();
const tester = await reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(initDashboardTemplating([interval]))
.whenAsyncActionIsDispatched(updateIntervalVariableOptions(toVariableIdentifier(interval)), true);
tester.thenDispatchedActionsShouldEqual(
createIntervalOptions({ type: 'interval', uuid: '0', data: undefined }),
setCurrentVariableValue({
type: 'interval',
uuid: '0',
data: { option: { text: '1s', value: '1s', selected: false } },
})
);
});
});
describe('when updateIntervalVariableOptions is dispatched but something throws', () => {
it('then an app event should be emitted', async () => {
const timeSrvMock = ({
timeRange: jest.fn().mockReturnValue({
from: dateTime(new Date())
.subtract(1, 'days')
.toDate(),
to: new Date(),
raw: {
from: 'now-1d',
to: 'now',
},
}),
} as unknown) as TimeSrv;
const originalTimeSrv = getTimeSrv();
setTimeSrv(timeSrvMock);
const interval = variableMockBuilder('interval')
.withUuid('0')
.withQuery('1s,1m,1h,1d')
.withAuto(true)
.withAutoMin('1') // illegal interval string
.create();
const appEventMock = ({
emit: jest.fn(),
} as unknown) as Emitter;
const dependencies: UpdateIntervalVariableOptionsDependencies = { appEvents: appEventMock };
await reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(initDashboardTemplating([interval]))
.whenAsyncActionIsDispatched(updateIntervalVariableOptions(toVariableIdentifier(interval), dependencies), true);
expect(appEventMock.emit).toHaveBeenCalledTimes(1);
expect(appEventMock.emit).toHaveBeenCalledWith(AppEvents.alertError, [
'Templating',
'Invalid interval string, expecting a number followed by one of "Mwdhmsy"',
]);
setTimeSrv(originalTimeSrv);
});
});
describe('when updateAutoValue is dispatched', () => {
describe('and auto is false', () => {
it('then no dependencies are called', async () => {
const interval = variableMockBuilder('interval')
.withUuid('0')
.withAuto(false)
.create();
const dependencies: UpdateAutoValueDependencies = {
kbn: {
calculateInterval: jest.fn(),
},
getTimeSrv: () => {
return ({
timeRange: jest.fn().mockReturnValue({
from: '2001-01-01',
to: '2001-01-02',
raw: {
from: '2001-01-01',
to: '2001-01-02',
},
}),
} as unknown) as TimeSrv;
},
templateSrv: ({
setGrafanaVariable: jest.fn(),
} as unknown) as TemplateSrv,
};
await reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(initDashboardTemplating([interval]))
.whenAsyncActionIsDispatched(updateAutoValue(toVariableIdentifier(interval), dependencies), true);
expect(dependencies.kbn.calculateInterval).toHaveBeenCalledTimes(0);
expect(dependencies.getTimeSrv().timeRange).toHaveBeenCalledTimes(0);
expect(dependencies.templateSrv.setGrafanaVariable).toHaveBeenCalledTimes(0);
});
});
describe('and auto is true', () => {
it('then correct dependencies are called', async () => {
const interval = variableMockBuilder('interval')
.withUuid('0')
.withName('intervalName')
.withAuto(true)
.withAutoCount(33)
.withAutoMin('13s')
.create();
const timeRangeMock = jest.fn().mockReturnValue({
from: '2001-01-01',
to: '2001-01-02',
raw: {
from: '2001-01-01',
to: '2001-01-02',
},
});
const setGrafanaVariableMock = jest.fn();
const dependencies: UpdateAutoValueDependencies = {
kbn: {
calculateInterval: jest.fn().mockReturnValue({ interval: '10s' }),
},
getTimeSrv: () => {
return ({
timeRange: timeRangeMock,
} as unknown) as TimeSrv;
},
templateSrv: ({
setGrafanaVariable: setGrafanaVariableMock,
} as unknown) as TemplateSrv,
};
await reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(initDashboardTemplating([interval]))
.whenAsyncActionIsDispatched(updateAutoValue(toVariableIdentifier(interval), dependencies), true);
expect(dependencies.kbn.calculateInterval).toHaveBeenCalledTimes(1);
expect(dependencies.kbn.calculateInterval).toHaveBeenCalledWith(
{
from: '2001-01-01',
to: '2001-01-02',
raw: {
from: '2001-01-01',
to: '2001-01-02',
},
},
33,
'13s'
);
expect(timeRangeMock).toHaveBeenCalledTimes(1);
expect(setGrafanaVariableMock).toHaveBeenCalledTimes(2);
expect(setGrafanaVariableMock.mock.calls[0][0]).toBe('$__auto_interval_intervalName');
expect(setGrafanaVariableMock.mock.calls[0][1]).toBe('10s');
expect(setGrafanaVariableMock.mock.calls[1][0]).toBe('$__auto_interval');
expect(setGrafanaVariableMock.mock.calls[1][1]).toBe('10s');
});
});
});
});

View File

@ -0,0 +1,56 @@
import { AppEvents } from '@grafana/data';
import { toVariablePayload, VariableIdentifier } from '../state/types';
import { ThunkResult } from '../../../types';
import { createIntervalOptions } from './reducer';
import { validateVariableSelectionState } from '../state/actions';
import { getVariable } from '../state/selectors';
import { IntervalVariableModel } from '../../templating/variable';
import kbn from '../../../core/utils/kbn';
import { getTimeSrv } from '../../dashboard/services/TimeSrv';
import templateSrv from '../../templating/template_srv';
import appEvents from '../../../core/app_events';
export interface UpdateIntervalVariableOptionsDependencies {
appEvents: typeof appEvents;
}
export const updateIntervalVariableOptions = (
identifier: VariableIdentifier,
dependencies: UpdateIntervalVariableOptionsDependencies = { appEvents: appEvents }
): ThunkResult<void> => async dispatch => {
try {
await dispatch(createIntervalOptions(toVariablePayload(identifier)));
await dispatch(updateAutoValue(identifier));
await dispatch(validateVariableSelectionState(identifier));
} catch (error) {
dependencies.appEvents.emit(AppEvents.alertError, ['Templating', error.message]);
}
};
export interface UpdateAutoValueDependencies {
kbn: typeof kbn;
getTimeSrv: typeof getTimeSrv;
templateSrv: typeof templateSrv;
}
export const updateAutoValue = (
identifier: VariableIdentifier,
dependencies: UpdateAutoValueDependencies = {
kbn: kbn,
getTimeSrv: getTimeSrv,
templateSrv: templateSrv,
}
): ThunkResult<void> => (dispatch, getState) => {
const variableInState = getVariable<IntervalVariableModel>(identifier.uuid, getState());
if (variableInState.auto) {
const res = dependencies.kbn.calculateInterval(
dependencies.getTimeSrv().timeRange(),
variableInState.auto_count,
variableInState.auto_min
);
dependencies.templateSrv.setGrafanaVariable('$__auto_interval_' + variableInState.name, res.interval);
// for backward compatibility, to be removed eventually
dependencies.templateSrv.setGrafanaVariable('$__auto_interval', res.interval);
}
};

View File

@ -0,0 +1,42 @@
import cloneDeep from 'lodash/cloneDeep';
import { IntervalVariableModel } from '../../templating/variable';
import { dispatch } from '../../../store/store';
import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions';
import { VariableAdapter } from '../adapters';
import { initialIntervalVariableModelState, intervalVariableReducer } from './reducer';
import { OptionsPicker } from '../pickers';
import { toVariableIdentifier } from '../state/types';
import { IntervalVariableEditor } from './IntervalVariableEditor';
import { updateAutoValue, updateIntervalVariableOptions } from './actions';
export const createIntervalVariableAdapter = (): VariableAdapter<IntervalVariableModel> => {
return {
description: 'Define a timespan interval (ex 1m, 1h, 1d)',
label: 'Interval',
initialState: initialIntervalVariableModelState,
reducer: intervalVariableReducer,
picker: OptionsPicker,
editor: IntervalVariableEditor,
dependsOn: () => {
return false;
},
setValue: async (variable, option, emitChanges = false) => {
await dispatch(updateAutoValue(toVariableIdentifier(variable)));
await dispatch(setOptionAsCurrent(toVariableIdentifier(variable), option, emitChanges));
},
setValueFromUrl: async (variable, urlValue) => {
await dispatch(updateAutoValue(toVariableIdentifier(variable)));
await dispatch(setOptionFromUrl(toVariableIdentifier(variable), urlValue));
},
updateOptions: async variable => {
await dispatch(updateIntervalVariableOptions(toVariableIdentifier(variable)));
},
getSaveModel: variable => {
const { index, uuid, initLock, global, ...rest } = cloneDeep(variable);
return rest;
},
getValueForUrl: variable => {
return variable.current.value;
},
};
};

View File

@ -0,0 +1,123 @@
import cloneDeep from 'lodash/cloneDeep';
import { getVariableTestContext } from '../state/helpers';
import { toVariablePayload } from '../state/types';
import { createIntervalVariableAdapter } from './adapter';
import { IntervalVariableModel } from '../../templating/variable';
import { reducerTester } from '../../../../test/core/redux/reducerTester';
import { VariablesState } from '../state/variablesReducer';
import { createIntervalOptions, intervalVariableReducer } from './reducer';
describe('intervalVariableReducer', () => {
const adapter = createIntervalVariableAdapter();
describe('when createIntervalOptions is dispatched', () => {
describe('and auto is false', () => {
it('then state should be correct', () => {
const uuid = '0';
const query = '1s,1m,1h,1d';
const auto = false;
const { initialState } = getVariableTestContext<IntervalVariableModel>(adapter, { uuid, query, auto });
const payload = toVariablePayload({ uuid: '0', type: 'interval' });
reducerTester<VariablesState>()
.givenReducer(intervalVariableReducer, cloneDeep(initialState))
.whenActionIsDispatched(createIntervalOptions(payload))
.thenStateShouldEqual({
'0': {
...initialState['0'],
uuid: '0',
query: '1s,1m,1h,1d',
auto: false,
options: [
{ text: '1s', value: '1s', selected: false },
{ text: '1m', value: '1m', selected: false },
{ text: '1h', value: '1h', selected: false },
{ text: '1d', value: '1d', selected: false },
],
} as IntervalVariableModel,
});
});
});
describe('and auto is true', () => {
it('then state should be correct', () => {
const uuid = '0';
const query = '1s,1m,1h,1d';
const auto = true;
const { initialState } = getVariableTestContext<IntervalVariableModel>(adapter, { uuid, query, auto });
const payload = toVariablePayload({ uuid: '0', type: 'interval' });
reducerTester<VariablesState>()
.givenReducer(intervalVariableReducer, cloneDeep(initialState))
.whenActionIsDispatched(createIntervalOptions(payload))
.thenStateShouldEqual({
'0': {
...initialState['0'],
uuid: '0',
query: '1s,1m,1h,1d',
auto: true,
options: [
{ text: 'auto', value: '$__auto_interval_0', selected: false },
{ text: '1s', value: '1s', selected: false },
{ text: '1m', value: '1m', selected: false },
{ text: '1h', value: '1h', selected: false },
{ text: '1d', value: '1d', selected: false },
],
} as IntervalVariableModel,
});
});
});
describe('and query contains "', () => {
it('then state should be correct', () => {
const uuid = '0';
const query = '"kalle, anka","donald, duck"';
const auto = false;
const { initialState } = getVariableTestContext<IntervalVariableModel>(adapter, { uuid, query, auto });
const payload = toVariablePayload({ uuid: '0', type: 'interval' });
reducerTester<VariablesState>()
.givenReducer(intervalVariableReducer, cloneDeep(initialState))
.whenActionIsDispatched(createIntervalOptions(payload))
.thenStateShouldEqual({
'0': {
...initialState['0'],
uuid: '0',
query: '"kalle, anka","donald, duck"',
auto: false,
options: [
{ text: 'kalle, anka', value: 'kalle, anka', selected: false },
{ text: 'donald, duck', value: 'donald, duck', selected: false },
],
} as IntervalVariableModel,
});
});
});
describe("and query contains '", () => {
it('then state should be correct', () => {
const uuid = '0';
const query = "'kalle, anka','donald, duck'";
const auto = false;
const { initialState } = getVariableTestContext<IntervalVariableModel>(adapter, { uuid, query, auto });
const payload = toVariablePayload({ uuid: '0', type: 'interval' });
reducerTester<VariablesState>()
.givenReducer(intervalVariableReducer, cloneDeep(initialState))
.whenActionIsDispatched(createIntervalOptions(payload))
.thenStateShouldEqual({
'0': {
...initialState['0'],
uuid: '0',
query: "'kalle, anka','donald, duck'",
auto: false,
options: [
{ text: 'kalle, anka', value: 'kalle, anka', selected: false },
{ text: 'donald, duck', value: 'donald, duck', selected: false },
],
} as IntervalVariableModel,
});
});
});
});
});

View File

@ -0,0 +1,55 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { IntervalVariableModel, VariableHide, VariableOption, VariableRefresh } from '../../templating/variable';
import { EMPTY_UUID, getInstanceState, VariablePayload } from '../state/types';
import { initialVariablesState, VariablesState } from '../state/variablesReducer';
import _ from 'lodash';
export const initialIntervalVariableModelState: IntervalVariableModel = {
uuid: EMPTY_UUID,
global: false,
type: 'interval',
name: '',
label: '',
hide: VariableHide.dontHide,
skipUrlSync: false,
auto_count: 30,
auto_min: '10s',
options: [],
auto: false,
query: '1m,10m,30m,1h,6h,12h,1d,7d,14d,30d',
refresh: VariableRefresh.onTimeRangeChanged,
current: {} as VariableOption,
index: -1,
initLock: null,
};
export const intervalVariableSlice = createSlice({
name: 'templating/interval',
initialState: initialVariablesState,
reducers: {
createIntervalOptions: (state: VariablesState, action: PayloadAction<VariablePayload>) => {
const instanceState = getInstanceState<IntervalVariableModel>(state, action.payload.uuid!);
const options: VariableOption[] = _.map(instanceState.query.match(/(["'])(.*?)\1|\w+/g), text => {
text = text.replace(/["']+/g, '');
return { text: text.trim(), value: text.trim(), selected: false };
});
if (instanceState.auto) {
// add auto option if missing
if (options.length && options[0].text !== 'auto') {
options.unshift({
text: 'auto',
value: '$__auto_interval_' + instanceState.name,
selected: false,
});
}
}
instanceState.options = options;
},
},
});
export const intervalVariableReducer = intervalVariableSlice.reducer;
export const { createIntervalOptions } = intervalVariableSlice.actions;

View File

@ -62,7 +62,7 @@ describe('options picker actions', () => {
tags: [] as any[],
};
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [setCurrentValue, changeQueryValue, updateOption, hideAction] = actions;
const expectedNumberOfActions = 4;
@ -93,7 +93,7 @@ describe('options picker actions', () => {
.whenActionIsDispatched(navigateOptions(NavigationKey.moveDown, false))
.whenAsyncActionIsDispatched(navigateOptions(key, clearOthers), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [toggleOptionAction] = actions;
const expectedNumberOfActions = 1;
@ -118,7 +118,7 @@ describe('options picker actions', () => {
.whenActionIsDispatched(navigateOptions(NavigationKey.moveDown, clearOthers))
.whenAsyncActionIsDispatched(navigateOptions(key, clearOthers), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [toggleOptionAction] = actions;
const expectedNumberOfActions = 1;
@ -145,7 +145,7 @@ describe('options picker actions', () => {
.whenActionIsDispatched(navigateOptions(NavigationKey.moveDown, clearOthers))
.whenAsyncActionIsDispatched(navigateOptions(key, clearOthers), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [toggleOptionAction] = actions;
const expectedNumberOfActions = 1;
@ -173,7 +173,7 @@ describe('options picker actions', () => {
.whenActionIsDispatched(navigateOptions(NavigationKey.moveUp, clearOthers))
.whenAsyncActionIsDispatched(navigateOptions(key, clearOthers), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [toggleOptionAction] = actions;
const expectedNumberOfActions = 1;
@ -208,7 +208,7 @@ describe('options picker actions', () => {
tags: [] as any[],
};
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [toggleOptionAction, setCurrentValue, changeQueryValue, updateOption, hideAction] = actions;
const expectedNumberOfActions = 5;
@ -237,7 +237,7 @@ describe('options picker actions', () => {
.whenActionIsDispatched(showOptions(variable))
.whenAsyncActionIsDispatched(filterOrSearchOptions(filter), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [updateQueryValue, updateAndFilter] = actions;
const expectedNumberOfActions = 2;
@ -267,7 +267,7 @@ describe('options picker actions', () => {
tags: [] as any[],
};
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [setCurrentValue, changeQueryValue, hideAction] = actions;
const expectedNumberOfActions = 3;
@ -303,7 +303,7 @@ describe('options picker actions', () => {
tags: [] as any[],
};
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [setCurrentValue, changeQueryValue, updateOption, hideAction] = actions;
const expectedNumberOfActions = 4;
@ -334,7 +334,7 @@ describe('options picker actions', () => {
const option = createOption('A');
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [toggleOptionAction] = actions;
const expectedNumberOfActions = 1;
@ -356,7 +356,7 @@ describe('options picker actions', () => {
.whenActionIsDispatched(showOptions(variable))
.whenAsyncActionIsDispatched(toggleAndFetchTag(tag), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [toggleTagAction] = actions;
const expectedNumberOfActions = 1;
@ -383,7 +383,7 @@ describe('options picker actions', () => {
.whenActionIsDispatched(showOptions(variable))
.whenAsyncActionIsDispatched(toggleAndFetchTag(tag), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [toggleTagAction] = actions;
const expectedNumberOfActions = 1;

View File

@ -64,7 +64,7 @@ describe('query actions', () => {
const option = createOption(ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [updateOptions, updateTags, setCurrentAction] = actions;
const expectedNumberOfActions = 3;
@ -91,7 +91,7 @@ describe('query actions', () => {
const option = createOption('A');
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [updateOptions, updateTags, setCurrentAction] = actions;
const expectedNumberOfActions = 3;
@ -117,7 +117,7 @@ describe('query actions', () => {
const option = createOption('A');
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [updateOptions, setCurrentAction] = actions;
const expectedNumberOfActions = 2;
@ -142,7 +142,7 @@ describe('query actions', () => {
const option = createOption(ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [updateOptions, setCurrentAction] = actions;
const expectedNumberOfActions = 2;
@ -168,7 +168,7 @@ describe('query actions', () => {
const option = createOption(ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [clearErrors, updateOptions, setCurrentAction] = actions;
const expectedNumberOfActions = 3;
@ -193,7 +193,7 @@ describe('query actions', () => {
.whenActionIsDispatched(setIdInEditor({ id: variable.uuid! }))
.whenAsyncActionIsDispatched(updateQueryVariableOptions(toVariablePayload(variable)), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [clearErrors, errorOccurred] = actions;
const expectedNumberOfActions = 2;
@ -221,7 +221,7 @@ describe('query actions', () => {
.whenActionIsDispatched(initDashboardTemplating([variable]))
.whenAsyncActionIsDispatched(initQueryVariableEditor(toVariablePayload(variable)), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [updateDatasources, setDatasource, setEditor] = actions;
const expectedNumberOfActions = 3;
@ -254,7 +254,7 @@ describe('query actions', () => {
.whenActionIsDispatched(initDashboardTemplating([variable]))
.whenAsyncActionIsDispatched(initQueryVariableEditor(toVariablePayload(variable)), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [updateDatasources, setDatasource, setEditor] = actions;
const expectedNumberOfActions = 3;
@ -286,7 +286,7 @@ describe('query actions', () => {
.whenActionIsDispatched(initDashboardTemplating([variable]))
.whenAsyncActionIsDispatched(initQueryVariableEditor(toVariablePayload(variable)), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [updateDatasources, setDatasource, setEditor] = actions;
const expectedNumberOfActions = 3;
@ -312,7 +312,7 @@ describe('query actions', () => {
.whenActionIsDispatched(initDashboardTemplating([variable]))
.whenAsyncActionIsDispatched(initQueryVariableEditor(toVariablePayload(variable)), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [updateDatasources] = actions;
const expectedNumberOfActions = 1;
@ -336,7 +336,7 @@ describe('query actions', () => {
.whenActionIsDispatched(initDashboardTemplating([variable]))
.whenAsyncActionIsDispatched(changeQueryVariableDataSource(toVariablePayload(variable), 'datasource'), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [updateDatasource, updateEditor] = actions;
const expectedNumberOfActions = 2;
@ -366,7 +366,7 @@ describe('query actions', () => {
.whenActionIsDispatched(initDashboardTemplating([variable]))
.whenAsyncActionIsDispatched(changeQueryVariableDataSource(toVariablePayload(variable), 'datasource'), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [updateDatasource, updateEditor] = actions;
const expectedNumberOfActions = 2;
@ -400,7 +400,7 @@ describe('query actions', () => {
const option = createOption(ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [clearError, changeQuery, changeDefinition, updateOptions, updateTags, setOption] = actions;
const expectedNumberOfActions = 6;
@ -437,7 +437,7 @@ describe('query actions', () => {
const option = createOption(ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [clearError, changeQuery, changeDefinition, updateOptions, setOption] = actions;
const expectedNumberOfActions = 5;
@ -472,7 +472,7 @@ describe('query actions', () => {
const option = createOption('A');
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [clearError, changeQuery, changeDefinition, updateOptions, setOption] = actions;
const expectedNumberOfActions = 5;
@ -504,7 +504,7 @@ describe('query actions', () => {
const errorText = 'Query cannot contain a reference to itself. Variable: $' + variable.name;
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [editorError] = actions;
const expectedNumberOfActions = 1;

View File

@ -30,7 +30,7 @@ describe('shared actions', () => {
reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(initDashboardTemplating(list))
.thenDispatchedActionPredicateShouldEqual(dispatchedActions => {
.thenDispatchedActionsPredicateShouldEqual(dispatchedActions => {
expect(dispatchedActions.length).toEqual(8);
expect(dispatchedActions[0]).toEqual(
addVariable(toVariablePayload(query, { global: false, index: 0, model: query }))
@ -85,7 +85,7 @@ describe('shared actions', () => {
.whenActionIsDispatched(initDashboardTemplating(list))
.whenAsyncActionIsDispatched(processVariables(), true);
await tester.thenDispatchedActionPredicateShouldEqual(dispatchedActions => {
await tester.thenDispatchedActionsPredicateShouldEqual(dispatchedActions => {
expect(dispatchedActions.length).toEqual(8);
expect(dispatchedActions[0]).toEqual(
@ -142,7 +142,7 @@ describe('shared actions', () => {
.whenActionIsDispatched(addVariable(toVariablePayload(custom, { global: false, index: 0, model: custom })))
.whenAsyncActionIsDispatched(setOptionFromUrl(toVariableIdentifier(custom), urlValue), true);
await tester.thenDispatchedActionShouldEqual(
await tester.thenDispatchedActionsShouldEqual(
setCurrentVariableValue(
toVariablePayload(
{ type: 'custom', uuid: '0' },
@ -191,7 +191,7 @@ describe('shared actions', () => {
true
);
await tester.thenDispatchedActionPredicateShouldEqual(dispatchedActions => {
await tester.thenDispatchedActionsPredicateShouldEqual(dispatchedActions => {
const expectedActions: AnyAction[] = !withOptions
? []
: [
@ -249,7 +249,7 @@ describe('shared actions', () => {
true
);
await tester.thenDispatchedActionPredicateShouldEqual(dispatchedActions => {
await tester.thenDispatchedActionsPredicateShouldEqual(dispatchedActions => {
const expectedActions: AnyAction[] = !withOptions
? []
: [

View File

@ -111,6 +111,21 @@ export const variableMockBuilder = (type: VariableType) => {
return instance;
};
const withAuto = (auto: boolean) => {
model.auto = auto;
return instance;
};
const withAutoCount = (autoCount: number) => {
model.auto_count = autoCount;
return instance;
};
const withAutoMin = (autoMin: string) => {
model.auto_min = autoMin;
return instance;
};
const create = () => model;
const instance = {
@ -122,6 +137,9 @@ export const variableMockBuilder = (type: VariableType) => {
withQuery,
withMulti,
withRegEx,
withAuto,
withAutoCount,
withAutoMin,
create,
};

View File

@ -111,7 +111,9 @@ describe('processVariable', () => {
.whenActionIsDispatched(initDashboardTemplating(list))
.whenAsyncActionIsDispatched(processVariable(toVariableIdentifier(custom), queryParams), true);
await tester.thenDispatchedActionShouldEqual(resolveInitLock(toVariablePayload({ type: 'custom', uuid: '0' })));
await tester.thenDispatchedActionsShouldEqual(
resolveInitLock(toVariablePayload({ type: 'custom', uuid: '0' }))
);
});
});
@ -124,7 +126,7 @@ describe('processVariable', () => {
.whenActionIsDispatched(initDashboardTemplating(list))
.whenAsyncActionIsDispatched(processVariable(toVariableIdentifier(custom), queryParams), true);
await tester.thenDispatchedActionShouldEqual(
await tester.thenDispatchedActionsShouldEqual(
setCurrentVariableValue(
toVariablePayload({ type: 'custom', uuid: '0' }, { option: { text: ['B'], value: ['B'], selected: false } })
),
@ -149,7 +151,7 @@ describe('processVariable', () => {
.whenActionIsDispatched(initDashboardTemplating(list))
.whenAsyncActionIsDispatched(processVariable(toVariableIdentifier(queryNoDepends), queryParams), true);
await tester.thenDispatchedActionShouldEqual(
await tester.thenDispatchedActionsShouldEqual(
resolveInitLock(toVariablePayload({ type: 'query', uuid: '2' }))
);
});
@ -165,7 +167,7 @@ describe('processVariable', () => {
.whenActionIsDispatched(initDashboardTemplating(list))
.whenAsyncActionIsDispatched(processVariable(toVariableIdentifier(queryNoDepends), queryParams), true);
await tester.thenDispatchedActionShouldEqual(
await tester.thenDispatchedActionsShouldEqual(
updateVariableOptions(
toVariablePayload({ type: 'query', uuid: '2' }, [
{ value: 'A', text: 'A' },
@ -196,7 +198,7 @@ describe('processVariable', () => {
.whenActionIsDispatched(initDashboardTemplating(list))
.whenAsyncActionIsDispatched(processVariable(toVariableIdentifier(queryNoDepends), queryParams), true);
await tester.thenDispatchedActionShouldEqual(
await tester.thenDispatchedActionsShouldEqual(
setCurrentVariableValue(
toVariablePayload(
{ type: 'query', uuid: '2' },
@ -222,7 +224,7 @@ describe('processVariable', () => {
.whenActionIsDispatched(initDashboardTemplating(list))
.whenAsyncActionIsDispatched(processVariable(toVariableIdentifier(queryNoDepends), queryParams), true);
await tester.thenDispatchedActionShouldEqual(
await tester.thenDispatchedActionsShouldEqual(
updateVariableOptions(
toVariablePayload({ type: 'query', uuid: '2' }, [
{ value: 'A', text: 'A' },
@ -267,7 +269,7 @@ describe('processVariable', () => {
true
);
await tester.thenDispatchedActionShouldEqual(
await tester.thenDispatchedActionsShouldEqual(
resolveInitLock(toVariablePayload({ type: 'query', uuid: '1' }))
);
});
@ -288,7 +290,7 @@ describe('processVariable', () => {
true
);
await tester.thenDispatchedActionShouldEqual(
await tester.thenDispatchedActionsShouldEqual(
updateVariableOptions(
toVariablePayload({ type: 'query', uuid: '1' }, [
{ value: 'AA', text: 'AA' },
@ -327,7 +329,7 @@ describe('processVariable', () => {
true
);
await tester.thenDispatchedActionShouldEqual(
await tester.thenDispatchedActionsShouldEqual(
setCurrentVariableValue(
toVariablePayload(
{ type: 'query', uuid: '1' },
@ -358,7 +360,7 @@ describe('processVariable', () => {
true
);
await tester.thenDispatchedActionShouldEqual(
await tester.thenDispatchedActionsShouldEqual(
updateVariableOptions(
toVariablePayload({ type: 'query', uuid: '1' }, [
{ value: 'AA', text: 'AA' },

View File

@ -44,7 +44,7 @@ describe('textbox actions', () => {
.whenActionIsDispatched(initDashboardTemplating([variable]))
.whenAsyncActionIsDispatched(updateTextBoxVariableOptions(toVariablePayload(variable)), true);
tester.thenDispatchedActionPredicateShouldEqual(actions => {
tester.thenDispatchedActionsPredicateShouldEqual(actions => {
const [createAction, setCurrentAction] = actions;
const expectedNumberOfActions = 2;

View File

@ -21,8 +21,8 @@ export interface ReduxTesterWhen<State> {
}
export interface ReduxTesterThen<State> {
thenDispatchedActionShouldEqual: (...dispatchedAction: AnyAction[]) => ReduxTesterWhen<State>;
thenDispatchedActionPredicateShouldEqual: (
thenDispatchedActionsShouldEqual: (...dispatchedActions: AnyAction[]) => ReduxTesterWhen<State>;
thenDispatchedActionsPredicateShouldEqual: (
predicate: (dispatchedActions: AnyAction[]) => boolean
) => ReduxTesterWhen<State>;
}
@ -85,7 +85,7 @@ export const reduxTester = <State>(args?: ReduxTesterArguments<State>): ReduxTes
return instance;
};
const thenDispatchedActionShouldEqual = (...actions: AnyAction[]): ReduxTesterWhen<State> => {
const thenDispatchedActionsShouldEqual = (...actions: AnyAction[]): ReduxTesterWhen<State> => {
if (debug) {
console.log('Dispatched Actions', JSON.stringify(dispatchedActions, null, 2));
}
@ -98,7 +98,7 @@ export const reduxTester = <State>(args?: ReduxTesterArguments<State>): ReduxTes
return instance;
};
const thenDispatchedActionPredicateShouldEqual = (
const thenDispatchedActionsPredicateShouldEqual = (
predicate: (dispatchedActions: AnyAction[]) => boolean
): ReduxTesterWhen<State> => {
if (debug) {
@ -113,8 +113,8 @@ export const reduxTester = <State>(args?: ReduxTesterArguments<State>): ReduxTes
givenRootReducer,
whenActionIsDispatched,
whenAsyncActionIsDispatched,
thenDispatchedActionShouldEqual,
thenDispatchedActionPredicateShouldEqual,
thenDispatchedActionsShouldEqual,
thenDispatchedActionsPredicateShouldEqual,
};
return instance;