Traceview find: background color and prev/next (#46527)
* Next/prev buttons * expand * Sticky search bar * Removed anys * testing * testing * Tests for next/prev/suffix * More tests * Span bar row color * Add clear to input and update search bar styles * Update test * PR changes Co-authored-by: Connor Lindsey <cblindsey3@gmail.com>
This commit is contained in:
co-authored by
Connor Lindsey
parent
4d0204d012
commit
58922d78df
+1
-1
@@ -92,7 +92,7 @@ exports[`no enzyme tests`] = {
|
||||
"packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.test.js:3669774385": [
|
||||
[15, 26, 13, "RegExp match", "2409514259"]
|
||||
],
|
||||
"packages/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar.test.js:1298620967": [
|
||||
"packages/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar.test.js:2095199604": [
|
||||
[15, 19, 13, "RegExp match", "2409514259"]
|
||||
],
|
||||
"packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.test.js:793239444": [
|
||||
|
||||
@@ -21,7 +21,6 @@ import { dateTimeFormat, GrafanaTheme2, TimeZone } from '@grafana/data';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
|
||||
import SpanGraph from './SpanGraph';
|
||||
import TracePageSearchBar from './TracePageSearchBar';
|
||||
import { autoColor, TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from '..';
|
||||
import LabeledList from '../common/LabeledList';
|
||||
import TraceName from '../common/TraceName';
|
||||
@@ -138,22 +137,15 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
|
||||
type TracePageHeaderEmbedProps = {
|
||||
canCollapse: boolean;
|
||||
clearSearch: () => void;
|
||||
focusUiFindMatches: () => void;
|
||||
hideMap: boolean;
|
||||
hideSummary: boolean;
|
||||
nextResult: () => void;
|
||||
onSlimViewClicked: () => void;
|
||||
onTraceGraphViewClicked: () => void;
|
||||
prevResult: () => void;
|
||||
resultCount: number;
|
||||
slimView: boolean;
|
||||
trace: Trace;
|
||||
updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void;
|
||||
updateViewRangeTime: TUpdateViewRangeTimeFunction;
|
||||
viewRange: ViewRange;
|
||||
searchValue: string;
|
||||
onSearchValueChange: (value: string) => void;
|
||||
timeZone: TimeZone;
|
||||
};
|
||||
|
||||
@@ -200,21 +192,14 @@ export const HEADER_ITEMS = [
|
||||
export default function TracePageHeader(props: TracePageHeaderEmbedProps) {
|
||||
const {
|
||||
canCollapse,
|
||||
clearSearch,
|
||||
focusUiFindMatches,
|
||||
hideMap,
|
||||
hideSummary,
|
||||
nextResult,
|
||||
onSlimViewClicked,
|
||||
prevResult,
|
||||
resultCount,
|
||||
slimView,
|
||||
trace,
|
||||
updateNextViewRangeTime,
|
||||
updateViewRangeTime,
|
||||
viewRange,
|
||||
searchValue,
|
||||
onSearchValueChange,
|
||||
timeZone,
|
||||
} = props;
|
||||
|
||||
@@ -267,17 +252,6 @@ export default function TracePageHeader(props: TracePageHeaderEmbedProps) {
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
<TracePageSearchBar
|
||||
clearSearch={clearSearch}
|
||||
focusUiFindMatches={focusUiFindMatches}
|
||||
nextResult={nextResult}
|
||||
prevResult={prevResult}
|
||||
resultCount={resultCount}
|
||||
// TODO: we can change this when we have scroll to span functionality
|
||||
navigable={false}
|
||||
searchValue={searchValue}
|
||||
onSearchValueChange={onSearchValueChange}
|
||||
/>
|
||||
</div>
|
||||
{summaryItems && <LabeledList className={styles.TracePageHeaderOverviewItems} items={summaryItems} />}
|
||||
{!hideMap && !slimView && (
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { createTheme } from '@grafana/data';
|
||||
|
||||
import * as markers from './TracePageSearchBar.markers';
|
||||
import TracePageSearchBar, { getStyles } from './TracePageSearchBar';
|
||||
@@ -24,7 +25,7 @@ const defaultProps = {
|
||||
navigable: true,
|
||||
nextResult: () => {},
|
||||
prevResult: () => {},
|
||||
resultCount: 0,
|
||||
suffix: '',
|
||||
searchValue: 'something',
|
||||
};
|
||||
|
||||
@@ -45,26 +46,28 @@ describe('<TracePageSearchBar>', () => {
|
||||
name: 'search',
|
||||
})
|
||||
);
|
||||
expect(suffix.hasClass(getStyles().TracePageSearchBarCount)).toBe(true);
|
||||
expect(suffix.text()).toBe(String(defaultProps.resultCount));
|
||||
const theme = createTheme();
|
||||
expect(suffix.hasClass(getStyles(theme).TracePageSearchBarSuffix)).toBe(true);
|
||||
expect(suffix.text()).toBe(String(defaultProps.suffix));
|
||||
});
|
||||
|
||||
it('renders buttons', () => {
|
||||
const buttons = wrapper.find('Button');
|
||||
expect(buttons.length).toBe(4);
|
||||
expect(buttons.length).toBe(2);
|
||||
buttons.forEach((button) => {
|
||||
expect(button.prop('disabled')).toBe(false);
|
||||
});
|
||||
expect(wrapper.find('Button[icon="arrow-up"]').prop('onClick')).toBe(defaultProps.prevResult);
|
||||
expect(wrapper.find('Button[icon="arrow-down"]').prop('onClick')).toBe(defaultProps.nextResult);
|
||||
expect(wrapper.find('Button[icon="times"]').prop('onClick')).toBe(defaultProps.clearSearch);
|
||||
});
|
||||
|
||||
it('hides navigation buttons when not navigable', () => {
|
||||
it('only shows navigable buttons when navigable is true', () => {
|
||||
wrapper.setProps({ navigable: false });
|
||||
const button = wrapper.find('Button');
|
||||
expect(button.length).toBe(1);
|
||||
expect(button.prop('icon')).toBe('times');
|
||||
var buttons = wrapper.find('Button');
|
||||
expect(buttons.length).toBe(0);
|
||||
wrapper.setProps({ navigable: true });
|
||||
buttons = wrapper.find('Button');
|
||||
expect(buttons.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,7 +82,7 @@ describe('<TracePageSearchBar>', () => {
|
||||
|
||||
it('renders buttons', () => {
|
||||
const buttons = wrapper.find('Button');
|
||||
expect(buttons.length).toBe(4);
|
||||
expect(buttons.length).toBe(2);
|
||||
buttons.forEach((button) => {
|
||||
expect(button.prop('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import cx from 'classnames';
|
||||
import IoAndroidLocate from 'react-icons/lib/io/android-locate';
|
||||
import { css } from '@emotion/css';
|
||||
import { Button, useStyles2 } from '@grafana/ui';
|
||||
|
||||
@@ -24,11 +23,24 @@ import UiFindInput from '../common/UiFindInput';
|
||||
import { ubFlexAuto, ubJustifyEnd } from '../uberUtilityStyles';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import { memo } from 'react';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
export const getStyles = () => {
|
||||
export const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
TracePageSearchBar: css`
|
||||
label: TracePageSearchBar;
|
||||
float: right;
|
||||
position: sticky;
|
||||
top: 8px;
|
||||
right: 0;
|
||||
z-index: ${theme.zIndex.navbarFixed};
|
||||
background: ${theme.colors.background.primary};
|
||||
margin-top: 8px;
|
||||
margin-bottom: -48px;
|
||||
padding: 8px;
|
||||
margin-right: 2px;
|
||||
border-radius: 4px;
|
||||
box-shadow: ${theme.shadows.z2};
|
||||
`,
|
||||
TracePageSearchBarBar: css`
|
||||
label: TracePageSearchBarBar;
|
||||
@@ -38,14 +50,14 @@ export const getStyles = () => {
|
||||
max-width: 100%;
|
||||
}
|
||||
`,
|
||||
TracePageSearchBarCount: css`
|
||||
label: TracePageSearchBarCount;
|
||||
TracePageSearchBarSuffix: css`
|
||||
label: TracePageSearchBarSuffix;
|
||||
opacity: 0.6;
|
||||
`,
|
||||
TracePageSearchBarBtn: css`
|
||||
label: TracePageSearchBarBtn;
|
||||
border-left: none;
|
||||
transition: 0.2s;
|
||||
margin-left: 8px;
|
||||
`,
|
||||
TracePageSearchBarBtnDisabled: css`
|
||||
label: TracePageSearchBarBtnDisabled;
|
||||
@@ -61,72 +73,62 @@ export const getStyles = () => {
|
||||
type TracePageSearchBarProps = {
|
||||
prevResult: () => void;
|
||||
nextResult: () => void;
|
||||
clearSearch: () => void;
|
||||
focusUiFindMatches: () => void;
|
||||
resultCount: number;
|
||||
navigable: boolean;
|
||||
searchValue: string;
|
||||
onSearchValueChange: (value: string) => void;
|
||||
searchBarSuffix: string;
|
||||
};
|
||||
|
||||
export default memo(function TracePageSearchBar(props: TracePageSearchBarProps) {
|
||||
const {
|
||||
clearSearch,
|
||||
focusUiFindMatches,
|
||||
navigable,
|
||||
nextResult,
|
||||
prevResult,
|
||||
resultCount,
|
||||
onSearchValueChange,
|
||||
searchValue,
|
||||
} = props;
|
||||
const { navigable, nextResult, prevResult, onSearchValueChange, searchValue, searchBarSuffix } = props;
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const count = searchValue ? <span className={styles.TracePageSearchBarCount}>{resultCount}</span> : null;
|
||||
const suffix = searchValue ? (
|
||||
<span className={styles.TracePageSearchBarSuffix} data-testid="trace-page-search-bar-suffix">
|
||||
{searchBarSuffix}
|
||||
</span>
|
||||
) : null;
|
||||
|
||||
const btnClass = cx(styles.TracePageSearchBarBtn, { [styles.TracePageSearchBarBtnDisabled]: !searchValue });
|
||||
const uiFindInputInputProps = {
|
||||
'data-test': markers.IN_TRACE_SEARCH,
|
||||
className: cx(styles.TracePageSearchBarBar, ubFlexAuto),
|
||||
name: 'search',
|
||||
suffix: count,
|
||||
suffix,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.TracePageSearchBar}>
|
||||
<span className={ubJustifyEnd} style={{ display: 'flex' }}>
|
||||
<UiFindInput onChange={onSearchValueChange} value={searchValue} inputProps={uiFindInputInputProps} />
|
||||
<UiFindInput
|
||||
onChange={onSearchValueChange}
|
||||
value={searchValue}
|
||||
inputProps={uiFindInputInputProps}
|
||||
allowClear={true}
|
||||
/>
|
||||
<>
|
||||
{navigable && (
|
||||
<>
|
||||
<Button
|
||||
className={cx(btnClass, styles.TracePageSearchBarLocateBtn)}
|
||||
disabled={!searchValue}
|
||||
type="button"
|
||||
onClick={focusUiFindMatches}
|
||||
>
|
||||
<IoAndroidLocate />
|
||||
</Button>
|
||||
<Button className={btnClass} disabled={!searchValue} type="button" icon="arrow-up" onClick={prevResult} />
|
||||
<Button
|
||||
className={btnClass}
|
||||
variant="secondary"
|
||||
disabled={!searchValue}
|
||||
type="button"
|
||||
icon="arrow-down"
|
||||
data-testid="trace-page-search-bar-next-result-button"
|
||||
onClick={nextResult}
|
||||
/>
|
||||
<Button
|
||||
className={btnClass}
|
||||
variant="secondary"
|
||||
disabled={!searchValue}
|
||||
type="button"
|
||||
icon="arrow-up"
|
||||
data-testid="trace-page-search-bar-prev-result-button"
|
||||
onClick={prevResult}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
variant={'secondary'}
|
||||
fill={'text'}
|
||||
// className={btnClass}
|
||||
disabled={!searchValue}
|
||||
type="button"
|
||||
icon="times"
|
||||
onClick={clearSearch}
|
||||
title={'Clear search'}
|
||||
/>
|
||||
</>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -166,17 +166,17 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => {
|
||||
`,
|
||||
rowMatchingFilter: css`
|
||||
label: rowMatchingFilter;
|
||||
background-color: ${autoColor(theme, '#fffce4')};
|
||||
background-color: ${autoColor(theme, '#fffbde')};
|
||||
&:hover .${nameWrapperClassName} {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
${autoColor(theme, '#fff5e1')},
|
||||
${autoColor(theme, '#fff5e1')} 75%,
|
||||
${autoColor(theme, '#ffe6c9')}
|
||||
${autoColor(theme, '#fffbde')},
|
||||
${autoColor(theme, '#fffbde')} 75%,
|
||||
${autoColor(theme, '#f7f1c6')}
|
||||
);
|
||||
}
|
||||
&:hover .${viewClassName} {
|
||||
background-color: ${autoColor(theme, '#fff3d7')};
|
||||
background-color: ${autoColor(theme, '#f7f1c6')};
|
||||
outline: 1px solid ${autoColor(theme, '#ddd')};
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -101,6 +101,7 @@ type TVirtualizedTraceViewOwnProps = {
|
||||
createSpanLink?: SpanLinkFunc;
|
||||
scrollElement?: Element;
|
||||
focusedSpanId?: string;
|
||||
focusedSpanIdForSearch: string;
|
||||
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
||||
topOfExploreViewRef?: RefObject<HTMLDivElement>;
|
||||
};
|
||||
@@ -223,6 +224,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
trace: nextTrace,
|
||||
uiFind,
|
||||
focusedSpanId,
|
||||
focusedSpanIdForSearch,
|
||||
} = this.props;
|
||||
|
||||
if (trace !== nextTrace) {
|
||||
@@ -241,6 +243,10 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
if (focusedSpanId !== prevProps.focusedSpanId) {
|
||||
this.scrollToSpan(focusedSpanId);
|
||||
}
|
||||
|
||||
if (focusedSpanIdForSearch !== prevProps.focusedSpanIdForSearch) {
|
||||
this.scrollToSpan(focusedSpanIdForSearch);
|
||||
}
|
||||
}
|
||||
|
||||
getRowStates(): RowState[] {
|
||||
@@ -378,6 +384,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
theme,
|
||||
createSpanLink,
|
||||
focusedSpanId,
|
||||
focusedSpanIdForSearch,
|
||||
} = this.props;
|
||||
// to avert flow error
|
||||
if (!trace) {
|
||||
@@ -387,7 +394,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
const isCollapsed = childrenHiddenIDs.has(spanID);
|
||||
const isDetailExpanded = detailStates.has(spanID);
|
||||
const isMatchingFilter = findMatchesIDs ? findMatchesIDs.has(spanID) : false;
|
||||
const isFocused = spanID === focusedSpanId;
|
||||
const isFocused = spanID === focusedSpanId || spanID === focusedSpanIdForSearch;
|
||||
const showErrorIcon = isErrorSpan(span) || (isCollapsed && spanContainsErredSpan(trace.spans, spanIndex));
|
||||
|
||||
// Check for direct child "server" span if the span is a "client" span.
|
||||
|
||||
@@ -105,6 +105,7 @@ type TProps = TExtractUiFindFromStateReturn & {
|
||||
createSpanLink?: SpanLinkFunc;
|
||||
scrollElement?: Element;
|
||||
focusedSpanId?: string;
|
||||
focusedSpanIdForSearch: string;
|
||||
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
||||
topOfExploreViewRef?: RefObject<HTMLDivElement>;
|
||||
};
|
||||
@@ -163,6 +164,7 @@ export class UnthemedTraceTimelineViewer extends React.PureComponent<TProps, Sta
|
||||
traceTimeline,
|
||||
theme,
|
||||
topOfExploreViewRef,
|
||||
focusedSpanIdForSearch,
|
||||
...rest
|
||||
} = this.props;
|
||||
const { trace } = rest;
|
||||
@@ -194,6 +196,7 @@ export class UnthemedTraceTimelineViewer extends React.PureComponent<TProps, Sta
|
||||
setSpanNameColumnWidth={setSpanNameColumnWidth}
|
||||
currentViewRangeTime={viewRange.time.current}
|
||||
topOfExploreViewRef={topOfExploreViewRef}
|
||||
focusedSpanIdForSearch={focusedSpanIdForSearch}
|
||||
/>
|
||||
</div>
|
||||
</ExternalLinkContext.Provider>
|
||||
|
||||
@@ -4,12 +4,13 @@ import { TraceView } from './TraceView';
|
||||
import { setDataSourceSrv } from '@grafana/runtime';
|
||||
import { ExploreId } from 'app/types';
|
||||
import { TraceData, TraceSpanData } from '@jaegertracing/jaeger-ui-components/src/types/trace';
|
||||
import { getDefaultTimeRange, LoadingState, MutableDataFrame } from '@grafana/data';
|
||||
import { DataFrame, MutableDataFrame, getDefaultTimeRange, LoadingState } from '@grafana/data';
|
||||
import { configureStore } from '../../../store/configureStore';
|
||||
import { Provider } from 'react-redux';
|
||||
import { transformDataFrames } from './utils/transform';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
function renderTraceView(frames = [frameOld]) {
|
||||
function getTraceView(frames: DataFrame[]) {
|
||||
const store = configureStore();
|
||||
const mockPanelData = {
|
||||
state: LoadingState.Done,
|
||||
@@ -17,11 +18,31 @@ function renderTraceView(frames = [frameOld]) {
|
||||
timeRange: getDefaultTimeRange(),
|
||||
};
|
||||
|
||||
const { container, baseElement } = render(
|
||||
const traceView = (
|
||||
<Provider store={store}>
|
||||
<TraceView exploreId={ExploreId.left} dataFrames={frames} splitOpenFn={() => {}} queryResponse={mockPanelData} />
|
||||
<TraceView
|
||||
exploreId={ExploreId.left}
|
||||
dataFrames={frames}
|
||||
splitOpenFn={() => {}}
|
||||
traceProp={transformDataFrames(frames[0])!}
|
||||
search=""
|
||||
focusedSpanIdForSearch=""
|
||||
expandOne={() => {}}
|
||||
expandAll={() => {}}
|
||||
collapseOne={() => {}}
|
||||
collapseAll={() => {}}
|
||||
childrenToggle={() => {}}
|
||||
childrenHiddenIDs={new Set()}
|
||||
queryResponse={mockPanelData}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
return traceView;
|
||||
}
|
||||
|
||||
function renderTraceView(frames = [frameOld]) {
|
||||
const { container, baseElement } = render(getTraceView(frames));
|
||||
|
||||
return {
|
||||
header: container.children[0],
|
||||
timeline: container.children[1],
|
||||
@@ -79,42 +100,6 @@ describe('TraceView', () => {
|
||||
expect(screen.queryByText(/Tags/)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('toggles children visibility', () => {
|
||||
renderTraceViewNew();
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3);
|
||||
userEvent.click(screen.getAllByText('', { selector: 'span[data-test-id="SpanTreeOffset--indentGuide"]' })[0]);
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(1);
|
||||
|
||||
userEvent.click(screen.getAllByText('', { selector: 'span[data-test-id="SpanTreeOffset--indentGuide"]' })[0]);
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3);
|
||||
});
|
||||
|
||||
it('toggles collapses and expands one level of spans', () => {
|
||||
renderTraceViewNew();
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3);
|
||||
userEvent.click(screen.getByLabelText('Collapse +1'));
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(2);
|
||||
userEvent.click(screen.getByLabelText('Expand +1'));
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3);
|
||||
});
|
||||
|
||||
it('toggles collapses and expands all levels', () => {
|
||||
renderTraceViewNew();
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3);
|
||||
userEvent.click(screen.getByLabelText('Collapse All'));
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(1);
|
||||
userEvent.click(screen.getByLabelText('Expand All'));
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3);
|
||||
});
|
||||
|
||||
it('searches for spans', () => {
|
||||
renderTraceViewNew();
|
||||
userEvent.type(screen.getByPlaceholderText('Find...'), '1ed38015486087ca');
|
||||
expect(
|
||||
(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' })[0].parentNode! as HTMLElement).className
|
||||
).toContain('rowMatchingFilter');
|
||||
});
|
||||
|
||||
it('shows timeline ticks', () => {
|
||||
renderTraceViewNew();
|
||||
function ticks() {
|
||||
@@ -151,38 +136,13 @@ describe('TraceView', () => {
|
||||
});
|
||||
|
||||
it('resets detail view for new trace with the identical spanID', () => {
|
||||
const store = configureStore();
|
||||
const mockPanelData = {
|
||||
state: LoadingState.Done,
|
||||
series: [],
|
||||
timeRange: getDefaultTimeRange(),
|
||||
};
|
||||
|
||||
const { rerender } = render(
|
||||
<Provider store={store}>
|
||||
<TraceView
|
||||
exploreId={ExploreId.left}
|
||||
dataFrames={[frameOld]}
|
||||
splitOpenFn={() => {}}
|
||||
queryResponse={mockPanelData}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
const { rerender } = render(getTraceView([frameOld]));
|
||||
const span = screen.getAllByText('', { selector: 'div[data-test-id="span-view"]' })[2];
|
||||
userEvent.click(span);
|
||||
//Process is in detail view
|
||||
expect(screen.getByText(/Process/)).toBeInTheDocument();
|
||||
|
||||
rerender(
|
||||
<Provider store={store}>
|
||||
<TraceView
|
||||
exploreId={ExploreId.left}
|
||||
dataFrames={[frameNew]}
|
||||
splitOpenFn={() => {}}
|
||||
queryResponse={mockPanelData}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
rerender(getTraceView([frameNew]));
|
||||
expect(screen.queryByText(/Process/)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -298,7 +258,7 @@ const response: TraceData & { spans: TraceSpanData[] } = {
|
||||
warnings: null as any,
|
||||
};
|
||||
|
||||
const frameOld = new MutableDataFrame({
|
||||
export const frameOld = new MutableDataFrame({
|
||||
fields: [
|
||||
{
|
||||
name: 'trace',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
DataFrame,
|
||||
DataFrameView,
|
||||
DataLink,
|
||||
DataSourceApi,
|
||||
Field,
|
||||
@@ -9,16 +8,13 @@ import {
|
||||
mapInternalLinkToExplore,
|
||||
PanelData,
|
||||
SplitOpen,
|
||||
TraceSpanRow,
|
||||
} from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import {
|
||||
Trace,
|
||||
TracePageHeader,
|
||||
TraceProcess,
|
||||
TraceResponse,
|
||||
TraceSpan,
|
||||
TraceTimelineViewer,
|
||||
transformTraceData,
|
||||
TTraceTimeline,
|
||||
} from '@jaegertracing/jaeger-ui-components';
|
||||
import { TraceToLogsData } from 'app/core/components/TraceToLogs/TraceToLogsSettings';
|
||||
@@ -30,10 +26,8 @@ import React, { RefObject, useCallback, useEffect, useMemo, useState } from 'rea
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { changePanelState } from '../state/explorePane';
|
||||
import { createSpanLinkFactory } from './createSpanLink';
|
||||
import { useChildrenState } from './useChildrenState';
|
||||
import { useDetailState } from './useDetailState';
|
||||
import { useHoverIndentGuide } from './useHoverIndentGuide';
|
||||
import { useSearch } from './useSearch';
|
||||
import { useViewRange } from './useViewRange';
|
||||
|
||||
function noop(): {} {
|
||||
@@ -46,14 +40,31 @@ type Props = {
|
||||
exploreId: ExploreId;
|
||||
scrollElement?: Element;
|
||||
topOfExploreViewRef?: RefObject<HTMLDivElement>;
|
||||
traceProp: Trace;
|
||||
spanFindMatches?: Set<string>;
|
||||
search: string;
|
||||
focusedSpanIdForSearch: string;
|
||||
expandOne: (spans: TraceSpan[]) => void;
|
||||
expandAll: () => void;
|
||||
collapseOne: (spans: TraceSpan[]) => void;
|
||||
collapseAll: (spans: TraceSpan[]) => void;
|
||||
childrenToggle: (spanId: string) => void;
|
||||
childrenHiddenIDs: Set<string>;
|
||||
queryResponse: PanelData;
|
||||
};
|
||||
|
||||
export function TraceView(props: Props) {
|
||||
// At this point we only show single trace
|
||||
const frame = props.dataFrames[0];
|
||||
const {
|
||||
expandOne,
|
||||
expandAll,
|
||||
collapseOne,
|
||||
collapseAll,
|
||||
childrenToggle,
|
||||
childrenHiddenIDs,
|
||||
spanFindMatches,
|
||||
traceProp,
|
||||
} = props;
|
||||
|
||||
const { expandOne, collapseOne, childrenToggle, collapseAll, childrenHiddenIDs, expandAll } = useChildrenState();
|
||||
const {
|
||||
detailStates,
|
||||
toggleDetail,
|
||||
@@ -65,7 +76,7 @@ export function TraceView(props: Props) {
|
||||
detailTagsToggle,
|
||||
detailWarningsToggle,
|
||||
detailStackTracesToggle,
|
||||
} = useDetailState(frame);
|
||||
} = useDetailState(props.dataFrames[0]);
|
||||
|
||||
const { removeHoverIndentGuideId, addHoverIndentGuideId, hoverIndentGuideIds } = useHoverIndentGuide();
|
||||
const { viewRange, updateViewRangeTime, updateNextViewRangeTime } = useViewRange();
|
||||
@@ -79,15 +90,12 @@ export function TraceView(props: Props) {
|
||||
*/
|
||||
const [slim, setSlim] = useState(false);
|
||||
|
||||
const traceProp = useMemo(() => transformDataFrames(frame), [frame]);
|
||||
const { search, setSearch, spanFindMatches, clearSearch } = useSearch(traceProp?.spans);
|
||||
|
||||
const datasource = useSelector(
|
||||
(state: StoreState) => state.explore[props.exploreId]?.datasourceInstance ?? undefined
|
||||
);
|
||||
|
||||
const [focusedSpanId, createFocusSpanLink] = useFocusSpanLink({
|
||||
refId: frame?.refId,
|
||||
refId: props.dataFrames[0]?.refId,
|
||||
exploreId: props.exploreId,
|
||||
datasource,
|
||||
});
|
||||
@@ -104,9 +112,9 @@ export function TraceView(props: Props) {
|
||||
hoverIndentGuideIds,
|
||||
shouldScrollToFirstUiFindMatch: false,
|
||||
spanNameColumnWidth,
|
||||
traceID: traceProp?.traceID,
|
||||
traceID: props.traceProp?.traceID,
|
||||
}),
|
||||
[childrenHiddenIDs, detailStates, hoverIndentGuideIds, spanNameColumnWidth, traceProp?.traceID]
|
||||
[childrenHiddenIDs, detailStates, hoverIndentGuideIds, spanNameColumnWidth, props.traceProp?.traceID]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -118,8 +126,8 @@ export function TraceView(props: Props) {
|
||||
const traceToLogsOptions = (getDatasourceSrv().getInstanceSettings(datasource?.name)?.jsonData as TraceToLogsData)
|
||||
?.tracesToLogs;
|
||||
const createSpanLink = useMemo(
|
||||
() => createSpanLinkFactory({ splitOpenFn: props.splitOpenFn, traceToLogsOptions, dataFrame: frame }),
|
||||
[props.splitOpenFn, traceToLogsOptions, frame]
|
||||
() => createSpanLinkFactory({ splitOpenFn: props.splitOpenFn, traceToLogsOptions, dataFrame: props.dataFrames[0] }),
|
||||
[props.splitOpenFn, traceToLogsOptions, props.dataFrames]
|
||||
);
|
||||
const onSlimViewClicked = useCallback(() => setSlim(!slim), [slim]);
|
||||
const timeZone = useSelector((state: StoreState) => getTimeZone(state.user));
|
||||
@@ -132,22 +140,15 @@ export function TraceView(props: Props) {
|
||||
<>
|
||||
<TracePageHeader
|
||||
canCollapse={false}
|
||||
clearSearch={clearSearch}
|
||||
focusUiFindMatches={noop}
|
||||
hideMap={false}
|
||||
hideSummary={false}
|
||||
nextResult={noop}
|
||||
onSlimViewClicked={onSlimViewClicked}
|
||||
onTraceGraphViewClicked={noop}
|
||||
prevResult={noop}
|
||||
resultCount={spanFindMatches?.size ?? 0}
|
||||
slimView={slim}
|
||||
trace={traceProp}
|
||||
updateNextViewRangeTime={updateNextViewRangeTime}
|
||||
updateViewRangeTime={updateViewRangeTime}
|
||||
viewRange={viewRange}
|
||||
searchValue={search}
|
||||
onSearchValueChange={setSearch}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
<TraceTimelineViewer
|
||||
@@ -181,10 +182,11 @@ export function TraceView(props: Props) {
|
||||
addHoverIndentGuideId={addHoverIndentGuideId}
|
||||
removeHoverIndentGuideId={removeHoverIndentGuideId}
|
||||
linksGetter={noop as any}
|
||||
uiFind={search}
|
||||
uiFind={props.search}
|
||||
createSpanLink={createSpanLink}
|
||||
scrollElement={props.scrollElement}
|
||||
focusedSpanId={focusedSpanId}
|
||||
focusedSpanIdForSearch={props.focusedSpanIdForSearch}
|
||||
createFocusSpanLink={createFocusSpanLink}
|
||||
topOfExploreViewRef={props.topOfExploreViewRef}
|
||||
/>
|
||||
@@ -192,56 +194,6 @@ export function TraceView(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
function transformDataFrames(frame?: DataFrame): Trace | null {
|
||||
if (!frame) {
|
||||
return null;
|
||||
}
|
||||
let data: TraceResponse =
|
||||
frame.fields.length === 1
|
||||
? // For backward compatibility when we sent whole json response in a single field/value
|
||||
frame.fields[0].values.get(0)
|
||||
: transformTraceDataFrame(frame);
|
||||
return transformTraceData(data);
|
||||
}
|
||||
|
||||
function transformTraceDataFrame(frame: DataFrame): TraceResponse {
|
||||
const view = new DataFrameView<TraceSpanRow>(frame);
|
||||
const processes: Record<string, TraceProcess> = {};
|
||||
for (let i = 0; i < view.length; i++) {
|
||||
const span = view.get(i);
|
||||
if (!processes[span.spanID]) {
|
||||
processes[span.spanID] = {
|
||||
serviceName: span.serviceName,
|
||||
tags: span.serviceTags,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
traceID: view.get(0).traceID,
|
||||
processes,
|
||||
spans: view.toArray().map((s, index) => {
|
||||
const references = [];
|
||||
if (s.parentSpanID) {
|
||||
references.push({ refType: 'CHILD_OF' as const, spanID: s.parentSpanID, traceID: s.traceID });
|
||||
}
|
||||
if (s.references) {
|
||||
references.push(...s.references.map((reference) => ({ refType: 'FOLLOWS_FROM' as const, ...reference })));
|
||||
}
|
||||
return {
|
||||
...s,
|
||||
duration: s.duration * 1000,
|
||||
startTime: s.startTime * 1000,
|
||||
processID: s.spanID,
|
||||
flags: 0,
|
||||
references,
|
||||
logs: s.logs?.map((l) => ({ ...l, timestamp: l.timestamp * 1000 })) || [],
|
||||
dataFrameRowIndex: index,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles focusing a span. Returns the span id to focus to based on what is in current explore state and also a
|
||||
* function to change the focused span id.
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { TraceViewContainer } from './TraceViewContainer';
|
||||
import { frameOld } from './TraceView.test';
|
||||
import { ExploreId } from 'app/types';
|
||||
import { configureStore } from '../../../store/configureStore';
|
||||
import { getDefaultTimeRange, LoadingState } from '@grafana/data';
|
||||
import { Provider } from 'react-redux';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
function renderTraceViewContainer(frames = [frameOld]) {
|
||||
const store = configureStore();
|
||||
const mockPanelData = {
|
||||
state: LoadingState.Done,
|
||||
series: [],
|
||||
timeRange: getDefaultTimeRange(),
|
||||
};
|
||||
|
||||
const { container, baseElement } = render(
|
||||
<Provider store={store}>
|
||||
<TraceViewContainer
|
||||
exploreId={ExploreId.left}
|
||||
dataFrames={frames}
|
||||
splitOpenFn={() => {}}
|
||||
queryResponse={mockPanelData}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
return {
|
||||
header: container.children[0],
|
||||
timeline: container.children[1],
|
||||
container,
|
||||
baseElement,
|
||||
};
|
||||
}
|
||||
|
||||
describe('TraceViewContainer', () => {
|
||||
it('toggles children visibility', () => {
|
||||
renderTraceViewContainer();
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3);
|
||||
userEvent.click(screen.getAllByText('', { selector: 'span[data-test-id="SpanTreeOffset--indentGuide"]' })[0]);
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(1);
|
||||
|
||||
userEvent.click(screen.getAllByText('', { selector: 'span[data-test-id="SpanTreeOffset--indentGuide"]' })[0]);
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3);
|
||||
});
|
||||
|
||||
it('toggles collapses and expands one level of spans', () => {
|
||||
renderTraceViewContainer();
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3);
|
||||
userEvent.click(screen.getByLabelText('Collapse +1'));
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(2);
|
||||
userEvent.click(screen.getByLabelText('Expand +1'));
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3);
|
||||
});
|
||||
|
||||
it('toggles collapses and expands all levels', () => {
|
||||
renderTraceViewContainer();
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3);
|
||||
userEvent.click(screen.getByLabelText('Collapse All'));
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(1);
|
||||
userEvent.click(screen.getByLabelText('Expand All'));
|
||||
expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3);
|
||||
});
|
||||
|
||||
it('searches for spans', () => {
|
||||
renderTraceViewContainer();
|
||||
userEvent.type(screen.getByPlaceholderText('Find...'), '1ed38015486087ca');
|
||||
expect(
|
||||
(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' })[0].parentNode! as HTMLElement).className
|
||||
).toContain('rowMatchingFilter');
|
||||
});
|
||||
|
||||
it('can select next/prev results', () => {
|
||||
renderTraceViewContainer();
|
||||
userEvent.type(screen.getByPlaceholderText('Find...'), 'logproto');
|
||||
const nextResultButton = screen.getByTestId('trace-page-search-bar-next-result-button');
|
||||
const prevResultButton = screen.getByTestId('trace-page-search-bar-prev-result-button');
|
||||
const suffix = screen.getByTestId('trace-page-search-bar-suffix');
|
||||
|
||||
userEvent.click(nextResultButton);
|
||||
expect(suffix.textContent).toBe('1 of 2');
|
||||
expect(
|
||||
(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' })[1].parentNode! as HTMLElement).className
|
||||
).toContain('rowFocused');
|
||||
userEvent.click(nextResultButton);
|
||||
expect(suffix.textContent).toBe('2 of 2');
|
||||
expect(
|
||||
(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' })[2].parentNode! as HTMLElement).className
|
||||
).toContain('rowFocused');
|
||||
userEvent.click(nextResultButton);
|
||||
expect(suffix.textContent).toBe('1 of 2');
|
||||
expect(
|
||||
(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' })[1].parentNode! as HTMLElement).className
|
||||
).toContain('rowFocused');
|
||||
userEvent.click(prevResultButton);
|
||||
expect(suffix.textContent).toBe('2 of 2');
|
||||
expect(
|
||||
(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' })[2].parentNode! as HTMLElement).className
|
||||
).toContain('rowFocused');
|
||||
userEvent.click(prevResultButton);
|
||||
expect(suffix.textContent).toBe('1 of 2');
|
||||
expect(
|
||||
(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' })[1].parentNode! as HTMLElement).className
|
||||
).toContain('rowFocused');
|
||||
userEvent.click(prevResultButton);
|
||||
expect(suffix.textContent).toBe('2 of 2');
|
||||
expect(
|
||||
(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' })[2].parentNode! as HTMLElement).className
|
||||
).toContain('rowFocused');
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,12 @@
|
||||
import React, { RefObject } from 'react';
|
||||
import React, { RefObject, useMemo, useState } from 'react';
|
||||
import { Collapse } from '@grafana/ui';
|
||||
import { DataFrame, PanelData, SplitOpen } from '@grafana/data';
|
||||
import { DataFrame, SplitOpen, PanelData } from '@grafana/data';
|
||||
import { TraceView } from './TraceView';
|
||||
import { ExploreId } from 'app/types/explore';
|
||||
|
||||
import TracePageSearchBar from '@jaegertracing/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar';
|
||||
import { useSearch } from './useSearch';
|
||||
import { transformDataFrames } from './utils/transform';
|
||||
import { useChildrenState } from './useChildrenState';
|
||||
interface Props {
|
||||
dataFrames: DataFrame[];
|
||||
splitOpenFn: SplitOpen;
|
||||
@@ -13,18 +16,103 @@ interface Props {
|
||||
queryResponse: PanelData;
|
||||
}
|
||||
export function TraceViewContainer(props: Props) {
|
||||
// At this point we only show single trace
|
||||
const frame = props.dataFrames[0];
|
||||
|
||||
const { dataFrames, splitOpenFn, exploreId, scrollElement, topOfExploreViewRef, queryResponse } = props;
|
||||
const traceProp = useMemo(() => transformDataFrames(frame), [frame]);
|
||||
const { search, setSearch, spanFindMatches } = useSearch(traceProp?.spans);
|
||||
const { expandOne, collapseOne, childrenToggle, collapseAll, childrenHiddenIDs, expandAll } = useChildrenState();
|
||||
|
||||
const [focusedSpanIdForSearch, setFocusedSpanIdForSearch] = useState('');
|
||||
const [searchBarSuffix, setSearchBarSuffix] = useState('');
|
||||
|
||||
const setTraceSearch = (value: string) => {
|
||||
setFocusedSpanIdForSearch('');
|
||||
setSearchBarSuffix('');
|
||||
setSearch(value);
|
||||
};
|
||||
|
||||
const nextResult = () => {
|
||||
expandAll();
|
||||
const spanMatches = Array.from(spanFindMatches!);
|
||||
const prevMatchedIndex = spanMatches.indexOf(focusedSpanIdForSearch)
|
||||
? spanMatches.indexOf(focusedSpanIdForSearch)
|
||||
: 0;
|
||||
|
||||
// new query || at end, go to start
|
||||
if (prevMatchedIndex === -1 || prevMatchedIndex === spanMatches.length - 1) {
|
||||
setFocusedSpanIdForSearch(spanMatches[0]);
|
||||
setSearchBarSuffix(getSearchBarSuffix(1));
|
||||
return;
|
||||
}
|
||||
|
||||
// get next
|
||||
setFocusedSpanIdForSearch(spanMatches[prevMatchedIndex + 1]);
|
||||
setSearchBarSuffix(getSearchBarSuffix(prevMatchedIndex + 2));
|
||||
};
|
||||
|
||||
const prevResult = () => {
|
||||
expandAll();
|
||||
const spanMatches = Array.from(spanFindMatches!);
|
||||
const prevMatchedIndex = spanMatches.indexOf(focusedSpanIdForSearch)
|
||||
? spanMatches.indexOf(focusedSpanIdForSearch)
|
||||
: 0;
|
||||
|
||||
// new query || at start, go to end
|
||||
if (prevMatchedIndex === -1 || prevMatchedIndex === 0) {
|
||||
setFocusedSpanIdForSearch(spanMatches[spanMatches.length - 1]);
|
||||
setSearchBarSuffix(getSearchBarSuffix(spanMatches.length));
|
||||
return;
|
||||
}
|
||||
|
||||
// get prev
|
||||
setFocusedSpanIdForSearch(spanMatches[prevMatchedIndex - 1]);
|
||||
setSearchBarSuffix(getSearchBarSuffix(prevMatchedIndex));
|
||||
};
|
||||
|
||||
const getSearchBarSuffix = (index: number): string => {
|
||||
if (spanFindMatches?.size && spanFindMatches?.size > 0) {
|
||||
return index + ' of ' + spanFindMatches?.size;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
if (!traceProp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapse label="Trace View" isOpen>
|
||||
<TraceView
|
||||
exploreId={exploreId}
|
||||
dataFrames={dataFrames}
|
||||
splitOpenFn={splitOpenFn}
|
||||
scrollElement={scrollElement}
|
||||
topOfExploreViewRef={topOfExploreViewRef}
|
||||
queryResponse={queryResponse}
|
||||
<>
|
||||
<TracePageSearchBar
|
||||
nextResult={nextResult}
|
||||
prevResult={prevResult}
|
||||
navigable={true}
|
||||
searchValue={search}
|
||||
onSearchValueChange={setTraceSearch}
|
||||
searchBarSuffix={searchBarSuffix}
|
||||
/>
|
||||
</Collapse>
|
||||
|
||||
<Collapse label="Trace View" isOpen>
|
||||
<TraceView
|
||||
exploreId={exploreId}
|
||||
dataFrames={dataFrames}
|
||||
splitOpenFn={splitOpenFn}
|
||||
scrollElement={scrollElement}
|
||||
topOfExploreViewRef={topOfExploreViewRef}
|
||||
traceProp={traceProp}
|
||||
spanFindMatches={spanFindMatches}
|
||||
search={search}
|
||||
focusedSpanIdForSearch={focusedSpanIdForSearch}
|
||||
expandOne={expandOne}
|
||||
collapseOne={collapseOne}
|
||||
collapseAll={collapseAll}
|
||||
expandAll={expandAll}
|
||||
childrenToggle={childrenToggle}
|
||||
childrenHiddenIDs={childrenHiddenIDs}
|
||||
queryResponse={queryResponse}
|
||||
/>
|
||||
</Collapse>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { filterSpans, TraceSpan } from '@jaegertracing/jaeger-ui-components';
|
||||
|
||||
/**
|
||||
@@ -11,9 +11,5 @@ export function useSearch(spans?: TraceSpan[]) {
|
||||
return search && spans ? filterSpans(search, spans) : undefined;
|
||||
}, [search, spans]);
|
||||
|
||||
const clearSearch = useCallback(() => {
|
||||
setSearch('');
|
||||
}, [setSearch]);
|
||||
|
||||
return { search, setSearch, spanFindMatches, clearSearch };
|
||||
return { search, setSearch, spanFindMatches };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { DataFrame, DataFrameView, TraceSpanRow } from '@grafana/data';
|
||||
import { Trace, TraceProcess, TraceResponse, transformTraceData } from '@jaegertracing/jaeger-ui-components';
|
||||
|
||||
export function transformDataFrames(frame?: DataFrame): Trace | null {
|
||||
if (!frame) {
|
||||
return null;
|
||||
}
|
||||
let data: TraceResponse =
|
||||
frame.fields.length === 1
|
||||
? // For backward compatibility when we sent whole json response in a single field/value
|
||||
frame.fields[0].values.get(0)
|
||||
: transformTraceDataFrame(frame);
|
||||
return transformTraceData(data);
|
||||
}
|
||||
|
||||
function transformTraceDataFrame(frame: DataFrame): TraceResponse {
|
||||
const view = new DataFrameView<TraceSpanRow>(frame);
|
||||
const processes: Record<string, TraceProcess> = {};
|
||||
for (let i = 0; i < view.length; i++) {
|
||||
const span = view.get(i);
|
||||
if (!processes[span.spanID]) {
|
||||
processes[span.spanID] = {
|
||||
serviceName: span.serviceName,
|
||||
tags: span.serviceTags,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
traceID: view.get(0).traceID,
|
||||
processes,
|
||||
spans: view.toArray().map((s, index) => {
|
||||
const references = [];
|
||||
if (s.parentSpanID) {
|
||||
references.push({ refType: 'CHILD_OF' as const, spanID: s.parentSpanID, traceID: s.traceID });
|
||||
}
|
||||
if (s.references) {
|
||||
references.push(...s.references.map((reference) => ({ refType: 'FOLLOWS_FROM' as const, ...reference })));
|
||||
}
|
||||
return {
|
||||
...s,
|
||||
duration: s.duration * 1000,
|
||||
startTime: s.startTime * 1000,
|
||||
processID: s.spanID,
|
||||
flags: 0,
|
||||
references,
|
||||
logs: s.logs?.map((l) => ({ ...l, timestamp: l.timestamp * 1000 })) || [],
|
||||
dataFrameRowIndex: index,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user