hookStore.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import * as Sentry from '@sentry/react';
  2. import isUndefined from 'lodash/isUndefined';
  3. import Reflux from 'reflux';
  4. import {HookName, Hooks} from 'app/types/hooks';
  5. /**
  6. * See types/hooks for hook usage reference.
  7. */
  8. const validHookNames = new Set<HookName>([
  9. '_',
  10. 'analytics:event',
  11. 'analytics:init-user',
  12. 'analytics:track-adhoc-event',
  13. 'analytics:track-event',
  14. 'analytics:log-experiment',
  15. 'component:disabled-member',
  16. 'component:disabled-member-tooltip',
  17. 'component:header-date-range',
  18. 'component:header-selector-items',
  19. 'component:global-notifications',
  20. 'component:member-list-header',
  21. 'component:dashboards-header',
  22. 'feature-disabled:alerts-page',
  23. 'feature-disabled:alert-wizard-performance',
  24. 'feature-disabled:configure-distributed-tracing',
  25. 'feature-disabled:custom-inbound-filters',
  26. 'feature-disabled:custom-symbol-sources',
  27. 'feature-disabled:data-forwarding',
  28. 'feature-disabled:discard-groups',
  29. 'feature-disabled:discover-page',
  30. 'feature-disabled:discover-saved-query-create',
  31. 'feature-disabled:discover-sidebar-item',
  32. 'feature-disabled:discover2-page',
  33. 'feature-disabled:discover2-sidebar-item',
  34. 'feature-disabled:events-page',
  35. 'feature-disabled:events-sidebar-item',
  36. 'feature-disabled:grid-editable-actions',
  37. 'feature-disabled:open-discover',
  38. 'feature-disabled:dashboards-edit',
  39. 'feature-disabled:dashboards-page',
  40. 'feature-disabled:dashboards-sidebar-item',
  41. 'feature-disabled:incidents-sidebar-item',
  42. 'feature-disabled:performance-new-project',
  43. 'feature-disabled:performance-page',
  44. 'feature-disabled:performance-quick-trace',
  45. 'feature-disabled:performance-sidebar-item',
  46. 'feature-disabled:project-performance-score-card',
  47. 'feature-disabled:project-selector-checkbox',
  48. 'feature-disabled:rate-limits',
  49. 'feature-disabled:sso-basic',
  50. 'feature-disabled:sso-rippling',
  51. 'feature-disabled:sso-saml2',
  52. 'feature-disabled:trace-view-link',
  53. 'footer',
  54. 'help-modal:footer',
  55. 'integrations:feature-gates',
  56. 'member-invite-modal:customization',
  57. 'metrics:event',
  58. 'onboarding:extra-chrome',
  59. 'onboarding-wizard:skip-help',
  60. 'organization:header',
  61. 'routes',
  62. 'routes:admin',
  63. 'routes:api',
  64. 'routes:organization',
  65. 'routes:organization-root',
  66. 'settings:api-navigation-config',
  67. 'settings:organization-navigation',
  68. 'settings:organization-navigation-config',
  69. 'sidebar:bottom-items',
  70. 'sidebar:help-menu',
  71. 'sidebar:item-label',
  72. 'sidebar:item-override',
  73. 'sidebar:organization-dropdown-menu',
  74. 'sidebar:organization-dropdown-menu-bottom',
  75. ]);
  76. type HookStoreInterface = {
  77. // XXX(epurkhiser): We could type this as {[H in HookName]?:
  78. // Array<Hooks[H]>}, however this causes typescript to produce a complex
  79. // union that it complains is 'too complex'
  80. hooks: any;
  81. add<H extends HookName>(hookName: H, callback: Hooks[H]): void;
  82. remove<H extends HookName>(hookName: H, callback: Hooks[H]): void;
  83. get<H extends HookName>(hookName: H): Array<Hooks[H]>;
  84. };
  85. const hookStoreConfig: Reflux.StoreDefinition & HookStoreInterface = {
  86. hooks: {},
  87. init() {
  88. this.hooks = {};
  89. },
  90. add(hookName, callback) {
  91. // Gracefully error on invalid hooks, but maintain registration
  92. // TODO(ts): With typescript we can remove this in the future
  93. if (!validHookNames.has(hookName)) {
  94. // eslint-disable-next-line no-console
  95. console.error('Invalid hook name: ' + hookName);
  96. Sentry.withScope(scope => {
  97. scope.setExtra('hookName', hookName);
  98. Sentry.captureException(new Error('Invalid hook name'));
  99. });
  100. }
  101. if (isUndefined(this.hooks[hookName])) {
  102. this.hooks[hookName] = [];
  103. }
  104. this.hooks[hookName]!.push(callback);
  105. this.trigger(hookName, this.hooks[hookName]);
  106. },
  107. remove(hookName, callback) {
  108. if (isUndefined(this.hooks[hookName])) {
  109. return;
  110. }
  111. this.hooks[hookName] = this.hooks[hookName]!.filter(cb => cb !== callback);
  112. this.trigger(hookName, this.hooks[hookName]);
  113. },
  114. get(hookName) {
  115. return this.hooks[hookName]! || [];
  116. },
  117. };
  118. type HookStore = Reflux.Store & HookStoreInterface;
  119. /**
  120. * HookStore is used to allow extensibility into Sentry's frontend via
  121. * registration of 'hook functions'.
  122. *
  123. * This functionality is primarily used by the SASS sentry.io product.
  124. */
  125. const HookStore = Reflux.createStore(hookStoreConfig) as HookStore;
  126. export default HookStore;