Tracing: Add trace to metrics config behind feature toggle (#46298)
* Add trace to metrics behind feature flag
This commit is contained in:
@@ -60,4 +60,5 @@ export interface FeatureToggles {
|
||||
cloudWatchDynamicLabels?: boolean;
|
||||
datasourceQueryMultiStatus?: boolean;
|
||||
azureMonitorExperimentalUI?: boolean;
|
||||
traceToMetrics?: boolean;
|
||||
}
|
||||
|
||||
@@ -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(<ReferencesButton {...props} />);
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -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 (
|
||||
<Tooltip content={tooltipText}>
|
||||
<ReferenceLink reference={ref} focusSpan={focusSpan} className={styles.MultiParent}>
|
||||
{children}
|
||||
</ReferenceLink>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReferencesButton;
|
||||
@@ -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('<SpanBarRow>', () => {
|
||||
const span = Object.assign(newSpan, {
|
||||
references: [
|
||||
{
|
||||
refType: 'CHILD_OF',
|
||||
refType: 'FOLLOWS_FROM',
|
||||
traceID: 'trace1',
|
||||
spanID: 'span0',
|
||||
span: {
|
||||
@@ -97,7 +97,7 @@ describe('<SpanBarRow>', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
refType: 'CHILD_OF',
|
||||
refType: 'FOLLOWS_FROM',
|
||||
traceID: 'otherTrace',
|
||||
spanID: 'span1',
|
||||
span: {
|
||||
@@ -107,13 +107,20 @@ describe('<SpanBarRow>', () => {
|
||||
],
|
||||
});
|
||||
|
||||
const spanRow = shallow(<SpanBarRow {...props} span={span} />)
|
||||
const spanRow = shallow(
|
||||
<SpanBarRow
|
||||
{...props}
|
||||
span={span}
|
||||
createSpanLink={() => ({
|
||||
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('<SpanBarRow>', () => {
|
||||
{
|
||||
subsidiarilyReferencedBy: [
|
||||
{
|
||||
refType: 'CHILD_OF',
|
||||
refType: 'FOLLOWS_FROM',
|
||||
traceID: 'trace1',
|
||||
spanID: 'span0',
|
||||
span: {
|
||||
@@ -132,13 +139,21 @@ describe('<SpanBarRow>', () => {
|
||||
},
|
||||
props.span
|
||||
);
|
||||
const spanRow = shallow(<SpanBarRow {...props} span={span} />)
|
||||
const spanRow = shallow(
|
||||
<SpanBarRow
|
||||
{...props}
|
||||
span={span}
|
||||
createSpanLink={() => ({
|
||||
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('<SpanBarRow>', () => {
|
||||
{
|
||||
subsidiarilyReferencedBy: [
|
||||
{
|
||||
refType: 'CHILD_OF',
|
||||
refType: 'FOLLOWS_FROM',
|
||||
traceID: 'trace1',
|
||||
spanID: 'span0',
|
||||
span: {
|
||||
@@ -154,7 +169,7 @@ describe('<SpanBarRow>', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
refType: 'CHILD_OF',
|
||||
refType: 'FOLLOWS_FROM',
|
||||
traceID: 'trace1',
|
||||
spanID: 'span1',
|
||||
span: {
|
||||
@@ -165,12 +180,19 @@ describe('<SpanBarRow>', () => {
|
||||
},
|
||||
props.span
|
||||
);
|
||||
const spanRow = shallow(<SpanBarRow {...props} span={span} />)
|
||||
const spanRow = shallow(
|
||||
<SpanBarRow
|
||||
{...props}
|
||||
span={span}
|
||||
createSpanLink={() => ({
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<string>;
|
||||
addHoverIndentGuideId: (spanID: string) => void;
|
||||
removeHoverIndentGuideId: (spanID: string) => void;
|
||||
@@ -370,7 +369,6 @@ export class UnthemedSpanBarRow extends React.PureComponent<SpanBarRowProps> {
|
||||
getViewedBounds,
|
||||
traceStartTime,
|
||||
span,
|
||||
focusSpan,
|
||||
hoverIndentGuideIds,
|
||||
addHoverIndentGuideId,
|
||||
removeHoverIndentGuideId,
|
||||
@@ -402,6 +400,14 @@ export class UnthemedSpanBarRow extends React.PureComponent<SpanBarRowProps> {
|
||||
hintClassName = styles.labelRight;
|
||||
}
|
||||
|
||||
const countLinks = (links?: SpanLinks): number => {
|
||||
if (!links) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Object.values(links).reduce((count, arr) => count + arr.length, 0);
|
||||
};
|
||||
|
||||
return (
|
||||
<TimelineRow
|
||||
className={cx(
|
||||
@@ -476,8 +482,14 @@ export class UnthemedSpanBarRow extends React.PureComponent<SpanBarRowProps> {
|
||||
</a>
|
||||
{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 (
|
||||
<a
|
||||
href={link.href}
|
||||
@@ -499,31 +511,12 @@ export class UnthemedSpanBarRow extends React.PureComponent<SpanBarRowProps> {
|
||||
{link.content}
|
||||
</a>
|
||||
);
|
||||
} else if (links && count > 1) {
|
||||
return <SpanLinksMenu links={links} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})()}
|
||||
|
||||
{span.references && span.references.length > 1 && (
|
||||
<ReferencesButton
|
||||
references={span.references}
|
||||
tooltipText="Contains multiple references"
|
||||
focusSpan={focusSpan}
|
||||
>
|
||||
<Icon name="link" />
|
||||
</ReferencesButton>
|
||||
)}
|
||||
{span.subsidiarilyReferencedBy && span.subsidiarilyReferencedBy.length > 0 && (
|
||||
<ReferencesButton
|
||||
references={span.subsidiarilyReferencedBy}
|
||||
tooltipText={`This span is referenced by ${
|
||||
span.subsidiarilyReferencedBy.length === 1 ? 'another span' : 'multiple other spans'
|
||||
}`}
|
||||
focusSpan={focusSpan}
|
||||
>
|
||||
<MdFileUpload />
|
||||
</ReferencesButton>
|
||||
)}
|
||||
</div>
|
||||
</TimelineRow.Cell>
|
||||
<TimelineRow.Cell
|
||||
|
||||
@@ -190,7 +190,7 @@ export default function SpanDetail(props: SpanDetailProps) {
|
||||
: []),
|
||||
];
|
||||
const styles = useStyles2(getStyles);
|
||||
const link = createSpanLink?.(span);
|
||||
const links = createSpanLink?.(span);
|
||||
const focusSpanLink = createFocusSpanLink(traceID, spanID);
|
||||
|
||||
return (
|
||||
@@ -201,8 +201,11 @@ export default function SpanDetail(props: SpanDetailProps) {
|
||||
<LabeledList className={ubTxRightAlign} divider={true} items={overviewItems} />
|
||||
</div>
|
||||
</div>
|
||||
{link ? (
|
||||
<DataLinkButton link={{ ...link, title: 'Logs for this span' } as any} buttonProps={{ icon: 'gf-logs' }} />
|
||||
{links?.logLinks?.[0] ? (
|
||||
<DataLinkButton
|
||||
link={{ ...links?.logLinks?.[0], title: 'Logs for this span' } as any}
|
||||
buttonProps={{ icon: 'gf-logs' }}
|
||||
/>
|
||||
) : null}
|
||||
<Divider className={ubMy1} type={'horizontal'} />
|
||||
<div>
|
||||
|
||||
@@ -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<typeof getStyles>, closeMenu: () => void) => {
|
||||
return (
|
||||
<>
|
||||
{!!links.logLinks?.length ? (
|
||||
<MenuGroup label="Logs">
|
||||
{links.logLinks.map((link, i) => (
|
||||
<MenuItem
|
||||
key={i}
|
||||
label="Logs for this span"
|
||||
onClick={(e) => {
|
||||
if (link.onClick) {
|
||||
link.onClick(e);
|
||||
}
|
||||
closeMenu();
|
||||
}}
|
||||
url={link.href}
|
||||
className={styles.menuItem}
|
||||
/>
|
||||
))}
|
||||
</MenuGroup>
|
||||
) : null}
|
||||
{!!links.metricLinks?.length ? (
|
||||
<MenuGroup label="Metrics">
|
||||
{links.metricLinks.map((link, i) => (
|
||||
<MenuItem
|
||||
key={i}
|
||||
label="Metrics for this span"
|
||||
onClick={(e) => {
|
||||
if (link.onClick) {
|
||||
link.onClick(e);
|
||||
}
|
||||
closeMenu();
|
||||
}}
|
||||
url={link.href}
|
||||
className={styles.menuItem}
|
||||
/>
|
||||
))}
|
||||
</MenuGroup>
|
||||
) : null}
|
||||
{!!links.traceLinks?.length ? (
|
||||
<MenuGroup label="Traces">
|
||||
{links.traceLinks.map((link, i) => (
|
||||
<MenuItem
|
||||
key={i}
|
||||
label={link.title ?? 'View linked span'}
|
||||
onClick={(e) => {
|
||||
if (link.onClick) {
|
||||
link.onClick(e);
|
||||
}
|
||||
closeMenu();
|
||||
}}
|
||||
url={link.href}
|
||||
className={styles.menuItem}
|
||||
/>
|
||||
))}
|
||||
</MenuGroup>
|
||||
) : 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 (
|
||||
<>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
setIsMenuOpen(true);
|
||||
setMenuPosition({
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
});
|
||||
}}
|
||||
className={styles.button}
|
||||
>
|
||||
<Icon name="link" className={styles.button} />
|
||||
</button>
|
||||
|
||||
{isMenuOpen ? (
|
||||
<ContextMenu
|
||||
onClose={() => 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;
|
||||
`,
|
||||
};
|
||||
};
|
||||
@@ -387,7 +387,6 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
findMatchesIDs,
|
||||
spanNameColumnWidth,
|
||||
trace,
|
||||
focusSpan,
|
||||
hoverIndentGuideIds,
|
||||
addHoverIndentGuideId,
|
||||
removeHoverIndentGuideId,
|
||||
@@ -455,7 +454,6 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
getViewedBounds={this.getViewedBounds()}
|
||||
traceStartTime={trace.startTime}
|
||||
span={span}
|
||||
focusSpan={focusSpan}
|
||||
hoverIndentGuideIds={hoverIndentGuideIds}
|
||||
addHoverIndentGuideId={addHoverIndentGuideId}
|
||||
removeHoverIndentGuideId={removeHoverIndentGuideId}
|
||||
|
||||
@@ -6,6 +6,13 @@ export type SpanLinkDef = {
|
||||
href: string;
|
||||
onClick?: (event: any) => 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;
|
||||
|
||||
Reference in New Issue
Block a user