mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Tracing: Fix links to traces in Explore (#50113)
* Tracing: Fix links to traces in Explore * Fix links in dashboard * Fix references and tracetimelineviewer tests * Remove hard-coded references to fix tests * Add noopener
This commit is contained in:
parent
f9ddb8bf86
commit
c65d62c95b
@ -59,7 +59,7 @@ exports[`no enzyme tests`] = {
|
|||||||
"packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.test.js:3960703835": [
|
"packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.test.js:3960703835": [
|
||||||
[14, 19, 13, "RegExp match", "2409514259"]
|
[14, 19, 13, "RegExp match", "2409514259"]
|
||||||
],
|
],
|
||||||
"packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.test.js:2429764318": [
|
"packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.test.js:2025513694": [
|
||||||
[14, 19, 13, "RegExp match", "2409514259"]
|
[14, 19, 13, "RegExp match", "2409514259"]
|
||||||
],
|
],
|
||||||
"packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.js:3813002651": [
|
"packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.js:3813002651": [
|
||||||
@ -89,10 +89,10 @@ exports[`no enzyme tests`] = {
|
|||||||
"packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.test.js:551014442": [
|
"packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.test.js:551014442": [
|
||||||
[13, 26, 13, "RegExp match", "2409514259"]
|
[13, 26, 13, "RegExp match", "2409514259"]
|
||||||
],
|
],
|
||||||
"packages/jaeger-ui-components/src/TraceTimelineViewer/index.test.js:381298544": [
|
"packages/jaeger-ui-components/src/TraceTimelineViewer/index.test.js:276996587": [
|
||||||
[14, 19, 13, "RegExp match", "2409514259"]
|
[14, 19, 13, "RegExp match", "2409514259"]
|
||||||
],
|
],
|
||||||
"packages/jaeger-ui-components/src/url/ReferenceLink.test.js:3249503373": [
|
"packages/jaeger-ui-components/src/url/ReferenceLink.test.js:261377050": [
|
||||||
[14, 26, 13, "RegExp match", "2409514259"]
|
[14, 26, 13, "RegExp match", "2409514259"]
|
||||||
],
|
],
|
||||||
"public/app/core/components/PageActionBar/PageActionBar.test.tsx:1251504193": [
|
"public/app/core/components/PageActionBar/PageActionBar.test.tsx:1251504193": [
|
||||||
|
@ -63,7 +63,7 @@ describe('<AccordianReferences>', () => {
|
|||||||
highContrast: false,
|
highContrast: false,
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
onToggle: jest.fn(),
|
onToggle: jest.fn(),
|
||||||
focusSpan: jest.fn(),
|
createFocusSpanLink: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -88,7 +88,7 @@ describe('<References>', () => {
|
|||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
data: references,
|
data: references,
|
||||||
focusSpan: jest.fn(),
|
createFocusSpanLink: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -17,7 +17,7 @@ import * as React from 'react';
|
|||||||
import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down';
|
import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down';
|
||||||
import IoIosArrowRight from 'react-icons/lib/io/ios-arrow-right';
|
import IoIosArrowRight from 'react-icons/lib/io/ios-arrow-right';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { Field, GrafanaTheme2, LinkModel } from '@grafana/data';
|
||||||
import { Icon, useStyles2 } from '@grafana/ui';
|
import { Icon, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { autoColor } from '../../Theme';
|
import { autoColor } from '../../Theme';
|
||||||
@ -103,6 +103,11 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
serviceName: css`
|
serviceName: css`
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
`,
|
`,
|
||||||
|
title: css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -114,7 +119,7 @@ type AccordianReferencesProps = {
|
|||||||
openedItems?: Set<TraceSpanReference>;
|
openedItems?: Set<TraceSpanReference>;
|
||||||
onItemToggle?: (reference: TraceSpanReference) => void;
|
onItemToggle?: (reference: TraceSpanReference) => void;
|
||||||
onToggle?: null | (() => void);
|
onToggle?: null | (() => void);
|
||||||
focusSpan: (uiFind: string) => void;
|
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel<Field>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ReferenceItemProps = {
|
type ReferenceItemProps = {
|
||||||
@ -122,20 +127,20 @@ type ReferenceItemProps = {
|
|||||||
interactive?: boolean;
|
interactive?: boolean;
|
||||||
openedItems?: Set<TraceSpanReference>;
|
openedItems?: Set<TraceSpanReference>;
|
||||||
onItemToggle?: (reference: TraceSpanReference) => void;
|
onItemToggle?: (reference: TraceSpanReference) => void;
|
||||||
focusSpan: (uiFind: string) => void;
|
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel<Field>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// export for test
|
// export for test
|
||||||
export function References(props: ReferenceItemProps) {
|
export function References(props: ReferenceItemProps) {
|
||||||
const { data, focusSpan, openedItems, onItemToggle, interactive } = props;
|
const { data, createFocusSpanLink, openedItems, onItemToggle, interactive } = props;
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.AccordianReferencesContent}>
|
<div className={styles.AccordianReferencesContent}>
|
||||||
{data.map((reference, i) => (
|
{data.map((reference, i) => (
|
||||||
<div className={i < data.length - 1 ? styles.AccordianReferenceItem : undefined} key={reference.spanID}>
|
<div className={i < data.length - 1 ? styles.AccordianReferenceItem : undefined} key={i}>
|
||||||
<div className={styles.item} key={`${reference.spanID}`}>
|
<div className={styles.item} key={`${reference.spanID}`}>
|
||||||
<ReferenceLink reference={reference} focusSpan={focusSpan}>
|
<ReferenceLink reference={reference} createFocusSpanLink={createFocusSpanLink}>
|
||||||
<span className={styles.itemContent}>
|
<span className={styles.itemContent}>
|
||||||
{reference.span ? (
|
{reference.span ? (
|
||||||
<span>
|
<span>
|
||||||
@ -145,7 +150,7 @@ export function References(props: ReferenceItemProps) {
|
|||||||
<small className="endpoint-name">{reference.span.operationName}</small>
|
<small className="endpoint-name">{reference.span.operationName}</small>
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="span-svc-name">
|
<span className={cx('span-svc-name', styles.title)}>
|
||||||
View Linked Span <Icon name="external-link-alt" />
|
View Linked Span <Icon name="external-link-alt" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@ -187,7 +192,7 @@ const AccordianReferences: React.FC<AccordianReferencesProps> = ({
|
|||||||
onToggle,
|
onToggle,
|
||||||
onItemToggle,
|
onItemToggle,
|
||||||
openedItems,
|
openedItems,
|
||||||
focusSpan,
|
createFocusSpanLink,
|
||||||
}) => {
|
}) => {
|
||||||
const isEmpty = !Array.isArray(data) || !data.length;
|
const isEmpty = !Array.isArray(data) || !data.length;
|
||||||
let arrow: React.ReactNode | null = null;
|
let arrow: React.ReactNode | null = null;
|
||||||
@ -217,7 +222,7 @@ const AccordianReferences: React.FC<AccordianReferencesProps> = ({
|
|||||||
<References
|
<References
|
||||||
data={data}
|
data={data}
|
||||||
openedItems={openedItems}
|
openedItems={openedItems}
|
||||||
focusSpan={focusSpan}
|
createFocusSpanLink={createFocusSpanLink}
|
||||||
onItemToggle={onItemToggle}
|
onItemToggle={onItemToggle}
|
||||||
interactive={interactive}
|
interactive={interactive}
|
||||||
/>
|
/>
|
||||||
|
@ -116,7 +116,6 @@ type SpanDetailProps = {
|
|||||||
stackTracesToggle: (spanID: string) => void;
|
stackTracesToggle: (spanID: string) => void;
|
||||||
referenceItemToggle: (spanID: string, reference: TraceSpanReference) => void;
|
referenceItemToggle: (spanID: string, reference: TraceSpanReference) => void;
|
||||||
referencesToggle: (spanID: string) => void;
|
referencesToggle: (spanID: string) => void;
|
||||||
focusSpan: (uiFind: string) => void;
|
|
||||||
createSpanLink?: SpanLinkFunc;
|
createSpanLink?: SpanLinkFunc;
|
||||||
focusedSpanId?: string;
|
focusedSpanId?: string;
|
||||||
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
||||||
@ -137,7 +136,6 @@ export default function SpanDetail(props: SpanDetailProps) {
|
|||||||
stackTracesToggle,
|
stackTracesToggle,
|
||||||
referencesToggle,
|
referencesToggle,
|
||||||
referenceItemToggle,
|
referenceItemToggle,
|
||||||
focusSpan,
|
|
||||||
createSpanLink,
|
createSpanLink,
|
||||||
createFocusSpanLink,
|
createFocusSpanLink,
|
||||||
topOfViewRefType,
|
topOfViewRefType,
|
||||||
@ -284,7 +282,7 @@ export default function SpanDetail(props: SpanDetailProps) {
|
|||||||
openedItems={referencesState.openedItems}
|
openedItems={referencesState.openedItems}
|
||||||
onToggle={() => referencesToggle(spanID)}
|
onToggle={() => referencesToggle(spanID)}
|
||||||
onItemToggle={(reference) => referenceItemToggle(spanID, reference)}
|
onItemToggle={(reference) => referenceItemToggle(spanID, reference)}
|
||||||
focusSpan={focusSpan}
|
createFocusSpanLink={createFocusSpanLink}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{topOfViewRefType === TopOfViewRefType.Explore && (
|
{topOfViewRefType === TopOfViewRefType.Explore && (
|
||||||
|
@ -86,7 +86,6 @@ type SpanDetailRowProps = {
|
|||||||
span: TraceSpan;
|
span: TraceSpan;
|
||||||
tagsToggle: (spanID: string) => void;
|
tagsToggle: (spanID: string) => void;
|
||||||
traceStartTime: number;
|
traceStartTime: number;
|
||||||
focusSpan: (uiFind: string) => void;
|
|
||||||
hoverIndentGuideIds: Set<string>;
|
hoverIndentGuideIds: Set<string>;
|
||||||
addHoverIndentGuideId: (spanID: string) => void;
|
addHoverIndentGuideId: (spanID: string) => void;
|
||||||
removeHoverIndentGuideId: (spanID: string) => void;
|
removeHoverIndentGuideId: (spanID: string) => void;
|
||||||
@ -122,7 +121,6 @@ export class UnthemedSpanDetailRow extends React.PureComponent<SpanDetailRowProp
|
|||||||
span,
|
span,
|
||||||
tagsToggle,
|
tagsToggle,
|
||||||
traceStartTime,
|
traceStartTime,
|
||||||
focusSpan,
|
|
||||||
hoverIndentGuideIds,
|
hoverIndentGuideIds,
|
||||||
addHoverIndentGuideId,
|
addHoverIndentGuideId,
|
||||||
removeHoverIndentGuideId,
|
removeHoverIndentGuideId,
|
||||||
@ -169,7 +167,6 @@ export class UnthemedSpanDetailRow extends React.PureComponent<SpanDetailRowProp
|
|||||||
span={span}
|
span={span}
|
||||||
tagsToggle={tagsToggle}
|
tagsToggle={tagsToggle}
|
||||||
traceStartTime={traceStartTime}
|
traceStartTime={traceStartTime}
|
||||||
focusSpan={focusSpan}
|
|
||||||
createSpanLink={createSpanLink}
|
createSpanLink={createSpanLink}
|
||||||
focusedSpanId={focusedSpanId}
|
focusedSpanId={focusedSpanId}
|
||||||
createFocusSpanLink={createFocusSpanLink}
|
createFocusSpanLink={createFocusSpanLink}
|
||||||
|
@ -88,7 +88,6 @@ type TVirtualizedTraceViewOwnProps = {
|
|||||||
scrollToFirstVisibleSpan: () => void;
|
scrollToFirstVisibleSpan: () => void;
|
||||||
registerAccessors: (accesors: Accessors) => void;
|
registerAccessors: (accesors: Accessors) => void;
|
||||||
trace: Trace;
|
trace: Trace;
|
||||||
focusSpan: (uiFind: string) => void;
|
|
||||||
linksGetter: (span: TraceSpan, items: TraceKeyValuePair[], itemIndex: number) => TraceLink[];
|
linksGetter: (span: TraceSpan, items: TraceKeyValuePair[], itemIndex: number) => TraceLink[];
|
||||||
childrenToggle: (spanID: string) => void;
|
childrenToggle: (spanID: string) => void;
|
||||||
clearShouldScrollToFirstUiFindMatch: () => void;
|
clearShouldScrollToFirstUiFindMatch: () => void;
|
||||||
@ -479,7 +478,6 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
|||||||
detailToggle,
|
detailToggle,
|
||||||
spanNameColumnWidth,
|
spanNameColumnWidth,
|
||||||
trace,
|
trace,
|
||||||
focusSpan,
|
|
||||||
hoverIndentGuideIds,
|
hoverIndentGuideIds,
|
||||||
addHoverIndentGuideId,
|
addHoverIndentGuideId,
|
||||||
removeHoverIndentGuideId,
|
removeHoverIndentGuideId,
|
||||||
@ -514,7 +512,6 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
|||||||
span={span}
|
span={span}
|
||||||
tagsToggle={detailTagsToggle}
|
tagsToggle={detailTagsToggle}
|
||||||
traceStartTime={trace.startTime}
|
traceStartTime={trace.startTime}
|
||||||
focusSpan={focusSpan}
|
|
||||||
hoverIndentGuideIds={hoverIndentGuideIds}
|
hoverIndentGuideIds={hoverIndentGuideIds}
|
||||||
addHoverIndentGuideId={addHoverIndentGuideId}
|
addHoverIndentGuideId={addHoverIndentGuideId}
|
||||||
removeHoverIndentGuideId={removeHoverIndentGuideId}
|
removeHoverIndentGuideId={removeHoverIndentGuideId}
|
||||||
|
@ -53,7 +53,6 @@ describe('<TraceTimelineViewer>', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = shallow(<TraceTimelineViewer {...props} />)
|
wrapper = shallow(<TraceTimelineViewer {...props} />)
|
||||||
.dive()
|
|
||||||
.dive()
|
.dive()
|
||||||
.dive();
|
.dive();
|
||||||
});
|
});
|
||||||
|
@ -24,7 +24,6 @@ import { merge as mergeShortcuts } from '../keyboard-shortcuts';
|
|||||||
import { SpanLinkFunc, TNil } from '../types';
|
import { SpanLinkFunc, TNil } from '../types';
|
||||||
import TTraceTimeline from '../types/TTraceTimeline';
|
import TTraceTimeline from '../types/TTraceTimeline';
|
||||||
import { TraceSpan, Trace, TraceLog, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace';
|
import { TraceSpan, Trace, TraceLog, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace';
|
||||||
import ExternalLinkContext from '../url/externalLinkContext';
|
|
||||||
|
|
||||||
import TimelineHeaderRow from './TimelineHeaderRow';
|
import TimelineHeaderRow from './TimelineHeaderRow';
|
||||||
import VirtualizedTraceView, { TopOfViewRefType } from './VirtualizedTraceView';
|
import VirtualizedTraceView, { TopOfViewRefType } from './VirtualizedTraceView';
|
||||||
@ -79,8 +78,6 @@ type TProps = TExtractUiFindFromStateReturn & {
|
|||||||
updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void;
|
updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void;
|
||||||
updateViewRangeTime: TUpdateViewRangeTimeFunction;
|
updateViewRangeTime: TUpdateViewRangeTimeFunction;
|
||||||
viewRange: ViewRange;
|
viewRange: ViewRange;
|
||||||
focusSpan: (uiFind: string) => void;
|
|
||||||
createLinkToExternalSpan: (traceID: string, spanID: string) => string;
|
|
||||||
|
|
||||||
setSpanNameColumnWidth: (width: number) => void;
|
setSpanNameColumnWidth: (width: number) => void;
|
||||||
collapseAll: (spans: TraceSpan[]) => void;
|
collapseAll: (spans: TraceSpan[]) => void;
|
||||||
@ -163,7 +160,6 @@ export class UnthemedTraceTimelineViewer extends React.PureComponent<TProps, Sta
|
|||||||
updateNextViewRangeTime,
|
updateNextViewRangeTime,
|
||||||
updateViewRangeTime,
|
updateViewRangeTime,
|
||||||
viewRange,
|
viewRange,
|
||||||
createLinkToExternalSpan,
|
|
||||||
traceTimeline,
|
traceTimeline,
|
||||||
theme,
|
theme,
|
||||||
topOfViewRef,
|
topOfViewRef,
|
||||||
@ -174,35 +170,33 @@ export class UnthemedTraceTimelineViewer extends React.PureComponent<TProps, Sta
|
|||||||
const styles = getStyles(theme);
|
const styles = getStyles(theme);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExternalLinkContext.Provider value={createLinkToExternalSpan}>
|
<div
|
||||||
<div
|
className={styles.TraceTimelineViewer}
|
||||||
className={styles.TraceTimelineViewer}
|
ref={(ref: HTMLDivElement | null) => ref && this.setState({ height: ref.getBoundingClientRect().height })}
|
||||||
ref={(ref: HTMLDivElement | null) => ref && this.setState({ height: ref.getBoundingClientRect().height })}
|
>
|
||||||
>
|
<TimelineHeaderRow
|
||||||
<TimelineHeaderRow
|
duration={trace.duration}
|
||||||
duration={trace.duration}
|
nameColumnWidth={traceTimeline.spanNameColumnWidth}
|
||||||
nameColumnWidth={traceTimeline.spanNameColumnWidth}
|
numTicks={NUM_TICKS}
|
||||||
numTicks={NUM_TICKS}
|
onCollapseAll={this.collapseAll}
|
||||||
onCollapseAll={this.collapseAll}
|
onCollapseOne={this.collapseOne}
|
||||||
onCollapseOne={this.collapseOne}
|
onColummWidthChange={setSpanNameColumnWidth}
|
||||||
onColummWidthChange={setSpanNameColumnWidth}
|
onExpandAll={this.expandAll}
|
||||||
onExpandAll={this.expandAll}
|
onExpandOne={this.expandOne}
|
||||||
onExpandOne={this.expandOne}
|
viewRangeTime={viewRange.time}
|
||||||
viewRangeTime={viewRange.time}
|
updateNextViewRangeTime={updateNextViewRangeTime}
|
||||||
updateNextViewRangeTime={updateNextViewRangeTime}
|
updateViewRangeTime={updateViewRangeTime}
|
||||||
updateViewRangeTime={updateViewRangeTime}
|
columnResizeHandleHeight={this.state.height}
|
||||||
columnResizeHandleHeight={this.state.height}
|
/>
|
||||||
/>
|
<VirtualizedTraceView
|
||||||
<VirtualizedTraceView
|
{...rest}
|
||||||
{...rest}
|
{...traceTimeline}
|
||||||
{...traceTimeline}
|
setSpanNameColumnWidth={setSpanNameColumnWidth}
|
||||||
setSpanNameColumnWidth={setSpanNameColumnWidth}
|
currentViewRangeTime={viewRange.time.current}
|
||||||
currentViewRangeTime={viewRange.time.current}
|
topOfViewRef={topOfViewRef}
|
||||||
topOfViewRef={topOfViewRef}
|
focusedSpanIdForSearch={focusedSpanIdForSearch}
|
||||||
focusedSpanIdForSearch={focusedSpanIdForSearch}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
</ExternalLinkContext.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,62 +16,26 @@ import { shallow, mount } from 'enzyme';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import ReferenceLink from './ReferenceLink';
|
import ReferenceLink from './ReferenceLink';
|
||||||
import ExternalLinkContext from './externalLinkContext';
|
|
||||||
|
|
||||||
describe(ReferenceLink, () => {
|
describe(ReferenceLink, () => {
|
||||||
const focusMock = jest.fn();
|
const createFocusSpanLinkMock = jest.fn((traceId, spanId) => {
|
||||||
|
return {
|
||||||
|
href: `${traceId}-${spanId}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const sameTraceRef = {
|
const ref = {
|
||||||
refType: 'CHILD_OF',
|
refType: 'FOLLOWS_FROM',
|
||||||
traceID: 'trace1',
|
traceID: 'trace1',
|
||||||
spanID: 'span1',
|
spanID: 'span1',
|
||||||
span: {
|
|
||||||
// not null or undefined is an indicator of an internal reference
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const externalRef = {
|
|
||||||
refType: 'CHILD_OF',
|
|
||||||
traceID: 'trace2',
|
|
||||||
spanID: 'span2',
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('rendering', () => {
|
describe('rendering', () => {
|
||||||
it('render for this trace', () => {
|
it('renders reference with correct href', () => {
|
||||||
const component = shallow(<ReferenceLink reference={sameTraceRef} focusSpan={focusMock} />);
|
const component = shallow(<ReferenceLink reference={ref} createFocusSpanLink={createFocusSpanLinkMock} />);
|
||||||
|
|
||||||
const link = component.find('a');
|
const link = component.find('a');
|
||||||
expect(link.length).toBe(1);
|
expect(link.length).toBe(1);
|
||||||
expect(link.props().role).toBe('button');
|
expect(link.prop('href')).toBe('trace1-span1');
|
||||||
});
|
|
||||||
|
|
||||||
it('render for external trace', () => {
|
|
||||||
const component = mount(
|
|
||||||
<ExternalLinkContext.Provider value={(trace, span) => `${trace}/${span}`}>
|
|
||||||
<ReferenceLink reference={externalRef} focusSpan={focusMock} />
|
|
||||||
</ExternalLinkContext.Provider>
|
|
||||||
);
|
|
||||||
const link = component.find('a[href="trace2/span2"]');
|
|
||||||
expect(link.length).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if ExternalLinkContext is not set', () => {
|
|
||||||
// Prevent writing to stderr during this render.
|
|
||||||
const err = console.error;
|
|
||||||
console.error = jest.fn();
|
|
||||||
expect(() => mount(<ReferenceLink reference={externalRef} focusSpan={focusMock} />)).toThrow(
|
|
||||||
'ExternalLinkContext'
|
|
||||||
);
|
|
||||||
// Restore writing to stderr.
|
|
||||||
console.error = err;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('focus span', () => {
|
|
||||||
it('call focusSpan', () => {
|
|
||||||
focusMock.mockReset();
|
|
||||||
const component = shallow(<ReferenceLink reference={sameTraceRef} focusSpan={focusMock} />);
|
|
||||||
const link = component.find('a');
|
|
||||||
link.simulate('click');
|
|
||||||
expect(focusMock).toHaveBeenLastCalledWith('span1');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -14,48 +14,36 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { TraceSpanReference } from '../types/trace';
|
import { Field, LinkModel } from '@grafana/data';
|
||||||
|
|
||||||
import ExternalLinkContext from './externalLinkContext';
|
import { TraceSpanReference } from '../types/trace';
|
||||||
|
|
||||||
type ReferenceLinkProps = {
|
type ReferenceLinkProps = {
|
||||||
reference: TraceSpanReference;
|
reference: TraceSpanReference;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel<Field>;
|
||||||
focusSpan: (spanID: string) => void;
|
|
||||||
onClick?: () => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ReferenceLink(props: ReferenceLinkProps) {
|
export default function ReferenceLink(props: ReferenceLinkProps) {
|
||||||
const { reference, children, className, focusSpan, ...otherProps } = props;
|
const { reference, children, createFocusSpanLink } = props;
|
||||||
delete otherProps.onClick;
|
|
||||||
if (reference.span) {
|
const link = createFocusSpanLink(reference.traceID, reference.spanID);
|
||||||
return (
|
|
||||||
<a role="button" onClick={() => focusSpan(reference.spanID)} className={className} {...otherProps}>
|
|
||||||
{children}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExternalLinkContext.Consumer>
|
<a
|
||||||
{(createLinkToExternalSpan) => {
|
href={link.href}
|
||||||
if (!createLinkToExternalSpan) {
|
target={link.target}
|
||||||
throw new Error("ExternalLinkContext does not have a value, you probably forgot to setup it's provider");
|
rel="noopener noreferrer"
|
||||||
}
|
onClick={
|
||||||
|
link.onClick
|
||||||
return (
|
? (event) => {
|
||||||
<a
|
event.preventDefault();
|
||||||
href={createLinkToExternalSpan(reference.traceID, reference.spanID)}
|
link.onClick!(event);
|
||||||
target="_blank"
|
}
|
||||||
rel="noopener noreferrer"
|
: undefined
|
||||||
className={className}
|
}
|
||||||
{...otherProps}
|
>
|
||||||
>
|
{children}
|
||||||
{children}
|
</a>
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</ExternalLinkContext.Consumer>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
// Copyright (c) 2017 Uber Technologies, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* There are several places where external links to spans are created. The url layout though is something
|
|
||||||
* that should be decided on the application level and not on the component level but at the same time
|
|
||||||
* propagating the factory function everywhere could be cumbersome so we use this context for that.
|
|
||||||
*/
|
|
||||||
const ExternalLinkContext = React.createContext<((traceID: string, spanID: string) => string) | undefined>(undefined);
|
|
||||||
ExternalLinkContext.displayName = 'ExternalLinkContext';
|
|
||||||
export default ExternalLinkContext;
|
|
@ -23,6 +23,7 @@ import { TraceToLogsData } from 'app/core/components/TraceToLogs/TraceToLogsSett
|
|||||||
import { TraceToMetricsData } from 'app/core/components/TraceToMetrics/TraceToMetricsSettings';
|
import { TraceToMetricsData } from 'app/core/components/TraceToMetrics/TraceToMetricsSettings';
|
||||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
import { getTimeZone } from 'app/features/profile/state/selectors';
|
import { getTimeZone } from 'app/features/profile/state/selectors';
|
||||||
|
import { TempoQuery } from 'app/plugins/datasource/tempo/datasource';
|
||||||
import { StoreState } from 'app/types';
|
import { StoreState } from 'app/types';
|
||||||
import { ExploreId } from 'app/types/explore';
|
import { ExploreId } from 'app/types/explore';
|
||||||
|
|
||||||
@ -99,13 +100,9 @@ export function TraceView(props: Props) {
|
|||||||
refId: props.dataFrames[0]?.refId,
|
refId: props.dataFrames[0]?.refId,
|
||||||
exploreId: props.exploreId!,
|
exploreId: props.exploreId!,
|
||||||
datasource,
|
datasource,
|
||||||
|
splitOpenFn: props.splitOpenFn!,
|
||||||
});
|
});
|
||||||
|
|
||||||
const createLinkToExternalSpan = (traceId: string, spanId: string) => {
|
|
||||||
const link = createFocusSpanLink(traceId, spanId);
|
|
||||||
return link.href;
|
|
||||||
};
|
|
||||||
|
|
||||||
const traceTimeline: TTraceTimeline = useMemo(
|
const traceTimeline: TTraceTimeline = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
childrenHiddenIDs,
|
childrenHiddenIDs,
|
||||||
@ -162,8 +159,6 @@ export function TraceView(props: Props) {
|
|||||||
updateNextViewRangeTime={updateNextViewRangeTime}
|
updateNextViewRangeTime={updateNextViewRangeTime}
|
||||||
updateViewRangeTime={updateViewRangeTime}
|
updateViewRangeTime={updateViewRangeTime}
|
||||||
viewRange={viewRange}
|
viewRange={viewRange}
|
||||||
focusSpan={noop}
|
|
||||||
createLinkToExternalSpan={createLinkToExternalSpan}
|
|
||||||
setSpanNameColumnWidth={setSpanNameColumnWidth}
|
setSpanNameColumnWidth={setSpanNameColumnWidth}
|
||||||
collapseAll={collapseAll}
|
collapseAll={collapseAll}
|
||||||
collapseOne={collapseOne}
|
collapseOne={collapseOne}
|
||||||
@ -208,6 +203,7 @@ export function TraceView(props: Props) {
|
|||||||
*/
|
*/
|
||||||
function useFocusSpanLink(options: {
|
function useFocusSpanLink(options: {
|
||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
|
splitOpenFn: SplitOpen;
|
||||||
refId?: string;
|
refId?: string;
|
||||||
datasource?: DataSourceApi;
|
datasource?: DataSourceApi;
|
||||||
}): [string | undefined, (traceId: string, spanId: string) => LinkModel<Field>] {
|
}): [string | undefined, (traceId: string, spanId: string) => LinkModel<Field>] {
|
||||||
@ -234,7 +230,10 @@ function useFocusSpanLink(options: {
|
|||||||
internal: {
|
internal: {
|
||||||
datasourceUid: options.datasource?.uid!,
|
datasourceUid: options.datasource?.uid!,
|
||||||
datasourceName: options.datasource?.name!,
|
datasourceName: options.datasource?.name!,
|
||||||
query: query,
|
query: {
|
||||||
|
...query,
|
||||||
|
query: traceId,
|
||||||
|
},
|
||||||
panelsState: {
|
panelsState: {
|
||||||
trace: {
|
trace: {
|
||||||
spanId,
|
spanId,
|
||||||
@ -243,13 +242,34 @@ function useFocusSpanLink(options: {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Check if the link is to a different trace or not.
|
||||||
|
// If it's the same trace, only update panel state with setFocusedSpanId (no navigation).
|
||||||
|
// If it's a different trace, use splitOpenFn to open a new explore panel
|
||||||
|
const sameTrace = query?.queryType === 'traceId' && (query as TempoQuery).query === traceId;
|
||||||
|
|
||||||
return mapInternalLinkToExplore({
|
return mapInternalLinkToExplore({
|
||||||
link,
|
link,
|
||||||
internalLink: link.internal!,
|
internalLink: link.internal!,
|
||||||
scopedVars: {},
|
scopedVars: {},
|
||||||
range: {} as any,
|
range: {} as any,
|
||||||
field: {} as Field,
|
field: {} as Field,
|
||||||
onClickFn: () => setFocusedSpanId(focusedSpanId === spanId ? undefined : spanId),
|
onClickFn: sameTrace
|
||||||
|
? () => setFocusedSpanId(focusedSpanId === spanId ? undefined : spanId)
|
||||||
|
: options.splitOpenFn
|
||||||
|
? () =>
|
||||||
|
options.splitOpenFn({
|
||||||
|
datasourceUid: options.datasource?.uid!,
|
||||||
|
query: {
|
||||||
|
...query!,
|
||||||
|
query: traceId,
|
||||||
|
},
|
||||||
|
panelsState: {
|
||||||
|
trace: {
|
||||||
|
spanId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()),
|
replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user