diff --git a/.betterer.results b/.betterer.results index c720b20a63b..4da356b24ab 100644 --- a/.betterer.results +++ b/.betterer.results @@ -59,7 +59,7 @@ exports[`no enzyme tests`] = { "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.test.js:3960703835": [ [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"] ], "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": [ [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"] ], - "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"] ], "public/app/core/components/PageActionBar/PageActionBar.test.tsx:1251504193": [ diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.test.js index 956125c6479..88003e8a888 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.test.js @@ -63,7 +63,7 @@ describe('', () => { highContrast: false, isOpen: false, onToggle: jest.fn(), - focusSpan: jest.fn(), + createFocusSpanLink: jest.fn(), }; beforeEach(() => { @@ -88,7 +88,7 @@ describe('', () => { const props = { data: references, - focusSpan: jest.fn(), + createFocusSpanLink: jest.fn(), }; beforeEach(() => { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx index 7018b61692f..e976bfb4d83 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx @@ -17,7 +17,7 @@ import * as React from 'react'; import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down'; 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 { autoColor } from '../../Theme'; @@ -103,6 +103,11 @@ const getStyles = (theme: GrafanaTheme2) => { serviceName: css` margin-right: 8px; `, + title: css` + display: flex; + align-items: center; + gap: 4px; + `, }; }; @@ -114,7 +119,7 @@ type AccordianReferencesProps = { openedItems?: Set; onItemToggle?: (reference: TraceSpanReference) => void; onToggle?: null | (() => void); - focusSpan: (uiFind: string) => void; + createFocusSpanLink: (traceId: string, spanId: string) => LinkModel; }; type ReferenceItemProps = { @@ -122,20 +127,20 @@ type ReferenceItemProps = { interactive?: boolean; openedItems?: Set; onItemToggle?: (reference: TraceSpanReference) => void; - focusSpan: (uiFind: string) => void; + createFocusSpanLink: (traceId: string, spanId: string) => LinkModel; }; // export for test export function References(props: ReferenceItemProps) { - const { data, focusSpan, openedItems, onItemToggle, interactive } = props; + const { data, createFocusSpanLink, openedItems, onItemToggle, interactive } = props; const styles = useStyles2(getStyles); return (
{data.map((reference, i) => ( -
+
- + {reference.span ? ( @@ -145,7 +150,7 @@ export function References(props: ReferenceItemProps) { {reference.span.operationName} ) : ( - + View Linked Span )} @@ -187,7 +192,7 @@ const AccordianReferences: React.FC = ({ onToggle, onItemToggle, openedItems, - focusSpan, + createFocusSpanLink, }) => { const isEmpty = !Array.isArray(data) || !data.length; let arrow: React.ReactNode | null = null; @@ -217,7 +222,7 @@ const AccordianReferences: React.FC = ({ diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx index 9b2c784b2f9..65c9dbf6c71 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx @@ -116,7 +116,6 @@ type SpanDetailProps = { stackTracesToggle: (spanID: string) => void; referenceItemToggle: (spanID: string, reference: TraceSpanReference) => void; referencesToggle: (spanID: string) => void; - focusSpan: (uiFind: string) => void; createSpanLink?: SpanLinkFunc; focusedSpanId?: string; createFocusSpanLink: (traceId: string, spanId: string) => LinkModel; @@ -137,7 +136,6 @@ export default function SpanDetail(props: SpanDetailProps) { stackTracesToggle, referencesToggle, referenceItemToggle, - focusSpan, createSpanLink, createFocusSpanLink, topOfViewRefType, @@ -284,7 +282,7 @@ export default function SpanDetail(props: SpanDetailProps) { openedItems={referencesState.openedItems} onToggle={() => referencesToggle(spanID)} onItemToggle={(reference) => referenceItemToggle(spanID, reference)} - focusSpan={focusSpan} + createFocusSpanLink={createFocusSpanLink} /> )} {topOfViewRefType === TopOfViewRefType.Explore && ( diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx index 7f5905ba6d6..aedf2034c18 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx @@ -86,7 +86,6 @@ type SpanDetailRowProps = { span: TraceSpan; tagsToggle: (spanID: string) => void; traceStartTime: number; - focusSpan: (uiFind: string) => void; hoverIndentGuideIds: Set; addHoverIndentGuideId: (spanID: string) => void; removeHoverIndentGuideId: (spanID: string) => void; @@ -122,7 +121,6 @@ export class UnthemedSpanDetailRow extends React.PureComponent void; registerAccessors: (accesors: Accessors) => void; trace: Trace; - focusSpan: (uiFind: string) => void; linksGetter: (span: TraceSpan, items: TraceKeyValuePair[], itemIndex: number) => TraceLink[]; childrenToggle: (spanID: string) => void; clearShouldScrollToFirstUiFindMatch: () => void; @@ -479,7 +478,6 @@ export class UnthemedVirtualizedTraceView extends React.Component', () => { beforeEach(() => { wrapper = shallow() - .dive() .dive() .dive(); }); diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx index fe612d483f5..9a6649ebf77 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx @@ -24,7 +24,6 @@ import { merge as mergeShortcuts } from '../keyboard-shortcuts'; import { SpanLinkFunc, TNil } from '../types'; import TTraceTimeline from '../types/TTraceTimeline'; import { TraceSpan, Trace, TraceLog, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace'; -import ExternalLinkContext from '../url/externalLinkContext'; import TimelineHeaderRow from './TimelineHeaderRow'; import VirtualizedTraceView, { TopOfViewRefType } from './VirtualizedTraceView'; @@ -79,8 +78,6 @@ type TProps = TExtractUiFindFromStateReturn & { updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void; updateViewRangeTime: TUpdateViewRangeTimeFunction; viewRange: ViewRange; - focusSpan: (uiFind: string) => void; - createLinkToExternalSpan: (traceID: string, spanID: string) => string; setSpanNameColumnWidth: (width: number) => void; collapseAll: (spans: TraceSpan[]) => void; @@ -163,7 +160,6 @@ export class UnthemedTraceTimelineViewer extends React.PureComponent -
ref && this.setState({ height: ref.getBoundingClientRect().height })} - > - - -
- +
ref && this.setState({ height: ref.getBoundingClientRect().height })} + > + + +
); } } diff --git a/packages/jaeger-ui-components/src/url/ReferenceLink.test.js b/packages/jaeger-ui-components/src/url/ReferenceLink.test.js index 1334f8b980c..2d8e76a009b 100644 --- a/packages/jaeger-ui-components/src/url/ReferenceLink.test.js +++ b/packages/jaeger-ui-components/src/url/ReferenceLink.test.js @@ -16,62 +16,26 @@ import { shallow, mount } from 'enzyme'; import React from 'react'; import ReferenceLink from './ReferenceLink'; -import ExternalLinkContext from './externalLinkContext'; describe(ReferenceLink, () => { - const focusMock = jest.fn(); + const createFocusSpanLinkMock = jest.fn((traceId, spanId) => { + return { + href: `${traceId}-${spanId}`, + }; + }); - const sameTraceRef = { - refType: 'CHILD_OF', + const ref = { + refType: 'FOLLOWS_FROM', traceID: 'trace1', 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', () => { - it('render for this trace', () => { - const component = shallow(); + it('renders reference with correct href', () => { + const component = shallow(); + const link = component.find('a'); expect(link.length).toBe(1); - expect(link.props().role).toBe('button'); - }); - - it('render for external trace', () => { - const component = mount( - `${trace}/${span}`}> - - - ); - 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()).toThrow( - 'ExternalLinkContext' - ); - // Restore writing to stderr. - console.error = err; - }); - }); - describe('focus span', () => { - it('call focusSpan', () => { - focusMock.mockReset(); - const component = shallow(); - const link = component.find('a'); - link.simulate('click'); - expect(focusMock).toHaveBeenLastCalledWith('span1'); + expect(link.prop('href')).toBe('trace1-span1'); }); }); }); diff --git a/packages/jaeger-ui-components/src/url/ReferenceLink.tsx b/packages/jaeger-ui-components/src/url/ReferenceLink.tsx index 9841e9874df..a96c2b75cb9 100644 --- a/packages/jaeger-ui-components/src/url/ReferenceLink.tsx +++ b/packages/jaeger-ui-components/src/url/ReferenceLink.tsx @@ -14,48 +14,36 @@ 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 = { reference: TraceSpanReference; children: React.ReactNode; - className?: string; - focusSpan: (spanID: string) => void; - onClick?: () => void; + createFocusSpanLink: (traceId: string, spanId: string) => LinkModel; }; export default function ReferenceLink(props: ReferenceLinkProps) { - const { reference, children, className, focusSpan, ...otherProps } = props; - delete otherProps.onClick; - if (reference.span) { - return ( - focusSpan(reference.spanID)} className={className} {...otherProps}> - {children} - - ); - } + const { reference, children, createFocusSpanLink } = props; + + const link = createFocusSpanLink(reference.traceID, reference.spanID); return ( - - {(createLinkToExternalSpan) => { - if (!createLinkToExternalSpan) { - throw new Error("ExternalLinkContext does not have a value, you probably forgot to setup it's provider"); - } - - return ( - - {children} - - ); - }} - + { + event.preventDefault(); + link.onClick!(event); + } + : undefined + } + > + {children} + ); } diff --git a/packages/jaeger-ui-components/src/url/externalLinkContext.tsx b/packages/jaeger-ui-components/src/url/externalLinkContext.tsx deleted file mode 100644 index a14d47cb3a5..00000000000 --- a/packages/jaeger-ui-components/src/url/externalLinkContext.tsx +++ /dev/null @@ -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; diff --git a/public/app/features/explore/TraceView/TraceView.tsx b/public/app/features/explore/TraceView/TraceView.tsx index c0e09e3356f..199e3fd7670 100644 --- a/public/app/features/explore/TraceView/TraceView.tsx +++ b/public/app/features/explore/TraceView/TraceView.tsx @@ -23,6 +23,7 @@ import { TraceToLogsData } from 'app/core/components/TraceToLogs/TraceToLogsSett 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 { TempoQuery } from 'app/plugins/datasource/tempo/datasource'; import { StoreState } from 'app/types'; import { ExploreId } from 'app/types/explore'; @@ -99,13 +100,9 @@ export function TraceView(props: Props) { refId: props.dataFrames[0]?.refId, exploreId: props.exploreId!, datasource, + splitOpenFn: props.splitOpenFn!, }); - const createLinkToExternalSpan = (traceId: string, spanId: string) => { - const link = createFocusSpanLink(traceId, spanId); - return link.href; - }; - const traceTimeline: TTraceTimeline = useMemo( () => ({ childrenHiddenIDs, @@ -162,8 +159,6 @@ export function TraceView(props: Props) { updateNextViewRangeTime={updateNextViewRangeTime} updateViewRangeTime={updateViewRangeTime} viewRange={viewRange} - focusSpan={noop} - createLinkToExternalSpan={createLinkToExternalSpan} setSpanNameColumnWidth={setSpanNameColumnWidth} collapseAll={collapseAll} collapseOne={collapseOne} @@ -208,6 +203,7 @@ export function TraceView(props: Props) { */ function useFocusSpanLink(options: { exploreId: ExploreId; + splitOpenFn: SplitOpen; refId?: string; datasource?: DataSourceApi; }): [string | undefined, (traceId: string, spanId: string) => LinkModel] { @@ -234,7 +230,10 @@ function useFocusSpanLink(options: { internal: { datasourceUid: options.datasource?.uid!, datasourceName: options.datasource?.name!, - query: query, + query: { + ...query, + query: traceId, + }, panelsState: { trace: { 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({ link, internalLink: link.internal!, scopedVars: {}, range: {} as any, 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()), }); };