mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Correlations: Enable traceView formatted links (#67160)
* Enable correlations links for traceView formatted fields * Add other links to the span UI * Show both “legacy” and dataframe links * bandaid test * Remove special path logic, stringify all non strings before applying regex * Fix test * Do not create new instance of factory for every call, change header to correlations * Get links from more than one field * Remove categories * Fix google cloud link tests * Add test for multiple internal links * Remove changes to datasources provisioning * Added sorting, changed log link title
This commit is contained in:
parent
f478504bc9
commit
0db397b8be
@ -12,7 +12,9 @@ export const getTransformationVars = (
|
||||
let transformVal: { [key: string]: string | boolean | null | undefined } = {};
|
||||
if (transformation.type === SupportedTransformationType.Regex && transformation.expression) {
|
||||
const regexp = new RegExp(transformation.expression, 'gi');
|
||||
const matches = fieldValue.matchAll(regexp);
|
||||
const stringFieldVal = typeof fieldValue === 'string' ? fieldValue : safeStringifyValue(fieldValue);
|
||||
|
||||
const matches = stringFieldVal.matchAll(regexp);
|
||||
for (const match of matches) {
|
||||
if (match.groups) {
|
||||
transformVal = match.groups;
|
||||
|
@ -17,8 +17,7 @@ import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { NONE, DURATION, TAG } from '../settings/SpanBarSettings';
|
||||
import { TraceSpan } from '../types';
|
||||
import { SpanLinks } from '../types/links';
|
||||
import { SpanLinkDef, TraceSpan } from '../types';
|
||||
|
||||
import SpanBarRow, { SpanBarRowProps } from './SpanBarRow';
|
||||
|
||||
@ -111,11 +110,7 @@ describe('<SpanBarRow>', () => {
|
||||
<SpanBarRow
|
||||
{...(props as unknown as SpanBarRowProps)}
|
||||
span={span}
|
||||
createSpanLink={() =>
|
||||
({
|
||||
traceLinks: [{ href: 'href' }, { href: 'href' }],
|
||||
} as SpanLinks)
|
||||
}
|
||||
createSpanLink={() => [{ href: 'href' }, { href: 'href' }] as SpanLinkDef[]}
|
||||
/>
|
||||
);
|
||||
expect(screen.getAllByTestId('SpanLinksMenu')).toHaveLength(1);
|
||||
@ -142,11 +137,7 @@ describe('<SpanBarRow>', () => {
|
||||
<SpanBarRow
|
||||
{...(props as unknown as SpanBarRowProps)}
|
||||
span={span}
|
||||
createSpanLink={() =>
|
||||
({
|
||||
traceLinks: [{ content: 'This span is referenced by another span', href: 'href' }],
|
||||
} as SpanLinks)
|
||||
}
|
||||
createSpanLink={() => [{ content: 'This span is referenced by another span', href: 'href' }] as SpanLinkDef[]}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByRole('link', { name: 'This span is referenced by another span' })).toBeInTheDocument();
|
||||
@ -181,11 +172,7 @@ describe('<SpanBarRow>', () => {
|
||||
<SpanBarRow
|
||||
{...(props as unknown as SpanBarRowProps)}
|
||||
span={span}
|
||||
createSpanLink={() =>
|
||||
({
|
||||
traceLinks: [{ href: 'href' }, { href: 'href' }],
|
||||
} as SpanLinks)
|
||||
}
|
||||
createSpanLink={() => [{ href: 'href' }, { href: 'href' }] as SpanLinkDef[]}
|
||||
/>
|
||||
);
|
||||
expect(screen.getAllByTestId('SpanLinksMenu')).toHaveLength(1);
|
||||
|
@ -22,7 +22,6 @@ import { Icon, stylesFactory, withTheme2 } from '@grafana/ui';
|
||||
import { autoColor } from '../Theme';
|
||||
import { DURATION, NONE, TAG } from '../settings/SpanBarSettings';
|
||||
import { SpanBarOptions, SpanLinkFunc, TraceSpan, TNil } from '../types';
|
||||
import { SpanLinks } from '../types/links';
|
||||
|
||||
import SpanBar from './SpanBar';
|
||||
import { SpanLinksMenu } from './SpanLinks';
|
||||
@ -404,14 +403,6 @@ export class UnthemedSpanBarRow extends React.PureComponent<SpanBarRowProps> {
|
||||
hintClassName = styles.labelRight;
|
||||
}
|
||||
|
||||
const countLinks = (links?: SpanLinks): number => {
|
||||
if (!links) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Object.values(links).reduce((count, arr) => count + arr.length, 0);
|
||||
};
|
||||
|
||||
return (
|
||||
<TimelineRow
|
||||
className={cx(
|
||||
@ -490,32 +481,31 @@ export class UnthemedSpanBarRow extends React.PureComponent<SpanBarRowProps> {
|
||||
{createSpanLink &&
|
||||
(() => {
|
||||
const links = createSpanLink(span);
|
||||
const count = countLinks(links);
|
||||
const count = links?.length || 0;
|
||||
if (links && count === 1) {
|
||||
const link = links.logLinks?.[0] ?? links.metricLinks?.[0] ?? links.traceLinks?.[0] ?? undefined;
|
||||
if (!link) {
|
||||
if (!links[0]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
href={link.href}
|
||||
href={links[0].href}
|
||||
// Needs to have target otherwise preventDefault would not work due to angularRouter.
|
||||
target={'_blank'}
|
||||
style={{ marginRight: '5px' }}
|
||||
rel="noopener noreferrer"
|
||||
onClick={
|
||||
link.onClick
|
||||
links[0].onClick
|
||||
? (event) => {
|
||||
if (!(event.ctrlKey || event.metaKey || event.shiftKey) && link.onClick) {
|
||||
if (!(event.ctrlKey || event.metaKey || event.shiftKey) && links[0].onClick) {
|
||||
event.preventDefault();
|
||||
link.onClick(event);
|
||||
links[0].onClick(event);
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{link.content}
|
||||
{links[0].content}
|
||||
</a>
|
||||
);
|
||||
} else if (links && count > 1) {
|
||||
|
@ -24,6 +24,7 @@ import { autoColor } from '../../Theme';
|
||||
import { Divider } from '../../common/Divider';
|
||||
import LabeledList from '../../common/LabeledList';
|
||||
import { SpanLinkFunc, TNil } from '../../types';
|
||||
import { SpanLinkType } from '../../types/links';
|
||||
import { TraceKeyValuePair, TraceLink, TraceLog, TraceSpan, TraceSpanReference } from '../../types/trace';
|
||||
import { uAlignIcon, ubM0, ubMb1, ubMy1, ubTxRightAlign } from '../../uberUtilityStyles';
|
||||
import { TopOfViewRefType } from '../VirtualizedTraceView';
|
||||
@ -197,14 +198,15 @@ export default function SpanDetail(props: SpanDetailProps) {
|
||||
let logLinkButton: JSX.Element | undefined = undefined;
|
||||
if (createSpanLink) {
|
||||
const links = createSpanLink(span);
|
||||
if (links?.logLinks) {
|
||||
const logLinks = links?.filter((link) => link.type === SpanLinkType.Logs);
|
||||
if (links && logLinks && logLinks.length > 0) {
|
||||
logLinkButton = (
|
||||
<DataLinkButton
|
||||
link={{
|
||||
...links.logLinks[0],
|
||||
...logLinks[0],
|
||||
title: 'Logs for this span',
|
||||
target: '_blank',
|
||||
origin: links.logLinks[0].field,
|
||||
origin: logLinks[0].field,
|
||||
onClick: (event: React.MouseEvent) => {
|
||||
reportInteraction('grafana_traces_trace_view_span_link_clicked', {
|
||||
datasourceType: datasourceType,
|
||||
@ -212,7 +214,7 @@ export default function SpanDetail(props: SpanDetailProps) {
|
||||
type: 'log',
|
||||
location: 'spanDetails',
|
||||
});
|
||||
links?.logLinks?.[0].onClick?.(event);
|
||||
logLinks?.[0].onClick?.(event);
|
||||
},
|
||||
}}
|
||||
buttonProps={{ icon: 'gf-logs' }}
|
||||
|
@ -2,36 +2,36 @@ import { css } from '@emotion/css';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
import { useStyles2, MenuGroup, MenuItem, Icon, ContextMenu } from '@grafana/ui';
|
||||
import { useStyles2, MenuItem, Icon, ContextMenu } from '@grafana/ui';
|
||||
|
||||
import { SpanLinks } from '../types/links';
|
||||
import { SpanLinkDef } from '../types/links';
|
||||
|
||||
interface SpanLinksProps {
|
||||
links: SpanLinks;
|
||||
links: SpanLinkDef[];
|
||||
datasourceType: string;
|
||||
}
|
||||
|
||||
const renderMenuItems = (
|
||||
links: SpanLinks,
|
||||
links: SpanLinkDef[],
|
||||
styles: ReturnType<typeof getStyles>,
|
||||
closeMenu: () => void,
|
||||
datasourceType: string
|
||||
) => {
|
||||
return (
|
||||
<>
|
||||
{!!links.logLinks?.length ? (
|
||||
<MenuGroup label="Logs">
|
||||
{links.logLinks.map((link, i) => (
|
||||
links.sort(function (linkA, linkB) {
|
||||
return (linkA.title || 'link').toLowerCase().localeCompare((linkB.title || 'link').toLowerCase());
|
||||
});
|
||||
|
||||
return links.map((link, i) => (
|
||||
<MenuItem
|
||||
key={i}
|
||||
label="Logs for this span"
|
||||
label={link.title || 'Link'}
|
||||
onClick={
|
||||
link.onClick
|
||||
? (event) => {
|
||||
reportInteraction('grafana_traces_trace_view_span_link_clicked', {
|
||||
reportInteraction(`grafana_traces_trace_view_span_link_clicked`, {
|
||||
datasourceType: datasourceType,
|
||||
grafana_version: config.buildInfo.version,
|
||||
type: 'log',
|
||||
type: link.type,
|
||||
location: 'menu',
|
||||
});
|
||||
event?.preventDefault();
|
||||
@ -43,65 +43,7 @@ const renderMenuItems = (
|
||||
url={link.href}
|
||||
className={styles.menuItem}
|
||||
/>
|
||||
))}
|
||||
</MenuGroup>
|
||||
) : null}
|
||||
{!!links.metricLinks?.length ? (
|
||||
<MenuGroup label="Metrics">
|
||||
{links.metricLinks.map((link, i) => (
|
||||
<MenuItem
|
||||
key={i}
|
||||
label={link.title ?? 'Metrics for this span'}
|
||||
onClick={
|
||||
link.onClick
|
||||
? (event) => {
|
||||
reportInteraction('grafana_traces_trace_view_span_link_clicked', {
|
||||
datasourceType: datasourceType,
|
||||
grafana_version: config.buildInfo.version,
|
||||
type: 'metric',
|
||||
location: 'menu',
|
||||
});
|
||||
event?.preventDefault();
|
||||
link.onClick!(event);
|
||||
closeMenu();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
url={link.href}
|
||||
className={styles.menuItem}
|
||||
/>
|
||||
))}
|
||||
</MenuGroup>
|
||||
) : null}
|
||||
{!!links.traceLinks?.length ? (
|
||||
<MenuGroup label="Traces">
|
||||
{links.traceLinks.map((link, i) => (
|
||||
<MenuItem
|
||||
key={i}
|
||||
label={link.title ?? 'View linked span'}
|
||||
onClick={
|
||||
link.onClick
|
||||
? (event) => {
|
||||
reportInteraction('grafana_traces_trace_view_span_link_clicked', {
|
||||
datasourceType: datasourceType,
|
||||
grafana_version: config.buildInfo.version,
|
||||
type: 'trace',
|
||||
location: 'menu',
|
||||
});
|
||||
event?.preventDefault();
|
||||
link.onClick!(event);
|
||||
closeMenu();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
url={link.href}
|
||||
className={styles.menuItem}
|
||||
/>
|
||||
))}
|
||||
</MenuGroup>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
));
|
||||
};
|
||||
|
||||
export const SpanLinksMenu = ({ links, datasourceType }: SpanLinksProps) => {
|
||||
@ -130,7 +72,7 @@ export const SpanLinksMenu = ({ links, datasourceType }: SpanLinksProps) => {
|
||||
<ContextMenu
|
||||
onClose={() => setIsMenuOpen(false)}
|
||||
renderMenuItems={() => renderMenuItems(links, styles, closeMenu, datasourceType)}
|
||||
focusOnOpen={true}
|
||||
focusOnOpen={false}
|
||||
x={menuPosition.x}
|
||||
y={menuPosition.y}
|
||||
/>
|
||||
|
@ -4,18 +4,20 @@ import { Field } from '@grafana/data';
|
||||
|
||||
import { TraceSpan } from './trace';
|
||||
|
||||
export enum SpanLinkType {
|
||||
Logs = 'log',
|
||||
Traces = 'trace',
|
||||
Metrics = 'metric',
|
||||
Unknown = 'unknown',
|
||||
}
|
||||
|
||||
export type SpanLinkDef = {
|
||||
href: string;
|
||||
onClick?: (event: unknown) => void;
|
||||
content: React.ReactNode;
|
||||
title?: string;
|
||||
field: Field;
|
||||
type: SpanLinkType;
|
||||
};
|
||||
|
||||
export type SpanLinks = {
|
||||
logLinks?: SpanLinkDef[];
|
||||
traceLinks?: SpanLinkDef[];
|
||||
metricLinks?: SpanLinkDef[];
|
||||
};
|
||||
|
||||
export type SpanLinkFunc = (span: TraceSpan) => SpanLinks | undefined;
|
||||
export type SpanLinkFunc = (span: TraceSpan) => SpanLinkDef[] | undefined;
|
||||
|
@ -1,4 +1,11 @@
|
||||
import { DataSourceInstanceSettings, LinkModel, MutableDataFrame } from '@grafana/data';
|
||||
import {
|
||||
DataSourceInstanceSettings,
|
||||
LinkModel,
|
||||
createDataFrame,
|
||||
SupportedTransformationType,
|
||||
DataLinkConfigOrigin,
|
||||
FieldType,
|
||||
} from '@grafana/data';
|
||||
import { DataSourceSrv, setDataSourceSrv, setTemplateSrv } from '@grafana/runtime';
|
||||
import { TraceToMetricsOptions } from 'app/core/components/TraceToMetrics/TraceToMetricsSettings';
|
||||
import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
@ -8,10 +15,17 @@ import { LinkSrv, setLinkSrv } from '../../panel/panellinks/link_srv';
|
||||
import { TemplateSrv } from '../../templating/template_srv';
|
||||
|
||||
import { Trace, TraceSpan } from './components';
|
||||
import { SpanLinkType } from './components/types/links';
|
||||
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'] }] });
|
||||
const dummyDataFrame = createDataFrame({ fields: [{ name: 'traceId', values: ['trace1'] }] });
|
||||
|
||||
jest.mock('app/core/services/context_srv', () => ({
|
||||
contextSrv: {
|
||||
hasAccessToExplore: () => true,
|
||||
},
|
||||
}));
|
||||
|
||||
describe('createSpanLinkFactory', () => {
|
||||
it('returns no links if there is no data source uid', () => {
|
||||
@ -22,9 +36,8 @@ describe('createSpanLinkFactory', () => {
|
||||
dataFrame: dummyDataFrame,
|
||||
});
|
||||
const links = createLink!(createTraceSpan());
|
||||
expect(links?.logLinks).toBeUndefined();
|
||||
expect(links?.metricLinks).toBeUndefined();
|
||||
expect(links?.traceLinks).toHaveLength(0);
|
||||
expect(links).toBeDefined();
|
||||
expect(links).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe('should return loki link', () => {
|
||||
@ -43,8 +56,9 @@ describe('createSpanLinkFactory', () => {
|
||||
const createLink = setupSpanLinkFactory();
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(createTraceSpan());
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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":""}]}'
|
||||
@ -68,8 +82,9 @@ describe('createSpanLinkFactory', () => {
|
||||
},
|
||||
})
|
||||
);
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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":""}]}'
|
||||
@ -93,8 +108,9 @@ describe('createSpanLinkFactory', () => {
|
||||
},
|
||||
})
|
||||
);
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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":""}]}'
|
||||
@ -119,8 +135,9 @@ describe('createSpanLinkFactory', () => {
|
||||
},
|
||||
})
|
||||
);
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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":""}]}'
|
||||
@ -136,8 +153,9 @@ describe('createSpanLinkFactory', () => {
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(decodeURIComponent(linkDef!.href)).toBe(
|
||||
'/explore?left=' +
|
||||
JSON.stringify({
|
||||
@ -157,7 +175,7 @@ describe('createSpanLinkFactory', () => {
|
||||
const splitOpenFn = jest.fn();
|
||||
const createLink = createSpanLinkFactory({
|
||||
splitOpenFn,
|
||||
dataFrame: new MutableDataFrame({
|
||||
dataFrame: createDataFrame({
|
||||
fields: [
|
||||
{ name: 'traceID', values: ['testTraceId'] },
|
||||
{
|
||||
@ -172,8 +190,9 @@ describe('createSpanLinkFactory', () => {
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Unknown);
|
||||
expect(linkDef!.href).toBe('testSpanId');
|
||||
});
|
||||
|
||||
@ -197,8 +216,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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":""}]}'
|
||||
@ -226,8 +246,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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":""}]}'
|
||||
@ -251,7 +272,8 @@ describe('createSpanLinkFactory', () => {
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(links?.logLinks).toBeUndefined();
|
||||
expect(links).toBeDefined();
|
||||
expect(links?.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('interpolates span intrinsics', () => {
|
||||
@ -260,8 +282,9 @@ describe('createSpanLinkFactory', () => {
|
||||
});
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(createTraceSpan());
|
||||
expect(links?.logLinks).toBeDefined();
|
||||
expect(decodeURIComponent(links!.logLinks![0].href)).toContain('spanName=\\"operation\\"');
|
||||
expect(links).toBeDefined();
|
||||
expect(links![0].type).toBe(SpanLinkType.Logs);
|
||||
expect(decodeURIComponent(links![0].href)).toContain('spanName=\\"operation\\"');
|
||||
});
|
||||
});
|
||||
|
||||
@ -289,8 +312,9 @@ describe('createSpanLinkFactory', () => {
|
||||
});
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(linkDef!.href).toContain(`${encodeURIComponent('datasource":"splunkUID","queries":[{"query"')}`);
|
||||
expect(linkDef!.href).not.toContain(`${encodeURIComponent('datasource":"splunkUID","queries":[{"expr"')}`);
|
||||
});
|
||||
@ -301,8 +325,9 @@ describe('createSpanLinkFactory', () => {
|
||||
});
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(linkDef!.href).toContain(
|
||||
`${encodeURIComponent('{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"}')}`
|
||||
);
|
||||
@ -321,8 +346,9 @@ describe('createSpanLinkFactory', () => {
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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":""}]}'
|
||||
@ -344,8 +370,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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":""}]}'
|
||||
@ -370,8 +397,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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":""}]}'
|
||||
@ -399,8 +427,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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":""}]}'
|
||||
@ -435,9 +464,9 @@ describe('createSpanLinkFactory', () => {
|
||||
expect(createLink).toBeDefined();
|
||||
|
||||
const links = createLink!(createTraceSpan());
|
||||
const linkDef = links?.metricLinks?.[0];
|
||||
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Metrics);
|
||||
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"}]}'
|
||||
@ -458,7 +487,8 @@ describe('createSpanLinkFactory', () => {
|
||||
expect(createLink).toBeDefined();
|
||||
|
||||
const links = createLink!(createTraceSpan());
|
||||
expect(links?.metricLinks).toBeUndefined();
|
||||
expect(links).toBeDefined();
|
||||
expect(links?.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('returns multiple queries including default', () => {
|
||||
@ -479,11 +509,12 @@ describe('createSpanLinkFactory', () => {
|
||||
expect(createLink).toBeDefined();
|
||||
|
||||
const links = createLink!(createTraceSpan());
|
||||
expect(links?.metricLinks).toBeDefined();
|
||||
expect(links?.metricLinks).toHaveLength(3);
|
||||
expect(links).toBeDefined();
|
||||
expect(links).toHaveLength(3);
|
||||
|
||||
const namedLink = links?.metricLinks?.[0];
|
||||
const namedLink = links?.[0];
|
||||
expect(namedLink).toBeDefined();
|
||||
expect(namedLink?.type).toBe(SpanLinkType.Metrics);
|
||||
expect(namedLink!.title).toBe('Named Query');
|
||||
expect(namedLink!.href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
@ -491,8 +522,9 @@ describe('createSpanLinkFactory', () => {
|
||||
)}`
|
||||
);
|
||||
|
||||
const defaultLink = links?.metricLinks?.[1];
|
||||
const defaultLink = links?.[1];
|
||||
expect(defaultLink).toBeDefined();
|
||||
expect(defaultLink?.type).toBe(SpanLinkType.Metrics);
|
||||
expect(defaultLink!.title).toBe('defaultQuery');
|
||||
expect(defaultLink!.href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
@ -500,8 +532,9 @@ describe('createSpanLinkFactory', () => {
|
||||
)}`
|
||||
);
|
||||
|
||||
const unnamedQuery = links?.metricLinks?.[2];
|
||||
const unnamedQuery = links?.[2];
|
||||
expect(unnamedQuery).toBeDefined();
|
||||
expect(unnamedQuery?.type).toBe(SpanLinkType.Metrics);
|
||||
expect(unnamedQuery!.title).toBeUndefined();
|
||||
expect(unnamedQuery!.href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
@ -526,9 +559,9 @@ describe('createSpanLinkFactory', () => {
|
||||
expect(createLink).toBeDefined();
|
||||
|
||||
const links = createLink!(createTraceSpan());
|
||||
const linkDef = links?.metricLinks?.[0];
|
||||
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Metrics);
|
||||
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"}]}'
|
||||
@ -566,7 +599,8 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
expect(links).toBeDefined();
|
||||
expect(links!.metricLinks![0]!.href).toBe(
|
||||
expect(links![0].type).toBe(SpanLinkType.Metrics);
|
||||
expect(links![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"}]}'
|
||||
)}`
|
||||
@ -587,7 +621,7 @@ describe('createSpanLinkFactory', () => {
|
||||
createTraceSpan({ references: [{ refType: 'CHILD_OF', spanID: 'parent', traceID: 'traceID' }] })
|
||||
);
|
||||
|
||||
const traceLinks = links?.traceLinks;
|
||||
const traceLinks = links;
|
||||
expect(traceLinks).toBeDefined();
|
||||
expect(traceLinks).toHaveLength(0);
|
||||
});
|
||||
@ -609,9 +643,12 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const traceLinks = links?.traceLinks;
|
||||
const traceLinks = links;
|
||||
expect(traceLinks).toBeDefined();
|
||||
expect(traceLinks).toHaveLength(2);
|
||||
expect(traceLinks![0].type).toBe(SpanLinkType.Traces);
|
||||
expect(traceLinks![1].type).toBe(SpanLinkType.Traces);
|
||||
|
||||
expect(traceLinks![0]).toEqual(
|
||||
expect.objectContaining({
|
||||
href: 'traceID-span1',
|
||||
@ -651,8 +688,9 @@ describe('createSpanLinkFactory', () => {
|
||||
});
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(decodeURIComponent(linkDef!.href)).toContain(
|
||||
`datasource":"${searchUID}","queries":[{"query":"cluster:\\"cluster1\\" AND hostname:\\"hostname1\\"","refId":"","metrics":[{"id":"1","type":"logs"}]}]`
|
||||
);
|
||||
@ -664,8 +702,9 @@ describe('createSpanLinkFactory', () => {
|
||||
});
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(linkDef!.href).toContain(
|
||||
`${encodeURIComponent('{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"}')}`
|
||||
);
|
||||
@ -687,8 +726,9 @@ describe('createSpanLinkFactory', () => {
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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"}]}]}`
|
||||
@ -715,8 +755,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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"}]}]}`
|
||||
);
|
||||
@ -739,8 +780,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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"}]}]}`
|
||||
@ -768,8 +810,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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"}]}]}`
|
||||
@ -800,8 +843,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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"}]}]}`
|
||||
@ -834,8 +878,9 @@ describe('createSpanLinkFactory', () => {
|
||||
});
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(decodeURIComponent(linkDef!.href)).toContain(
|
||||
`datasource":"${searchUID}","queries":[{"query":"cluster=\\"cluster1\\" AND hostname=\\"hostname1\\"","refId":""}]`
|
||||
);
|
||||
@ -847,8 +892,9 @@ describe('createSpanLinkFactory', () => {
|
||||
});
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(linkDef!.href).toContain(
|
||||
`${encodeURIComponent('{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"}')}`
|
||||
);
|
||||
@ -870,8 +916,9 @@ describe('createSpanLinkFactory', () => {
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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":""}]}`
|
||||
@ -898,8 +945,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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":""}]}`
|
||||
);
|
||||
@ -922,8 +970,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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":""}]}`
|
||||
@ -951,8 +1000,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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":""}]}`
|
||||
@ -983,8 +1033,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
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":""}]}`
|
||||
@ -1027,8 +1078,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(decodeURIComponent(linkDef!.href)).toContain(
|
||||
'"queries":' +
|
||||
JSON.stringify([{ expr: '{service="serviceName", pod="podName"} |="serviceName" |="trace1"', refId: '' }])
|
||||
@ -1043,7 +1095,8 @@ describe('createSpanLinkFactory', () => {
|
||||
});
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(createTraceSpan());
|
||||
expect(links?.logLinks).toBeUndefined();
|
||||
expect(links).toBeDefined();
|
||||
expect(links?.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1071,8 +1124,9 @@ describe('createSpanLinkFactory', () => {
|
||||
});
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(linkDef!.href).toContain(`${encodeURIComponent('datasource":"falconLogScaleUID","queries":[{"lsql"')}`);
|
||||
});
|
||||
|
||||
@ -1086,8 +1140,9 @@ describe('createSpanLinkFactory', () => {
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(linkDef!.href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"falconLogScaleUID","queries":[{"lsql":"cluster=\\"cluster1\\" OR hostname=\\"hostname1\\" or \\"7946b05c2e2e4e5a\\" or \\"6605c7b08e715d6c\\"","refId":""}]}'
|
||||
@ -1109,8 +1164,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(linkDef!.href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"falconLogScaleUID","queries":[{"lsql":"ip=\\"192.168.0.1\\"","refId":""}]}'
|
||||
@ -1135,8 +1191,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(linkDef!.href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"falconLogScaleUID","queries":[{"lsql":"hostname=\\"hostname1\\" OR ip=\\"192.168.0.1\\"","refId":""}]}'
|
||||
@ -1164,8 +1221,9 @@ describe('createSpanLinkFactory', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(linkDef!.href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"falconLogScaleUID","queries":[{"lsql":"service=\\"serviceName\\" OR pod=\\"podName\\"","refId":""}]}'
|
||||
@ -1175,6 +1233,47 @@ describe('createSpanLinkFactory', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('dataFrame links', () => {
|
||||
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('creates multiple span links for the dataframe links', () => {
|
||||
const multiLinkDataFrame = createMultiLinkDataFrame();
|
||||
const splitOpenFn = jest.fn();
|
||||
const createLink = createSpanLinkFactory({
|
||||
splitOpenFn,
|
||||
dataFrame: multiLinkDataFrame,
|
||||
trace: dummyTraceData,
|
||||
});
|
||||
|
||||
const links = createLink!(createTraceSpan());
|
||||
expect(links).toBeDefined();
|
||||
expect(links?.length).toEqual(3);
|
||||
expect(links![0].href).toBe('testSpanId');
|
||||
expect(links![0].type).toBe(SpanLinkType.Unknown);
|
||||
expect(links![1].href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1_uid","queries":[{"message":"SELECT * FROM superhero WHERE name=host"}]}'
|
||||
)}`
|
||||
);
|
||||
expect(links![1].type).toBe(SpanLinkType.Unknown);
|
||||
expect(links![2].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":"go_memstats_heap_inuse_bytes{job=\'host\'}"}]}'
|
||||
)}`
|
||||
);
|
||||
expect(links![2].type).toBe(SpanLinkType.Unknown);
|
||||
});
|
||||
});
|
||||
|
||||
function setupSpanLinkFactory(options: Partial<TraceToLogsOptionsV2> = {}, datasourceUid = 'lokiUid') {
|
||||
const splitOpenFn = jest.fn();
|
||||
return createSpanLinkFactory({
|
||||
@ -1232,3 +1331,68 @@ function createTraceSpan(overrides: Partial<TraceSpan> = {}) {
|
||||
...overrides,
|
||||
} as TraceSpan;
|
||||
}
|
||||
|
||||
function createMultiLinkDataFrame() {
|
||||
return createDataFrame({
|
||||
fields: [
|
||||
{ name: 'traceID', values: ['testTraceId'] },
|
||||
{
|
||||
name: 'spanID',
|
||||
config: { links: [{ title: 'link', url: '${__data.fields.spanID}' }] },
|
||||
values: ['testSpanId'],
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
type: FieldType.other,
|
||||
config: {
|
||||
links: [
|
||||
{
|
||||
internal: {
|
||||
query: {
|
||||
message: 'SELECT * FROM superhero WHERE name=${job}',
|
||||
},
|
||||
datasourceUid: 'loki1_uid',
|
||||
datasourceName: 'loki1',
|
||||
transformations: [
|
||||
{
|
||||
type: SupportedTransformationType.Regex,
|
||||
expression: '{(?=[^\\}]*\\bkey":"host")[^\\}]*\\bvalue":"(.*?)".*}',
|
||||
mapValue: 'job',
|
||||
},
|
||||
],
|
||||
},
|
||||
url: '',
|
||||
title: 'Test',
|
||||
origin: DataLinkConfigOrigin.Correlations,
|
||||
},
|
||||
{
|
||||
internal: {
|
||||
query: {
|
||||
expr: "go_memstats_heap_inuse_bytes{job='${job}'}",
|
||||
},
|
||||
datasourceUid: 'loki1_uid',
|
||||
datasourceName: 'loki1',
|
||||
transformations: [
|
||||
{
|
||||
type: SupportedTransformationType.Regex,
|
||||
expression: '{(?=[^\\}]*\\bkey":"host")[^\\}]*\\bvalue":"(.*?)".*}',
|
||||
mapValue: 'job',
|
||||
},
|
||||
],
|
||||
},
|
||||
url: '',
|
||||
title: 'Test2',
|
||||
origin: DataLinkConfigOrigin.Correlations,
|
||||
},
|
||||
],
|
||||
},
|
||||
values: [
|
||||
{
|
||||
key: 'host',
|
||||
value: 'host',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
@ -23,10 +23,10 @@ import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { PromQuery } from 'app/plugins/datasource/prometheus/types';
|
||||
|
||||
import { LokiQuery } from '../../../plugins/datasource/loki/types';
|
||||
import { getFieldLinksForExplore, getVariableUsageInfo } from '../utils/links';
|
||||
import { ExploreFieldLinkModel, getFieldLinksForExplore, getVariableUsageInfo } from '../utils/links';
|
||||
|
||||
import { SpanLinkFunc, Trace, TraceSpan } from './components';
|
||||
import { SpanLinks } from './components/types/links';
|
||||
import { SpanLinkDef, SpanLinkFunc, Trace, TraceSpan } from './components';
|
||||
import { SpanLinkType } from './components/types/links';
|
||||
|
||||
/**
|
||||
* This is a factory for the link creator. It returns the function mainly so it can return undefined in which case
|
||||
@ -54,13 +54,8 @@ export function createSpanLinkFactory({
|
||||
|
||||
let scopedVars = scopedVarsFromTrace(trace);
|
||||
const hasLinks = dataFrame.fields.some((f) => Boolean(f.config.links?.length));
|
||||
const legacyFormat = dataFrame.fields.length === 1;
|
||||
|
||||
if (legacyFormat || !hasLinks) {
|
||||
// if the dataframe contains just a single blob of data (legacy format) or does not have any links configured,
|
||||
// let's try to use the old legacy path.
|
||||
// TODO: This was mainly a backward compatibility thing but at this point can probably be removed.
|
||||
return legacyCreateSpanLinkFactory(
|
||||
const createSpanLinks = legacyCreateSpanLinkFactory(
|
||||
splitOpenFn,
|
||||
// We need this to make the types happy but for this branch of code it does not matter which field we supply.
|
||||
dataFrame.fields[0],
|
||||
@ -69,18 +64,21 @@ export function createSpanLinkFactory({
|
||||
createFocusSpanLink,
|
||||
scopedVars
|
||||
);
|
||||
}
|
||||
|
||||
return function SpanLink(span: TraceSpan): SpanLinkDef[] | undefined {
|
||||
let spanLinks = createSpanLinks(span);
|
||||
|
||||
if (hasLinks) {
|
||||
return function SpanLink(span: TraceSpan): SpanLinks | undefined {
|
||||
scopedVars = {
|
||||
...scopedVars,
|
||||
...scopedVarsFromSpan(span),
|
||||
};
|
||||
// We should be here only if there are some links in the dataframe
|
||||
const field = dataFrame.fields.find((f) => Boolean(f.config.links?.length))!;
|
||||
const fields = dataFrame.fields.filter((f) => Boolean(f.config.links?.length))!;
|
||||
try {
|
||||
const links = getFieldLinksForExplore({
|
||||
let links: ExploreFieldLinkModel[] = [];
|
||||
fields.forEach((field) => {
|
||||
const fieldLinksForExplore = getFieldLinksForExplore({
|
||||
field,
|
||||
rowIndex: span.dataFrameRowIndex!,
|
||||
splitOpenFn,
|
||||
@ -88,26 +86,30 @@ export function createSpanLinkFactory({
|
||||
dataFrame,
|
||||
vars: scopedVars,
|
||||
});
|
||||
links = links.concat(fieldLinksForExplore);
|
||||
});
|
||||
|
||||
const newSpanLinks: SpanLinkDef[] = links.map((link) => {
|
||||
return {
|
||||
logLinks: [
|
||||
{
|
||||
href: links[0].href,
|
||||
onClick: links[0].onClick,
|
||||
content: <Icon name="gf-logs" title="Explore the logs for this in split view" />,
|
||||
field: links[0].origin,
|
||||
},
|
||||
],
|
||||
title: link.title,
|
||||
href: link.href,
|
||||
onClick: link.onClick,
|
||||
content: <Icon name="link" title={link.title || 'Link'} />,
|
||||
field: link.origin,
|
||||
type: SpanLinkType.Unknown,
|
||||
};
|
||||
});
|
||||
|
||||
spanLinks.push.apply(spanLinks, newSpanLinks);
|
||||
} catch (error) {
|
||||
// It's fairly easy to crash here for example if data source defines wrong interpolation in the data link
|
||||
console.error(error);
|
||||
return undefined;
|
||||
return spanLinks;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return spanLinks;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,12 +136,12 @@ function legacyCreateSpanLinkFactory(
|
||||
metricsDataSourceSettings = getDatasourceSrv().getInstanceSettings(traceToMetricsOptions.datasourceUid);
|
||||
}
|
||||
|
||||
return function SpanLink(span: TraceSpan): SpanLinks {
|
||||
return function SpanLink(span: TraceSpan): SpanLinkDef[] {
|
||||
scopedVars = {
|
||||
...scopedVars,
|
||||
...scopedVarsFromSpan(span),
|
||||
};
|
||||
const links: SpanLinks = { traceLinks: [] };
|
||||
const links: SpanLinkDef[] = [];
|
||||
let query: DataQuery | undefined;
|
||||
let tags = '';
|
||||
|
||||
@ -216,21 +218,20 @@ function legacyCreateSpanLinkFactory(
|
||||
replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()),
|
||||
});
|
||||
|
||||
links.logLinks = [
|
||||
{
|
||||
links.push({
|
||||
href: link.href,
|
||||
title: 'Related logs',
|
||||
onClick: link.onClick,
|
||||
content: <Icon name="gf-logs" title="Explore the logs for this in split view" />,
|
||||
field,
|
||||
},
|
||||
];
|
||||
type: SpanLinkType.Logs,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get metrics links
|
||||
if (metricsDataSourceSettings && traceToMetricsOptions?.queries) {
|
||||
links.metricLinks = [];
|
||||
for (const query of traceToMetricsOptions.queries) {
|
||||
const expr = buildMetricsQuery(query, traceToMetricsOptions?.tags || [], span);
|
||||
const dataLink: DataLink<PromQuery> = {
|
||||
@ -263,12 +264,13 @@ function legacyCreateSpanLinkFactory(
|
||||
replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()),
|
||||
});
|
||||
|
||||
links.metricLinks.push({
|
||||
links.push({
|
||||
title: query?.name,
|
||||
href: link.href,
|
||||
onClick: link.onClick,
|
||||
content: <Icon name="chart-line" title="Explore metrics for this span" />,
|
||||
field,
|
||||
type: SpanLinkType.Metrics,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -283,12 +285,13 @@ function legacyCreateSpanLinkFactory(
|
||||
|
||||
const link = createFocusSpanLink(reference.traceID, reference.spanID);
|
||||
|
||||
links.traceLinks!.push({
|
||||
links!.push({
|
||||
href: link.href,
|
||||
title: reference.span ? reference.span.operationName : 'View linked span',
|
||||
content: <Icon name="link" title="View linked span" />,
|
||||
onClick: link.onClick,
|
||||
field: link.origin,
|
||||
type: SpanLinkType.Traces,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -297,12 +300,13 @@ function legacyCreateSpanLinkFactory(
|
||||
for (const reference of span.subsidiarilyReferencedBy) {
|
||||
const link = createFocusSpanLink(reference.traceID, reference.spanID);
|
||||
|
||||
links.traceLinks!.push({
|
||||
links!.push({
|
||||
href: link.href,
|
||||
title: reference.span ? reference.span.operationName : 'View linked span',
|
||||
content: <Icon name="link" title="View linked span" />,
|
||||
onClick: link.onClick,
|
||||
field: link.origin,
|
||||
type: SpanLinkType.Traces,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -358,6 +358,61 @@ describe('explore links utils', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('returns internal links within a result consistent with trace data', () => {
|
||||
const transformationLink: DataLink = {
|
||||
title: '',
|
||||
url: '',
|
||||
internal: {
|
||||
query: { query: 'http_requests{env=${msg}}' },
|
||||
datasourceUid: 'uid_1',
|
||||
datasourceName: 'test_ds',
|
||||
transformations: [
|
||||
{
|
||||
type: SupportedTransformationType.Regex,
|
||||
expression: '{(?=[^\\}]*\\bkey":"keyA")[^\\}]*\\bvalue":"(.*?)".*}',
|
||||
field: 'serviceTags',
|
||||
mapValue: 'msg',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const { field, range, dataFrame } = setup(transformationLink, true, {
|
||||
name: 'serviceTags',
|
||||
type: FieldType.other,
|
||||
values: [
|
||||
[
|
||||
{ value: 'broccoli', key: 'keyA' },
|
||||
{ value: 'apple', key: 'keyB' },
|
||||
],
|
||||
[
|
||||
{ key: 'keyA', value: 'cauliflower' },
|
||||
{ value: 'durian', key: 'keyB' },
|
||||
],
|
||||
],
|
||||
config: {
|
||||
links: [transformationLink],
|
||||
},
|
||||
});
|
||||
|
||||
const links = [
|
||||
getFieldLinksForExplore({ field, rowIndex: 0, range, dataFrame }),
|
||||
getFieldLinksForExplore({ field, rowIndex: 1, range, dataFrame }),
|
||||
];
|
||||
expect(links[0]).toHaveLength(1);
|
||||
expect(links[0][0].href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
'{"range":{"from":"now-1h","to":"now"},"datasource":"uid_1","queries":[{"query":"http_requests{env=broccoli}"}]}'
|
||||
)}`
|
||||
);
|
||||
expect(links[1]).toHaveLength(1);
|
||||
expect(links[1][0].href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
'{"range":{"from":"now-1h","to":"now"},"datasource":"uid_1","queries":[{"query":"http_requests{env=cauliflower}"}]}'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('returns internal links with logfmt with stringified booleans', () => {
|
||||
const transformationLink: DataLink = {
|
||||
title: '',
|
||||
@ -684,7 +739,7 @@ const ROW_WITH_NULL_VALUE = { value: null, index: 1 };
|
||||
function setup(
|
||||
link: DataLink,
|
||||
hasAccess = true,
|
||||
fieldOverride?: Field<string | null>,
|
||||
fieldOverride?: Field<string | Array<{ key: string; value: string }> | null>, // key/value array for traceView fields
|
||||
dataFrameOtherFieldOverride?: Field[]
|
||||
) {
|
||||
setLinkSrv({
|
||||
|
Loading…
Reference in New Issue
Block a user