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/features/explore/TraceView/createSpanLink.test.ts

924 lines
30 KiB

import { DataSourceInstanceSettings, LinkModel, MutableDataFrame } from '@grafana/data';
import { DataSourceSrv, setDataSourceSrv, setTemplateSrv } from '@grafana/runtime';
import { Trace, TraceSpan } from '@jaegertracing/jaeger-ui-components';
import { TraceToMetricsOptions } from 'app/core/components/TraceToMetrics/TraceToMetricsSettings';
import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
import { TraceToLogsOptionsV2 } from '../../../core/components/TraceToLogs/TraceToLogsSettings';
import { LinkSrv, setLinkSrv } from '../../panel/panellinks/link_srv';
import { TemplateSrv } from '../../templating/template_srv';
import { createSpanLinkFactory } from './createSpanLink';
const dummyTraceData = { duration: 10, traceID: 'trace1', traceName: 'test trace' } as unknown as Trace;
const dummyDataFrame = new MutableDataFrame({ fields: [{ name: 'traceId', values: ['trace1'] }] });
describe('createSpanLinkFactory', () => {
it('returns no links if there is no data source uid', () => {
const splitOpenFn = jest.fn();
const createLink = createSpanLinkFactory({
splitOpenFn: splitOpenFn,
trace: dummyTraceData,
dataFrame: dummyDataFrame,
});
const links = createLink!(createTraceSpan());
expect(links?.logLinks).toBeUndefined();
expect(links?.metricLinks).toBeUndefined();
expect(links?.traceLinks).toHaveLength(0);
});
describe('should return loki link', () => {
beforeAll(() => {
setDataSourceSrv({
getInstanceSettings() {
return { uid: 'loki1_uid', name: 'loki1', type: 'loki' } as unknown as DataSourceInstanceSettings;
},
} as unknown as DataSourceSrv);
setLinkSrv(new LinkSrv());
setTemplateSrv(new TemplateSrv());
});
it('with default keys when tags not configured', () => {
const createLink = setupSpanLinkFactory();
expect(createLink).toBeDefined();
const links = createLink!(createTraceSpan());
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1_uid","queries":[{"expr":"{cluster=\\"cluster1\\", hostname=\\"hostname1\\"}","refId":""}]}'
)}`
);
});
it('with tags that passed in and without tags that are not in the span', () => {
const createLink = setupSpanLinkFactory({
tags: [{ key: 'ip' }, { key: 'newTag' }],
});
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [
{ key: 'hostname', value: 'hostname1' },
{ key: 'ip', value: '192.168.0.1' },
],
},
})
);
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1_uid","queries":[{"expr":"{ip=\\"192.168.0.1\\"}","refId":""}]}'
)}`
);
});
it('from tags and process tags as well', () => {
const createLink = setupSpanLinkFactory({
tags: [{ key: 'ip' }, { key: 'host' }],
});
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [
{ key: 'hostname', value: 'hostname1' },
{ key: 'ip', value: '192.168.0.1' },
],
},
})
);
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1_uid","queries":[{"expr":"{ip=\\"192.168.0.1\\", host=\\"host\\"}","refId":""}]}'
)}`
);
});
it('with adjusted start and end time', () => {
const createLink = setupSpanLinkFactory({
spanStartTimeShift: '1m',
spanEndTimeShift: '1m',
});
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [
{ key: 'hostname', value: 'hostname1' },
{ key: 'ip', value: '192.168.0.1' },
],
},
})
);
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:01:00.000Z","to":"2020-10-14T01:01:01.000Z"},"datasource":"loki1_uid","queries":[{"expr":"{hostname=\\"hostname1\\"}","refId":""}]}'
)}`
);
});
it('filters by trace and span ID', () => {
const createLink = setupSpanLinkFactory({
filterBySpanID: true,
filterByTraceID: true,
});
expect(createLink).toBeDefined();
const links = createLink!(createTraceSpan());
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(decodeURIComponent(linkDef!.href)).toBe(
'/explore?left=' +
JSON.stringify({
range: { from: '2020-10-14T01:00:00.000Z', to: '2020-10-14T01:00:01.000Z' },
datasource: 'loki1_uid',
queries: [
{
expr: '{cluster="cluster1", hostname="hostname1"} |="7946b05c2e2e4e5a" |="6605c7b08e715d6c"',
refId: '',
},
],
})
);
});
it('creates link from dataFrame', () => {
const splitOpenFn = jest.fn();
const createLink = createSpanLinkFactory({
splitOpenFn,
dataFrame: new MutableDataFrame({
fields: [
{ name: 'traceID', values: ['testTraceId'] },
{
name: 'spanID',
config: { links: [{ title: 'link', url: '${__data.fields.spanID}' }] },
values: ['testSpanId'],
},
],
}),
trace: dummyTraceData,
});
expect(createLink).toBeDefined();
const links = createLink!(createTraceSpan());
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe('testSpanId');
});
it('handles renamed tags', () => {
const createLink = setupSpanLinkFactory({
tags: [
{ key: 'service.name', value: 'service' },
{ key: 'k8s.pod.name', value: 'pod' },
],
});
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [
{ key: 'service.name', value: 'serviceName' },
{ key: 'k8s.pod.name', value: 'podName' },
],
},
})
);
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1_uid","queries":[{"expr":"{service=\\"serviceName\\", pod=\\"podName\\"}","refId":""}]}'
)}`
);
});
it('handles incomplete renamed tags', () => {
const createLink = setupSpanLinkFactory({
tags: [
{ key: 'service.name', value: '' },
{ key: 'k8s.pod.name', value: 'pod' },
],
});
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [
{ key: 'service.name', value: 'serviceName' },
{ key: 'k8s.pod.name', value: 'podName' },
],
},
})
);
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1_uid","queries":[{"expr":"{service.name=\\"serviceName\\", pod=\\"podName\\"}","refId":""}]}'
)}`
);
});
it('handles empty queries', () => {
const createLink = setupSpanLinkFactory({
tags: [],
});
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [
{ key: 'service.name', value: 'serviceName' },
{ key: 'k8s.pod.name', value: 'podName' },
],
},
})
);
expect(links?.logLinks).toBeUndefined();
});
it('interpolates span intrinsics', () => {
const createLink = setupSpanLinkFactory({
tags: [{ key: 'name', value: 'spanName' }],
});
expect(createLink).toBeDefined();
const links = createLink!(createTraceSpan());
expect(links?.logLinks).toBeDefined();
expect(decodeURIComponent(links!.logLinks![0].href)).toContain('spanName=\\"operation\\"');
});
});
describe('should return splunk link', () => {
const splunkUID = 'splunkUID';
beforeAll(() => {
setDataSourceSrv({
getInstanceSettings() {
return {
uid: splunkUID,
name: 'Splunk 8',
type: 'grafana-splunk-datasource',
} as unknown as DataSourceInstanceSettings;
},
} as unknown as DataSourceSrv);
setLinkSrv(new LinkSrv());
setTemplateSrv(new TemplateSrv());
});
it('the `query` keyword is used in the link rather than `expr` that loki uses', () => {
const createLink = setupSpanLinkFactory({
datasourceUid: splunkUID,
});
const links = createLink!(createTraceSpan());
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toContain(`${encodeURIComponent('datasource":"splunkUID","queries":[{"query"')}`);
expect(linkDef!.href).not.toContain(`${encodeURIComponent('datasource":"splunkUID","queries":[{"expr"')}`);
});
it('automatically timeshifts the timerange by one second in a splunk query', () => {
const createLink = setupSpanLinkFactory({
datasourceUid: splunkUID,
});
const links = createLink!(createTraceSpan());
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toContain(
`${encodeURIComponent('{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"}')}`
);
expect(linkDef!.href).not.toContain(
`${encodeURIComponent('{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:00.000Z"}')}`
);
});
it('formats query correctly if filterByTraceID and or filterBySpanID is true', () => {
const createLink = setupSpanLinkFactory({
datasourceUid: splunkUID,
filterByTraceID: true,
filterBySpanID: true,
});
expect(createLink).toBeDefined();
const links = createLink!(createTraceSpan());
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"splunkUID","queries":[{"query":"cluster=\\"cluster1\\" hostname=\\"hostname1\\" \\"7946b05c2e2e4e5a\\" \\"6605c7b08e715d6c\\"","refId":""}]}'
)}`
);
});
it('should format one tag correctly', () => {
const createLink = setupSpanLinkFactory({
tags: [{ key: 'ip' }],
});
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [{ key: 'ip', value: '192.168.0.1' }],
},
})
);
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"splunkUID","queries":[{"query":"ip=\\"192.168.0.1\\"","refId":""}]}'
)}`
);
});
it('should format multiple tags correctly', () => {
const createLink = setupSpanLinkFactory({
tags: [{ key: 'ip' }, { key: 'hostname' }],
});
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [
{ key: 'hostname', value: 'hostname1' },
{ key: 'ip', value: '192.168.0.1' },
],
},
})
);
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"splunkUID","queries":[{"query":"hostname=\\"hostname1\\" ip=\\"192.168.0.1\\"","refId":""}]}'
)}`
);
});
it('handles renamed tags', () => {
const createLink = setupSpanLinkFactory({
tags: [
{ key: 'service.name', value: 'service' },
{ key: 'k8s.pod.name', value: 'pod' },
],
});
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [
{ key: 'service.name', value: 'serviceName' },
{ key: 'k8s.pod.name', value: 'podName' },
],
},
})
);
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"splunkUID","queries":[{"query":"service=\\"serviceName\\" pod=\\"podName\\"","refId":""}]}'
)}`
);
});
});
describe('should return metric link', () => {
beforeAll(() => {
setDataSourceSrv({
getInstanceSettings() {
return { uid: 'prom1Uid', name: 'prom1', type: 'prometheus' } as unknown as DataSourceInstanceSettings;
},
} as unknown as DatasourceSrv);
setLinkSrv(new LinkSrv());
setTemplateSrv(new TemplateSrv());
});
it('returns single query with span', () => {
const splitOpenFn = jest.fn();
const createLink = createSpanLinkFactory({
splitOpenFn,
traceToMetricsOptions: {
datasourceUid: 'prom1Uid',
queries: [{ query: 'customQuery' }],
},
trace: dummyTraceData,
dataFrame: dummyDataFrame,
});
expect(createLink).toBeDefined();
const links = createLink!(createTraceSpan());
const linkDef = links?.metricLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"prom1Uid","queries":[{"expr":"customQuery","refId":"A"}]}'
)}`
);
});
it('returns nothing if no queries specified', () => {
const splitOpenFn = jest.fn();
const createLink = createSpanLinkFactory({
splitOpenFn,
traceToMetricsOptions: {
datasourceUid: 'prom1',
} as TraceToMetricsOptions,
trace: dummyTraceData,
dataFrame: dummyDataFrame,
});
expect(createLink).toBeDefined();
const links = createLink!(createTraceSpan());
expect(links?.metricLinks).toBeUndefined();
});
it('returns multiple queries including default', () => {
const splitOpenFn = jest.fn();
const createLink = createSpanLinkFactory({
splitOpenFn,
traceToMetricsOptions: {
datasourceUid: 'prom1Uid',
queries: [
{ name: 'Named Query', query: 'customQuery' },
{ name: 'defaultQuery', query: '' },
{ query: 'no_name_here' },
],
},
trace: dummyTraceData,
dataFrame: dummyDataFrame,
});
expect(createLink).toBeDefined();
const links = createLink!(createTraceSpan());
expect(links?.metricLinks).toBeDefined();
expect(links?.metricLinks).toHaveLength(3);
const namedLink = links?.metricLinks?.[0];
expect(namedLink).toBeDefined();
expect(namedLink!.title).toBe('Named Query');
expect(namedLink!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"prom1Uid","queries":[{"expr":"customQuery","refId":"A"}]}'
)}`
);
const defaultLink = links?.metricLinks?.[1];
expect(defaultLink).toBeDefined();
expect(defaultLink!.title).toBe('defaultQuery');
expect(defaultLink!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"prom1Uid","queries":[{"expr":"histogram_quantile(0.5, sum(rate(traces_spanmetrics_latency_bucket{service=\\"test service\\"}[5m])) by (le))","refId":"A"}]}'
)}`
);
const unnamedQuery = links?.metricLinks?.[2];
expect(unnamedQuery).toBeDefined();
expect(unnamedQuery!.title).toBeUndefined();
expect(unnamedQuery!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"prom1Uid","queries":[{"expr":"no_name_here","refId":"A"}]}'
)}`
);
});
it('with adjusted start and end time', () => {
const splitOpenFn = jest.fn();
const createLink = createSpanLinkFactory({
splitOpenFn,
traceToMetricsOptions: {
datasourceUid: 'prom1Uid',
queries: [{ query: 'customQuery' }],
spanStartTimeShift: '-1h',
spanEndTimeShift: '1h',
},
trace: dummyTraceData,
dataFrame: dummyDataFrame,
});
expect(createLink).toBeDefined();
const links = createLink!(createTraceSpan());
const linkDef = links?.metricLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T00:00:00.000Z","to":"2020-10-14T02:00:01.000Z"},"datasource":"prom1Uid","queries":[{"expr":"customQuery","refId":"A"}]}'
)}`
);
});
});
it('correctly interpolates span attributes', () => {
const splitOpenFn = jest.fn();
const createLink = createSpanLinkFactory({
splitOpenFn,
traceToMetricsOptions: {
datasourceUid: 'prom1Uid',
queries: [{ name: 'Named Query', query: 'metric{$__tags, $__tags}[5m]' }],
tags: [
{ key: 'job', value: '' },
{ key: 'k8s.pod', value: 'pod' },
],
},
trace: dummyTraceData,
dataFrame: dummyDataFrame,
});
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [
{ key: 'job', value: 'tns/app' },
{ key: 'k8s.pod', value: 'sample-pod' },
],
},
})
);
expect(links).toBeDefined();
expect(links!.metricLinks![0]!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"prom1Uid","queries":[{"expr":"metric{job=\\"tns/app\\", pod=\\"sample-pod\\", job=\\"tns/app\\", pod=\\"sample-pod\\"}[5m]","refId":"A"}]}'
)}`
);
});
describe('should return span links', () => {
beforeAll(() => {
setDataSourceSrv(new DatasourceSrv());
setLinkSrv(new LinkSrv());
setTemplateSrv(new TemplateSrv());
});
it('ignores parent span link', () => {
const createLink = setupSpanLinkFactory();
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({ references: [{ refType: 'CHILD_OF', spanID: 'parent', traceID: 'traceID' }] })
);
const traceLinks = links?.traceLinks;
expect(traceLinks).toBeDefined();
expect(traceLinks).toHaveLength(0);
});
it('returns links for references and subsidiarilyReferencedBy references', () => {
const createLink = setupSpanLinkFactory();
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
references: [
{
refType: 'FOLLOWS_FROM',
spanID: 'span1',
traceID: 'traceID',
span: { operationName: 'SpanName' } as TraceSpan,
},
],
subsidiarilyReferencedBy: [{ refType: 'FOLLOWS_FROM', spanID: 'span3', traceID: 'traceID2' }],
})
);
const traceLinks = links?.traceLinks;
expect(traceLinks).toBeDefined();
expect(traceLinks).toHaveLength(2);
expect(traceLinks![0]).toEqual(
expect.objectContaining({
href: 'traceID-span1',
title: 'SpanName',
})
);
expect(traceLinks![1]).toEqual(
expect.objectContaining({
href: 'traceID2-span3',
title: 'View linked span',
})
);
});
});
describe('elasticsearch/opensearch link', () => {
const searchUID = 'searchUID';
beforeAll(() => {
setDataSourceSrv({
getInstanceSettings() {
return {
uid: searchUID,
name: 'Elasticsearch',
type: 'elasticsearch',
} as unknown as DataSourceInstanceSettings;
},
} as unknown as DataSourceSrv);
setLinkSrv(new LinkSrv());
setTemplateSrv(new TemplateSrv());
});
it('creates link with correct simple query', () => {
const createLink = setupSpanLinkFactory({
datasourceUid: searchUID,
});
const links = createLink!(createTraceSpan());
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(decodeURIComponent(linkDef!.href)).toContain(
`datasource":"${searchUID}","queries":[{"query":"cluster:\\"cluster1\\" AND hostname:\\"hostname1\\"","refId":"","metrics":[{"id":"1","type":"logs"}]}]`
);
});
it('automatically timeshifts the time range by one second in a query', () => {
const createLink = setupSpanLinkFactory({
datasourceUid: searchUID,
});
const links = createLink!(createTraceSpan());
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toContain(
`${encodeURIComponent('{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"}')}`
);
expect(linkDef!.href).not.toContain(
`${encodeURIComponent('{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:00.000Z"}')}`
);
});
it('formats query correctly if filterByTraceID and or filterBySpanID is true', () => {
const createLink = setupSpanLinkFactory(
{
datasourceUid: searchUID,
filterByTraceID: true,
filterBySpanID: true,
},
searchUID
);
expect(createLink).toBeDefined();
const links = createLink!(createTraceSpan());
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
`{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"${searchUID}","queries":[{"query":"\\"6605c7b08e715d6c\\" AND \\"7946b05c2e2e4e5a\\" AND cluster:\\"cluster1\\" AND hostname:\\"hostname1\\"","refId":"","metrics":[{"id":"1","type":"logs"}]}]}`
)}`
);
});
it('formats query correctly if only filterByTraceID is true', () => {
const createLink = setupSpanLinkFactory(
{
datasourceUid: searchUID,
filterByTraceID: true,
},
searchUID
);
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [],
},
})
);
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(decodeURIComponent(linkDef!.href)).toBe(
`/explore?left={"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"searchUID","queries":[{"query":"\\"7946b05c2e2e4e5a\\"","refId":"","metrics":[{"id":"1","type":"logs"}]}]}`
);
});
it('should format one tag correctly', () => {
const createLink = setupSpanLinkFactory(
{
tags: [{ key: 'ip' }],
},
searchUID
);
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [{ key: 'ip', value: '192.168.0.1' }],
},
})
);
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
`{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"${searchUID}","queries":[{"query":"ip:\\"192.168.0.1\\"","refId":"","metrics":[{"id":"1","type":"logs"}]}]}`
)}`
);
});
it('should format multiple tags correctly', () => {
const createLink = setupSpanLinkFactory(
{
tags: [{ key: 'ip' }, { key: 'hostname' }],
},
searchUID
);
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [
{ key: 'hostname', value: 'hostname1' },
{ key: 'ip', value: '192.168.0.1' },
],
},
})
);
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
`{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"${searchUID}","queries":[{"query":"hostname:\\"hostname1\\" AND ip:\\"192.168.0.1\\"","refId":"","metrics":[{"id":"1","type":"logs"}]}]}`
)}`
);
});
it('handles renamed tags', () => {
const createLink = setupSpanLinkFactory(
{
tags: [
{ key: 'service.name', value: 'service' },
{ key: 'k8s.pod.name', value: 'pod' },
],
},
searchUID
);
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [
{ key: 'service.name', value: 'serviceName' },
{ key: 'k8s.pod.name', value: 'podName' },
],
},
})
);
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
`{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"${searchUID}","queries":[{"query":"service:\\"serviceName\\" AND pod:\\"podName\\"","refId":"","metrics":[{"id":"1","type":"logs"}]}]}`
)}`
);
});
});
describe('custom query', () => {
beforeAll(() => {
setDataSourceSrv({
getInstanceSettings() {
return { uid: 'loki1_uid', name: 'loki1', type: 'loki' } as unknown as DataSourceInstanceSettings;
},
} as unknown as DataSourceSrv);
setLinkSrv(new LinkSrv());
setTemplateSrv(new TemplateSrv());
});
it('interpolates custom query correctly', () => {
const createLink = setupSpanLinkFactory({
tags: [
{ key: 'service.name', value: 'service' },
{ key: 'k8s.pod.name', value: 'pod' },
],
customQuery: true,
query: '{${__tags}} |="${__span.tags["service.name"]}" |="${__trace.traceId}"',
});
expect(createLink).toBeDefined();
const links = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [
{ key: 'service.name', value: 'serviceName' },
{ key: 'k8s.pod.name', value: 'podName' },
],
},
})
);
const linkDef = links?.logLinks?.[0];
expect(linkDef).toBeDefined();
expect(decodeURIComponent(linkDef!.href)).toContain(
'"queries":' +
JSON.stringify([{ expr: '{service="serviceName", pod="podName"} |="serviceName" |="trace1"', refId: '' }])
);
});
it('does not return a link if variables are not matched', () => {
const createLink = setupSpanLinkFactory({
tags: [{ key: 'service.name', value: 'service' }],
customQuery: true,
query: '{${__tags}} |="${__span.tags["service.name"]}" |="${__trace.id}"',
});
expect(createLink).toBeDefined();
const links = createLink!(createTraceSpan());
expect(links?.logLinks).toBeUndefined();
});
});
});
function setupSpanLinkFactory(options: Partial<TraceToLogsOptionsV2> = {}, datasourceUid = 'lokiUid') {
const splitOpenFn = jest.fn();
return createSpanLinkFactory({
splitOpenFn,
traceToLogsOptions: {
customQuery: false,
datasourceUid,
...options,
},
createFocusSpanLink: (traceId, spanId) => {
return {
href: `${traceId}-${spanId}`,
} as unknown as LinkModel;
},
trace: dummyTraceData,
dataFrame: dummyDataFrame,
});
}
function createTraceSpan(overrides: Partial<TraceSpan> = {}) {
return {
spanID: '6605c7b08e715d6c',
traceID: '7946b05c2e2e4e5a',
processID: 'processId',
operationName: 'operation',
logs: [],
startTime: new Date('2020-10-14T01:00:00Z').valueOf() * 1000,
duration: 1000 * 1000,
flags: 0,
hasChildren: false,
dataFrameRowIndex: 0,
tags: [
{
key: 'host',
value: 'host',
},
],
process: {
serviceName: 'test service',
tags: [
{
key: 'cluster',
value: 'cluster1',
},
{
key: 'hostname',
value: 'hostname1',
},
{
key: 'label2',
value: 'val2',
},
],
},
...overrides,
} as TraceSpan;
}