From cd617b65209e139b8cd18ec211ed098460c8a0f9 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Fri, 26 Aug 2022 09:26:48 +0200 Subject: [PATCH] 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 --- .betterer.results | 25 +-- .../LokiExploreQueryEditor.test.tsx | 20 +-- .../components/LokiExploreQueryEditor.tsx | 2 +- .../LokiExploreQueryEditor.test.tsx.snap | 82 ++++++++- .../datasource/loki/datasource.test.ts | 169 +++++++----------- .../datasource/loki/language_provider.test.ts | 65 ++++--- public/app/plugins/datasource/loki/mocks.ts | 131 +++++++++----- 7 files changed, 283 insertions(+), 211 deletions(-) diff --git a/.betterer.results b/.betterer.results index c2a5871fb7c..d46bf998089 100644 --- a/.betterer.results +++ b/.betterer.results @@ -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"] ], diff --git a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx index d2ee8a6c956..3effa1a57a4 100644 --- a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx @@ -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 | ReturnType) => { + 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> = []; - const props: any = { + const props: Props = { query, data, range, datasource, - exploreMode, history, onChange, onRunQuery, }; - Object.assign(props, { ...props, ...propOverrides }); return renderMethod(); }; diff --git a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx index 6fcfde57716..dacf67da7ba 100644 --- a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx @@ -10,7 +10,7 @@ import { LokiQuery, LokiOptions } from '../types'; import { LokiOptionFields } from './LokiOptionFields'; import { LokiQueryField } from './LokiQueryField'; -type Props = QueryEditorProps; +export type Props = QueryEditorProps; export const LokiExploreQueryEditor = memo((props: Props) => { const { query, data, datasource, history, onChange, onRunQuery, range } = props; diff --git a/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap b/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap index 4e407d408f2..83c86d1813b 100644 --- a/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap +++ b/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap @@ -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 []} diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index 7b332ab6811..06af0b94d88 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -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; - 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({ 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({ 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({ 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({ 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 { +function makeAnnotationQueryRequest(options = {}): AnnotationQueryRequest { const timeRange = { from: dateTime(), to: dateTime(), @@ -872,7 +839,9 @@ function makeAnnotationQueryRequest(options: any): AnnotationQueryRequest ({ @@ -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, + series?: Record>> +): 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; +} diff --git a/public/app/plugins/datasource/loki/mocks.ts b/public/app/plugins/datasource/loki/mocks.ts index e8a33540a4b..59de27bdcc1 100644 --- a/public/app/plugins/datasource/loki/mocks.ts +++ b/public/app/plugins/datasource/loki/mocks.ts @@ -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 { return getMockDataSource({ 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> = {}, + timeSrvStub = defaultTimeSrvMock +): LokiDatasource { + const customSettings: DataSourceInstanceSettings = { + 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, + series?: Record>> +) { + // 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) { + 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}`); + } + } + }; +}