Elasticsearch: fix types in test and add mock factory (#54582)

* refactor(elastic-test): add datasource mock factory

* refactor(elastic-test): use new factory and attempt to fix some type issues

* refactor(elastic-test): type fixes

* refactor(elastic-test): update test with mock changes

* refactor(elastic-test): remove commented code

Git history should be more than enough to go back to previous revisions instead of keeping commented code.

* refactor(elastic-test): use mock factory and fix type issues in language provider test

* Chore: rename mock parameter

* Chore: remove unnecessary conditional

* Chore: remove ts-expect-error
This commit is contained in:
Matias Chomicki 2022-09-06 14:35:54 +02:00 committed by GitHub
parent 638fb5dc6d
commit 5767c01a79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 147 additions and 243 deletions

View File

@ -6225,20 +6225,6 @@ exports[`better eslint`] = {
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/plugins/datasource/elasticsearch/datasource.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
[0, 0, 0, "Unexpected any. Specify a different type.", "8"],
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
[0, 0, 0, "Unexpected any. Specify a different type.", "10"],
[0, 0, 0, "Unexpected any. Specify a different type.", "11"]
],
"public/app/plugins/datasource/elasticsearch/datasource.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
@ -6325,10 +6311,6 @@ exports[`better eslint`] = {
"public/app/plugins/datasource/elasticsearch/hooks/useStatelessReducer.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/plugins/datasource/elasticsearch/language_provider.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
],
"public/app/plugins/datasource/elasticsearch/language_provider.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],

View File

@ -6,23 +6,27 @@ import {
CoreApp,
DataLink,
DataQueryRequest,
DataQueryResponse,
DataSourceInstanceSettings,
DataSourcePluginMeta,
dateMath,
DateTime,
dateTime,
Field,
MutableDataFrame,
RawTimeRange,
TimeRange,
toUtc,
} from '@grafana/data';
import { BackendSrvRequest, FetchResponse } from '@grafana/runtime';
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { createFetchResponse } from '../../../../test/helpers/createFetchResponse';
import { Filters } from './components/QueryEditor/BucketAggregationsEditor/aggregations';
import { ElasticDatasource, enhanceDataFrame } from './datasource';
import { createElasticDatasource } from './mocks';
import { ElasticsearchOptions, ElasticsearchQuery } from './types';
const ELASTICSEARCH_MOCK_URL = 'http://elasticsearch.local';
@ -41,6 +45,17 @@ jest.mock('@grafana/runtime', () => ({
const TIMESRV_START = [2022, 8, 21, 6, 10, 10];
const TIMESRV_END = [2022, 8, 24, 6, 10, 21];
const DATAQUERY_BASE = {
requestId: '1',
interval: '',
intervalMs: 0,
scopedVars: {
test: { text: '', value: '' },
},
timezone: '',
app: 'test',
startTime: 0,
};
jest.mock('app/features/dashboard/services/TimeSrv', () => ({
...(jest.requireActual('app/features/dashboard/services/TimeSrv') as unknown as object),
@ -58,73 +73,65 @@ const createTimeRange = (from: DateTime, to: DateTime): TimeRange => ({
},
});
interface Args {
data?: any;
interface TestContext {
data?: Data;
from?: string;
jsonData?: any;
jsonData?: Partial<ElasticsearchOptions>;
database?: string;
mockImplementation?: (options: BackendSrvRequest) => Observable<FetchResponse>;
fetchMockImplementation?: (options: BackendSrvRequest) => Observable<FetchResponse>;
}
interface Data {
[key: string]: undefined | string | string[] | number | Data | Data[];
}
function getTestContext({
data = {},
data = { responses: [] },
from = 'now-5m',
jsonData = {},
database = '[asd-]YYYY.MM.DD',
mockImplementation = undefined,
}: Args = {}) {
jest.clearAllMocks();
jsonData,
database = '[test-]YYYY.MM.DD',
fetchMockImplementation = undefined,
}: TestContext = {}) {
const defaultMock = (options: BackendSrvRequest) => of(createFetchResponse(data));
const fetchMock = jest.spyOn(backendSrv, 'fetch');
fetchMock.mockImplementation(mockImplementation ?? defaultMock);
fetchMock.mockImplementation(fetchMockImplementation ?? defaultMock);
const templateSrv: any = {
replace: jest.fn((text?: string) => {
const timeSrv = {
time: { from, to: 'now' },
timeRange: () => ({
from: dateMath.parse(timeSrv.time.from, false),
to: dateMath.parse(timeSrv.time.to, true),
}),
setTime: (time: RawTimeRange) => {
timeSrv.time = time;
},
} as TimeSrv;
const settings: Partial<DataSourceInstanceSettings<ElasticsearchOptions>> = { url: ELASTICSEARCH_MOCK_URL };
settings.jsonData = jsonData as ElasticsearchOptions;
const templateSrv = {
replace: (text?: string) => {
if (text?.startsWith('$')) {
return `resolvedVariable`;
} else {
return text;
}
}),
getAdhocFilters: jest.fn(() => []),
};
},
getAdhocFilters: () => [],
} as unknown as TemplateSrv;
const timeSrv: any = {
time: { from, to: 'now' },
};
timeSrv.timeRange = jest.fn(() => {
return {
from: dateMath.parse(timeSrv.time.from, false),
to: dateMath.parse(timeSrv.time.to, true),
};
});
timeSrv.setTime = jest.fn((time) => {
timeSrv.time = time;
});
const instanceSettings: DataSourceInstanceSettings<ElasticsearchOptions> = {
id: 1,
meta: {} as DataSourcePluginMeta,
name: 'test-elastic',
type: 'type',
uid: 'uid',
access: 'proxy',
url: ELASTICSEARCH_MOCK_URL,
database,
jsonData,
readOnly: false,
};
const ds = new ElasticDatasource(instanceSettings, templateSrv);
const ds = createElasticDatasource(settings, templateSrv);
return { timeSrv, ds, fetchMock };
}
describe('ElasticDatasource', function (this: any) {
describe('ElasticDatasource', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('When calling getTagValues', () => {
it('should respect the currently selected time range', () => {
const data = {
@ -175,22 +182,23 @@ describe('ElasticDatasource', function (this: any) {
const today = toUtc().format('YYYY.MM.DD');
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock.mock.calls[0][0].url).toBe(`${ELASTICSEARCH_MOCK_URL}/asd-${today}/_mapping`);
expect(fetchMock.mock.calls[0][0].url).toBe(`${ELASTICSEARCH_MOCK_URL}/test-${today}/_mapping`);
});
});
describe('When issuing metric query with interval pattern', () => {
async function runScenario() {
const range = { from: toUtc([2015, 4, 30, 10]), to: toUtc([2015, 5, 1, 10]) };
const targets = [
const range = { from: toUtc([2015, 4, 30, 10]), to: toUtc([2015, 5, 1, 10]), raw: { from: '', to: '' } };
const targets: ElasticsearchQuery[] = [
{
refId: 'test',
alias: '$varAlias',
bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '1' }],
metrics: [{ type: 'count', id: '1' }],
query: 'escape\\:test',
},
];
const query: any = { range, targets };
const query = { ...DATAQUERY_BASE, range, targets };
const data = {
responses: [
{
@ -209,7 +217,7 @@ describe('ElasticDatasource', function (this: any) {
};
const { ds, fetchMock } = getTestContext({ jsonData: { interval: 'Daily', esVersion: '7.10.0' }, data });
let result: any = {};
let result: DataQueryResponse = { data: [] };
await expect(ds.query(query)).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
expect(received[0]).toEqual({
@ -218,7 +226,7 @@ describe('ElasticDatasource', function (this: any) {
datapoints: [[10, 1000]],
metric: 'count',
props: {},
refId: undefined,
refId: 'test',
target: 'resolvedVariable',
},
],
@ -237,7 +245,7 @@ describe('ElasticDatasource', function (this: any) {
it('should translate index pattern to current day', async () => {
const { header } = await runScenario();
expect(header.index).toEqual(['asd-2015.05.30', 'asd-2015.05.31', 'asd-2015.06.01']);
expect(header.index).toEqual(['test-2015.05.30', 'test-2015.05.31', 'test-2015.06.01']);
});
it('should not resolve the variable in the original alias field in the query', async () => {
@ -291,7 +299,7 @@ describe('ElasticDatasource', function (this: any) {
} as DataQueryRequest<ElasticsearchQuery>;
const queryBuilderSpy = jest.spyOn(ds.queryBuilder, 'getLogsQuery');
let response: any = {};
let response: DataQueryResponse = { data: [] };
await expect(ds.query(query)).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
@ -328,8 +336,10 @@ describe('ElasticDatasource', function (this: any) {
describe('When issuing document query', () => {
async function runScenario() {
const range = createTimeRange(dateTime([2015, 4, 30, 10]), dateTime([2015, 5, 1, 10]));
const targets = [{ refId: 'A', metrics: [{ type: 'raw_document', id: '1' }], query: 'test' }];
const query: any = { range, targets };
const targets: ElasticsearchQuery[] = [
{ refId: 'A', metrics: [{ type: 'raw_document', id: '1' }], query: 'test' },
];
const query = { ...DATAQUERY_BASE, range, targets };
const data = { responses: [] };
const { ds, fetchMock } = getTestContext({ jsonData: { esVersion: '7.10.0' }, data, database: 'test' });
@ -420,7 +430,7 @@ describe('ElasticDatasource', function (this: any) {
};
const { ds } = getTestContext({
mockImplementation: () => throwError(response),
fetchMockImplementation: () => throwError(response),
from: undefined,
jsonData: { esVersion: '7.10.0' },
});
@ -465,102 +475,6 @@ describe('ElasticDatasource', function (this: any) {
});
});
// describe('When getting fields', () => {
// const data = {
// metricbeat: {
// mappings: {
// metricsets: {
// _all: {},
// _meta: {
// test: 'something',
// },
// properties: {
// '@timestamp': { type: 'date' },
// __timestamp: { type: 'date' },
// '@timestampnano': { type: 'date_nanos' },
// beat: {
// properties: {
// name: {
// fields: { raw: { type: 'keyword' } },
// type: 'string',
// },
// hostname: { type: 'string' },
// },
// },
// system: {
// properties: {
// cpu: {
// properties: {
// system: { type: 'float' },
// user: { type: 'float' },
// },
// },
// process: {
// properties: {
// cpu: {
// properties: {
// total: { type: 'float' },
// },
// },
// name: { type: 'string' },
// },
// },
// },
// },
// },
// },
// },
// },
// };
// it('should return nested fields', async () => {
// const { ds } = getTestContext({ data, jsonData: { esVersion: 50 }, database: 'metricbeat' });
// await expect(ds.getFields()).toEmitValuesWith((received) => {
// expect(received.length).toBe(1);
// const fieldObjects = received[0];
// const fields = map(fieldObjects, 'text');
// expect(fields).toEqual([
// '@timestamp',
// '__timestamp',
// '@timestampnano',
// 'beat.name.raw',
// 'beat.name',
// 'beat.hostname',
// 'system.cpu.system',
// 'system.cpu.user',
// 'system.process.cpu.total',
// 'system.process.name',
// ]);
// });
// });
// it('should return number fields', async () => {
// const { ds } = getTestContext({ data, jsonData: { esVersion: 50 }, database: 'metricbeat' });
// await expect(ds.getFields(['number'])).toEmitValuesWith((received) => {
// expect(received.length).toBe(1);
// const fieldObjects = received[0];
// const fields = map(fieldObjects, 'text');
// expect(fields).toEqual(['system.cpu.system', 'system.cpu.user', 'system.process.cpu.total']);
// });
// });
// it('should return date fields', async () => {
// const { ds } = getTestContext({ data, jsonData: { esVersion: 50 }, database: 'metricbeat' });
// await expect(ds.getFields(['date'])).toEmitValuesWith((received) => {
// expect(received.length).toBe(1);
// const fieldObjects = received[0];
// const fields = map(fieldObjects, 'text');
// expect(fields).toEqual(['@timestamp', '__timestamp', '@timestampnano']);
// });
// });
// });
describe('When getting field mappings on indices with gaps', () => {
const basicResponse = {
metricbeat: {
@ -580,55 +494,13 @@ describe('ElasticDatasource', function (this: any) {
},
};
// const alternateResponse = {
// metricbeat: {
// mappings: {
// metricsets: {
// _all: {},
// properties: {
// '@timestamp': { type: 'date' },
// },
// },
// },
// },
// };
// it('should return fields of the newest available index', async () => {
// const twoDaysBefore = toUtc().subtract(2, 'day').format('YYYY.MM.DD');
// const threeDaysBefore = toUtc().subtract(3, 'day').format('YYYY.MM.DD');
// const baseUrl = `${ELASTICSEARCH_MOCK_URL}/asd-${twoDaysBefore}/_mapping`;
// const alternateUrl = `${ELASTICSEARCH_MOCK_URL}/asd-${threeDaysBefore}/_mapping`;
// const { ds, timeSrv } = getTestContext({
// from: 'now-2w',
// jsonData: { interval: 'Daily', esVersion: 50 },
// mockImplementation: (options) => {
// if (options.url === baseUrl) {
// return of(createFetchResponse(basicResponse));
// } else if (options.url === alternateUrl) {
// return of(createFetchResponse(alternateResponse));
// }
// return throwError({ status: 404 });
// },
// });
// const range = timeSrv.timeRange();
// await expect(ds.getFields(undefined, range)).toEmitValuesWith((received) => {
// expect(received.length).toBe(1);
// const fieldObjects = received[0];
// const fields = map(fieldObjects, 'text');
// expect(fields).toEqual(['@timestamp', 'beat.hostname']);
// });
// });
it('should not retry when ES is down', async () => {
const twoDaysBefore = toUtc().subtract(2, 'day').format('YYYY.MM.DD');
const { ds, timeSrv, fetchMock } = getTestContext({
from: 'now-2w',
jsonData: { interval: 'Daily', esVersion: '7.10.0' },
mockImplementation: (options) => {
fetchMockImplementation: (options) => {
if (options.url === `${ELASTICSEARCH_MOCK_URL}/asd-${twoDaysBefore}/_mapping`) {
return of(createFetchResponse(basicResponse));
}
@ -649,7 +521,7 @@ describe('ElasticDatasource', function (this: any) {
const { ds, timeSrv, fetchMock } = getTestContext({
from: 'now-2w',
jsonData: { interval: 'Daily', esVersion: '7.10.0' },
mockImplementation: (options) => {
fetchMockImplementation: (options) => {
return throwError({ status: 404 });
},
});
@ -824,7 +696,7 @@ describe('ElasticDatasource', function (this: any) {
describe('When issuing aggregation query on es5.x', () => {
async function runScenario() {
const range = createTimeRange(dateTime([2015, 4, 30, 10]), dateTime([2015, 5, 1, 10]));
const targets = [
const targets: ElasticsearchQuery[] = [
{
refId: 'A',
bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '2' }],
@ -832,7 +704,7 @@ describe('ElasticDatasource', function (this: any) {
query: 'test',
},
];
const query: any = { range, targets };
const query = { ...DATAQUERY_BASE, range, targets };
const data = { responses: [] };
const { ds, fetchMock } = getTestContext({ jsonData: { esVersion: '7.10.0' }, data, database: 'test' });
@ -926,7 +798,7 @@ describe('ElasticDatasource', function (this: any) {
describe('query', () => {
it('should replace range as integer not string', async () => {
const { ds } = getTestContext({ jsonData: { interval: 'Daily', esVersion: '7.10.0', timeField: '@time' } });
const postMock = jest.fn((url: string, data: any) => of(createFetchResponse({ responses: [] })));
const postMock = jest.fn((url: string, data) => of(createFetchResponse({ responses: [] })));
ds['post'] = postMock;
await expect(ds.query(createElasticQuery())).toEmitValuesWith((received) => {
@ -1140,7 +1012,11 @@ const createElasticQuery = (): DataQueryRequest<ElasticsearchQuery> => {
range: {
from: dateTime([2015, 4, 30, 10]),
to: dateTime([2015, 5, 1, 10]),
} as any,
raw: {
from: '',
to: '',
},
},
targets: [
{
refId: '',

View File

@ -1,36 +1,29 @@
import { AbstractLabelOperator, AbstractQuery, DataSourceInstanceSettings } from '@grafana/data';
import { AbstractLabelOperator, AbstractQuery } from '@grafana/data';
import { TemplateSrv } from '../../../features/templating/template_srv';
import { ElasticDatasource } from './datasource';
import LanguageProvider from './language_provider';
import { ElasticsearchOptions, ElasticsearchQuery } from './types';
const templateSrvStub = {
getAdhocFilters: jest.fn(() => [] as any[]),
replace: jest.fn((a: string) => a),
} as any;
const dataSource = new ElasticDatasource(
{
url: 'http://es.com',
database: '[asd-]YYYY.MM.DD',
jsonData: {
interval: 'Daily',
esVersion: '2.0.0',
timeField: '@time',
},
} as DataSourceInstanceSettings<ElasticsearchOptions>,
templateSrvStub as TemplateSrv
);
import { createElasticDatasource } from './mocks';
import { ElasticsearchQuery } from './types';
const baseLogsQuery: Partial<ElasticsearchQuery> = {
metrics: [{ type: 'logs', id: '1' }],
};
describe('transform abstract query to elasticsearch query', () => {
let datasource: ElasticDatasource;
beforeEach(() => {
const templateSrvStub = {
getAdhocFilters: jest.fn(() => []),
replace: jest.fn((a: string) => a),
} as unknown as TemplateSrv;
datasource = createElasticDatasource({}, templateSrvStub);
});
it('With some labels', () => {
const instance = new LanguageProvider(dataSource);
const instance = new LanguageProvider(datasource);
const abstractQuery: AbstractQuery = {
refId: 'bar',
labelMatchers: [
@ -50,7 +43,7 @@ describe('transform abstract query to elasticsearch query', () => {
});
it('Empty query', () => {
const instance = new LanguageProvider(dataSource);
const instance = new LanguageProvider(datasource);
const abstractQuery = { labelMatchers: [], refId: 'foo' };
const result = instance.importFromAbstractQuery(abstractQuery);

View File

@ -0,0 +1,53 @@
import { DataSourceInstanceSettings, PluginType } from '@grafana/data';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { ElasticDatasource } from './datasource';
import { ElasticsearchOptions } from './types';
export function createElasticDatasource(
settings: Partial<DataSourceInstanceSettings<ElasticsearchOptions>> = {},
templateSrv: TemplateSrv
) {
const { jsonData, ...rest } = settings;
const instanceSettings: DataSourceInstanceSettings<ElasticsearchOptions> = {
id: 1,
meta: {
id: 'id',
name: 'name',
type: PluginType.datasource,
module: '',
baseUrl: '',
info: {
author: {
name: 'Test',
},
description: '',
links: [],
logos: {
large: '',
small: '',
},
screenshots: [],
updated: '',
version: '',
},
},
readOnly: false,
name: 'test-elastic',
type: 'type',
uid: 'uid',
access: 'proxy',
url: '',
jsonData: {
timeField: '',
esVersion: '',
timeInterval: '',
...jsonData,
},
database: '[test-]YYYY.MM.DD',
...rest,
};
return new ElasticDatasource(instanceSettings, templateSrv);
}