context.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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. HIGHLIGHTS = 'highlights',
  20. RESOURCES = 'resources', // Position controlled by flag
  21. EXCEPTION = 'exception',
  22. STACKTRACE = 'stacktrace',
  23. SPANS = 'spans',
  24. EVIDENCE = 'evidence',
  25. MESSAGE = 'message',
  26. SUSPECT_ROOT_CAUSE = 'suspect-root-cause',
  27. SPAN_EVIDENCE = 'span-evidence',
  28. HYDRATION_DIFF = 'hydration-diff',
  29. REPLAY = 'replay',
  30. HPKP = 'hpkp',
  31. CSP = 'csp',
  32. EXPECTCT = 'expectct',
  33. EXPECTSTAPLE = 'expectstaple',
  34. TEMPLATE = 'template',
  35. BREADCRUMBS = 'breadcrumbs',
  36. /**
  37. * Also called images loaded
  38. */
  39. DEBUGMETA = 'debugmeta',
  40. REQUEST = 'request',
  41. TAGS = 'tags',
  42. SCREENSHOT = 'screenshot',
  43. FEATURE_FLAGS = 'feature-flags',
  44. CONTEXTS = 'contexts',
  45. EXTRA = 'extra',
  46. PACKAGES = 'packages',
  47. DEVICE = 'device',
  48. VIEW_HIERARCHY = 'view-hierarchy',
  49. ATTACHMENTS = 'attachments',
  50. SDK = 'sdk',
  51. GROUPING_INFO = 'grouping-info',
  52. PROCESSING_ERROR = 'processing-error',
  53. RRWEB = 'rrweb', // Legacy integration prior to replays
  54. MERGED_ISSUES = 'merged',
  55. SIMILAR_ISSUES = 'similar',
  56. REGRESSION_SUMMARY = 'regression-summary',
  57. REGRESSION_BREAKPOINT_CHART = 'regression-breakpoint-chart',
  58. REGRESSION_FLAMEGRAPH = 'regression-flamegraph',
  59. REGRESSION_PROFILE_COMPARISON = 'regression-profile-comparison',
  60. REGRESSION_EVENT_COMPARISON = 'regression-event-comparison',
  61. REGRESSION_POTENTIAL_CAUSES = 'regression-potential-causes',
  62. REGRESSION_AFFECTED_TRANSACTIONS = 'regression-affected-transactions',
  63. }
  64. /**
  65. * This can be extended to create shared state for each section.
  66. * For example, if we needed to know the number of context cards we're rendering,
  67. * the <ContextDataSection /> can update the config for other components to read from.
  68. */
  69. export interface SectionConfig {
  70. key: SectionKey;
  71. initialCollapse?: boolean;
  72. }
  73. export interface EventDetailsContextType extends EventDetailsState {
  74. dispatch: Dispatch<EventDetailsActions>;
  75. }
  76. export const EventDetailsContext = createContext<EventDetailsContextType>({
  77. sectionData: {},
  78. dispatch: () => {},
  79. });
  80. export function useEventDetails() {
  81. return useContext(EventDetailsContext);
  82. }
  83. export interface EventDetailsState {
  84. sectionData: {
  85. [key in SectionKey]?: SectionConfig;
  86. };
  87. navScrollMargin?: number;
  88. }
  89. type UpdateSectionAction = {
  90. key: SectionKey;
  91. type: 'UPDATE_SECTION';
  92. config?: Partial<SectionConfig>;
  93. };
  94. type UpdateDetailsAction = {
  95. type: 'UPDATE_DETAILS';
  96. state?: Omit<EventDetailsState, 'sectionData'>;
  97. };
  98. export type EventDetailsActions = UpdateSectionAction | UpdateDetailsAction;
  99. function updateSection(
  100. state: EventDetailsState,
  101. sectionKey: SectionKey,
  102. updatedConfig: Partial<SectionConfig>
  103. ): EventDetailsState {
  104. const existingConfig = state.sectionData[sectionKey] ?? {key: sectionKey};
  105. const nextState: EventDetailsState = {
  106. ...state,
  107. sectionData: {
  108. ...state.sectionData,
  109. [sectionKey]: {...existingConfig, ...updatedConfig},
  110. },
  111. };
  112. return nextState;
  113. }
  114. /**
  115. * If trying to use the current state of the event page, you likely want to use `useEventDetails`
  116. * instead. This hook is just meant to create state for the provider.
  117. */
  118. export function useEventDetailsReducer() {
  119. const initialState: EventDetailsState = {
  120. sectionData: {},
  121. };
  122. const reducer: Reducer<EventDetailsState, EventDetailsActions> = useCallback(
  123. (state, action): EventDetailsState => {
  124. switch (action.type) {
  125. case 'UPDATE_SECTION':
  126. return updateSection(state, action.key, action.config ?? {});
  127. case 'UPDATE_DETAILS':
  128. return {...state, ...action.state};
  129. default:
  130. return state;
  131. }
  132. },
  133. []
  134. );
  135. const [eventDetails, dispatch] = useReducer(reducer, initialState);
  136. return {
  137. eventDetails,
  138. dispatch,
  139. };
  140. }