flamegraphOptionsContextMenu.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import {Fragment} from 'react';
  2. import {IconCopy} from 'sentry/icons';
  3. import {t} from 'sentry/locale';
  4. import {
  5. FlamegraphAxisOptions,
  6. FlamegraphColorCodings,
  7. FlamegraphSorting,
  8. FlamegraphViewOptions,
  9. } from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/reducers/flamegraphPreferences';
  10. import {useFlamegraphPreferences} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphPreferences';
  11. import {useDispatchFlamegraphState} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphState';
  12. import {FlamegraphFrame} from 'sentry/utils/profiling/flamegraphFrame';
  13. import {useContextMenu} from 'sentry/utils/profiling/hooks/useContextMenu';
  14. import {
  15. ProfilingContextMenu,
  16. ProfilingContextMenuGroup,
  17. ProfilingContextMenuHeading,
  18. ProfilingContextMenuItemButton,
  19. ProfilingContextMenuItemCheckbox,
  20. ProfilingContextMenuLayer,
  21. } from './ProfilingContextMenu/profilingContextMenu';
  22. const FLAMEGRAPH_COLOR_CODINGS: FlamegraphColorCodings = [
  23. 'by symbol name',
  24. 'by system / application',
  25. 'by library',
  26. 'by recursion',
  27. 'by frequency',
  28. ];
  29. const FLAMEGRAPH_VIEW_OPTIONS: FlamegraphViewOptions = ['top down', 'bottom up'];
  30. const FLAMEGRAPH_SORTING_OPTIONS: FlamegraphSorting = ['left heavy', 'call order'];
  31. const FLAMEGRAPH_AXIS_OPTIONS: FlamegraphAxisOptions = ['standalone', 'transaction'];
  32. interface FlameGraphOptionsContextMenuProps {
  33. contextMenu: ReturnType<typeof useContextMenu>;
  34. hoveredNode: FlamegraphFrame | null;
  35. isHighlightingAllOccurences: boolean;
  36. onCopyFunctionNameClick: () => void;
  37. onHighlightAllOccurencesClick: () => void;
  38. }
  39. export function FlamegraphOptionsContextMenu(props: FlameGraphOptionsContextMenuProps) {
  40. const preferences = useFlamegraphPreferences();
  41. const dispatch = useDispatchFlamegraphState();
  42. return props.contextMenu.open ? (
  43. <Fragment>
  44. <ProfilingContextMenuLayer onClick={() => props.contextMenu.setOpen(false)} />
  45. <ProfilingContextMenu
  46. {...props.contextMenu.getMenuProps()}
  47. style={{
  48. position: 'absolute',
  49. left: props.contextMenu.position?.left ?? -9999,
  50. top: props.contextMenu.position?.top ?? -9999,
  51. maxHeight: props.contextMenu.containerCoordinates?.height ?? 'auto',
  52. }}
  53. >
  54. {props.hoveredNode ? (
  55. <ProfilingContextMenuGroup>
  56. <ProfilingContextMenuHeading>{t('Frame')}</ProfilingContextMenuHeading>
  57. <ProfilingContextMenuItemCheckbox
  58. {...props.contextMenu.getMenuItemProps({
  59. onClick: props.onHighlightAllOccurencesClick,
  60. })}
  61. checked={props.isHighlightingAllOccurences}
  62. >
  63. {t('Highlight all occurrences')}
  64. </ProfilingContextMenuItemCheckbox>
  65. <ProfilingContextMenuItemButton
  66. {...props.contextMenu.getMenuItemProps({
  67. onClick: () => {
  68. props.onCopyFunctionNameClick();
  69. // This is a button, so close the context menu.
  70. props.contextMenu.setOpen(false);
  71. },
  72. })}
  73. icon={<IconCopy size="xs" />}
  74. >
  75. {t('Copy function name')}
  76. </ProfilingContextMenuItemButton>
  77. </ProfilingContextMenuGroup>
  78. ) : null}
  79. <ProfilingContextMenuGroup>
  80. <ProfilingContextMenuHeading>{t('Color Coding')}</ProfilingContextMenuHeading>
  81. {FLAMEGRAPH_COLOR_CODINGS.map((coding, idx) => (
  82. <ProfilingContextMenuItemCheckbox
  83. key={idx}
  84. {...props.contextMenu.getMenuItemProps({
  85. onClick: () => dispatch({type: 'set color coding', payload: coding}),
  86. })}
  87. onClick={() => dispatch({type: 'set color coding', payload: coding})}
  88. checked={preferences.colorCoding === coding}
  89. >
  90. {coding}
  91. </ProfilingContextMenuItemCheckbox>
  92. ))}
  93. </ProfilingContextMenuGroup>
  94. <ProfilingContextMenuGroup>
  95. <ProfilingContextMenuHeading>{t('View')}</ProfilingContextMenuHeading>
  96. {FLAMEGRAPH_VIEW_OPTIONS.map((view, idx) => (
  97. <ProfilingContextMenuItemCheckbox
  98. key={idx}
  99. {...props.contextMenu.getMenuItemProps({
  100. onClick: () => dispatch({type: 'set view', payload: view}),
  101. })}
  102. onClick={() => dispatch({type: 'set view', payload: view})}
  103. checked={preferences.view === view}
  104. >
  105. {view}
  106. </ProfilingContextMenuItemCheckbox>
  107. ))}
  108. </ProfilingContextMenuGroup>
  109. <ProfilingContextMenuGroup>
  110. <ProfilingContextMenuHeading>{t('Sorting')}</ProfilingContextMenuHeading>
  111. {FLAMEGRAPH_SORTING_OPTIONS.map((sorting, idx) => (
  112. <ProfilingContextMenuItemCheckbox
  113. key={idx}
  114. {...props.contextMenu.getMenuItemProps({
  115. onClick: () => dispatch({type: 'set sorting', payload: sorting}),
  116. })}
  117. onClick={() => dispatch({type: 'set sorting', payload: sorting})}
  118. checked={preferences.sorting === sorting}
  119. >
  120. {sorting}
  121. </ProfilingContextMenuItemCheckbox>
  122. ))}
  123. </ProfilingContextMenuGroup>
  124. <ProfilingContextMenuGroup>
  125. <ProfilingContextMenuHeading>{t('X Axis')}</ProfilingContextMenuHeading>
  126. {FLAMEGRAPH_AXIS_OPTIONS.map((axis, idx) => (
  127. <ProfilingContextMenuItemCheckbox
  128. key={idx}
  129. {...props.contextMenu.getMenuItemProps({
  130. onClick: () => dispatch({type: 'set xAxis', payload: axis}),
  131. })}
  132. onClick={() => dispatch({type: 'set xAxis', payload: axis})}
  133. checked={preferences.xAxis === axis}
  134. >
  135. {axis}
  136. </ProfilingContextMenuItemCheckbox>
  137. ))}
  138. </ProfilingContextMenuGroup>
  139. </ProfilingContextMenu>
  140. </Fragment>
  141. ) : null;
  142. }