tracePreferences.tsx 5.7 KB

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