Loki: refactor mock instance creation and clean up datasource test (#54176)

* refactor(loki-test): move mock creation to mock module

* refactor(loki-test): allow to pass custom settings to the mock constructor

* refactor(loki-test): refactor mock factory in datasource test

* refactor(loki-test): revert change in undefined test case for dsMaxLines

* refactor(loki-test): move type assertion to mock definition

* refactor(loki-test): fix more type issues

* refactor(loki-explore-query-editor): refactor test to use createLokiDatasource() and fix type issues

* refactor(loki-test): replace makeMockLokiDatasource with metadataRequest factory

* fix: remove any

* fix(loki-test): fix remaining usages of any

* Fix: add missing exported type
This commit is contained in:
Matias Chomicki
2022-08-26 09:26:48 +02:00
committed by GitHub
parent 448a67ab43
commit cd617b6520
7 changed files with 283 additions and 211 deletions

View File

@@ -83,7 +83,7 @@ exports[`no enzyme tests`] = {
"public/app/plugins/datasource/influxdb/components/ConfigEditor.test.tsx:57753101": [
[0, 19, 13, "RegExp match", "2409514259"]
],
"public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx:1488067923": [
"public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx:2984948507": [
[0, 26, 13, "RegExp match", "2409514259"]
],
"public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx:146069464": [
@@ -7126,11 +7126,6 @@ exports[`better eslint`] = {
"public/app/plugins/datasource/jaeger/util.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx: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"]
],
"public/app/plugins/datasource/loki/components/LokiLabelBrowser.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]
@@ -7163,15 +7158,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
[0, 0, 0, "Unexpected any. Specify a different type.", "7"]
],
"public/app/plugins/datasource/loki/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"]
],
"public/app/plugins/datasource/loki/datasource.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
@@ -7185,11 +7171,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]
],
"public/app/plugins/datasource/loki/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"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
],
"public/app/plugins/datasource/loki/language_provider.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
@@ -7210,10 +7191,6 @@ exports[`better eslint`] = {
"public/app/plugins/datasource/loki/live_streams_result_transformer.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/plugins/datasource/loki/mocks.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
],
"public/app/plugins/datasource/loki/querybuilder/binaryScalarOperations.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],

View File

@@ -2,19 +2,22 @@ import { mount, shallow } from 'enzyme';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { ExploreMode, LoadingState, PanelData, toUtc, TimeRange } from '@grafana/data';
import { LoadingState, PanelData, toUtc, TimeRange, HistoryItem } from '@grafana/data';
import { TemplateSrv } from '@grafana/runtime';
import { LokiDatasource } from '../datasource';
import LokiLanguageProvider from '../language_provider';
import { makeMockLokiDatasource } from '../mocks';
import { createLokiDatasource } from '../mocks';
import { LokiQuery } from '../types';
import { LokiExploreQueryEditor } from './LokiExploreQueryEditor';
import { LokiExploreQueryEditor, Props } from './LokiExploreQueryEditor';
import { LokiOptionFields } from './LokiOptionFields';
const setup = (renderMethod: any, propOverrides?: object) => {
const datasource: LokiDatasource = makeMockLokiDatasource({});
const setup = (renderMethod: (c: JSX.Element) => ReturnType<typeof shallow> | ReturnType<typeof mount>) => {
const datasource: LokiDatasource = createLokiDatasource({} as unknown as TemplateSrv);
datasource.languageProvider = new LokiLanguageProvider(datasource);
jest.spyOn(datasource, 'metadataRequest').mockResolvedValue([]);
const onRunQuery = jest.fn();
const onChange = jest.fn();
const query: LokiQuery = { expr: '', refId: 'A', maxLines: 0 };
@@ -58,21 +61,18 @@ const setup = (renderMethod: any, propOverrides?: object) => {
},
},
};
const history: any[] = [];
const exploreMode: ExploreMode = ExploreMode.Logs;
const history: Array<HistoryItem<LokiQuery>> = [];
const props: any = {
const props: Props = {
query,
data,
range,
datasource,
exploreMode,
history,
onChange,
onRunQuery,
};
Object.assign(props, { ...props, ...propOverrides });
return renderMethod(<LokiExploreQueryEditor {...props} />);
};

View File

