context.tsx 5.7 KB

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