2022-06-08 01:14:34 -05:00
|
|
|
import { of } from 'rxjs';
|
2020-11-18 08:18:55 -06:00
|
|
|
import { take } from 'rxjs/operators';
|
2022-04-22 08:33:13 -05:00
|
|
|
import { getQueryOptions } from 'test/helpers/getQueryOptions';
|
|
|
|
|
2021-09-28 03:42:38 -05:00
|
|
|
import {
|
2021-12-14 07:36:47 -06:00
|
|
|
AbstractLabelOperator,
|
2021-09-28 03:42:38 -05:00
|
|
|
AnnotationQueryRequest,
|
2022-12-08 06:04:05 -06:00
|
|
|
CoreApp,
|
2021-09-28 03:42:38 -05:00
|
|
|
DataFrame,
|
2022-06-08 01:14:34 -05:00
|
|
|
dataFrameToJSON,
|
|
|
|
DataQueryResponse,
|
2022-08-03 08:57:38 -05:00
|
|
|
DataSourceInstanceSettings,
|
2021-09-28 03:42:38 -05:00
|
|
|
dateTime,
|
2021-12-14 07:36:47 -06:00
|
|
|
FieldType,
|
2023-01-20 07:20:49 -06:00
|
|
|
SupplementaryQueryType,
|
2021-09-28 03:42:38 -05:00
|
|
|
} from '@grafana/data';
|
2022-12-06 14:54:20 -06:00
|
|
|
import {
|
|
|
|
BackendSrv,
|
|
|
|
BackendSrvRequest,
|
2023-02-09 11:27:02 -06:00
|
|
|
config,
|
2022-12-06 14:54:20 -06:00
|
|
|
FetchResponse,
|
|
|
|
getBackendSrv,
|
|
|
|
reportInteraction,
|
|
|
|
setBackendSrv,
|
|
|
|
} from '@grafana/runtime';
|
2022-04-22 08:33:13 -05:00
|
|
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
|
|
|
|
2020-08-31 23:21:21 -05:00
|
|
|
import { initialCustomVariableModelState } from '../../../features/variables/custom/reducer';
|
2022-04-22 08:33:13 -05:00
|
|
|
import { CustomVariableModel } from '../../../features/variables/types';
|
|
|
|
|
2022-12-07 09:24:22 -06:00
|
|
|
import { LokiDatasource, REF_ID_DATA_SAMPLES } from './datasource';
|
2022-12-06 14:54:20 -06:00
|
|
|
import { createLokiDatasource, createMetadataRequest } from './mocks';
|
2023-04-03 07:30:08 -05:00
|
|
|
import { runSplitQuery } from './querySplitting';
|
2023-02-13 00:59:20 -06:00
|
|
|
import { parseToNodeNamesArray } from './queryUtils';
|
2023-06-29 10:50:17 -05:00
|
|
|
import { LokiOptions, LokiQuery, LokiQueryType, LokiVariableQueryType, SupportingQueryType } from './types';
|
2022-08-30 11:18:51 -05:00
|
|
|
import { LokiVariableSupport } from './variables';
|
2020-01-21 03:08:07 -06:00
|
|
|
|
2022-12-06 14:54:20 -06:00
|
|
|
jest.mock('@grafana/runtime', () => {
|
|
|
|
return {
|
|
|
|
...jest.requireActual('@grafana/runtime'),
|
|
|
|
reportInteraction: jest.fn(),
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2023-04-03 07:30:08 -05:00
|
|
|
jest.mock('./querySplitting');
|
2023-02-09 11:27:02 -06:00
|
|
|
|
2021-07-06 01:56:01 -05:00
|
|
|
const templateSrvStub = {
|
2022-08-26 02:26:48 -05:00
|
|
|
getAdhocFilters: jest.fn(() => [] as unknown[]),
|
|
|
|
replace: jest.fn((a: string, ...rest: unknown[]) => a),
|
|
|
|
} as unknown as TemplateSrv;
|
2020-08-14 03:33:37 -05:00
|
|
|
|
2022-06-08 01:14:34 -05:00
|
|
|
const testFrame: DataFrame = {
|
|
|
|
refId: 'A',
|
|
|
|
fields: [
|
|
|
|
{
|
|
|
|
name: 'Time',
|
|
|
|
type: FieldType.time,
|
|
|
|
config: {},
|
2023-04-20 09:59:18 -05:00
|
|
|
values: [1, 2],
|
2022-06-08 01:14:34 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'Line',
|
|
|
|
type: FieldType.string,
|
|
|
|
config: {},
|
2023-04-20 09:59:18 -05:00
|
|
|
values: ['line1', 'line2'],
|
2022-06-08 01:14:34 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'labels',
|
|
|
|
type: FieldType.other,
|
|
|
|
config: {},
|
2023-04-20 09:59:18 -05:00
|
|
|
values: [
|
2022-06-08 01:14:34 -05:00
|
|
|
{
|
|
|
|
label: 'value',
|
|
|
|
label2: 'value ',
|
|
|
|
},
|
2020-10-16 10:24:23 -05:00
|
|
|
{
|
2022-06-08 01:14:34 -05:00
|
|
|
label: '',
|
|
|
|
label2: 'value2',
|
|
|
|
label3: ' ',
|
2020-10-16 10:24:23 -05:00
|
|
|
},
|
2023-04-20 09:59:18 -05:00
|
|
|
],
|
2020-10-16 10:24:23 -05:00
|
|
|
},
|
2022-06-08 01:14:34 -05:00
|
|
|
{
|
|
|
|
name: 'tsNs',
|
|
|
|
type: FieldType.string,
|
|
|
|
config: {},
|
2023-04-20 09:59:18 -05:00
|
|
|
values: ['1000000', '2000000'],
|
2022-06-08 01:14:34 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'id',
|
|
|
|
type: FieldType.string,
|
|
|
|
config: {},
|
2023-04-20 09:59:18 -05:00
|
|
|
values: ['id1', 'id2'],
|
2022-06-08 01:14:34 -05:00
|
|
|
},
|
|
|
|
],
|
|
|
|
length: 2,
|
2020-10-16 10:24:23 -05:00
|
|
|
};
|
|
|
|
|
2022-06-08 01:14:34 -05:00
|
|
|
const testLogsResponse: FetchResponse = {
|
2020-11-20 04:12:34 -06:00
|
|
|
data: {
|
2022-06-08 01:14:34 -05:00
|
|
|
results: {
|
|
|
|
A: {
|
|
|
|
frames: [dataFrameToJSON(testFrame)],
|
|
|
|
},
|
2020-11-20 04:12:34 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
ok: true,
|
2022-02-02 06:02:32 -06:00
|
|
|
headers: {} as unknown as Headers,
|
2020-11-20 04:12:34 -06:00
|
|
|
redirected: false,
|
|
|
|
status: 200,
|
2022-06-08 01:14:34 -05:00
|
|
|
statusText: 'Success',
|
|
|
|
type: 'default',
|
2020-11-20 04:12:34 -06:00
|
|
|
url: '',
|
2022-02-02 06:02:32 -06:00
|
|
|
config: {} as unknown as BackendSrvRequest,
|
2020-11-20 04:12:34 -06:00
|
|
|
};
|
|
|
|
|
2021-08-27 10:06:22 -05:00
|
|
|
interface AdHocFilter {
|
|
|
|
condition: string;
|
|
|
|
key: string;
|
|
|
|
operator: string;
|
|
|
|
value: string;
|
|
|
|
}
|
|
|
|
|
2018-12-05 17:19:55 -06:00
|
|
|
describe('LokiDatasource', () => {
|
2022-06-08 01:14:34 -05:00
|
|
|
let origBackendSrv: BackendSrv;
|
2018-12-05 17:19:55 -06:00
|
|
|
|
2020-01-21 03:08:07 -06:00
|
|
|
beforeEach(() => {
|
2022-06-08 01:14:34 -05:00
|
|
|
origBackendSrv = getBackendSrv();
|
2020-01-21 03:08:07 -06:00
|
|
|
});
|
2018-12-31 05:25:28 -06:00
|
|
|
|
2022-06-08 01:14:34 -05:00
|
|
|
afterEach(() => {
|
|
|
|
setBackendSrv(origBackendSrv);
|
2022-12-06 14:54:20 -06:00
|
|
|
(reportInteraction as jest.Mock).mockClear();
|
2019-12-04 06:43:22 -06:00
|
|
|
});
|
|
|
|
|
2020-11-20 04:12:34 -06:00
|
|
|
describe('when doing logs queries with limits', () => {
|
2022-06-08 01:14:34 -05:00
|
|
|
const runTest = async (
|
|
|
|
queryMaxLines: number | undefined,
|
2022-08-26 02:26:48 -05:00
|
|
|
dsMaxLines: string | undefined,
|
2022-12-08 06:04:05 -06:00
|
|
|
expectedMaxLines: number,
|
|
|
|
app: CoreApp | undefined
|
2022-06-08 01:14:34 -05:00
|
|
|
) => {
|
2022-08-26 02:26:48 -05:00
|
|
|
const settings = {
|
2020-10-16 06:30:02 -05:00
|
|
|
jsonData: {
|
|
|
|
maxLines: dsMaxLines,
|
|
|
|
},
|
2022-08-03 08:57:38 -05:00
|
|
|
} as DataSourceInstanceSettings<LokiOptions>;
|
2020-01-21 03:08:07 -06:00
|
|
|
|
2022-08-26 02:26:48 -05:00
|
|
|
const ds = createLokiDatasource(templateSrvStub, settings);
|
2019-11-15 09:38:25 -06:00
|
|
|
|
2022-06-08 01:14:34 -05:00
|
|
|
// we need to check the final query before it is sent out,
|
|
|
|
// and applyTemplateVariables is a convenient place to do that.
|
|
|
|
const spy = jest.spyOn(ds, 'applyTemplateVariables');
|
2020-08-31 23:21:21 -05:00
|
|
|
|
2022-06-08 01:14:34 -05:00
|
|
|
const options = getQueryOptions<LokiQuery>({
|
|
|
|
targets: [{ expr: '{a="b"}', refId: 'B', maxLines: queryMaxLines }],
|
2022-12-08 06:04:05 -06:00
|
|
|
app: app ?? CoreApp.Dashboard,
|
2022-06-08 01:14:34 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
const fetchMock = jest.fn().mockReturnValue(of({ data: testLogsResponse }));
|
|
|
|
setBackendSrv({ ...origBackendSrv, fetch: fetchMock });
|
2020-08-31 23:21:21 -05:00
|
|
|
|
2020-11-18 08:18:55 -06:00
|
|
|
await expect(ds.query(options).pipe(take(1))).toEmitValuesWith(() => {
|
|
|
|
expect(fetchMock.mock.calls.length).toBe(1);
|
2022-06-08 01:14:34 -05:00
|
|
|
expect(spy.mock.calls[0][0].maxLines).toBe(expectedMaxLines);
|
2020-11-18 08:18:55 -06:00
|
|
|
});
|
2020-08-31 23:21:21 -05:00
|
|
|
};
|
2019-01-18 11:59:32 -06:00
|
|
|
|
2022-06-08 01:14:34 -05:00
|
|
|
it('should use datasource max lines when no query max lines', async () => {
|
2022-12-08 06:04:05 -06:00
|
|
|
await runTest(undefined, '40', 40, undefined);
|
2019-09-05 07:04:01 -05:00
|
|
|
});
|
2019-01-18 11:59:32 -06:00
|
|
|
|
2022-06-08 01:14:34 -05:00
|
|
|
it('should use query max lines, if exists', async () => {
|
2022-12-08 06:04:05 -06:00
|
|
|
await runTest(80, undefined, 80, undefined);
|
2020-11-18 08:18:55 -06:00
|
|
|
});
|
|
|
|
|
2022-06-08 01:14:34 -05:00
|
|
|
it('should use query max lines, if both exist, even if it is higher than ds max lines', async () => {
|
2022-12-08 06:04:05 -06:00
|
|
|
await runTest(80, '40', 80, undefined);
|
2018-12-31 05:25:28 -06:00
|
|
|
});
|
2022-12-06 14:54:20 -06:00
|
|
|
|
2023-01-18 03:14:42 -06:00
|
|
|
it('should use query max lines, if both exist, even if it is 0', async () => {
|
|
|
|
await runTest(0, '40', 0, undefined);
|
|
|
|
});
|
|
|
|
|
2022-12-06 14:54:20 -06:00
|
|
|
it('should report query interaction', async () => {
|
2022-12-08 06:04:05 -06:00
|
|
|
await runTest(80, '40', 80, CoreApp.Explore);
|
|
|
|
expect(reportInteraction).toHaveBeenCalledWith(
|
|
|
|
'grafana_loki_query_executed',
|
|
|
|
expect.objectContaining({
|
|
|
|
query_type: 'logs',
|
|
|
|
line_limit: 80,
|
|
|
|
parsed_query: parseToNodeNamesArray('{a="b"}').join(','),
|
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not report query interaction for dashboard query', async () => {
|
|
|
|
await runTest(80, '40', 80, CoreApp.Dashboard);
|
|
|
|
expect(reportInteraction).not.toBeCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not report query interaction for panel edit query', async () => {
|
|
|
|
await runTest(80, '40', 80, CoreApp.PanelEditor);
|
2022-12-06 14:54:20 -06:00
|
|
|
expect(reportInteraction).toHaveBeenCalledWith(
|
|
|
|
'grafana_loki_query_executed',
|
|
|
|
expect.objectContaining({
|
|
|
|
query_type: 'logs',
|
|
|
|
line_limit: 80,
|
|
|
|
parsed_query: parseToNodeNamesArray('{a="b"}').join(','),
|
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
2020-08-31 23:21:21 -05:00
|
|
|
});
|
2019-02-01 06:30:15 -06:00
|
|
|
|
2022-06-08 01:14:34 -05:00
|
|
|
describe('When using adhoc filters', () => {
|
|
|
|
const DEFAULT_EXPR = 'rate({bar="baz", job="foo"} |= "bar" [5m])';
|
|
|
|
const query: LokiQuery = { expr: DEFAULT_EXPR, refId: 'A' };
|
2022-08-26 02:26:48 -05:00
|
|
|
const mockedGetAdhocFilters = templateSrvStub.getAdhocFilters as jest.Mock;
|
|
|
|
const ds = createLokiDatasource(templateSrvStub);
|
2019-02-01 06:30:15 -06:00
|
|
|
|
2022-06-08 01:14:34 -05:00
|
|
|
it('should not modify expression with no filters', async () => {
|
|
|
|
expect(ds.applyTemplateVariables(query, {}).expr).toBe(DEFAULT_EXPR);
|
2020-10-04 14:41:12 -05:00
|
|
|
});
|
|
|
|
|
2022-06-08 01:14:34 -05:00
|
|
|
it('should add filters to expression', async () => {
|
2022-08-26 02:26:48 -05:00
|
|
|
mockedGetAdhocFilters.mockReturnValue([
|
2022-06-08 01:14:34 -05:00
|
|
|
{
|
|
|
|
key: 'k1',
|
|
|
|
operator: '=',
|
|
|
|
value: 'v1',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
key: 'k2',
|
|
|
|
operator: '!=',
|
|
|
|
value: 'v2',
|
|
|
|
},
|
|
|
|
]);
|
2020-07-09 03:13:41 -05:00
|
|
|
|
2022-06-08 01:14:34 -05:00
|
|
|
expect(ds.applyTemplateVariables(query, {}).expr).toBe(
|
2022-06-24 15:29:22 -05:00
|
|
|
'rate({bar="baz", job="foo", k1="v1", k2!="v2"} |= "bar" [5m])'
|
2020-11-18 08:18:55 -06:00
|
|
|
);
|
2021-07-06 03:30:27 -05:00
|
|
|
});
|
|
|
|
|
2022-06-08 01:14:34 -05:00
|
|
|
it('should add escaping if needed to regex filter expressions', async () => {
|
2022-08-26 02:26:48 -05:00
|
|
|
mockedGetAdhocFilters.mockReturnValue([
|
2022-06-08 01:14:34 -05:00
|
|
|
{
|
|
|
|
key: 'k1',
|
|
|
|
operator: '=~',
|
|
|
|
value: 'v.*',
|
2021-07-06 01:56:01 -05:00
|
|
|
},
|
2022-06-08 01:14:34 -05:00
|
|
|
{
|
|
|
|
key: 'k2',
|
|
|
|
operator: '=~',
|
|
|
|
value: `v'.*`,
|
|
|
|
},
|
|
|
|
]);
|
|
|
|
expect(ds.applyTemplateVariables(query, {}).expr).toBe(
|
2022-10-07 09:21:59 -05:00
|
|
|
'rate({bar="baz", job="foo", k1=~"v.*", k2=~"v\\\\\'.*"} |= "bar" [5m])'
|
2022-06-08 01:14:34 -05:00
|
|
|
);
|
2021-07-06 01:56:01 -05:00
|
|
|
});
|
2018-12-31 05:25:28 -06:00
|
|
|
});
|
|
|
|
|
2020-08-31 23:21:21 -05:00
|
|
|
describe('when interpolating variables', () => {
|
2019-11-15 09:38:25 -06:00
|
|
|
let ds: LokiDatasource;
|
2020-06-04 06:44:48 -05:00
|
|
|
let variable: CustomVariableModel;
|
2019-11-06 10:29:44 -06:00
|
|
|
|
|
|
|
beforeEach(() => {
|
2022-08-26 02:26:48 -05:00
|
|
|
ds = createLokiDatasource(templateSrvStub);
|
2020-06-04 06:44:48 -05:00
|
|
|
variable = { ...initialCustomVariableModelState };
|
2019-11-06 10:29:44 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should only escape single quotes', () => {
|
|
|
|
expect(ds.interpolateQueryExpr("abc'$^*{}[]+?.()|", variable)).toEqual("abc\\\\'$^*{}[]+?.()|");
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return a number', () => {
|
|
|
|
expect(ds.interpolateQueryExpr(1000, variable)).toEqual(1000);
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('and variable allows multi-value', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
variable.multi = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should regex escape values if the value is a string', () => {
|
|
|
|
expect(ds.interpolateQueryExpr('looking*glass', variable)).toEqual('looking\\\\*glass');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return pipe separated values if the value is an array of strings', () => {
|
|
|
|
expect(ds.interpolateQueryExpr(['a|bc', 'de|f'], variable)).toEqual('a\\\\|bc|de\\\\|f');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('and variable allows all', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
variable.includeAll = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should regex escape values if the array is a string', () => {
|
|
|
|
expect(ds.interpolateQueryExpr('looking*glass', variable)).toEqual('looking\\\\*glass');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return pipe separated values if the value is an array of strings', () => {
|
|
|
|
expect(ds.interpolateQueryExpr(['a|bc', 'de|f'], variable)).toEqual('a\\\\|bc|de\\\\|f');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-09-29 04:17:41 -05:00
|
|
|
describe('when running interpolateVariablesInQueries', () => {
|
|
|
|
it('should call addAdHocFilters', () => {
|
|
|
|
const ds = createLokiDatasource(templateSrvStub);
|
|
|
|
ds.addAdHocFilters = jest.fn();
|
|
|
|
const expr = 'rate({bar="baz", job="foo"} [5m]';
|
|
|
|
const queries = [
|
|
|
|
{
|
|
|
|
refId: 'A',
|
|
|
|
expr,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
ds.interpolateVariablesInQueries(queries, {});
|
|
|
|
expect(ds.addAdHocFilters).toHaveBeenCalledWith(expr);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-12-05 17:19:55 -06:00
|
|
|
describe('when performing testDataSource', () => {
|
2022-08-26 02:26:48 -05:00
|
|
|
let ds: LokiDatasource;
|
|
|
|
beforeEach(() => {
|
|
|
|
ds = createLokiDatasource(templateSrvStub);
|
|
|
|
});
|
|
|
|
|
2022-05-05 02:43:36 -05:00
|
|
|
it('should return successfully when call succeeds with labels', async () => {
|
|
|
|
ds.metadataRequest = () => Promise.resolve(['avalue']);
|
2020-08-31 23:21:21 -05:00
|
|
|
|
2022-05-05 02:43:36 -05:00
|
|
|
const result = await ds.testDatasource();
|
2018-12-05 17:19:55 -06:00
|
|
|
|
2022-05-05 02:43:36 -05:00
|
|
|
expect(result).toStrictEqual({
|
|
|
|
status: 'success',
|
2023-04-14 04:39:54 -05:00
|
|
|
message: 'Data source successfully connected.',
|
2018-12-05 17:19:55 -06:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-05-05 02:43:36 -05:00
|
|
|
it('should return error when call succeeds without labels', async () => {
|
|
|
|
ds.metadataRequest = () => Promise.resolve([]);
|
2020-11-18 08:18:55 -06:00
|
|
|
|
2022-05-05 02:43:36 -05:00
|
|
|
const result = await ds.testDatasource();
|
2020-08-31 23:21:21 -05:00
|
|
|
|
2022-05-05 02:43:36 -05:00
|
|
|
expect(result).toStrictEqual({
|
|
|
|
status: 'error',
|
2023-04-14 04:39:54 -05:00
|
|
|
message:
|
|
|
|
'Data source connected, but no labels were received. Verify that Loki and Promtail are correctly configured.',
|
2018-12-05 17:19:55 -06:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-05-05 02:43:36 -05:00
|
|
|
it('should return error status with no details when call fails with no details', async () => {
|
|
|
|
ds.metadataRequest = () => Promise.reject({});
|
2020-08-31 23:21:21 -05:00
|
|
|
|
2022-05-05 02:43:36 -05:00
|
|
|
const result = await ds.testDatasource();
|
2018-12-05 17:19:55 -06:00
|
|
|
|
2022-05-05 02:43:36 -05:00
|
|
|
expect(result).toStrictEqual({
|
|
|
|
status: 'error',
|
2023-04-14 04:39:54 -05:00
|
|
|
message: 'Unable to connect with Loki. Please check the server logs for more details.',
|
2018-12-05 17:19:55 -06:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-05-05 02:43:36 -05:00
|
|
|
it('should return error status with details when call fails with details', async () => {
|
|
|
|
ds.metadataRequest = () =>
|
|
|
|
Promise.reject({
|
|
|
|
data: {
|
|
|
|
message: 'error42',
|
|
|
|
},
|
|
|
|
});
|
2020-08-31 23:21:21 -05:00
|
|
|
|
2022-05-05 02:43:36 -05:00
|
|
|
const result = await ds.testDatasource();
|
2018-12-05 17:19:55 -06:00
|
|
|
|
2022-05-05 02:43:36 -05:00
|
|
|
expect(result).toStrictEqual({
|
|
|
|
status: 'error',
|
2023-04-14 04:39:54 -05:00
|
|
|
message: 'Unable to connect with Loki (error42). Please check the server logs for more details.',
|
2018-12-05 17:19:55 -06:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2019-09-10 04:04:44 -05:00
|
|
|
|
2020-08-31 23:21:21 -05:00
|
|
|
describe('when calling annotationQuery', () => {
|
2022-08-26 02:26:48 -05:00
|
|
|
const getTestContext = (frame: DataFrame, options = {}) => {
|
2021-05-28 03:12:03 -05:00
|
|
|
const query = makeAnnotationQueryRequest(options);
|
2020-11-18 08:18:55 -06:00
|
|
|
|
2022-08-26 02:26:48 -05:00
|
|
|
const ds = createLokiDatasource(templateSrvStub);
|
2022-06-08 01:14:34 -05:00
|
|
|
const response: DataQueryResponse = {
|
|
|
|
data: [frame],
|
|
|
|
};
|
|
|
|
ds.query = () => of(response);
|
|
|
|
return ds.annotationQuery(query);
|
2020-08-31 23:21:21 -05:00
|
|
|
};
|
|
|
|
|
2019-11-15 09:38:25 -06:00
|
|
|
it('should transform the loki data to annotation response', async () => {
|
2022-06-08 01:14:34 -05:00
|
|
|
const testFrame: DataFrame = {
|
|
|
|
refId: 'A',
|
|
|
|
fields: [
|
|
|
|
{
|
|
|
|
name: 'Time',
|
|
|
|
type: FieldType.time,
|
|
|
|
config: {},
|
2023-04-20 09:59:18 -05:00
|
|
|
values: [1, 2],
|
2022-06-08 01:14:34 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'Line',
|
|
|
|
type: FieldType.string,
|
|
|
|
config: {},
|
2023-04-20 09:59:18 -05:00
|
|
|
values: ['hello', 'hello 2'],
|
2022-06-08 01:14:34 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'labels',
|
|
|
|
type: FieldType.other,
|
|
|
|
config: {},
|
2023-04-20 09:59:18 -05:00
|
|
|
values: [
|
2020-08-31 23:21:21 -05:00
|
|
|
{
|
2022-06-08 01:14:34 -05:00
|
|
|
label: 'value',
|
|
|
|
label2: 'value ',
|
2020-01-21 03:08:07 -06:00
|
|
|
},
|
2020-08-31 23:21:21 -05:00
|
|
|
{
|
2022-06-08 01:14:34 -05:00
|
|
|
label: '',
|
|
|
|
label2: 'value2',
|
|
|
|
label3: ' ',
|
2020-08-31 23:21:21 -05:00
|
|
|
},
|
2023-04-20 09:59:18 -05:00
|
|
|
],
|
2020-08-31 23:21:21 -05:00
|
|
|
},
|
2022-06-08 01:14:34 -05:00
|
|
|
{
|
|
|
|
name: 'tsNs',
|
|
|
|
type: FieldType.string,
|
|
|
|
config: {},
|
2023-04-20 09:59:18 -05:00
|
|
|
values: ['1000000', '2000000'],
|
2022-06-08 01:14:34 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'id',
|
|
|
|
type: FieldType.string,
|
|
|
|
config: {},
|
2023-04-20 09:59:18 -05:00
|
|
|
values: ['id1', 'id2'],
|
2022-06-08 01:14:34 -05:00
|
|
|
},
|
|
|
|
],
|
|
|
|
length: 2,
|
|
|
|
};
|
|
|
|
const res = await getTestContext(testFrame, { stepInterval: '15s' });
|
2019-09-10 04:04:44 -05:00
|
|
|
|
|
|
|
expect(res.length).toBe(2);
|
|
|
|
expect(res[0].text).toBe('hello');
|
|
|
|
expect(res[0].tags).toEqual(['value']);
|
|
|
|
|
|
|
|
expect(res[1].text).toBe('hello 2');
|
|
|
|
expect(res[1].tags).toEqual(['value2']);
|
|
|
|
});
|
2022-06-08 01:14:34 -05:00
|
|
|
|
2021-05-28 03:12:03 -05:00
|
|
|
describe('Formatting', () => {
|
2022-06-08 01:14:34 -05:00
|
|
|
const testFrame: DataFrame = {
|
|
|
|
refId: 'A',
|
|
|
|
fields: [
|
|
|
|
{
|
|
|
|
name: 'Time',
|
|
|
|
type: FieldType.time,
|
|
|
|
config: {},
|
2023-04-20 09:59:18 -05:00
|
|
|
values: [1],
|
2022-06-08 01:14:34 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'Line',
|
|
|
|
type: FieldType.string,
|
|
|
|
config: {},
|
2023-04-20 09:59:18 -05:00
|
|
|
values: ['hello'],
|
2022-06-08 01:14:34 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'labels',
|
|
|
|
type: FieldType.other,
|
|
|
|
config: {},
|
2023-04-20 09:59:18 -05:00
|
|
|
values: [
|
2021-05-28 03:12:03 -05:00
|
|
|
{
|
2022-06-08 01:14:34 -05:00
|
|
|
label: 'value',
|
|
|
|
label2: 'value2',
|
|
|
|
label3: 'value3',
|
2021-05-28 03:12:03 -05:00
|
|
|
},
|
2023-04-20 09:59:18 -05:00
|
|
|
],
|
2021-05-28 03:12:03 -05:00
|
|
|
},
|
2022-06-08 01:14:34 -05:00
|
|
|
{
|
|
|
|
name: 'tsNs',
|
|
|
|
type: FieldType.string,
|
|
|
|
config: {},
|
2023-04-20 09:59:18 -05:00
|
|
|
values: ['1000000'],
|
2022-06-08 01:14:34 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'id',
|
|
|
|
type: FieldType.string,
|
|
|
|
config: {},
|
2023-04-20 09:59:18 -05:00
|
|
|
values: ['id1'],
|
2022-06-08 01:14:34 -05:00
|
|
|
},
|
|
|
|
],
|
|
|
|
length: 1,
|
|
|
|
};
|
2021-05-28 03:12:03 -05:00
|
|
|
describe('When tagKeys is set', () => {
|
|
|
|
it('should only include selected labels', async () => {
|
2022-06-08 01:14:34 -05:00
|
|
|
const res = await getTestContext(testFrame, { tagKeys: 'label2,label3', stepInterval: '15s' });
|
2021-05-28 03:12:03 -05:00
|
|
|
|
|
|
|
expect(res.length).toBe(1);
|
|
|
|
expect(res[0].text).toBe('hello');
|
|
|
|
expect(res[0].tags).toEqual(['value2', 'value3']);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe('When textFormat is set', () => {
|
2022-06-24 15:29:22 -05:00
|
|
|
it('should format the text accordingly', async () => {
|
2022-06-08 01:14:34 -05:00
|
|
|
const res = await getTestContext(testFrame, { textFormat: 'hello {{label2}}', stepInterval: '15s' });
|
2021-05-28 03:12:03 -05:00
|
|
|
|
|
|
|
expect(res.length).toBe(1);
|
|
|
|
expect(res[0].text).toBe('hello value2');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe('When titleFormat is set', () => {
|
2022-06-24 15:29:22 -05:00
|
|
|
it('should format the title accordingly', async () => {
|
2022-06-08 01:14:34 -05:00
|
|
|
const res = await getTestContext(testFrame, { titleFormat: 'Title {{label2}}', stepInterval: '15s' });
|
2021-05-28 03:12:03 -05:00
|
|
|
|
|
|
|
expect(res.length).toBe(1);
|
|
|
|
expect(res[0].title).toBe('Title value2');
|
|
|
|
expect(res[0].text).toBe('hello');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2019-09-10 04:04:44 -05:00
|
|
|
});
|
2019-12-03 02:40:22 -06:00
|
|
|
|
|
|
|
describe('metricFindQuery', () => {
|
2022-08-26 02:26:48 -05:00
|
|
|
const getTestContext = () => {
|
|
|
|
const ds = createLokiDatasource(templateSrvStub);
|
|
|
|
jest
|
|
|
|
.spyOn(ds, 'metadataRequest')
|
|
|
|
.mockImplementation(
|
|
|
|
createMetadataRequest(
|
|
|
|
{ label1: ['value1', 'value2'], label2: ['value3', 'value4'] },
|
|
|
|
{ '{label1="value1", label2="value2"}': [{ label5: 'value5' }] }
|
|
|
|
)
|
|
|
|
);
|
2020-08-31 23:21:21 -05:00
|
|
|
|
|
|
|
return { ds };
|
|
|
|
};
|
|
|
|
|
2022-09-12 10:48:04 -05:00
|
|
|
it('should return label names for Loki', async () => {
|
2022-08-26 02:26:48 -05:00
|
|
|
const { ds } = getTestContext();
|
2019-12-03 02:40:22 -06:00
|
|
|
|
2022-09-12 10:48:04 -05:00
|
|
|
const legacyResult = await ds.metricFindQuery('label_names()');
|
|
|
|
const result = await ds.metricFindQuery({ refId: 'test', type: LokiVariableQueryType.LabelNames });
|
2020-08-31 23:21:21 -05:00
|
|
|
|
2022-09-12 10:48:04 -05:00
|
|
|
expect(legacyResult).toEqual(result);
|
|
|
|
expect(result).toEqual([{ text: 'label1' }, { text: 'label2' }]);
|
2019-12-03 02:40:22 -06:00
|
|
|
});
|
|
|
|
|
2022-09-12 10:48:04 -05:00
|
|
|
it('should return label values for Loki when no matcher', async () => {
|
2022-08-26 02:26:48 -05:00
|
|
|
const { ds } = getTestContext();
|
2020-08-31 23:21:21 -05:00
|
|
|
|
2022-09-12 10:48:04 -05:00
|
|
|
const legacyResult = await ds.metricFindQuery('label_values(label1)');
|
|
|
|
const result = await ds.metricFindQuery({
|
|
|
|
refId: 'test',
|
|
|
|
type: LokiVariableQueryType.LabelValues,
|
|
|
|
label: 'label1',
|
|
|
|
});
|
2020-08-31 23:21:21 -05:00
|
|
|
|
2022-09-12 10:48:04 -05:00
|
|
|
expect(legacyResult).toEqual(result);
|
|
|
|
expect(result).toEqual([{ text: 'value1' }, { text: 'value2' }]);
|
2019-12-03 02:40:22 -06:00
|
|
|
});
|
|
|
|
|
2022-09-12 10:48:04 -05:00
|
|
|
it('should return label values for Loki with matcher', async () => {
|
2022-08-26 02:26:48 -05:00
|
|
|
const { ds } = getTestContext();
|
2020-08-31 23:21:21 -05:00
|
|
|
|
2022-09-12 10:48:04 -05:00
|
|
|
const legacyResult = await ds.metricFindQuery('label_values({label1="value1", label2="value2"},label5)');
|
|
|
|
const result = await ds.metricFindQuery({
|
|
|
|
refId: 'test',
|
|
|
|
type: LokiVariableQueryType.LabelValues,
|
|
|
|
stream: '{label1="value1", label2="value2"}',
|
|
|
|
label: 'label5',
|
|
|
|
});
|
2020-08-31 23:21:21 -05:00
|
|
|
|
2022-09-12 10:48:04 -05:00
|
|
|
expect(legacyResult).toEqual(result);
|
|
|
|
expect(result).toEqual([{ text: 'value5' }]);
|
2021-06-11 03:57:40 -05:00
|
|
|
});
|
|
|
|
|
2022-09-12 10:48:04 -05:00
|
|
|
it('should return empty array when incorrect query for Loki', async () => {
|
2022-08-26 02:26:48 -05:00
|
|
|
const { ds } = getTestContext();
|
2021-06-11 03:57:40 -05:00
|
|
|
|
2022-09-12 10:48:04 -05:00
|
|
|
const result = await ds.metricFindQuery('incorrect_query');
|
2021-06-11 03:57:40 -05:00
|
|
|
|
2022-09-12 10:48:04 -05:00
|
|
|
expect(result).toEqual([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should interpolate strings in the query', async () => {
|
|
|
|
const { ds } = getTestContext();
|
|
|
|
|
|
|
|
await ds.metricFindQuery('label_names()');
|
|
|
|
await ds.metricFindQuery({
|
|
|
|
refId: 'test',
|
|
|
|
type: LokiVariableQueryType.LabelValues,
|
|
|
|
stream: '{label1="value1", label2="value2"}',
|
|
|
|
label: 'label5',
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(templateSrvStub.replace).toHaveBeenCalledWith('label_names()', undefined, expect.any(Function));
|
|
|
|
expect(templateSrvStub.replace).toHaveBeenCalledWith(
|
|
|
|
'{label1="value1", label2="value2"}',
|
|
|
|
undefined,
|
|
|
|
expect.any(Function)
|
|
|
|
);
|
|
|
|
expect(templateSrvStub.replace).toHaveBeenCalledWith('label5', undefined, expect.any(Function));
|
2019-12-03 02:40:22 -06:00
|
|
|
});
|
|
|
|
});
|
2021-06-21 11:50:42 -05:00
|
|
|
|
|
|
|
describe('modifyQuery', () => {
|
|
|
|
describe('when called with ADD_FILTER', () => {
|
2022-08-26 02:26:48 -05:00
|
|
|
let ds: LokiDatasource;
|
|
|
|
beforeEach(() => {
|
|
|
|
ds = createLokiDatasource(templateSrvStub);
|
|
|
|
});
|
|
|
|
|
2021-06-21 11:50:42 -05:00
|
|
|
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"}' };
|
2022-07-18 07:13:34 -05:00
|
|
|
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER' };
|
2021-06-21 11:50:42 -05:00
|
|
|
const result = ds.modifyQuery(query, action);
|
|
|
|
|
|
|
|
expect(result.refId).toEqual('A');
|
2022-06-24 15:29:22 -05:00
|
|
|
expect(result.expr).toEqual('{bar="baz", job="grafana"}');
|
2021-06-21 11:50:42 -05:00
|
|
|
});
|
|
|
|
|
2022-05-04 05:49:04 -05:00
|
|
|
it('then the correctly escaped label should be added for logs query', () => {
|
|
|
|
const query: LokiQuery = { refId: 'A', expr: '{bar="baz"}' };
|
2022-07-18 07:13:34 -05:00
|
|
|
const action = { options: { key: 'job', value: '\\test' }, type: 'ADD_FILTER' };
|
2022-05-04 05:49:04 -05:00
|
|
|
const result = ds.modifyQuery(query, action);
|
|
|
|
|
|
|
|
expect(result.refId).toEqual('A');
|
2022-06-24 15:29:22 -05:00
|
|
|
expect(result.expr).toEqual('{bar="baz", job="\\\\test"}');
|
2022-05-04 05:49:04 -05:00
|
|
|
});
|
|
|
|
|
2021-06-21 11:50:42 -05:00
|
|
|
it('then the correct label should be added for metrics query', () => {
|
|
|
|
const query: LokiQuery = { refId: 'A', expr: 'rate({bar="baz"}[5m])' };
|
2022-07-18 07:13:34 -05:00
|
|
|
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER' };
|
2021-06-21 11:50:42 -05:00
|
|
|
const result = ds.modifyQuery(query, action);
|
|
|
|
|
|
|
|
expect(result.refId).toEqual('A');
|
2022-06-24 15:29:22 -05:00
|
|
|
expect(result.expr).toEqual('rate({bar="baz", job="grafana"}[5m])');
|
2021-06-21 11:50:42 -05:00
|
|
|
});
|
2023-06-26 09:45:33 -05:00
|
|
|
|
|
|
|
describe('and the filter is already present', () => {
|
|
|
|
it('then it should remove the filter', () => {
|
|
|
|
const query: LokiQuery = { refId: 'A', expr: '{bar="baz", job="grafana"}' };
|
2022-07-18 07:13:34 -05:00
|
|
|
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER' };
|
2021-06-21 11:50:42 -05:00
|
|
|
const result = ds.modifyQuery(query, action);
|
|
|
|
|
|
|
|
expect(result.refId).toEqual('A');
|
2023-06-26 09:45:33 -05:00
|
|
|
expect(result.expr).toEqual('{bar="baz"}');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('then it should remove the filter with escaped value', () => {
|
|
|
|
const query: LokiQuery = { refId: 'A', expr: '{place="luna", job="\\"grafana/data\\""}' };
|
|
|
|
const action = { options: { key: 'job', value: '"grafana/data"' }, type: 'ADD_FILTER' };
|
|
|
|
const result = ds.modifyQuery(query, action);
|
|
|
|
|
|
|
|
expect(result.refId).toEqual('A');
|
|
|
|
expect(result.expr).toEqual('{place="luna"}');
|
2021-06-21 11:50:42 -05:00
|
|
|
});
|
2023-06-26 09:45:33 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('and query has parser', () => {
|
|
|
|
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 result = ds.modifyQuery(query, action);
|
|
|
|
|
|
|
|
expect(result.refId).toEqual('A');
|
|
|
|
expect(result.expr).toEqual('{bar="baz"} | logfmt | job=`grafana`');
|
|
|
|
});
|
|
|
|
|
|
|
|
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 result = ds.modifyQuery(query, action);
|
|
|
|
|
|
|
|
expect(result.refId).toEqual('A');
|
|
|
|
expect(result.expr).toEqual('rate({bar="baz"} | logfmt | job=`grafana` [5m])');
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('and the filter is already present', () => {
|
|
|
|
it('then it should remove the filter', () => {
|
|
|
|
const query: LokiQuery = { refId: 'A', expr: '{bar="baz"} | logfmt | job="grafana"' };
|
2022-07-18 07:13:34 -05:00
|
|
|
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER' };
|
2021-06-21 11:50:42 -05:00
|
|
|
const result = ds.modifyQuery(query, action);
|
|
|
|
|
|
|
|
expect(result.refId).toEqual('A');
|
2023-06-26 09:45:33 -05:00
|
|
|
expect(result.expr).toEqual('{bar="baz"} | logfmt');
|
2021-06-21 11:50:42 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('when called with ADD_FILTER_OUT', () => {
|
|
|
|
describe('and query has no parser', () => {
|
2022-08-26 02:26:48 -05:00
|
|
|
let ds: LokiDatasource;
|
|
|
|
beforeEach(() => {
|
|
|
|
ds = createLokiDatasource(templateSrvStub);
|
|
|
|
});
|
|
|
|
|
2021-06-21 11:50:42 -05:00
|
|
|
it('then the correct label should be added for logs query', () => {
|
|
|
|
const query: LokiQuery = { refId: 'A', expr: '{bar="baz"}' };
|
2022-07-18 07:13:34 -05:00
|
|
|
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER_OUT' };
|
2021-06-21 11:50:42 -05:00
|
|
|
const result = ds.modifyQuery(query, action);
|
|
|
|
|
|
|
|
expect(result.refId).toEqual('A');
|
2022-06-24 15:29:22 -05:00
|
|
|
expect(result.expr).toEqual('{bar="baz", job!="grafana"}');
|
2021-06-21 11:50:42 -05:00
|
|
|
});
|
|
|
|
|
2022-05-04 05:49:04 -05:00
|
|
|
it('then the correctly escaped label should be added for logs query', () => {
|
|
|
|
const query: LokiQuery = { refId: 'A', expr: '{bar="baz"}' };
|
2022-07-18 07:13:34 -05:00
|
|
|
const action = { options: { key: 'job', value: '"test' }, type: 'ADD_FILTER_OUT' };
|
2022-05-04 05:49:04 -05:00
|
|
|
const result = ds.modifyQuery(query, action);
|
|
|
|
|
|
|
|
expect(result.refId).toEqual('A');
|
2022-06-24 15:29:22 -05:00
|
|
|
expect(result.expr).toEqual('{bar="baz", job!="\\"test"}');
|
2022-05-04 05:49:04 -05:00
|
|
|
});
|
|
|
|
|
2021-06-21 11:50:42 -05:00
|
|
|
it('then the correct label should be added for metrics query', () => {
|
|
|
|
const query: LokiQuery = { refId: 'A', expr: 'rate({bar="baz"}[5m])' };
|
2022-07-18 07:13:34 -05:00
|
|
|
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER_OUT' };
|
2021-06-21 11:50:42 -05:00
|
|
|
const result = ds.modifyQuery(query, action);
|
|
|
|
|
|
|
|
expect(result.refId).toEqual('A');
|
2022-06-24 15:29:22 -05:00
|
|
|
expect(result.expr).toEqual('rate({bar="baz", job!="grafana"}[5m])');
|
2021-06-21 11:50:42 -05:00
|
|
|
});
|
2022-08-26 02:26:48 -05:00
|
|
|
|
2023-06-26 09:45:33 -05:00
|
|
|
describe('and the opposite filter is present', () => {
|
|
|
|
it('then it should remove the filter', () => {
|
|
|
|
const query: LokiQuery = { refId: 'A', expr: '{bar="baz", job="grafana"}' };
|
2022-07-18 07:13:34 -05:00
|
|
|
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER_OUT' };
|
2021-06-21 11:50:42 -05:00
|
|
|
const result = ds.modifyQuery(query, action);
|
|
|
|
|
|
|
|
expect(result.refId).toEqual('A');
|
2023-06-26 09:45:33 -05:00
|
|
|
expect(result.expr).toEqual('{bar="baz", job!="grafana"}');
|
2021-06-21 11:50:42 -05:00
|
|
|
});
|
2023-06-26 09:45:33 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
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 result = ds.modifyQuery(query, action);
|
|
|
|
|
|
|
|
expect(result.refId).toEqual('A');
|
|
|
|
expect(result.expr).toEqual('{bar="baz"} | logfmt | job!=`grafana`');
|
|
|
|
});
|
|
|
|
|
|
|
|
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 result = ds.modifyQuery(query, action);
|
|
|
|
|
|
|
|
expect(result.refId).toEqual('A');
|
|
|
|
expect(result.expr).toEqual('rate({bar="baz"} | logfmt | job!=`grafana` [5m])');
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('and the filter is already present', () => {
|
|
|
|
it('then it should remove the filter', () => {
|
|
|
|
const query: LokiQuery = { refId: 'A', expr: '{bar="baz"} | logfmt | job="grafana"' };
|
2022-07-18 07:13:34 -05:00
|
|
|
const action = { options: { key: 'job', value: 'grafana' }, type: 'ADD_FILTER_OUT' };
|
2021-06-21 11:50:42 -05:00
|
|
|
const result = ds.modifyQuery(query, action);
|
|
|
|
|
|
|
|
expect(result.refId).toEqual('A');
|
2023-06-26 09:45:33 -05:00
|
|
|
expect(result.expr).toEqual('{bar="baz"} | logfmt | job!=`grafana`');
|
2021-06-21 11:50:42 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2021-08-16 07:02:13 -05:00
|
|
|
|
2021-08-27 10:06:22 -05:00
|
|
|
describe('addAdHocFilters', () => {
|
|
|
|
let ds: LokiDatasource;
|
2022-10-07 09:21:59 -05:00
|
|
|
const createTemplateSrvMock = (options: { adHocFilters: AdHocFilter[] }) => {
|
|
|
|
return {
|
|
|
|
getAdhocFilters: (): AdHocFilter[] => options.adHocFilters,
|
|
|
|
replace: (a: string) => a,
|
|
|
|
} as unknown as TemplateSrv;
|
|
|
|
};
|
2021-08-27 10:06:22 -05:00
|
|
|
describe('when called with "=" operator', () => {
|
|
|
|
beforeEach(() => {
|
2022-10-07 09:21:59 -05:00
|
|
|
const defaultAdHocFilters: AdHocFilter[] = [
|
2021-08-27 10:06:22 -05:00
|
|
|
{
|
|
|
|
condition: '',
|
|
|
|
key: 'job',
|
|
|
|
operator: '=',
|
|
|
|
value: 'grafana',
|
|
|
|
},
|
|
|
|
];
|
2022-10-07 09:21:59 -05:00
|
|
|
ds = createLokiDatasource(createTemplateSrvMock({ adHocFilters: defaultAdHocFilters }));
|
2021-08-27 10:06:22 -05:00
|
|
|
});
|
|
|
|
describe('and query has no parser', () => {
|
|
|
|
it('then the correct label should be added for logs query', () => {
|
2022-06-24 15:29:22 -05:00
|
|
|
assertAdHocFilters('{bar="baz"}', '{bar="baz", job="grafana"}', ds);
|
2021-08-27 10:06:22 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
it('then the correct label should be added for metrics query', () => {
|
2022-06-24 15:29:22 -05:00
|
|
|
assertAdHocFilters('rate({bar="baz"}[5m])', 'rate({bar="baz", job="grafana"}[5m])', ds);
|
2021-08-27 10:06:22 -05:00
|
|
|
});
|
2022-09-09 05:04:51 -05:00
|
|
|
|
|
|
|
it('then the correct label should be added for metrics query and variable', () => {
|
|
|
|
assertAdHocFilters('rate({bar="baz"}[$__interval])', 'rate({bar="baz", job="grafana"}[$__interval])', ds);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('then the correct label should be added for logs query with empty selector', () => {
|
|
|
|
assertAdHocFilters('{}', '{job="grafana"}', ds);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('then the correct label should be added for metrics query with empty selector', () => {
|
|
|
|
assertAdHocFilters('rate({}[5m])', 'rate({job="grafana"}[5m])', ds);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('then the correct label should be added for metrics query with empty selector and variable', () => {
|
|
|
|
assertAdHocFilters('rate({}[$__interval])', 'rate({job="grafana"}[$__interval])', ds);
|
|
|
|
});
|
2022-10-07 09:21:59 -05:00
|
|
|
it('should correctly escape special characters in ad hoc filter', () => {
|
|
|
|
const ds = createLokiDatasource(
|
|
|
|
createTemplateSrvMock({
|
|
|
|
adHocFilters: [
|
|
|
|
{
|
|
|
|
condition: '',
|
|
|
|
key: 'instance',
|
|
|
|
operator: '=',
|
|
|
|
value: '"test"',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
})
|
|
|
|
);
|
|
|
|
assertAdHocFilters('{job="grafana"}', '{job="grafana", instance="\\"test\\""}', ds);
|
|
|
|
});
|
2021-08-27 10:06:22 -05:00
|
|
|
});
|
|
|
|
describe('and query has parser', () => {
|
|
|
|
it('then the correct label should be added for logs query', () => {
|
2022-06-24 15:29:22 -05:00
|
|
|
assertAdHocFilters('{bar="baz"} | logfmt', '{bar="baz"} | logfmt | job=`grafana`', ds);
|
2021-08-27 10:06:22 -05:00
|
|
|
});
|
|
|
|
it('then the correct label should be added for metrics query', () => {
|
2022-06-24 15:29:22 -05:00
|
|
|
assertAdHocFilters('rate({bar="baz"} | logfmt [5m])', 'rate({bar="baz"} | logfmt | job=`grafana` [5m])', ds);
|
2021-08-27 10:06:22 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('when called with "!=" operator', () => {
|
|
|
|
beforeEach(() => {
|
2022-10-07 09:21:59 -05:00
|
|
|
const defaultAdHocFilters: AdHocFilter[] = [
|
2021-08-27 10:06:22 -05:00
|
|
|
{
|
|
|
|
condition: '',
|
|
|
|
key: 'job',
|
|
|
|
operator: '!=',
|
|
|
|
value: 'grafana',
|
|
|
|
},
|
|
|
|
];
|
2022-10-07 09:21:59 -05:00
|
|
|
ds = createLokiDatasource(createTemplateSrvMock({ adHocFilters: defaultAdHocFilters }));
|
2021-08-27 10:06:22 -05:00
|
|
|
});
|
|
|
|
describe('and query has no parser', () => {
|
|
|
|
it('then the correct label should be added for logs query', () => {
|
2022-06-24 15:29:22 -05:00
|
|
|
assertAdHocFilters('{bar="baz"}', '{bar="baz", job!="grafana"}', ds);
|
2021-08-27 10:06:22 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
it('then the correct label should be added for metrics query', () => {
|
2022-06-24 15:29:22 -05:00
|
|
|
assertAdHocFilters('rate({bar="baz"}[5m])', 'rate({bar="baz", job!="grafana"}[5m])', ds);
|
2021-08-27 10:06:22 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
describe('and query has parser', () => {
|
|
|
|
it('then the correct label should be added for logs query', () => {
|
2022-06-24 15:29:22 -05:00
|
|
|
assertAdHocFilters('{bar="baz"} | logfmt', '{bar="baz"} | logfmt | job!=`grafana`', ds);
|
2021-08-27 10:06:22 -05:00
|
|
|
});
|
|
|
|
it('then the correct label should be added for metrics query', () => {
|
2022-06-24 15:29:22 -05:00
|
|
|
assertAdHocFilters('rate({bar="baz"} | logfmt [5m])', 'rate({bar="baz"} | logfmt | job!=`grafana` [5m])', ds);
|
2021-08-27 10:06:22 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2022-10-07 09:21:59 -05:00
|
|
|
|
|
|
|
describe('when called with regex operator', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
const defaultAdHocFilters: AdHocFilter[] = [
|
|
|
|
{
|
|
|
|
condition: '',
|
|
|
|
key: 'instance',
|
|
|
|
operator: '=~',
|
|
|
|
value: '.*',
|
|
|
|
},
|
|
|
|
];
|
|
|
|
ds = createLokiDatasource(createTemplateSrvMock({ adHocFilters: defaultAdHocFilters }));
|
|
|
|
});
|
|
|
|
it('should not escape special characters in ad hoc filter', () => {
|
|
|
|
assertAdHocFilters('{job="grafana"}', '{job="grafana", instance=~".*"}', ds);
|
|
|
|
});
|
|
|
|
});
|
2021-08-27 10:06:22 -05:00
|
|
|
});
|
|
|
|
|
2021-09-30 08:46:11 -05:00
|
|
|
describe('logs volume data provider', () => {
|
2022-08-26 02:26:48 -05:00
|
|
|
let ds: LokiDatasource;
|
|
|
|
beforeEach(() => {
|
|
|
|
ds = createLokiDatasource(templateSrvStub);
|
|
|
|
});
|
|
|
|
|
2022-02-15 02:05:03 -06:00
|
|
|
it('creates provider for logs query', () => {
|
|
|
|
const options = getQueryOptions<LokiQuery>({
|
2023-01-26 09:06:10 -06:00
|
|
|
targets: [{ expr: '{label=value}', refId: 'A', queryType: LokiQueryType.Range }],
|
2021-11-10 04:20:30 -06:00
|
|
|
});
|
|
|
|
|
2023-01-20 07:20:49 -06:00
|
|
|
expect(ds.getDataProvider(SupplementaryQueryType.LogsVolume, options)).toBeDefined();
|
2022-02-15 02:05:03 -06:00
|
|
|
});
|
2021-11-10 04:20:30 -06:00
|
|
|
|
2022-02-15 02:05:03 -06:00
|
|
|
it('does not create provider for metrics query', () => {
|
|
|
|
const options = getQueryOptions<LokiQuery>({
|
|
|
|
targets: [{ expr: 'rate({label=value}[1m])', refId: 'A' }],
|
2021-09-30 08:46:11 -05:00
|
|
|
});
|
|
|
|
|
2023-01-20 07:20:49 -06:00
|
|
|
expect(ds.getDataProvider(SupplementaryQueryType.LogsVolume, options)).not.toBeDefined();
|
2021-09-30 08:46:11 -05:00
|
|
|
});
|
|
|
|
|
2022-02-15 02:05:03 -06:00
|
|
|
it('creates provider if at least one query is a logs query', () => {
|
|
|
|
const options = getQueryOptions<LokiQuery>({
|
|
|
|
targets: [
|
2023-01-26 09:06:10 -06:00
|
|
|
{ expr: 'rate({label=value}[1m])', queryType: LokiQueryType.Range, refId: 'A' },
|
|
|
|
{ expr: '{label=value}', queryType: LokiQueryType.Range, refId: 'B' },
|
2022-02-15 02:05:03 -06:00
|
|
|
],
|
2021-09-30 08:46:11 -05:00
|
|
|
});
|
|
|
|
|
2023-01-20 07:20:49 -06:00
|
|
|
expect(ds.getDataProvider(SupplementaryQueryType.LogsVolume, options)).toBeDefined();
|
2021-09-30 08:46:11 -05:00
|
|
|
});
|
2022-06-20 04:31:36 -05:00
|
|
|
|
|
|
|
it('does not create provider if there is only an instant logs query', () => {
|
|
|
|
const options = getQueryOptions<LokiQuery>({
|
|
|
|
targets: [{ expr: '{label=value', refId: 'A', queryType: LokiQueryType.Instant }],
|
|
|
|
});
|
|
|
|
|
2023-01-20 07:20:49 -06:00
|
|
|
expect(ds.getDataProvider(SupplementaryQueryType.LogsVolume, options)).not.toBeDefined();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('logs sample data provider', () => {
|
|
|
|
let ds: LokiDatasource;
|
|
|
|
beforeEach(() => {
|
|
|
|
ds = createLokiDatasource(templateSrvStub);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('creates provider for metrics query', () => {
|
|
|
|
const options = getQueryOptions<LokiQuery>({
|
|
|
|
targets: [{ expr: 'rate({label=value}[5m])', refId: 'A' }],
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(ds.getDataProvider(SupplementaryQueryType.LogsSample, options)).toBeDefined();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not create provider for log query', () => {
|
|
|
|
const options = getQueryOptions<LokiQuery>({
|
|
|
|
targets: [{ expr: '{label=value}', refId: 'A' }],
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(ds.getDataProvider(SupplementaryQueryType.LogsSample, options)).not.toBeDefined();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('creates provider if at least one query is a metric query', () => {
|
|
|
|
const options = getQueryOptions<LokiQuery>({
|
|
|
|
targets: [
|
|
|
|
{ expr: 'rate({label=value}[1m])', refId: 'A' },
|
|
|
|
{ expr: '{label=value}', refId: 'B' },
|
|
|
|
],
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(ds.getDataProvider(SupplementaryQueryType.LogsSample, options)).toBeDefined();
|
2022-06-20 04:31:36 -05:00
|
|
|
});
|
2021-09-30 08:46:11 -05:00
|
|
|
});
|
2021-12-14 07:36:47 -06:00
|
|
|
|
2023-01-26 09:06:10 -06:00
|
|
|
describe('getSupplementaryQuery', () => {
|
|
|
|
let ds: LokiDatasource;
|
|
|
|
beforeEach(() => {
|
|
|
|
ds = createLokiDatasource(templateSrvStub);
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('logs volume', () => {
|
|
|
|
it('returns logs volume query for range log query', () => {
|
|
|
|
expect(
|
2023-04-19 10:04:47 -05:00
|
|
|
ds.getSupplementaryQuery(
|
|
|
|
{ type: SupplementaryQueryType.LogsVolume },
|
|
|
|
{
|
|
|
|
expr: '{label=value}',
|
|
|
|
queryType: LokiQueryType.Range,
|
|
|
|
refId: 'A',
|
|
|
|
}
|
|
|
|
)
|
2023-01-26 09:06:10 -06:00
|
|
|
).toEqual({
|
|
|
|
expr: 'sum by (level) (count_over_time({label=value}[$__interval]))',
|
2023-04-26 06:26:07 -05:00
|
|
|
queryType: LokiQueryType.Range,
|
2023-01-26 09:06:10 -06:00
|
|
|
refId: 'log-volume-A',
|
2023-01-27 09:41:40 -06:00
|
|
|
supportingQueryType: SupportingQueryType.LogsVolume,
|
2023-01-26 09:06:10 -06:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not return logs volume query for instant log query', () => {
|
|
|
|
expect(
|
2023-04-19 10:04:47 -05:00
|
|
|
ds.getSupplementaryQuery(
|
|
|
|
{ type: SupplementaryQueryType.LogsVolume },
|
|
|
|
{
|
|
|
|
expr: '{label=value}',
|
|
|
|
queryType: LokiQueryType.Instant,
|
|
|
|
refId: 'A',
|
|
|
|
}
|
|
|
|
)
|
2023-01-26 09:06:10 -06:00
|
|
|
).toEqual(undefined);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not return logs volume query for metric query', () => {
|
|
|
|
expect(
|
2023-04-19 10:04:47 -05:00
|
|
|
ds.getSupplementaryQuery(
|
|
|
|
{ type: SupplementaryQueryType.LogsVolume },
|
|
|
|
{
|
|
|
|
expr: 'rate({label=value}[5m]',
|
|
|
|
queryType: LokiQueryType.Range,
|
|
|
|
refId: 'A',
|
|
|
|
}
|
|
|
|
)
|
2023-01-26 09:06:10 -06:00
|
|
|
).toEqual(undefined);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('logs sample', () => {
|
|
|
|
it('returns logs sample query for range metric query', () => {
|
|
|
|
expect(
|
2023-04-19 10:04:47 -05:00
|
|
|
ds.getSupplementaryQuery(
|
|
|
|
{ type: SupplementaryQueryType.LogsSample },
|
|
|
|
{
|
|
|
|
expr: 'rate({label=value}[5m]',
|
|
|
|
queryType: LokiQueryType.Range,
|
|
|
|
refId: 'A',
|
|
|
|
}
|
|
|
|
)
|
2023-01-26 09:06:10 -06:00
|
|
|
).toEqual({
|
|
|
|
expr: '{label=value}',
|
|
|
|
queryType: 'range',
|
|
|
|
refId: 'log-sample-A',
|
2023-04-19 10:04:47 -05:00
|
|
|
maxLines: 20,
|
2023-01-26 09:06:10 -06:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns logs sample query for instant metric query', () => {
|
|
|
|
expect(
|
2023-04-19 10:04:47 -05:00
|
|
|
ds.getSupplementaryQuery(
|
|
|
|
{ type: SupplementaryQueryType.LogsSample },
|
|
|
|
{
|
|
|
|
expr: 'rate({label=value}[5m]',
|
|
|
|
queryType: LokiQueryType.Instant,
|
|
|
|
refId: 'A',
|
|
|
|
}
|
|
|
|
)
|
2023-01-26 09:06:10 -06:00
|
|
|
).toEqual({
|
|
|
|
expr: '{label=value}',
|
2023-04-26 06:26:07 -05:00
|
|
|
queryType: LokiQueryType.Range,
|
2023-01-26 09:06:10 -06:00
|
|
|
refId: 'log-sample-A',
|
2023-04-19 10:04:47 -05:00
|
|
|
maxLines: 20,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('correctly overrides maxLines if limit is set', () => {
|
|
|
|
expect(
|
|
|
|
ds.getSupplementaryQuery(
|
|
|
|
{ type: SupplementaryQueryType.LogsSample, limit: 5 },
|
|
|
|
{
|
|
|
|
expr: 'rate({label=value}[5m]',
|
|
|
|
queryType: LokiQueryType.Instant,
|
|
|
|
refId: 'A',
|
|
|
|
}
|
|
|
|
)
|
|
|
|
).toEqual({
|
|
|
|
expr: '{label=value}',
|
2023-04-26 06:26:07 -05:00
|
|
|
queryType: LokiQueryType.Range,
|
2023-04-19 10:04:47 -05:00
|
|
|
refId: 'log-sample-A',
|
|
|
|
maxLines: 5,
|
2023-01-26 09:06:10 -06:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not return logs sample query for log query query', () => {
|
|
|
|
expect(
|
2023-04-19 10:04:47 -05:00
|
|
|
ds.getSupplementaryQuery(
|
|
|
|
{ type: SupplementaryQueryType.LogsSample },
|
|
|
|
{
|
|
|
|
expr: '{label=value}',
|
|
|
|
queryType: LokiQueryType.Range,
|
|
|
|
refId: 'A',
|
|
|
|
}
|
|
|
|
)
|
2023-01-26 09:06:10 -06:00
|
|
|
).toEqual(undefined);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2021-12-14 07:36:47 -06:00
|
|
|
describe('importing queries', () => {
|
2022-08-26 02:26:48 -05:00
|
|
|
let ds: LokiDatasource;
|
|
|
|
beforeEach(() => {
|
|
|
|
ds = createLokiDatasource(templateSrvStub);
|
|
|
|
});
|
|
|
|
|
2021-12-14 07:36:47 -06:00
|
|
|
it('keeps all labels when no labels are loaded', async () => {
|
2022-11-14 18:35:50 -06:00
|
|
|
ds.getResource = () => Promise.resolve({ data: [] } as any);
|
2021-12-14 07:36:47 -06:00
|
|
|
const queries = await ds.importFromAbstractQueries([
|
|
|
|
{
|
|
|
|
refId: 'A',
|
|
|
|
labelMatchers: [
|
|
|
|
{ name: 'foo', operator: AbstractLabelOperator.Equal, value: 'bar' },
|
|
|
|
{ name: 'foo2', operator: AbstractLabelOperator.Equal, value: 'bar2' },
|
|
|
|
],
|
|
|
|
},
|
|
|
|
]);
|
|
|
|
expect(queries[0].expr).toBe('{foo="bar", foo2="bar2"}');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('filters out non existing labels', async () => {
|
2022-11-14 18:35:50 -06:00
|
|
|
ds.getResource = () => Promise.resolve({ data: ['foo'] } as any);
|
2021-12-14 07:36:47 -06:00
|
|
|
const queries = await ds.importFromAbstractQueries([
|
|
|
|
{
|
|
|
|
refId: 'A',
|
|
|
|
labelMatchers: [
|
|
|
|
{ name: 'foo', operator: AbstractLabelOperator.Equal, value: 'bar' },
|
|
|
|
{ name: 'foo2', operator: AbstractLabelOperator.Equal, value: 'bar2' },
|
|
|
|
],
|
|
|
|
},
|
|
|
|
]);
|
|
|
|
expect(queries[0].expr).toBe('{foo="bar"}');
|
|
|
|
});
|
|
|
|
});
|
2022-09-14 07:25:25 -05:00
|
|
|
|
|
|
|
describe('getDataSamples', () => {
|
2023-03-22 11:56:40 -05:00
|
|
|
let ds: LokiDatasource;
|
|
|
|
beforeEach(() => {
|
|
|
|
ds = createLokiDatasource(templateSrvStub);
|
|
|
|
});
|
|
|
|
it('ignores invalid queries', () => {
|
|
|
|
const spy = jest.spyOn(ds, 'query');
|
|
|
|
ds.getDataSamples({ expr: 'not a query', refId: 'A' });
|
|
|
|
expect(spy).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
it('ignores metric queries', () => {
|
|
|
|
const spy = jest.spyOn(ds, 'query');
|
|
|
|
ds.getDataSamples({ expr: 'count_over_time({a="b"}[1m])', refId: 'A' });
|
|
|
|
expect(spy).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
it('uses the current interval in the request', () => {
|
|
|
|
const spy = jest.spyOn(ds, 'query').mockImplementation(() => of({} as DataQueryResponse));
|
|
|
|
ds.getDataSamples({ expr: '{job="bar"}', refId: 'A' });
|
|
|
|
expect(spy).toHaveBeenCalledWith(
|
|
|
|
expect.objectContaining({
|
|
|
|
range: ds.getTimeRange(),
|
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
it('hides the request from the inspector', () => {
|
2022-09-14 07:25:25 -05:00
|
|
|
const spy = jest.spyOn(ds, 'query').mockImplementation(() => of({} as DataQueryResponse));
|
|
|
|
ds.getDataSamples({ expr: '{job="bar"}', refId: 'A' });
|
|
|
|
expect(spy).toHaveBeenCalledWith(
|
|
|
|
expect.objectContaining({
|
|
|
|
hideFromInspector: true,
|
2022-12-07 09:24:22 -06:00
|
|
|
requestId: REF_ID_DATA_SAMPLES,
|
2022-09-14 07:25:25 -05:00
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
2023-02-09 11:27:02 -06:00
|
|
|
|
|
|
|
describe('Query splitting', () => {
|
|
|
|
beforeAll(() => {
|
|
|
|
config.featureToggles.lokiQuerySplitting = true;
|
2023-04-03 07:30:08 -05:00
|
|
|
jest.mocked(runSplitQuery).mockReturnValue(
|
2023-02-09 11:27:02 -06:00
|
|
|
of({
|
|
|
|
data: [],
|
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
afterAll(() => {
|
|
|
|
config.featureToggles.lokiQuerySplitting = false;
|
|
|
|
});
|
2023-02-13 00:59:20 -06:00
|
|
|
it.each([
|
|
|
|
[[{ expr: 'count_over_time({a="b"}[1m])', refId: 'A' }]],
|
|
|
|
[[{ expr: '{a="b"}', refId: 'A' }]],
|
|
|
|
[
|
|
|
|
[
|
|
|
|
{ expr: 'count_over_time({a="b"}[1m])', refId: 'A', hide: true },
|
|
|
|
{ expr: '{a="b"}', refId: 'B' },
|
|
|
|
],
|
|
|
|
],
|
|
|
|
])('supports query splitting when the requirements are met', async (targets: LokiQuery[]) => {
|
2023-02-09 11:27:02 -06:00
|
|
|
const ds = createLokiDatasource(templateSrvStub);
|
|
|
|
const query = getQueryOptions<LokiQuery>({
|
2023-02-13 00:59:20 -06:00
|
|
|
targets,
|
2023-02-09 11:27:02 -06:00
|
|
|
app: CoreApp.Dashboard,
|
|
|
|
});
|
|
|
|
|
|
|
|
await expect(ds.query(query)).toEmitValuesWith(() => {
|
2023-04-03 07:30:08 -05:00
|
|
|
expect(runSplitQuery).toHaveBeenCalled();
|
2023-02-09 11:27:02 -06:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2023-06-05 08:16:12 -05:00
|
|
|
|
|
|
|
describe('getQueryStats', () => {
|
2023-06-29 10:50:17 -05:00
|
|
|
let ds: LokiDatasource;
|
|
|
|
beforeEach(() => {
|
|
|
|
ds = createLokiDatasource(templateSrvStub);
|
|
|
|
ds.statsMetadataRequest = jest.fn().mockResolvedValue({ streams: 1, chunks: 1, bytes: 1, entries: 1 });
|
|
|
|
ds.interpolateString = jest.fn().mockImplementation((value: string) => value.replace('$__interval', '1m'));
|
|
|
|
});
|
|
|
|
|
2023-06-05 08:16:12 -05:00
|
|
|
it('uses statsMetadataRequest', async () => {
|
2023-06-29 10:50:17 -05:00
|
|
|
const result = await ds.getQueryStats('{foo="bar"}');
|
|
|
|
|
|
|
|
expect(ds.statsMetadataRequest).toHaveBeenCalled();
|
|
|
|
expect(result).toEqual({ streams: 1, chunks: 1, bytes: 1, entries: 1 });
|
|
|
|
});
|
|
|
|
|
|
|
|
it('supports queries with template variables', async () => {
|
|
|
|
const result = await ds.getQueryStats('rate({instance="server\\1"}[$__interval])');
|
|
|
|
|
|
|
|
expect(result).toEqual({
|
|
|
|
streams: 1,
|
|
|
|
chunks: 1,
|
|
|
|
bytes: 1,
|
|
|
|
entries: 1,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not call stats if the query is invalid', async () => {
|
|
|
|
const result = await ds.getQueryStats('rate({label="value"}');
|
|
|
|
|
|
|
|
expect(ds.statsMetadataRequest).not.toHaveBeenCalled();
|
|
|
|
expect(result).toBe(undefined);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('combines the stats of each label matcher', async () => {
|
|
|
|
const result = await ds.getQueryStats('count_over_time({foo="bar"}[1m]) + count_over_time({test="test"}[1m])');
|
|
|
|
|
|
|
|
expect(ds.statsMetadataRequest).toHaveBeenCalled();
|
|
|
|
expect(result).toEqual({ streams: 2, chunks: 2, bytes: 2, entries: 2 });
|
2023-06-05 08:16:12 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('statsMetadataRequest', () => {
|
|
|
|
it('throws error if url starts with /', () => {
|
|
|
|
const ds = createLokiDatasource();
|
|
|
|
expect(async () => {
|
|
|
|
await ds.statsMetadataRequest('/index');
|
|
|
|
}).rejects.toThrow('invalid metadata request url: /index');
|
|
|
|
});
|
|
|
|
});
|
2018-12-05 17:19:55 -06:00
|
|
|
});
|
2019-09-05 07:04:01 -05:00
|
|
|
|
2022-06-03 03:53:03 -05:00
|
|
|
describe('applyTemplateVariables', () => {
|
|
|
|
it('should add the adhoc filter to the query', () => {
|
2022-08-26 02:26:48 -05:00
|
|
|
const ds = createLokiDatasource(templateSrvStub);
|
2022-06-03 03:53:03 -05:00
|
|
|
const spy = jest.spyOn(ds, 'addAdHocFilters');
|
|
|
|
ds.applyTemplateVariables({ expr: '{test}', refId: 'A' }, {});
|
|
|
|
expect(spy).toHaveBeenCalledWith('{test}');
|
|
|
|
});
|
2023-06-05 04:28:04 -05:00
|
|
|
|
|
|
|
describe('with template and built-in variables', () => {
|
|
|
|
const scopedVars = {
|
|
|
|
__interval: { text: '1m', value: '1m' },
|
|
|
|
__interval_ms: { text: '1000', value: '1000' },
|
|
|
|
__range: { text: '1m', value: '1m' },
|
|
|
|
__range_ms: { text: '1000', value: '1000' },
|
|
|
|
__range_s: { text: '60', value: '60' },
|
|
|
|
testVariable: { text: 'foo', value: 'foo' },
|
|
|
|
};
|
|
|
|
|
|
|
|
it('should not interpolate __interval variables', () => {
|
|
|
|
const templateSrvMock = {
|
|
|
|
getAdhocFilters: jest.fn().mockImplementation((query: string) => query),
|
|
|
|
replace: jest.fn((a: string, ...rest: unknown[]) => a),
|
|
|
|
} as unknown as TemplateSrv;
|
|
|
|
|
|
|
|
const ds = createLokiDatasource(templateSrvMock);
|
|
|
|
ds.addAdHocFilters = jest.fn().mockImplementation((query: string) => query);
|
|
|
|
ds.applyTemplateVariables(
|
|
|
|
{ expr: 'rate({job="grafana"}[$__interval]) + rate({job="grafana"}[$__interval_ms])', refId: 'A' },
|
|
|
|
scopedVars
|
|
|
|
);
|
|
|
|
expect(templateSrvMock.replace).toHaveBeenCalledTimes(2);
|
|
|
|
// Interpolated legend
|
|
|
|
expect(templateSrvMock.replace).toHaveBeenCalledWith(
|
|
|
|
undefined,
|
|
|
|
expect.not.objectContaining({
|
|
|
|
__interval: { text: '1m', value: '1m' },
|
|
|
|
__interval_ms: { text: '1000', value: '1000' },
|
|
|
|
})
|
|
|
|
);
|
|
|
|
// Interpolated expr
|
|
|
|
expect(templateSrvMock.replace).toHaveBeenCalledWith(
|
|
|
|
'rate({job="grafana"}[$__interval]) + rate({job="grafana"}[$__interval_ms])',
|
|
|
|
expect.not.objectContaining({
|
|
|
|
__interval: { text: '1m', value: '1m' },
|
|
|
|
__interval_ms: { text: '1000', value: '1000' },
|
|
|
|
}),
|
|
|
|
expect.any(Function)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not interpolate __range variables', () => {
|
|
|
|
const templateSrvMock = {
|
|
|
|
getAdhocFilters: jest.fn().mockImplementation((query: string) => query),
|
|
|
|
replace: jest.fn((a: string, ...rest: unknown[]) => a),
|
|
|
|
} as unknown as TemplateSrv;
|
|
|
|
|
|
|
|
const ds = createLokiDatasource(templateSrvMock);
|
|
|
|
ds.addAdHocFilters = jest.fn().mockImplementation((query: string) => query);
|
|
|
|
ds.applyTemplateVariables(
|
|
|
|
{
|
|
|
|
expr: 'rate({job="grafana"}[$__range]) + rate({job="grafana"}[$__range_ms]) + rate({job="grafana"}[$__range_s])',
|
|
|
|
refId: 'A',
|
|
|
|
},
|
|
|
|
scopedVars
|
|
|
|
);
|
|
|
|
expect(templateSrvMock.replace).toHaveBeenCalledTimes(2);
|
|
|
|
// Interpolated legend
|
|
|
|
expect(templateSrvMock.replace).toHaveBeenCalledWith(
|
|
|
|
undefined,
|
|
|
|
expect.not.objectContaining({
|
|
|
|
__range: { text: '1m', value: '1m' },
|
|
|
|
__range_ms: { text: '1000', value: '1000' },
|
|
|
|
__range_s: { text: '60', value: '60' },
|
|
|
|
})
|
|
|
|
);
|
|
|
|
// Interpolated expr
|
|
|
|
expect(templateSrvMock.replace).toHaveBeenCalledWith(
|
|
|
|
'rate({job="grafana"}[$__range]) + rate({job="grafana"}[$__range_ms]) + rate({job="grafana"}[$__range_s])',
|
|
|
|
expect.not.objectContaining({
|
|
|
|
__range: { text: '1m', value: '1m' },
|
|
|
|
__range_ms: { text: '1000', value: '1000' },
|
|
|
|
__range_s: { text: '60', value: '60' },
|
|
|
|
}),
|
|
|
|
expect.any(Function)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
2022-06-03 03:53:03 -05:00
|
|
|
});
|
|
|
|
|
2022-10-06 09:35:30 -05:00
|
|
|
describe('getTimeRange*()', () => {
|
|
|
|
it('exposes the current time range', () => {
|
|
|
|
const ds = createLokiDatasource();
|
|
|
|
const timeRange = ds.getTimeRange();
|
|
|
|
|
|
|
|
expect(timeRange.from).toBeDefined();
|
|
|
|
expect(timeRange.to).toBeDefined();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('exposes time range as params', () => {
|
|
|
|
const ds = createLokiDatasource();
|
|
|
|
const params = ds.getTimeRangeParams();
|
|
|
|
|
|
|
|
// Returns a very big integer, so we stringify it for the assertion
|
|
|
|
expect(JSON.stringify(params)).toEqual('{"start":1524650400000000000,"end":1524654000000000000}');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-08-30 11:18:51 -05:00
|
|
|
describe('Variable support', () => {
|
|
|
|
it('has Loki variable support', () => {
|
|
|
|
const ds = createLokiDatasource(templateSrvStub);
|
|
|
|
|
|
|
|
expect(ds.variables).toBeInstanceOf(LokiVariableSupport);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-04-19 04:03:45 -05:00
|
|
|
describe('showContextToggle()', () => {
|
|
|
|
it('always displays logs context', () => {
|
|
|
|
const ds = createLokiDatasource(templateSrvStub);
|
|
|
|
|
|
|
|
expect(ds.showContextToggle()).toBe(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2021-08-27 10:06:22 -05:00
|
|
|
function assertAdHocFilters(query: string, expectedResults: string, ds: LokiDatasource) {
|
|
|
|
const lokiQuery: LokiQuery = { refId: 'A', expr: query };
|
|
|
|
const result = ds.addAdHocFilters(lokiQuery.expr);
|
|
|
|
|
|
|
|
expect(result).toEqual(expectedResults);
|
|
|
|
}
|
|
|
|
|
2022-08-26 02:26:48 -05:00
|
|
|
function makeAnnotationQueryRequest(options = {}): AnnotationQueryRequest<LokiQuery> {
|
2019-09-10 04:04:44 -05:00
|
|
|
const timeRange = {
|
|
|
|
from: dateTime(),
|
|
|
|
to: dateTime(),
|
|
|
|
};
|
|
|
|
return {
|
|
|
|
annotation: {
|
|
|
|
expr: '{test=test}',
|
|
|
|
refId: '',
|
2022-08-26 02:26:48 -05:00
|
|
|
datasource: {
|
|
|
|
type: 'loki',
|
|
|
|
},
|
2019-09-10 04:04:44 -05:00
|
|
|
enable: true,
|
|
|
|
name: 'test-annotation',
|
2021-04-12 02:41:07 -05:00
|
|
|
iconColor: 'red',
|
2021-05-28 03:12:03 -05:00
|
|
|
...options,
|
2019-09-10 04:04:44 -05:00
|
|
|
},
|
|
|
|
dashboard: {
|
|
|
|
id: 1,
|
2022-08-26 02:26:48 -05:00
|
|
|
},
|
2019-09-10 04:04:44 -05:00
|
|
|
range: {
|
|
|
|
...timeRange,
|
|
|
|
raw: timeRange,
|
|
|
|
},
|
|
|
|
rangeRaw: timeRange,
|
|
|
|
};
|
|
|
|
}
|