@@ -10,7 +10,7 @@ import { LokiQuery, LokiOptions } from '../types';
import { LokiOptionFields } from './LokiOptionFields';
import { LokiQueryField } from './LokiQueryField';
type Props = QueryEditorProps<LokiDatasource, LokiQuery, LokiOptions>;
export type Props = QueryEditorProps<LokiDatasource, LokiQuery, LokiOptions>;
export const LokiExploreQueryEditor = memo((props: Props) => {
const { query, data, datasource, history, onChange, onRunQuery, range } = props;

View File

@@ -53,9 +53,48 @@ exports[`LokiExploreQueryEditor should render component 1`] = `
}
data-testid="loki-editor-explore"
datasource={
Object {
"getTimeRangeParams": [Function],
"interpolateString": [Function],
LokiDatasource {
"annotations": Object {
"QueryEditor": Object {
"$$typeof": Symbol(react.memo),
"compare": null,
"type": [Function],
},
},
"getLogRowContext": [Function],
"id": 0,
"instanceSettings": Object {
"access": "direct",
"id": 0,
"jsonData": Object {
"maxLines": "20",
},
"meta": Object {
"baseUrl": "",
"id": "id",
"info": Object {
"author": Object {
"name": "Test",
},
"description": "",
"links": Array [],
"logos": Object {
"large": "",
"small": "",
},
"screenshots": Array [],
"updated": "",
"version": "",
},
"module": "",
"name": "name",
"type": "datasource",
},
"name": "",
"type": "",
"uid": "",
"url": "myloggingurl",
},
"languageProvider": LokiLanguageProvider {
"cleanText": [Function],
"datasource": [Circular],
@@ -245,7 +284,42 @@ exports[`LokiExploreQueryEditor should render component 1`] = `
"start": [Function],
"started": false,
},
"metadataRequest": [Function],
"maxLines": 20,
"meta": Object {
"baseUrl": "",
"id": "id",
"info": Object {
"author": Object {
"name": "Test",
},
"description": "",
"links": Array [],
"logos": Object {
"large": "",
"small": "",
},
"screenshots": Array [],
"updated": "",
"version": "",
},
"module": "",
"name": "name",
"type": "datasource",
},
"metadataRequest": [MockFunction],
"name": "",
"prepareLogRowContextQueryTarget": [Function],
"runLiveQuery": [Function],
"streamOptionsProvider": [Function],
"streams": LiveStreams {
"streams": Object {},
},
"templateSrv": Object {},
"timeSrv": Object {
"timeRange": [Function],
},
"type": "",
"uid": "",
}
}
history={Array []}

View File

