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