grafana/public/app/features/variables/state/actions.test.ts
Hugo Häggmark b4f00d6312
Variables: Only update panels that are impacted by variable change (#39420)
* Refactor: adds affectedPanelIds and fixes some bugs

* Refactor: Fixes all dependencies and affected panel ids

* Refactor: glue it together with events

* Chore: remove debug code

* Chore: remove unused events

* Chore: removes unused function

* Chore: reverts processRepeats

* Chore: update to use redux state

* Refactor: adds feature toggle in variables settings

* Refactor: adds appEvents to jest-setup

* Tests: adds tests for strict panel refresh logic

* Refactor: small refactor

* Refactor: moved to more events

* Tests: fixes test

* Refactor: makes sure we store strictPanelRefreshMode in dashboard model

* Refactor: reporting and adds tests

* Tests: fix broken tests

* Tests: fix broken initDashboard test

* Tests: fix broken Wrapper test

* Refactor: adds solution for $__all_variables

* Chore: updates to radio button

* Refactor: removes toggle and calculates threshold instead

* Chore: fix up tests

* Refactor: moving functions around

* Tests: fixes broken test

* Update public/app/features/dashboard/services/TimeSrv.ts

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>

* Chore: fix after PR comments

* Chore: fix import and add comment

* Chore: update after PR comments

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>
2021-11-09 12:30:04 +01:00

847 lines
35 KiB
TypeScript

import { AnyAction } from 'redux';
import { getRootReducer, getTemplatingRootReducer, RootReducerType, TemplatingReducerType } from './helpers';
import { variableAdapters } from '../adapters';
import { createQueryVariableAdapter } from '../query/adapter';
import { createCustomVariableAdapter } from '../custom/adapter';
import { createTextBoxVariableAdapter } from '../textbox/adapter';
import { createConstantVariableAdapter } from '../constant/adapter';
import { reduxTester } from '../../../../test/core/redux/reduxTester';
import { TemplatingState } from 'app/features/variables/state/reducers';
import {
cancelVariables,
changeVariableMultiValue,
cleanUpVariables,
fixSelectedInconsistency,
initDashboardTemplating,
initVariablesTransaction,
isVariableUrlValueDifferentFromCurrent,
processVariables,
validateVariableSelectionState,
} from './actions';
import {
addVariable,
changeVariableProp,
removeVariable,
setCurrentVariableValue,
variableStateCompleted,
variableStateFetching,
variableStateNotStarted,
} from './sharedReducer';
import {
ALL_VARIABLE_TEXT,
ALL_VARIABLE_VALUE,
NEW_VARIABLE_ID,
toVariableIdentifier,
toVariablePayload,
} from './types';
import {
constantBuilder,
customBuilder,
datasourceBuilder,
queryBuilder,
textboxBuilder,
} from '../shared/testing/builders';
import { changeVariableName } from '../editor/actions';
import {
changeVariableNameFailed,
changeVariableNameSucceeded,
cleanEditorState,
initialVariableEditorState,
setIdInEditor,
} from '../editor/reducer';
import {
TransactionStatus,
variablesClearTransaction,
variablesCompleteTransaction,
variablesInitTransaction,
} from './transactionReducer';
import { cleanPickerState, initialState } from '../pickers/OptionsPicker/reducer';
import { cleanVariables } from './variablesReducer';
import { expect } from '../../../../test/lib/common';
import { ConstantVariableModel, VariableRefresh } from '../types';
import { updateVariableOptions } from '../query/reducer';
import { setVariableQueryRunner, VariableQueryRunner } from '../query/VariableQueryRunner';
import * as runtime from '@grafana/runtime';
import { LoadingState } from '@grafana/data';
import { toAsyncOfResult } from '../../query/state/DashboardQueryRunner/testHelpers';
variableAdapters.setInit(() => [
createQueryVariableAdapter(),
createCustomVariableAdapter(),
createTextBoxVariableAdapter(),
createConstantVariableAdapter(),
]);
const metricFindQuery = jest
.fn()
.mockResolvedValueOnce([{ text: 'responses' }, { text: 'timers' }])
.mockResolvedValue([{ text: '200' }, { text: '500' }]);
const getMetricSources = jest.fn().mockReturnValue([]);
const getDatasource = jest.fn().mockResolvedValue({ metricFindQuery });
jest.mock('app/features/dashboard/services/TimeSrv', () => ({
getTimeSrv: () => ({
timeRange: jest.fn().mockReturnValue(undefined),
}),
}));
runtime.setDataSourceSrv({
get: getDatasource,
getList: getMetricSources,
} as any);
describe('shared actions', () => {
describe('when initDashboardTemplating is dispatched', () => {
it('then correct actions are dispatched', () => {
const query = queryBuilder().build();
const constant = constantBuilder().build();
const datasource = datasourceBuilder().build();
const custom = customBuilder().build();
const textbox = textboxBuilder().build();
const list = [query, constant, datasource, custom, textbox];
reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(initDashboardTemplating(list))
.thenDispatchedActionsPredicateShouldEqual((dispatchedActions) => {
expect(dispatchedActions.length).toEqual(8);
expect(dispatchedActions[0]).toEqual(
addVariable(toVariablePayload(query, { global: false, index: 0, model: query }))
);
expect(dispatchedActions[1]).toEqual(
addVariable(toVariablePayload(constant, { global: false, index: 1, model: constant }))
);
expect(dispatchedActions[2]).toEqual(
addVariable(toVariablePayload(custom, { global: false, index: 2, model: custom }))
);
expect(dispatchedActions[3]).toEqual(
addVariable(toVariablePayload(textbox, { global: false, index: 3, model: textbox }))
);
// because uuid are dynamic we need to get the uuid from the resulting state
// an alternative would be to add our own uuids in the model above instead
expect(dispatchedActions[4]).toEqual(
variableStateNotStarted(toVariablePayload({ ...query, id: dispatchedActions[4].payload.id }))
);
expect(dispatchedActions[5]).toEqual(
variableStateNotStarted(toVariablePayload({ ...constant, id: dispatchedActions[5].payload.id }))
);
expect(dispatchedActions[6]).toEqual(
variableStateNotStarted(toVariablePayload({ ...custom, id: dispatchedActions[6].payload.id }))
);
expect(dispatchedActions[7]).toEqual(
variableStateNotStarted(toVariablePayload({ ...textbox, id: dispatchedActions[7].payload.id }))
);
return true;
});
});
});
describe('when processVariables is dispatched', () => {
it('then correct actions are dispatched', async () => {
const query = queryBuilder().build();
const constant = constantBuilder().build();
const datasource = datasourceBuilder().build();
const custom = customBuilder().build();
const textbox = textboxBuilder().build();
const list = [query, constant, datasource, custom, textbox];
const preloadedState = {
templating: ({} as unknown) as TemplatingState,
};
const locationService: any = { getSearchObject: () => ({}) };
runtime.setLocationService(locationService);
const variableQueryRunner: any = {
cancelRequest: jest.fn(),
queueRequest: jest.fn(),
getResponse: () => toAsyncOfResult({ state: LoadingState.Done, identifier: toVariableIdentifier(query) }),
destroy: jest.fn(),
};
setVariableQueryRunner(variableQueryRunner);
const tester = await reduxTester<TemplatingReducerType>({ preloadedState })
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(variablesInitTransaction({ uid: '' }))
.whenActionIsDispatched(initDashboardTemplating(list))
.whenAsyncActionIsDispatched(processVariables(), true);
await tester.thenDispatchedActionsPredicateShouldEqual((dispatchedActions) => {
expect(dispatchedActions.length).toEqual(5);
expect(dispatchedActions[0]).toEqual(
variableStateFetching(toVariablePayload({ ...query, id: dispatchedActions[0].payload.id }))
);
expect(dispatchedActions[1]).toEqual(
variableStateCompleted(toVariablePayload({ ...constant, id: dispatchedActions[1].payload.id }))
);
expect(dispatchedActions[2]).toEqual(
variableStateCompleted(toVariablePayload({ ...custom, id: dispatchedActions[2].payload.id }))
);
expect(dispatchedActions[3]).toEqual(
variableStateCompleted(toVariablePayload({ ...textbox, id: dispatchedActions[3].payload.id }))
);
expect(dispatchedActions[4]).toEqual(
variableStateCompleted(toVariablePayload({ ...query, id: dispatchedActions[4].payload.id }))
);
return true;
});
});
// Fix for https://github.com/grafana/grafana/issues/28791
it('fix for https://github.com/grafana/grafana/issues/28791', async () => {
setVariableQueryRunner(new VariableQueryRunner());
const stats = queryBuilder()
.withId('stats')
.withName('stats')
.withQuery('stats.*')
.withRefresh(VariableRefresh.onDashboardLoad)
.withCurrent(['response'], ['response'])
.withMulti()
.withIncludeAll()
.build();
const substats = queryBuilder()
.withId('substats')
.withName('substats')
.withQuery('stats.$stats.*')
.withRefresh(VariableRefresh.onDashboardLoad)
.withCurrent([ALL_VARIABLE_TEXT], [ALL_VARIABLE_VALUE])
.withMulti()
.withIncludeAll()
.build();
const list = [stats, substats];
const query = { orgId: '1', 'var-stats': 'response', 'var-substats': ALL_VARIABLE_TEXT };
const locationService: any = { getSearchObject: () => query };
runtime.setLocationService(locationService);
const preloadedState = {
templating: ({} as unknown) as TemplatingState,
};
const tester = await reduxTester<TemplatingReducerType>({ preloadedState })
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(variablesInitTransaction({ uid: '' }))
.whenActionIsDispatched(initDashboardTemplating(list))
.whenAsyncActionIsDispatched(processVariables(), true);
await tester.thenDispatchedActionsShouldEqual(
variableStateFetching(toVariablePayload(stats)),
updateVariableOptions(
toVariablePayload(stats, { results: [{ text: 'responses' }, { text: 'timers' }], templatedRegex: '' })
),
setCurrentVariableValue(
toVariablePayload(stats, { option: { text: ALL_VARIABLE_TEXT, value: ALL_VARIABLE_VALUE, selected: false } })
),
variableStateCompleted(toVariablePayload(stats)),
setCurrentVariableValue(
toVariablePayload(stats, { option: { text: ['response'], value: ['response'], selected: false } })
),
variableStateFetching(toVariablePayload(substats)),
updateVariableOptions(
toVariablePayload(substats, { results: [{ text: '200' }, { text: '500' }], templatedRegex: '' })
),
setCurrentVariableValue(
toVariablePayload(substats, {
option: { text: [ALL_VARIABLE_TEXT], value: [ALL_VARIABLE_VALUE], selected: true },
})
),
variableStateCompleted(toVariablePayload(substats)),
setCurrentVariableValue(
toVariablePayload(substats, {
option: { text: [ALL_VARIABLE_TEXT], value: [ALL_VARIABLE_VALUE], selected: false },
})
)
);
});
});
describe('when validateVariableSelectionState is dispatched with a custom variable (no dependencies)', () => {
describe('and not multivalue', () => {
it.each`
withOptions | withCurrent | defaultValue | expected
${['A', 'B', 'C']} | ${undefined} | ${undefined} | ${'A'}
${['A', 'B', 'C']} | ${'B'} | ${undefined} | ${'B'}
${['A', 'B', 'C']} | ${'B'} | ${'C'} | ${'B'}
${['A', 'B', 'C']} | ${'X'} | ${undefined} | ${'A'}
${['A', 'B', 'C']} | ${'X'} | ${'C'} | ${'C'}
${undefined} | ${'B'} | ${undefined} | ${'should not dispatch setCurrentVariableValue'}
`('then correct actions are dispatched', async ({ withOptions, withCurrent, defaultValue, expected }) => {
let custom;
if (!withOptions) {
custom = customBuilder().withId('0').withCurrent(withCurrent).withoutOptions().build();
} else {
custom = customBuilder()
.withId('0')
.withOptions(...withOptions)
.withCurrent(withCurrent)
.build();
}
const tester = await reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(addVariable(toVariablePayload(custom, { global: false, index: 0, model: custom })))
.whenAsyncActionIsDispatched(
validateVariableSelectionState(toVariableIdentifier(custom), defaultValue),
true
);
await tester.thenDispatchedActionsPredicateShouldEqual((dispatchedActions) => {
const expectedActions: AnyAction[] = !withOptions
? []
: [
setCurrentVariableValue(
toVariablePayload(
{ type: 'custom', id: '0' },
{ option: { text: expected, value: expected, selected: false } }
)
),
];
expect(dispatchedActions).toEqual(expectedActions);
return true;
});
});
});
describe('and multivalue', () => {
it.each`
withOptions | withCurrent | defaultValue | expectedText | expectedSelected
${['A', 'B', 'C']} | ${['B']} | ${undefined} | ${['B']} | ${true}
${['A', 'B', 'C']} | ${['B']} | ${'C'} | ${['B']} | ${true}
${['A', 'B', 'C']} | ${['B', 'C']} | ${undefined} | ${['B', 'C']} | ${true}
${['A', 'B', 'C']} | ${['B', 'C']} | ${'C'} | ${['B', 'C']} | ${true}
${['A', 'B', 'C']} | ${['X']} | ${undefined} | ${'A'} | ${false}
${['A', 'B', 'C']} | ${['X']} | ${'C'} | ${'A'} | ${false}
`(
'then correct actions are dispatched',
async ({ withOptions, withCurrent, defaultValue, expectedText, expectedSelected }) => {
let custom;
if (!withOptions) {
custom = customBuilder().withId('0').withMulti().withCurrent(withCurrent).withoutOptions().build();
} else {
custom = customBuilder()
.withId('0')
.withMulti()
.withOptions(...withOptions)
.withCurrent(withCurrent)
.build();
}
const tester = await reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(addVariable(toVariablePayload(custom, { global: false, index: 0, model: custom })))
.whenAsyncActionIsDispatched(
validateVariableSelectionState(toVariableIdentifier(custom), defaultValue),
true
);
await tester.thenDispatchedActionsPredicateShouldEqual((dispatchedActions) => {
const expectedActions: AnyAction[] = !withOptions
? []
: [
setCurrentVariableValue(
toVariablePayload(
{ type: 'custom', id: '0' },
{ option: { text: expectedText, value: expectedText, selected: expectedSelected } }
)
),
];
expect(dispatchedActions).toEqual(expectedActions);
return true;
});
}
);
});
});
describe('changeVariableName', () => {
describe('when changeVariableName is dispatched with the same name', () => {
it('then the correct actions are dispatched', () => {
const textbox = textboxBuilder().withId('textbox').withName('textbox').build();
const constant = constantBuilder().withId('constant').withName('constant').build();
reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(addVariable(toVariablePayload(textbox, { global: false, index: 0, model: textbox })))
.whenActionIsDispatched(
addVariable(toVariablePayload(constant, { global: false, index: 1, model: constant }))
)
.whenActionIsDispatched(changeVariableName(toVariableIdentifier(constant), constant.name), true)
.thenDispatchedActionsShouldEqual(
changeVariableNameSucceeded({ type: 'constant', id: 'constant', data: { newName: 'constant' } })
);
});
});
describe('when changeVariableName is dispatched with an unique name', () => {
it('then the correct actions are dispatched', () => {
const textbox = textboxBuilder().withId('textbox').withName('textbox').build();
const constant = constantBuilder().withId('constant').withName('constant').build();
reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(addVariable(toVariablePayload(textbox, { global: false, index: 0, model: textbox })))
.whenActionIsDispatched(
addVariable(toVariablePayload(constant, { global: false, index: 1, model: constant }))
)
.whenActionIsDispatched(changeVariableName(toVariableIdentifier(constant), 'constant1'), true)
.thenDispatchedActionsShouldEqual(
addVariable({
type: 'constant',
id: 'constant1',
data: {
global: false,
index: 1,
model: {
...constant,
name: 'constant1',
id: 'constant1',
global: false,
index: 1,
current: { selected: true, text: '', value: '' },
options: [{ selected: true, text: '', value: '' }],
} as ConstantVariableModel,
},
}),
changeVariableNameSucceeded({ type: 'constant', id: 'constant1', data: { newName: 'constant1' } }),
setIdInEditor({ id: 'constant1' }),
removeVariable({ type: 'constant', id: 'constant', data: { reIndex: false } })
);
});
});
describe('when changeVariableName is dispatched with an unique name for a new variable', () => {
it('then the correct actions are dispatched', () => {
const textbox = textboxBuilder().withId('textbox').withName('textbox').build();
const constant = constantBuilder().withId(NEW_VARIABLE_ID).withName('constant').build();
reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(addVariable(toVariablePayload(textbox, { global: false, index: 0, model: textbox })))
.whenActionIsDispatched(
addVariable(toVariablePayload(constant, { global: false, index: 1, model: constant }))
)
.whenActionIsDispatched(changeVariableName(toVariableIdentifier(constant), 'constant1'), true)
.thenDispatchedActionsShouldEqual(
addVariable({
type: 'constant',
id: 'constant1',
data: {
global: false,
index: 1,
model: {
...constant,
name: 'constant1',
id: 'constant1',
global: false,
index: 1,
current: { selected: true, text: '', value: '' },
options: [{ selected: true, text: '', value: '' }],
} as ConstantVariableModel,
},
}),
changeVariableNameSucceeded({ type: 'constant', id: 'constant1', data: { newName: 'constant1' } }),
setIdInEditor({ id: 'constant1' }),
removeVariable({ type: 'constant', id: NEW_VARIABLE_ID, data: { reIndex: false } })
);
});
});
describe('when changeVariableName is dispatched with __newName', () => {
it('then the correct actions are dispatched', () => {
const textbox = textboxBuilder().withId('textbox').withName('textbox').build();
const constant = constantBuilder().withId('constant').withName('constant').build();
reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(addVariable(toVariablePayload(textbox, { global: false, index: 0, model: textbox })))
.whenActionIsDispatched(
addVariable(toVariablePayload(constant, { global: false, index: 1, model: constant }))
)
.whenActionIsDispatched(changeVariableName(toVariableIdentifier(constant), '__newName'), true)
.thenDispatchedActionsShouldEqual(
changeVariableNameFailed({
newName: '__newName',
errorText: "Template names cannot begin with '__', that's reserved for Grafana's global variables",
})
);
});
});
describe('when changeVariableName is dispatched with illegal characters', () => {
it('then the correct actions are dispatched', () => {
const textbox = textboxBuilder().withId('textbox').withName('textbox').build();
const constant = constantBuilder().withId('constant').withName('constant').build();
reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(addVariable(toVariablePayload(textbox, { global: false, index: 0, model: textbox })))
.whenActionIsDispatched(
addVariable(toVariablePayload(constant, { global: false, index: 1, model: constant }))
)
.whenActionIsDispatched(changeVariableName(toVariableIdentifier(constant), '#constant!'), true)
.thenDispatchedActionsShouldEqual(
changeVariableNameFailed({
newName: '#constant!',
errorText: 'Only word and digit characters are allowed in variable names',
})
);
});
});
describe('when changeVariableName is dispatched with a name that is already used', () => {
it('then the correct actions are dispatched', () => {
const textbox = textboxBuilder().withId('textbox').withName('textbox').build();
const constant = constantBuilder().withId('constant').withName('constant').build();
reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(addVariable(toVariablePayload(textbox, { global: false, index: 0, model: textbox })))
.whenActionIsDispatched(
addVariable(toVariablePayload(constant, { global: false, index: 1, model: constant }))
)
.whenActionIsDispatched(changeVariableName(toVariableIdentifier(constant), 'textbox'), true)
.thenDispatchedActionsShouldEqual(
changeVariableNameFailed({
newName: 'textbox',
errorText: 'Variable with the same name already exists',
})
);
});
});
});
describe('changeVariableMultiValue', () => {
describe('when changeVariableMultiValue is dispatched for variable with multi enabled', () => {
it('then correct actions are dispatched', () => {
const custom = customBuilder().withId('custom').withMulti(true).withCurrent(['A'], ['A']).build();
reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(addVariable(toVariablePayload(custom, { global: false, index: 0, model: custom })))
.whenActionIsDispatched(changeVariableMultiValue(toVariableIdentifier(custom), false), true)
.thenDispatchedActionsShouldEqual(
changeVariableProp(
toVariablePayload(custom, {
propName: 'multi',
propValue: false,
})
),
changeVariableProp(
toVariablePayload(custom, {
propName: 'current',
propValue: {
value: 'A',
text: 'A',
selected: true,
},
})
)
);
});
});
describe('when changeVariableMultiValue is dispatched for variable with multi disabled', () => {
it('then correct actions are dispatched', () => {
const custom = customBuilder().withId('custom').withMulti(false).withCurrent(['A'], ['A']).build();
reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(addVariable(toVariablePayload(custom, { global: false, index: 0, model: custom })))
.whenActionIsDispatched(changeVariableMultiValue(toVariableIdentifier(custom), true), true)
.thenDispatchedActionsShouldEqual(
changeVariableProp(
toVariablePayload(custom, {
propName: 'multi',
propValue: true,
})
),
changeVariableProp(
toVariablePayload(custom, {
propName: 'current',
propValue: {
value: ['A'],
text: ['A'],
selected: true,
},
})
)
);
});
});
});
describe('initVariablesTransaction', () => {
function getTestContext() {
const reportSpy = jest.spyOn(runtime, 'reportInteraction').mockReturnValue(undefined);
const constant = constantBuilder().withId('constant').withName('constant').build();
const templating: any = { list: [constant] };
const uid = 'uid';
const dashboard: any = { title: 'Some dash', uid, templating };
return { reportSpy, constant, templating, uid, dashboard };
}
describe('when called and the previous dashboard has completed', () => {
it('then correct actions are dispatched', async () => {
const { constant, uid, dashboard } = getTestContext();
const tester = await reduxTester<RootReducerType>()
.givenRootReducer(getRootReducer())
.whenAsyncActionIsDispatched(initVariablesTransaction(uid, dashboard));
tester.thenDispatchedActionsPredicateShouldEqual((dispatchedActions) => {
expect(dispatchedActions[0]).toEqual(variablesInitTransaction({ uid }));
expect(dispatchedActions[1].type).toEqual(addVariable.type);
expect(dispatchedActions[1].payload.id).toEqual('__dashboard');
expect(dispatchedActions[2].type).toEqual(addVariable.type);
expect(dispatchedActions[2].payload.id).toEqual('__org');
expect(dispatchedActions[3].type).toEqual(addVariable.type);
expect(dispatchedActions[3].payload.id).toEqual('__user');
expect(dispatchedActions[4]).toEqual(
addVariable(toVariablePayload(constant, { global: false, index: 0, model: constant }))
);
expect(dispatchedActions[5]).toEqual(variableStateNotStarted(toVariablePayload(constant)));
expect(dispatchedActions[6]).toEqual(variableStateCompleted(toVariablePayload(constant)));
expect(dispatchedActions[7]).toEqual(variablesCompleteTransaction({ uid }));
return dispatchedActions.length === 8;
});
});
});
describe('when called and the previous dashboard is still processing variables', () => {
it('then correct actions are dispatched', async () => {
const { constant, uid, dashboard } = getTestContext();
const transactionState = { uid: 'previous-uid', status: TransactionStatus.Fetching };
const tester = await reduxTester<RootReducerType>({
preloadedState: ({
templating: {
transaction: transactionState,
variables: {},
optionsPicker: { ...initialState },
editor: { ...initialVariableEditorState },
},
} as unknown) as RootReducerType,
})
.givenRootReducer(getRootReducer())
.whenAsyncActionIsDispatched(initVariablesTransaction(uid, dashboard));
tester.thenDispatchedActionsPredicateShouldEqual((dispatchedActions) => {
expect(dispatchedActions[0]).toEqual(cleanVariables());
expect(dispatchedActions[1]).toEqual(cleanEditorState());
expect(dispatchedActions[2]).toEqual(cleanPickerState());
expect(dispatchedActions[3]).toEqual(variablesClearTransaction());
expect(dispatchedActions[4]).toEqual(variablesInitTransaction({ uid }));
expect(dispatchedActions[5].type).toEqual(addVariable.type);
expect(dispatchedActions[5].payload.id).toEqual('__dashboard');
expect(dispatchedActions[6].type).toEqual(addVariable.type);
expect(dispatchedActions[6].payload.id).toEqual('__org');
expect(dispatchedActions[7].type).toEqual(addVariable.type);
expect(dispatchedActions[7].payload.id).toEqual('__user');
expect(dispatchedActions[8]).toEqual(
addVariable(toVariablePayload(constant, { global: false, index: 0, model: constant }))
);
expect(dispatchedActions[9]).toEqual(variableStateNotStarted(toVariablePayload(constant)));
expect(dispatchedActions[10]).toEqual(variableStateCompleted(toVariablePayload(constant)));
expect(dispatchedActions[11]).toEqual(variablesCompleteTransaction({ uid }));
return dispatchedActions.length === 12;
});
});
});
});
describe('cleanUpVariables', () => {
describe('when called', () => {
it('then correct actions are dispatched', async () => {
reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(cleanUpVariables())
.thenDispatchedActionsShouldEqual(
cleanVariables(),
cleanEditorState(),
cleanPickerState(),
variablesClearTransaction()
);
});
});
});
describe('cancelVariables', () => {
const cancelAllInFlightRequestsMock = jest.fn();
const backendSrvMock: any = {
cancelAllInFlightRequests: cancelAllInFlightRequestsMock,
};
describe('when called', () => {
it('then cancelAllInFlightRequests should be called and correct actions are dispatched', async () => {
reduxTester<{ templating: TemplatingState }>()
.givenRootReducer(getTemplatingRootReducer())
.whenActionIsDispatched(cancelVariables({ getBackendSrv: () => backendSrvMock }))
.thenDispatchedActionsShouldEqual(
cleanVariables(),
cleanEditorState(),
cleanPickerState(),
variablesClearTransaction()
);
expect(cancelAllInFlightRequestsMock).toHaveBeenCalledTimes(1);
});
});
});
describe('fixSelectedInconsistency', () => {
describe('when called for a single value variable', () => {
describe('and there is an inconsistency between current and selected in options', () => {
it('then it should set the correct selected', () => {
const variable = customBuilder().withId('custom').withCurrent('A').withOptions('A', 'B', 'C').build();
variable.options[1].selected = true;
expect(variable.options).toEqual([
{ text: 'A', value: 'A', selected: false },
{ text: 'B', value: 'B', selected: true },
{ text: 'C', value: 'C', selected: false },
]);
fixSelectedInconsistency(variable);
expect(variable.options).toEqual([
{ text: 'A', value: 'A', selected: true },
{ text: 'B', value: 'B', selected: false },
{ text: 'C', value: 'C', selected: false },
]);
});
});
describe('and there is no matching option in options', () => {
it('then the first option should be selected', () => {
const variable = customBuilder().withId('custom').withCurrent('A').withOptions('X', 'Y', 'Z').build();
expect(variable.options).toEqual([
{ text: 'X', value: 'X', selected: false },
{ text: 'Y', value: 'Y', selected: false },
{ text: 'Z', value: 'Z', selected: false },
]);
fixSelectedInconsistency(variable);
expect(variable.options).toEqual([
{ text: 'X', value: 'X', selected: true },
{ text: 'Y', value: 'Y', selected: false },
{ text: 'Z', value: 'Z', selected: false },
]);
});
});
});
describe('when called for a multi value variable', () => {
describe('and there is an inconsistency between current and selected in options', () => {
it('then it should set the correct selected', () => {
const variable = customBuilder().withId('custom').withCurrent(['A', 'C']).withOptions('A', 'B', 'C').build();
variable.options[1].selected = true;
expect(variable.options).toEqual([
{ text: 'A', value: 'A', selected: false },
{ text: 'B', value: 'B', selected: true },
{ text: 'C', value: 'C', selected: false },
]);
fixSelectedInconsistency(variable);
expect(variable.options).toEqual([
{ text: 'A', value: 'A', selected: true },
{ text: 'B', value: 'B', selected: false },
{ text: 'C', value: 'C', selected: true },
]);
});
});
describe('and there is no matching option in options', () => {
it('then the first option should be selected', () => {
const variable = customBuilder().withId('custom').withCurrent(['A', 'C']).withOptions('X', 'Y', 'Z').build();
expect(variable.options).toEqual([
{ text: 'X', value: 'X', selected: false },
{ text: 'Y', value: 'Y', selected: false },
{ text: 'Z', value: 'Z', selected: false },
]);
fixSelectedInconsistency(variable);
expect(variable.options).toEqual([
{ text: 'X', value: 'X', selected: true },
{ text: 'Y', value: 'Y', selected: false },
{ text: 'Z', value: 'Z', selected: false },
]);
});
});
});
});
describe('isVariableUrlValueDifferentFromCurrent', () => {
describe('when called with a single valued variable', () => {
describe('and values are equal', () => {
it('then it should return false', () => {
const variable = queryBuilder().withMulti(false).withCurrent('A', 'A').build();
const urlValue = 'A';
expect(isVariableUrlValueDifferentFromCurrent(variable, urlValue)).toBe(false);
});
});
describe('and values are different', () => {
it('then it should return true', () => {
const variable = queryBuilder().withMulti(false).withCurrent('A', 'A').build();
const urlValue = 'B';
expect(isVariableUrlValueDifferentFromCurrent(variable, urlValue)).toBe(true);
});
});
});
describe('when called with a multi valued variable', () => {
describe('and values are equal', () => {
it('then it should return false', () => {
const variable = queryBuilder().withMulti(true).withCurrent(['A'], ['A']).build();
const urlValue = ['A'];
expect(isVariableUrlValueDifferentFromCurrent(variable, urlValue)).toBe(false);
});
describe('but urlValue is not an array', () => {
it('then it should return false', () => {
const variable = queryBuilder().withMulti(true).withCurrent(['A'], ['A']).build();
const urlValue = 'A';
expect(isVariableUrlValueDifferentFromCurrent(variable, urlValue)).toBe(false);
});
});
});
describe('and values are different', () => {
it('then it should return true', () => {
const variable = queryBuilder().withMulti(true).withCurrent(['A'], ['A']).build();
const urlValue = ['C'];
expect(isVariableUrlValueDifferentFromCurrent(variable, urlValue)).toBe(true);
});
describe('but urlValue is not an array', () => {
it('then it should return true', () => {
const variable = queryBuilder().withMulti(true).withCurrent(['A'], ['A']).build();
const urlValue = 'C';
expect(isVariableUrlValueDifferentFromCurrent(variable, urlValue)).toBe(true);
});
});
});
});
});
});