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; search: string; focusedSpanIdForSearch: string; queryResponse: PanelData; datasource: DataSourceApi | undefined; topOfViewRef: RefObject; 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)); const datasourceType = datasource ? datasource?.type : 'unknown'; return ( <> {props.dataFrames?.length && props.dataFrames[0]?.meta?.preferredVisualisationType === 'trace' && traceProp ? ( <> ) : (
No data
)} ); } /** * 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] { 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]; }