@parca/profile 0.19.133 → 0.19.134

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts.map +1 -1
  3. package/dist/ProfileExplorer/ProfileExplorerSingle.js +3 -9
  4. package/dist/ProfileFlameChart/SamplesStrips/index.d.ts +2 -2
  5. package/dist/ProfileFlameChart/SamplesStrips/index.d.ts.map +1 -1
  6. package/dist/ProfileFlameChart/SamplesStrips/index.js +61 -39
  7. package/dist/ProfileFlameChart/SamplesStrips/labelSetUtils.d.ts +7 -0
  8. package/dist/ProfileFlameChart/SamplesStrips/labelSetUtils.d.ts.map +1 -0
  9. package/dist/ProfileFlameChart/SamplesStrips/labelSetUtils.js +79 -0
  10. package/dist/ProfileFlameChart/index.d.ts +1 -2
  11. package/dist/ProfileFlameChart/index.d.ts.map +1 -1
  12. package/dist/ProfileFlameChart/index.js +14 -21
  13. package/dist/ProfileFlameGraph/FlameGraphArrow/MiniMap.d.ts +3 -0
  14. package/dist/ProfileFlameGraph/FlameGraphArrow/MiniMap.d.ts.map +1 -1
  15. package/dist/ProfileFlameGraph/FlameGraphArrow/MiniMap.js +89 -24
  16. package/dist/ProfileFlameGraph/FlameGraphArrow/ZoomControls.d.ts.map +1 -1
  17. package/dist/ProfileFlameGraph/FlameGraphArrow/ZoomControls.js +2 -1
  18. package/dist/ProfileFlameGraph/FlameGraphArrow/index.d.ts.map +1 -1
  19. package/dist/ProfileFlameGraph/FlameGraphArrow/index.js +2 -2
  20. package/dist/ProfileFlameGraph/FlameGraphArrow/useZoom.d.ts +4 -0
  21. package/dist/ProfileFlameGraph/FlameGraphArrow/useZoom.d.ts.map +1 -1
  22. package/dist/ProfileFlameGraph/FlameGraphArrow/useZoom.js +51 -10
  23. package/dist/ProfileFlameGraph/index.d.ts +0 -1
  24. package/dist/ProfileFlameGraph/index.d.ts.map +1 -1
  25. package/dist/ProfileFlameGraph/index.js +3 -8
  26. package/dist/ProfileView/components/DashboardItems/index.d.ts +1 -2
  27. package/dist/ProfileView/components/DashboardItems/index.d.ts.map +1 -1
  28. package/dist/ProfileView/components/DashboardItems/index.js +2 -2
  29. package/dist/ProfileView/index.d.ts +1 -1
  30. package/dist/ProfileView/index.d.ts.map +1 -1
  31. package/dist/ProfileView/index.js +1 -2
  32. package/dist/ProfileView/types/visualization.d.ts +0 -1
  33. package/dist/ProfileView/types/visualization.d.ts.map +1 -1
  34. package/dist/ProfileViewWithData.d.ts +1 -2
  35. package/dist/ProfileViewWithData.d.ts.map +1 -1
  36. package/dist/ProfileViewWithData.js +2 -2
  37. package/dist/TimelineGuide/index.js +1 -1
  38. package/dist/styles.css +1 -1
  39. package/package.json +3 -3
  40. package/src/ProfileExplorer/ProfileExplorerSingle.tsx +3 -14
  41. package/src/ProfileFlameChart/SamplesStrips/index.tsx +90 -49
  42. package/src/ProfileFlameChart/SamplesStrips/labelSetUtils.test.ts +73 -0
  43. package/src/ProfileFlameChart/SamplesStrips/labelSetUtils.ts +86 -0
  44. package/src/ProfileFlameChart/index.tsx +16 -45
  45. package/src/ProfileFlameGraph/FlameGraphArrow/MiniMap.tsx +119 -25
  46. package/src/ProfileFlameGraph/FlameGraphArrow/ZoomControls.tsx +3 -1
  47. package/src/ProfileFlameGraph/FlameGraphArrow/index.tsx +5 -3
  48. package/src/ProfileFlameGraph/FlameGraphArrow/useZoom.ts +78 -17
  49. package/src/ProfileFlameGraph/index.tsx +4 -24
  50. package/src/ProfileView/components/DashboardItems/index.tsx +0 -3
  51. package/src/ProfileView/index.tsx +0 -2
  52. package/src/ProfileView/types/visualization.ts +0 -1
  53. package/src/ProfileViewWithData.tsx +0 -3
  54. package/src/TimelineGuide/index.tsx +1 -1
