onTimeRangeUpdated.test.ts 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import { dateTime, TimeRange } from '@grafana/data';
  2. import { reduxTester } from '../../../../test/core/redux/reduxTester';
  3. import { silenceConsoleOutput } from '../../../../test/core/utils/silenceConsoleOutput';
  4. import { expect } from '../../../../test/lib/common';
  5. import { appEvents } from '../../../core/core';
  6. import { notifyApp } from '../../../core/reducers/appNotification';
  7. import { DashboardState } from '../../../types';
  8. import { DashboardModel } from '../../dashboard/state';
  9. import { TemplateSrv } from '../../templating/template_srv';
  10. import { variableAdapters } from '../adapters';
  11. import { createConstantVariableAdapter } from '../constant/adapter';
  12. import { createIntervalVariableAdapter } from '../interval/adapter';
  13. import { createIntervalOptions } from '../interval/reducer';
  14. import { constantBuilder, intervalBuilder } from '../shared/testing/builders';
  15. import { VariableRefresh } from '../types';
  16. import { toKeyedVariableIdentifier, toVariablePayload } from '../utils';
  17. import { onTimeRangeUpdated, OnTimeRangeUpdatedDependencies, setOptionAsCurrent } from './actions';
  18. import { getPreloadedState, getRootReducer, RootReducerType } from './helpers';
  19. import { toKeyedAction } from './keyedVariablesReducer';
  20. import {
  21. setCurrentVariableValue,
  22. variableStateCompleted,
  23. variableStateFailed,
  24. variableStateFetching,
  25. } from './sharedReducer';
  26. import { variablesInitTransaction } from './transactionReducer';
  27. variableAdapters.setInit(() => [createIntervalVariableAdapter(), createConstantVariableAdapter()]);
  28. const getTestContext = (dashboard: DashboardModel) => {
  29. jest.clearAllMocks();
  30. const key = 'key';
  31. const interval = intervalBuilder()
  32. .withId('interval-0')
  33. .withRootStateKey(key)
  34. .withName('interval-0')
  35. .withOptions('1m', '10m', '30m', '1h', '6h', '12h', '1d', '7d', '14d', '30d')
  36. .withCurrent('1m')
  37. .withRefresh(VariableRefresh.onTimeRangeChanged)
  38. .build();
  39. const constant = constantBuilder()
  40. .withId('constant-1')
  41. .withRootStateKey(key)
  42. .withName('constant-1')
  43. .withOptions('a constant')
  44. .withCurrent('a constant')
  45. .build();
  46. const range: TimeRange = {
  47. from: dateTime(new Date().getTime()).subtract(1, 'minutes'),
  48. to: dateTime(new Date().getTime()),
  49. raw: {
  50. from: 'now-1m',
  51. to: 'now',
  52. },
  53. };
  54. const updateTimeRangeMock = jest.fn();
  55. const templateSrvMock = { updateTimeRange: updateTimeRangeMock } as unknown as TemplateSrv;
  56. const dependencies: OnTimeRangeUpdatedDependencies = { templateSrv: templateSrvMock, events: appEvents };
  57. const templateVariableValueUpdatedMock = jest.fn();
  58. const startRefreshMock = jest.fn();
  59. dashboard.templateVariableValueUpdated = templateVariableValueUpdatedMock;
  60. dashboard.startRefresh = startRefreshMock;
  61. const dashboardState = {
  62. getModel: () => dashboard,
  63. } as unknown as DashboardState;
  64. const adapter = variableAdapters.get('interval');
  65. const templatingState = {
  66. variables: {
  67. 'interval-0': { ...interval },
  68. 'constant-1': { ...constant },
  69. },
  70. };
  71. const preloadedState = {
  72. dashboard: dashboardState,
  73. ...getPreloadedState(key, templatingState),
  74. } as unknown as RootReducerType;
  75. return {
  76. key,
  77. interval,
  78. range,
  79. dependencies,
  80. adapter,
  81. preloadedState,
  82. updateTimeRangeMock,
  83. templateVariableValueUpdatedMock,
  84. startRefreshMock,
  85. };
  86. };
  87. describe('when onTimeRangeUpdated is dispatched', () => {
  88. describe('and options are changed by update', () => {
  89. it('then correct actions are dispatched and correct dependencies are called', async () => {
  90. const {
  91. key,
  92. preloadedState,
  93. range,
  94. dependencies,
  95. updateTimeRangeMock,
  96. templateVariableValueUpdatedMock,
  97. startRefreshMock,
  98. } = getTestContext(getDashboardModel());
  99. const tester = await reduxTester<RootReducerType>({ preloadedState })
  100. .givenRootReducer(getRootReducer())
  101. .whenActionIsDispatched(toKeyedAction(key, variablesInitTransaction({ uid: key })))
  102. .whenAsyncActionIsDispatched(onTimeRangeUpdated(key, range, dependencies));
  103. tester.thenDispatchedActionsShouldEqual(
  104. toKeyedAction(key, variablesInitTransaction({ uid: key })),
  105. toKeyedAction(key, variableStateFetching(toVariablePayload({ type: 'interval', id: 'interval-0' }))),
  106. toKeyedAction(key, createIntervalOptions(toVariablePayload({ type: 'interval', id: 'interval-0' }))),
  107. toKeyedAction(
  108. key,
  109. setCurrentVariableValue(
  110. toVariablePayload(
  111. { type: 'interval', id: 'interval-0' },
  112. { option: { text: '1m', value: '1m', selected: false } }
  113. )
  114. )
  115. ),
  116. toKeyedAction(key, variableStateCompleted(toVariablePayload({ type: 'interval', id: 'interval-0' })))
  117. );
  118. expect(updateTimeRangeMock).toHaveBeenCalledTimes(1);
  119. expect(updateTimeRangeMock).toHaveBeenCalledWith(range);
  120. expect(templateVariableValueUpdatedMock).toHaveBeenCalledTimes(1);
  121. expect(startRefreshMock).toHaveBeenCalledTimes(1);
  122. });
  123. });
  124. describe('and options are not changed by update', () => {
  125. it('then correct actions are dispatched and correct dependencies are called', async () => {
  126. const {
  127. key,
  128. interval,
  129. preloadedState,
  130. range,
  131. dependencies,
  132. updateTimeRangeMock,
  133. templateVariableValueUpdatedMock,
  134. startRefreshMock,
  135. } = getTestContext(getDashboardModel());
  136. const base = await reduxTester<RootReducerType>({ preloadedState })
  137. .givenRootReducer(getRootReducer())
  138. .whenActionIsDispatched(toKeyedAction(key, variablesInitTransaction({ uid: key })))
  139. .whenAsyncActionIsDispatched(
  140. setOptionAsCurrent(toKeyedVariableIdentifier(interval), interval.options[0], false)
  141. );
  142. const tester = await base.whenAsyncActionIsDispatched(onTimeRangeUpdated(key, range, dependencies), true);
  143. tester.thenDispatchedActionsShouldEqual(
  144. toKeyedAction(key, variableStateFetching(toVariablePayload({ type: 'interval', id: 'interval-0' }))),
  145. toKeyedAction(key, createIntervalOptions(toVariablePayload({ type: 'interval', id: 'interval-0' }))),
  146. toKeyedAction(
  147. key,
  148. setCurrentVariableValue(
  149. toVariablePayload(
  150. { type: 'interval', id: 'interval-0' },
  151. { option: { text: '1m', value: '1m', selected: false } }
  152. )
  153. )
  154. ),
  155. toKeyedAction(key, variableStateCompleted(toVariablePayload({ type: 'interval', id: 'interval-0' })))
  156. );
  157. expect(updateTimeRangeMock).toHaveBeenCalledTimes(1);
  158. expect(updateTimeRangeMock).toHaveBeenCalledWith(range);
  159. expect(templateVariableValueUpdatedMock).toHaveBeenCalledTimes(0);
  160. expect(startRefreshMock).toHaveBeenCalledTimes(1);
  161. });
  162. });
  163. describe('and updateOptions throws', () => {
  164. silenceConsoleOutput();
  165. it('then correct actions are dispatched and correct dependencies are called', async () => {
  166. const {
  167. key,
  168. adapter,
  169. preloadedState,
  170. range,
  171. dependencies,
  172. updateTimeRangeMock,
  173. templateVariableValueUpdatedMock,
  174. startRefreshMock,
  175. } = getTestContext(getDashboardModel());
  176. adapter.updateOptions = jest.fn().mockRejectedValue(new Error('Something broke'));
  177. const tester = await reduxTester<RootReducerType>({ preloadedState, debug: true })
  178. .givenRootReducer(getRootReducer())
  179. .whenActionIsDispatched(toKeyedAction(key, variablesInitTransaction({ uid: key })))
  180. .whenAsyncActionIsDispatched(onTimeRangeUpdated(key, range, dependencies), true);
  181. tester.thenDispatchedActionsPredicateShouldEqual((dispatchedActions) => {
  182. expect(dispatchedActions[0]).toEqual(
  183. toKeyedAction(key, variableStateFetching(toVariablePayload({ type: 'interval', id: 'interval-0' })))
  184. );
  185. expect(dispatchedActions[1]).toEqual(
  186. toKeyedAction(
  187. key,
  188. variableStateFailed(
  189. toVariablePayload({ type: 'interval', id: 'interval-0' }, { error: new Error('Something broke') })
  190. )
  191. )
  192. );
  193. expect(dispatchedActions[2].type).toEqual(notifyApp.type);
  194. expect(dispatchedActions[2].payload.title).toEqual('Templating');
  195. expect(dispatchedActions[2].payload.text).toEqual('Template variable service failed Something broke');
  196. expect(dispatchedActions[2].payload.severity).toEqual('error');
  197. return dispatchedActions.length === 3;
  198. });
  199. expect(updateTimeRangeMock).toHaveBeenCalledTimes(1);
  200. expect(updateTimeRangeMock).toHaveBeenCalledWith(range);
  201. expect(templateVariableValueUpdatedMock).toHaveBeenCalledTimes(0);
  202. expect(startRefreshMock).toHaveBeenCalledTimes(0);
  203. });
  204. });
  205. });
  206. function getDashboardModel(): DashboardModel {
  207. return new DashboardModel({ schemaVersion: 9999 }); // ignore any schema migrations
  208. }