From c1b5ea3e54169c7a4bbd58bd8be2c4c192a093c0 Mon Sep 17 00:00:00 2001 From: Connor Lindsey Date: Thu, 5 May 2022 14:46:18 -0600 Subject: [PATCH] Tracing: Add trace to metrics config behind feature toggle (#46298) * Add trace to metrics behind feature flag --- .betterer.results | 5 +- .../src/types/featureToggles.gen.ts | 1 + .../ReferencesButton.test.js | 64 ------ .../TraceTimelineViewer/ReferencesButton.tsx | 66 ------ .../TraceTimelineViewer/SpanBarRow.test.js | 58 ++++-- .../src/TraceTimelineViewer/SpanBarRow.tsx | 49 ++--- .../TraceTimelineViewer/SpanDetail/index.tsx | 9 +- .../src/TraceTimelineViewer/SpanLinks.tsx | 121 +++++++++++ .../VirtualizedTraceView.tsx | 2 - .../jaeger-ui-components/src/types/links.ts | 9 +- pkg/services/featuremgmt/registry.go | 6 + pkg/services/featuremgmt/toggles_gen.go | 4 + .../TraceToLogs/TraceToLogsSettings.tsx | 2 +- .../TraceToMetrics/TraceToMetricsSettings.tsx | 80 ++++++++ .../features/explore/TraceView/TraceView.tsx | 17 +- .../explore/TraceView/createSpanLink.test.ts | 166 +++++++++++++-- .../explore/TraceView/createSpanLink.tsx | 193 +++++++++++++----- .../jaeger/components/ConfigEditor.tsx | 9 + .../tempo/configuration/ConfigEditor.tsx | 11 + .../configuration/ServiceGraphSettings.tsx | 28 +-- .../datasource/zipkin/ConfigEditor.tsx | 8 + 21 files changed, 635 insertions(+), 273 deletions(-) delete mode 100644 packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.test.js delete mode 100644 packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.tsx create mode 100644 packages/jaeger-ui-components/src/TraceTimelineViewer/SpanLinks.tsx create mode 100644 public/app/core/components/TraceToMetrics/TraceToMetricsSettings.tsx diff --git a/.betterer.results b/.betterer.results index 333c18a6cba..60dc1e11ac1 100644 --- a/.betterer.results +++ b/.betterer.results @@ -86,13 +86,10 @@ exports[`no enzyme tests`] = { "packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.test.js:1734982398": [ [14, 26, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.test.js:3807792910": [ - [14, 19, 13, "RegExp match", "2409514259"] - ], "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.test.js:1478502145": [ [14, 17, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js:3826510429": [ + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js:1451240090": [ [14, 26, 13, "RegExp match", "2409514259"] ], "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.test.js:2408389970": [ diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 53216524eb1..86ef808d005 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -60,4 +60,5 @@ export interface FeatureToggles { cloudWatchDynamicLabels?: boolean; datasourceQueryMultiStatus?: boolean; azureMonitorExperimentalUI?: boolean; + traceToMetrics?: boolean; } diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.test.js deleted file mode 100644 index bb464b2388a..00000000000 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.test.js +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2019 The Jaeger Authors. -// -// 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 { shallow } from 'enzyme'; -import React from 'react'; - -import { Tooltip } from '@grafana/ui'; - -import traceGenerator from '../demo/trace-generators'; -import transformTraceData from '../model/transform-trace-data'; -import ReferenceLink from '../url/ReferenceLink'; - -import ReferencesButton, { getStyles } from './ReferencesButton'; - -describe(ReferencesButton, () => { - const trace = transformTraceData(traceGenerator.trace({ numberOfSpans: 10 })); - const oneReference = trace.spans[1].references; - - const moreReferences = oneReference.slice(); - const externalSpanID = 'extSpan'; - - moreReferences.push( - { - refType: 'CHILD_OF', - traceID: trace.traceID, - spanID: trace.spans[2].spanID, - span: trace.spans[2], - }, - { - refType: 'CHILD_OF', - traceID: 'otherTrace', - spanID: externalSpanID, - } - ); - - const baseProps = { - focusSpan: () => {}, - }; - - it('renders single reference', () => { - const props = { ...baseProps, references: oneReference }; - const wrapper = shallow(); - const refLink = wrapper.find(ReferenceLink); - const tooltip = wrapper.find(Tooltip); - const styles = getStyles(); - - expect(refLink.length).toBe(1); - expect(refLink.prop('reference')).toBe(oneReference[0]); - expect(refLink.first().props().className).toBe(styles.MultiParent); - expect(tooltip.length).toBe(1); - expect(tooltip.prop('content')).toBe(props.tooltipText); - }); -}); diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.tsx deleted file mode 100644 index 96456f43cf7..00000000000 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.tsx +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2019 The Jaeger Authors. -// -// 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 { css } from '@emotion/css'; -import React from 'react'; - -import { Tooltip, useStyles2 } from '@grafana/ui'; - -import { TraceSpanReference } from '../types/trace'; -import ReferenceLink from '../url/ReferenceLink'; - -export const getStyles = () => { - return { - MultiParent: css` - padding: 0 5px; - & ~ & { - margin-left: 5px; - } - `, - TraceRefLink: css` - display: flex; - justify-content: space-between; - `, - NewWindowIcon: css` - margin: 0.2em 0 0; - `, - tooltip: css` - max-width: none; - `, - }; -}; - -type TReferencesButtonProps = { - references: TraceSpanReference[]; - children: React.ReactNode; - tooltipText: string; - focusSpan: (spanID: string) => void; -}; - -const ReferencesButton = (props: TReferencesButtonProps) => { - const { references, children, tooltipText, focusSpan } = props; - const styles = useStyles2(getStyles); - - // TODO: handle multiple items with some dropdown - const ref = references[0]; - return ( - - - {children} - - - ); -}; - -export default ReferencesButton; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js index 104bdb2a02a..4835da041e2 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js @@ -15,8 +15,8 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import ReferencesButton from './ReferencesButton'; import SpanBarRow from './SpanBarRow'; +import { SpanLinksMenu } from './SpanLinks'; import SpanTreeOffset from './SpanTreeOffset'; jest.mock('./SpanTreeOffset', () => { @@ -89,7 +89,7 @@ describe('', () => { const span = Object.assign(newSpan, { references: [ { - refType: 'CHILD_OF', + refType: 'FOLLOWS_FROM', traceID: 'trace1', spanID: 'span0', span: { @@ -97,7 +97,7 @@ describe('', () => { }, }, { - refType: 'CHILD_OF', + refType: 'FOLLOWS_FROM', traceID: 'otherTrace', spanID: 'span1', span: { @@ -107,13 +107,20 @@ describe('', () => { ], }); - const spanRow = shallow() + const spanRow = shallow( + ({ + traceLinks: [{ href: 'href' }, { href: 'href' }], + })} + /> + ) .dive() .dive() .dive(); - const refButton = spanRow.find(ReferencesButton); - expect(refButton.length).toEqual(1); - expect(refButton.at(0).props().tooltipText).toEqual('Contains multiple references'); + const menu = spanRow.find(SpanLinksMenu); + expect(menu.length).toEqual(1); }); it('render referenced to by single span', () => { @@ -121,7 +128,7 @@ describe('', () => { { subsidiarilyReferencedBy: [ { - refType: 'CHILD_OF', + refType: 'FOLLOWS_FROM', traceID: 'trace1', spanID: 'span0', span: { @@ -132,13 +139,21 @@ describe('', () => { }, props.span ); - const spanRow = shallow() + const spanRow = shallow( + ({ + traceLinks: [{ content: 'This span is referenced by another span', href: 'href' }], + })} + /> + ) .dive() .dive() .dive(); - const refButton = spanRow.find(ReferencesButton); - expect(refButton.length).toEqual(1); - expect(refButton.at(0).props().tooltipText).toEqual('This span is referenced by another span'); + const menu = spanRow.find(`a[href="href"]`); + expect(menu.length).toEqual(1); + expect(menu.at(0).text()).toEqual('This span is referenced by another span'); }); it('render referenced to by multiple span', () => { @@ -146,7 +161,7 @@ describe('', () => { { subsidiarilyReferencedBy: [ { - refType: 'CHILD_OF', + refType: 'FOLLOWS_FROM', traceID: 'trace1', spanID: 'span0', span: { @@ -154,7 +169,7 @@ describe('', () => { }, }, { - refType: 'CHILD_OF', + refType: 'FOLLOWS_FROM', traceID: 'trace1', spanID: 'span1', span: { @@ -165,12 +180,19 @@ describe('', () => { }, props.span ); - const spanRow = shallow() + const spanRow = shallow( + ({ + traceLinks: [{ href: 'href' }, { href: 'href' }], + })} + /> + ) .dive() .dive() .dive(); - const refButton = spanRow.find(ReferencesButton); - expect(refButton.length).toEqual(1); - expect(refButton.at(0).props().tooltipText).toEqual('This span is referenced by multiple other spans'); + const menu = spanRow.find(SpanLinksMenu); + expect(menu.length).toEqual(1); }); }); diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx index 9239a0e1f32..af6948a46f0 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx @@ -17,17 +17,17 @@ import cx from 'classnames'; import * as React from 'react'; import IoAlert from 'react-icons/lib/io/alert'; import IoArrowRightA from 'react-icons/lib/io/arrow-right-a'; -import MdFileUpload from 'react-icons/lib/md/file-upload'; import { GrafanaTheme2 } from '@grafana/data'; -import { Icon, stylesFactory, withTheme2 } from '@grafana/ui'; +import { stylesFactory, withTheme2 } from '@grafana/ui'; import { autoColor } from '../Theme'; import { SpanLinkFunc, TNil } from '../types'; +import { SpanLinks } from '../types/links'; import { TraceSpan } from '../types/trace'; -import ReferencesButton from './ReferencesButton'; import SpanBar from './SpanBar'; +import { SpanLinksMenu } from './SpanLinks'; import SpanTreeOffset from './SpanTreeOffset'; import Ticks from './Ticks'; import TimelineRow from './TimelineRow'; @@ -322,7 +322,6 @@ type SpanBarRowProps = { getViewedBounds: ViewedBoundsFunctionType; traceStartTime: number; span: TraceSpan; - focusSpan: (spanID: string) => void; hoverIndentGuideIds: Set; addHoverIndentGuideId: (spanID: string) => void; removeHoverIndentGuideId: (spanID: string) => void; @@ -370,7 +369,6 @@ export class UnthemedSpanBarRow extends React.PureComponent { getViewedBounds, traceStartTime, span, - focusSpan, hoverIndentGuideIds, addHoverIndentGuideId, removeHoverIndentGuideId, @@ -402,6 +400,14 @@ export class UnthemedSpanBarRow extends React.PureComponent { hintClassName = styles.labelRight; } + const countLinks = (links?: SpanLinks): number => { + if (!links) { + return 0; + } + + return Object.values(links).reduce((count, arr) => count + arr.length, 0); + }; + return ( { {createSpanLink && (() => { - const link = createSpanLink(span); - if (link) { + const links = createSpanLink(span); + const count = countLinks(links); + if (links && count === 1) { + const link = links.logLinks?.[0] ?? links.metricLinks?.[0] ?? links.traceLinks?.[0] ?? undefined; + if (!link) { + return null; + } + return ( { {link.content} ); + } else if (links && count > 1) { + return ; } else { return null; } })()} - - {span.references && span.references.length > 1 && ( - - - - )} - {span.subsidiarilyReferencedBy && span.subsidiarilyReferencedBy.length > 0 && ( - - - - )} - {link ? ( - + {links?.logLinks?.[0] ? ( + ) : null}
diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanLinks.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanLinks.tsx new file mode 100644 index 00000000000..27fbf1cc3fd --- /dev/null +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanLinks.tsx @@ -0,0 +1,121 @@ +import { css } from '@emotion/css'; +import React, { useState } from 'react'; + +import { useStyles2, MenuGroup, MenuItem, Icon, ContextMenu } from '@grafana/ui'; + +import { SpanLinks } from '../types/links'; + +interface SpanLinksProps { + links: SpanLinks; +} + +const renderMenuItems = (links: SpanLinks, styles: ReturnType, closeMenu: () => void) => { + return ( + <> + {!!links.logLinks?.length ? ( + + {links.logLinks.map((link, i) => ( + { + if (link.onClick) { + link.onClick(e); + } + closeMenu(); + }} + url={link.href} + className={styles.menuItem} + /> + ))} + + ) : null} + {!!links.metricLinks?.length ? ( + + {links.metricLinks.map((link, i) => ( + { + if (link.onClick) { + link.onClick(e); + } + closeMenu(); + }} + url={link.href} + className={styles.menuItem} + /> + ))} + + ) : null} + {!!links.traceLinks?.length ? ( + + {links.traceLinks.map((link, i) => ( + { + if (link.onClick) { + link.onClick(e); + } + closeMenu(); + }} + url={link.href} + className={styles.menuItem} + /> + ))} + + ) : null} + + ); +}; + +export const SpanLinksMenu = ({ links }: SpanLinksProps) => { + const styles = useStyles2(getStyles); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 }); + + const closeMenu = () => setIsMenuOpen(false); + + return ( + <> + + + {isMenuOpen ? ( + setIsMenuOpen(false)} + renderMenuItems={() => renderMenuItems(links, styles, closeMenu)} + focusOnOpen={true} + x={menuPosition.x} + y={menuPosition.y} + /> + ) : null} + + ); +}; + +const getStyles = () => { + return { + button: css` + background: transparent; + border: none; + padding: 0; + margin: 0 3px 0 0; + `, + menuItem: css` + max-width: 60ch; + overflow: hidden; + `, + }; +}; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx index bf3411015f8..bb71a941b29 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx @@ -387,7 +387,6 @@ export class UnthemedVirtualizedTraceView extends React.Component void; content: React.ReactNode; + title?: string; }; -export type SpanLinkFunc = (span: TraceSpan) => SpanLinkDef | undefined; +export type SpanLinks = { + logLinks?: SpanLinkDef[]; + traceLinks?: SpanLinkDef[]; + metricLinks?: SpanLinkDef[]; +}; + +export type SpanLinkFunc = (span: TraceSpan) => SpanLinks | undefined; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 5080805e73a..bc60b9b5ca9 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -248,5 +248,11 @@ var ( RequiresDevMode: true, FrontendOnly: true, }, + { + Name: "traceToMetrics", + Description: "Enable trace to metrics links", + State: FeatureStateAlpha, + FrontendOnly: true, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index ded1ce2e065..8d21cf68d13 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -182,4 +182,8 @@ const ( // FlagAzureMonitorExperimentalUI // Use grafana-experimental UI in Azure Monitor FlagAzureMonitorExperimentalUI = "azureMonitorExperimentalUI" + + // FlagTraceToMetrics + // Enable trace to metrics links + FlagTraceToMetrics = "traceToMetrics" ) diff --git a/public/app/core/components/TraceToLogs/TraceToLogsSettings.tsx b/public/app/core/components/TraceToLogs/TraceToLogsSettings.tsx index bfafdfee442..0dd4f62c50a 100644 --- a/public/app/core/components/TraceToLogs/TraceToLogsSettings.tsx +++ b/public/app/core/components/TraceToLogs/TraceToLogsSettings.tsx @@ -40,7 +40,7 @@ export function TraceToLogsSettings({ options, onOptionsChange }: Props) {

Trace to logs

- Trace to logs lets you navigate from a trace span to the selected data source's log. + Trace to logs lets you navigate from a trace span to the selected data source's logs.
diff --git a/public/app/core/components/TraceToMetrics/TraceToMetricsSettings.tsx b/public/app/core/components/TraceToMetrics/TraceToMetricsSettings.tsx new file mode 100644 index 00000000000..fc450315580 --- /dev/null +++ b/public/app/core/components/TraceToMetrics/TraceToMetricsSettings.tsx @@ -0,0 +1,80 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { + DataSourceJsonData, + DataSourcePluginOptionsEditorProps, + GrafanaTheme, + updateDatasourcePluginJsonDataOption, +} from '@grafana/data'; +import { DataSourcePicker } from '@grafana/runtime'; +import { Button, InlineField, InlineFieldRow, useStyles } from '@grafana/ui'; + +export interface TraceToMetricsOptions { + datasourceUid?: string; +} + +export interface TraceToMetricsData extends DataSourceJsonData { + tracesToMetrics?: TraceToMetricsOptions; +} + +interface Props extends DataSourcePluginOptionsEditorProps {} + +export function TraceToMetricsSettings({ options, onOptionsChange }: Props) { + const styles = useStyles(getStyles); + + return ( +
+

Trace to metrics

+ +
+ Trace to metrics lets you navigate from a trace span to the selected data source. +
+ + + + + updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToMetrics', { + ...options.jsonData.tracesToMetrics, + datasourceUid: ds.uid, + }) + } + /> + + {options.jsonData.tracesToMetrics?.datasourceUid ? ( + + ) : null} + +
+ ); +} + +const getStyles = (theme: GrafanaTheme) => ({ + infoText: css` + padding-bottom: ${theme.spacing.md}; + color: ${theme.colors.textSemiWeak}; + `, + row: css` + label: row; + align-items: baseline; + `, +}); diff --git a/public/app/features/explore/TraceView/TraceView.tsx b/public/app/features/explore/TraceView/TraceView.tsx index ae36bb1b8d9..c0e09e3356f 100644 --- a/public/app/features/explore/TraceView/TraceView.tsx +++ b/public/app/features/explore/TraceView/TraceView.tsx @@ -20,6 +20,7 @@ import { getTemplateSrv } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; import { Trace, TracePageHeader, TraceTimelineViewer, TTraceTimeline } from '@jaegertracing/jaeger-ui-components'; import { TraceToLogsData } from 'app/core/components/TraceToLogs/TraceToLogsSettings'; +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 { StoreState } from 'app/types'; @@ -117,12 +118,20 @@ export function TraceView(props: Props) { [childrenHiddenIDs, detailStates, hoverIndentGuideIds, spanNameColumnWidth, props.traceProp?.traceID] ); - const traceToLogsOptions = (getDatasourceSrv().getInstanceSettings(datasource?.name)?.jsonData as TraceToLogsData) - ?.tracesToLogs; + const instanceSettings = getDatasourceSrv().getInstanceSettings(datasource?.name); + const traceToLogsOptions = (instanceSettings?.jsonData as TraceToLogsData)?.tracesToLogs; + const traceToMetricsOptions = (instanceSettings?.jsonData as TraceToMetricsData)?.tracesToMetrics; + const createSpanLink = useMemo( () => - createSpanLinkFactory({ splitOpenFn: props.splitOpenFn!, traceToLogsOptions, dataFrame: props.dataFrames[0] }), - [props.splitOpenFn, traceToLogsOptions, props.dataFrames] + createSpanLinkFactory({ + splitOpenFn: props.splitOpenFn!, + traceToLogsOptions, + traceToMetricsOptions, + dataFrame: props.dataFrames[0], + createFocusSpanLink, + }), + [props.splitOpenFn, traceToLogsOptions, traceToMetricsOptions, props.dataFrames, createFocusSpanLink] ); const onSlimViewClicked = useCallback(() => setSlim(!slim), [slim]); const timeZone = useSelector((state: StoreState) => getTimeZone(state.user)); diff --git a/public/app/features/explore/TraceView/createSpanLink.test.ts b/public/app/features/explore/TraceView/createSpanLink.test.ts index e88ce728b73..89d2e05c16a 100644 --- a/public/app/features/explore/TraceView/createSpanLink.test.ts +++ b/public/app/features/explore/TraceView/createSpanLink.test.ts @@ -1,6 +1,7 @@ import { DataSourceInstanceSettings, MutableDataFrame } from '@grafana/data'; import { setDataSourceSrv, setTemplateSrv } from '@grafana/runtime'; import { TraceSpan } from '@jaegertracing/jaeger-ui-components'; +import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; import { TraceToLogsOptions } from '../../../core/components/TraceToLogs/TraceToLogsSettings'; import { LinkSrv, setLinkSrv } from '../../panel/panellinks/link_srv'; @@ -9,10 +10,13 @@ import { TemplateSrv } from '../../templating/template_srv'; import { createSpanLinkFactory } from './createSpanLink'; describe('createSpanLinkFactory', () => { - it('returns undefined if there is no data source uid', () => { + it('returns no links if there is no data source uid', () => { const splitOpenFn = jest.fn(); const createLink = createSpanLinkFactory({ splitOpenFn: splitOpenFn }); - expect(createLink).not.toBeDefined(); + const links = createLink!(createTraceSpan()); + expect(links?.logLinks).toBeUndefined(); + expect(links?.metricLinks).toBeUndefined(); + expect(links?.traceLinks).toHaveLength(0); }); describe('should return loki link', () => { @@ -30,7 +34,9 @@ describe('createSpanLinkFactory', () => { it('with default keys when tags not configured', () => { const createLink = setupSpanLinkFactory(); expect(createLink).toBeDefined(); - const linkDef = createLink!(createTraceSpan()); + const links = createLink!(createTraceSpan()); + const linkDef = links?.logLinks?.[0]; + expect(linkDef).toBeDefined(); expect(linkDef!.href).toBe( `/explore?left=${encodeURIComponent( '{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{cluster=\\"cluster1\\", hostname=\\"hostname1\\"}","refId":""}],"panelsState":{}}' @@ -43,7 +49,7 @@ describe('createSpanLinkFactory', () => { tags: ['ip', 'newTag'], }); expect(createLink).toBeDefined(); - const linkDef = createLink!( + const links = createLink!( createTraceSpan({ process: { serviceName: 'service', @@ -54,6 +60,8 @@ describe('createSpanLinkFactory', () => { }, }) ); + const linkDef = links?.logLinks?.[0]; + expect(linkDef).toBeDefined(); expect(linkDef!.href).toBe( `/explore?left=${encodeURIComponent( '{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\"}","refId":""}],"panelsState":{}}' @@ -66,7 +74,7 @@ describe('createSpanLinkFactory', () => { tags: ['ip', 'host'], }); expect(createLink).toBeDefined(); - const linkDef = createLink!( + const links = createLink!( createTraceSpan({ process: { serviceName: 'service', @@ -77,6 +85,8 @@ describe('createSpanLinkFactory', () => { }, }) ); + const linkDef = links?.logLinks?.[0]; + expect(linkDef).toBeDefined(); expect(linkDef!.href).toBe( `/explore?left=${encodeURIComponent( '{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\", host=\\"host\\"}","refId":""}],"panelsState":{}}' @@ -90,7 +100,7 @@ describe('createSpanLinkFactory', () => { spanEndTimeShift: '1m', }); expect(createLink).toBeDefined(); - const linkDef = createLink!( + const links = createLink!( createTraceSpan({ process: { serviceName: 'service', @@ -101,6 +111,8 @@ describe('createSpanLinkFactory', () => { }, }) ); + const linkDef = links?.logLinks?.[0]; + expect(linkDef).toBeDefined(); expect(linkDef!.href).toBe( `/explore?left=${encodeURIComponent( '{"range":{"from":"2020-10-14T01:01:00.000Z","to":"2020-10-14T01:01:01.000Z"},"datasource":"loki1","queries":[{"expr":"{hostname=\\"hostname1\\"}","refId":""}],"panelsState":{}}' @@ -114,8 +126,10 @@ describe('createSpanLinkFactory', () => { filterByTraceID: true, }); expect(createLink).toBeDefined(); - const linkDef = createLink!(createTraceSpan()); + const links = createLink!(createTraceSpan()); + const linkDef = links?.logLinks?.[0]; + expect(linkDef).toBeDefined(); expect(linkDef!.href).toBe( `/explore?left=${encodeURIComponent( '{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{cluster=\\"cluster1\\", hostname=\\"hostname1\\"} |=\\"7946b05c2e2e4e5a\\" |=\\"6605c7b08e715d6c\\"","refId":""}],"panelsState":{}}' @@ -139,8 +153,10 @@ describe('createSpanLinkFactory', () => { }), }); expect(createLink).toBeDefined(); - const linkDef = createLink!(createTraceSpan()); + const links = createLink!(createTraceSpan()); + const linkDef = links?.logLinks?.[0]; + expect(linkDef).toBeDefined(); expect(linkDef!.href).toBe('testSpanId'); }); @@ -153,7 +169,7 @@ describe('createSpanLinkFactory', () => { ], }); expect(createLink).toBeDefined(); - const linkDef = createLink!( + const links = createLink!( createTraceSpan({ process: { serviceName: 'service', @@ -164,6 +180,9 @@ describe('createSpanLinkFactory', () => { }, }) ); + + const linkDef = links?.logLinks?.[0]; + expect(linkDef).toBeDefined(); expect(linkDef!.href).toBe( `/explore?left=${encodeURIComponent( '{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{service=\\"serviceName\\", pod=\\"podName\\"}","refId":""}],"panelsState":{}}' @@ -180,7 +199,7 @@ describe('createSpanLinkFactory', () => { ], }); expect(createLink).toBeDefined(); - const linkDef = createLink!( + const links = createLink!( createTraceSpan({ process: { serviceName: 'service', @@ -191,6 +210,9 @@ describe('createSpanLinkFactory', () => { }, }) ); + + const linkDef = links?.logLinks?.[0]; + expect(linkDef).toBeDefined(); expect(linkDef!.href).toBe( `/explore?left=${encodeURIComponent( '{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{service.name=\\"serviceName\\", pod=\\"podName\\"}","refId":""}],"panelsState":{}}' @@ -203,7 +225,7 @@ describe('createSpanLinkFactory', () => { tags: [], }); expect(createLink).toBeDefined(); - const linkDef = createLink!( + const links = createLink!( createTraceSpan({ process: { serviceName: 'service', @@ -214,7 +236,7 @@ describe('createSpanLinkFactory', () => { }, }) ); - expect(linkDef).toBeUndefined(); + expect(links?.logLinks).toBeUndefined(); }); }); @@ -236,7 +258,10 @@ describe('createSpanLinkFactory', () => { const createLink = setupSpanLinkFactory({ datasourceUid: splunkUID, }); - const linkDef = createLink!(createTraceSpan()); + const links = createLink!(createTraceSpan()); + + const linkDef = links?.logLinks?.[0]; + expect(linkDef).toBeDefined(); expect(linkDef!.href).toContain(`${encodeURIComponent('datasource":"Splunk 8","queries":[{"query"')}`); expect(linkDef!.href).not.toContain(`${encodeURIComponent('datasource":"Splunk 8","queries":[{"expr"')}`); }); @@ -245,7 +270,10 @@ describe('createSpanLinkFactory', () => { const createLink = setupSpanLinkFactory({ datasourceUid: splunkUID, }); - const linkDef = createLink!(createTraceSpan()); + const links = createLink!(createTraceSpan()); + + const linkDef = links?.logLinks?.[0]; + expect(linkDef).toBeDefined(); expect(linkDef!.href).toContain( `${encodeURIComponent('{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"}')}` ); @@ -262,8 +290,10 @@ describe('createSpanLinkFactory', () => { }); expect(createLink).toBeDefined(); - const linkDef = createLink!(createTraceSpan()); + const links = createLink!(createTraceSpan()); + const linkDef = links?.logLinks?.[0]; + expect(linkDef).toBeDefined(); expect(linkDef!.href).toBe( `/explore?left=${encodeURIComponent( '{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"Splunk 8","queries":[{"query":"cluster=\\"cluster1\\" hostname=\\"hostname1\\" \\"7946b05c2e2e4e5a\\" \\"6605c7b08e715d6c\\"","refId":""}],"panelsState":{}}' @@ -276,7 +306,7 @@ describe('createSpanLinkFactory', () => { tags: ['ip'], }); expect(createLink).toBeDefined(); - const linkDef = createLink!( + const links = createLink!( createTraceSpan({ process: { serviceName: 'service', @@ -285,6 +315,8 @@ describe('createSpanLinkFactory', () => { }) ); + const linkDef = links?.logLinks?.[0]; + expect(linkDef).toBeDefined(); expect(linkDef!.href).toBe( `/explore?left=${encodeURIComponent( '{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"Splunk 8","queries":[{"query":"ip=\\"192.168.0.1\\"","refId":""}],"panelsState":{}}' @@ -297,7 +329,7 @@ describe('createSpanLinkFactory', () => { tags: ['ip', 'hostname'], }); expect(createLink).toBeDefined(); - const linkDef = createLink!( + const links = createLink!( createTraceSpan({ process: { serviceName: 'service', @@ -309,6 +341,8 @@ describe('createSpanLinkFactory', () => { }) ); + const linkDef = links?.logLinks?.[0]; + expect(linkDef).toBeDefined(); expect(linkDef!.href).toBe( `/explore?left=${encodeURIComponent( '{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"Splunk 8","queries":[{"query":"hostname=\\"hostname1\\" ip=\\"192.168.0.1\\"","refId":""}],"panelsState":{}}' @@ -325,7 +359,7 @@ describe('createSpanLinkFactory', () => { ], }); expect(createLink).toBeDefined(); - const linkDef = createLink!( + const links = createLink!( createTraceSpan({ process: { serviceName: 'service', @@ -336,6 +370,9 @@ describe('createSpanLinkFactory', () => { }, }) ); + + const linkDef = links?.logLinks?.[0]; + expect(linkDef).toBeDefined(); expect(linkDef!.href).toBe( `/explore?left=${encodeURIComponent( '{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"Splunk 8","queries":[{"query":"service=\\"serviceName\\" pod=\\"podName\\"","refId":""}],"panelsState":{}}' @@ -343,6 +380,94 @@ describe('createSpanLinkFactory', () => { ); }); }); + + describe('should return metric link', () => { + beforeAll(() => { + setDataSourceSrv({ + getInstanceSettings(uid: string): DataSourceInstanceSettings | undefined { + return { uid: 'prom1', name: 'prom1', type: 'prometheus' } as any; + }, + } as any); + + setLinkSrv(new LinkSrv()); + setTemplateSrv(new TemplateSrv()); + }); + + it('returns query with span', () => { + const splitOpenFn = jest.fn(); + const createLink = createSpanLinkFactory({ + splitOpenFn, + traceToMetricsOptions: { + datasourceUid: 'prom1', + }, + }); + expect(createLink).toBeDefined(); + + const links = createLink!(createTraceSpan()); + const linkDef = links?.metricLinks?.[0]; + + expect(linkDef).toBeDefined(); + expect(linkDef!.href).toBe( + `/explore?left=${encodeURIComponent( + '{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"prom1","queries":[{"expr":"histogram_quantile(0.5, sum(rate(tempo_spanmetrics_latency_bucket{operation=\\"operation\\"}[5m])) by (le))","refId":""}],"panelsState":{}}' + )}` + ); + }); + }); + + describe('should return span links', () => { + beforeAll(() => { + setDataSourceSrv(new DatasourceSrv()); + setLinkSrv(new LinkSrv()); + setTemplateSrv(new TemplateSrv()); + }); + + it('ignores parent span link', () => { + const createLink = setupSpanLinkFactory(); + expect(createLink).toBeDefined(); + const links = createLink!( + createTraceSpan({ references: [{ refType: 'CHILD_OF', spanID: 'parent', traceID: 'traceID' }] }) + ); + + const traceLinks = links?.traceLinks; + expect(traceLinks).toBeDefined(); + expect(traceLinks).toHaveLength(0); + }); + + it('returns links for references and subsidiarilyReferencedBy references', () => { + const createLink = setupSpanLinkFactory(); + expect(createLink).toBeDefined(); + const links = createLink!( + createTraceSpan({ + references: [ + { + refType: 'FOLLOWS_FROM', + spanID: 'span1', + traceID: 'traceID', + span: { operationName: 'SpanName' } as any, + }, + ], + subsidiarilyReferencedBy: [{ refType: 'FOLLOWS_FROM', spanID: 'span3', traceID: 'traceID2' }], + }) + ); + + const traceLinks = links?.traceLinks; + expect(traceLinks).toBeDefined(); + expect(traceLinks).toHaveLength(2); + expect(traceLinks![0]).toEqual( + expect.objectContaining({ + href: 'traceID-span1', + title: 'SpanName', + }) + ); + expect(traceLinks![1]).toEqual( + expect.objectContaining({ + href: 'traceID2-span3', + title: 'View linked span', + }) + ); + }); + }); }); function setupSpanLinkFactory(options: Partial = {}, datasourceUid = 'lokiUid') { @@ -353,6 +478,11 @@ function setupSpanLinkFactory(options: Partial = {}, datasou datasourceUid, ...options, }, + createFocusSpanLink: (traceId, spanId) => { + return { + href: `${traceId}-${spanId}`, + } as any; + }, }); } diff --git a/public/app/features/explore/TraceView/createSpanLink.tsx b/public/app/features/explore/TraceView/createSpanLink.tsx index a036eeb05d8..9eb6e5f075a 100644 --- a/public/app/features/explore/TraceView/createSpanLink.tsx +++ b/public/app/features/explore/TraceView/createSpanLink.tsx @@ -1,3 +1,4 @@ +import { SpanLinks } from '@jaegertracing/jaeger-ui-components/src/types/links'; import React from 'react'; import { @@ -5,6 +6,7 @@ import { DataLink, DataQuery, DataSourceInstanceSettings, + DataSourceJsonData, dateTime, Field, KeyValue, @@ -16,9 +18,11 @@ import { } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; import { Icon } from '@grafana/ui'; -import { SpanLinkDef, SpanLinkFunc, TraceSpan } from '@jaegertracing/jaeger-ui-components'; +import { SpanLinkFunc, TraceSpan } from '@jaegertracing/jaeger-ui-components'; import { TraceToLogsOptions } from 'app/core/components/TraceToLogs/TraceToLogsSettings'; +import { TraceToMetricsOptions } from 'app/core/components/TraceToMetrics/TraceToMetricsSettings'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { PromQuery } from 'app/plugins/datasource/prometheus/types'; import { LokiQuery } from '../../../plugins/datasource/loki/types'; import { getFieldLinksForExplore } from '../utils/links'; @@ -31,18 +35,22 @@ import { getFieldLinksForExplore } from '../utils/links'; export function createSpanLinkFactory({ splitOpenFn, traceToLogsOptions, + traceToMetricsOptions, dataFrame, + createFocusSpanLink, }: { splitOpenFn: SplitOpen; traceToLogsOptions?: TraceToLogsOptions; + traceToMetricsOptions?: TraceToMetricsOptions; dataFrame?: DataFrame; + createFocusSpanLink?: (traceId: string, spanId: string) => LinkModel; }): SpanLinkFunc | undefined { if (!dataFrame || dataFrame.fields.length === 1 || !dataFrame.fields.some((f) => Boolean(f.config.links?.length))) { // if the dataframe contains just a single blob of data (legacy format) or does not have any links configured, // let's try to use the old legacy path. - return legacyCreateSpanLinkFactory(splitOpenFn, traceToLogsOptions); + return legacyCreateSpanLinkFactory(splitOpenFn, traceToLogsOptions, traceToMetricsOptions, createFocusSpanLink); } else { - return function SpanLink(span: TraceSpan): SpanLinkDef | undefined { + return function SpanLink(span: TraceSpan): SpanLinks | undefined { // We should be here only if there are some links in the dataframe const field = dataFrame.fields.find((f) => Boolean(f.config.links?.length))!; try { @@ -55,9 +63,13 @@ export function createSpanLinkFactory({ }); return { - href: links[0].href, - onClick: links[0].onClick, - content: , + logLinks: [ + { + href: links[0].href, + onClick: links[0].onClick, + content: , + }, + ], }; } catch (error) { // It's fairly easy to crash here for example if data source defines wrong interpolation in the data link @@ -68,65 +80,144 @@ export function createSpanLinkFactory({ } } -function legacyCreateSpanLinkFactory(splitOpenFn: SplitOpen, traceToLogsOptions?: TraceToLogsOptions) { - // We should return if dataSourceUid is undefined otherwise getInstanceSettings would return testDataSource. - if (!traceToLogsOptions?.datasourceUid) { - return undefined; +function legacyCreateSpanLinkFactory( + splitOpenFn: SplitOpen, + traceToLogsOptions?: TraceToLogsOptions, + traceToMetricsOptions?: TraceToMetricsOptions, + createFocusSpanLink?: (traceId: string, spanId: string) => LinkModel +) { + let logsDataSourceSettings: DataSourceInstanceSettings | undefined; + const isSplunkDS = logsDataSourceSettings?.type === 'grafana-splunk-datasource'; + if (traceToLogsOptions?.datasourceUid) { + logsDataSourceSettings = getDatasourceSrv().getInstanceSettings(traceToLogsOptions.datasourceUid); } - const dataSourceSettings = getDatasourceSrv().getInstanceSettings(traceToLogsOptions.datasourceUid); - const isSplunkDS = dataSourceSettings?.type === 'grafana-splunk-datasource'; - - if (!dataSourceSettings) { - return undefined; + let metricsDataSourceSettings: DataSourceInstanceSettings | undefined; + if (traceToMetricsOptions?.datasourceUid) { + metricsDataSourceSettings = getDatasourceSrv().getInstanceSettings(traceToMetricsOptions.datasourceUid); } - return function SpanLink(span: TraceSpan): SpanLinkDef | undefined { + return function SpanLink(span: TraceSpan): SpanLinks { + const links: SpanLinks = { traceLinks: [] }; // This is reusing existing code from derived fields which may not be ideal match so some data is a bit faked at // the moment. Issue is that the trace itself isn't clearly mapped to dataFrame (right now it's just a json blob // inside a single field) so the dataLinks as config of that dataFrame abstraction breaks down a bit and we do // it manually here instead of leaving it for the data source to supply the config. let dataLink: DataLink | undefined = {} as DataLink | undefined; - let link: LinkModel; - switch (dataSourceSettings?.type) { - case 'loki': - dataLink = getLinkForLoki(span, traceToLogsOptions, dataSourceSettings); - if (!dataLink) { - return undefined; - } - break; - case 'grafana-splunk-datasource': - dataLink = getLinkForSplunk(span, traceToLogsOptions, dataSourceSettings); - break; - default: - return undefined; + // Get logs link + if (logsDataSourceSettings && traceToLogsOptions) { + switch (logsDataSourceSettings?.type) { + case 'loki': + dataLink = getLinkForLoki(span, traceToLogsOptions, logsDataSourceSettings); + break; + case 'grafana-splunk-datasource': + dataLink = getLinkForSplunk(span, traceToLogsOptions, logsDataSourceSettings); + break; + } + + if (dataLink) { + const link = mapInternalLinkToExplore({ + link: dataLink, + internalLink: dataLink.internal!, + scopedVars: {}, + range: getTimeRangeFromSpan( + span, + { + startMs: traceToLogsOptions.spanStartTimeShift + ? rangeUtil.intervalToMs(traceToLogsOptions.spanStartTimeShift) + : 0, + endMs: traceToLogsOptions.spanEndTimeShift + ? rangeUtil.intervalToMs(traceToLogsOptions.spanEndTimeShift) + : 0, + }, + isSplunkDS + ), + field: {} as Field, + onClickFn: splitOpenFn, + replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()), + }); + + links.logLinks = [ + { + href: link.href, + onClick: link.onClick, + content: , + }, + ]; + } } - link = mapInternalLinkToExplore({ - link: dataLink, - internalLink: dataLink?.internal!, - scopedVars: {}, - range: getTimeRangeFromSpan( - span, - { - startMs: traceToLogsOptions.spanStartTimeShift - ? rangeUtil.intervalToMs(traceToLogsOptions.spanStartTimeShift) - : 0, - endMs: traceToLogsOptions.spanEndTimeShift ? rangeUtil.intervalToMs(traceToLogsOptions.spanEndTimeShift) : 0, + // Get metrics links + if (metricsDataSourceSettings && traceToMetricsOptions) { + const dataLink: DataLink = { + title: metricsDataSourceSettings.name, + url: '', + internal: { + datasourceUid: metricsDataSourceSettings.uid, + datasourceName: metricsDataSourceSettings.name, + query: { + expr: `histogram_quantile(0.5, sum(rate(tempo_spanmetrics_latency_bucket{operation="${span.operationName}"}[5m])) by (le))`, + refId: '', + }, }, - isSplunkDS - ), - field: {} as Field, - onClickFn: splitOpenFn, - replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()), - }); + }; - return { - href: link.href, - onClick: link.onClick, - content: , - }; + const link = mapInternalLinkToExplore({ + link: dataLink, + internalLink: dataLink.internal!, + scopedVars: {}, + range: getTimeRangeFromSpan(span, { + startMs: 0, + endMs: 0, + }), + field: {} as Field, + onClickFn: splitOpenFn, + replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()), + }); + + links.metricLinks = [ + { + href: link.href, + onClick: link.onClick, + content: , + }, + ]; + } + + // Get trace links + if (span.references && createFocusSpanLink) { + for (const reference of span.references) { + // Ignore parent-child links + if (reference.refType === 'CHILD_OF') { + continue; + } + + const link = createFocusSpanLink(reference.traceID, reference.spanID); + + links.traceLinks!.push({ + href: link.href, + title: reference.span ? reference.span.operationName : 'View linked span', + content: , + onClick: link.onClick, + }); + } + } + + if (span.subsidiarilyReferencedBy && createFocusSpanLink) { + for (const reference of span.subsidiarilyReferencedBy) { + const link = createFocusSpanLink(reference.traceID, reference.spanID); + + links.traceLinks!.push({ + href: link.href, + title: reference.span ? reference.span.operationName : 'View linked span', + content: , + onClick: link.onClick, + }); + } + } + + return links; }; } diff --git a/public/app/plugins/datasource/jaeger/components/ConfigEditor.tsx b/public/app/plugins/datasource/jaeger/components/ConfigEditor.tsx index 114a7ef8a3e..46fcb204394 100644 --- a/public/app/plugins/datasource/jaeger/components/ConfigEditor.tsx +++ b/public/app/plugins/datasource/jaeger/components/ConfigEditor.tsx @@ -1,9 +1,11 @@ import React from 'react'; import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { DataSourceHttpSettings } from '@grafana/ui'; import { NodeGraphSettings } from 'app/core/components/NodeGraphSettings'; import { TraceToLogsSettings } from 'app/core/components/TraceToLogs/TraceToLogsSettings'; +import { TraceToMetricsSettings } from 'app/core/components/TraceToMetrics/TraceToMetricsSettings'; export type Props = DataSourcePluginOptionsEditorProps; @@ -20,6 +22,13 @@ export const ConfigEditor: React.FC = ({ options, onOptionsChange }) => {
+ + {config.featureToggles.traceToMetrics ? ( +
+ +
+ ) : null} +
diff --git a/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx b/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx index fc21868589b..618c0bc28a3 100644 --- a/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx +++ b/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx @@ -5,6 +5,7 @@ import { config } from '@grafana/runtime'; import { DataSourceHttpSettings } from '@grafana/ui'; import { NodeGraphSettings } from 'app/core/components/NodeGraphSettings'; import { TraceToLogsSettings } from 'app/core/components/TraceToLogs/TraceToLogsSettings'; +import { TraceToMetricsSettings } from 'app/core/components/TraceToMetrics/TraceToMetricsSettings'; import { LokiSearchSettings } from './LokiSearchSettings'; import { SearchSettings } from './SearchSettings'; @@ -25,19 +26,29 @@ export const ConfigEditor: React.FC = ({ options, onOptionsChange }) => {
+ + {config.featureToggles.traceToMetrics ? ( +
+ +
+ ) : null} + {config.featureToggles.tempoServiceGraph && (
)} + {config.featureToggles.tempoSearch && (
)} +
+
diff --git a/public/app/plugins/datasource/tempo/configuration/ServiceGraphSettings.tsx b/public/app/plugins/datasource/tempo/configuration/ServiceGraphSettings.tsx index 8e5712ef9f7..b5c40fc8464 100644 --- a/public/app/plugins/datasource/tempo/configuration/ServiceGraphSettings.tsx +++ b/public/app/plugins/datasource/tempo/configuration/ServiceGraphSettings.tsx @@ -39,19 +39,21 @@ export function ServiceGraphSettings({ options, onOptionsChange }: Props) { } /> - + {options.jsonData.serviceMap?.datasourceUid ? ( + + ) : null}
); diff --git a/public/app/plugins/datasource/zipkin/ConfigEditor.tsx b/public/app/plugins/datasource/zipkin/ConfigEditor.tsx index 267dabcf9fc..fc876e5a91e 100644 --- a/public/app/plugins/datasource/zipkin/ConfigEditor.tsx +++ b/public/app/plugins/datasource/zipkin/ConfigEditor.tsx @@ -1,9 +1,11 @@ import React from 'react'; import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { DataSourceHttpSettings } from '@grafana/ui'; import { NodeGraphSettings } from 'app/core/components/NodeGraphSettings'; import { TraceToLogsSettings } from 'app/core/components/TraceToLogs/TraceToLogsSettings'; +import { TraceToMetricsSettings } from 'app/core/components/TraceToMetrics/TraceToMetricsSettings'; export type Props = DataSourcePluginOptionsEditorProps; @@ -21,6 +23,12 @@ export const ConfigEditor: React.FC = ({ options, onOptionsChange }) => { + {config.featureToggles.traceToMetrics ? ( +
+ +
+ ) : null} +