useExperiment.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import {useCallback, useEffect} from 'react';
  2. import * as Sentry from '@sentry/react';
  3. import {experimentConfig, unassignedValue} from 'sentry/data/experimentConfig';
  4. import ConfigStore from 'sentry/stores/configStore';
  5. import {useLegacyStore} from 'sentry/stores/useLegacyStore';
  6. import {
  7. ExperimentAssignment,
  8. ExperimentKey,
  9. ExperimentType,
  10. OrgExperiments,
  11. UserExperiments,
  12. } from 'sentry/types/experiments';
  13. import {defined} from 'sentry/utils';
  14. import {logExperiment as logExperimentAnalytics} from 'sentry/utils/analytics';
  15. import useOrganization from 'sentry/utils/useOrganization';
  16. type UseExperimentOptions = {
  17. /**
  18. * By default this hook will log the exposure of the experiment upon mounting
  19. * of the component.
  20. *
  21. * If this is undesirable, for example if the experiment is hidden behind
  22. * some user action beyond this component being mounted, then you will want
  23. * to customize when exposure to the experiment has been logged.
  24. *
  25. * NOTE: If set to false, YOU ARE RESPONSIBLE for logging exposure of the
  26. * experiment!! If you do not log exposure your experiment will not be
  27. * correct!!
  28. */
  29. logExperimentOnMount?: boolean;
  30. };
  31. type UseExperimentReturnValue<E extends ExperimentKey> = {
  32. experimentAssignment: ExperimentAssignment[E];
  33. /**
  34. * Call this method when the user has been exposed to the experiment.
  35. * You do not need to call this unless you have disabled logging on mount.
  36. */
  37. logExperiment: () => void;
  38. };
  39. export type UseExperiment = <E extends ExperimentKey>(
  40. experiment: E,
  41. options?: UseExperimentOptions
  42. ) => UseExperimentReturnValue<E>;
  43. function useExperimentAssignment(experiment: ExperimentKey) {
  44. const organization = useOrganization();
  45. const {user} = useLegacyStore(ConfigStore);
  46. const config = experimentConfig[experiment];
  47. if (!config) {
  48. Sentry.withScope(scope => {
  49. scope.setExtra('experiment', experiment);
  50. Sentry.captureMessage(
  51. 'useExperiment called with an experiment that does not exist in the config.'
  52. );
  53. });
  54. return unassignedValue;
  55. }
  56. if (config.type === ExperimentType.Organization) {
  57. const key = experiment as keyof OrgExperiments;
  58. const assignment = organization.experiments?.[key];
  59. if (!defined(assignment)) {
  60. Sentry.withScope(scope => {
  61. scope.setExtra('experiment', experiment);
  62. scope.setExtra('orgExperiments', organization.experiments);
  63. Sentry.captureMessage(
  64. 'useExperiment called with org experiment but no matching experiment exists on the org.'
  65. );
  66. });
  67. }
  68. return assignment ?? unassignedValue;
  69. }
  70. if (config.type === ExperimentType.User) {
  71. const key = experiment as keyof UserExperiments;
  72. const assignment = user?.experiments?.[key];
  73. if (!defined(assignment)) {
  74. Sentry.withScope(scope => {
  75. scope.setExtra('experiment', experiment);
  76. scope.setExtra('userExperiments', user?.experiments);
  77. Sentry.captureMessage(
  78. 'useExperiment called with user experiment but no matching experiment exists on the user.'
  79. );
  80. });
  81. }
  82. return assignment ?? unassignedValue;
  83. }
  84. return unassignedValue;
  85. }
  86. export const useExperiment: UseExperiment = (
  87. experiment,
  88. {logExperimentOnMount = true} = {}
  89. ) => {
  90. const organization = useOrganization();
  91. const logExperiment = useCallback(() => {
  92. logExperimentAnalytics({
  93. key: experiment,
  94. organization,
  95. });
  96. }, [experiment, organization]);
  97. useEffect(() => {
  98. if (logExperimentOnMount) {
  99. logExperiment();
  100. }
  101. }, [logExperiment, logExperimentOnMount]);
  102. const experimentAssignment = useExperimentAssignment(experiment);
  103. return {
  104. experimentAssignment,
  105. logExperiment,
  106. };
  107. };