Skip to content

Commit 9b4a214

Browse files
authored
feat(apm): Initial transactions view (#13920)
Closes SEN-808 Closes SEN-846
1 parent b5a0a68 commit 9b4a214

File tree

9 files changed

+1898
-0
lines changed

9 files changed

+1898
-0
lines changed

src/sentry/static/sentry/app/components/events/eventEntries.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import SentryTypes from 'app/sentryTypes';
3232
import StacktraceInterface from 'app/components/events/interfaces/stacktrace';
3333
import TemplateInterface from 'app/components/events/interfaces/template';
3434
import ThreadsInterface from 'app/components/events/interfaces/threads';
35+
import SpansInterface from 'app/components/events/interfaces/spans';
3536
import withApi from 'app/utils/withApi';
3637
import withOrganization from 'app/utils/withOrganization';
3738

@@ -48,6 +49,7 @@ export const INTERFACES = {
4849
breadcrumbs: BreadcrumbsInterface,
4950
threads: ThreadsInterface,
5051
debugmeta: DebugMetaInterface,
52+
spans: SpansInterface,
5153
};
5254

5355
class EventEntries extends React.Component {
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import React from 'react';
2+
3+
import {rectOfContent, clamp} from './utils';
4+
5+
// we establish the minimum window size so that the window size of 0% is not possible
6+
const MINIMUM_WINDOW_SIZE = 0.5 / 100; // 0.5% window size
7+
8+
enum ViewHandleType {
9+
Left,
10+
Right,
11+
}
12+
13+
export type DragManagerChildrenProps = {
14+
isDragging: boolean;
15+
16+
// left-side handle
17+
18+
onLeftHandleDragStart: (event: React.MouseEvent<SVGRectElement, MouseEvent>) => void;
19+
leftHandlePosition: number; // between 0 to 1
20+
viewWindowStart: number; // between 0 to 1
21+
22+
// right-side handle
23+
24+
onRightHandleDragStart: (event: React.MouseEvent<SVGRectElement, MouseEvent>) => void;
25+
rightHandlePosition: number; // between 0 to 1
26+
viewWindowEnd: number; // between 0 to 1
27+
};
28+
29+
type DragManagerProps = {
30+
children: (props: DragManagerChildrenProps) => JSX.Element;
31+
interactiveLayerRef: React.RefObject<HTMLDivElement>;
32+
};
33+
34+
type DragManagerState = {
35+
isDragging: boolean;
36+
currentDraggingHandle: ViewHandleType | undefined;
37+
leftHandlePosition: number;
38+
rightHandlePosition: number;
39+
40+
viewWindowStart: number;
41+
viewWindowEnd: number;
42+
};
43+
44+
class DragManager extends React.Component<DragManagerProps, DragManagerState> {
45+
state: DragManagerState = {
46+
isDragging: false,
47+
currentDraggingHandle: void 0,
48+
leftHandlePosition: 0, // positioned on the left-most side at 0%
49+
rightHandlePosition: 1, // positioned on the right-most side at 100%
50+
51+
viewWindowStart: 0,
52+
viewWindowEnd: 1,
53+
};
54+
55+
previousUserSelect: string | null = null;
56+
57+
hasInteractiveLayer = (): boolean => {
58+
return !!this.props.interactiveLayerRef.current;
59+
};
60+
61+
onDragStart = (viewHandle: ViewHandleType) => (
62+
event: React.MouseEvent<SVGRectElement, MouseEvent>
63+
) => {
64+
if (
65+
this.state.isDragging ||
66+
event.type !== 'mousedown' ||
67+
!this.hasInteractiveLayer()
68+
) {
69+
return;
70+
}
71+
72+
// prevent the user from selecting things outside the minimap when dragging
73+
// the mouse cursor outside the minimap
74+
75+
this.previousUserSelect = document.body.style.userSelect;
76+
document.body.style.userSelect = 'none';
77+
78+
// attach event listeners so that the mouse cursor can drag outside of the
79+
// minimap
80+
window.addEventListener('mousemove', this.onDragMove);
81+
window.addEventListener('mouseup', this.onDragEnd);
82+
83+
// indicate drag has begun
84+
85+
this.setState({
86+
isDragging: true,
87+
currentDraggingHandle: viewHandle,
88+
});
89+
};
90+
91+
onLeftHandleDragStart = (event: React.MouseEvent<SVGRectElement, MouseEvent>) => {
92+
this.onDragStart(ViewHandleType.Left)(event);
93+
};
94+
95+
onRightHandleDragStart = (event: React.MouseEvent<SVGRectElement, MouseEvent>) => {
96+
this.onDragStart(ViewHandleType.Right)(event);
97+
};
98+
99+
onDragMove = (event: MouseEvent) => {
100+
if (
101+
!this.state.isDragging ||
102+
event.type !== 'mousemove' ||
103+
!this.hasInteractiveLayer()
104+
) {
105+
return;
106+
}
107+
108+
const rect = rectOfContent(this.props.interactiveLayerRef.current!);
109+
110+
// mouse x-coordinate relative to the interactive layer's left side
111+
const rawMouseX = (event.pageX - rect.x) / rect.width;
112+
113+
switch (this.state.currentDraggingHandle) {
114+
case ViewHandleType.Left: {
115+
const min = 0;
116+
const max = this.state.rightHandlePosition - MINIMUM_WINDOW_SIZE;
117+
118+
this.setState({
119+
// clamp rawMouseX to be within [0, rightHandlePosition - MINIMUM_WINDOW_SIZE]
120+
leftHandlePosition: clamp(rawMouseX, min, max),
121+
});
122+
break;
123+
}
124+
case ViewHandleType.Right: {
125+
const min = this.state.leftHandlePosition + MINIMUM_WINDOW_SIZE;
126+
const max = 1;
127+
128+
this.setState({
129+
// clamp rawMouseX to be within [leftHandlePosition + MINIMUM_WINDOW_SIZE, 1]
130+
rightHandlePosition: clamp(rawMouseX, min, max),
131+
});
132+
break;
133+
}
134+
default: {
135+
throw Error('this.state.currentDraggingHandle is undefined');
136+
}
137+
}
138+
};
139+
140+
onDragEnd = (event: MouseEvent) => {
141+
if (
142+
!this.state.isDragging ||
143+
event.type !== 'mouseup' ||
144+
!this.hasInteractiveLayer()
145+
) {
146+
return;
147+
}
148+
149+
// remove listeners that were attached in onDragStart
150+
151+
window.removeEventListener('mousemove', this.onDragMove);
152+
window.removeEventListener('mouseup', this.onDragEnd);
153+
154+
// restore body styles
155+
156+
document.body.style.userSelect = this.previousUserSelect;
157+
this.previousUserSelect = null;
158+
159+
// indicate drag has ended
160+
161+
switch (this.state.currentDraggingHandle) {
162+
case ViewHandleType.Left: {
163+
this.setState(state => {
164+
return {
165+
isDragging: false,
166+
currentDraggingHandle: void 0,
167+
168+
// commit leftHandlePosition to be viewWindowStart
169+
viewWindowStart: state.leftHandlePosition,
170+
};
171+
});
172+
break;
173+
}
174+
case ViewHandleType.Right: {
175+
this.setState(state => {
176+
return {
177+
isDragging: false,
178+
currentDraggingHandle: void 0,
179+
180+
// commit rightHandlePosition to be viewWindowEnd
181+
viewWindowEnd: state.rightHandlePosition,
182+
};
183+
});
184+
break;
185+
}
186+
default: {
187+
throw Error('this.state.currentDraggingHandle is undefined');
188+
}
189+
}
190+
191+
this.setState({
192+
isDragging: false,
193+
currentDraggingHandle: void 0,
194+
});
195+
};
196+
197+
render() {
198+
const childrenProps = {
199+
isDragging: this.state.isDragging,
200+
201+
onLeftHandleDragStart: this.onLeftHandleDragStart,
202+
leftHandlePosition: this.state.leftHandlePosition,
203+
viewWindowStart: this.state.viewWindowStart,
204+
205+
onRightHandleDragStart: this.onRightHandleDragStart,
206+
rightHandlePosition: this.state.rightHandlePosition,
207+
viewWindowEnd: this.state.viewWindowEnd,
208+
};
209+
210+
return this.props.children(childrenProps);
211+
}
212+
}
213+
214+
export default DragManager;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
3+
import {t} from 'app/locale';
4+
import SentryTypes from 'app/sentryTypes';
5+
6+
import {Panel, PanelHeader, PanelBody} from 'app/components/panels';
7+
8+
import {SpanEntry, SentryEvent} from './types';
9+
import TransactionView from './transactionView';
10+
11+
type SpansInterfacePropTypes = {
12+
event: SentryEvent;
13+
} & SpanEntry;
14+
15+
class SpansInterface extends React.Component<SpansInterfacePropTypes> {
16+
static propTypes = {
17+
event: SentryTypes.Event.isRequired,
18+
};
19+
render() {
20+
const {event} = this.props;
21+
22+
return (
23+
<Panel>
24+
<PanelHeader disablePadding={false} hasButtons={false}>
25+
{t('Trace View')}
26+
</PanelHeader>
27+
<PanelBody>
28+
<TransactionView event={event} />
29+
</PanelBody>
30+
</Panel>
31+
);
32+
}
33+
}
34+
35+
export default SpansInterface;

0 commit comments

Comments
 (0)