123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- import { of, throwError } from 'rxjs';
- import { delay } from 'rxjs/operators';
- import { getDefaultTimeRange, LoadingState, VariableSupportType } from '@grafana/data';
- import { queryBuilder } from '../shared/testing/builders';
- import { getPreloadedState } from '../state/helpers';
- import { toKeyedAction } from '../state/keyedVariablesReducer';
- import { initialTransactionState } from '../state/transactionReducer';
- import { KeyedVariableIdentifier } from '../state/types';
- import { QueryVariableModel } from '../types';
- import { toKeyedVariableIdentifier } from '../utils';
- import { UpdateOptionsResults, VariableQueryRunner } from './VariableQueryRunner';
- import { QueryRunner, QueryRunners } from './queryRunners';
- import { updateVariableOptions } from './reducer';
- type DoneCallback = {
- (...args: any[]): any;
- fail(error?: string | { message: string }): any;
- };
- function expectOnResults(args: {
- runner: VariableQueryRunner;
- identifier: KeyedVariableIdentifier;
- done: DoneCallback;
- expect: (results: UpdateOptionsResults[]) => void;
- }) {
- const { runner, identifier, done, expect: expectCallback } = args;
- const results: UpdateOptionsResults[] = [];
- const subscription = runner.getResponse(identifier).subscribe({
- next: (value) => {
- results.push(value);
- if (value.state === LoadingState.Done || value.state === LoadingState.Error) {
- try {
- expectCallback(results);
- subscription.unsubscribe();
- done();
- } catch (err) {
- subscription.unsubscribe();
- done(err);
- }
- }
- },
- });
- }
- function getTestContext(variable?: QueryVariableModel) {
- const getTimeSrv = jest.fn().mockReturnValue({
- timeRange: jest.fn().mockReturnValue(getDefaultTimeRange()),
- });
- const key = '0123456789';
- variable = variable ?? queryBuilder().withId('query').withRootStateKey(key).withName('query').build();
- const datasource: any = { metricFindQuery: jest.fn().mockResolvedValue([]) };
- const identifier = toKeyedVariableIdentifier(variable);
- const searchFilter = undefined;
- const getTemplatedRegex = jest.fn().mockReturnValue('getTemplatedRegex result');
- const dispatch = jest.fn().mockResolvedValue({});
- const templatingState = {
- transaction: { ...initialTransactionState, uid: key },
- variables: {
- [variable.id]: variable,
- },
- };
- const getState = jest.fn().mockReturnValue(getPreloadedState(key, templatingState));
- const queryRunner: QueryRunner = {
- type: VariableSupportType.Standard,
- canRun: jest.fn().mockReturnValue(true),
- getTarget: jest.fn().mockReturnValue({ refId: 'A', query: 'A query' }),
- runRequest: jest.fn().mockReturnValue(of({ series: [], state: LoadingState.Done })),
- };
- const queryRunners = {
- getRunnerForDatasource: jest.fn().mockReturnValue(queryRunner),
- } as unknown as QueryRunners;
- const getVariable = jest.fn().mockReturnValue(variable);
- const runRequest = jest.fn().mockReturnValue(of({}));
- const runner = new VariableQueryRunner({
- getTimeSrv,
- getTemplatedRegex,
- dispatch,
- getState,
- getVariable,
- queryRunners,
- runRequest,
- });
- return {
- key,
- identifier,
- datasource,
- runner,
- searchFilter,
- getTemplatedRegex,
- dispatch,
- getState,
- queryRunner,
- queryRunners,
- getVariable,
- runRequest,
- variable,
- getTimeSrv,
- };
- }
- describe('VariableQueryRunner', () => {
- describe('happy case', () => {
- it('then it should work as expected', (done) => {
- const { key, identifier, runner, datasource, getState, getVariable, queryRunners, queryRunner, dispatch } =
- getTestContext();
- expectOnResults({
- identifier,
- runner,
- expect: (results) => {
- // verify that the observable works as expected
- expect(results).toEqual([
- { state: LoadingState.Loading, identifier },
- { state: LoadingState.Done, identifier },
- ]);
- // verify that mocks have been called as expected
- expect(getState).toHaveBeenCalledTimes(3);
- expect(getVariable).toHaveBeenCalledTimes(1);
- expect(queryRunners.getRunnerForDatasource).toHaveBeenCalledTimes(1);
- expect(queryRunner.getTarget).toHaveBeenCalledTimes(1);
- expect(queryRunner.runRequest).toHaveBeenCalledTimes(1);
- expect(datasource.metricFindQuery).not.toHaveBeenCalled();
- // updateVariableOptions and validateVariableSelectionState
- expect(dispatch).toHaveBeenCalledTimes(2);
- expect(dispatch.mock.calls[0][0]).toEqual(
- toKeyedAction(
- key,
- updateVariableOptions({
- id: 'query',
- type: 'query',
- data: { results: [], templatedRegex: 'getTemplatedRegex result' },
- })
- )
- );
- },
- done,
- });
- runner.queueRequest({ identifier, datasource });
- });
- });
- describe('error cases', () => {
- describe('queryRunners.getRunnerForDatasource throws', () => {
- it('then it should work as expected', (done) => {
- const { identifier, runner, datasource, getState, getVariable, queryRunners, queryRunner, dispatch } =
- getTestContext();
- queryRunners.getRunnerForDatasource = jest.fn().mockImplementation(() => {
- throw new Error('getRunnerForDatasource error');
- });
- expectOnResults({
- identifier,
- runner,
- expect: (results) => {
- // verify that the observable works as expected
- expect(results).toEqual([
- { state: LoadingState.Loading, identifier },
- { state: LoadingState.Error, identifier, error: new Error('getRunnerForDatasource error') },
- ]);
- // verify that mocks have been called as expected
- expect(getState).toHaveBeenCalledTimes(2);
- expect(getVariable).toHaveBeenCalledTimes(1);
- expect(queryRunners.getRunnerForDatasource).toHaveBeenCalledTimes(1);
- expect(queryRunner.getTarget).not.toHaveBeenCalled();
- expect(queryRunner.runRequest).not.toHaveBeenCalled();
- expect(datasource.metricFindQuery).not.toHaveBeenCalled();
- expect(dispatch).not.toHaveBeenCalled();
- },
- done,
- });
- runner.queueRequest({ identifier, datasource });
- });
- });
- describe('runRequest throws', () => {
- it('then it should work as expected', (done) => {
- const { identifier, runner, datasource, getState, getVariable, queryRunners, queryRunner, dispatch } =
- getTestContext();
- queryRunner.runRequest = jest.fn().mockReturnValue(throwError(new Error('runRequest error')));
- expectOnResults({
- identifier,
- runner,
- expect: (results) => {
- // verify that the observable works as expected
- expect(results).toEqual([
- { state: LoadingState.Loading, identifier },
- { state: LoadingState.Error, identifier, error: new Error('runRequest error') },
- ]);
- // verify that mocks have been called as expected
- expect(getState).toHaveBeenCalledTimes(2);
- expect(getVariable).toHaveBeenCalledTimes(1);
- expect(queryRunners.getRunnerForDatasource).toHaveBeenCalledTimes(1);
- expect(queryRunner.getTarget).toHaveBeenCalledTimes(1);
- expect(queryRunner.runRequest).toHaveBeenCalledTimes(1);
- expect(datasource.metricFindQuery).not.toHaveBeenCalled();
- expect(dispatch).not.toHaveBeenCalled();
- },
- done,
- });
- runner.queueRequest({ identifier, datasource });
- });
- });
- });
- describe('cancellation cases', () => {
- describe('long running request is cancelled', () => {
- it('then it should work as expected', (done) => {
- const { identifier, datasource, runner, queryRunner } = getTestContext();
- queryRunner.runRequest = jest
- .fn()
- .mockReturnValue(of({ series: [], state: LoadingState.Done }).pipe(delay(10000)));
- expectOnResults({
- identifier,
- runner,
- expect: (results) => {
- // verify that the observable works as expected
- expect(results).toEqual([
- { state: LoadingState.Loading, identifier },
- { state: LoadingState.Loading, identifier, cancelled: true },
- { state: LoadingState.Done, identifier },
- ]);
- },
- done,
- });
- runner.queueRequest({ identifier, datasource });
- runner.cancelRequest(identifier);
- });
- });
- describe('an identical request is triggered before first request is finished', () => {
- it('then it should work as expected', (done) => {
- const { identifier, datasource, runner, queryRunner } = getTestContext();
- queryRunner.runRequest = jest
- .fn()
- .mockReturnValueOnce(of({ series: [], state: LoadingState.Done }).pipe(delay(10000)))
- .mockReturnValue(of({ series: [], state: LoadingState.Done }));
- expectOnResults({
- identifier,
- runner,
- expect: (results) => {
- // verify that the observable works as expected
- expect(results).toEqual([
- { state: LoadingState.Loading, identifier },
- { state: LoadingState.Loading, identifier },
- { state: LoadingState.Loading, identifier, cancelled: true },
- { state: LoadingState.Done, identifier },
- ]);
- },
- done,
- });
- runner.queueRequest({ identifier, datasource });
- runner.queueRequest({ identifier, datasource });
- });
- });
- });
- });
|