@ -15,6 +15,7 @@
import { css } from '@emotion/css' ;
import { SpanStatusCode } from '@opentelemetry/api' ;
import cx from 'classnames' ;
import { useCallback , useMemo } from 'react' ;
import {
CoreApp ,
@ -25,9 +26,12 @@ import {
TimeRange ,
TraceKeyValuePair ,
TraceLog ,
PluginExtensionResourceAttributesContext ,
PluginExtensionPoints ,
} from '@grafana/data' ;
import { Trans , useTranslate } from '@grafana/i18n' ;
import { TraceToProfilesOptions } from '@grafana/o11y-ds-frontend' ;
import { usePluginLinks } from '@grafana/runtime' ;
import { TimeZone } from '@grafana/schema' ;
import { Divider , Icon , TextArea , useStyles2 } from '@grafana/ui' ;
@ -35,8 +39,8 @@ import { pyroscopeProfileIdTagKey } from '../../../createSpanLink';
import { autoColor } from '../../Theme' ;
import LabeledList from '../../common/LabeledList' ;
import { KIND , LIBRARY_NAME , LIBRARY_VERSION , STATUS , STATUS_MESSAGE , TRACE_STATE } from '../../constants/span' ;
import { SpanLinkFunc , TNil } from '../../types' ;
import { TraceLink , TraceSpan , TraceSpanReference } from '../../types/trace' ;
import { SpanLinkFunc } from '../../types' ;
import { TraceProcess , TraceSpan , TraceSpanReference } from '../../types/trace' ;
import { formatDuration } from '../utils' ;
import AccordianKeyValues from './AccordianKeyValues' ;
@ -47,6 +51,44 @@ import DetailState from './DetailState';
import { getSpanDetailLinkButtons } from './SpanDetailLinkButtons' ;
import SpanFlameGraph from './SpanFlameGraph' ;
const useResourceAttributesExtensionLinks = ( process : TraceProcess , datasourceType : string , datasourceUid : string ) = > {
// Stable context for useMemo inside usePluginLinks
const context : PluginExtensionResourceAttributesContext = useMemo ( ( ) = > {
const attributes = ( process . tags ? ? [ ] ) . reduce < Record < string , string [ ] > > ( ( acc , tag ) = > {
if ( acc [ tag . key ] ) {
acc [ tag . key ] . push ( tag . value ) ;
} else {
acc [ tag . key ] = [ tag . value ] ;
}
return acc ;
} , { } ) ;
return {
attributes ,
datasource : {
type : datasourceType ,
uid : datasourceUid ,
} ,
} ;
} , [ process . tags , datasourceType , datasourceUid ] ) ;
const { links } = usePluginLinks ( {
extensionPointId : PluginExtensionPoints.TraceViewResourceAttributes ,
limitPerPlugin : 10 ,
context ,
} ) ;
const resourceLinksGetter = useCallback (
( pairs : TraceKeyValuePair [ ] , index : number ) = > {
const { key } = pairs [ index ] ? ? { } ;
return links . filter ( ( link ) = > link . category === key ) ;
} ,
[ links ]
) ;
return resourceLinksGetter ;
} ;
const getStyles = ( theme : GrafanaTheme2 ) = > {
return {
header : css ( {
@ -145,7 +187,6 @@ export type TraceFlameGraphs = {
export type SpanDetailProps = {
detailState : DetailState ;
linksGetter : ( ( links : TraceKeyValuePair [ ] , index : number ) = > TraceLink [ ] ) | TNil ;
logItemToggle : ( spanID : string , log : TraceLog ) = > void ;
logsToggle : ( spanID : string ) = > void ;
processToggle : ( spanID : string ) = > void ;
@ -164,6 +205,7 @@ export type SpanDetailProps = {
focusedSpanId? : string ;
createFocusSpanLink : ( traceId : string , spanId : string ) = > LinkModel ;
datasourceType : string ;
datasourceUid : string ;
traceFlameGraphs : TraceFlameGraphs ;
setTraceFlameGraphs : ( flameGraphs : TraceFlameGraphs ) = > void ;
setRedrawListView : ( redraw : { } ) = > void ;
@ -174,7 +216,6 @@ export type SpanDetailProps = {
export default function SpanDetail ( props : SpanDetailProps ) {
const {
detailState ,
linksGetter ,
logItemToggle ,
logsToggle ,
processToggle ,
@ -190,6 +231,7 @@ export default function SpanDetail(props: SpanDetailProps) {
createSpanLink ,
createFocusSpanLink ,
datasourceType ,
datasourceUid ,
traceFlameGraphs ,
setTraceFlameGraphs ,
traceToProfilesOptions ,
@ -302,6 +344,8 @@ export default function SpanDetail(props: SpanDetailProps) {
} ) ;
const focusSpanLink = createFocusSpanLink ( traceID , spanID ) ;
const resourceLinksGetter = useResourceAttributesExtensionLinks ( process , datasourceType , datasourceUid ) ;
return (
< div data - testid = "span-detail-component" >
< div className = { styles . header } >
@ -319,7 +363,6 @@ export default function SpanDetail(props: SpanDetailProps) {
< AccordianKeyValues
data = { tags }
label = { t ( 'explore.span-detail.label-span-attributes' , 'Span attributes' ) }
linksGetter = { linksGetter }
isOpen = { isTagsOpen }
onToggle = { ( ) = > tagsToggle ( spanID ) }
/ >
@ -328,7 +371,7 @@ export default function SpanDetail(props: SpanDetailProps) {
className = { styles . AccordianKeyValuesItem }
data = { process . tags }
label = { t ( 'explore.span-detail.label-resource-attributes' , 'Resource attributes' ) }
linksGetter = { l inksGetter}
linksGetter = { resourceL inksGetter}
isOpen = { isProcessOpen }
onToggle = { ( ) = > processToggle ( spanID ) }
/ >
@ -336,7 +379,6 @@ export default function SpanDetail(props: SpanDetailProps) {
< / div >
{ logs && logs . length > 0 && (
< AccordianLogs
linksGetter = { linksGetter }
logs = { logs }
isOpen = { logsState . isOpen }
openedItems = { logsState . openedItems }