Commit cf5064bf by Hugo Häggmark Committed by GitHub

Variables: replaces homegrown variableAdapters with Registry (#22866)

* Refactor: intial commit

* Tests: fixes tests

* Refactor: adds stricter typings
parent 277edca3
......@@ -41,6 +41,7 @@ import { PerformanceBackend } from './core/services/echo/backends/PerformanceBac
import 'app/routes/GrafanaCtrl';
import 'app/features/all';
import { getStandardFieldConfigs } from '@grafana/ui';
import { getDefaultVariableAdapters, variableAdapters } from './features/variables/adapters';
// add move to lodash for backward compatabiltiy
// @ts-ignore
......@@ -84,6 +85,7 @@ export class GrafanaApp {
setMarkdownOptions({ sanitize: !config.disableSanitizeHtml });
standardFieldConfigEditorRegistry.setInit(getStandardFieldConfigs);
variableAdapters.setInit(getDefaultVariableAdapters);
app.config(
(
......
......@@ -188,7 +188,7 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult<void> {
const list =
dashboard.variables.list.length > 0
? dashboard.variables.list
: dashboard.templating.list.filter(v => variableAdapters.contains(v.type));
: dashboard.templating.list.filter(v => variableAdapters.getIfExists(v.type));
await dispatch(initDashboardTemplating(list));
await dispatch(processVariables());
}
......
......@@ -10,14 +10,6 @@ import { CustomVariable } from './custom_variable';
import { ConstantVariable } from './constant_variable';
import { AdhocVariable } from './adhoc_variable';
import { TextBoxVariable } from './TextBoxVariable';
import { variableAdapters } from '../variables/adapters';
import { createQueryVariableAdapter } from '../variables/query/adapter';
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 { createAdHocVariableAdapter } from '../variables/adhoc/adapter';
import { createIntervalVariableAdapter } from '../variables/interval/adapter';
coreModule.factory('templateSrv', () => templateSrv);
......@@ -31,11 +23,3 @@ export {
AdhocVariable,
TextBoxVariable,
};
variableAdapters.set('query', createQueryVariableAdapter());
variableAdapters.set('custom', createCustomVariableAdapter());
variableAdapters.set('textbox', createTextBoxVariableAdapter());
variableAdapters.set('constant', createConstantVariableAdapter());
variableAdapters.set('datasource', createDataSourceVariableAdapter());
variableAdapters.set('adhoc', createAdHocVariableAdapter());
variableAdapters.set('interval', createIntervalVariableAdapter());
......@@ -2,14 +2,34 @@ import { ComponentType } from 'react';
import { Reducer } from 'redux';
import { UrlQueryValue } from '@grafana/runtime';
import { VariableModel, VariableOption, VariableType } from '../templating/variable';
import {
AdHocVariableModel,
ConstantVariableModel,
CustomVariableModel,
DataSourceVariableModel,
IntervalVariableModel,
QueryVariableModel,
TextBoxVariableModel,
VariableModel,
VariableOption,
VariableType,
} from '../templating/variable';
import { VariableEditorProps } from './editor/types';
import { VariablesState } from './state/variablesReducer';
import { VariablePickerProps } from './pickers/types';
import { Registry } from '@grafana/data';
import { createQueryVariableAdapter } from './query/adapter';
import { createCustomVariableAdapter } from './custom/adapter';
import { createTextBoxVariableAdapter } from './textbox/adapter';
import { createConstantVariableAdapter } from './constant/adapter';
import { createDataSourceVariableAdapter } from './datasource/adapter';
import { createIntervalVariableAdapter } from './interval/adapter';
import { createAdHocVariableAdapter } from './adhoc/adapter';
export interface VariableAdapter<Model extends VariableModel> {
id: VariableType;
description: string;
label: string;
name: string;
initialState: Model;
dependsOn: (variable: Model, variableToTest: Model) => boolean;
setValue: (variable: Model, option: VariableOption, emitChanges?: boolean) => Promise<void>;
......@@ -22,40 +42,24 @@ export interface VariableAdapter<Model extends VariableModel> {
reducer: Reducer<VariablesState>;
}
const allVariableAdapters: Record<VariableType, VariableAdapter<any> | null> = {
interval: null,
query: null,
datasource: null,
custom: null,
constant: null,
adhoc: null,
textbox: null,
};
export type VariableModels =
| QueryVariableModel
| CustomVariableModel
| TextBoxVariableModel
| ConstantVariableModel
| DataSourceVariableModel
| IntervalVariableModel
| AdHocVariableModel;
export type VariableTypeRegistry<Model extends VariableModel = VariableModel> = Registry<VariableAdapter<Model>>;
export interface VariableAdapters {
contains: (type: VariableType) => boolean;
get: (type: VariableType) => VariableAdapter<any>;
set: (type: VariableType, adapter: VariableAdapter<any>) => void;
registeredTypes: () => Array<{ type: VariableType; label: string }>;
}
export const variableAdapters: VariableAdapters = {
contains: (type: VariableType): boolean => !!allVariableAdapters[type],
get: (type: VariableType): VariableAdapter<any> => {
if (allVariableAdapters[type] !== null) {
// @ts-ignore
// Suppressing strict null check in this case we know that this is an instance otherwise we throw
// Type 'VariableAdapter<any, any> | null' is not assignable to type 'VariableAdapter<any, any>'.
// Type 'null' is not assignable to type 'VariableAdapter<any, any>'.
return allVariableAdapters[type];
}
export const getDefaultVariableAdapters = () => [
createQueryVariableAdapter(),
createCustomVariableAdapter(),
createTextBoxVariableAdapter(),
createConstantVariableAdapter(),
createDataSourceVariableAdapter(),
createIntervalVariableAdapter(),
createAdHocVariableAdapter(),
];
throw new Error(`There is no adapter for type:${type}`);
},
set: (type, adapter) => (allVariableAdapters[type] = adapter),
registeredTypes: (): Array<{ type: VariableType; label: string }> => {
return Object.keys(allVariableAdapters)
.filter((key: VariableType) => allVariableAdapters[key] !== null)
.map((key: VariableType) => ({ type: key, label: allVariableAdapters[key]!.label }));
},
};
export const variableAdapters: VariableTypeRegistry = new Registry<VariableAdapter<VariableModels>>();
......@@ -40,9 +40,9 @@ type ReducersUsedInContext = {
location: LocationState;
};
describe('adhoc actions', () => {
variableAdapters.set('adhoc', createAdHocVariableAdapter());
variableAdapters.setInit(() => [createAdHocVariableAdapter()]);
describe('adhoc actions', () => {
describe('when applyFilterFromTable is dispatched and filter already exist', () => {
it('then correct actions are dispatched', async () => {
const options: AdHocTableOptions = {
......
......@@ -12,8 +12,9 @@ const noop = async () => {};
export const createAdHocVariableAdapter = (): VariableAdapter<AdHocVariableModel> => {
return {
id: 'adhoc',
description: 'Add key/value filters on the fly',
label: 'Ad hoc filters',
name: 'Ad hoc filters',
initialState: initialAdHocVariableModelState,
reducer: adHocVariableReducer,
picker: AdHocPicker,
......
......@@ -11,7 +11,7 @@ import { setCurrentVariableValue } from '../state/sharedReducer';
import { initDashboardTemplating } from '../state/actions';
describe('constant actions', () => {
variableAdapters.set('constant', createConstantVariableAdapter());
variableAdapters.setInit(() => [createConstantVariableAdapter()]);
describe('when updateConstantVariableOptions is dispatched', () => {
it('then correct actions are dispatched', async () => {
......
......@@ -11,8 +11,9 @@ import { toVariableIdentifier } from '../state/types';
export const createConstantVariableAdapter = (): VariableAdapter<ConstantVariableModel> => {
return {
id: 'constant',
description: 'Define a hidden constant variable, useful for metric prefixes in dashboards you want to share',
label: 'Constant',
name: 'Constant',
initialState: initialConstantVariableModelState,
reducer: constantVariableReducer,
picker: OptionsPicker,
......
......@@ -11,7 +11,7 @@ import { TemplatingState } from '../state/reducers';
import { createCustomOptionsFromQuery } from './reducer';
describe('custom actions', () => {
variableAdapters.set('custom', createCustomVariableAdapter());
variableAdapters.setInit(() => [createCustomVariableAdapter()]);
describe('when updateCustomVariableOptions is dispatched', () => {
it('then correct actions are dispatched', async () => {
......
......@@ -11,8 +11,9 @@ import { ALL_VARIABLE_TEXT, toVariableIdentifier } from '../state/types';
export const createCustomVariableAdapter = (): VariableAdapter<CustomVariableModel> => {
return {
id: 'custom',
description: 'Define variable values manually',
label: 'Custom',
name: 'Custom',
initialState: initialCustomVariableModelState,
reducer: customVariableReducer,
picker: OptionsPicker,
......
......@@ -18,7 +18,7 @@ import { changeVariableEditorExtended } from '../editor/reducer';
import { datasourceBuilder } from '../shared/testing/builders';
describe('data source actions', () => {
variableAdapters.set('datasource', createDataSourceVariableAdapter());
variableAdapters.setInit(() => [createDataSourceVariableAdapter()]);
describe('when updateDataSourceVariableOptions is dispatched', () => {
describe('and there is no regex', () => {
......
......@@ -11,8 +11,9 @@ import { updateDataSourceVariableOptions } from './actions';
export const createDataSourceVariableAdapter = (): VariableAdapter<DataSourceVariableModel> => {
return {
id: 'datasource',
description: 'Enabled you to dynamically switch the datasource for multiple panels',
label: 'Datasource',
name: 'Datasource',
initialState: initialDataSourceVariableModelState,
reducer: dataSourceVariableReducer,
picker: OptionsPicker,
......
......@@ -143,9 +143,9 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props> {
onChange={this.onTypeChange}
aria-label={e2e.pages.Dashboard.Settings.Variables.Edit.General.selectors.generalTypeSelect}
>
{variableAdapters.registeredTypes().map(item => (
<option key={item.type} label={item.label} value={item.type}>
{item.label}
{variableAdapters.list().map(({ id, name }) => (
<option key={id} label={name} value={id}>
{name}
</option>
))}
</select>
......
......@@ -20,7 +20,7 @@ import { TemplateSrv } from '../../templating/template_srv';
import { intervalBuilder } from '../shared/testing/builders';
describe('interval actions', () => {
variableAdapters.set('interval', createIntervalVariableAdapter());
variableAdapters.setInit(() => [createIntervalVariableAdapter()]);
describe('when updateIntervalVariableOptions is dispatched', () => {
it('then correct actions are dispatched', async () => {
const interval = intervalBuilder()
......
......@@ -11,8 +11,9 @@ import { updateAutoValue, updateIntervalVariableOptions } from './actions';
export const createIntervalVariableAdapter = (): VariableAdapter<IntervalVariableModel> => {
return {
id: 'interval',
description: 'Define a timespan interval (ex 1m, 1h, 1d)',
label: 'Interval',
name: 'Interval',
initialState: initialIntervalVariableModelState,
reducer: intervalVariableReducer,
picker: OptionsPicker,
......
......@@ -40,7 +40,7 @@ jest.mock('@grafana/runtime', () => {
});
describe('options picker actions', () => {
variableAdapters.set('query', createQueryVariableAdapter());
variableAdapters.setInit(() => [createQueryVariableAdapter()]);
describe('when navigateOptions is dispatched with navigation key cancel', () => {
it('then correct actions are dispatched', async () => {
......
......@@ -47,7 +47,7 @@ jest.mock('../../plugins/plugin_loader', () => ({
}));
describe('query actions', () => {
variableAdapters.set('query', createQueryVariableAdapter());
variableAdapters.setInit(() => [createQueryVariableAdapter()]);
describe('when updateQueryVariableOptions is dispatched for variable with tags and includeAll', () => {
it('then correct actions are dispatched', async () => {
......
......@@ -12,8 +12,9 @@ import { ALL_VARIABLE_TEXT, toVariableIdentifier } from '../state/types';
export const createQueryVariableAdapter = (): VariableAdapter<QueryVariableModel> => {
return {
id: 'query',
description: 'Variable values are fetched from a datasource query',
label: 'Query',
name: 'Query',
initialState: initialQueryVariableModelState,
reducer: queryVariableReducer,
picker: OptionsPicker,
......
......@@ -55,7 +55,7 @@ export const initDashboardTemplating = (list: VariableModel[]): ThunkResult<void
let orderIndex = 0;
for (let index = 0; index < list.length; index++) {
const model = list[index];
if (!variableAdapters.contains(model.type)) {
if (!variableAdapters.getIfExists(model.type)) {
continue;
}
......@@ -76,7 +76,7 @@ export const processVariableDependencies = async (variable: VariableModel, state
continue;
}
if (variableAdapters.contains(variable.type)) {
if (variableAdapters.getIfExists(variable.type)) {
if (variableAdapters.get(variable.type).dependsOn(variable, otherVariable)) {
dependencies.push(otherVariable.initLock!.promise);
}
......
import { dateTime, TimeRange } from '@grafana/data';
import { TemplateSrv } from '../../templating/template_srv';
import { Emitter } from '../../../core/utils/emitter';
import { onTimeRangeUpdated, OnTimeRangeUpdatedDependencies } from './actions';
import { DashboardModel } from '../../dashboard/state';
import { DashboardState } from '../../../types';
import { createIntervalVariableAdapter } from '../interval/adapter';
import { variableAdapters } from '../adapters';
import { createConstantVariableAdapter } from '../constant/adapter';
import { VariableRefresh } from '../../templating/variable';
import { constantBuilder, intervalBuilder } from '../shared/testing/builders';
variableAdapters.setInit(() => [createIntervalVariableAdapter(), createConstantVariableAdapter()]);
const getOnTimeRangeUpdatedContext = (args: { update?: boolean; throw?: boolean }) => {
const range: TimeRange = {
from: dateTime(new Date().getTime()).subtract(1, 'minutes'),
to: dateTime(new Date().getTime()),
raw: {
from: 'now-1m',
to: 'now',
},
};
const updateTimeRangeMock = jest.fn();
const templateSrvMock = ({ updateTimeRange: updateTimeRangeMock } as unknown) as TemplateSrv;
const emitMock = jest.fn();
const appEventsMock = ({ emit: emitMock } as unknown) as Emitter;
const dependencies: OnTimeRangeUpdatedDependencies = { templateSrv: templateSrvMock, appEvents: appEventsMock };
const templateVariableValueUpdatedMock = jest.fn();
const dashboard = ({
getModel: () =>
(({
templateVariableValueUpdated: templateVariableValueUpdatedMock,
startRefresh: startRefreshMock,
} as unknown) as DashboardModel),
} as unknown) as DashboardState;
const startRefreshMock = jest.fn();
const adapter = variableAdapters.get('interval');
adapter.updateOptions = args.throw ? jest.fn().mockRejectedValue('Something broke') : jest.fn().mockResolvedValue({});
// initial variable state
const initialVariable = intervalBuilder()
.withId('interval-0')
.withName('interval-0')
.withOptions('1m', '10m', '30m', '1h', '6h', '12h', '1d', '7d', '14d', '30d')
.withCurrent('1m')
.withRefresh(VariableRefresh.onTimeRangeChanged)
.build();
// the constant variable should be filtered out
const constant = constantBuilder()
.withId('constant-1')
.withName('constant-1')
.withOptions('a constant')
.withCurrent('a constant')
.build();
const initialState = {
templating: { variables: { '0': { ...initialVariable }, '1': { ...constant } } },
dashboard,
};
// updated variable state
const updatedVariable = intervalBuilder()
.withId('interval-0')
.withName('interval-0')
.withOptions('1m')
.withCurrent('1m')
.withRefresh(VariableRefresh.onTimeRangeChanged)
.build();
const variable = args.update ? { ...updatedVariable } : { ...initialVariable };
const state = { templating: { variables: { 'interval-0': variable, 'constant-1': { ...constant } } }, dashboard };
const getStateMock = jest
.fn()
.mockReturnValueOnce(initialState)
.mockReturnValue(state);
const dispatchMock = jest.fn();
return {
range,
dependencies,
dispatchMock,
getStateMock,
updateTimeRangeMock,
templateVariableValueUpdatedMock,
startRefreshMock,
emitMock,
};
};
describe('when onTimeRangeUpdated is dispatched', () => {
describe('and options are changed by update', () => {
it('then correct dependencies are called', async () => {
const {
range,
dependencies,
dispatchMock,
getStateMock,
updateTimeRangeMock,
templateVariableValueUpdatedMock,
startRefreshMock,
emitMock,
} = getOnTimeRangeUpdatedContext({ update: true });
await onTimeRangeUpdated(range, dependencies)(dispatchMock, getStateMock, undefined);
expect(dispatchMock).toHaveBeenCalledTimes(0);
expect(getStateMock).toHaveBeenCalledTimes(4);
expect(updateTimeRangeMock).toHaveBeenCalledTimes(1);
expect(updateTimeRangeMock).toHaveBeenCalledWith(range);
expect(templateVariableValueUpdatedMock).toHaveBeenCalledTimes(1);
expect(startRefreshMock).toHaveBeenCalledTimes(1);
expect(emitMock).toHaveBeenCalledTimes(0);
});
});
describe('and options are not changed by update', () => {
it('then correct dependencies are called', async () => {
const {
range,
dependencies,
dispatchMock,
getStateMock,
updateTimeRangeMock,
templateVariableValueUpdatedMock,
startRefreshMock,
emitMock,
} = getOnTimeRangeUpdatedContext({ update: false });
await onTimeRangeUpdated(range, dependencies)(dispatchMock, getStateMock, undefined);
expect(dispatchMock).toHaveBeenCalledTimes(0);
expect(getStateMock).toHaveBeenCalledTimes(3);
expect(updateTimeRangeMock).toHaveBeenCalledTimes(1);
expect(updateTimeRangeMock).toHaveBeenCalledWith(range);
expect(templateVariableValueUpdatedMock).toHaveBeenCalledTimes(0);
expect(startRefreshMock).toHaveBeenCalledTimes(1);
expect(emitMock).toHaveBeenCalledTimes(0);
});
});
describe('and updateOptions throws', () => {
it('then correct dependencies are called', async () => {
const {
range,
dependencies,
dispatchMock,
getStateMock,
updateTimeRangeMock,
templateVariableValueUpdatedMock,
startRefreshMock,
emitMock,
} = getOnTimeRangeUpdatedContext({ update: false, throw: true });
await onTimeRangeUpdated(range, dependencies)(dispatchMock, getStateMock, undefined);
expect(dispatchMock).toHaveBeenCalledTimes(0);
expect(getStateMock).toHaveBeenCalledTimes(1);
expect(updateTimeRangeMock).toHaveBeenCalledTimes(1);
expect(updateTimeRangeMock).toHaveBeenCalledWith(range);
expect(templateVariableValueUpdatedMock).toHaveBeenCalledTimes(0);
expect(startRefreshMock).toHaveBeenCalledTimes(0);
expect(emitMock).toHaveBeenCalledTimes(1);
});
});
});
......@@ -62,14 +62,14 @@ jest.mock('app/features/plugins/datasource_srv', () => ({
}),
}));
variableAdapters.setInit(() => [createCustomVariableAdapter(), createQueryVariableAdapter()]);
describe('processVariable', () => {
// these following processVariable tests will test the following base setup
// custom doesn't depend on any other variable
// queryDependsOnCustom depends on custom
// queryNoDepends doesn't depend on any other variable
const getAndSetupProcessVariableContext = () => {
variableAdapters.set('custom', createCustomVariableAdapter());
variableAdapters.set('query', createQueryVariableAdapter());
const custom = customBuilder()
.withId('custom')
.withName('custom')
......
import { reducerTester } from '../../../../test/core/redux/reducerTester';
import { cleanUpDashboard } from 'app/features/dashboard/state/reducers';
import { VariableHide, VariableModel } from '../../templating/variable';
import { QueryVariableModel, VariableHide, VariableType } from '../../templating/variable';
import { VariableAdapter, variableAdapters } from '../adapters';
import { createAction } from '@reduxjs/toolkit';
import { variablesReducer, VariablesState } from './variablesReducer';
import { toVariablePayload, VariablePayload } from './types';
const variableAdapter: VariableAdapter<QueryVariableModel> = {
id: ('mock' as unknown) as VariableType,
name: 'Mock label',
description: 'Mock description',
dependsOn: jest.fn(),
updateOptions: jest.fn(),
initialState: {} as QueryVariableModel,
reducer: jest.fn().mockReturnValue({}),
getValueForUrl: jest.fn(),
getSaveModel: jest.fn(),
picker: null as any,
editor: null as any,
setValue: jest.fn(),
setValueFromUrl: jest.fn(),
};
variableAdapters.setInit(() => [{ ...variableAdapter }]);
describe('variablesReducer', () => {
describe('when cleanUpDashboard is dispatched', () => {
it('then all variables except global variables should be removed', () => {
......@@ -91,30 +109,16 @@ describe('variablesReducer', () => {
skipUrlSync: false,
},
};
const variableAdapter: VariableAdapter<VariableModel> = {
label: 'Mock label',
description: 'Mock description',
dependsOn: jest.fn(),
updateOptions: jest.fn(),
initialState: {} as VariableModel,
reducer: jest.fn().mockReturnValue(initialState),
getValueForUrl: jest.fn(),
getSaveModel: jest.fn(),
picker: null as any,
editor: null as any,
setValue: jest.fn(),
setValueFromUrl: jest.fn(),
};
variableAdapters.set('query', variableAdapter);
variableAdapters.get('mock').reducer = jest.fn().mockReturnValue(initialState);
const mockAction = createAction<VariablePayload>('mockAction');
reducerTester<VariablesState>()
.givenReducer(variablesReducer, initialState)
.whenActionIsDispatched(mockAction(toVariablePayload({ type: 'query', id: '0' })))
.whenActionIsDispatched(mockAction(toVariablePayload({ type: ('mock' as unknown) as VariableType, id: '0' })))
.thenStateShouldEqual(initialState);
expect(variableAdapter.reducer).toHaveBeenCalledTimes(1);
expect(variableAdapter.reducer).toHaveBeenCalledWith(
expect(variableAdapters.get('mock').reducer).toHaveBeenCalledTimes(1);
expect(variableAdapters.get('mock').reducer).toHaveBeenCalledWith(
initialState,
mockAction(toVariablePayload({ type: 'query', id: '0' }))
mockAction(toVariablePayload({ type: ('mock' as unknown) as VariableType, id: '0' }))
);
});
});
......@@ -132,27 +136,13 @@ describe('variablesReducer', () => {
skipUrlSync: false,
},
};
const variableAdapter: VariableAdapter<VariableModel> = {
label: 'Mock label',
description: 'Mock description',
dependsOn: jest.fn(),
updateOptions: jest.fn(),
initialState: {} as VariableModel,
reducer: jest.fn().mockReturnValue(initialState),
getValueForUrl: jest.fn(),
getSaveModel: jest.fn(),
picker: null as any,
editor: null as any,
setValue: jest.fn(),
setValueFromUrl: jest.fn(),
};
variableAdapters.set('query', variableAdapter);
variableAdapters.get('mock').reducer = jest.fn().mockReturnValue(initialState);
const mockAction = createAction<VariablePayload>('mockAction');
reducerTester<VariablesState>()
.givenReducer(variablesReducer, initialState)
.whenActionIsDispatched(mockAction(toVariablePayload({ type: 'adhoc', id: '0' })))
.thenStateShouldEqual(initialState);
expect(variableAdapter.reducer).toHaveBeenCalledTimes(0);
expect(variableAdapters.get('mock').reducer).toHaveBeenCalledTimes(0);
});
});
......@@ -169,27 +159,13 @@ describe('variablesReducer', () => {
skipUrlSync: false,
},
};
const variableAdapter: VariableAdapter<VariableModel> = {
label: 'Mock label',
description: 'Mock description',
dependsOn: jest.fn(),
updateOptions: jest.fn(),
initialState: {} as VariableModel,
reducer: jest.fn().mockReturnValue(initialState),
getValueForUrl: jest.fn(),
getSaveModel: jest.fn(),
picker: null as any,
editor: null as any,
setValue: jest.fn(),
setValueFromUrl: jest.fn(),
};
variableAdapters.set('query', variableAdapter);
variableAdapters.get('mock').reducer = jest.fn().mockReturnValue(initialState);
const mockAction = createAction<string>('mockAction');
reducerTester<VariablesState>()
.givenReducer(variablesReducer, initialState)
.whenActionIsDispatched(mockAction('mocked'))
.thenStateShouldEqual(initialState);
expect(variableAdapter.reducer).toHaveBeenCalledTimes(0);
expect(variableAdapters.get('mock').reducer).toHaveBeenCalledTimes(0);
});
});
});
......@@ -24,12 +24,13 @@ import { getVariableState, getVariableTestContext } from './helpers';
import { initialVariablesState, VariablesState } from './variablesReducer';
import { changeVariableNameSucceeded } from '../editor/reducer';
variableAdapters.setInit(() => [createQueryVariableAdapter()]);
describe('sharedReducer', () => {
describe('when addVariable is dispatched', () => {
it('then state should be correct', () => {
const model = ({ name: 'name from model', type: 'type from model' } as unknown) as QueryVariableModel;
const payload = toVariablePayload({ id: '0', type: 'query' }, { global: true, index: 0, model });
variableAdapters.set('query', createQueryVariableAdapter());
reducerTester<VariablesState>()
.givenReducer(sharedReducer, { ...initialVariablesState })
.whenActionIsDispatched(addVariable(payload))
......@@ -107,7 +108,6 @@ describe('sharedReducer', () => {
describe('when duplicateVariable is dispatched', () => {
it('then state should be correct', () => {
variableAdapters.set('query', createQueryVariableAdapter());
const initialState: VariablesState = getVariableState(3);
const payload = toVariablePayload({ id: '1', type: 'query' }, { newId: '11' });
reducerTester<VariablesState>()
......@@ -193,7 +193,6 @@ describe('sharedReducer', () => {
describe('when storeNewVariable is dispatched', () => {
it('then state should be correct', () => {
variableAdapters.set('query', createQueryVariableAdapter());
const initialState: VariablesState = getVariableState(3, -1, true);
const payload = toVariablePayload({ id: '11', type: 'query' });
reducerTester<VariablesState>()
......
......@@ -27,7 +27,7 @@ export const variablesReducer = (
return variables;
}
if (action?.payload?.type && variableAdapters.contains(action?.payload?.type)) {
if (action?.payload?.type && variableAdapters.getIfExists(action?.payload?.type)) {
// Now that we know we are dealing with a payload that is addressed for an adapted variable let's reduce state:
// Firstly call the sharedTemplatingReducer that handles all shared actions between variable types
// Secondly call the specific variable type's reducer
......
......@@ -11,7 +11,7 @@ import { setCurrentVariableValue } from '../state/sharedReducer';
import { initDashboardTemplating } from '../state/actions';
describe('textbox actions', () => {
variableAdapters.set('textbox', createTextBoxVariableAdapter());
variableAdapters.setInit(() => [createTextBoxVariableAdapter()]);
describe('when updateTextBoxVariableOptions is dispatched', () => {
it('then correct actions are dispatched', async () => {
......
......@@ -12,8 +12,9 @@ import { toVariableIdentifier } from '../state/types';
export const createTextBoxVariableAdapter = (): VariableAdapter<TextBoxVariableModel> => {
return {
id: 'textbox',
description: 'Define a textbox variable, where users can enter any arbitrary string',
label: 'Text box',
name: 'Text box',
initialState: initialTextBoxVariableModelState,
reducer: textBoxVariableReducer,
picker: TextBoxVariablePicker,
......
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