123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- 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') as any),
- getBackendSrv: () => backendSrv,
- getTemplateSrv: () => ({
- replace: (val: string, subs: ScopedVars): string => {
- return subs[val]?.value ?? val;
- },
- }),
- }));
- 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?operation=%2Fapi%2Fservices&service=jaeger-query&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`,
- });
- });
- });
- 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',
- },
- ],
- };
|