mirror of
https://github.com/grafana/grafana.git
synced 2025-02-16 18:34:52 -06:00
* Add and configure eslint-plugin-import * Fix the lint:ts npm command * Autofix + prettier all the files * Manually fix remaining files * Move jquery code in jest-setup to external file to safely reorder imports * Resolve issue caused by circular dependencies within Prometheus * Update .betterer.results * Fix missing // @ts-ignore * ignore iconBundle.ts * Fix missing // @ts-ignore
282 lines
7.8 KiB
TypeScript
282 lines
7.8 KiB
TypeScript
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(),
|
|
};
|
|
};
|