Tempo / Trace Viewer: Implement deep linking to spans

This commit is contained in:
Erin Noe-Payne
2022-01-24 10:49:35 -05:00
committed by GitHub
parent fdeaf7a5c4
commit ac945fb6e1
24 changed files with 472 additions and 61 deletions

View File

@@ -1,4 +1,15 @@
import { DataFrame, DataFrameView, SplitOpen, TraceSpanRow } from '@grafana/data';
import {
DataFrame,
DataFrameView,
DataLink,
DataSourceApi,
Field,
LinkModel,
mapInternalLinkToExplore,
SplitOpen,
TraceSpanRow,
} from '@grafana/data';
import { getTemplateSrv } from '@grafana/runtime';
import {
Trace,
TracePageHeader,
@@ -15,7 +26,8 @@ import { getTimeZone } from 'app/features/profile/state/selectors';
import { StoreState } from 'app/types';
import { ExploreId } from 'app/types/explore';
import React, { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { changePanelState } from '../state/explorePane';
import { createSpanLinkFactory } from './createSpanLink';
import { UIElements } from './uiElements';
import { useChildrenState } from './useChildrenState';
@@ -66,10 +78,16 @@ export function TraceView(props: Props) {
const traceProp = useMemo(() => transformDataFrames(frame), [frame]);
const { search, setSearch, spanFindMatches } = useSearch(traceProp?.spans);
const dataSourceName = useSelector((state: StoreState) => state.explore[props.exploreId]?.datasourceInstance?.name);
const traceToLogsOptions = (getDatasourceSrv().getInstanceSettings(dataSourceName)?.jsonData as TraceToLogsData)
?.tracesToLogs;
const timeZone = useSelector((state: StoreState) => getTimeZone(state.user));
const datasource = useSelector(
(state: StoreState) => state.explore[props.exploreId]?.datasourceInstance ?? undefined
);
const [focusedSpanId, createFocusSpanLink] = useFocusSpanLink({
refId: frame?.refId,
exploreId: props.exploreId,
datasource,
});
const traceTimeline: TTraceTimeline = useMemo(
() => ({
@@ -83,11 +101,14 @@ export function TraceView(props: Props) {
[childrenHiddenIDs, detailStates, hoverIndentGuideIds, spanNameColumnWidth, traceProp?.traceID]
);
const traceToLogsOptions = (getDatasourceSrv().getInstanceSettings(datasource?.name)?.jsonData as TraceToLogsData)
?.tracesToLogs;
const createSpanLink = useMemo(
() => createSpanLinkFactory({ splitOpenFn: props.splitOpenFn, traceToLogsOptions, dataFrame: frame }),
[props.splitOpenFn, traceToLogsOptions, frame]
);
const onSlimViewClicked = useCallback(() => setSlim(!slim), [slim]);
const timeZone = useSelector((state: StoreState) => getTimeZone(state.user));
if (!props.dataFrames?.length || !traceProp) {
return null;
@@ -151,6 +172,8 @@ export function TraceView(props: Props) {
uiFind={search}
createSpanLink={createSpanLink}
scrollElement={props.scrollElement}
focusedSpanId={focusedSpanId}
createFocusSpanLink={createFocusSpanLink}
/>
</UIElementsContext.Provider>
);
@@ -198,3 +221,59 @@ function transformTraceDataFrame(frame: DataFrame): TraceResponse {
}),
};
}
/**
* Handles focusing a span. Returns the span id to focus to based on what is in current explore state and also a
* function to change the focused span id.
* @param options
*/
function useFocusSpanLink(options: {
exploreId: ExploreId;
refId?: string;
datasource?: DataSourceApi;
}): [string | undefined, (traceId: string, spanId: string) => LinkModel<Field>] {
const panelState = useSelector((state: StoreState) => state.explore[options.exploreId]?.panelsState.trace);
const focusedSpanId = panelState?.spanId;
const dispatch = useDispatch();
const setFocusedSpanId = (spanId?: string) =>
dispatch(
changePanelState(options.exploreId, 'trace', {
...panelState,
spanId,
})
);
const query = useSelector((state: StoreState) =>
state.explore[options.exploreId]?.queries.find((query) => query.refId === options.refId)
);
const createFocusSpanLink = (traceId: string, spanId: string) => {
const link: DataLink = {
title: 'Deep link to this span',
url: '',
internal: {
datasourceUid: options.datasource?.uid!,
datasourceName: options.datasource?.name!,
query: query,
panelsState: {
trace: {
spanId,
},
},
},
};
return mapInternalLinkToExplore({
link,
internalLink: link.internal!,
scopedVars: {},
range: {} as any,
field: {} as Field,
onClickFn: () => setFocusedSpanId(focusedSpanId === spanId ? undefined : spanId),
replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()),
});
};
return [focusedSpanId, createFocusSpanLink];
}

View File

@@ -31,7 +31,7 @@ describe('createSpanLinkFactory', () => {
const linkDef = createLink!(createTraceSpan());
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{cluster=\\"cluster1\\", hostname=\\"hostname1\\"}","refId":""}]}'
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{cluster=\\"cluster1\\", hostname=\\"hostname1\\"}","refId":""}],"panelsState":{}}'
)}`
);
});
@@ -54,7 +54,7 @@ describe('createSpanLinkFactory', () => {
);
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\"}","refId":""}]}'
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\"}","refId":""}],"panelsState":{}}'
)}`
);
});
@@ -77,7 +77,7 @@ describe('createSpanLinkFactory', () => {
);
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\", host=\\"host\\"}","refId":""}]}'
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\", host=\\"host\\"}","refId":""}],"panelsState":{}}'
)}`
);
});
@@ -101,7 +101,7 @@ describe('createSpanLinkFactory', () => {
);
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:01:00.000Z","to":"2020-10-14T01:01:01.000Z"},"datasource":"loki1","queries":[{"expr":"{hostname=\\"hostname1\\"}","refId":""}]}'
'{"range":{"from":"2020-10-14T01:01:00.000Z","to":"2020-10-14T01:01:01.000Z"},"datasource":"loki1","queries":[{"expr":"{hostname=\\"hostname1\\"}","refId":""}],"panelsState":{}}'
)}`
);
});
@@ -116,7 +116,7 @@ describe('createSpanLinkFactory', () => {
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{cluster=\\"cluster1\\", hostname=\\"hostname1\\"} |=\\"7946b05c2e2e4e5a\\" |=\\"6605c7b08e715d6c\\"","refId":""}]}'
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{cluster=\\"cluster1\\", hostname=\\"hostname1\\"} |=\\"7946b05c2e2e4e5a\\" |=\\"6605c7b08e715d6c\\"","refId":""}],"panelsState":{}}'
)}`
);
});