tracePreferences.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import * as Sentry from '@sentry/react';
  2. import clamp from 'sentry/utils/number/clamp';
  3. import {traceReducerExhaustiveActionCheck} from '../traceState';
  4. type TraceLayoutPreferences = 'drawer left' | 'drawer bottom' | 'drawer right';
  5. type TracePreferencesAction =
  6. | {payload: TraceLayoutPreferences; type: 'set layout'}
  7. | {
  8. payload: number;
  9. type: 'set drawer dimension';
  10. }
  11. | {payload: number; type: 'set list width'}
  12. | {payload: boolean; type: 'minimize drawer'}
  13. | {payload: boolean; type: 'set missing instrumentation'}
  14. | {payload: boolean; type: 'set autogrouping'};
  15. type TraceDrawerPreferences = {
  16. layoutOptions: TraceLayoutPreferences[];
  17. minimized: boolean;
  18. sizes: {
  19. [key in TraceLayoutPreferences]: number;
  20. };
  21. };
  22. export type TracePreferencesState = {
  23. autogroup: {
  24. parent: boolean;
  25. sibling: boolean;
  26. };
  27. drawer: TraceDrawerPreferences;
  28. layout: TraceLayoutPreferences;
  29. list: {
  30. width: number;
  31. };
  32. missing_instrumentation: boolean;
  33. };
  34. export const TRACE_DRAWER_DEFAULT_SIZES: TraceDrawerPreferences['sizes'] = {
  35. 'drawer left': 0.33,
  36. 'drawer right': 0.33,
  37. 'drawer bottom': 0.5,
  38. };
  39. export const DEFAULT_TRACE_VIEW_PREFERENCES: TracePreferencesState = {
  40. drawer: {
  41. minimized: false,
  42. sizes: {
  43. 'drawer left': 0.33,
  44. 'drawer right': 0.33,
  45. 'drawer bottom': 0.5,
  46. },
  47. layoutOptions: ['drawer left', 'drawer right', 'drawer bottom'],
  48. },
  49. autogroup: {
  50. parent: true,
  51. sibling: true,
  52. },
  53. missing_instrumentation: true,
  54. layout: 'drawer right',
  55. list: {
  56. width: 0.5,
  57. },
  58. };
  59. export function storeTraceViewPreferences(
  60. key: string,
  61. state: TracePreferencesState
  62. ): void {
  63. // Make sure we dont fire this during a render phase
  64. window.requestAnimationFrame(() => {
  65. try {
  66. localStorage.setItem(key, JSON.stringify(state));
  67. } catch (e) {
  68. Sentry.captureException(e);
  69. }
  70. });
  71. }
  72. function isInt(value: any): value is number {
  73. return typeof value === 'number' && !isNaN(value);
  74. }
  75. function correctListWidth(state: TracePreferencesState): TracePreferencesState {
  76. if (state.list.width < 0.1 || state.list.width > 0.9) {
  77. state.list.width = 0.5;
  78. }
  79. return state;
  80. }
  81. function isPreferenceState(parsed: any): parsed is TracePreferencesState {
  82. return (
  83. parsed?.drawer &&
  84. typeof parsed.drawer.minimized === 'boolean' &&
  85. Array.isArray(parsed.drawer.layoutOptions) &&
  86. parsed.drawer.sizes &&
  87. isInt(parsed.drawer.sizes['drawer left']) &&
  88. isInt(parsed.drawer.sizes['drawer right']) &&
  89. isInt(parsed.drawer.sizes['drawer bottom']) &&
  90. parsed.layout &&
  91. typeof parsed.layout === 'string' &&
  92. parsed.list &&
  93. isInt(parsed.list.width)
  94. );
  95. }
  96. function isValidAutogrouping(
  97. state: TracePreferencesState
  98. ): state is TracePreferencesState & {autogrouping: undefined} {
  99. if (state.autogroup === undefined) {
  100. return false;
  101. }
  102. if (
  103. typeof state.autogroup.parent !== 'boolean' ||
  104. typeof state.autogroup.sibling !== 'boolean'
  105. ) {
  106. return false;
  107. }
  108. return true;
  109. }
  110. function isValidMissingInstrumentation(
  111. state: TracePreferencesState
  112. ): state is TracePreferencesState & {missing_instrumentation: undefined} {
  113. if (typeof state.missing_instrumentation !== 'boolean') {
  114. return false;
  115. }
  116. return true;
  117. }
  118. export function loadTraceViewPreferences(key: string): TracePreferencesState | null {
  119. const stored = localStorage.getItem(key);
  120. if (stored) {
  121. try {
  122. const parsed = JSON.parse(stored);
  123. // We need a more robust way to validate the stored preferences.
  124. // Since we dont have a schema validation lib, just do it manually for now.
  125. if (isPreferenceState(parsed)) {
  126. correctListWidth(parsed);
  127. // Correct old preferences that are missing autogrouping
  128. if (!isValidAutogrouping(parsed)) {
  129. parsed.autogroup = {...DEFAULT_TRACE_VIEW_PREFERENCES.autogroup};
  130. }
  131. if (!isValidMissingInstrumentation(parsed)) {
  132. parsed.missing_instrumentation =
  133. DEFAULT_TRACE_VIEW_PREFERENCES.missing_instrumentation;
  134. }
  135. return parsed;
  136. }
  137. } catch (e) {
  138. Sentry.captureException(e);
  139. }
  140. }
  141. return null;
  142. }
  143. export function tracePreferencesReducer(
  144. state: TracePreferencesState,
  145. action: TracePreferencesAction
  146. ): TracePreferencesState {
  147. switch (action.type) {
  148. case 'minimize drawer':
  149. return {...state, drawer: {...state.drawer, minimized: action.payload}};
  150. case 'set layout':
  151. return {
  152. ...state,
  153. layout: action.payload,
  154. drawer: {...state.drawer, minimized: false},
  155. };
  156. case 'set drawer dimension':
  157. return {
  158. ...state,
  159. drawer: {
  160. ...state.drawer,
  161. sizes: {
  162. ...state.drawer.sizes,
  163. [state.layout]: clamp(action.payload, 0, 1),
  164. },
  165. },
  166. };
  167. case 'set autogrouping': {
  168. return {
  169. ...state,
  170. autogroup: {sibling: action.payload, parent: action.payload},
  171. };
  172. }
  173. case 'set missing instrumentation':
  174. return {
  175. ...state,
  176. missing_instrumentation: action.payload,
  177. };
  178. case 'set list width':
  179. return {
  180. ...state,
  181. list: {
  182. width: clamp(action.payload, 0.1, 0.9),
  183. },
  184. };
  185. default:
  186. traceReducerExhaustiveActionCheck(action);
  187. return state;
  188. }
  189. }