Commit ba50e965 by Zoltán Bedi Committed by GitHub

Jaeger: Add stack trace to span detail row (#26427)

* Add stack trace to span detail row

* Modify accordian text not to have a whitespace

* Modify stackTrace to stackTraces

* Modify AccordianText ti get text component as prop

* Fix typecheck and test failure

* Span details text area do not wrap line
parent fabd879c
...@@ -57,6 +57,7 @@ export type TraceSpanData = { ...@@ -57,6 +57,7 @@ export type TraceSpanData = {
tags?: TraceKeyValuePair[]; tags?: TraceKeyValuePair[];
references?: TraceSpanReference[]; references?: TraceSpanReference[];
warnings?: string[] | null; warnings?: string[] | null;
stackTraces?: string[];
flags: number; flags: number;
}; };
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
}, },
"dependencies": { "dependencies": {
"@grafana/data": "7.2.0-pre.0", "@grafana/data": "7.2.0-pre.0",
"@grafana/ui": "7.2.0-pre.0",
"@types/classnames": "^2.2.7", "@types/classnames": "^2.2.7",
"@types/deep-freeze": "^0.1.1", "@types/deep-freeze": "^0.1.1",
"@types/hoist-non-react-statics": "^3.3.1", "@types/hoist-non-react-statics": "^3.3.1",
...@@ -22,6 +23,7 @@ ...@@ -22,6 +23,7 @@
"@types/moment": "^2.13.0", "@types/moment": "^2.13.0",
"@types/react-icons": "2.2.7", "@types/react-icons": "2.2.7",
"@types/recompose": "^0.30.7", "@types/recompose": "^0.30.7",
"@types/slate-react": "0.22.5",
"chance": "^1.0.10", "chance": "^1.0.10",
"classnames": "^2.2.5", "classnames": "^2.2.5",
"combokeys": "^3.0.0", "combokeys": "^3.0.0",
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { mount } from 'enzyme';
import AccordianText from './AccordianText'; import AccordianText from './AccordianText';
import TextList from './TextList'; import TextList from './TextList';
...@@ -32,7 +32,7 @@ describe('<AccordianText>', () => { ...@@ -32,7 +32,7 @@ describe('<AccordianText>', () => {
}; };
beforeEach(() => { beforeEach(() => {
wrapper = shallow(<AccordianText {...props} />); wrapper = mount(<AccordianText {...props} />);
}); });
it('renders without exploding', () => { it('renders without exploding', () => {
......
...@@ -40,17 +40,31 @@ const getStyles = createStyle((theme: Theme) => { ...@@ -40,17 +40,31 @@ const getStyles = createStyle((theme: Theme) => {
type AccordianTextProps = { type AccordianTextProps = {
className?: string | TNil; className?: string | TNil;
data: string[];
headerClassName?: string | TNil; headerClassName?: string | TNil;
data: string[];
highContrast?: boolean; highContrast?: boolean;
interactive?: boolean; interactive?: boolean;
isOpen: boolean; isOpen: boolean;
label: React.ReactNode; label: React.ReactNode | string;
onToggle?: null | (() => void); onToggle?: null | (() => void);
TextComponent?: React.ElementType<{ data: string[] }>;
}; };
function DefaultTextComponent({ data }: { data: string[] }) {
return <TextList data={data} />;
}
export default function AccordianText(props: AccordianTextProps) { export default function AccordianText(props: AccordianTextProps) {
const { className, data, headerClassName, interactive, isOpen, label, onToggle } = props; const {
className,
data,
headerClassName,
interactive,
isOpen,
label,
onToggle,
TextComponent = DefaultTextComponent,
} = props;
const isEmpty = !Array.isArray(data) || !data.length; const isEmpty = !Array.isArray(data) || !data.length;
const accordianKeyValuesStyles = getAccordianKeyValuesStyles(useTheme()); const accordianKeyValuesStyles = getAccordianKeyValuesStyles(useTheme());
const iconCls = cx(uAlignIcon, { [accordianKeyValuesStyles.emptyIcon]: isEmpty }); const iconCls = cx(uAlignIcon, { [accordianKeyValuesStyles.emptyIcon]: isEmpty });
...@@ -68,9 +82,10 @@ export default function AccordianText(props: AccordianTextProps) { ...@@ -68,9 +82,10 @@ export default function AccordianText(props: AccordianTextProps) {
return ( return (
<div className={className || ''}> <div className={className || ''}>
<div className={cx(styles.header, headerClassName)} {...headerProps} data-test-id="AccordianText--header"> <div className={cx(styles.header, headerClassName)} {...headerProps} data-test-id="AccordianText--header">
{arrow} <strong>{label}</strong> ({data.length}) {arrow}
<strong>{label}</strong> ({data.length})
</div> </div>
{isOpen && <TextList data={data} />} {isOpen && <TextComponent data={data} />}
</div> </div>
); );
} }
......
...@@ -22,6 +22,7 @@ export default class DetailState { ...@@ -22,6 +22,7 @@ export default class DetailState {
isProcessOpen: boolean; isProcessOpen: boolean;
logs: { isOpen: boolean; openedItems: Set<TraceLog> }; logs: { isOpen: boolean; openedItems: Set<TraceLog> };
isWarningsOpen: boolean; isWarningsOpen: boolean;
isStackTracesOpen: boolean;
isReferencesOpen: boolean; isReferencesOpen: boolean;
constructor(oldState?: DetailState) { constructor(oldState?: DetailState) {
...@@ -30,12 +31,14 @@ export default class DetailState { ...@@ -30,12 +31,14 @@ export default class DetailState {
isProcessOpen, isProcessOpen,
isReferencesOpen, isReferencesOpen,
isWarningsOpen, isWarningsOpen,
isStackTracesOpen,
logs, logs,
}: DetailState | Record<string, undefined> = oldState || {}; }: DetailState | Record<string, undefined> = oldState || {};
this.isTagsOpen = Boolean(isTagsOpen); this.isTagsOpen = Boolean(isTagsOpen);
this.isProcessOpen = Boolean(isProcessOpen); this.isProcessOpen = Boolean(isProcessOpen);
this.isReferencesOpen = Boolean(isReferencesOpen); this.isReferencesOpen = Boolean(isReferencesOpen);
this.isWarningsOpen = Boolean(isWarningsOpen); this.isWarningsOpen = Boolean(isWarningsOpen);
this.isStackTracesOpen = Boolean(isStackTracesOpen);
this.logs = { this.logs = {
isOpen: Boolean(logs && logs.isOpen), isOpen: Boolean(logs && logs.isOpen),
openedItems: logs && logs.openedItems ? new Set(logs.openedItems) : new Set(), openedItems: logs && logs.openedItems ? new Set(logs.openedItems) : new Set(),
...@@ -66,6 +69,12 @@ export default class DetailState { ...@@ -66,6 +69,12 @@ export default class DetailState {
return next; return next;
} }
toggleStackTraces() {
const next = new DetailState(this);
next.isStackTracesOpen = !this.isStackTracesOpen;
return next;
}
toggleLogs() { toggleLogs() {
const next = new DetailState(this); const next = new DetailState(this);
next.logs.isOpen = !this.logs.isOpen; next.logs.isOpen = !this.logs.isOpen;
......
...@@ -30,6 +30,7 @@ import AccordianReferences from './AccordianReferences'; ...@@ -30,6 +30,7 @@ import AccordianReferences from './AccordianReferences';
import { autoColor, createStyle, Theme, useTheme } from '../../Theme'; import { autoColor, createStyle, Theme, useTheme } from '../../Theme';
import { UIDivider } from '../../uiElementsContext'; import { UIDivider } from '../../uiElementsContext';
import { ubFlex, ubFlexAuto, ubItemsCenter, ubM0, ubMb1, ubMy1, ubTxRightAlign } from '../../uberUtilityStyles'; import { ubFlex, ubFlexAuto, ubItemsCenter, ubM0, ubMb1, ubMy1, ubTxRightAlign } from '../../uberUtilityStyles';
import { TextArea } from '@grafana/ui';
const getStyles = createStyle((theme: Theme) => { const getStyles = createStyle((theme: Theme) => {
return { return {
...@@ -94,6 +95,10 @@ const getStyles = createStyle((theme: Theme) => { ...@@ -94,6 +95,10 @@ const getStyles = createStyle((theme: Theme) => {
label: AccordianWarningsLabel; label: AccordianWarningsLabel;
color: ${autoColor(theme, '#d36c08')}; color: ${autoColor(theme, '#d36c08')};
`, `,
Textarea: css`
word-break: break-all;
white-space: pre;
`,
}; };
}); });
...@@ -107,6 +112,7 @@ type SpanDetailProps = { ...@@ -107,6 +112,7 @@ type SpanDetailProps = {
tagsToggle: (spanID: string) => void; tagsToggle: (spanID: string) => void;
traceStartTime: number; traceStartTime: number;
warningsToggle: (spanID: string) => void; warningsToggle: (spanID: string) => void;
stackTracesToggle: (spanID: string) => void;
referencesToggle: (spanID: string) => void; referencesToggle: (spanID: string) => void;
focusSpan: (uiFind: string) => void; focusSpan: (uiFind: string) => void;
}; };
...@@ -122,11 +128,30 @@ export default function SpanDetail(props: SpanDetailProps) { ...@@ -122,11 +128,30 @@ export default function SpanDetail(props: SpanDetailProps) {
tagsToggle, tagsToggle,
traceStartTime, traceStartTime,
warningsToggle, warningsToggle,
stackTracesToggle,
referencesToggle, referencesToggle,
focusSpan, focusSpan,
} = props; } = props;
const { isTagsOpen, isProcessOpen, logs: logsState, isWarningsOpen, isReferencesOpen } = detailState; const {
const { operationName, process, duration, relativeStartTime, spanID, logs, tags, warnings, references } = span; isTagsOpen,
isProcessOpen,
logs: logsState,
isWarningsOpen,
isReferencesOpen,
isStackTracesOpen,
} = detailState;
const {
operationName,
process,
duration,
relativeStartTime,
spanID,
logs,
tags,
warnings,
references,
stackTraces,
} = span;
const overviewItems = [ const overviewItems = [
{ {
key: 'svc', key: 'svc',
...@@ -195,6 +220,24 @@ export default function SpanDetail(props: SpanDetailProps) { ...@@ -195,6 +220,24 @@ export default function SpanDetail(props: SpanDetailProps) {
onToggle={() => warningsToggle(spanID)} onToggle={() => warningsToggle(spanID)}
/> />
)} )}
{stackTraces && stackTraces.length && (
<AccordianText
label="Stack trace"
data={stackTraces}
isOpen={isStackTracesOpen}
TextComponent={textComponentProps => (
<TextArea
className={styles.Textarea}
style={{ cursor: 'unset' }}
readOnly
cols={10}
rows={10}
value={textComponentProps.data}
/>
)}
onToggle={() => stackTracesToggle(spanID)}
/>
)}
{references && references.length > 1 && ( {references && references.length > 1 && (
<AccordianReferences <AccordianReferences
data={references} data={references}
......
...@@ -76,6 +76,7 @@ type SpanDetailRowProps = { ...@@ -76,6 +76,7 @@ type SpanDetailRowProps = {
processToggle: (spanID: string) => void; processToggle: (spanID: string) => void;
referencesToggle: (spanID: string) => void; referencesToggle: (spanID: string) => void;
warningsToggle: (spanID: string) => void; warningsToggle: (spanID: string) => void;
stackTracesToggle: (spanID: string) => void;
span: TraceSpan; span: TraceSpan;
tagsToggle: (spanID: string) => void; tagsToggle: (spanID: string) => void;
traceStartTime: number; traceStartTime: number;
...@@ -106,6 +107,7 @@ export class UnthemedSpanDetailRow extends React.PureComponent<SpanDetailRowProp ...@@ -106,6 +107,7 @@ export class UnthemedSpanDetailRow extends React.PureComponent<SpanDetailRowProp
processToggle, processToggle,
referencesToggle, referencesToggle,
warningsToggle, warningsToggle,
stackTracesToggle,
span, span,
tagsToggle, tagsToggle,
traceStartTime, traceStartTime,
...@@ -147,6 +149,7 @@ export class UnthemedSpanDetailRow extends React.PureComponent<SpanDetailRowProp ...@@ -147,6 +149,7 @@ export class UnthemedSpanDetailRow extends React.PureComponent<SpanDetailRowProp
processToggle={processToggle} processToggle={processToggle}
referencesToggle={referencesToggle} referencesToggle={referencesToggle}
warningsToggle={warningsToggle} warningsToggle={warningsToggle}
stackTracesToggle={stackTracesToggle}
span={span} span={span}
tagsToggle={tagsToggle} tagsToggle={tagsToggle}
traceStartTime={traceStartTime} traceStartTime={traceStartTime}
......
...@@ -68,6 +68,7 @@ type TVirtualizedTraceViewOwnProps = { ...@@ -68,6 +68,7 @@ type TVirtualizedTraceViewOwnProps = {
detailLogItemToggle: (spanID: string, log: TraceLog) => void; detailLogItemToggle: (spanID: string, log: TraceLog) => void;
detailLogsToggle: (spanID: string) => void; detailLogsToggle: (spanID: string) => void;
detailWarningsToggle: (spanID: string) => void; detailWarningsToggle: (spanID: string) => void;
detailStackTracesToggle: (spanID: string) => void;
detailReferencesToggle: (spanID: string) => void; detailReferencesToggle: (spanID: string) => void;
detailProcessToggle: (spanID: string) => void; detailProcessToggle: (spanID: string) => void;
detailTagsToggle: (spanID: string) => void; detailTagsToggle: (spanID: string) => void;
...@@ -392,6 +393,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra ...@@ -392,6 +393,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
detailProcessToggle, detailProcessToggle,
detailReferencesToggle, detailReferencesToggle,
detailWarningsToggle, detailWarningsToggle,
detailStackTracesToggle,
detailStates, detailStates,
detailTagsToggle, detailTagsToggle,
detailToggle, detailToggle,
...@@ -423,6 +425,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra ...@@ -423,6 +425,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
processToggle={detailProcessToggle} processToggle={detailProcessToggle}
referencesToggle={detailReferencesToggle} referencesToggle={detailReferencesToggle}
warningsToggle={detailWarningsToggle} warningsToggle={detailWarningsToggle}
stackTracesToggle={detailStackTracesToggle}
span={span} span={span}
tagsToggle={detailTagsToggle} tagsToggle={detailTagsToggle}
traceStartTime={trace.startTime} traceStartTime={trace.startTime}
......
...@@ -89,6 +89,7 @@ type TProps = TExtractUiFindFromStateReturn & { ...@@ -89,6 +89,7 @@ type TProps = TExtractUiFindFromStateReturn & {
detailLogItemToggle: (spanID: string, log: TraceLog) => void; detailLogItemToggle: (spanID: string, log: TraceLog) => void;
detailLogsToggle: (spanID: string) => void; detailLogsToggle: (spanID: string) => void;
detailWarningsToggle: (spanID: string) => void; detailWarningsToggle: (spanID: string) => void;
detailStackTracesToggle: (spanID: string) => void;
detailReferencesToggle: (spanID: string) => void; detailReferencesToggle: (spanID: string) => void;
detailProcessToggle: (spanID: string) => void; detailProcessToggle: (spanID: string) => void;
detailTagsToggle: (spanID: string) => void; detailTagsToggle: (spanID: string) => void;
......
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": "node_modules/@types", "baseUrl": "node_modules/@types",
"paths": {
"@grafana/slate-react": ["slate-react"]
},
"typeRoots": ["node_modules/@types"] "typeRoots": ["node_modules/@types"]
}, },
"extends": "@grafana/tsconfig", "extends": "@grafana/tsconfig",
"include": ["src/**/*.ts*", "typings", "../../public/app/types/jquery/*.ts"] "include": ["src/**/*.ts*", "typings", "../../public/app/types/jquery/*.ts", "../../public/app/types/svg.d.ts"]
} }
...@@ -33,6 +33,7 @@ export function TraceView(props: Props) { ...@@ -33,6 +33,7 @@ export function TraceView(props: Props) {
detailReferencesToggle, detailReferencesToggle,
detailTagsToggle, detailTagsToggle,
detailWarningsToggle, detailWarningsToggle,
detailStackTracesToggle,
} = useDetailState(); } = useDetailState();
const { removeHoverIndentGuideId, addHoverIndentGuideId, hoverIndentGuideIds } = useHoverIndentGuide(); const { removeHoverIndentGuideId, addHoverIndentGuideId, hoverIndentGuideIds } = useHoverIndentGuide();
const { viewRange, updateViewRangeTime, updateNextViewRangeTime } = useViewRange(); const { viewRange, updateViewRangeTime, updateNextViewRangeTime } = useViewRange();
...@@ -126,6 +127,7 @@ export function TraceView(props: Props) { ...@@ -126,6 +127,7 @@ export function TraceView(props: Props) {
detailLogItemToggle={detailLogItemToggle} detailLogItemToggle={detailLogItemToggle}
detailLogsToggle={detailLogsToggle} detailLogsToggle={detailLogsToggle}
detailWarningsToggle={detailWarningsToggle} detailWarningsToggle={detailWarningsToggle}
detailStackTracesToggle={detailStackTracesToggle}
detailReferencesToggle={detailReferencesToggle} detailReferencesToggle={detailReferencesToggle}
detailProcessToggle={detailProcessToggle} detailProcessToggle={detailProcessToggle}
detailTagsToggle={detailTagsToggle} detailTagsToggle={detailTagsToggle}
......
...@@ -44,6 +44,9 @@ export function useDetailState() { ...@@ -44,6 +44,9 @@ export function useDetailState() {
detailWarningsToggle: useCallback(makeDetailSubsectionToggle('warnings', detailStates, setDetailStates), [ detailWarningsToggle: useCallback(makeDetailSubsectionToggle('warnings', detailStates, setDetailStates), [
detailStates, detailStates,
]), ]),
detailStackTracesToggle: useCallback(makeDetailSubsectionToggle('stackTraces', detailStates, setDetailStates), [
detailStates,
]),
detailReferencesToggle: useCallback(makeDetailSubsectionToggle('references', detailStates, setDetailStates), [ detailReferencesToggle: useCallback(makeDetailSubsectionToggle('references', detailStates, setDetailStates), [
detailStates, detailStates,
]), ]),
...@@ -55,7 +58,7 @@ export function useDetailState() { ...@@ -55,7 +58,7 @@ export function useDetailState() {
} }
function makeDetailSubsectionToggle( function makeDetailSubsectionToggle(
subSection: 'tags' | 'process' | 'logs' | 'warnings' | 'references', subSection: 'tags' | 'process' | 'logs' | 'warnings' | 'references' | 'stackTraces',
detailStates: Map<string, DetailState>, detailStates: Map<string, DetailState>,
setDetailStates: (detailStates: Map<string, DetailState>) => void setDetailStates: (detailStates: Map<string, DetailState>) => void
) { ) {
...@@ -73,6 +76,8 @@ function makeDetailSubsectionToggle( ...@@ -73,6 +76,8 @@ function makeDetailSubsectionToggle(
detailState = old.toggleWarnings(); detailState = old.toggleWarnings();
} else if (subSection === 'references') { } else if (subSection === 'references') {
detailState = old.toggleReferences(); detailState = old.toggleReferences();
} else if (subSection === 'stackTraces') {
detailState = old.toggleStackTraces();
} else { } else {
detailState = old.toggleLogs(); detailState = old.toggleLogs();
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment