diff --git a/satellite/analytics/service.go b/satellite/analytics/service.go index ce4adbfb759c..eb3437e38404 100644 --- a/satellite/analytics/service.go +++ b/satellite/analytics/service.go @@ -17,6 +17,11 @@ import ( ) const ( + // SourceTrialExpiringNotice is the trial expiring notice source. + SourceTrialExpiringNotice = "trial_expiring_notice" + // SourceTrialExpiredNotice is the trial expired notice source. + SourceTrialExpiredNotice = "trial_expired_notice" + eventInviteLinkClicked = "Invite Link Clicked" eventInviteLinkSignup = "Invite Link Signup" eventAccountCreated = "Account Created" @@ -111,6 +116,7 @@ const ( eventBusinessSelected = "Business Selected" eventUserUpgraded = "User Upgraded" eventUpgradeClicked = "Upgrade Clicked" + eventArrivedFromSource = "Arrived From Source" ) var ( @@ -173,6 +179,7 @@ type Service struct { config Config satelliteName string clientEvents map[string]bool + sources map[string]interface{} segment segment.Client hubspot *HubSpotEvents @@ -185,6 +192,7 @@ func NewService(log *zap.Logger, config Config, satelliteName string) *Service { config: config, satelliteName: satelliteName, clientEvents: make(map[string]bool), + sources: make(map[string]interface{}), hubspot: NewHubSpotEvents(log.Named("hubspotclient"), config.HubSpot, satelliteName), } if config.Enabled { @@ -204,10 +212,13 @@ func NewService(log *zap.Logger, config Config, satelliteName string) *Service { eventProjectStorageLimitUpdated, eventProjectBandwidthLimitUpdated, eventProjectInvitationAccepted, eventProjectInvitationDeclined, eventGalleryViewClicked, eventResendInviteClicked, eventRemoveProjectMemberCLicked, eventCopyInviteLinkClicked, eventUserSignUp, eventPersonalInfoSubmitted, eventBusinessInfoSubmitted, eventUseCaseSelected, eventOnboardingCompleted, eventOnboardingAbandoned, - eventPersonalSelected, eventBusinessSelected, eventUserUpgraded, eventUpgradeClicked} { + eventPersonalSelected, eventBusinessSelected, eventUserUpgraded, eventUpgradeClicked, eventArrivedFromSource} { service.clientEvents[name] = true } + service.sources[SourceTrialExpiredNotice] = struct{}{} + service.sources[SourceTrialExpiringNotice] = struct{}{} + return service } @@ -657,6 +668,13 @@ func (service *Service) TrackEvent(eventName string, userID uuid.UUID, email str return } + if v, ok := customProps["source"]; ok { + if _, ok = service.sources[v]; !ok { + service.log.Error("Event source is not in allowed list", zap.String("eventName", eventName), zap.String("source", v)) + return + } + } + props := segment.NewProperties() props.Set("email", email) diff --git a/satellite/console/emailreminders/chore.go b/satellite/console/emailreminders/chore.go index a40f5596c67b..2e2128e87790 100644 --- a/satellite/console/emailreminders/chore.go +++ b/satellite/console/emailreminders/chore.go @@ -5,6 +5,7 @@ package emailreminders import ( "context" + "fmt" "strings" "time" @@ -14,6 +15,7 @@ import ( "storj.io/common/sync2" "storj.io/storj/private/post" + "storj.io/storj/satellite/analytics" "storj.io/storj/satellite/console" "storj.io/storj/satellite/console/consoleauth" "storj.io/storj/satellite/console/consoleweb/consoleapi" @@ -144,7 +146,7 @@ func (chore *Chore) sendExpirationNotifications(ctx context.Context) (err error) mon.IntVal("expiring_needing_reminder").Observe(int64(len(users))) expirationWarning := &console.TrialExpirationReminderEmail{ - SignInLink: chore.address + "login?source=trial_expiring_notice", + SignInLink: chore.address + fmt.Sprintf("login?source=%s", analytics.SourceTrialExpiringNotice), Origin: chore.address, ContactInfoURL: chore.supportURL, ScheduleMeetingLink: chore.scheduleMeetingURL, @@ -171,7 +173,7 @@ func (chore *Chore) sendExpirationNotifications(ctx context.Context) (err error) mon.IntVal("expired_needing_notice").Observe(int64(len(users))) expirationNotice := &console.TrialExpiredEmail{ - SignInLink: chore.address + "login?source=trial_expired_notice", + SignInLink: chore.address + fmt.Sprintf("login?source=%s", analytics.SourceTrialExpiredNotice), Origin: chore.address, ContactInfoURL: chore.supportURL, ScheduleMeetingLink: chore.scheduleMeetingURL, diff --git a/web/satellite/src/App.vue b/web/satellite/src/App.vue index 26ad08f970a1..114b31c4842f 100644 --- a/web/satellite/src/App.vue +++ b/web/satellite/src/App.vue @@ -77,6 +77,7 @@ const user = computed(() => usersStore.state.user); */ async function setup() { isLoading.value = true; + const source = new URLSearchParams(window.location.search).get('source'); try { await usersStore.getUser(); const promises: Promise[] = [ @@ -93,6 +94,12 @@ async function setup() { const invites = projectsStore.state.invitations; const projects = projectsStore.state.projects; + if (source) { + const props = new Map(); + props.set('source', source); + analyticsStore.eventTriggered(AnalyticsEvent.ARRIVED_FROM_SOURCE, props); + } + if (appStore.state.hasJustLoggedIn && !invites.length && projects.length <= 1) { if (!projects.length) { await projectsStore.createDefaultProject(usersStore.state.user.id); diff --git a/web/satellite/src/store/modules/analyticsStore.ts b/web/satellite/src/store/modules/analyticsStore.ts index 7ea023207fc0..93fd5abfd293 100644 --- a/web/satellite/src/store/modules/analyticsStore.ts +++ b/web/satellite/src/store/modules/analyticsStore.ts @@ -34,16 +34,16 @@ export const useAnalyticsStore = defineStore('analytics', () => { } catch (_) { /*empty*/ } } - function eventTriggered(eventName: AnalyticsEvent, props?: {[p: string]: string}): void { - analytics.eventTriggered(eventName, props).catch(_ => {}); + function eventTriggered(eventName: AnalyticsEvent, props?: Map): void { + analytics.eventTriggered(eventName, props).catch(_ => { }); } function linkEventTriggered(eventName: AnalyticsEvent, link: string): void { - analytics.linkEventTriggered(eventName, link).catch(_ => {}); + analytics.linkEventTriggered(eventName, link).catch(_ => { }); } function pageVisit(pagePath: string, source: string): void { - analytics.pageVisit(pagePath).catch(_ => {}); + analytics.pageVisit(pagePath).catch(_ => { }); if (!plausible.value) { return; @@ -73,7 +73,7 @@ export const useAnalyticsStore = defineStore('analytics', () => { } function errorEventTriggered(source: AnalyticsErrorEventSource): void { - analytics.errorEventTriggered(source).catch(_ => {}); + analytics.errorEventTriggered(source).catch(_ => { }); } return { diff --git a/web/satellite/src/utils/constants/analyticsEventNames.ts b/web/satellite/src/utils/constants/analyticsEventNames.ts index b2695f063c6d..f878c150ded6 100644 --- a/web/satellite/src/utils/constants/analyticsEventNames.ts +++ b/web/satellite/src/utils/constants/analyticsEventNames.ts @@ -48,6 +48,7 @@ export enum AnalyticsEvent { PERSONAL_SELECTED = 'Personal Selected', BUSINESS_SELECTED = 'Business Selected', UPGRADE_CLICKED = 'Upgrade Clicked', + ARRIVED_FROM_SOURCE = 'Arrived From Source', } export enum AnalyticsErrorEventSource {