The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/plugins/datasource/jaeger/datasource.test.ts

360 lines
11 KiB

import { lastValueFrom, of, throwError } from 'rxjs';
import { createFetchResponse } from 'test/helpers/createFetchResponse';
import {
DataQueryRequest,
DataSourceInstanceSettings,
dateTime,
FieldType,
PluginType,
ScopedVars,
} from '@grafana/data';
import { backendSrv } from 'app/core/services/backend_srv';
import { ALL_OPERATIONS_KEY } from './components/SearchForm';
import { JaegerDatasource, JaegerJsonData } from './datasource';
import mockJson from './mockJsonResponse.json';
import {
testResponse,
testResponseDataFrameFields,
testResponseEdgesFields,
testResponseNodesFields,
} from './testResponse';
import { JaegerQuery } from './types';
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getBackendSrv: () => backendSrv,
getTemplateSrv: () => ({
replace: (val: string, subs: ScopedVars): string => {
return subs[val]?.value ?? val;
},
containsTemplate: (val: string): boolean => {
return val.includes('$');
},
}),
}));
const timeSrvStub: any = {
timeRange(): any {
return {
from: dateTime(1531468681),
to: dateTime(1531489712),
};
},
};
describe('JaegerDatasource', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('returns trace and graph when queried', async () => {
setupFetchMock({ data: [testResponse] });
const ds = new JaegerDatasource(defaultSettings);
const response = await lastValueFrom(ds.query(defaultQuery));
expect(response.data.length).toBe(3);
expect(response.data[0].fields).toMatchObject(testResponseDataFrameFields);
expect(response.data[1].fields).toMatchObject(testResponseNodesFields);
expect(response.data[2].fields).toMatchObject(testResponseEdgesFields);
});
it('returns trace when traceId with special characters is queried', async () => {
const mock = setupFetchMock({ data: [testResponse] });
const ds = new JaegerDatasource(defaultSettings);
const query = {
...defaultQuery,
targets: [
{
query: 'a/b',
refId: '1',
},
],
};
await lastValueFrom(ds.query(query));
expect(mock).toBeCalledWith({ url: `${defaultSettings.url}/api/traces/a%2Fb` });
});
it('returns empty response if trace id is not specified', async () => {
const ds = new JaegerDatasource(defaultSettings);
const response = await lastValueFrom(
ds.query({
...defaultQuery,
targets: [],
})
);
const field = response.data[0].fields[0];
expect(field.name).toBe('trace');
expect(field.type).toBe(FieldType.trace);
expect(field.values.length).toBe(0);
});
it('should handle json file upload', async () => {
const ds = new JaegerDatasource(defaultSettings);
ds.uploadedJson = JSON.stringify(mockJson);
const response = await lastValueFrom(
ds.query({
...defaultQuery,
targets: [{ queryType: 'upload', refId: 'A' }],
})
);
const field = response.data[0].fields[0];
expect(field.name).toBe('traceID');
expect(field.type).toBe(FieldType.string);
expect(field.values.length).toBe(2);
});
it('should fail on invalid json file upload', async () => {
const ds = new JaegerDatasource(defaultSettings);
ds.uploadedJson = JSON.stringify({ key: 'value', arr: [] });
const response = await lastValueFrom(
ds.query({
targets: [{ queryType: 'upload', refId: 'A' }],
} as any)
);
expect(response.error?.message).toBeDefined();
expect(response.data.length).toBe(0);
});
it('should return search results when the query type is search', async () => {
const mock = setupFetchMock({ data: [testResponse] });
const ds = new JaegerDatasource(defaultSettings, timeSrvStub);
const response = await lastValueFrom(
ds.query({
...defaultQuery,
targets: [{ queryType: 'search', refId: 'a', service: 'jaeger-query', operation: '/api/services' }],
})
);
expect(mock).toBeCalledWith({
url: `${defaultSettings.url}/api/traces?service=jaeger-query&operation=%2Fapi%2Fservices&start=1531468681000&end=1531489712000&lookback=custom`,
});
expect(response.data[0].meta.preferredVisualisationType).toBe('table');
// Make sure that traceID field has data link configured
expect(response.data[0].fields[0].config.links).toHaveLength(1);
expect(response.data[0].fields[0].name).toBe('traceID');
});
it('should remove operation from the query when all is selected', async () => {
const mock = setupFetchMock({ data: [testResponse] });
const ds = new JaegerDatasource(defaultSettings, timeSrvStub);
await lastValueFrom(
ds.query({
...defaultQuery,
targets: [{ queryType: 'search', refId: 'a', service: 'jaeger-query', operation: ALL_OPERATIONS_KEY }],
})
);
expect(mock).toBeCalledWith({
url: `${defaultSettings.url}/api/traces?service=jaeger-query&start=1531468681000&end=1531489712000&lookback=custom`,
});
});
it('should convert tags from logfmt format to an object', async () => {
const mock = setupFetchMock({ data: [testResponse] });
const ds = new JaegerDatasource(defaultSettings, timeSrvStub);
await lastValueFrom(
ds.query({
...defaultQuery,
targets: [{ queryType: 'search', refId: 'a', service: 'jaeger-query', tags: 'error=true' }],
})
);
expect(mock).toBeCalledWith({
url: `${defaultSettings.url}/api/traces?service=jaeger-query&tags=%7B%22error%22%3A%22true%22%7D&start=1531468681000&end=1531489712000&lookback=custom`,
});
});
it('should resolve templates in traceID', async () => {
const mock = setupFetchMock({ data: [testResponse] });
const ds = new JaegerDatasource(defaultSettings, timeSrvStub);
await lastValueFrom(
ds.query({
...defaultQuery,
scopedVars: {
$traceid: {
text: 'traceid',
value: '5311b0dd0ca8df3463df93c99cb805a6',
},
},
targets: [
{
query: '$traceid',
refId: '1',
},
],
})
);
expect(mock).toBeCalledWith({
url: `${defaultSettings.url}/api/traces/5311b0dd0ca8df3463df93c99cb805a6`,
});
});
it('should resolve templates in tags', async () => {
const mock = setupFetchMock({ data: [testResponse] });
const ds = new JaegerDatasource(defaultSettings, timeSrvStub);
await lastValueFrom(
ds.query({
...defaultQuery,
scopedVars: {
'error=$error': {
text: 'error',
value: 'error=true',
},
},
targets: [{ queryType: 'search', refId: 'a', service: 'jaeger-query', tags: 'error=$error' }],
})
);
expect(mock).toBeCalledWith({
url: `${defaultSettings.url}/api/traces?service=jaeger-query&tags=%7B%22error%22%3A%22true%22%7D&start=1531468681000&end=1531489712000&lookback=custom`,
});
});
it('should interpolate variables correctly', async () => {
const mock = setupFetchMock({ data: [testResponse] });
const ds = new JaegerDatasource(defaultSettings, timeSrvStub);
const text = 'interpolationText';
await lastValueFrom(
ds.query({
...defaultQuery,
scopedVars: {
$interpolationVar: {
text: text,
value: text,
},
},
targets: [
{
queryType: 'search',
refId: 'a',
service: '$interpolationVar',
operation: '$interpolationVar',
minDuration: '$interpolationVar',
maxDuration: '$interpolationVar',
},
],
})
);
expect(mock).toBeCalledWith({
url: `${defaultSettings.url}/api/traces?service=interpolationText&operation=interpolationText&minDuration=interpolationText&maxDuration=interpolationText&start=1531468681000&end=1531489712000&lookback=custom`,
});
});
});
describe('when performing testDataSource', () => {
describe('and call succeeds', () => {
it('should return successfully', async () => {
setupFetchMock({ data: ['service1'] });
const ds = new JaegerDatasource(defaultSettings);
const response = await ds.testDatasource();
expect(response.status).toEqual('success');
expect(response.message).toBe('Data source connected and services found.');
});
});
describe('and call succeeds, but returns no services', () => {
it('should display an error', async () => {
setupFetchMock(undefined);
const ds = new JaegerDatasource(defaultSettings);
const response = await ds.testDatasource();
expect(response.status).toEqual('error');
expect(response.message).toBe(
'Data source connected, but no services received. Verify that Jaeger is configured properly.'
);
});
});
describe('and call returns error with message', () => {
it('should return the formatted error', async () => {
setupFetchMock(
undefined,
throwError({
statusText: 'Not found',
status: 404,
data: {
message: '404 page not found',
},
})
);
const ds = new JaegerDatasource(defaultSettings);
const response = await ds.testDatasource();
expect(response.status).toEqual('error');
expect(response.message).toBe('Jaeger: Not found. 404. 404 page not found');
});
});
describe('and call returns error without message', () => {
it('should return JSON error', async () => {
setupFetchMock(
undefined,
throwError({
statusText: 'Bad gateway',
status: 502,
data: {
errors: ['Could not connect to Jaeger backend'],
},
})
);
const ds = new JaegerDatasource(defaultSettings);
const response = await ds.testDatasource();
expect(response.status).toEqual('error');
expect(response.message).toBe('Jaeger: Bad gateway. 502. {"errors":["Could not connect to Jaeger backend"]}');
});
});
});
function setupFetchMock(response: any, mock?: any) {
const defaultMock = () => mock ?? of(createFetchResponse(response));
const fetchMock = jest.spyOn(backendSrv, 'fetch');
fetchMock.mockImplementation(defaultMock);
return fetchMock;
}
const defaultSettings: DataSourceInstanceSettings<JaegerJsonData> = {
id: 0,
uid: '0',
type: 'tracing',
name: 'jaeger',
url: 'http://grafana.com',
access: 'proxy',
meta: {
id: 'jaeger',
name: 'jaeger',
type: PluginType.datasource,
info: {} as any,
module: '',
baseUrl: '',
},
jsonData: {
nodeGraph: {
enabled: true,
},
},
};
const defaultQuery: DataQueryRequest<JaegerQuery> = {
requestId: '1',
dashboardId: 0,
interval: '0',
intervalMs: 10,
panelId: 0,
scopedVars: {},
range: {
from: dateTime().subtract(1, 'h'),
to: dateTime(),
raw: { from: '1h', to: 'now' },
},
timezone: 'browser',
app: 'explore',
startTime: 0,
targets: [
{
query: '12345',
refId: '1',
},
],
};