123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- import { Observable, of, throwError } from 'rxjs';
- import { delay, take } from 'rxjs/operators';
- import { createFetchResponse } from 'test/helpers/createFetchResponse';
- import {
- ArrayVector,
- DataFrame,
- DataFrameJSON,
- DataSourceApi,
- Field,
- FieldType,
- getDefaultRelativeTimeRange,
- LoadingState,
- rangeUtil,
- } from '@grafana/data';
- import { DataSourceSrv, FetchResponse } from '@grafana/runtime';
- import { BackendSrv } from 'app/core/services/backend_srv';
- import { AlertQuery } from 'app/types/unified-alerting-dto';
- import { AlertingQueryResponse, AlertingQueryRunner } from './AlertingQueryRunner';
- describe('AlertingQueryRunner', () => {
- it('should successfully map response and return panel data by refId', async () => {
- const response = createFetchResponse<AlertingQueryResponse>({
- results: {
- A: { frames: [createDataFrameJSON([1, 2, 3])] },
- B: { frames: [createDataFrameJSON([5, 6])] },
- },
- });
- const runner = new AlertingQueryRunner(
- mockBackendSrv({
- fetch: () => of(response),
- }),
- mockDataSourceSrv()
- );
- const data = runner.get();
- runner.run([createQuery('A'), createQuery('B')]);
- await expect(data.pipe(take(1))).toEmitValuesWith((values) => {
- const [data] = values;
- expect(data).toEqual({
- A: {
- annotations: [],
- state: LoadingState.Done,
- series: [
- expectDataFrameWithValues({
- time: [1620051612238, 1620051622238, 1620051632238],
- values: [1, 2, 3],
- }),
- ],
- structureRev: 1,
- timeRange: expect.anything(),
- timings: {
- dataProcessingTime: expect.any(Number),
- },
- },
- B: {
- annotations: [],
- state: LoadingState.Done,
- series: [
- expectDataFrameWithValues({
- time: [1620051612238, 1620051622238],
- values: [5, 6],
- }),
- ],
- structureRev: 1,
- timeRange: expect.anything(),
- timings: {
- dataProcessingTime: expect.any(Number),
- },
- },
- });
- });
- });
- it('should successfully map response with sliding relative time range', async () => {
- const response = createFetchResponse<AlertingQueryResponse>({
- results: {
- A: { frames: [createDataFrameJSON([1, 2, 3])] },
- B: { frames: [createDataFrameJSON([5, 6])] },
- },
- });
- const runner = new AlertingQueryRunner(
- mockBackendSrv({
- fetch: () => of(response),
- }),
- mockDataSourceSrv()
- );
- const data = runner.get();
- runner.run([createQuery('A'), createQuery('B')]);
- await expect(data.pipe(take(1))).toEmitValuesWith((values) => {
- const [data] = values;
- // these test are flakey since the absolute computed "timeRange" can differ from the relative "defaultRelativeTimeRange"
- // so instead we will check if the size of the timeranges match
- const relativeA = rangeUtil.timeRangeToRelative(data.A.timeRange);
- const relativeB = rangeUtil.timeRangeToRelative(data.B.timeRange);
- const defaultRange = getDefaultRelativeTimeRange();
- expect(relativeA.from - defaultRange.from).toEqual(relativeA.to - defaultRange.to);
- expect(relativeB.from - defaultRange.from).toEqual(relativeB.to - defaultRange.to);
- });
- });
- it('should emit loading state if response is slower then 200ms', async () => {
- const response = createFetchResponse<AlertingQueryResponse>({
- results: {
- A: { frames: [createDataFrameJSON([1, 2, 3])] },
- B: { frames: [createDataFrameJSON([5, 6])] },
- },
- });
- const runner = new AlertingQueryRunner(
- mockBackendSrv({
- fetch: () => of(response).pipe(delay(210)),
- }),
- mockDataSourceSrv()
- );
- const data = runner.get();
- runner.run([createQuery('A'), createQuery('B')]);
- await expect(data.pipe(take(2))).toEmitValuesWith((values) => {
- const [loading, data] = values;
- expect(loading.A.state).toEqual(LoadingState.Loading);
- expect(loading.B.state).toEqual(LoadingState.Loading);
- expect(data).toEqual({
- A: {
- annotations: [],
- state: LoadingState.Done,
- series: [
- expectDataFrameWithValues({
- time: [1620051612238, 1620051622238, 1620051632238],
- values: [1, 2, 3],
- }),
- ],
- structureRev: 2,
- timeRange: expect.anything(),
- timings: {
- dataProcessingTime: expect.any(Number),
- },
- },
- B: {
- annotations: [],
- state: LoadingState.Done,
- series: [
- expectDataFrameWithValues({
- time: [1620051612238, 1620051622238],
- values: [5, 6],
- }),
- ],
- structureRev: 2,
- timeRange: expect.anything(),
- timings: {
- dataProcessingTime: expect.any(Number),
- },
- },
- });
- });
- });
- it('should emit error state if fetch request fails', async () => {
- const error = new Error('could not query data');
- const runner = new AlertingQueryRunner(
- mockBackendSrv({
- fetch: () => throwError(error),
- }),
- mockDataSourceSrv()
- );
- const data = runner.get();
- runner.run([createQuery('A'), createQuery('B')]);
- await expect(data.pipe(take(1))).toEmitValuesWith((values) => {
- const [data] = values;
- expect(data.A.state).toEqual(LoadingState.Error);
- expect(data.A.error).toEqual(error);
- expect(data.B.state).toEqual(LoadingState.Error);
- expect(data.B.error).toEqual(error);
- });
- });
- it('should not execute if a query fails filterQuery check', async () => {
- const runner = new AlertingQueryRunner(
- mockBackendSrv({
- fetch: () => throwError(new Error("shouldn't happen")),
- }),
- mockDataSourceSrv({ filterQuery: () => false })
- );
- const data = runner.get();
- runner.run([createQuery('A'), createQuery('B')]);
- await expect(data.pipe(take(1))).toEmitValuesWith((values) => {
- const [data] = values;
- expect(data.A.state).toEqual(LoadingState.Done);
- expect(data.A.series).toHaveLength(0);
- expect(data.B.state).toEqual(LoadingState.Done);
- expect(data.B.series).toHaveLength(0);
- });
- });
- });
- type MockBackendSrvConfig = {
- fetch: () => Observable<FetchResponse<AlertingQueryResponse>>;
- };
- const mockBackendSrv = ({ fetch }: MockBackendSrvConfig): BackendSrv => {
- return {
- fetch,
- resolveCancelerIfExists: jest.fn(),
- } as unknown as BackendSrv;
- };
- const mockDataSourceSrv = (dsApi?: Partial<DataSourceApi>) => {
- return {
- get: () => Promise.resolve(dsApi ?? {}),
- } as unknown as DataSourceSrv;
- };
- const expectDataFrameWithValues = ({ time, values }: { time: number[]; values: number[] }): DataFrame => {
- return {
- fields: [
- {
- config: {},
- entities: {},
- name: 'time',
- state: null,
- type: FieldType.time,
- values: new ArrayVector(time),
- } as Field,
- {
- config: {},
- entities: {},
- name: 'value',
- state: null,
- type: FieldType.number,
- values: new ArrayVector(values),
- } as Field,
- ],
- length: values.length,
- };
- };
- const createDataFrameJSON = (values: number[]): DataFrameJSON => {
- const startTime = 1620051602238;
- const timeValues = values.map((_, index) => startTime + (index + 1) * 10000);
- return {
- schema: {
- fields: [
- { name: 'time', type: FieldType.time },
- { name: 'value', type: FieldType.number },
- ],
- },
- data: {
- values: [timeValues, values],
- },
- };
- };
- const createQuery = (refId: string): AlertQuery => {
- return {
- refId,
- queryType: '',
- datasourceUid: '',
- model: { refId },
- relativeTimeRange: getDefaultRelativeTimeRange(),
- };
- };
|