1import { getRootReducer, RootReducerType } from './helpers';
2import { variableAdapters } from '../adapters';
3import { createQueryVariableAdapter } from '../query/adapter';
4import { createConstantVariableAdapter } from '../constant/adapter';
5import { reduxTester } from '../../../../test/core/redux/reduxTester';
6import {
7  addVariable,
8  changeVariableProp,
9  setCurrentVariableValue,
10  variableStateCompleted,
11  variableStateFetching,
12  variableStateNotStarted,
13} from './sharedReducer';
14import { toVariablePayload } from './types';
15import { adHocBuilder, constantBuilder, datasourceBuilder, queryBuilder } from '../shared/testing/builders';
16import { cleanEditorState, initialVariableEditorState } from '../editor/reducer';
17import {
18  TransactionStatus,
19  variablesClearTransaction,
20  variablesCompleteTransaction,
21  variablesInitTransaction,
22} from './transactionReducer';
23import { cleanPickerState, initialState } from '../pickers/OptionsPicker/reducer';
24import { cleanVariables } from './variablesReducer';
25import { createAdHocVariableAdapter } from '../adhoc/adapter';
26import { createDataSourceVariableAdapter } from '../datasource/adapter';
27import { DataSourceRef, LoadingState } from '@grafana/data/src';
28import { setDataSourceSrv } from '@grafana/runtime/src';
29import { VariableModel } from '../types';
30import { toAsyncOfResult } from '../../query/state/DashboardQueryRunner/testHelpers';
31import { setVariableQueryRunner } from '../query/VariableQueryRunner';
32import { createDataSourceOptions } from '../datasource/reducer';
33import { initVariablesTransaction } from './actions';
34
35variableAdapters.setInit(() => [
36  createQueryVariableAdapter(),
37  createConstantVariableAdapter(),
38  createAdHocVariableAdapter(),
39  createDataSourceVariableAdapter(),
40]);
41
42function getTestContext(variables?: VariableModel[]) {
43  const uid = 'uid';
44  const constant = constantBuilder().withId('constant').withName('constant').build();
45  const templating = { list: variables ?? [constant] };
46  const getInstanceSettingsMock = jest.fn().mockReturnValue(undefined);
47  setDataSourceSrv({
48    get: jest.fn().mockResolvedValue({}),
49    getList: jest.fn().mockReturnValue([]),
50    getInstanceSettings: getInstanceSettingsMock,
51  });
52  const variableQueryRunner: any = {
53    cancelRequest: jest.fn(),
54    queueRequest: jest.fn(),
55    getResponse: () => toAsyncOfResult({ state: LoadingState.Done, identifier: { type: 'query', id: 'query' } }),
56    destroy: jest.fn(),
57  };
58  setVariableQueryRunner(variableQueryRunner);
59
60  const dashboard: any = { title: 'Some dash', uid, templating };
61
62  return { constant, getInstanceSettingsMock, templating, uid, dashboard };
63}
64
65describe('initVariablesTransaction', () => {
66  describe('when called and the previous dashboard has completed', () => {
67    it('then correct actions are dispatched', async () => {
68      const { constant, uid, dashboard } = getTestContext();
69      const tester = await reduxTester<RootReducerType>()
70        .givenRootReducer(getRootReducer())
71        .whenAsyncActionIsDispatched(initVariablesTransaction(uid, dashboard));
72
73      tester.thenDispatchedActionsPredicateShouldEqual((dispatchedActions) => {
74        expect(dispatchedActions[0]).toEqual(variablesInitTransaction({ uid }));
75        expect(dispatchedActions[1].type).toEqual(addVariable.type);
76        expect(dispatchedActions[1].payload.id).toEqual('__dashboard');
77        expect(dispatchedActions[2].type).toEqual(addVariable.type);
78        expect(dispatchedActions[2].payload.id).toEqual('__org');
79        expect(dispatchedActions[3].type).toEqual(addVariable.type);
80        expect(dispatchedActions[3].payload.id).toEqual('__user');
81        expect(dispatchedActions[4]).toEqual(
82          addVariable(toVariablePayload(constant, { global: false, index: 0, model: constant }))
83        );
84        expect(dispatchedActions[5]).toEqual(variableStateNotStarted(toVariablePayload(constant)));
85        expect(dispatchedActions[6]).toEqual(variableStateCompleted(toVariablePayload(constant)));
86
87        expect(dispatchedActions[7]).toEqual(variablesCompleteTransaction({ uid }));
88        return dispatchedActions.length === 8;
89      });
90    });
91
92    describe('and there are variables that have data source that need to be migrated', () => {
93      it('then correct actions are dispatched', async () => {
94        const legacyDs = ('${ds}' as unknown) as DataSourceRef;
95        const ds = datasourceBuilder().withId('ds').withName('ds').withQuery('prom').build();
96        const query = queryBuilder().withId('query').withName('query').withDatasource(legacyDs).build();
97        const adhoc = adHocBuilder().withId('adhoc').withName('adhoc').withDatasource(legacyDs).build();
98        const { uid, dashboard } = getTestContext([ds, query, adhoc]);
99        const tester = await reduxTester<RootReducerType>()
100          .givenRootReducer(getRootReducer())
101          .whenAsyncActionIsDispatched(initVariablesTransaction(uid, dashboard));
102
103        tester.thenDispatchedActionsPredicateShouldEqual((dispatchedActions) => {
104          expect(dispatchedActions[0]).toEqual(variablesInitTransaction({ uid }));
105          expect(dispatchedActions[1].type).toEqual(addVariable.type);
106          expect(dispatchedActions[1].payload.id).toEqual('__dashboard');
107          expect(dispatchedActions[2].type).toEqual(addVariable.type);
108          expect(dispatchedActions[2].payload.id).toEqual('__org');
109          expect(dispatchedActions[3].type).toEqual(addVariable.type);
110          expect(dispatchedActions[3].payload.id).toEqual('__user');
111          expect(dispatchedActions[4]).toEqual(
112            addVariable(toVariablePayload(ds, { global: false, index: 0, model: ds }))
113          );
114          expect(dispatchedActions[5]).toEqual(
115            addVariable(toVariablePayload(query, { global: false, index: 1, model: query }))
116          );
117          expect(dispatchedActions[6]).toEqual(
118            addVariable(toVariablePayload(adhoc, { global: false, index: 2, model: adhoc }))
119          );
120          expect(dispatchedActions[7]).toEqual(variableStateNotStarted(toVariablePayload(ds)));
121          expect(dispatchedActions[8]).toEqual(variableStateNotStarted(toVariablePayload(query)));
122          expect(dispatchedActions[9]).toEqual(variableStateNotStarted(toVariablePayload(adhoc)));
123          expect(dispatchedActions[10]).toEqual(
124            changeVariableProp(toVariablePayload(query, { propName: 'datasource', propValue: { uid: '${ds}' } }))
125          );
126          expect(dispatchedActions[11]).toEqual(
127            changeVariableProp(toVariablePayload(adhoc, { propName: 'datasource', propValue: { uid: '${ds}' } }))
128          );
129          expect(dispatchedActions[12]).toEqual(variableStateFetching(toVariablePayload(ds)));
130          expect(dispatchedActions[13]).toEqual(variableStateCompleted(toVariablePayload(adhoc)));
131          expect(dispatchedActions[14]).toEqual(
132            createDataSourceOptions(toVariablePayload(ds, { sources: [], regex: undefined }))
133          );
134          expect(dispatchedActions[15]).toEqual(
135            setCurrentVariableValue(
136              toVariablePayload(ds, { option: { selected: false, text: 'No data sources found', value: '' } })
137            )
138          );
139          expect(dispatchedActions[16]).toEqual(variableStateCompleted(toVariablePayload(ds)));
140          expect(dispatchedActions[17]).toEqual(variableStateFetching(toVariablePayload(query)));
141          expect(dispatchedActions[18]).toEqual(variableStateCompleted(toVariablePayload(query)));
142          expect(dispatchedActions[19]).toEqual(variablesCompleteTransaction({ uid }));
143
144          return dispatchedActions.length === 20;
145        });
146      });
147    });
148  });
149
150  describe('when called and the previous dashboard is still processing variables', () => {
151    it('then correct actions are dispatched', async () => {
152      const { constant, uid, dashboard } = getTestContext();
153      const transactionState = { uid: 'previous-uid', status: TransactionStatus.Fetching };
154
155      const tester = await reduxTester<RootReducerType>({
156        preloadedState: ({
157          templating: {
158            transaction: transactionState,
159            variables: {},
160            optionsPicker: { ...initialState },
161            editor: { ...initialVariableEditorState },
162          },
163        } as unknown) as RootReducerType,
164      })
165        .givenRootReducer(getRootReducer())
166        .whenAsyncActionIsDispatched(initVariablesTransaction(uid, dashboard));
167
168      tester.thenDispatchedActionsPredicateShouldEqual((dispatchedActions) => {
169        expect(dispatchedActions[0]).toEqual(cleanVariables());
170        expect(dispatchedActions[1]).toEqual(cleanEditorState());
171        expect(dispatchedActions[2]).toEqual(cleanPickerState());
172        expect(dispatchedActions[3]).toEqual(variablesClearTransaction());
173        expect(dispatchedActions[4]).toEqual(variablesInitTransaction({ uid }));
174        expect(dispatchedActions[5].type).toEqual(addVariable.type);
175        expect(dispatchedActions[5].payload.id).toEqual('__dashboard');
176        expect(dispatchedActions[6].type).toEqual(addVariable.type);
177        expect(dispatchedActions[6].payload.id).toEqual('__org');
178        expect(dispatchedActions[7].type).toEqual(addVariable.type);
179        expect(dispatchedActions[7].payload.id).toEqual('__user');
180        expect(dispatchedActions[8]).toEqual(
181          addVariable(toVariablePayload(constant, { global: false, index: 0, model: constant }))
182        );
183        expect(dispatchedActions[9]).toEqual(variableStateNotStarted(toVariablePayload(constant)));
184        expect(dispatchedActions[10]).toEqual(variableStateCompleted(toVariablePayload(constant)));
185        expect(dispatchedActions[11]).toEqual(variablesCompleteTransaction({ uid }));
186        return dispatchedActions.length === 12;
187      });
188    });
189  });
190});
191