grafana/public/app/features/explore/TraceView/TraceView.tsx
Connor Lindsey c1b5ea3e54
Tracing: Add trace to metrics config behind feature toggle (#46298)
* Add trace to metrics behind feature flag
2022-05-05 14:46:18 -06:00

259 lines
8.7 KiB
TypeScript

import { css } from '@emotion/css';
import { TopOfViewRefType } from '@jaegertracing/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView';
import React, { RefObject, useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
DataFrame,
DataLink,
DataQuery,
DataSourceApi,
DataSourceJsonData,
Field,
GrafanaTheme2,
LinkModel,
mapInternalLinkToExplore,
PanelData,
SplitOpen,
} from '@grafana/data';
import { getTemplateSrv } from '@grafana/runtime';
import { useStyles2 } from '@grafana/ui';
import { Trace, TracePageHeader, TraceTimelineViewer, TTraceTimeline } from '@jaegertracing/jaeger-ui-components';
import { TraceToLogsData } from 'app/core/components/TraceToLogs/TraceToLogsSettings';
import { TraceToMetricsData } from 'app/core/components/TraceToMetrics/TraceToMetricsSettings';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { getTimeZone } from 'app/features/profile/state/selectors';
import { StoreState } from 'app/types';
import { ExploreId } from 'app/types/explore';
import { changePanelState } from '../state/explorePane';
import { createSpanLinkFactory } from './createSpanLink';
import { useChildrenState } from './useChildrenState';
import { useDetailState } from './useDetailState';
import { useHoverIndentGuide } from './useHoverIndentGuide';
import { useViewRange } from './useViewRange';
const getStyles = (theme: GrafanaTheme2) => ({
noDataMsg: css`
height: 100%;
width: 100%;
display: grid;
place-items: center;
font-size: ${theme.typography.h4.fontSize};
color: ${theme.colors.text.secondary};
`,
});
function noop(): {} {
return {};
}
type Props = {
dataFrames: DataFrame[];
splitOpenFn?: SplitOpen;
exploreId?: ExploreId;
scrollElement?: Element;
traceProp: Trace;
spanFindMatches?: Set<string>;
search: string;
focusedSpanIdForSearch: string;
queryResponse: PanelData;
datasource: DataSourceApi<DataQuery, DataSourceJsonData, {}> | undefined;
topOfViewRef: RefObject<HTMLDivElement>;
topOfViewRefType: TopOfViewRefType;
};
export function TraceView(props: Props) {
const { spanFindMatches, traceProp, datasource, topOfViewRef, topOfViewRefType } = props;
const {
detailStates,
toggleDetail,
detailLogItemToggle,
detailLogsToggle,
detailProcessToggle,
detailReferencesToggle,
detailReferenceItemToggle,
detailTagsToggle,
detailWarningsToggle,
detailStackTracesToggle,
} = useDetailState(props.dataFrames[0]);
const { removeHoverIndentGuideId, addHoverIndentGuideId, hoverIndentGuideIds } = useHoverIndentGuide();
const { viewRange, updateViewRangeTime, updateNextViewRangeTime } = useViewRange();
const { expandOne, collapseOne, childrenToggle, collapseAll, childrenHiddenIDs, expandAll } = useChildrenState();
const styles = useStyles2(getStyles);
/**
* Keeps state of resizable name column width
*/
const [spanNameColumnWidth, setSpanNameColumnWidth] = useState(0.25);
/**
* State of the top minimap, slim means it is collapsed.
*/
const [slim, setSlim] = useState(false);
const [focusedSpanId, createFocusSpanLink] = useFocusSpanLink({
refId: props.dataFrames[0]?.refId,
exploreId: props.exploreId!,
datasource,
});
const createLinkToExternalSpan = (traceId: string, spanId: string) => {
const link = createFocusSpanLink(traceId, spanId);
return link.href;
};
const traceTimeline: TTraceTimeline = useMemo(
() => ({
childrenHiddenIDs,
detailStates,
hoverIndentGuideIds,
shouldScrollToFirstUiFindMatch: false,
spanNameColumnWidth,
traceID: props.traceProp?.traceID,
}),
[childrenHiddenIDs, detailStates, hoverIndentGuideIds, spanNameColumnWidth, props.traceProp?.traceID]
);
const instanceSettings = getDatasourceSrv().getInstanceSettings(datasource?.name);
const traceToLogsOptions = (instanceSettings?.jsonData as TraceToLogsData)?.tracesToLogs;
const traceToMetricsOptions = (instanceSettings?.jsonData as TraceToMetricsData)?.tracesToMetrics;
const createSpanLink = useMemo(
() =>
createSpanLinkFactory({
splitOpenFn: props.splitOpenFn!,
traceToLogsOptions,
traceToMetricsOptions,
dataFrame: props.dataFrames[0],
createFocusSpanLink,
}),
[props.splitOpenFn, traceToLogsOptions, traceToMetricsOptions, props.dataFrames, createFocusSpanLink]
);
const onSlimViewClicked = useCallback(() => setSlim(!slim), [slim]);
const timeZone = useSelector((state: StoreState) => getTimeZone(state.user));
return (
<>
{props.dataFrames?.length && props.dataFrames[0]?.meta?.preferredVisualisationType === 'trace' && traceProp ? (
<>
<TracePageHeader
canCollapse={false}
hideMap={false}
hideSummary={false}
onSlimViewClicked={onSlimViewClicked}
onTraceGraphViewClicked={noop}
slimView={slim}
trace={traceProp}
updateNextViewRangeTime={updateNextViewRangeTime}
updateViewRangeTime={updateViewRangeTime}
viewRange={viewRange}
timeZone={timeZone}
/>
<TraceTimelineViewer
registerAccessors={noop}
scrollToFirstVisibleSpan={noop}
findMatchesIDs={spanFindMatches}
trace={traceProp}
traceTimeline={traceTimeline}
updateNextViewRangeTime={updateNextViewRangeTime}
updateViewRangeTime={updateViewRangeTime}
viewRange={viewRange}
focusSpan={noop}
createLinkToExternalSpan={createLinkToExternalSpan}
setSpanNameColumnWidth={setSpanNameColumnWidth}
collapseAll={collapseAll}
collapseOne={collapseOne}
expandAll={expandAll}
expandOne={expandOne}
childrenToggle={childrenToggle}
clearShouldScrollToFirstUiFindMatch={noop}
detailLogItemToggle={detailLogItemToggle}
detailLogsToggle={detailLogsToggle}
detailWarningsToggle={detailWarningsToggle}
detailStackTracesToggle={detailStackTracesToggle}
detailReferencesToggle={detailReferencesToggle}
detailReferenceItemToggle={detailReferenceItemToggle}
detailProcessToggle={detailProcessToggle}
detailTagsToggle={detailTagsToggle}
detailToggle={toggleDetail}
setTrace={noop}
addHoverIndentGuideId={addHoverIndentGuideId}
removeHoverIndentGuideId={removeHoverIndentGuideId}
linksGetter={noop as any}
uiFind={props.search}
createSpanLink={createSpanLink}
scrollElement={props.scrollElement}
focusedSpanId={focusedSpanId}
focusedSpanIdForSearch={props.focusedSpanIdForSearch!}
createFocusSpanLink={createFocusSpanLink}
topOfViewRef={topOfViewRef}
topOfViewRefType={topOfViewRefType}
/>
</>
) : (
<div className={styles.noDataMsg}>No data</div>
)}
</>
);
}
/**
* 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];
}