123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- import { throwError } from 'rxjs';
- import { delay, first } from 'rxjs/operators';
- import { AlertState, AlertStateInfo } from '@grafana/data';
- import { setDataSourceSrv } from '@grafana/runtime';
- import { silenceConsoleOutput } from '../../../../../test/core/utils/silenceConsoleOutput';
- import { backendSrv } from '../../../../core/services/backend_srv';
- import * as annotationsSrv from '../../../annotations/executeAnnotationQuery';
- import { createDashboardQueryRunner } from './DashboardQueryRunner';
- import { getDefaultOptions, LEGACY_DS_NAME, NEXT_GEN_DS_NAME, toAsyncOfResult } from './testHelpers';
- import { DashboardQueryRunner, DashboardQueryRunnerResult } from './types';
- jest.mock('@grafana/runtime', () => ({
- ...(jest.requireActual('@grafana/runtime') as unknown as object),
- getBackendSrv: () => backendSrv,
- }));
- function getTestContext() {
- jest.clearAllMocks();
- const timeSrvMock: any = { timeRange: jest.fn() };
- const options = getDefaultOptions();
- // These tests are setup so all the workers and runners are invoked once, this wouldn't be the case in real life
- const runner = createDashboardQueryRunner({ dashboard: options.dashboard, timeSrv: timeSrvMock });
- const getResults: AlertStateInfo[] = [
- { id: 1, state: AlertState.Alerting, dashboardId: 1, panelId: 1 },
- { id: 2, state: AlertState.Alerting, dashboardId: 1, panelId: 2 },
- ];
- const getMock = jest.spyOn(backendSrv, 'get').mockResolvedValue(getResults);
- const executeAnnotationQueryMock = jest
- .spyOn(annotationsSrv, 'executeAnnotationQuery')
- .mockReturnValue(toAsyncOfResult({ events: [{ id: 'NextGen' }] }));
- const annotationQueryMock = jest.fn().mockResolvedValue([{ id: 'Legacy' }]);
- const dataSourceSrvMock: any = {
- get: async (name: string) => {
- if (name === LEGACY_DS_NAME) {
- return {
- annotationQuery: annotationQueryMock,
- };
- }
- if (name === NEXT_GEN_DS_NAME) {
- return {
- annotations: {},
- };
- }
- return {};
- },
- };
- setDataSourceSrv(dataSourceSrvMock);
- return { runner, options, annotationQueryMock, executeAnnotationQueryMock, getMock };
- }
- function expectOnResults(args: {
- runner: DashboardQueryRunner;
- panelId: number;
- done: jest.DoneCallback;
- expect: (results: DashboardQueryRunnerResult) => void;
- }) {
- const { runner, done, panelId, expect: expectCallback } = args;
- runner
- .getResult(panelId)
- .pipe(first())
- .subscribe({
- next: (value) => {
- try {
- expectCallback(value);
- done();
- } catch (err) {
- done(err);
- }
- },
- });
- }
- describe('DashboardQueryRunnerImpl', () => {
- describe('when calling run and all workers succeed', () => {
- it('then it should return the correct results', (done) => {
- const { runner, options, annotationQueryMock, executeAnnotationQueryMock, getMock } = getTestContext();
- expectOnResults({
- runner,
- panelId: 1,
- done,
- expect: (results) => {
- // should have one alert state, one snapshot, one legacy and one next gen result
- // having both snapshot and legacy/next gen is a imaginary example for testing purposes and doesn't exist for real
- expect(results).toEqual(getExpectedForAllResult());
- expect(annotationQueryMock).toHaveBeenCalledTimes(1);
- expect(executeAnnotationQueryMock).toHaveBeenCalledTimes(1);
- expect(getMock).toHaveBeenCalledTimes(1);
- },
- });
- runner.run(options);
- });
- });
- describe('when calling run and all workers succeed but take longer than 200ms', () => {
- it('then it should return the empty results', (done) => {
- const { runner, options, annotationQueryMock, executeAnnotationQueryMock, getMock } = getTestContext();
- const wait = 201;
- executeAnnotationQueryMock.mockReturnValue(toAsyncOfResult({ events: [{ id: 'NextGen' }] }).pipe(delay(wait)));
- expectOnResults({
- runner,
- panelId: 1,
- done,
- expect: (results) => {
- // should have one alert state, one snapshot, one legacy and one next gen result
- // having both snapshot and legacy/next gen is a imaginary example for testing purposes and doesn't exist for real
- expect(results).toEqual({ annotations: [] });
- expect(annotationQueryMock).toHaveBeenCalledTimes(1);
- expect(executeAnnotationQueryMock).toHaveBeenCalledTimes(1);
- expect(getMock).toHaveBeenCalledTimes(1);
- },
- });
- runner.run(options);
- });
- });
- describe('when calling run and all workers succeed but the subscriber subscribes after the run', () => {
- it('then it should return the last results', (done) => {
- const { runner, options, annotationQueryMock, executeAnnotationQueryMock, getMock } = getTestContext();
- runner.run(options);
- setTimeout(
- () =>
- expectOnResults({
- runner,
- panelId: 1,
- done,
- expect: (results) => {
- // should have one alert state, one snapshot, one legacy and one next gen result
- // having both snapshot and legacy/next gen is a imaginary example for testing purposes and doesn't exist for real
- expect(results).toEqual(getExpectedForAllResult());
- expect(annotationQueryMock).toHaveBeenCalledTimes(1);
- expect(executeAnnotationQueryMock).toHaveBeenCalledTimes(1);
- expect(getMock).toHaveBeenCalledTimes(1);
- },
- }),
- 200
- ); // faking a late subscriber to make sure we get the latest results
- });
- });
- describe('when calling run and all workers fail', () => {
- silenceConsoleOutput();
- it('then it should return the correct results', (done) => {
- const { runner, options, annotationQueryMock, executeAnnotationQueryMock, getMock } = getTestContext();
- getMock.mockRejectedValue({ message: 'Get error' });
- annotationQueryMock.mockRejectedValue({ message: 'Legacy error' });
- executeAnnotationQueryMock.mockReturnValue(throwError({ message: 'NextGen error' }));
- expectOnResults({
- runner,
- panelId: 1,
- done,
- expect: (results) => {
- // should have one alert state, one snapshot, one legacy and one next gen result
- // having both snapshot and legacy/next gen is a imaginary example for testing purposes and doesn't exist for real
- const expected = { alertState: undefined, annotations: [getExpectedForAllResult().annotations[2]] };
- expect(results).toEqual(expected);
- expect(annotationQueryMock).toHaveBeenCalledTimes(1);
- expect(executeAnnotationQueryMock).toHaveBeenCalledTimes(1);
- expect(getMock).toHaveBeenCalledTimes(1);
- },
- });
- runner.run(options);
- });
- });
- describe('when calling run and AlertStatesWorker fails', () => {
- silenceConsoleOutput();
- it('then it should return the correct results', (done) => {
- const { runner, options, annotationQueryMock, executeAnnotationQueryMock, getMock } = getTestContext();
- getMock.mockRejectedValue({ message: 'Get error' });
- expectOnResults({
- runner,
- panelId: 1,
- done,
- expect: (results) => {
- // should have one alert state, one snapshot, one legacy and one next gen result
- // having both snapshot and legacy/next gen is a imaginary example for testing purposes and doesn't exist for real
- const { annotations } = getExpectedForAllResult();
- const expected = { alertState: undefined, annotations };
- expect(results).toEqual(expected);
- expect(annotationQueryMock).toHaveBeenCalledTimes(1);
- expect(executeAnnotationQueryMock).toHaveBeenCalledTimes(1);
- expect(getMock).toHaveBeenCalledTimes(1);
- },
- });
- runner.run(options);
- });
- describe('when calling run and AnnotationsWorker fails', () => {
- silenceConsoleOutput();
- it('then it should return the correct results', (done) => {
- const { runner, options, annotationQueryMock, executeAnnotationQueryMock, getMock } = getTestContext();
- annotationQueryMock.mockRejectedValue({ message: 'Legacy error' });
- executeAnnotationQueryMock.mockReturnValue(throwError({ message: 'NextGen error' }));
- expectOnResults({
- runner,
- panelId: 1,
- done,
- expect: (results) => {
- // should have one alert state, one snapshot, one legacy and one next gen result
- // having both snapshot and legacy/next gen is a imaginary example for testing purposes and doesn't exist for real
- const { alertState, annotations } = getExpectedForAllResult();
- const expected = { alertState, annotations: [annotations[2]] };
- expect(results).toEqual(expected);
- expect(annotationQueryMock).toHaveBeenCalledTimes(1);
- expect(executeAnnotationQueryMock).toHaveBeenCalledTimes(1);
- expect(getMock).toHaveBeenCalledTimes(1);
- },
- });
- runner.run(options);
- });
- });
- });
- describe('when calling run twice', () => {
- it('then it should cancel previous run', (done) => {
- const { runner, options, annotationQueryMock, executeAnnotationQueryMock, getMock } = getTestContext();
- executeAnnotationQueryMock.mockReturnValueOnce(
- toAsyncOfResult({ events: [{ id: 'NextGen' }] }).pipe(delay(10000))
- );
- expectOnResults({
- runner,
- panelId: 1,
- done,
- expect: (results) => {
- // should have one alert state, one snapshot, one legacy and one next gen result
- // having both snapshot and legacy/next gen is a imaginary example for testing purposes and doesn't exist for real
- const { alertState, annotations } = getExpectedForAllResult();
- const expected = { alertState, annotations };
- expect(results).toEqual(expected);
- expect(annotationQueryMock).toHaveBeenCalledTimes(2);
- expect(executeAnnotationQueryMock).toHaveBeenCalledTimes(2);
- expect(getMock).toHaveBeenCalledTimes(2);
- },
- });
- runner.run(options);
- runner.run(options);
- });
- });
- describe('when calling cancel', () => {
- it('then it should cancel matching workers', (done) => {
- const { runner, options, annotationQueryMock, executeAnnotationQueryMock, getMock } = getTestContext();
- executeAnnotationQueryMock.mockReturnValueOnce(
- toAsyncOfResult({ events: [{ id: 'NextGen' }] }).pipe(delay(10000))
- );
- expectOnResults({
- runner,
- panelId: 1,
- done,
- expect: (results) => {
- // should have one alert state, one snapshot, one legacy and one next gen result
- // having both snapshot and legacy/next gen is a imaginary example for testing purposes and doesn't exist for real
- const { alertState, annotations } = getExpectedForAllResult();
- expect(results).toEqual({ alertState, annotations: [annotations[0], annotations[2]] });
- expect(annotationQueryMock).toHaveBeenCalledTimes(1);
- expect(executeAnnotationQueryMock).toHaveBeenCalledTimes(1);
- expect(getMock).toHaveBeenCalledTimes(1);
- },
- });
- runner.run(options);
- setTimeout(() => {
- // call to async needs to be async or the cancellation will be called before any of the workers have started
- runner.cancel(options.dashboard.annotations.list[1]);
- }, 100);
- });
- });
- });
- function getExpectedForAllResult(): DashboardQueryRunnerResult {
- return {
- alertState: {
- dashboardId: 1,
- id: 1,
- panelId: 1,
- state: AlertState.Alerting,
- },
- annotations: [
- {
- color: '#ffc0cb',
- id: 'Legacy',
- isRegion: false,
- source: {
- datasource: 'Legacy',
- enable: true,
- hide: false,
- iconColor: 'pink',
- id: undefined,
- name: 'Test',
- snapshotData: undefined,
- },
- type: 'Test',
- },
- {
- color: '#ffc0cb',
- id: 'NextGen',
- isRegion: false,
- source: {
- datasource: 'NextGen',
- enable: true,
- hide: false,
- iconColor: 'pink',
- id: undefined,
- name: 'Test',
- snapshotData: undefined,
- },
- type: 'Test',
- },
- {
- annotation: {
- datasource: 'Legacy',
- enable: true,
- hide: false,
- iconColor: 'pink',
- id: 'Snapshotted',
- name: 'Test',
- },
- color: '#ffc0cb',
- isRegion: true,
- source: {
- datasource: 'Legacy',
- enable: true,
- hide: false,
- iconColor: 'pink',
- id: 'Snapshotted',
- name: 'Test',
- },
- time: 1,
- timeEnd: 2,
- type: 'Test',
- },
- ],
- };
- }
|