traceTabs.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import * as Sentry from '@sentry/react';
  2. import {t} from 'sentry/locale';
  3. import type {
  4. TraceTree,
  5. TraceTreeNode,
  6. } from 'sentry/views/performance/newTraceDetails/traceTree';
  7. import {
  8. isAutogroupedNode,
  9. isMissingInstrumentationNode,
  10. isSpanNode,
  11. isTraceErrorNode,
  12. isTraceNode,
  13. isTransactionNode,
  14. } from './guards';
  15. export function getTraceTabTitle(node: TraceTreeNode<TraceTree.NodeValue>) {
  16. if (isTransactionNode(node)) {
  17. return (
  18. node.value['transaction.op'] +
  19. (node.value.transaction ? ' - ' + node.value.transaction : '')
  20. );
  21. }
  22. if (isSpanNode(node)) {
  23. return node.value.op + (node.value.description ? ' - ' + node.value.description : '');
  24. }
  25. if (isAutogroupedNode(node)) {
  26. return t('Autogroup');
  27. }
  28. if (isMissingInstrumentationNode(node)) {
  29. return t('Missing Instrumentation');
  30. }
  31. if (isTraceErrorNode(node)) {
  32. return node.value.title || 'Error';
  33. }
  34. if (isTraceNode(node)) {
  35. return t('Trace');
  36. }
  37. Sentry.captureMessage('Unknown node type in trace drawer');
  38. return 'Unknown';
  39. }
  40. type Tab = {
  41. node: TraceTreeNode<TraceTree.NodeValue> | 'Trace';
  42. };
  43. export type TraceTabsReducerState = {
  44. current: Tab | null;
  45. last_clicked: Tab | null;
  46. tabs: Tab[];
  47. };
  48. export type TraceTabsReducerAction =
  49. | {
  50. payload: Tab['node'] | number;
  51. type: 'activate tab';
  52. pin_previous?: boolean;
  53. }
  54. | {type: 'pin tab'}
  55. | {payload: number; type: 'unpin tab'}
  56. | {type: 'clear clicked tab'};
  57. export function traceTabsReducer(
  58. state: TraceTabsReducerState,
  59. action: TraceTabsReducerAction
  60. ): TraceTabsReducerState {
  61. switch (action.type) {
  62. case 'activate tab': {
  63. // If an index was passed, activate the tab at that index
  64. if (typeof action.payload === 'number') {
  65. return {
  66. ...state,
  67. current: state.tabs[action.payload] ?? state.last_clicked,
  68. };
  69. }
  70. // check if the tab is already pinned somewhere and activate it
  71. // this prevents duplicate tabs from being created, but that
  72. // doesnt seem like a usable feature anyways
  73. for (const tab of state.tabs) {
  74. if (tab.node === action.payload) {
  75. return {
  76. ...state,
  77. current: tab,
  78. last_clicked: state.last_clicked,
  79. };
  80. }
  81. }
  82. const tab = {node: action.payload};
  83. // If its pinned, activate it and pin the previous tab
  84. if (action.pin_previous && state.last_clicked) {
  85. if (state.last_clicked.node === action.payload) {
  86. return {
  87. ...state,
  88. current: state.last_clicked,
  89. last_clicked: null,
  90. tabs: [...state.tabs, state.last_clicked],
  91. };
  92. }
  93. return {
  94. ...state,
  95. current: tab,
  96. last_clicked: tab,
  97. tabs: [...state.tabs, state.last_clicked],
  98. };
  99. }
  100. // If it's not pinned, create a new tab and activate it
  101. return {
  102. ...state,
  103. current: tab,
  104. last_clicked: tab,
  105. };
  106. }
  107. case 'pin tab': {
  108. return {
  109. ...state,
  110. current: state.last_clicked,
  111. last_clicked: null,
  112. tabs: [...state.tabs, state.last_clicked!],
  113. };
  114. }
  115. case 'unpin tab': {
  116. const newTabs = state.tabs.filter((_tab, index) => {
  117. return index !== action.payload;
  118. });
  119. const nextTabIsPersistent = typeof newTabs[newTabs.length - 1].node === 'string';
  120. if (nextTabIsPersistent) {
  121. if (!state.last_clicked && !state.current) {
  122. throw new Error(
  123. 'last_clicked and current should not be null when nextTabIsPersistent is true'
  124. );
  125. }
  126. const nextTab = nextTabIsPersistent
  127. ? state.last_clicked ?? state.current
  128. : newTabs[newTabs.length - 1];
  129. return {
  130. ...state,
  131. current: nextTab,
  132. last_clicked: nextTab,
  133. tabs: newTabs,
  134. };
  135. }
  136. const next = state.last_clicked ?? newTabs[newTabs.length - 1];
  137. return {
  138. ...state,
  139. current: next,
  140. last_clicked: next,
  141. tabs: newTabs,
  142. };
  143. }
  144. default: {
  145. throw new Error('Invalid action');
  146. }
  147. }
  148. }