@@ -43,6 +43,9 @@ interface MiniMapProps {
43
43
  profileSource: ProfileSource;
44
44
  isDarkMode: boolean;
45
45
  scrollLeft: number;
46
+ scrollLeftRef: React.RefObject<number>;
47
+ onZoomToPosition?: (normalizedX: number, targetZoom: number) => void;
48
+ onSetZoomWithScroll?: (zoom: number, scrollLeft: number) => void;
46
49
  }
47
50
 
48
51
  export const MiniMap = React.memo(function MiniMap({
@@ -56,7 +59,10 @@ export const MiniMap = React.memo(function MiniMap({
56
59
  colorBy,
57
60
  profileSource,
58
61
  isDarkMode,
59
- scrollLeft,
62
+ scrollLeft: _scrollLeft,
63
+ scrollLeftRef,
64
+ onZoomToPosition,
65
+ onSetZoomWithScroll,
60
66
  }: MiniMapProps): React.JSX.Element | null {
61
67
  const canvasRef = useRef<HTMLCanvasElement>(null);
62
68
  const containerElRef = useRef<HTMLDivElement>(null);
@@ -83,7 +89,6 @@ export const MiniMap = React.memo(function MiniMap({
83
89
  ctx.fillStyle = isDarkMode ? '#374151' : '#f3f4f6';
84
90
  ctx.fillRect(0, 0, width, MINIMAP_HEIGHT);
85
91
 
86
- const xScale = width / zoomedWidth;
87
92
  const yScale = MINIMAP_HEIGHT / totalHeight;
88
93
 
89
94
  const tsBounds = boundsFromProfileSource(profileSource);
@@ -109,11 +114,11 @@ export const MiniMap = React.memo(function MiniMap({
109
114
  const cumulative = Number(cumulativeCol.get(row) ?? 0n);
110
115
  if (cumulative <= 0) continue;
111
116
 
112
- const nodeWidth = (cumulative / tsRange) * zoomedWidth * xScale;
117
+ const nodeWidth = (cumulative / tsRange) * width;
113
118
  if (nodeWidth < 0.5) continue;
114
119
 
115
120
  const ts = tsCol != null ? Number(tsCol.get(row)) : 0;
116
- const x = ((ts - Number(tsBounds[0])) / tsRange) * zoomedWidth * xScale;
121
+ const x = ((ts - Number(tsBounds[0])) / tsRange) * width;
117
122
  const y = (depth - 1) * RowHeight * yScale;
118
123
  const h = Math.max(1, RowHeight * yScale);
119
124
 
@@ -129,21 +134,17 @@ export const MiniMap = React.memo(function MiniMap({
129
134
  ctx.fillStyle = color ?? (isDarkMode ? '#6b7280' : '#9ca3af');
130
135
  ctx.fillRect(x, y, Math.max(0.5, nodeWidth), h);
131
136
  }
132
- }, [
133
- table,
134
- width,
135
- zoomedWidth,
136
- totalHeight,
137
- maxDepth,
138
- colorBy,
139
- colors,
140
- isDarkMode,
141
- profileSource,
142
- ]);
137
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- zoomedWidth intentionally excluded: canvas is zoom-independent
138
+ }, [table, width, totalHeight, maxDepth, colorBy, colors, isDarkMode, profileSource]);
143
139
 
144
140
  const isZoomed = zoomedWidth > width;
145
141
  const sliderWidth = Math.max(20, (width / zoomedWidth) * width);
146
- const sliderLeft = Math.min((scrollLeft / zoomedWidth) * width, width - sliderWidth);
142
+ // Use scrollLeftRef for positioning it's pre-set before flushSync during zoom changes,
143
+ // avoiding the 1-frame lag where viewport.scrollLeft is stale but zoomedWidth is already updated.
144
+ const currentScrollLeft = scrollLeftRef.current ?? 0;
145
+ const sliderLeft = Math.min((currentScrollLeft / zoomedWidth) * width, width - sliderWidth);
146
+
147
+ const EDGE_HIT_ZONE = 6;
147
148
 
148
149
  const handleMouseDown = useCallback(
149
150
  (e: React.MouseEvent) => {
@@ -153,12 +154,86 @@ export const MiniMap = React.memo(function MiniMap({
153
154
 
154
155
  const clickX = e.clientX - rect.left;
155
156
 
156
- // Check if clicking inside the slider
157
- if (clickX >= sliderLeft && clickX <= sliderLeft + sliderWidth) {
158
- // Start dragging
157
+ // When not zoomed, clicking the minimap zooms into a +-50px region
158
+ if (!isZoomed) {
159
+ const regionPx = 100; // 50px on each side of the click
160
+ const targetZoom = width / regionPx;
161
+ onZoomToPosition?.(clickX / width, targetZoom);
162
+ return;
163
+ }
164
+
165
+ const sliderRight = sliderLeft + sliderWidth;
166
+ const isNearLeftEdge =
167
+ Math.abs(clickX - sliderLeft) <= EDGE_HIT_ZONE && clickX <= sliderLeft + EDGE_HIT_ZONE;
168
+ const isNearRightEdge =
169
+ Math.abs(clickX - sliderRight) <= EDGE_HIT_ZONE && clickX >= sliderRight - EDGE_HIT_ZONE;
170
+
171
+ // Edge drag: resize the zoomed region by dragging one bound
172
+ if (isNearLeftEdge || isNearRightEdge) {
173
+ const edge = isNearLeftEdge ? 'left' : 'right';
174
+ // The opposite edge stays fixed in minimap coordinates
175
+ const anchorPx = edge === 'left' ? sliderRight : sliderLeft;
176
+ const MIN_SLIDER_PX = 10;
177
+
178
+ let edgeRafId: number | null = null;
179
+ let pendingEdgeEvent: MouseEvent | null = null;
180
+
181
+ const applyEdgeMove = (): void => {
182
+ edgeRafId = null;
183
+ const moveEvent = pendingEdgeEvent;
184
+ if (moveEvent == null) return;
185
+ pendingEdgeEvent = null;
186
+
187
+ const moveRect = containerElRef.current?.getBoundingClientRect();
188
+ if (moveRect == null) return;
189
+
190
+ let edgePx = moveEvent.clientX - moveRect.left;
191
+ edgePx = Math.max(0, Math.min(edgePx, width));
192
+
193
+ let newLeft: number;
194
+ let newRight: number;
195
+
196
+ if (edge === 'left') {
197
+ newLeft = Math.min(edgePx, anchorPx - MIN_SLIDER_PX);
198
+ newRight = anchorPx;
199
+ } else {
200
+ newLeft = anchorPx;
201
+ newRight = Math.max(edgePx, anchorPx + MIN_SLIDER_PX);
202
+ }
203
+
204
+ const newSliderWidth = newRight - newLeft;
205
+ const newZoom = width / newSliderWidth;
206
+ const newScrollLeft = newLeft * newZoom;
207
+ onSetZoomWithScroll?.(newZoom, newScrollLeft);
208
+ };
209
+
210
+ const handleEdgeMove = (moveEvent: MouseEvent): void => {
211
+ pendingEdgeEvent = moveEvent;
212
+ if (edgeRafId === null) {
213
+ edgeRafId = requestAnimationFrame(applyEdgeMove);
214
+ }
215
+ };
216
+
217
+ const handleEdgeUp = (): void => {
218
+ if (edgeRafId !== null) {
219
+ cancelAnimationFrame(edgeRafId);
220
+ // Apply final position immediately on mouse up
221
+ applyEdgeMove();
222
+ }
223
+ document.removeEventListener('mousemove', handleEdgeMove);
224
+ document.removeEventListener('mouseup', handleEdgeUp);
225
+ };
226
+
227
+ document.addEventListener('mousemove', handleEdgeMove);
228
+ document.addEventListener('mouseup', handleEdgeUp);
229
+ return;
230
+ }
231
+
232
+ // Check if clicking inside the slider — start pan drag
233
+ if (clickX >= sliderLeft && clickX <= sliderRight) {
159
234
  isDragging.current = true;
160
235
  dragStartX.current = e.clientX;
161
- dragStartScrollLeft.current = scrollLeft;
236
+ dragStartScrollLeft.current = currentScrollLeft;
162
237
  } else {
163
238
  // Click-to-jump: center viewport at click position
164
239
  const targetCenter = (clickX / width) * zoomedWidth;
@@ -198,7 +273,17 @@ export const MiniMap = React.memo(function MiniMap({
198
273
  document.addEventListener('mousemove', handleMouseMove);
199
274
  document.addEventListener('mouseup', handleMouseUp);
200
275
  },
201
- [sliderLeft, sliderWidth, scrollLeft, width, zoomedWidth, containerRef]
276
+ [
277
+ sliderLeft,
278
+ sliderWidth,
279
+ currentScrollLeft,
280
+ width,
281
+ zoomedWidth,
282
+ containerRef,
283
+ isZoomed,
284
+ onZoomToPosition,
285
+ onSetZoomWithScroll,
286
+ ]
202
287
  );
203
288
 
204
289
  // Forward wheel events to the container so zoom (Ctrl+scroll) works on the minimap
@@ -233,9 +318,9 @@ export const MiniMap = React.memo(function MiniMap({
233
318
  return (
234
319
  <div
235
320
  ref={containerElRef}
236
- className="relative select-none"
237
- style={{width, height: MINIMAP_HEIGHT, cursor: isZoomed ? 'pointer' : 'default'}}
238
- onMouseDown={isZoomed ? handleMouseDown : undefined}
321
+ className="relative select-none cursor-pointer"
322
+ style={{width, height: MINIMAP_HEIGHT}}
323
+ onMouseDown={handleMouseDown}
239
324
  >
240
325
  <canvas
241
326
  ref={canvasRef}
@@ -243,7 +328,6 @@ export const MiniMap = React.memo(function MiniMap({
243
328
  width,
244
329
  height: MINIMAP_HEIGHT,
245
330
  display: 'block',
246
- visibility: isZoomed ? 'visible' : 'hidden',
247
331
  }}
248
332
  />
249
333
  {isZoomed && (
@@ -258,6 +342,16 @@ export const MiniMap = React.memo(function MiniMap({
258
342
  className="absolute top-0 bottom-0 border-x-2 border-gray-500"
259
343
  style={{left: sliderLeft, width: sliderWidth}}
260
344
  />
345
+ {/* Left edge drag handle */}
346
+ <div
347
+ className="absolute top-0 bottom-0 cursor-col-resize"
348
+ style={{left: sliderLeft - EDGE_HIT_ZONE, width: EDGE_HIT_ZONE * 2}}
349
+ />
350
+ {/* Right edge drag handle */}
351
+ <div
352
+ className="absolute top-0 bottom-0 cursor-col-resize"
353
+ style={{left: sliderLeft + sliderWidth - EDGE_HIT_ZONE, width: EDGE_HIT_ZONE * 2}}
354
+ />
261
355
  {/* Right overlay */}
262
356
  <div
263
357
  className="absolute top-0 bottom-0 bg-black/30 dark:bg-black/50"
@@ -16,6 +16,8 @@ import React from 'react';
16
16
  import {Icon} from '@iconify/react';
17
17
  import {createPortal} from 'react-dom';
18
18
 
19
+ import {MAX_ZOOM} from './useZoom';
20
+
19
21
  interface ZoomControlsProps {
20
22
  zoomLevel: number;
21
23
  zoomIn: () => void;
@@ -50,7 +52,7 @@ export const ZoomControls = ({
50
52
  </button>
51
53
  <button
52
54
  onClick={zoomIn}
53
- disabled={zoomLevel >= 20}
55
+ disabled={zoomLevel >= MAX_ZOOM}
54
56
  className="rounded p-1 text-gray-600 hover:bg-gray-100 disabled:opacity-30 dark:text-gray-300 dark:hover:bg-gray-700"
55
57
  title="Zoom in"
56
58
  >
@@ -273,9 +273,8 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({
273
273
 
274
274
  const isZoomEnabled = isFlameChart;
275
275
 
276
- const {zoomLevel, zoomIn, zoomOut, resetZoom} = useZoom(
277
- isZoomEnabled ? containerRef : {current: null}
278
- );
276
+ const {zoomLevel, zoomIn, zoomOut, resetZoom, zoomToPosition, setZoomWithScroll, scrollLeftRef} =
277
+ useZoom(isZoomEnabled ? containerRef : {current: null});
279
278
  const zoomedWidth = isZoomEnabled ? Math.round((width ?? 1) * zoomLevel) : width ?? 0;
280
279
 
281
280
  // Reset zoom when the data changes (e.g. new query, different time range)
@@ -400,6 +399,9 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({
400
399
  profileSource={profileSource}
401
400
  isDarkMode={isDarkMode}
402
401
  scrollLeft={viewport.scrollLeft}
402
+ scrollLeftRef={scrollLeftRef}
403
+ onZoomToPosition={zoomToPosition}
404
+ onSetZoomWithScroll={setZoomWithScroll}
403
405
  />
404
406
  )}
405
407
  <div
@@ -16,7 +16,7 @@ import {useCallback, useEffect, useRef, useState} from 'react';
16
16
  import {flushSync} from 'react-dom';
17
17
 
18
18
  const MIN_ZOOM = 1.0;
19
- const MAX_ZOOM = 20.0;
19
+ export const MAX_ZOOM = 100.0;
20
20
  const BUTTON_ZOOM_STEP = 1.5;
21
21
  // Sensitivity for trackpad/wheel zoom - smaller = smoother
22
22
  const WHEEL_ZOOM_SENSITIVITY = 0.01;
@@ -26,6 +26,9 @@ interface UseZoomResult {
26
26
  zoomIn: () => void;
27
27
  zoomOut: () => void;
28
28
  resetZoom: () => void;
29
+ zoomToPosition: (normalizedX: number, targetZoom: number) => void;
30
+ setZoomWithScroll: (zoom: number, scrollLeft: number) => void;
31
+ scrollLeftRef: React.RefObject<number>;
29
32
  }
30
33
 
31
34
  const clampZoom = (zoom: number): number => {
@@ -35,32 +38,42 @@ const clampZoom = (zoom: number): number => {
35
38
  export const useZoom = (containerRef: React.RefObject<HTMLDivElement | null>): UseZoomResult => {
36
39
  const [zoomLevel, setZoomLevel] = useState(MIN_ZOOM);
37
40
  const zoomLevelRef = useRef(MIN_ZOOM);
41
+ const scrollLeftRef = useRef(0);
38
42
 
39
- // Adjust scrollLeft so the content under focalX stays fixed after zoom change.
40
- const adjustScroll = useCallback(
41
- (oldZoom: number, newZoom: number, focalX: number) => {
42
- const container = containerRef.current;
43
- if (container === null) return;
43
+ // Keep scrollLeftRef in sync with actual scroll position during regular scrolling
44
+ useEffect(() => {
45
+ const container = containerRef.current;
46
+ if (container === null) return;
44
47
 
45
- const contentX = container.scrollLeft + focalX;
46
- const ratio = contentX / oldZoom;
47
- container.scrollLeft = ratio * newZoom - focalX;
48
- },
49
- [containerRef]
50
- );
48
+ const onScroll = (): void => {
49
+ scrollLeftRef.current = container.scrollLeft;
50
+ };
51
+ container.addEventListener('scroll', onScroll, {passive: true});
52
+ return () => container.removeEventListener('scroll', onScroll);
53
+ }, [containerRef]);
51
54
 
52
55
  // Apply a new zoom level around a focal point
53
56
  const applyZoom = useCallback(
54
57
  (newZoom: number, focalX: number) => {
58
+ const container = containerRef.current;
59
+ if (container === null) return;
60
+
55
61
  const oldZoom = zoomLevelRef.current;
56
62
  if (newZoom === oldZoom) return;
57
- zoomLevelRef.current = newZoom;
58
63
 
59
- // flushSync ensures the DOM updates with the new content width before adjustScroll reads it
64
+ // Pre-compute intended scrollLeft BEFORE flushSync so MiniMap reads correct value during render
65
+ const contentX = container.scrollLeft + focalX;
66
+ const ratio = contentX / oldZoom;
67
+ const newScrollLeft = ratio * newZoom - focalX;
68
+ scrollLeftRef.current = newScrollLeft;
69
+
70
+ zoomLevelRef.current = newZoom;
60
71
  flushSync(() => setZoomLevel(newZoom));
61
- adjustScroll(oldZoom, newZoom, focalX);
72
+
73
+ // Apply scroll to DOM after render (content is now wide enough)
74
+ container.scrollLeft = newScrollLeft;
62
75
  },
63
- [adjustScroll]
76
+ [containerRef]
64
77
  );
65
78
 
66
79
  const zoomIn = useCallback(() => {
@@ -77,6 +90,7 @@ export const useZoom = (containerRef: React.RefObject<HTMLDivElement | null>): U
77
90
 
78
91
  const resetZoom = useCallback(() => {
79
92
  zoomLevelRef.current = MIN_ZOOM;
93
+ scrollLeftRef.current = 0;
80
94
  setZoomLevel(MIN_ZOOM);
81
95
  const container = containerRef.current;
82
96
  if (container !== null) {
@@ -112,5 +126,52 @@ export const useZoom = (containerRef: React.RefObject<HTMLDivElement | null>): U
112
126
  };
113
127
  }, [containerRef, applyZoom]);
114
128
 
115
- return {zoomLevel, zoomIn, zoomOut, resetZoom};
129
+ const zoomToPosition = useCallback(
130
+ (normalizedX: number, targetZoom: number) => {
131
+ const container = containerRef.current;
132
+ if (container === null) return;
133
+
134
+ const newZoom = clampZoom(targetZoom);
135
+ if (newZoom === zoomLevelRef.current) return;
136
+
137
+ const containerWidth = container.clientWidth;
138
+ const contentWidth = containerWidth * newZoom;
139
+ const targetScrollLeft = Math.max(
140
+ 0,
141
+ Math.min(normalizedX * contentWidth - containerWidth / 2, contentWidth - containerWidth)
142
+ );
143
+
144
+ // Pre-set scrollLeftRef before flushSync so MiniMap reads correct value during render
145
+ scrollLeftRef.current = targetScrollLeft;
146
+ zoomLevelRef.current = newZoom;
147
+ flushSync(() => setZoomLevel(newZoom));
148
+
149
+ container.scrollLeft = targetScrollLeft;
150
+ },
151
+ [containerRef]
152
+ );
153
+
154
+ const setZoomWithScroll = useCallback(
155
+ (zoom: number, newScrollLeft: number) => {
156
+ const container = containerRef.current;
157
+ if (container === null) return;
158
+
159
+ const clamped = clampZoom(zoom);
160
+ const contentWidth = container.clientWidth * clamped;
161
+ const clampedScroll = Math.max(
162
+ 0,
163
+ Math.min(newScrollLeft, contentWidth - container.clientWidth)
164
+ );
165
+
166
+ // Pre-set scrollLeftRef before flushSync so MiniMap reads correct value during render
167
+ scrollLeftRef.current = clampedScroll;
168
+ zoomLevelRef.current = clamped;
169
+ flushSync(() => setZoomLevel(clamped));
170
+
171
+ container.scrollLeft = clampedScroll;
172
+ },
173
+ [containerRef]
174
+ );
175
+
176
+ return {zoomLevel, zoomIn, zoomOut, resetZoom, zoomToPosition, setZoomWithScroll, scrollLeftRef};
116
177
  };
@@ -74,11 +74,9 @@ const ErrorContent = ({errorMessage}: {errorMessage: string | ReactNode}): JSX.E
74
74
 
75
75
  export const validateFlameChartQuery = (
76
76
  profileSource: MergedProfileSource
77
- ): {isValid: boolean; isNonDelta: boolean; isDurationTooLong: boolean} => {
77
+ ): {isValid: boolean; isNonDelta: boolean} => {
78
78
  const isNonDelta = !profileSource.ProfileType().delta;
79
- const duration = profileSource.mergeTo - profileSource.mergeFrom;
80
- const isDurationTooLong = duration > 60_000_000_000n; // 60 seconds in nanoseconds
81
- return {isValid: !isNonDelta && !isDurationTooLong, isNonDelta, isDurationTooLong};
79
+ return {isValid: !isNonDelta, isNonDelta};
82
80
  };
83
81
 
84
82
  const ProfileFlameGraph = function ProfileFlameGraphNonMemo({
@@ -194,13 +192,9 @@ const ProfileFlameGraph = function ProfileFlameGraphNonMemo({
194
192
  }, [loadingState]);
195
193
 
196
194
  const flameGraph = useMemo(() => {
197
- const {
198
- isValid: isFlameChartValid,
199
- isNonDelta,
200
- isDurationTooLong,
201
- } = isFlameChart
195
+ const {isValid: isFlameChartValid, isNonDelta} = isFlameChart
202
196
  ? validateFlameChartQuery(profileSource as MergedProfileSource)
203
- : {isValid: true, isNonDelta: false, isDurationTooLong: false};
197
+ : {isValid: true, isNonDelta: false};
204
198
  const isInvalidFlameChartQuery = isFlameChart && !isFlameChartValid;
205
199
 
206
200
  if (isLoading && !isInvalidFlameChartQuery) {
@@ -228,20 +222,6 @@ const ProfileFlameGraph = function ProfileFlameGraphNonMemo({
228
222
  }
229
223
  />
230
224
  );
231
- } else if (isDurationTooLong) {
232
- return (
233
- <ErrorContent
234
- errorMessage={
235
- <>
236
- <span>
237
- Flame chart is unavailable for queries longer than one minute. Please select a
238
- point in the metrics graph to continue.
239
- </span>
240
- {flamechartHelpText ?? null}
241
- </>
242
- }
243
- />
244
- );
245
225
  } else {
246
226
  return (
247
227
  <ErrorContent
@@ -50,7 +50,6 @@ interface GetDashboardItemProps {
50
50
  onRender?: ProfilerOnRenderCallback;
51
51
  };
52
52
  queryClient: QueryServiceClient;
53
- onSwitchToOneMinute?: () => void;
54
53
  }
55
54
 
56
55
  export const getDashboardItem = ({
@@ -69,7 +68,6 @@ export const getDashboardItem = ({
69
68
  setNewCurPathArrow,
70
69
  perf,
71
70
  queryClient,
72
- onSwitchToOneMinute,
73
71
  }: GetDashboardItemProps): JSX.Element => {
74
72
  switch (type) {
75
73
  case 'flamegraph':
@@ -124,7 +122,6 @@ export const getDashboardItem = ({
124
122
  isHalfScreen={isHalfScreen}
125
123
  metadataMappingFiles={flamegraphData.metadataMappingFiles}
126
124
  metadataLoading={flamegraphData.metadataLoading}
127
- onSwitchToOneMinute={onSwitchToOneMinute}
128
125
  />
129
126
  );
130
127
  case 'table':
@@ -46,7 +46,6 @@ export const ProfileView = ({
46
46
  compare,
47
47
  showVisualizationSelector,
48
48
  sandwichData,
49
- onSwitchToOneMinute,
50
49
  }: ProfileViewProps): JSX.Element => {
51
50
  const {
52
51
  timezone,
@@ -116,7 +115,6 @@ export const ProfileView = ({
116
115
  setNewCurPathArrow: setCurPathArrow,
117
116
  perf,
118
117
  queryClient,
119
- onSwitchToOneMinute,
120
118
  });
121
119
  };
122
120
 
@@ -84,5 +84,4 @@ export interface ProfileViewProps {
84
84
  onDownloadPProf: () => void;
85
85
  pprofDownloading?: boolean;
86
86
  showVisualizationSelector?: boolean;
87
- onSwitchToOneMinute?: () => void;
88
87
  }
@@ -43,14 +43,12 @@ interface ProfileViewWithDataProps {
43
43
  profileSource: ProfileSource;
44
44
  compare?: boolean;
45
45
  showVisualizationSelector?: boolean;
46
- onSwitchToOneMinute?: () => void;
47
46
  }
48
47
 
49
48
  export const ProfileViewWithData = ({
50
49
  queryClient,
51
50
  profileSource,
52
51
  showVisualizationSelector,
53
- onSwitchToOneMinute,
54
52
  }: ProfileViewWithDataProps): JSX.Element => {
55
53
  const metadata = useGrpcMetadata();
56
54
  const [dashboardItems, setDashboardItems] = useURLState<string[]>('dashboard_items', {
@@ -380,7 +378,6 @@ export const ProfileViewWithData = ({
380
378
  onDownloadPProf={() => void downloadPProfClick()}
381
379
  pprofDownloading={pprofDownloading}
382
380
  showVisualizationSelector={showVisualizationSelector}
383
- onSwitchToOneMinute={onSwitchToOneMinute}
384
381
  />
385
382
  );
386
383
  };
@@ -48,7 +48,7 @@ export const TimelineGuide = ({
48
48
  const xScale = scaleLinear(bounds, [0, width]);
49
49
 
50
50
  return (
51
- <div className="relative h-5">
51
+ <div className="relative h-5 z-40">
52
52
  <div className="pointer-events-none absolute" style={{width, height}}>
53
53
  <svg style={{width: '100%', height: '100%'}}>
54
54
  <g