@@ -14,36 +14,21 @@ import {
FieldType,
LogRowModel,
MutableDataFrame,
toUtc,
} from '@grafana/data';
import { BackendSrvRequest, FetchResponse, setBackendSrv, getBackendSrv, BackendSrv } from '@grafana/runtime';
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { initialCustomVariableModelState } from '../../../features/variables/custom/reducer';
import { CustomVariableModel } from '../../../features/variables/types';
import { LokiDatasource } from './datasource';
import { makeMockLokiDatasource } from './mocks';
import { createMetadataRequest, createLokiDatasource } from './mocks';
import { LokiOptions, LokiQuery, LokiQueryType } from './types';
const rawRange = {
from: toUtc('2018-04-25 10:00'),
to: toUtc('2018-04-25 11:00'),
};
const timeSrvStub = {
timeRange: () => ({
from: rawRange.from,
to: rawRange.to,
raw: rawRange,
}),
} as unknown as TimeSrv;
const templateSrvStub = {
getAdhocFilters: jest.fn(() => [] as any[]),
replace: jest.fn((a: string, ...rest: any) => a),
};
getAdhocFilters: jest.fn(() => [] as unknown[]),
replace: jest.fn((a: string, ...rest: unknown[]) => a),
} as unknown as TemplateSrv;
const testFrame: DataFrame = {
refId: 'A',
@@ -131,22 +116,16 @@ describe('LokiDatasource', () => {
describe('when doing logs queries with limits', () => {
const runTest = async (
queryMaxLines: number | undefined,
dsMaxLines: number | undefined,
dsMaxLines: string | undefined,
expectedMaxLines: number
) => {
let settings = {
url: 'myloggingurl',
const settings = {
jsonData: {
maxLines: dsMaxLines,
},
} as DataSourceInstanceSettings<LokiOptions>;
const templateSrvMock = {
getAdhocFilters: (): any[] => [],
replace: (a: string) => a,
} as unknown as TemplateSrv;
const ds = new LokiDatasource(settings, templateSrvMock, timeSrvStub);
const ds = createLokiDatasource(templateSrvStub, settings);
// we need to check the final query before it is sent out,
// and applyTemplateVariables is a convenient place to do that.
@@ -166,7 +145,7 @@ describe('LokiDatasource', () => {
};
it('should use datasource max lines when no query max lines', async () => {
await runTest(undefined, 40, 40);
await runTest(undefined, '40', 40);
});
it('should use query max lines, if exists', async () => {
@@ -174,30 +153,22 @@ describe('LokiDatasource', () => {
});
it('should use query max lines, if both exist, even if it is higher than ds max lines', async () => {
await runTest(80, 40, 80);
await runTest(80, '40', 80);
});
});
describe('When using adhoc filters', () => {
const DEFAULT_EXPR = 'rate({bar="baz", job="foo"} |= "bar" [5m])';
const query: LokiQuery = { expr: DEFAULT_EXPR, refId: 'A' };
const originalAdhocFiltersMock = templateSrvStub.getAdhocFilters();
const ds = new LokiDatasource(
{} as DataSourceInstanceSettings,
templateSrvStub as unknown as TemplateSrv,
timeSrvStub
);
afterAll(() => {
templateSrvStub.getAdhocFilters.mockReturnValue(originalAdhocFiltersMock);
});
const mockedGetAdhocFilters = templateSrvStub.getAdhocFilters as jest.Mock;
const ds = createLokiDatasource(templateSrvStub);
it('should not modify expression with no filters', async () => {
expect(ds.applyTemplateVariables(query, {}).expr).toBe(DEFAULT_EXPR);
});
it('should add filters to expression', async () => {
templateSrvStub.getAdhocFilters.mockReturnValue([
mockedGetAdhocFilters.mockReturnValue([
{
key: 'k1',
operator: '=',
@@ -216,7 +187,7 @@ describe('LokiDatasource', () => {
});
it('should add escaping if needed to regex filter expressions', async () => {
templateSrvStub.getAdhocFilters.mockReturnValue([
mockedGetAdhocFilters.mockReturnValue([
{
key: 'k1',
operator: '=~',
@@ -239,7 +210,7 @@ describe('LokiDatasource', () => {
let variable: CustomVariableModel;
beforeEach(() => {
ds = createLokiDSForTests();
ds = createLokiDatasource(templateSrvStub);
variable = { ...initialCustomVariableModelState };
});
@@ -281,8 +252,12 @@ describe('LokiDatasource', () => {
});
describe('when performing testDataSource', () => {
let ds: LokiDatasource;
beforeEach(() => {
ds = createLokiDatasource(templateSrvStub);
});
it('should return successfully when call succeeds with labels', async () => {
const ds = createLokiDSForTests({} as TemplateSrv);
ds.metadataRequest = () => Promise.resolve(['avalue']);
const result = await ds.testDatasource();
@@ -294,7 +269,6 @@ describe('LokiDatasource', () => {
});
it('should return error when call succeeds without labels', async () => {
const ds = createLokiDSForTests({} as TemplateSrv);
ds.metadataRequest = () => Promise.resolve([]);
const result = await ds.testDatasource();
@@ -306,7 +280,6 @@ describe('LokiDatasource', () => {
});
it('should return error status with no details when call fails with no details', async () => {
const ds = createLokiDSForTests({} as TemplateSrv);
ds.metadataRequest = () => Promise.reject({});
const result = await ds.testDatasource();
@@ -318,7 +291,6 @@ describe('LokiDatasource', () => {
});
it('should return error status with details when call fails with details', async () => {
const ds = createLokiDSForTests({} as TemplateSrv);
ds.metadataRequest = () =>
Promise.reject({
data: {
@@ -336,10 +308,10 @@ describe('LokiDatasource', () => {
});
describe('when calling annotationQuery', () => {
const getTestContext = (frame: DataFrame, options: any = []) => {
const getTestContext = (frame: DataFrame, options = {}) => {
const query = makeAnnotationQueryRequest(options);
const ds = createLokiDSForTests();
const ds = createLokiDatasource(templateSrvStub);
const response: DataQueryResponse = {
data: [frame],
};
@@ -477,20 +449,22 @@ describe('LokiDatasource', () => {
});
describe('metricFindQuery', () => {
const getTestContext = (mock: LokiDatasource) => {
const ds = createLokiDSForTests();
ds.metadataRequest = mock.metadataRequest;
const getTestContext = () => {
const ds = createLokiDatasource(templateSrvStub);
jest
.spyOn(ds, 'metadataRequest')
.mockImplementation(
createMetadataRequest(
{ label1: ['value1', 'value2'], label2: ['value3', 'value4'] },
{ '{label1="value1", label2="value2"}': [{ label5: 'value5' }] }
)
);
return { ds };
};
const mock = makeMockLokiDatasource(
{ label1: ['value1', 'value2'], label2: ['value3', 'value4'] },
{ '{label1="value1", label2="value2"}': [{ label5: 'value5' }] }
);
it(`should return label names for Loki`, async () => {
const { ds } = getTestContext(mock);
const { ds } = getTestContext();
const res = await ds.metricFindQuery('label_names()');
@@ -498,7 +472,7 @@ describe('LokiDatasource', () => {
});
it(`should return label values for Loki when no matcher`, async () => {
const { ds } = getTestContext(mock);
const { ds } = getTestContext();
const res = await ds.metricFindQuery('label_values(label1)');
@@ -506,7 +480,7 @@ describe('LokiDatasource', () => {
});
it(`should return label values for Loki with matcher`, async () => {
const { ds } = getTestContext(mock);
const { ds } = getTestContext();
const res = await ds.metricFindQuery('label_values({label1="value1", label2="value2"},label5)');
@@ -514,7 +488,7 @@ describe('LokiDatasource', () => {
});
it(`should return empty array when incorrect query for Loki`, async () => {
const { ds } = getTestContext(mock);
const { ds } = getTestContext();
const res = await ds.metricFindQuery('incorrect_query');
@@ -524,11 +498,15 @@ describe('LokiDatasource', () => {
describe('modifyQuery', () => {
describe('when called with ADD_FILTER', () => {
let ds: LokiDatasource;
beforeEach(() => {
ds = createLokiDatasource(templateSrvStub);
});
describe('and query has no parser', () => {
it('then the correct label should be added for logs query', () => {
const query: LokiQuery = { refId: 'A', expr: '{bar="baz"}' };
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER' };
const ds = createLokiDSForTests();
const result = ds.modifyQuery(query, action);
expect(result.refId).toEqual('A');
@@ -538,7 +516,6 @@ describe('LokiDatasource', () => {
it('then the correctly escaped label should be added for logs query', () => {
const query: LokiQuery = { refId: 'A', expr: '{bar="baz"}' };
const action = { options: { key: 'job', value: '\\test' }, type: 'ADD_FILTER' };
const ds = createLokiDSForTests();
const result = ds.modifyQuery(query, action);
expect(result.refId).toEqual('A');
@@ -548,7 +525,6 @@ describe('LokiDatasource', () => {
it('then the correct label should be added for metrics query', () => {
const query: LokiQuery = { refId: 'A', expr: 'rate({bar="baz"}[5m])' };
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER' };
const ds = createLokiDSForTests();
const result = ds.modifyQuery(query, action);
expect(result.refId).toEqual('A');
@@ -558,7 +534,6 @@ describe('LokiDatasource', () => {
it('then the correct label should be added for logs query', () => {
const query: LokiQuery = { refId: 'A', expr: '{bar="baz"} | logfmt' };
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER' };
const ds = createLokiDSForTests();
const result = ds.modifyQuery(query, action);
expect(result.refId).toEqual('A');
@@ -567,7 +542,6 @@ describe('LokiDatasource', () => {
it('then the correct label should be added for metrics query', () => {
const query: LokiQuery = { refId: 'A', expr: 'rate({bar="baz"} | logfmt [5m])' };
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER' };
const ds = createLokiDSForTests();
const result = ds.modifyQuery(query, action);
expect(result.refId).toEqual('A');
@@ -579,10 +553,14 @@ describe('LokiDatasource', () => {
describe('when called with ADD_FILTER_OUT', () => {
describe('and query has no parser', () => {
let ds: LokiDatasource;
beforeEach(() => {
ds = createLokiDatasource(templateSrvStub);
});
it('then the correct label should be added for logs query', () => {
const query: LokiQuery = { refId: 'A', expr: '{bar="baz"}' };
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER_OUT' };
const ds = createLokiDSForTests();
const result = ds.modifyQuery(query, action);
expect(result.refId).toEqual('A');
@@ -592,7 +570,6 @@ describe('LokiDatasource', () => {
it('then the correctly escaped label should be added for logs query', () => {
const query: LokiQuery = { refId: 'A', expr: '{bar="baz"}' };
const action = { options: { key: 'job', value: '"test' }, type: 'ADD_FILTER_OUT' };
const ds = createLokiDSForTests();
const result = ds.modifyQuery(query, action);
expect(result.refId).toEqual('A');
@@ -602,17 +579,20 @@ describe('LokiDatasource', () => {
it('then the correct label should be added for metrics query', () => {
const query: LokiQuery = { refId: 'A', expr: 'rate({bar="baz"}[5m])' };
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER_OUT' };
const ds = createLokiDSForTests();
const result = ds.modifyQuery(query, action);
expect(result.refId).toEqual('A');
expect(result.expr).toEqual('rate({bar="baz", job!="grafana"}[5m])');
});
describe('and query has parser', () => {
let ds: LokiDatasource;
beforeEach(() => {
ds = createLokiDatasource(templateSrvStub);
});
it('then the correct label should be added for logs query', () => {
const query: LokiQuery = { refId: 'A', expr: '{bar="baz"} | logfmt' };
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER_OUT' };
const ds = createLokiDSForTests();
const result = ds.modifyQuery(query, action);
expect(result.refId).toEqual('A');
@@ -621,7 +601,6 @@ describe('LokiDatasource', () => {
it('then the correct label should be added for metrics query', () => {
const query: LokiQuery = { refId: 'A', expr: 'rate({bar="baz"} | logfmt [5m])' };
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER_OUT' };
const ds = createLokiDSForTests();
const result = ds.modifyQuery(query, action);
expect(result.refId).toEqual('A');
@@ -649,7 +628,7 @@ describe('LokiDatasource', () => {
getAdhocFilters: (): AdHocFilter[] => adHocFilters,
replace: (a: string) => a,
} as unknown as TemplateSrv;
ds = createLokiDSForTests(templateSrvMock);
ds = createLokiDatasource(templateSrvMock);
});
describe('and query has no parser', () => {
it('then the correct label should be added for logs query', () => {
@@ -684,7 +663,7 @@ describe('LokiDatasource', () => {
getAdhocFilters: (): AdHocFilter[] => adHocFilters,
replace: (a: string) => a,
} as unknown as TemplateSrv;
ds = createLokiDSForTests(templateSrvMock);
ds = createLokiDatasource(templateSrvMock);
});
describe('and query has no parser', () => {
it('then the correct label should be added for logs query', () => {
@@ -707,7 +686,7 @@ describe('LokiDatasource', () => {
});
describe('prepareLogRowContextQueryTarget', () => {
const ds = createLokiDSForTests();
const ds = createLokiDatasource(templateSrvStub);
it('creates query with only labels from /labels API', async () => {
const row: LogRowModel = {
rowIndex: 0,
@@ -758,8 +737,12 @@ describe('LokiDatasource', () => {
});
describe('logs volume data provider', () => {
let ds: LokiDatasource;
beforeEach(() => {
ds = createLokiDatasource(templateSrvStub);
});
it('creates provider for logs query', () => {
const ds = createLokiDSForTests();
const options = getQueryOptions<LokiQuery>({
targets: [{ expr: '{label=value}', refId: 'A' }],
});
@@ -768,7 +751,6 @@ describe('LokiDatasource', () => {
});
it('does not create provider for metrics query', () => {
const ds = createLokiDSForTests();
const options = getQueryOptions<LokiQuery>({
targets: [{ expr: 'rate({label=value}[1m])', refId: 'A' }],
});
@@ -777,7 +759,6 @@ describe('LokiDatasource', () => {
});
it('creates provider if at least one query is a logs query', () => {
const ds = createLokiDSForTests();
const options = getQueryOptions<LokiQuery>({
targets: [
{ expr: 'rate({label=value}[1m])', refId: 'A' },
@@ -789,7 +770,6 @@ describe('LokiDatasource', () => {
});
it('does not create provider if there is only an instant logs query', () => {
const ds = createLokiDSForTests();
const options = getQueryOptions<LokiQuery>({
targets: [{ expr: '{label=value', refId: 'A', queryType: LokiQueryType.Instant }],
});
@@ -799,8 +779,12 @@ describe('LokiDatasource', () => {
});
describe('importing queries', () => {
let ds: LokiDatasource;
beforeEach(() => {
ds = createLokiDatasource(templateSrvStub);
});
it('keeps all labels when no labels are loaded', async () => {
const ds = createLokiDSForTests();
ds.getResource = () => Promise.resolve({ data: [] });
const queries = await ds.importFromAbstractQueries([
{
@@ -815,7 +799,6 @@ describe('LokiDatasource', () => {
});
it('filters out non existing labels', async () => {
const ds = createLokiDSForTests();
ds.getResource = () => Promise.resolve({ data: ['foo'] });
const queries = await ds.importFromAbstractQueries([
{
@@ -833,7 +816,7 @@ describe('LokiDatasource', () => {
describe('applyTemplateVariables', () => {
it('should add the adhoc filter to the query', () => {
const ds = createLokiDSForTests();
const ds = createLokiDatasource(templateSrvStub);
const spy = jest.spyOn(ds, 'addAdHocFilters');
ds.applyTemplateVariables({ expr: '{test}', refId: 'A' }, {});
expect(spy).toHaveBeenCalledWith('{test}');
@@ -847,23 +830,7 @@ function assertAdHocFilters(query: string, expectedResults: string, ds: LokiData
expect(result).toEqual(expectedResults);
}
function createLokiDSForTests(
templateSrvMock = {
getAdhocFilters: (): any[] => [],
replace: (a: string) => a,
} as unknown as TemplateSrv
): LokiDatasource {
const instanceSettings = {
url: 'myloggingurl',
} as DataSourceInstanceSettings;
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
const customSettings: DataSourceInstanceSettings = { ...instanceSettings, jsonData: customData };
return new LokiDatasource(customSettings, templateSrvMock, timeSrvStub as TimeSrv);
}
function makeAnnotationQueryRequest(options: any): AnnotationQueryRequest<LokiQuery> {
function makeAnnotationQueryRequest(options = {}): AnnotationQueryRequest<LokiQuery> {
const timeRange = {
from: dateTime(),
to: dateTime(),
@@ -872,7 +839,9 @@ function makeAnnotationQueryRequest(options: any): AnnotationQueryRequest<LokiQu
annotation: {
expr: '{test=test}',
refId: '',
datasource: 'loki',
datasource: {
type: 'loki',
},
enable: true,
name: 'test-annotation',
iconColor: 'red',
@@ -880,7 +849,7 @@ function makeAnnotationQueryRequest(options: any): AnnotationQueryRequest<LokiQu
},
dashboard: {
id: 1,
} as any,
},
range: {
...timeRange,
raw: timeRange,

View File

@@ -2,10 +2,11 @@ import Plain from 'slate-plain-serializer';
import { AbstractLabelOperator } from '@grafana/data';
import { TypeaheadInput } from '@grafana/ui';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { LokiDatasource } from './datasource';
import LanguageProvider, { LokiHistoryItem } from './language_provider';
import { makeMockLokiDatasource } from './mocks';
import { createLokiDatasource, createMetadataRequest } from './mocks';
import { LokiQueryType } from './types';
jest.mock('app/store/store', () => ({
@@ -21,7 +22,7 @@ jest.mock('app/store/store', () => ({
}));
describe('Language completion provider', () => {
const datasource = makeMockLokiDatasource({});
const datasource = setup({});
describe('query suggestions', () => {
it('returns no suggestions on empty context', async () => {
@@ -90,7 +91,7 @@ describe('Language completion provider', () => {
describe('fetchSeries', () => {
it('should use match[] parameter', () => {
const datasource = makeMockLokiDatasource({}, { '{foo="bar"}': [{ label1: 'label_val1' }] });
const datasource = setup({}, { '{foo="bar"}': [{ label1: 'label_val1' }] });
const languageProvider = new LanguageProvider(datasource);
const fetchSeries = languageProvider.fetchSeries;
const requestSpy = jest.spyOn(languageProvider, 'request');
@@ -105,11 +106,11 @@ describe('Language completion provider', () => {
describe('fetchSeriesLabels', () => {
it('should interpolate variable in series', () => {
const datasource: LokiDatasource = {
metadataRequest: () => ({ data: { data: [] as any[] } }),
getTimeRangeParams: () => ({ start: 0, end: 1 }),
interpolateString: (string: string) => string.replace(/\$/, 'interpolated-'),
} as any as LokiDatasource;
const datasource = setup({});
jest.spyOn(datasource, 'getTimeRangeParams').mockReturnValue({ start: 0, end: 1 });
jest
.spyOn(datasource, 'interpolateString')
.mockImplementation((string: string) => string.replace(/\$/, 'interpolated-'));
const languageProvider = new LanguageProvider(datasource);
const fetchSeriesLabels = languageProvider.fetchSeriesLabels;
@@ -126,7 +127,7 @@ describe('Language completion provider', () => {
describe('label key suggestions', () => {
it('returns all label suggestions on empty selector', async () => {
const datasource = makeMockLokiDatasource({ label1: [], label2: [] });
const datasource = setup({ label1: [], label2: [] });
const provider = await getLanguageProvider(datasource);
const input = createTypeaheadInput('{}', '', '', 1);
const result = await provider.provideCompletionItems(input);
@@ -143,7 +144,7 @@ describe('Language completion provider', () => {
});
it('returns all label suggestions on selector when starting to type', async () => {
const datasource = makeMockLokiDatasource({ label1: [], label2: [] });
const datasource = setup({ label1: [], label2: [] });
const provider = await getLanguageProvider(datasource);
const input = createTypeaheadInput('{l}', '', '', 2);
const result = await provider.provideCompletionItems(input);
@@ -162,10 +163,7 @@ describe('Language completion provider', () => {
describe('label suggestions facetted', () => {
it('returns facetted label suggestions based on selector', async () => {
const datasource = makeMockLokiDatasource(
{ label1: [], label2: [] },
{ '{foo="bar"}': [{ label1: 'label_val1' }] }
);
const datasource = setup({ label1: [], label2: [] }, { '{foo="bar"}': [{ label1: 'label_val1' }] });
const provider = await getLanguageProvider(datasource);
const input = createTypeaheadInput('{foo="bar",}', '', '', 11);
const result = await provider.provideCompletionItems(input);
@@ -174,10 +172,7 @@ describe('Language completion provider', () => {
});
it('returns facetted label suggestions for multipule selectors', async () => {
const datasource = makeMockLokiDatasource(
{ label1: [], label2: [] },
{ '{baz="42",foo="bar"}': [{ label2: 'label_val2' }] }
);
const datasource = setup({ label1: [], label2: [] }, { '{baz="42",foo="bar"}': [{ label2: 'label_val2' }] });
const provider = await getLanguageProvider(datasource);
const input = createTypeaheadInput('{baz="42",foo="bar",}', '', '', 20);
const result = await provider.provideCompletionItems(input);
@@ -188,7 +183,7 @@ describe('Language completion provider', () => {
describe('label suggestions', () => {
it('returns label values suggestions from Loki', async () => {
const datasource = makeMockLokiDatasource({ label1: ['label1_val1', 'label1_val2'], label2: [] });
const datasource = setup({ label1: ['label1_val1', 'label1_val2'], label2: [] });
const provider = await getLanguageProvider(datasource);
const input = createTypeaheadInput('{label1=}', '=', 'label1');
let result = await provider.provideCompletionItems(input);
@@ -206,7 +201,7 @@ describe('Language completion provider', () => {
]);
});
it('returns label values suggestions from Loki when re-editing', async () => {
const datasource = makeMockLokiDatasource({ label1: ['label1_val1', 'label1_val2'], label2: [] });
const datasource = setup({ label1: ['label1_val1', 'label1_val2'], label2: [] });
const provider = await getLanguageProvider(datasource);
const input = createTypeaheadInput('{label1="label1_v"}', 'label1_v', 'label1', 17, [
'attr-value',
@@ -228,7 +223,7 @@ describe('Language completion provider', () => {
describe('label values', () => {
it('should fetch label values if not cached', async () => {
const datasource = makeMockLokiDatasource({ testkey: ['label1_val1', 'label1_val2'], label2: [] });
const datasource = setup({ testkey: ['label1_val1', 'label1_val2'], label2: [] });
const provider = await getLanguageProvider(datasource);
const requestSpy = jest.spyOn(provider, 'request');
const labelValues = await provider.fetchLabelValues('testkey');
@@ -237,7 +232,7 @@ describe('Language completion provider', () => {
});
it('should return cached values', async () => {
const datasource = makeMockLokiDatasource({ testkey: ['label1_val1', 'label1_val2'], label2: [] });
const datasource = setup({ testkey: ['label1_val1', 'label1_val2'], label2: [] });
const provider = await getLanguageProvider(datasource);
const requestSpy = jest.spyOn(provider, 'request');
const labelValues = await provider.fetchLabelValues('testkey');
@@ -250,7 +245,7 @@ describe('Language completion provider', () => {
});
it('should encode special characters', async () => {
const datasource = makeMockLokiDatasource({ '`\\"testkey': ['label1_val1', 'label1_val2'], label2: [] });
const datasource = setup({ '`\\"testkey': ['label1_val1', 'label1_val2'], label2: [] });
const provider = await getLanguageProvider(datasource);
const requestSpy = jest.spyOn(provider, 'request');
await provider.fetchLabelValues('`\\"testkey');
@@ -262,9 +257,9 @@ describe('Language completion provider', () => {
describe('Request URL', () => {
it('should contain range params', async () => {
const datasourceWithLabels = makeMockLokiDatasource({ other: [] });
const datasourceWithLabels = setup({ other: [] });
const rangeParams = datasourceWithLabels.getTimeRangeParams();
const datasourceSpy = jest.spyOn(datasourceWithLabels as any, 'metadataRequest');
const datasourceSpy = jest.spyOn(datasourceWithLabels, 'metadataRequest');
const instance = new LanguageProvider(datasourceWithLabels);
instance.fetchLabels();
@@ -274,7 +269,7 @@ describe('Request URL', () => {
});
describe('Query imports', () => {
const datasource = makeMockLokiDatasource({});
const datasource = setup({});
it('returns empty queries', async () => {
const instance = new LanguageProvider(datasource);
@@ -334,3 +329,21 @@ function createTypeaheadInput(
labelKey,
};
}
function setup(
labelsAndValues: Record<string, string[]>,
series?: Record<string, Array<Record<string, string>>>
): LokiDatasource {
const datasource = createLokiDatasource({} as unknown as TemplateSrv);
const rangeMock = {
start: 1560153109000,
end: 1560163909000,
};
jest.spyOn(datasource, 'getTimeRangeParams').mockReturnValue(rangeMock);
jest.spyOn(datasource, 'metadataRequest').mockImplementation(createMetadataRequest(labelsAndValues, series));
jest.spyOn(datasource, 'interpolateString').mockImplementation((string: string) => string);
return datasource;
}

View File

@@ -1,57 +1,96 @@
import { DataSourceSettings } from '@grafana/data';
import { DataSourceInstanceSettings, DataSourceSettings, PluginType, toUtc } from '@grafana/data';
import { TemplateSrv } from '@grafana/runtime';
import { getMockDataSource } from '../../../features/datasources/__mocks__';
import { LokiDatasource } from './datasource';
import { LokiOptions } from './types';
interface Labels {
[label: string]: string[];
}
interface Series {
[label: string]: string;
}
interface SeriesForSelector {
[selector: string]: Series[];
}
export function makeMockLokiDatasource(labelsAndValues: Labels, series?: SeriesForSelector): LokiDatasource {
// added % to allow urlencoded labelKeys. Note, that this is not confirm with Loki, as loki does not allow specialcharacters in labelKeys, but needed for tests.
const lokiLabelsAndValuesEndpointRegex = /^label\/([%\w]*)\/values/;
const lokiSeriesEndpointRegex = /^series/;
const lokiLabelsEndpoint = 'labels';
const rangeMock = {
start: 1560153109000,
end: 1560163909000,
};
const labels = Object.keys(labelsAndValues);
return {
getTimeRangeParams: () => rangeMock,
metadataRequest: (url: string, params?: { [key: string]: string }) => {
if (url === lokiLabelsEndpoint) {
return labels;
} else {
const labelsMatch = url.match(lokiLabelsAndValuesEndpointRegex);
const seriesMatch = url.match(lokiSeriesEndpointRegex);
if (labelsMatch) {
return labelsAndValues[labelsMatch[1]] || [];
} else if (seriesMatch && series && params) {
return series[params['match[]']] || [];
} else {
throw new Error(`Unexpected url error, ${url}`);
}
}
},
interpolateString: (string: string) => string,
} as any;
}
export function createDefaultConfigOptions(): DataSourceSettings<LokiOptions> {
return getMockDataSource<LokiOptions>({
jsonData: { maxLines: '531' },
});
}
const rawRange = {
from: toUtc('2018-04-25 10:00'),
to: toUtc('2018-04-25 11:00'),
};
const defaultTimeSrvMock = {
timeRange: () => ({
from: rawRange.from,
to: rawRange.to,
raw: rawRange,
}),
};
export function createLokiDatasource(
templateSrvMock: TemplateSrv,
settings: Partial<DataSourceInstanceSettings<LokiOptions>> = {},
timeSrvStub = defaultTimeSrvMock
): LokiDatasource {
const customSettings: DataSourceInstanceSettings<LokiOptions> = {
url: 'myloggingurl',
id: 0,
uid: '',
type: '',
name: '',
meta: {
id: 'id',
name: 'name',
type: PluginType.datasource,
module: '',
baseUrl: '',
info: {
author: {
name: 'Test',
},
description: '',
links: [],
logos: {
large: '',
small: '',
},
screenshots: [],
updated: '',
version: '',
},
},
jsonData: {
maxLines: '20',
},
access: 'direct',
...settings,
};
// @ts-expect-error
return new LokiDatasource(customSettings, templateSrvMock, timeSrvStub);
}
export function createMetadataRequest(
labelsAndValues: Record<string, string[]>,
series?: Record<string, Array<Record<string, string>>>
) {
// added % to allow urlencoded labelKeys. Note, that this is not confirm with Loki, as loki does not allow specialcharacters in labelKeys, but needed for tests.
const lokiLabelsAndValuesEndpointRegex = /^label\/([%\w]*)\/values/;
const lokiSeriesEndpointRegex = /^series/;
const lokiLabelsEndpoint = 'labels';
const labels = Object.keys(labelsAndValues);
return async function metadataRequestMock(url: string, params?: Record<string, string | number>) {
if (url === lokiLabelsEndpoint) {
return labels;
} else {
const labelsMatch = url.match(lokiLabelsAndValuesEndpointRegex);
const seriesMatch = url.match(lokiSeriesEndpointRegex);
if (labelsMatch) {
return labelsAndValues[labelsMatch[1]] || [];
} else if (seriesMatch && series && params) {
return series[params['match[]']] || [];
} else {
throw new Error(`Unexpected url error, ${url}`);
}
}
};
}