diff --git a/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx b/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx index bc1ffcc65752a7..7651993712a5a1 100644 --- a/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx +++ b/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx @@ -18,7 +18,7 @@ import {useHasNewTimelineUI} from 'sentry/components/timeline/utils'; import {Tooltip} from 'sentry/components/tooltip'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; -import type {Extraction} from 'sentry/utils/replays/extractDomNodes'; +import type {Extraction} from 'sentry/utils/replays/extractHtml'; import {getReplayDiffOffsetsFromFrame} from 'sentry/utils/replays/getDiffTimestamps'; import getFrameDetails from 'sentry/utils/replays/getFrameDetails'; import type ReplayReader from 'sentry/utils/replays/replayReader'; diff --git a/static/app/components/replays/diff/replayTextDiff.tsx b/static/app/components/replays/diff/replayTextDiff.tsx index 50fd76f2e745c7..2bafcd5458ef40 100644 --- a/static/app/components/replays/diff/replayTextDiff.tsx +++ b/static/app/components/replays/diff/replayTextDiff.tsx @@ -7,7 +7,7 @@ import {CopyToClipboardButton} from 'sentry/components/copyToClipboardButton'; import SplitDiff from 'sentry/components/splitDiff'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; -import useExtractedPageHtml from 'sentry/utils/replays/hooks/useExtractedPageHtml'; +import useExtractPageHtml from 'sentry/utils/replays/hooks/useExtractPageHtml'; import type ReplayReader from 'sentry/utils/replays/replayReader'; interface Props { @@ -16,8 +16,8 @@ interface Props { rightOffsetMs: number; } -export function ReplayTextDiff({leftOffsetMs, replay, rightOffsetMs}: Props) { - const {data} = useExtractedPageHtml({ +export function ReplayTextDiff({replay, leftOffsetMs, rightOffsetMs}: Props) { + const {data} = useExtractPageHtml({ replay, offsetMsToStopAt: [leftOffsetMs, rightOffsetMs], }); diff --git a/static/app/utils/replays/countDomNodes.tsx b/static/app/utils/replays/countDomNodes.tsx deleted file mode 100644 index f219f504e0258c..00000000000000 --- a/static/app/utils/replays/countDomNodes.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import replayerStepper from 'sentry/utils/replays/replayerStepper'; -import type {RecordingFrame} from 'sentry/utils/replays/types'; - -export type DomNodeChartDatapoint = { - added: number; - count: number; - endTimestampMs: number; - removed: number; - startTimestampMs: number; - timestampMs: number; -}; - -type Args = { - frames: RecordingFrame[] | undefined; - rrwebEvents: RecordingFrame[] | undefined; - startTimestampMs: number; -}; - -export default function countDomNodes({ - frames, - rrwebEvents, - startTimestampMs, -}: Args): Promise> { - let frameCount = 0; - const length = frames?.length ?? 0; - const frameStep = Math.max(Math.round(length * 0.007), 1); - - let prevIds: number[] = []; - - return replayerStepper({ - frames, - rrwebEvents, - startTimestampMs, - shouldVisitFrame: () => { - frameCount++; - return frameCount % frameStep === 0; - }, - onVisitFrame: (frame, collection, replayer) => { - const ids = replayer.getMirror().getIds(); // gets list of DOM nodes present - const count = ids.length; - const added = ids.filter(id => !prevIds.includes(id)).length; - const removed = prevIds.filter(id => !ids.includes(id)).length; - collection.set(frame as RecordingFrame, { - count, - added, - removed, - timestampMs: frame.timestamp, - startTimestampMs: frame.timestamp, - endTimestampMs: frame.timestamp, - }); - prevIds = ids; - }, - }); -} diff --git a/static/app/utils/replays/extractDomNodes.tsx b/static/app/utils/replays/extractHtml.tsx similarity index 54% rename from static/app/utils/replays/extractDomNodes.tsx rename to static/app/utils/replays/extractHtml.tsx index c225aa1f6f2d58..cb6333177d5396 100644 --- a/static/app/utils/replays/extractDomNodes.tsx +++ b/static/app/utils/replays/extractHtml.tsx @@ -1,11 +1,6 @@ import type {Mirror} from '@sentry-internal/rrweb-snapshot'; -import replayerStepper from 'sentry/utils/replays/replayerStepper'; -import { - getNodeId, - type RecordingFrame, - type ReplayFrame, -} from 'sentry/utils/replays/types'; +import type {ReplayFrame} from 'sentry/utils/replays/types'; export type Extraction = { frame: ReplayFrame; @@ -13,50 +8,7 @@ export type Extraction = { timestamp: number; }; -type Args = { - /** - * Frames where we should stop and extract html for a given dom node - */ - frames: ReplayFrame[] | undefined; - - /** - * The rrweb events that constitute the replay - */ - rrwebEvents: RecordingFrame[] | undefined; - - /** - * The replay start time, in ms - */ - startTimestampMs: number; -}; - -export default function extractDomNodes({ - frames, - rrwebEvents, - startTimestampMs, -}: Args): Promise> { - return replayerStepper({ - frames, - rrwebEvents, - startTimestampMs, - shouldVisitFrame: frame => { - const nodeId = getNodeId(frame); - return nodeId !== undefined && nodeId !== -1; - }, - onVisitFrame: (frame, collection, replayer) => { - const mirror = replayer.getMirror(); - const nodeId = getNodeId(frame); - const html = extractHtml(nodeId as number, mirror); - collection.set(frame as ReplayFrame, { - frame, - html, - timestamp: frame.timestampMs, - }); - }, - }); -} - -function extractHtml(nodeId: number, mirror: Mirror): string | null { +export default function extractHtml(nodeId: number, mirror: Mirror): string | null { const node = mirror.getNode(nodeId); const html = diff --git a/static/app/utils/replays/extractPageHtml.tsx b/static/app/utils/replays/extractPageHtml.tsx deleted file mode 100644 index 00d5d97bca4786..00000000000000 --- a/static/app/utils/replays/extractPageHtml.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import replayerStepper from 'sentry/utils/replays/replayerStepper'; -import type {RecordingFrame, ReplayFrame} from 'sentry/utils/replays/types'; - -type Args = { - /** - * Offsets where we should stop and take a snapshot of the rendered HTML - */ - offsetMsToStopAt: number[]; - - /** - * The rrweb events that constitute the replay - */ - rrwebEvents: RecordingFrame[] | undefined; - - /** - * The replay startTimestampMs - */ - startTimestampMs: number; -}; - -export default async function extactPageHtml({ - offsetMsToStopAt, - rrwebEvents, - startTimestampMs, -}: Args): Promise<[number, string][]> { - const frames: ReplayFrame[] = offsetMsToStopAt.map(offsetMs => ({ - offsetMs, - timestamp: new Date(startTimestampMs + offsetMs), - timestampMs: startTimestampMs + offsetMs, - })) as ReplayFrame[]; // TODO Don't smash types into `as ReplayFrame[]`, instead make the object really conform - const results = await replayerStepper({ - frames, - rrwebEvents, - startTimestampMs, - shouldVisitFrame(_frame) { - // Visit all the timestamps (converted to frames) that were passed in above - return true; - }, - onVisitFrame(frame, collection, replayer) { - const doc = replayer.getMirror().getNode(1); - const html = (doc as Document)?.body.outerHTML ?? ''; - collection.set(frame, html); - }, - }); - return Array.from(results.entries()).map(([frame, html]) => { - return [frame.offsetMs, html]; - }); -} diff --git a/static/app/utils/replays/hooks/useCountDomNodes.tsx b/static/app/utils/replays/hooks/useCountDomNodes.tsx new file mode 100644 index 00000000000000..1bcb8ed379b081 --- /dev/null +++ b/static/app/utils/replays/hooks/useCountDomNodes.tsx @@ -0,0 +1,21 @@ +import {useQuery} from 'sentry/utils/queryClient'; +import type ReplayReader from 'sentry/utils/replays/replayReader'; + +export type DomNodeChartDatapoint = { + added: number; + count: number; + endTimestampMs: number; + removed: number; + startTimestampMs: number; + timestampMs: number; +}; + +export default function useCountDomNodes({replay}: {replay: null | ReplayReader}) { + return useQuery( + ['countDomNodes', replay], + () => { + return replay?.getCountDomNodes(); + }, + {enabled: Boolean(replay), cacheTime: Infinity} + ); +} diff --git a/static/app/utils/replays/hooks/useExtractDomNodes.tsx b/static/app/utils/replays/hooks/useExtractDomNodes.tsx new file mode 100644 index 00000000000000..7d4b9258ffb7ff --- /dev/null +++ b/static/app/utils/replays/hooks/useExtractDomNodes.tsx @@ -0,0 +1,12 @@ +import {useQuery} from 'sentry/utils/queryClient'; +import type ReplayReader from 'sentry/utils/replays/replayReader'; + +export default function useExtractDomNodes({replay}: {replay: null | ReplayReader}) { + return useQuery( + ['getDomNodes', replay], + () => { + return replay?.getExtractDomNodes(); + }, + {enabled: Boolean(replay), cacheTime: Infinity} + ); +} diff --git a/static/app/utils/replays/hooks/useExtractPageHtml.tsx b/static/app/utils/replays/hooks/useExtractPageHtml.tsx new file mode 100644 index 00000000000000..db83985d0c1cca --- /dev/null +++ b/static/app/utils/replays/hooks/useExtractPageHtml.tsx @@ -0,0 +1,72 @@ +import {useQuery} from 'sentry/utils/queryClient'; +import replayerStepper from 'sentry/utils/replays/replayerStepper'; +import type ReplayReader from 'sentry/utils/replays/replayReader'; +import type {RecordingFrame, ReplayFrame} from 'sentry/utils/replays/types'; + +type Args = { + /** + * Offsets where we should stop and take a snapshot of the rendered HTML + */ + offsetMsToStopAt: number[]; + + /** + * The rrweb events that constitute the replay + */ + rrwebEvents: RecordingFrame[] | undefined; + + /** + * The replay startTimestampMs + */ + startTimestampMs: number; +}; + +async function extractPageHtml({ + offsetMsToStopAt, + rrwebEvents, + startTimestampMs, +}: Args): Promise<[number, string][]> { + const frames: ReplayFrame[] = offsetMsToStopAt.map(offsetMs => ({ + offsetMs, + timestamp: new Date(startTimestampMs + offsetMs), + timestampMs: startTimestampMs + offsetMs, + })) as ReplayFrame[]; // TODO Don't smash types into `as ReplayFrame[]`, instead make the object really conform + const results = await replayerStepper({ + frames, + rrwebEvents, + startTimestampMs, + visitFrameCallbacks: { + extractPageHtml: { + shouldVisitFrame: () => { + // Visit all the timestamps (converted to frames) that were passed in above + return true; + }, + onVisitFrame: (frame, collection, replayer) => { + const doc = replayer.getMirror().getNode(1); + const html = (doc as Document)?.body.outerHTML ?? ''; + collection.set(frame, html); + }, + }, + }, + }); + return Array.from(results.extractPageHtml.entries()).map(([frame, html]) => { + return [frame.offsetMs, html]; + }); +} + +interface Props { + offsetMsToStopAt: number[]; + replay: ReplayReader | null; +} + +export default function useExtractedPageHtml({replay, offsetMsToStopAt}: Props) { + return useQuery( + ['extactPageHtml', replay, offsetMsToStopAt], + () => + extractPageHtml({ + offsetMsToStopAt, + rrwebEvents: replay?.getRRWebFrames(), + startTimestampMs: replay?.getReplay().started_at.getTime() ?? 0, + }), + {enabled: Boolean(replay), cacheTime: Infinity} + ); +} diff --git a/static/app/utils/replays/hooks/useExtractedDomNodes.tsx b/static/app/utils/replays/hooks/useExtractedDomNodes.tsx deleted file mode 100644 index c5eb4c7566c217..00000000000000 --- a/static/app/utils/replays/hooks/useExtractedDomNodes.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import {useQuery} from 'sentry/utils/queryClient'; -import extractDomNodes from 'sentry/utils/replays/extractDomNodes'; -import type ReplayReader from 'sentry/utils/replays/replayReader'; - -export default function useExtractedDomNodes({replay}: {replay: null | ReplayReader}) { - return useQuery( - ['getDomNodes', replay], - () => - extractDomNodes({ - frames: replay?.getDOMFrames(), - rrwebEvents: replay?.getRRWebFrames(), - startTimestampMs: replay?.getReplay().started_at.getTime() ?? 0, - }), - {enabled: Boolean(replay), cacheTime: Infinity} - ); -} diff --git a/static/app/utils/replays/hooks/useExtractedPageHtml.tsx b/static/app/utils/replays/hooks/useExtractedPageHtml.tsx deleted file mode 100644 index 2906cff4395fbd..00000000000000 --- a/static/app/utils/replays/hooks/useExtractedPageHtml.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import {useQuery} from 'sentry/utils/queryClient'; -import extractPageHtml from 'sentry/utils/replays/extractPageHtml'; -import type ReplayReader from 'sentry/utils/replays/replayReader'; - -interface Props { - offsetMsToStopAt: number[]; - replay: ReplayReader | null; -} - -export default function useExtractedPageHtml({replay, offsetMsToStopAt}: Props) { - return useQuery( - ['extactPageHtml', replay, offsetMsToStopAt], - () => - extractPageHtml({ - offsetMsToStopAt, - rrwebEvents: replay?.getRRWebFrames(), - startTimestampMs: replay?.getReplay().started_at.getTime() ?? 0, - }), - {enabled: Boolean(replay), cacheTime: Infinity} - ); -} diff --git a/static/app/utils/replays/replayReader.tsx b/static/app/utils/replays/replayReader.tsx index 80699955c698f7..7ee04443d8c94e 100644 --- a/static/app/utils/replays/replayReader.tsx +++ b/static/app/utils/replays/replayReader.tsx @@ -1,4 +1,5 @@ import * as Sentry from '@sentry/react'; +import type {eventWithTime} from '@sentry-internal/rrweb'; import memoize from 'lodash/memoize'; import {type Duration, duration} from 'moment-timezone'; @@ -6,6 +7,7 @@ import {defined} from 'sentry/utils'; import domId from 'sentry/utils/domId'; import localStorageWrapper from 'sentry/utils/localStorage'; import clamp from 'sentry/utils/number/clamp'; +import extractHtml from 'sentry/utils/replays/extractHtml'; import hydrateBreadcrumbs, { replayInitBreadcrumb, } from 'sentry/utils/replays/hydrateBreadcrumbs'; @@ -17,6 +19,7 @@ import { } from 'sentry/utils/replays/hydrateRRWebRecordingFrames'; import hydrateSpans from 'sentry/utils/replays/hydrateSpans'; import {replayTimestamps} from 'sentry/utils/replays/replayDataUtils'; +import replayerStepper from 'sentry/utils/replays/replayerStepper'; import type { BreadcrumbFrame, ClipWindow, @@ -26,6 +29,7 @@ import type { MemoryFrame, OptionFrame, RecordingFrame, + ReplayFrame, serializedNodeWithId, SlowClickFrame, SpanFrame, @@ -34,6 +38,7 @@ import type { import { BreadcrumbCategories, EventType, + getNodeId, IncrementalSource, isBackgroundFrame, isDeadClick, @@ -139,6 +144,53 @@ function removeDuplicateNavCrumbs( return otherBreadcrumbFrames.concat(uniqueNavCrumbs); } +const extractDomNodes = { + shouldVisitFrame: frame => { + const nodeId = getNodeId(frame); + return nodeId !== undefined && nodeId !== -1; + }, + onVisitFrame: (frame, collection, replayer) => { + const mirror = replayer.getMirror(); + const nodeId = getNodeId(frame); + const html = extractHtml(nodeId as number, mirror); + collection.set(frame as ReplayFrame, { + frame, + html, + timestamp: frame.timestampMs, + }); + }, +}; + +const countDomNodes = function (frames: eventWithTime[]) { + let frameCount = 0; + const length = frames?.length ?? 0; + const frameStep = Math.max(Math.round(length * 0.007), 1); + + let prevIds: number[] = []; + + return { + shouldVisitFrame() { + frameCount++; + return frameCount % frameStep === 0; + }, + onVisitFrame(frame, collection, replayer) { + const ids = replayer.getMirror().getIds(); // gets list of DOM nodes present + const count = ids.length; + const added = ids.filter(id => !prevIds.includes(id)).length; + const removed = prevIds.filter(id => !ids.includes(id)).length; + collection.set(frame as RecordingFrame, { + count, + added, + removed, + timestampMs: frame.timestamp, + startTimestampMs: frame.timestamp, + endTimestampMs: frame.timestamp, + }); + prevIds = ids; + }, + }; +}; + export default class ReplayReader { static factory({ attachments, @@ -291,6 +343,31 @@ export default class ReplayReader { private _startOffsetMs = 0; private _videoEvents: VideoEvent[] = []; private _clipWindow: ClipWindow | undefined = undefined; + private _collections: Record | undefined = undefined; + private _allFrames: (RecordingFrame | ReplayFrame)[] = []; + + private _getCollections = () => { + if (this._collections) { + return this._collections; + } + + if (this.getRRWebFrames().length > 2) { + this._allFrames = (this.getRRWebMutations() as (RecordingFrame | ReplayFrame)[]) + .concat(this.getDOMFrames()) + .sort(sortFrames); + + this._collections = replayerStepper({ + frames: this._allFrames, + rrwebEvents: this.getRRWebFrames(), + startTimestampMs: this.getReplay().started_at.getTime() ?? 0, + visitFrameCallbacks: { + extractDomNodes, + countDomNodes: countDomNodes(this.getRRWebMutations()), + }, + }); + } + return this._collections; + }; private _applyClipWindow = (clipWindow: ClipWindow) => { const clipStartTimestampMs = clamp( @@ -414,6 +491,16 @@ export default class ReplayReader { return this.processingErrors().length; }; + getCountDomNodes = async () => { + const results = await this._getCollections(); + return results?.countDomNodes; + }; + + getExtractDomNodes = async () => { + const results = await this._getCollections(); + return results?.extractDomNodes; + }; + getClipWindow = () => this._clipWindow; /** diff --git a/static/app/utils/replays/replayerStepper.tsx b/static/app/utils/replays/replayerStepper.tsx index 77dae4de4f7238..e4b8b208affab4 100644 --- a/static/app/utils/replays/replayerStepper.tsx +++ b/static/app/utils/replays/replayerStepper.tsx @@ -4,20 +4,31 @@ import type {RecordingFrame, ReplayFrame} from 'sentry/utils/replays/types'; import {createHiddenPlayer} from './createHiddenPlayer'; +type OnVisitFrameType = ( + frame: Frame, + collection: Map, + replayer: Replayer +) => void; + +type ShouldVisitFrameType = ( + frame: Frame, + replayer: Replayer +) => boolean; + +type CallbackArgs = { + onVisitFrame: OnVisitFrameType; + shouldVisitFrame: ShouldVisitFrameType; +}; + interface Args { frames: Frame[] | undefined; - onVisitFrame: ( - frame: Frame, - collection: Map, - replayer: Replayer - ) => void; rrwebEvents: RecordingFrame[] | undefined; - shouldVisitFrame: (frame: Frame, replayer: Replayer) => boolean; startTimestampMs: number; + visitFrameCallbacks: Record>; } type FrameRef = { - frame: Frame | undefined; + current: Frame | undefined; }; export default function replayerStepper< @@ -25,16 +36,18 @@ export default function replayerStepper< CollectionData, >({ frames, - onVisitFrame, rrwebEvents, - shouldVisitFrame, + visitFrameCallbacks, startTimestampMs, -}: Args): Promise> { - const collection = new Map(); +}: Args): Promise>> { + const collection = {}; + Object.keys(visitFrameCallbacks).forEach( + k => (collection[k] = new Map()) + ); return new Promise(resolve => { if (!frames?.length || !rrwebEvents?.length) { - resolve(new Map()); + resolve(collection); return; } @@ -62,25 +75,39 @@ export default function replayerStepper< }; const frameRef: FrameRef = { - frame: undefined, + current: undefined, + }; + + const activeCallbacks: { + current: Record>; + } = { + current: {}, }; const considerFrame = (frame: Frame) => { - if (shouldVisitFrame(frame, replayer)) { - frameRef.frame = frame; + activeCallbacks.current = Object.fromEntries( + Object.entries(visitFrameCallbacks).filter(([, v]) => { + return v.shouldVisitFrame(frame, replayer); + }) + ); + + if (Object.values(activeCallbacks.current).length) { + frameRef.current = frame; window.setTimeout(() => { const timestamp = 'offsetMs' in frame ? frame.offsetMs : frame.timestamp - startTimestampMs; replayer.pause(timestamp); }, 0); } else { - frameRef.frame = undefined; + frameRef.current = undefined; nextOrDone(); } }; const handlePause = () => { - onVisitFrame(frameRef.frame!, collection, replayer); + Object.entries(activeCallbacks.current).forEach(([k, v]) => { + v.onVisitFrame(frameRef.current!, collection[k], replayer); + }); nextOrDone(); }; diff --git a/static/app/views/replays/detail/breadcrumbs/breadcrumbRow.tsx b/static/app/views/replays/detail/breadcrumbs/breadcrumbRow.tsx index 33f9eac5a6bf2c..f0a0a818ef1bcf 100644 --- a/static/app/views/replays/detail/breadcrumbs/breadcrumbRow.tsx +++ b/static/app/views/replays/detail/breadcrumbs/breadcrumbRow.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import BreadcrumbItem from 'sentry/components/replays/breadcrumbs/breadcrumbItem'; import {useReplayContext} from 'sentry/components/replays/replayContext'; -import type {Extraction} from 'sentry/utils/replays/extractDomNodes'; +import type {Extraction} from 'sentry/utils/replays/extractHtml'; import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers'; import useCurrentHoverTime from 'sentry/utils/replays/playback/providers/useCurrentHoverTime'; import type {ReplayFrame} from 'sentry/utils/replays/types'; diff --git a/static/app/views/replays/detail/breadcrumbs/index.tsx b/static/app/views/replays/detail/breadcrumbs/index.tsx index 9423985810a2d0..4fdf4fcdb6c700 100644 --- a/static/app/views/replays/detail/breadcrumbs/index.tsx +++ b/static/app/views/replays/detail/breadcrumbs/index.tsx @@ -8,7 +8,7 @@ import {useReplayContext} from 'sentry/components/replays/replayContext'; import useJumpButtons from 'sentry/components/replays/useJumpButtons'; import {t} from 'sentry/locale'; import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers'; -import useExtractedDomNodes from 'sentry/utils/replays/hooks/useExtractedDomNodes'; +import useExtractDomNodes from 'sentry/utils/replays/hooks/useExtractDomNodes'; import useVirtualizedInspector from 'sentry/views/replays/detail//useVirtualizedInspector'; import BreadcrumbFilters from 'sentry/views/replays/detail/breadcrumbs/breadcrumbFilters'; import BreadcrumbRow from 'sentry/views/replays/detail/breadcrumbs/breadcrumbRow'; @@ -30,8 +30,12 @@ const cellMeasurer = { function Breadcrumbs() { const {currentTime, replay} = useReplayContext(); const {onClickTimestamp} = useCrumbHandlers(); - const {data: frameToExtraction, isFetching: isFetchingExtractions} = - useExtractedDomNodes({replay}); + + const {data: frameToExtraction, isFetching: isFetchingExtractions} = useExtractDomNodes( + { + replay, + } + ); const startTimestampMs = replay?.getStartTimestampMs() ?? 0; const frames = replay?.getChapterFrames(); diff --git a/static/app/views/replays/detail/memoryPanel/domNodesChart.tsx b/static/app/views/replays/detail/memoryPanel/domNodesChart.tsx index a9ba0aa74df53c..4437e483633150 100644 --- a/static/app/views/replays/detail/memoryPanel/domNodesChart.tsx +++ b/static/app/views/replays/detail/memoryPanel/domNodesChart.tsx @@ -16,7 +16,7 @@ import {getFormattedDate} from 'sentry/utils/dates'; import {axisLabelFormatter} from 'sentry/utils/discover/charts'; import domId from 'sentry/utils/domId'; import formatReplayDuration from 'sentry/utils/duration/formatReplayDuration'; -import type {DomNodeChartDatapoint} from 'sentry/utils/replays/countDomNodes'; +import type {DomNodeChartDatapoint} from 'sentry/utils/replays/hooks/useCountDomNodes'; interface Props extends Pick, 'currentTime' | 'setCurrentTime'> { diff --git a/static/app/views/replays/detail/memoryPanel/index.tsx b/static/app/views/replays/detail/memoryPanel/index.tsx index 9358cb2d18fb74..75e2ba83e074e8 100644 --- a/static/app/views/replays/detail/memoryPanel/index.tsx +++ b/static/app/views/replays/detail/memoryPanel/index.tsx @@ -6,26 +6,13 @@ import Placeholder from 'sentry/components/placeholder'; import {useReplayContext} from 'sentry/components/replays/replayContext'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; -import {useQuery} from 'sentry/utils/queryClient'; -import countDomNodes from 'sentry/utils/replays/countDomNodes'; +import useCountDomNodes, { + type DomNodeChartDatapoint, +} from 'sentry/utils/replays/hooks/useCountDomNodes'; import useCurrentHoverTime from 'sentry/utils/replays/playback/providers/useCurrentHoverTime'; -import type ReplayReader from 'sentry/utils/replays/replayReader'; import DomNodesChart from 'sentry/views/replays/detail/memoryPanel/domNodesChart'; import MemoryChart from 'sentry/views/replays/detail/memoryPanel/memoryChart'; -function useCountDomNodes({replay}: {replay: null | ReplayReader}) { - return useQuery( - ['countDomNodes', replay], - () => - countDomNodes({ - frames: replay?.getRRWebMutations(), - rrwebEvents: replay?.getRRWebFrames(), - startTimestampMs: replay?.getStartTimestampMs() ?? 0, - }), - {enabled: Boolean(replay), cacheTime: Infinity} - ); -} - export default function MemoryPanel() { const {currentTime, isFetching, replay, setCurrentTime} = useReplayContext(); const [currentHoverTime, setCurrentHoverTime] = useCurrentHoverTime(); @@ -74,7 +61,7 @@ export default function MemoryPanel() { currentHoverTime={currentHoverTime} currentTime={currentTime} durationMs={replay.getDurationMs()} - datapoints={domNodeData} + datapoints={domNodeData as DomNodeChartDatapoint[]} setCurrentHoverTime={setCurrentHoverTime} setCurrentTime={setCurrentTime} startTimestampMs={replay.getStartTimestampMs()}