context.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import {
  2. createContext,
  3. type Dispatch,
  4. type Reducer,
  5. useCallback,
  6. useContext,
  7. useReducer,
  8. } from 'react';
  9. export const enum SectionKey {
  10. /**
  11. * Trace timeline or linked error
  12. */
  13. TRACE = 'trace',
  14. USER_FEEDBACK = 'user-feedback',
  15. LLM_MONITORING = 'llm-monitoring',
  16. SOLUTIONS_HUB = 'solutions-hub',
  17. UPTIME = 'uptime', // Only Uptime issues
  18. DOWNTIME = 'downtime',
  19. CRON_TIMELINE = 'cron-timeline', // Only Cron issues
  20. CORRELATED_ISSUES = 'correlated-issues', // Only Metric issues
  21. CORRELATED_TRANSACTIONS = 'correlated-transactions', // Only Metric issues
  22. HIGHLIGHTS = 'highlights',
  23. RESOURCES = 'resources', // Position controlled by flag
  24. EXCEPTION = 'exception',
  25. STACKTRACE = 'stacktrace',
  26. THREADS = 'threads',
  27. SPANS = 'spans',
  28. EVIDENCE = 'evidence',
  29. MESSAGE = 'message',
  30. SUSPECT_ROOT_CAUSE = 'suspect-root-cause',
  31. SPAN_EVIDENCE = 'span-evidence',
  32. HYDRATION_DIFF = 'hydration-diff',
  33. REPLAY = 'replay',
  34. HPKP = 'hpkp',
  35. CSP = 'csp',
  36. EXPECTCT = 'expectct',
  37. EXPECTSTAPLE = 'expectstaple',
  38. TEMPLATE = 'template',
  39. BREADCRUMBS = 'breadcrumbs',
  40. /**
  41. * Also called images loaded
  42. */
  43. DEBUGMETA = 'debugmeta',
  44. REQUEST = 'request',
  45. TAGS = 'tags',
  46. SCREENSHOT = 'screenshot',
  47. FEATURE_FLAGS = 'feature-flags',
  48. CONTEXTS = 'contexts',
  49. EXTRA = 'extra',
  50. PACKAGES = 'packages',
  51. DEVICE = 'device',
  52. VIEW_HIERARCHY = 'view-hierarchy',
  53. ATTACHMENTS = 'attachments',
  54. SDK = 'sdk',
  55. GROUPING_INFO = 'grouping-info',
  56. PROCESSING_ERROR = 'processing-error',
  57. RRWEB = 'rrweb', // Legacy integration prior to replays
  58. MERGED_ISSUES = 'merged',
  59. SIMILAR_ISSUES = 'similar',
  60. REGRESSION_SUMMARY = 'regression-summary',
  61. REGRESSION_BREAKPOINT_CHART = 'regression-breakpoint-chart',
  62. REGRESSION_FLAMEGRAPH = 'regression-flamegraph',
  63. REGRESSION_PROFILE_COMPARISON = 'regression-profile-comparison',
  64. REGRESSION_EVENT_COMPARISON = 'regression-event-comparison',
  65. REGRESSION_POTENTIAL_CAUSES = 'regression-potential-causes',
  66. REGRESSION_AFFECTED_TRANSACTIONS = 'regression-affected-transactions',
  67. }
  68. /**
  69. * This can be extended to create shared state for each section.
  70. * For example, if we needed to know the number of context cards we're rendering,
  71. * the <ContextDataSection /> can update the config for other components to read from.
  72. */
  73. export interface SectionConfig {
  74. key: SectionKey;
  75. initialCollapse?: boolean;
  76. }
  77. export interface IssueDetailsContextType extends IssueDetailsState {
  78. dispatch: Dispatch<IssueDetailsActions>;
  79. }
  80. export const IssueDetailsContext = createContext<IssueDetailsContextType>({
  81. sectionData: {},
  82. isSidebarOpen: true,
  83. navScrollMargin: 0,
  84. eventCount: 0,
  85. dispatch: () => {},
  86. });
  87. export function useIssueDetails() {
  88. return useContext(IssueDetailsContext);
  89. }
  90. export interface IssueDetailsState {
  91. /**
  92. * Allows updating the event count based on the date/time/environment filters.
  93. */
  94. eventCount: number;
  95. /**
  96. * Controls whether the sidebar is open.
  97. */
  98. isSidebarOpen: boolean;
  99. /**
  100. * The margin to add to the 'Jump To' nav (accounts for the main app sidebar on small screen sizes).
  101. */
  102. navScrollMargin: number;
  103. /**
  104. * Controls the state of each section.
  105. */
  106. sectionData: {
  107. [key in SectionKey]?: SectionConfig;
  108. };
  109. }
  110. type UpdateEventSectionAction = {
  111. key: SectionKey;
  112. type: 'UPDATE_EVENT_SECTION';
  113. config?: Partial<SectionConfig>;
  114. };
  115. type UpdateNavScrollMarginAction = {
  116. margin: number;
  117. type: 'UPDATE_NAV_SCROLL_MARGIN';
  118. };
  119. type UpdateEventCountAction = {
  120. count: number;
  121. type: 'UPDATE_EVENT_COUNT';
  122. };
  123. type UpdateSidebarAction = {
  124. isOpen: boolean;
  125. type: 'UPDATE_SIDEBAR_STATE';
  126. };
  127. export type IssueDetailsActions =
  128. | UpdateEventSectionAction
  129. | UpdateNavScrollMarginAction
  130. | UpdateEventCountAction
  131. | UpdateSidebarAction;
  132. function updateEventSection(
  133. state: IssueDetailsState,
  134. sectionKey: SectionKey,
  135. updatedConfig: Partial<SectionConfig>
  136. ): IssueDetailsState {
  137. const existingConfig = state.sectionData[sectionKey] ?? {key: sectionKey};
  138. const nextState: IssueDetailsState = {
  139. ...state,
  140. sectionData: {
  141. ...state.sectionData,
  142. [sectionKey]: {...existingConfig, ...updatedConfig},
  143. },
  144. };
  145. return nextState;
  146. }
  147. /**
  148. * If trying to use the current state of the issue/event page, you likely want to use
  149. * `useIssueDetails` instead. This hook is just meant to create state for the provider.
  150. */
  151. export function useIssueDetailsReducer() {
  152. const initialState: IssueDetailsState = {
  153. sectionData: {},
  154. isSidebarOpen: true,
  155. eventCount: 0,
  156. navScrollMargin: 0,
  157. };
  158. const reducer: Reducer<IssueDetailsState, IssueDetailsActions> = useCallback(
  159. (state, action): IssueDetailsState => {
  160. switch (action.type) {
  161. case 'UPDATE_SIDEBAR_STATE':
  162. return {...state, isSidebarOpen: action.isOpen};
  163. case 'UPDATE_NAV_SCROLL_MARGIN':
  164. return {...state, navScrollMargin: action.margin};
  165. case 'UPDATE_EVENT_SECTION':
  166. return updateEventSection(state, action.key, action.config ?? {});
  167. case 'UPDATE_EVENT_COUNT':
  168. return {...state, eventCount: action.count};
  169. default:
  170. return state;
  171. }
  172. },
  173. []
  174. );
  175. const [issueDetails, dispatch] = useReducer(reducer, initialState);
  176. return {
  177. issueDetails,
  178. dispatch,
  179. };
  180. }