analyticsInitUser.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import * as Amplitude from '@amplitude/analytics-browser';
  2. import * as Sentry from '@sentry/react';
  3. import * as qs from 'query-string';
  4. import ConfigStore from 'sentry/stores/configStore';
  5. import type {User} from 'sentry/types/user';
  6. import sessionStorageWrapper from 'sentry/utils/sessionStorage';
  7. import trackMarketingEvent from 'getsentry/utils/trackMarketingEvent';
  8. const MARKETING_EVENT_SESSION_KEY = 'marketing_event_recorded';
  9. // fields should be string but need to validate
  10. type MarketingEventSchema = {
  11. event_name: unknown;
  12. event_label?: unknown;
  13. };
  14. /**
  15. * This function initializes the user for analytics (Amplitude)
  16. * It also handles other initialization logic for analytics like sending
  17. * events to Google Analytics and storing the previous_referrer into local storage
  18. */
  19. export default function analyticsInitUser(user: User) {
  20. const {frontend_events, referrer} = qs.parse(window.location.search) || {};
  21. // store the referrer in sessionStorage so we know what it was when the user
  22. // navigates to another page
  23. if (referrer && typeof referrer === 'string') {
  24. sessionStorageWrapper.setItem('previous_referrer', referrer);
  25. }
  26. // quit early if analytics is disabled
  27. if (!ConfigStore.get('enableAnalytics')) {
  28. return;
  29. }
  30. const amplitudeKey = ConfigStore.get('getsentry.amplitudeApiKey');
  31. if (!amplitudeKey) {
  32. return;
  33. }
  34. Amplitude.init(amplitudeKey, undefined, {
  35. minIdLength: 1,
  36. attribution: {
  37. disabled: false,
  38. },
  39. trackingOptions: {
  40. city: false,
  41. ip_address: false,
  42. dma: false,
  43. },
  44. });
  45. // Most of our in-app amplitude logging happens on the backend, whereas anonymous user tracking
  46. // happens in the JS SDK. This means when a user signs up, we need to somehow link their
  47. // anonymous activity with their in-app activity. Logging a dummy event from the JS SDK in-app
  48. // accomplishes that.
  49. const identify = new Amplitude.Identify();
  50. const identifyObj = identify
  51. .set('user_id', user.id)
  52. .set('lastAppPageLoad', new Date().toISOString())
  53. .set(
  54. 'isInternalUser',
  55. user.identities.some(ident => ident.organization?.slug === 'sentry') ||
  56. user.emails.some(email => email.email?.endsWith('sentry.io')) || // Has a sentry.io email
  57. user.isSuperuser // Has an identity for the Sentry organization.
  58. );
  59. // pass in timestamp from this moment instead of letting Amplitude determine it
  60. // which is roughly 100 ms later
  61. Amplitude.identify(identifyObj, {time: Date.now()});
  62. // the backend can send any arbitrary marketing events
  63. if (frontend_events && typeof frontend_events === 'string') {
  64. // if we've already recorded this event for this session, we don't need to record it again
  65. // this helps prevent duplicate events from mulitiple refreshes
  66. if (sessionStorageWrapper.getItem(MARKETING_EVENT_SESSION_KEY) === frontend_events) {
  67. return;
  68. }
  69. try {
  70. // events could either be an array or a single event
  71. const eventData = JSON.parse(frontend_events) as
  72. | MarketingEventSchema
  73. | MarketingEventSchema[];
  74. const events = Array.isArray(eventData) ? eventData : [eventData];
  75. events.forEach(event => {
  76. const {event_name, event_label} = event;
  77. // check event name is truthy string
  78. if (!event_name || typeof event_name !== 'string') {
  79. // eslint-disable-next-line no-console
  80. console.warn('Invalid event_name');
  81. return;
  82. }
  83. // check event label is either undefined or string
  84. if (event_label !== undefined && typeof event_label !== 'string') {
  85. // eslint-disable-next-line no-console
  86. console.warn('Invalid event_labels');
  87. return;
  88. }
  89. trackMarketingEvent(event_name, {event_label});
  90. });
  91. } catch (err) {
  92. Sentry.captureException(err);
  93. }
  94. sessionStorageWrapper.setItem(MARKETING_EVENT_SESSION_KEY, frontend_events);
  95. }
  96. }