import { createContext, type Dispatch, type Reducer, useCallback, useContext, useReducer, } from 'react'; export const enum SectionKey { /** * Trace timeline or linked error */ TRACE = 'trace', USER_FEEDBACK = 'user-feedback', LLM_MONITORING = 'llm-monitoring', UPTIME = 'uptime', // Only Uptime issues DOWNTIME = 'downtime', CRON_TIMELINE = 'cron-timeline', // Only Cron issues HIGHLIGHTS = 'highlights', RESOURCES = 'resources', // Position controlled by flag EXCEPTION = 'exception', STACKTRACE = 'stacktrace', SPANS = 'spans', EVIDENCE = 'evidence', MESSAGE = 'message', SUSPECT_ROOT_CAUSE = 'suspect-root-cause', SPAN_EVIDENCE = 'span-evidence', HYDRATION_DIFF = 'hydration-diff', REPLAY = 'replay', HPKP = 'hpkp', CSP = 'csp', EXPECTCT = 'expectct', EXPECTSTAPLE = 'expectstaple', TEMPLATE = 'template', BREADCRUMBS = 'breadcrumbs', /** * Also called images loaded */ DEBUGMETA = 'debugmeta', REQUEST = 'request', TAGS = 'tags', SCREENSHOT = 'screenshot', FEATURE_FLAGS = 'feature-flags', CONTEXTS = 'contexts', EXTRA = 'extra', PACKAGES = 'packages', DEVICE = 'device', VIEW_HIERARCHY = 'view-hierarchy', ATTACHMENTS = 'attachments', SDK = 'sdk', GROUPING_INFO = 'grouping-info', PROCESSING_ERROR = 'processing-error', RRWEB = 'rrweb', // Legacy integration prior to replays MERGED_ISSUES = 'merged', SIMILAR_ISSUES = 'similar', REGRESSION_SUMMARY = 'regression-summary', REGRESSION_BREAKPOINT_CHART = 'regression-breakpoint-chart', REGRESSION_FLAMEGRAPH = 'regression-flamegraph', REGRESSION_PROFILE_COMPARISON = 'regression-profile-comparison', REGRESSION_EVENT_COMPARISON = 'regression-event-comparison', REGRESSION_POTENTIAL_CAUSES = 'regression-potential-causes', REGRESSION_AFFECTED_TRANSACTIONS = 'regression-affected-transactions', } /** * This can be extended to create shared state for each section. * For example, if we needed to know the number of context cards we're rendering, * the can update the config for other components to read from. */ export interface SectionConfig { key: SectionKey; initialCollapse?: boolean; } export interface EventDetailsContextType extends EventDetailsState { dispatch: Dispatch; } export const EventDetailsContext = createContext({ sectionData: {}, dispatch: () => {}, }); export function useEventDetails() { return useContext(EventDetailsContext); } export interface EventDetailsState { sectionData: { [key in SectionKey]?: SectionConfig; }; navScrollMargin?: number; } type UpdateSectionAction = { key: SectionKey; type: 'UPDATE_SECTION'; config?: Partial; }; type UpdateDetailsAction = { type: 'UPDATE_DETAILS'; state?: Omit; }; export type EventDetailsActions = UpdateSectionAction | UpdateDetailsAction; function updateSection( state: EventDetailsState, sectionKey: SectionKey, updatedConfig: Partial ): EventDetailsState { const existingConfig = state.sectionData[sectionKey] ?? {key: sectionKey}; const nextState: EventDetailsState = { ...state, sectionData: { ...state.sectionData, [sectionKey]: {...existingConfig, ...updatedConfig}, }, }; return nextState; } /** * If trying to use the current state of the event page, you likely want to use `useEventDetails` * instead. This hook is just meant to create state for the provider. */ export function useEventDetailsReducer() { const initialState: EventDetailsState = { sectionData: {}, }; const reducer: Reducer = useCallback( (state, action): EventDetailsState => { switch (action.type) { case 'UPDATE_SECTION': return updateSection(state, action.key, action.config ?? {}); case 'UPDATE_DETAILS': return {...state, ...action.state}; default: return state; } }, [] ); const [eventDetails, dispatch] = useReducer(reducer, initialState); return { eventDetails, dispatch, }; }