useCrumbHandlers.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import {useCallback, useRef} from 'react';
  2. import {useReplayContext} from 'sentry/components/replays/replayContext';
  3. import {relativeTimeInMs} from 'sentry/components/replays/utils';
  4. import {BreadcrumbType, Crumb} from 'sentry/types/breadcrumbs';
  5. import useActiveReplayTab from 'sentry/utils/replays/hooks/useActiveReplayTab';
  6. import useOrganization from 'sentry/utils/useOrganization';
  7. import type {NetworkSpan} from 'sentry/views/replays/types';
  8. function useCrumbHandlers(startTimestampMs: number = 0) {
  9. const organization = useOrganization();
  10. const {
  11. clearAllHighlights,
  12. highlight,
  13. removeHighlight,
  14. setCurrentHoverTime,
  15. setCurrentTime,
  16. } = useReplayContext();
  17. const {setActiveTab} = useActiveReplayTab();
  18. const hasErrorTab = organization.features.includes('session-replay-errors-tab');
  19. const mouseEnterCallback = useRef<{
  20. id: string | number | null;
  21. timeoutId: NodeJS.Timeout | null;
  22. }>({
  23. id: null,
  24. timeoutId: null,
  25. });
  26. const handleMouseEnter = useCallback(
  27. (item: Crumb | NetworkSpan) => {
  28. // this debounces the mouseEnter callback in unison with mouseLeave
  29. // we ensure the pointer remains over the target element before dispatching state events in order to minimize unnecessary renders
  30. // this helps during scrolling or mouse move events which would otherwise fire in rapid succession slowing down our app
  31. mouseEnterCallback.current.id = item.id;
  32. mouseEnterCallback.current.timeoutId = setTimeout(() => {
  33. if (startTimestampMs) {
  34. setCurrentHoverTime(relativeTimeInMs(item.timestamp ?? '', startTimestampMs));
  35. }
  36. if (item.data && typeof item.data === 'object' && 'nodeId' in item.data) {
  37. // XXX: Kind of hacky, but mouseLeave does not fire if you move from a
  38. // crumb to a tooltip
  39. clearAllHighlights();
  40. highlight({nodeId: item.data.nodeId, annotation: item.data.label});
  41. }
  42. mouseEnterCallback.current.id = null;
  43. mouseEnterCallback.current.timeoutId = null;
  44. }, 250);
  45. },
  46. [setCurrentHoverTime, startTimestampMs, highlight, clearAllHighlights]
  47. );
  48. const handleMouseLeave = useCallback(
  49. (item: Crumb | NetworkSpan) => {
  50. // if there is a mouseEnter callback queued and we're leaving it we can just cancel the timeout
  51. if (mouseEnterCallback.current.id === item.id) {
  52. if (mouseEnterCallback.current.timeoutId) {
  53. clearTimeout(mouseEnterCallback.current.timeoutId);
  54. }
  55. mouseEnterCallback.current.id = null;
  56. mouseEnterCallback.current.timeoutId = null;
  57. // since there is no more work to do we just return
  58. return;
  59. }
  60. setCurrentHoverTime(undefined);
  61. if (item.data && typeof item.data === 'object' && 'nodeId' in item.data) {
  62. removeHighlight({nodeId: item.data.nodeId});
  63. }
  64. },
  65. [setCurrentHoverTime, removeHighlight]
  66. );
  67. const handleClick = useCallback(
  68. (crumb: Crumb | NetworkSpan) => {
  69. if (crumb.timestamp !== undefined) {
  70. setCurrentTime(relativeTimeInMs(crumb.timestamp, startTimestampMs));
  71. }
  72. if (
  73. crumb.data &&
  74. typeof crumb.data === 'object' &&
  75. 'action' in crumb.data &&
  76. crumb.data.action === 'largest-contentful-paint'
  77. ) {
  78. setActiveTab('dom');
  79. return;
  80. }
  81. if ('type' in crumb) {
  82. if (hasErrorTab && crumb.type === BreadcrumbType.ERROR) {
  83. setActiveTab('errors');
  84. return;
  85. }
  86. switch (crumb.type) {
  87. case BreadcrumbType.NAVIGATION:
  88. setActiveTab('network');
  89. break;
  90. case BreadcrumbType.UI:
  91. setActiveTab('dom');
  92. break;
  93. default:
  94. setActiveTab('console');
  95. break;
  96. }
  97. }
  98. },
  99. [setCurrentTime, startTimestampMs, setActiveTab, hasErrorTab]
  100. );
  101. return {
  102. handleMouseEnter,
  103. handleMouseLeave,
  104. handleClick,
  105. };
  106. }
  107. export default useCrumbHandlers;