123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- import {useCallback, useRef} from 'react';
- import {useReplayContext} from 'sentry/components/replays/replayContext';
- import {relativeTimeInMs} from 'sentry/components/replays/utils';
- import {BreadcrumbType, Crumb} from 'sentry/types/breadcrumbs';
- import useActiveReplayTab from 'sentry/utils/replays/hooks/useActiveReplayTab';
- import useOrganization from 'sentry/utils/useOrganization';
- import type {NetworkSpan} from 'sentry/views/replays/types';
- function useCrumbHandlers(startTimestampMs: number = 0) {
- const organization = useOrganization();
- const {
- clearAllHighlights,
- highlight,
- removeHighlight,
- setCurrentHoverTime,
- setCurrentTime,
- } = useReplayContext();
- const {setActiveTab} = useActiveReplayTab();
- const hasErrorTab = organization.features.includes('session-replay-errors-tab');
- const mouseEnterCallback = useRef<{
- id: string | number | null;
- timeoutId: NodeJS.Timeout | null;
- }>({
- id: null,
- timeoutId: null,
- });
- const handleMouseEnter = useCallback(
- (item: Crumb | NetworkSpan) => {
- // this debounces the mouseEnter callback in unison with mouseLeave
- // we ensure the pointer remains over the target element before dispatching state events in order to minimize unnecessary renders
- // this helps during scrolling or mouse move events which would otherwise fire in rapid succession slowing down our app
- mouseEnterCallback.current.id = item.id;
- mouseEnterCallback.current.timeoutId = setTimeout(() => {
- if (startTimestampMs) {
- setCurrentHoverTime(relativeTimeInMs(item.timestamp ?? '', startTimestampMs));
- }
- if (item.data && typeof item.data === 'object' && 'nodeId' in item.data) {
- // XXX: Kind of hacky, but mouseLeave does not fire if you move from a
- // crumb to a tooltip
- clearAllHighlights();
- highlight({nodeId: item.data.nodeId, annotation: item.data.label});
- }
- mouseEnterCallback.current.id = null;
- mouseEnterCallback.current.timeoutId = null;
- }, 250);
- },
- [setCurrentHoverTime, startTimestampMs, highlight, clearAllHighlights]
- );
- const handleMouseLeave = useCallback(
- (item: Crumb | NetworkSpan) => {
- // if there is a mouseEnter callback queued and we're leaving it we can just cancel the timeout
- if (mouseEnterCallback.current.id === item.id) {
- if (mouseEnterCallback.current.timeoutId) {
- clearTimeout(mouseEnterCallback.current.timeoutId);
- }
- mouseEnterCallback.current.id = null;
- mouseEnterCallback.current.timeoutId = null;
- // since there is no more work to do we just return
- return;
- }
- setCurrentHoverTime(undefined);
- if (item.data && typeof item.data === 'object' && 'nodeId' in item.data) {
- removeHighlight({nodeId: item.data.nodeId});
- }
- },
- [setCurrentHoverTime, removeHighlight]
- );
- const handleClick = useCallback(
- (crumb: Crumb | NetworkSpan) => {
- if (crumb.timestamp !== undefined) {
- setCurrentTime(relativeTimeInMs(crumb.timestamp, startTimestampMs));
- }
- if (
- crumb.data &&
- typeof crumb.data === 'object' &&
- 'action' in crumb.data &&
- crumb.data.action === 'largest-contentful-paint'
- ) {
- setActiveTab('dom');
- return;
- }
- if ('type' in crumb) {
- if (hasErrorTab && crumb.type === BreadcrumbType.ERROR) {
- setActiveTab('errors');
- return;
- }
- switch (crumb.type) {
- case BreadcrumbType.NAVIGATION:
- setActiveTab('network');
- break;
- case BreadcrumbType.UI:
- setActiveTab('dom');
- break;
- default:
- setActiveTab('console');
- break;
- }
- }
- },
- [setCurrentTime, startTimestampMs, setActiveTab, hasErrorTab]
- );
- return {
- handleMouseEnter,
- handleMouseLeave,
- handleClick,
- };
- }
- export default useCrumbHandlers;
|