TraceView: Resource attributes links extension point (#104680)

* TraceView: Resource attributes links extension point

* Add data source info to context

* Remove console log

* Removing unused linkGetters and more

* More tests

* Fixing last todos

* Fixing

* Fixing

* Change link style

* Rename extension context

* Fix lint error
revert-103961-zoltan/pgx
Edvard Falkskär 2 months ago committed by GitHub
parent eff86c6684
commit 0e4f06d452
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      packages/grafana-data/src/index.ts
  2. 10
      packages/grafana-data/src/types/pluginExtensions.ts
  3. 3
      public/app/features/explore/TraceView/TraceView.tsx
  4. 6
      public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx
  5. 7
      public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx
  6. 1
      public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx
  7. 16
      public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.tsx
  8. 33
      public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx
  9. 23
      public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.test.tsx
  10. 56
      public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx
  11. 14
      public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetailRow.tsx
  12. 10
      public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.tsx
  13. 7
      public/app/features/explore/TraceView/components/TraceTimelineViewer/index.tsx

@ -563,6 +563,7 @@ export {
type PluginExtensionAddedComponentConfig, type PluginExtensionAddedComponentConfig,
type PluginExtensionAddedLinkConfig, type PluginExtensionAddedLinkConfig,
type PluginExtensionAddedFunctionConfig, type PluginExtensionAddedFunctionConfig,
type PluginExtensionResourceAttributesContext,
} from './types/pluginExtensions'; } from './types/pluginExtensions';
export { export {
type ScopeDashboardBindingSpec, type ScopeDashboardBindingSpec,

@ -192,6 +192,7 @@ export enum PluginExtensionPoints {
UserProfileTab = 'grafana/user/profile/tab', UserProfileTab = 'grafana/user/profile/tab',
TraceViewDetails = 'grafana/traceview/details', TraceViewDetails = 'grafana/traceview/details',
QueryEditorRowAdaptiveTelemetryV1 = 'grafana/query-editor-row/adaptivetelemetry/v1', QueryEditorRowAdaptiveTelemetryV1 = 'grafana/query-editor-row/adaptivetelemetry/v1',
TraceViewResourceAttributes = 'grafana/traceview/resource-attributes',
} }
export type PluginExtensionPanelContext = { export type PluginExtensionPanelContext = {
@ -236,6 +237,15 @@ export type PluginExtensionDataSourceConfigContext<
export type PluginExtensionCommandPaletteContext = {}; export type PluginExtensionCommandPaletteContext = {};
export type PluginExtensionResourceAttributesContext = {
// Key-value pairs of resource attributes, attribute name is the key
attributes: Record<string, string[]>;
datasource: {
type: string;
uid: string;
};
};
type Dashboard = { type Dashboard = {
uid: string; uid: string;
title: string; title: string;

@ -169,6 +169,7 @@ export function TraceView(props: Props) {
); );
const timeZone = useSelector((state) => getTimeZone(state.user)); const timeZone = useSelector((state) => getTimeZone(state.user));
const datasourceType = datasource ? datasource?.type : 'unknown'; const datasourceType = datasource ? datasource?.type : 'unknown';
const datasourceUid = datasource ? datasource?.uid : '';
const scrollElement = props.scrollElement const scrollElement = props.scrollElement
? props.scrollElement ? props.scrollElement
: document.getElementsByClassName(props.scrollElementClass ?? '')[0]; : document.getElementsByClassName(props.scrollElementClass ?? '')[0];
@ -204,6 +205,7 @@ export function TraceView(props: Props) {
trace={traceProp} trace={traceProp}
traceToProfilesOptions={traceToProfilesOptions} traceToProfilesOptions={traceToProfilesOptions}
datasourceType={datasourceType} datasourceType={datasourceType}
datasourceUid={datasourceUid}
spanBarOptions={spanBarOptions?.spanBar} spanBarOptions={spanBarOptions?.spanBar}
traceTimeline={traceTimeline} traceTimeline={traceTimeline}
updateNextViewRangeTime={updateNextViewRangeTime} updateNextViewRangeTime={updateNextViewRangeTime}
@ -227,7 +229,6 @@ export function TraceView(props: Props) {
detailToggle={toggleDetail} detailToggle={toggleDetail}
addHoverIndentGuideId={addHoverIndentGuideId} addHoverIndentGuideId={addHoverIndentGuideId}
removeHoverIndentGuideId={removeHoverIndentGuideId} removeHoverIndentGuideId={removeHoverIndentGuideId}
linksGetter={() => []}
createSpanLink={createSpanLink} createSpanLink={createSpanLink}
scrollElement={scrollElement} scrollElement={scrollElement}
focusedSpanId={focusedSpanId} focusedSpanId={focusedSpanId}

@ -20,10 +20,10 @@ import { GrafanaTheme2, TraceKeyValuePair } from '@grafana/data';
import { Icon, useStyles2 } from '@grafana/ui'; import { Icon, useStyles2 } from '@grafana/ui';
import { autoColor } from '../../Theme'; import { autoColor } from '../../Theme';
import { TraceLink, TNil } from '../../types'; import { TNil } from '../../types';
import * as markers from './AccordianKeyValues.markers'; import * as markers from './AccordianKeyValues.markers';
import KeyValuesTable from './KeyValuesTable'; import KeyValuesTable, { KeyValuesTableLink } from './KeyValuesTable';
import { alignIcon } from '.'; import { alignIcon } from '.';
@ -94,7 +94,7 @@ export type AccordianKeyValuesProps = {
interactive?: boolean; interactive?: boolean;
isOpen: boolean; isOpen: boolean;
label: string | React.ReactNode; label: string | React.ReactNode;
linksGetter?: ((pairs: TraceKeyValuePair[], index: number) => TraceLink[]) | TNil; linksGetter?: ((pairs: TraceKeyValuePair[], index: number) => KeyValuesTableLink[]) | TNil;
onToggle?: null | (() => void); onToggle?: null | (() => void);
}; };

@ -16,13 +16,11 @@ import { css } from '@emotion/css';
import { sortBy as _sortBy } from 'lodash'; import { sortBy as _sortBy } from 'lodash';
import * as React from 'react'; import * as React from 'react';
import { GrafanaTheme2, TraceKeyValuePair, TraceLog } from '@grafana/data'; import { GrafanaTheme2, TraceLog } from '@grafana/data';
import { Trans } from '@grafana/i18n'; import { Trans } from '@grafana/i18n';
import { Icon, useStyles2 } from '@grafana/ui'; import { Icon, useStyles2 } from '@grafana/ui';
import { autoColor } from '../../Theme'; import { autoColor } from '../../Theme';
import { TNil } from '../../types';
import { TraceLink } from '../../types/trace';
import { formatDuration } from '../utils'; import { formatDuration } from '../utils';
import AccordianKeyValues from './AccordianKeyValues'; import AccordianKeyValues from './AccordianKeyValues';
@ -69,7 +67,6 @@ const getStyles = (theme: GrafanaTheme2) => {
export type AccordianLogsProps = { export type AccordianLogsProps = {
interactive?: boolean; interactive?: boolean;
isOpen: boolean; isOpen: boolean;
linksGetter?: ((pairs: TraceKeyValuePair[], index: number) => TraceLink[]) | TNil;
logs: TraceLog[]; logs: TraceLog[];
onItemToggle?: (log: TraceLog) => void; onItemToggle?: (log: TraceLog) => void;
onToggle?: () => void; onToggle?: () => void;
@ -80,7 +77,6 @@ export type AccordianLogsProps = {
export default function AccordianLogs({ export default function AccordianLogs({
interactive = true, interactive = true,
isOpen, isOpen,
linksGetter,
logs, logs,
openedItems, openedItems,
onItemToggle, onItemToggle,
@ -138,7 +134,6 @@ export default function AccordianLogs({
interactive={interactive} interactive={interactive}
isOpen={openedItems ? openedItems.has(log) : false} isOpen={openedItems ? openedItems.has(log) : false}
label={label} label={label}
linksGetter={linksGetter}
onToggle={interactive && onItemToggle ? () => onItemToggle(log) : null} onToggle={interactive && onItemToggle ? () => onItemToggle(log) : null}
/> />
); );

@ -176,7 +176,6 @@ export function References(props: ReferenceItemProps) {
interactive={interactive} interactive={interactive}
isOpen={openedItems ? openedItems.has(reference) : false} isOpen={openedItems ? openedItems.has(reference) : false}
label={t('explore.references.label-attributes', 'attributes')} label={t('explore.references.label-attributes', 'attributes')}
linksGetter={null}
onToggle={interactive && onItemToggle ? () => onItemToggle(reference) : null} onToggle={interactive && onItemToggle ? () => onItemToggle(reference) : null}
/> />
</div> </div>

@ -33,14 +33,12 @@ const setup = (propOverrides?: Partial<KeyValuesTableProps>) => {
describe('LinkValue', () => { describe('LinkValue', () => {
it('renders as expected', () => { it('renders as expected', () => {
const title = 'titleValue'; const link = {
const href = 'hrefValue'; title: 'titleValue',
path: 'hrefValue',
};
const childrenText = 'childrenTextValue'; const childrenText = 'childrenTextValue';
render( render(<LinkValue link={link}>{childrenText}</LinkValue>);
<LinkValue href={href} title={title}>
{childrenText}
</LinkValue>
);
expect(screen.getByRole('link', { name: 'titleValue' })).toBeInTheDocument(); expect(screen.getByRole('link', { name: 'titleValue' })).toBeInTheDocument();
expect(screen.getByText(/^childrenTextValue$/)).toBeInTheDocument(); expect(screen.getByText(/^childrenTextValue$/)).toBeInTheDocument();
}); });
@ -73,8 +71,8 @@ describe('KeyValuesTable tests', () => {
array[i].key === 'span.kind' array[i].key === 'span.kind'
? [ ? [
{ {
url: `http://example.com/?kind=${encodeURIComponent(array[i].value)}`, path: `http://example.com/?kind=${encodeURIComponent(array[i].value)}`,
text: `More info about ${array[i].value}`, title: `More info about ${array[i].value}`,
}, },
] ]
: [], : [],

@ -15,14 +15,13 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import cx from 'classnames'; import cx from 'classnames';
import { PropsWithChildren } from 'react'; import { PropsWithChildren } from 'react';
import * as React from 'react';
import { GrafanaTheme2, TraceKeyValuePair } from '@grafana/data'; import { GrafanaTheme2, PluginExtensionLink, TraceKeyValuePair } from '@grafana/data';
import { Icon, useStyles2 } from '@grafana/ui'; import { Icon, useStyles2 } from '@grafana/ui';
import { autoColor } from '../../Theme'; import { autoColor } from '../../Theme';
import CopyIcon from '../../common/CopyIcon'; import CopyIcon from '../../common/CopyIcon';
import { TraceLink, TNil } from '../../types'; import { TNil } from '../../types';
import jsonMarkup from './jsonMarkup'; import jsonMarkup from './jsonMarkup';
@ -57,6 +56,12 @@ export const getStyles = (theme: GrafanaTheme2) => {
[`&:not(:hover) .${copyIconClassName}`]: { [`&:not(:hover) .${copyIconClassName}`]: {
visibility: 'hidden', visibility: 'hidden',
}, },
'a span': {
color: `${theme.colors.text.link} !important`,
},
'a:hover span': {
textDecoration: 'underline',
},
}), }),
keyColumn: css({ keyColumn: css({
label: 'keyColumn', label: 'keyColumn',
@ -94,23 +99,25 @@ function parseIfComplexJson(value: unknown) {
return value; return value;
} }
export type KeyValuesTableLink = Pick<PluginExtensionLink, 'path' | 'title' | 'onClick' | 'icon'>;
interface LinkValueProps { interface LinkValueProps {
href: string; link: KeyValuesTableLink;
title?: string;
children: React.ReactNode;
} }
export const LinkValue = ({ href, title = '', children }: PropsWithChildren<LinkValueProps>) => { export const LinkValue = ({ link, children }: PropsWithChildren<LinkValueProps>) => {
const { path, title = '', onClick, icon = 'external-link-alt' } = link;
return ( return (
<a href={href} title={title} target="_blank" rel="noopener noreferrer"> <a href={path} title={title} onClick={onClick} target="_blank" rel="noopener noreferrer">
{children} <Icon name="external-link-alt" /> {children} <Icon name={icon} />
</a> </a>
); );
}; };
export type KeyValuesTableProps = { export type KeyValuesTableProps = {
data: TraceKeyValuePair[]; data: TraceKeyValuePair[];
linksGetter?: ((pairs: TraceKeyValuePair[], index: number) => TraceLink[]) | TNil; linksGetter?: ((pairs: TraceKeyValuePair[], index: number) => KeyValuesTableLink[]) | TNil;
}; };
export default function KeyValuesTable(props: KeyValuesTableProps) { export default function KeyValuesTable(props: KeyValuesTableProps) {
@ -125,15 +132,13 @@ export default function KeyValuesTable(props: KeyValuesTableProps) {
__html: jsonMarkup(parseIfComplexJson(row.value)), __html: jsonMarkup(parseIfComplexJson(row.value)),
}; };
const jsonTable = <div className={styles.jsonTable} dangerouslySetInnerHTML={markup} />; const jsonTable = <div className={styles.jsonTable} dangerouslySetInnerHTML={markup} />;
const links = linksGetter ? linksGetter(data, i) : null; const links = linksGetter?.(data, i);
let valueMarkup; let valueMarkup;
if (links && links.length) { if (links && links.length) {
// TODO: handle multiple items // TODO: handle multiple items
valueMarkup = ( valueMarkup = (
<div> <div>
<LinkValue href={links[0].url} title={links[0].text}> <LinkValue link={links[0]}>{jsonTable}</LinkValue>
{jsonTable}
</LinkValue>
</div> </div>
); );
} else { } else {

@ -76,6 +76,8 @@ describe('<SpanDetail>', () => {
to: 1000000000000, to: 1000000000000,
}, },
}, },
datasourceType: 'tempo',
datasourceUid: 'grafanacloud-traces',
}; };
span.tags = [ span.tags = [
@ -258,4 +260,25 @@ describe('<SpanDetail>', () => {
expect(screen.getByText(/(Count)/)).toBeInTheDocument(); expect(screen.getByText(/(Count)/)).toBeInTheDocument();
}); });
}); });
it('should load plugin links for resource attributes', () => {
const usePluginLinksMock = jest.fn().mockReturnValue({ links: [] });
setPluginLinksHook(usePluginLinksMock);
jest.requireMock('@grafana/runtime').usePluginLinks = usePluginLinksMock;
render(<SpanDetail {...(props as unknown as SpanDetailProps)} />);
expect(usePluginLinksMock).toHaveBeenCalledWith(
expect.objectContaining({
context: expect.objectContaining({
attributes: expect.objectContaining({
'http.url': expect.arrayContaining([expect.any(String)]),
}),
datasource: {
type: 'tempo',
uid: 'grafanacloud-traces',
},
}),
})
);
});
}); });

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

@ -16,14 +16,14 @@ import { css } from '@emotion/css';
import classNames from 'classnames'; import classNames from 'classnames';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { CoreApp, GrafanaTheme2, LinkModel, TimeRange, TraceKeyValuePair, TraceLog } from '@grafana/data'; import { CoreApp, GrafanaTheme2, LinkModel, TimeRange, TraceLog } from '@grafana/data';
import { TraceToProfilesOptions } from '@grafana/o11y-ds-frontend'; import { TraceToProfilesOptions } from '@grafana/o11y-ds-frontend';
import { TimeZone } from '@grafana/schema'; import { TimeZone } from '@grafana/schema';
import { Button, clearButtonStyles, stylesFactory, withTheme2 } from '@grafana/ui'; import { Button, clearButtonStyles, stylesFactory, withTheme2 } from '@grafana/ui';
import { autoColor } from '../Theme'; import { autoColor } from '../Theme';
import { SpanLinkFunc } from '../types'; import { SpanLinkFunc } from '../types';
import { TraceSpan, TraceLink, TraceSpanReference } from '../types/trace'; import { TraceSpan, TraceSpanReference } from '../types/trace';
import SpanDetail, { TraceFlameGraphs } from './SpanDetail'; import SpanDetail, { TraceFlameGraphs } from './SpanDetail';
import DetailState from './SpanDetail/DetailState'; import DetailState from './SpanDetail/DetailState';
@ -76,7 +76,6 @@ export type SpanDetailRowProps = {
columnDivision: number; columnDivision: number;
detailState: DetailState; detailState: DetailState;
onDetailToggled: (spanID: string) => void; onDetailToggled: (spanID: string) => void;
linksGetter: (span: TraceSpan, links: TraceKeyValuePair[], index: number) => TraceLink[];
logItemToggle: (spanID: string, log: TraceLog) => void; logItemToggle: (spanID: string, log: TraceLog) => void;
logsToggle: (spanID: string) => void; logsToggle: (spanID: string) => void;
processToggle: (spanID: string) => void; processToggle: (spanID: string) => void;
@ -99,6 +98,7 @@ export type SpanDetailRowProps = {
focusedSpanId?: string; focusedSpanId?: string;
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel; createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
datasourceType: string; datasourceType: string;
datasourceUid: string;
visibleSpanIds: string[]; visibleSpanIds: string[];
traceFlameGraphs: TraceFlameGraphs; traceFlameGraphs: TraceFlameGraphs;
setTraceFlameGraphs: (flameGraphs: TraceFlameGraphs) => void; setTraceFlameGraphs: (flameGraphs: TraceFlameGraphs) => void;
@ -112,11 +112,6 @@ export class UnthemedSpanDetailRow extends PureComponent<SpanDetailRowProps> {
this.props.onDetailToggled(this.props.span.spanID); this.props.onDetailToggled(this.props.span.spanID);
}; };
_linksGetter = (items: TraceKeyValuePair[], itemIndex: number) => {
const { linksGetter, span } = this.props;
return linksGetter(span, items, itemIndex);
};
render() { render() {
const { const {
color, color,
@ -144,6 +139,7 @@ export class UnthemedSpanDetailRow extends PureComponent<SpanDetailRowProps> {
focusedSpanId, focusedSpanId,
createFocusSpanLink, createFocusSpanLink,
datasourceType, datasourceType,
datasourceUid,
visibleSpanIds, visibleSpanIds,
traceFlameGraphs, traceFlameGraphs,
setTraceFlameGraphs, setTraceFlameGraphs,
@ -175,7 +171,6 @@ export class UnthemedSpanDetailRow extends PureComponent<SpanDetailRowProps> {
<div className={styles.infoWrapper} style={{ borderTopColor: color }}> <div className={styles.infoWrapper} style={{ borderTopColor: color }}>
<SpanDetail <SpanDetail
detailState={detailState} detailState={detailState}
linksGetter={this._linksGetter}
logItemToggle={logItemToggle} logItemToggle={logItemToggle}
logsToggle={logsToggle} logsToggle={logsToggle}
processToggle={processToggle} processToggle={processToggle}
@ -194,6 +189,7 @@ export class UnthemedSpanDetailRow extends PureComponent<SpanDetailRowProps> {
focusedSpanId={focusedSpanId} focusedSpanId={focusedSpanId}
createFocusSpanLink={createFocusSpanLink} createFocusSpanLink={createFocusSpanLink}
datasourceType={datasourceType} datasourceType={datasourceType}
datasourceUid={datasourceUid}
traceFlameGraphs={traceFlameGraphs} traceFlameGraphs={traceFlameGraphs}
setTraceFlameGraphs={setTraceFlameGraphs} setTraceFlameGraphs={setTraceFlameGraphs}
setRedrawListView={setRedrawListView} setRedrawListView={setRedrawListView}

@ -18,7 +18,7 @@ import memoizeOne from 'memoize-one';
import * as React from 'react'; import * as React from 'react';
import { RefObject } from 'react'; import { RefObject } from 'react';
import { CoreApp, GrafanaTheme2, LinkModel, TimeRange, TraceKeyValuePair, TraceLog } from '@grafana/data'; import { CoreApp, GrafanaTheme2, LinkModel, TimeRange, TraceLog } from '@grafana/data';
import { t } from '@grafana/i18n/internal'; import { t } from '@grafana/i18n/internal';
import { TraceToProfilesOptions } from '@grafana/o11y-ds-frontend'; import { TraceToProfilesOptions } from '@grafana/o11y-ds-frontend';
import { config, reportInteraction } from '@grafana/runtime'; import { config, reportInteraction } from '@grafana/runtime';
@ -28,7 +28,7 @@ import { stylesFactory, withTheme2, ToolbarButton } from '@grafana/ui';
import { PEER_SERVICE } from '../constants/tag-keys'; import { PEER_SERVICE } from '../constants/tag-keys';
import { CriticalPathSection, SpanBarOptions, SpanLinkFunc, TNil } from '../types'; import { CriticalPathSection, SpanBarOptions, SpanLinkFunc, TNil } from '../types';
import TTraceTimeline from '../types/TTraceTimeline'; import TTraceTimeline from '../types/TTraceTimeline';
import { TraceSpan, Trace, TraceLink, TraceSpanReference } from '../types/trace'; import { TraceSpan, Trace, TraceSpanReference } from '../types/trace';
import { getColorByKey } from '../utils/color-generator'; import { getColorByKey } from '../utils/color-generator';
import ListView from './ListView'; import ListView from './ListView';
@ -79,7 +79,6 @@ type TVirtualizedTraceViewOwnProps = {
trace: Trace; trace: Trace;
traceToProfilesOptions?: TraceToProfilesOptions; traceToProfilesOptions?: TraceToProfilesOptions;
spanBarOptions: SpanBarOptions | undefined; spanBarOptions: SpanBarOptions | undefined;
linksGetter: (span: TraceSpan, items: TraceKeyValuePair[], itemIndex: number) => TraceLink[];
childrenToggle: (spanID: string) => void; childrenToggle: (spanID: string) => void;
detailLogItemToggle: (spanID: string, log: TraceLog) => void; detailLogItemToggle: (spanID: string, log: TraceLog) => void;
detailLogsToggle: (spanID: string) => void; detailLogsToggle: (spanID: string) => void;
@ -104,6 +103,7 @@ type TVirtualizedTraceViewOwnProps = {
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel; createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
topOfViewRef?: RefObject<HTMLDivElement>; topOfViewRef?: RefObject<HTMLDivElement>;
datasourceType: string; datasourceType: string;
datasourceUid: string;
headerHeight: number; headerHeight: number;
criticalPath: CriticalPathSection[]; criticalPath: CriticalPathSection[];
traceFlameGraphs: TraceFlameGraphs; traceFlameGraphs: TraceFlameGraphs;
@ -551,12 +551,12 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
hoverIndentGuideIds, hoverIndentGuideIds,
addHoverIndentGuideId, addHoverIndentGuideId,
removeHoverIndentGuideId, removeHoverIndentGuideId,
linksGetter,
createSpanLink, createSpanLink,
focusedSpanId, focusedSpanId,
createFocusSpanLink, createFocusSpanLink,
theme, theme,
datasourceType, datasourceType,
datasourceUid,
traceFlameGraphs, traceFlameGraphs,
setTraceFlameGraphs, setTraceFlameGraphs,
setRedrawListView, setRedrawListView,
@ -577,7 +577,6 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
columnDivision={spanNameColumnWidth} columnDivision={spanNameColumnWidth}
onDetailToggled={detailToggle} onDetailToggled={detailToggle}
detailState={detailState} detailState={detailState}
linksGetter={linksGetter}
logItemToggle={detailLogItemToggle} logItemToggle={detailLogItemToggle}
logsToggle={detailLogsToggle} logsToggle={detailLogsToggle}
processToggle={detailProcessToggle} processToggle={detailProcessToggle}
@ -599,6 +598,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
focusedSpanId={focusedSpanId} focusedSpanId={focusedSpanId}
createFocusSpanLink={createFocusSpanLink} createFocusSpanLink={createFocusSpanLink}
datasourceType={datasourceType} datasourceType={datasourceType}
datasourceUid={datasourceUid}
visibleSpanIds={visibleSpanIds} visibleSpanIds={visibleSpanIds}
traceFlameGraphs={traceFlameGraphs} traceFlameGraphs={traceFlameGraphs}
setTraceFlameGraphs={setTraceFlameGraphs} setTraceFlameGraphs={setTraceFlameGraphs}

@ -15,7 +15,7 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { PureComponent, RefObject } from 'react'; import { PureComponent, RefObject } from 'react';
import { CoreApp, GrafanaTheme2, LinkModel, TimeRange, TraceKeyValuePair, TraceLog } from '@grafana/data'; import { CoreApp, GrafanaTheme2, LinkModel, TimeRange, TraceLog } from '@grafana/data';
import { SpanBarOptions, TraceToProfilesOptions } from '@grafana/o11y-ds-frontend'; import { SpanBarOptions, TraceToProfilesOptions } from '@grafana/o11y-ds-frontend';
import { config, reportInteraction } from '@grafana/runtime'; import { config, reportInteraction } from '@grafana/runtime';
import { TimeZone } from '@grafana/schema'; import { TimeZone } from '@grafana/schema';
@ -25,7 +25,7 @@ import { autoColor } from '../Theme';
import { merge as mergeShortcuts } from '../keyboard-shortcuts'; import { merge as mergeShortcuts } from '../keyboard-shortcuts';
import { CriticalPathSection, SpanLinkFunc, TNil } from '../types'; import { CriticalPathSection, SpanLinkFunc, TNil } from '../types';
import TTraceTimeline from '../types/TTraceTimeline'; import TTraceTimeline from '../types/TTraceTimeline';
import { TraceSpan, Trace, TraceLink, TraceSpanReference } from '../types/trace'; import { TraceSpan, Trace, TraceSpanReference } from '../types/trace';
import { TraceFlameGraphs } from './SpanDetail'; import { TraceFlameGraphs } from './SpanDetail';
import TimelineHeaderRow from './TimelineHeaderRow'; import TimelineHeaderRow from './TimelineHeaderRow';
@ -72,6 +72,7 @@ export type TProps = {
trace: Trace; trace: Trace;
traceToProfilesOptions?: TraceToProfilesOptions; traceToProfilesOptions?: TraceToProfilesOptions;
datasourceType: string; datasourceType: string;
datasourceUid: string;
spanBarOptions: SpanBarOptions | undefined; spanBarOptions: SpanBarOptions | undefined;
updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void; updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void;
updateViewRangeTime: TUpdateViewRangeTimeFunction; updateViewRangeTime: TUpdateViewRangeTimeFunction;
@ -96,7 +97,6 @@ export type TProps = {
detailToggle: (spanID: string) => void; detailToggle: (spanID: string) => void;
addHoverIndentGuideId: (spanID: string) => void; addHoverIndentGuideId: (spanID: string) => void;
removeHoverIndentGuideId: (spanID: string) => void; removeHoverIndentGuideId: (spanID: string) => void;
linksGetter: (span: TraceSpan, items: TraceKeyValuePair[], itemIndex: number) => TraceLink[];
theme: GrafanaTheme2; theme: GrafanaTheme2;
createSpanLink?: SpanLinkFunc; createSpanLink?: SpanLinkFunc;
scrollElement?: Element; scrollElement?: Element;
@ -222,6 +222,7 @@ export class UnthemedTraceTimelineViewer extends PureComponent<TProps, State> {
topOfViewRef={topOfViewRef} topOfViewRef={topOfViewRef}
focusedSpanIdForSearch={focusedSpanIdForSearch} focusedSpanIdForSearch={focusedSpanIdForSearch}
datasourceType={this.props.datasourceType} datasourceType={this.props.datasourceType}
datasourceUid={this.props.datasourceUid}
/> />
</div> </div>
); );

Loading…
Cancel
Save