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 PluginExtensionAddedLinkConfig,
type PluginExtensionAddedFunctionConfig,
type PluginExtensionResourceAttributesContext,
} from './types/pluginExtensions';
export {
type ScopeDashboardBindingSpec,

@ -192,6 +192,7 @@ export enum PluginExtensionPoints {
UserProfileTab = 'grafana/user/profile/tab',
TraceViewDetails = 'grafana/traceview/details',
QueryEditorRowAdaptiveTelemetryV1 = 'grafana/query-editor-row/adaptivetelemetry/v1',
TraceViewResourceAttributes = 'grafana/traceview/resource-attributes',
}
export type PluginExtensionPanelContext = {
@ -236,6 +237,15 @@ export type PluginExtensionDataSourceConfigContext<
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 = {
uid: string;
title: string;

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

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

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

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

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

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

@ -76,6 +76,8 @@ describe('<SpanDetail>', () => {
to: 1000000000000,
},
},
datasourceType: 'tempo',
datasourceUid: 'grafanacloud-traces',
};
span.tags = [
@ -258,4 +260,25 @@ describe('<SpanDetail>', () => {
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 { 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={linksGetter}
linksGetter={resourceLinksGetter}
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}

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

@ -18,7 +18,7 @@ import memoizeOne from 'memoize-one';
import * as React 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 { TraceToProfilesOptions } from '@grafana/o11y-ds-frontend';
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 { CriticalPathSection, SpanBarOptions, SpanLinkFunc, TNil } from '../types';
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 ListView from './ListView';
@ -79,7 +79,6 @@ type TVirtualizedTraceViewOwnProps = {
trace: Trace;
traceToProfilesOptions?: TraceToProfilesOptions;
spanBarOptions: SpanBarOptions | undefined;
linksGetter: (span: TraceSpan, items: TraceKeyValuePair[], itemIndex: number) => TraceLink[];
childrenToggle: (spanID: string) => void;
detailLogItemToggle: (spanID: string, log: TraceLog) => void;
detailLogsToggle: (spanID: string) => void;
@ -104,6 +103,7 @@ type TVirtualizedTraceViewOwnProps = {
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
topOfViewRef?: RefObject<HTMLDivElement>;
datasourceType: string;
datasourceUid: string;
headerHeight: number;
criticalPath: CriticalPathSection[];
traceFlameGraphs: TraceFlameGraphs;
@ -551,12 +551,12 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
hoverIndentGuideIds,
addHoverIndentGuideId,
removeHoverIndentGuideId,
linksGetter,
createSpanLink,
focusedSpanId,
createFocusSpanLink,
theme,
datasourceType,
datasourceUid,
traceFlameGraphs,
setTraceFlameGraphs,
setRedrawListView,
@ -577,7 +577,6 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
columnDivision={spanNameColumnWidth}
onDetailToggled={detailToggle}
detailState={detailState}
linksGetter={linksGetter}
logItemToggle={detailLogItemToggle}
logsToggle={detailLogsToggle}
processToggle={detailProcessToggle}
@ -599,6 +598,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
focusedSpanId={focusedSpanId}
createFocusSpanLink={createFocusSpanLink}
datasourceType={datasourceType}
datasourceUid={datasourceUid}
visibleSpanIds={visibleSpanIds}
traceFlameGraphs={traceFlameGraphs}
setTraceFlameGraphs={setTraceFlameGraphs}

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

Loading…
Cancel
Save