context.tsx 4.0 KB

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