context.tsx 5.2 KB

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