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:
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user