Traces: Span bar label (#50931)

* Add SpanBarSettings

* Add SpanBarSettings to Jaeger

* Add SpanBarSettings to Zipkin

* Updated title

* Add dropdown to select identifer and make duration a default option

* Show duration by default

* Add option to hide

* Move identifers into constants

* Add process

* Update text

* Update placeholders

* Text/meta data updates

* Added tests

* Added docs

* Update find

* Merge tag and prcoess options

* Update docs

* Updated tests

* Update betterer results and trace view to match

* Updated docs
This commit is contained in:
Joey Tawadrous
2022-07-06 08:14:03 +01:00
committed by GitHub
parent 21591be469
commit 01130f22b7
18 changed files with 8611 additions and 8 deletions
@@ -16,6 +16,8 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { NONE, DURATION, TAG } from '../settings/SpanBarSettings';
import SpanBarRow from './SpanBarRow';
describe('<SpanBarRow>', () => {
@@ -43,11 +45,10 @@ describe('<SpanBarRow>', () => {
showErrorIcon: false,
getViewedBounds: () => ({ start: 0, end: 1 }),
span: {
duration: 'test-duration',
duration: 9000,
hasChildren: true,
process: {
serviceName: 'service-name',
tags: [],
},
spanID,
logs: [],
@@ -181,4 +182,89 @@ describe('<SpanBarRow>', () => {
);
expect(screen.getAllByTestId('SpanLinksMenu')).toHaveLength(1);
});
describe('render span bar label', () => {
it('with default value', () => {
render(<SpanBarRow {...props} />);
expect(screen.getByText('(9ms)')).toBeInTheDocument();
});
it('with none value', () => {
const testProps = Object.assign(
{
spanBarOptions: {
type: NONE,
},
},
props
);
render(<SpanBarRow {...testProps} />);
expect(screen.queryByText('(9ms)')).not.toBeInTheDocument();
});
it('with duration value', () => {
const testProps = Object.assign(
{
spanBarOptions: {
type: DURATION,
},
},
props
);
render(<SpanBarRow {...testProps} />);
expect(screen.getByText('(9ms)')).toBeInTheDocument();
});
it('with tag value', () => {
const testProps = Object.assign(
{
spanBarOptions: {
type: TAG,
tag: 'tag',
},
},
{
...props,
span: {
process: {},
tags: [
{
key: 'tag',
value: 'tag-value',
},
],
},
}
);
render(<SpanBarRow {...testProps} />);
expect(screen.getByText('(tag-value)')).toBeInTheDocument();
});
it('with process value', () => {
let testProps = Object.assign(
{
spanBarOptions: {
type: TAG,
tag: 'tag',
},
},
{
...props,
span: {
process: {
tags: [
{
key: 'tag',
value: 'process-value',
},
],
},
tags: [],
},
}
);
render(<SpanBarRow {...testProps} />);
expect(screen.getByText('(process-value)')).toBeInTheDocument();
});
});
});
@@ -18,11 +18,12 @@ import * as React from 'react';
import IoAlert from 'react-icons/lib/io/alert';
import IoArrowRightA from 'react-icons/lib/io/arrow-right-a';
import { GrafanaTheme2 } from '@grafana/data';
import { GrafanaTheme2, TraceKeyValuePair } from '@grafana/data';
import { stylesFactory, withTheme2 } from '@grafana/ui';
import { autoColor } from '../Theme';
import { SpanLinkFunc, TNil } from '../types';
import { DURATION, NONE, TAG } from '../settings/SpanBarSettings';
import { SpanBarOptions, SpanLinkFunc, TNil } from '../types';
import { SpanLinks } from '../types/links';
import { TraceSpan } from '../types/trace';
@@ -290,6 +291,7 @@ type SpanBarRowProps = {
className?: string;
theme: GrafanaTheme2;
color: string;
spanBarOptions: SpanBarOptions | undefined;
columnDivision: number;
isChildrenExpanded: boolean;
isDetailExpanded: boolean;
@@ -352,6 +354,7 @@ export class UnthemedSpanBarRow extends React.PureComponent<SpanBarRowProps> {
const {
className,
color,
spanBarOptions,
columnDivision,
isChildrenExpanded,
isDetailExpanded,
@@ -379,6 +382,7 @@ export class UnthemedSpanBarRow extends React.PureComponent<SpanBarRowProps> {
process: { serviceName },
} = span;
const label = formatDuration(duration);
const viewBounds = getViewedBounds(span.startTime, span.startTime + span.duration);
const viewStart = viewBounds.start;
const viewEnd = viewBounds.end;
@@ -473,7 +477,7 @@ export class UnthemedSpanBarRow extends React.PureComponent<SpanBarRowProps> {
)}
</span>
<small className={styles.endpointName}>{rpc ? rpc.operationName : operationName}</small>
<small className={styles.endpointName}> | {label}</small>
<small className={styles.endpointName}> {this.getSpanBarLabel(span, spanBarOptions, label)}</small>
</a>
{createSpanLink &&
(() => {
@@ -542,6 +546,35 @@ export class UnthemedSpanBarRow extends React.PureComponent<SpanBarRowProps> {
</TimelineRow>
);
}
getSpanBarLabel = (span: TraceSpan, spanBarOptions: SpanBarOptions | undefined, duration: string) => {
const type = spanBarOptions?.type ?? '';
if (type === NONE) {
return '';
} else if (type === '' || type === DURATION) {
return `(${duration})`;
} else if (type === TAG) {
const tagKey = spanBarOptions?.tag?.trim() ?? '';
if (tagKey !== '' && span.tags) {
const tag = span.tags?.find((tag: TraceKeyValuePair) => {
return tag.key === tagKey;
});
const process = span.process?.tags?.find((process: TraceKeyValuePair) => {
return process.key === tagKey;
});
if (tag) {
return `(${tag.value})`;
}
if (process) {
return `(${process.value})`;
}
}
}
return '';
};
}
export default withTheme2(UnthemedSpanBarRow);
@@ -23,7 +23,7 @@ import { stylesFactory, withTheme2, ToolbarButton } from '@grafana/ui';
import { Accessors } from '../ScrollManager';
import { PEER_SERVICE } from '../constants/tag-keys';
import { SpanLinkFunc, TNil } from '../types';
import { SpanBarOptions, SpanLinkFunc, TNil } from '../types';
import TTraceTimeline from '../types/TTraceTimeline';
import { TraceLog, TraceSpan, Trace, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace';
import { getColorByKey } from '../utils/color-generator';
@@ -89,6 +89,7 @@ type TVirtualizedTraceViewOwnProps = {
scrollToFirstVisibleSpan: () => void;
registerAccessors: (accesors: Accessors) => void;
trace: Trace;
spanBarOptions: SpanBarOptions | undefined;
linksGetter: (span: TraceSpan, items: TraceKeyValuePair[], itemIndex: number) => TraceLink[];
childrenToggle: (spanID: string) => void;
clearShouldScrollToFirstUiFindMatch: () => void;
@@ -387,6 +388,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
findMatchesIDs,
spanNameColumnWidth,
trace,
spanBarOptions,
hoverIndentGuideIds,
addHoverIndentGuideId,
removeHoverIndentGuideId,
@@ -440,6 +442,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
clippingLeft={this.getClipping().left}
clippingRight={this.getClipping().right}
color={color}
spanBarOptions={spanBarOptions}
columnDivision={spanNameColumnWidth}
isChildrenExpanded={!isCollapsed}
isDetailExpanded={isDetailExpanded}
@@ -21,6 +21,7 @@ import { stylesFactory, withTheme2 } from '@grafana/ui';
import { Accessors } from '../ScrollManager';
import { autoColor } from '../Theme';
import { merge as mergeShortcuts } from '../keyboard-shortcuts';
import { SpanBarOptions } from '../settings/SpanBarSettings';
import { SpanLinkFunc, TNil } from '../types';
import TTraceTimeline from '../types/TTraceTimeline';
import { TraceSpan, Trace, TraceLog, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace';
@@ -75,6 +76,7 @@ type TProps = TExtractUiFindFromStateReturn & {
scrollToFirstVisibleSpan: () => void;
traceTimeline: TTraceTimeline;
trace: Trace;
spanBarOptions: SpanBarOptions | undefined;
updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void;
updateViewRangeTime: TUpdateViewRangeTimeFunction;
viewRange: ViewRange;
@@ -1,5 +1,6 @@
export { default as TraceTimelineViewer } from './TraceTimelineViewer';
export { default as TracePageHeader } from './TracePageHeader';
export { default as SpanBarSettings } from './settings/SpanBarSettings';
export * from './types';
export * from './TraceTimelineViewer/types';
export { default as DetailState } from './TraceTimelineViewer/SpanDetail/DetailState';
@@ -0,0 +1,90 @@
import { css } from '@emotion/css';
import React from 'react';
import {
DataSourceJsonData,
DataSourcePluginOptionsEditorProps,
GrafanaTheme,
toOption,
updateDatasourcePluginJsonDataOption,
} from '@grafana/data';
import { InlineField, InlineFieldRow, Input, Select, useStyles } from '@grafana/ui';
export interface SpanBarOptions {
type?: string;
tag?: string;
}
export interface SpanBarOptionsData extends DataSourceJsonData {
spanBar?: SpanBarOptions;
}
export const NONE = 'None';
export const DURATION = 'Duration';
export const TAG = 'Tag';
interface Props extends DataSourcePluginOptionsEditorProps<SpanBarOptionsData> {}
export default function SpanBarSettings({ options, onOptionsChange }: Props) {
const styles = useStyles(getStyles);
const selectOptions = [NONE, DURATION, TAG].map(toOption);
return (
<div className={css({ width: '100%' })}>
<h3 className="page-heading">Span bar label</h3>
<div className={styles.infoText}>Span bar label lets you add additional info to the span bar row.</div>
<InlineFieldRow className={styles.row}>
<InlineField label="Label" labelWidth={26} grow>
<Select
inputId="label"
options={selectOptions}
value={options.jsonData.spanBar?.type || ''}
onChange={(v) => {
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'spanBar', {
...options.jsonData.spanBar,
type: v?.value ?? '',
});
}}
placeholder="Duration"
isClearable
aria-label={'select-label-name'}
width={25}
/>
</InlineField>
</InlineFieldRow>
{options.jsonData.spanBar?.type === TAG && (
<InlineFieldRow className={styles.row}>
<InlineField label="Tag key" labelWidth={26} tooltip="Tag key which will be used to get the tag value">
<Input
type="text"
placeholder="Enter tag key"
onChange={(v) =>
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'spanBar', {
...options.jsonData.spanBar,
tag: v.currentTarget.value,
})
}
value={options.jsonData.spanBar?.tag || ''}
width={25}
/>
</InlineField>
</InlineFieldRow>
)}
</div>
);
}
const getStyles = (theme: GrafanaTheme) => ({
infoText: css`
label: infoText;
padding-bottom: ${theme.spacing.md};
color: ${theme.colors.textSemiWeak};
`,
row: css`
label: row;
align-items: baseline;
`,
});
@@ -13,6 +13,7 @@
// limitations under the License.
export { TraceSpan, TraceResponse, Trace, TraceProcess, TraceKeyValuePair, TraceLink } from './trace';
export { SpanLinkFunc, SpanLinkDef } from './links';
export { SpanBarOptions, SpanBarOptionsData } from '../settings/SpanBarSettings';
export { default as TTraceTimeline } from './TTraceTimeline';
export { default as TNil } from './TNil';
export { SpanLinkFunc, SpanLinkDef } from './links';