Skip to content

Commit 7b264ef

Browse files
JonasBaZylphrexgetsantry[bot]
authored
fix(trace) account for head or tail end of icons (#72884)
Span bars sometimes have icons at the head or tail end, which means they can overlap with the duration bars and cause visual issues. This PR computes the span bar space according to the min and max timestamps so that the duration labels no longer overlap. Before: https://github.com/getsentry/sentry/assets/9317857/d8ff6efc-7145-49ff-a7e1-4ab6a8c21f0a After: https://github.com/getsentry/sentry/assets/9317857/fd23c752-e5b3-4623-885f-e51460074a75 --------- Co-authored-by: Tony Xiao <[email protected]> Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
1 parent 6703e65 commit 7b264ef

File tree

3 files changed

+92
-62
lines changed

3 files changed

+92
-62
lines changed

static/app/views/performance/newTraceDetails/trace.tsx

+21-11
Original file line numberDiff line numberDiff line change
@@ -1433,17 +1433,16 @@ function BackgroundPatterns(props: BackgroundPatternsProps) {
14331433
);
14341434

14351435
return (
1436-
<Fragment key={i}>
1437-
<div
1438-
className="TracePatternContainer"
1439-
style={{
1440-
left: left * 100 + '%',
1441-
width: (1 - left) * 100 + '%',
1442-
}}
1443-
>
1444-
<div className="TracePattern performance_issue" />
1445-
</div>
1446-
</Fragment>
1436+
<div
1437+
key={i}
1438+
className="TracePatternContainer"
1439+
style={{
1440+
left: left * 100 + '%',
1441+
width: (1 - left) * 100 + '%',
1442+
}}
1443+
>
1444+
<div className="TracePattern performance_issue" />
1445+
</div>
14471446
);
14481447
})}
14491448
</Fragment>
@@ -2247,6 +2246,17 @@ const TraceStylingWrapper = styled('div')`
22472246
fill: ${p => p.theme.white};
22482247
}
22492248
}
2249+
2250+
&.error {
2251+
color: ${p => p.theme.red300};
2252+
2253+
.TraceChildrenCountWrapper {
2254+
button {
2255+
color: ${p => p.theme.white};
2256+
background-color: ${p => p.theme.red300};
2257+
}
2258+
}
2259+
}
22502260
}
22512261
}
22522262

static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,9 @@ export function makeTraceNodeBarColor(
274274
return pickBarColor(node.value.op);
275275
}
276276
if (isAutogroupedNode(node)) {
277+
if (node.errors.size > 0) {
278+
return theme.red300;
279+
}
277280
return theme.blue300;
278281
}
279282
if (isMissingInstrumentationNode(node)) {

static/app/views/performance/newTraceDetails/traceRenderers/virtualizedViewManager.tsx

+68-51
Original file line numberDiff line numberDiff line change
@@ -1170,64 +1170,37 @@ export class VirtualizedViewManager {
11701170
span_space: [number, number],
11711171
text: string
11721172
): [number, number] {
1173-
const text_left = span_space[0] > this.to_origin + this.trace_space.width * 0.8;
1174-
const width = this.text_measurer.measure(text);
1175-
1176-
const has_profiles = node && node.profiles.length > 0;
1177-
const has_error_icons =
1178-
node &&
1179-
(node.profiles.length > 0 ||
1180-
node.errors.size > 0 ||
1181-
node.performance_issues.size > 0);
1173+
const TEXT_PADDING = 2;
11821174

1183-
const has_icons = has_profiles || has_error_icons;
1175+
const icon_width_config_space = (18 * this.span_to_px[0]) / 2;
1176+
const text_anchor_left =
1177+
span_space[0] > this.to_origin + this.trace_space.width * 0.8;
1178+
const text_width = this.text_measurer.measure(text);
11841179

1185-
const node_width = span_space[1] / this.span_to_px[0];
1186-
const TEXT_PADDING = 2;
1187-
// This is inaccurate in the case of left anchored text. In order to determine a true overlap, we would need to compute
1188-
// the distance between the min timestamp of an icon and beginning of the span. Once we determine the distance, we can compute
1189-
// the width and see if there is an actual overlap. Since this is a rare case which only happens in the case where we anchor the text
1190-
// to the left (20% of the time) and the node may have many errors, this could be computationally expensive to do on every frame.
1191-
// We'll live with the inaccuracy for now as it is purely visual and just make sure to handle a single error case as it will be easy
1192-
// to determine if there is an overlap.
1193-
const TEXT_PADDING_LEFT = text_left && has_icons ? 10 : TEXT_PADDING;
1194-
1195-
const TEXT_PADDING_RIGHT =
1196-
!text_left && has_icons
1197-
? node_width < 10
1198-
? // If the node is too small, we need to make sure the text is anchored to the right edge of the icon.
1199-
// We take the distance from the right edge of the node to the right edge of the icon and subtract it from
1200-
// the base width (10) and the base padding when (expanded) to get the correct padding. If we take only 10px
1201-
// as our padding, the text can be anchored directly to the right edge of our icon - we want to preserve
1202-
// a min padding of 2px.
1203-
12 - node_width
1204-
: TEXT_PADDING
1205-
: TEXT_PADDING;
1180+
const timestamps = getIconTimestamps(node, span_space, icon_width_config_space);
1181+
const text_left = Math.min(span_space[0], timestamps[0]);
1182+
const text_right = Math.max(span_space[0] + span_space[1], timestamps[1]);
12061183

12071184
// precompute all anchor points aot, so we make the control flow more readable.
1208-
// this wastes some cycles, but it's not a big deal as computers go brrrr when it comes to simple arithmetic.
12091185
/// |---| text
1210-
const right_outside =
1211-
this.computeTransformXFromTimestamp(span_space[0] + span_space[1]) +
1212-
TEXT_PADDING_RIGHT;
1213-
/// text |---|
1214-
const left_outside =
1215-
this.computeTransformXFromTimestamp(span_space[0]) - TEXT_PADDING_LEFT - width;
1216-
1217-
// | text|
1186+
const right_outside = this.computeTransformXFromTimestamp(text_right) + TEXT_PADDING;
1187+
// |---text|
12181188
const right_inside =
12191189
this.computeTransformXFromTimestamp(span_space[0] + span_space[1]) -
1220-
width -
1190+
text_width -
12211191
TEXT_PADDING;
1222-
// |text |
1192+
// |text---|
12231193
const left_inside = this.computeTransformXFromTimestamp(span_space[0]) + TEXT_PADDING;
1194+
/// text |---|
1195+
const left_outside =
1196+
this.computeTransformXFromTimestamp(text_left) - TEXT_PADDING - text_width;
12241197

12251198
// Right edge of the window (when span extends beyond the view)
12261199
const window_right =
12271200
this.computeTransformXFromTimestamp(
12281201
this.to_origin + this.trace_view.left + this.trace_view.width
12291202
) -
1230-
width -
1203+
text_width -
12311204
TEXT_PADDING;
12321205
const window_left =
12331206
this.computeTransformXFromTimestamp(this.to_origin + this.trace_view.left) +
@@ -1244,22 +1217,22 @@ export class VirtualizedViewManager {
12441217

12451218
// Span is completely outside of the view on the left side
12461219
if (span_right < this.trace_view.x) {
1247-
return text_left ? [1, right_inside] : [0, right_outside];
1220+
return text_anchor_left ? [1, right_inside] : [0, right_outside];
12481221
}
12491222

12501223
// Span is completely outside of the view on the right side
12511224
if (span_left > this.trace_view.right) {
1252-
return text_left ? [0, left_outside] : [1, left_inside];
1225+
return text_anchor_left ? [0, left_outside] : [1, left_inside];
12531226
}
12541227

12551228
// Span "spans" the entire view
12561229
if (span_left <= this.trace_view.x && span_right >= this.trace_view.right) {
1257-
return text_left ? [1, window_left] : [1, window_right];
1230+
return text_anchor_left ? [1, window_left] : [1, window_right];
12581231
}
12591232

12601233
const full_span_px_width = span_space[1] / this.span_to_px[0];
12611234

1262-
if (text_left) {
1235+
if (text_anchor_left) {
12631236
// While we have space on the left, place the text there
12641237
if (space_left > 0) {
12651238
return [0, left_outside];
@@ -1270,7 +1243,7 @@ export class VirtualizedViewManager {
12701243

12711244
// If the text fits inside the visible portion of the span, anchor it to the left
12721245
// side of the window so that it is visible while the user pans the view
1273-
if (visible_width - TEXT_PADDING >= width) {
1246+
if (visible_width - TEXT_PADDING >= text_width) {
12741247
return [1, window_left];
12751248
}
12761249

@@ -1292,22 +1265,22 @@ export class VirtualizedViewManager {
12921265
// origin and check if it fits into the distance of space right edge - span right edge. In practice
12931266
// however, it seems that a magical number works just fine.
12941267
span_right > this.trace_space.right * 0.9 &&
1295-
space_right / this.span_to_px[0] < width
1268+
space_right / this.span_to_px[0] < text_width
12961269
) {
12971270
return [1, right_inside];
12981271
}
12991272
return [0, right_outside];
13001273
}
13011274

13021275
// If text fits inside the span
1303-
if (full_span_px_width > width) {
1276+
if (full_span_px_width > text_width) {
13041277
const distance = span_right - this.trace_view.right;
13051278
const visible_width =
13061279
(span_space[1] - distance) / this.span_to_px[0] - TEXT_PADDING;
13071280

13081281
// If the text fits inside the visible portion of the span, anchor it to the right
13091282
// side of the window so that it is visible while the user pans the view
1310-
if (visible_width - TEXT_PADDING >= width) {
1283+
if (visible_width - TEXT_PADDING >= text_width) {
13111284
return [1, window_right];
13121285
}
13131286

@@ -1704,6 +1677,50 @@ export class VirtualizedViewManager {
17041677
}
17051678
}
17061679

1680+
// Computes a min and max icon timestamp. This effectively extends or reduces the hitbox
1681+
// of the span to include the icon. We need this because when the icon is close to the edge
1682+
// it can extend it and cause overlaps with duration labels
1683+
function getIconTimestamps(
1684+
node: TraceTreeNode<any>,
1685+
span_space: [number, number],
1686+
icon_width: number
1687+
) {
1688+
let min_icon_timestamp = span_space[0];
1689+
let max_icon_timestamp = span_space[0] + span_space[1];
1690+
1691+
if (!node.errors.size && !node.performance_issues.size) {
1692+
return [min_icon_timestamp, max_icon_timestamp];
1693+
}
1694+
1695+
for (const issue of node.performance_issues) {
1696+
// Perf issues render icons at the start timestamp
1697+
if (typeof issue.start === 'number') {
1698+
min_icon_timestamp = Math.min(min_icon_timestamp, issue.start * 1e3 - icon_width);
1699+
max_icon_timestamp = Math.max(max_icon_timestamp, issue.start * 1e3 + icon_width);
1700+
}
1701+
}
1702+
1703+
for (const err of node.errors) {
1704+
if (typeof err.timestamp === 'number') {
1705+
min_icon_timestamp = Math.min(min_icon_timestamp, err.timestamp * 1e3 - icon_width);
1706+
max_icon_timestamp = Math.max(max_icon_timestamp, err.timestamp * 1e3 + icon_width);
1707+
}
1708+
}
1709+
1710+
min_icon_timestamp = clamp(
1711+
min_icon_timestamp,
1712+
span_space[0] - icon_width,
1713+
span_space[0] + span_space[1] + icon_width
1714+
);
1715+
max_icon_timestamp = clamp(
1716+
max_icon_timestamp,
1717+
span_space[0] - icon_width,
1718+
span_space[0] + span_space[1] + icon_width
1719+
);
1720+
1721+
return [min_icon_timestamp, max_icon_timestamp];
1722+
}
1723+
17071724
// Jest does not implement scroll updates, however since we have the
17081725
// middleware to handle scroll updates, we can dispatch a scroll event ourselves
17091726
function dispatchJestScrollUpdate(container: HTMLElement) {

0 commit comments

Comments
 (0)