diff --git a/satellite/analytics/service.go b/satellite/analytics/service.go index 9b76ebc2a..8318e8327 100644 --- a/satellite/analytics/service.go +++ b/satellite/analytics/service.go @@ -11,11 +11,14 @@ import ( ) const ( - eventAccountCreated = "Account Created" - eventSignedIn = "Signed In" - eventProjectCreated = "Project Created" - eventAccessGrantCreated = "Access Grant Created" - gatewayCredentialsCreated = "Credentials Created" + eventAccountCreated = "Account Created" + eventSignedIn = "Signed In" + eventProjectCreated = "Project Created" + eventAccessGrantCreated = "Access Grant Created" + eventAccountVerified = "Account Verified" + eventGatewayCredentialsCreated = "Credentials Created" + eventPassphraseCreated = "Passphrase Created" + eventExternalLinkClicked = "External Link Clicked" ) // Config is a configuration struct for analytics Service. @@ -47,7 +50,7 @@ func NewService(log *zap.Logger, config Config, satelliteName string) *Service { if config.Enabled { service.segment = segment.New(config.SegmentWriteKey) } - for _, name := range []string{gatewayCredentialsCreated} { + for _, name := range []string{eventGatewayCredentialsCreated, eventPassphraseCreated, eventExternalLinkClicked} { service.clientEvents[name] = true } return service @@ -170,6 +173,26 @@ func (service *Service) TrackAccessGrantCreated(userID uuid.UUID) { }) } +// TrackAccountVerified sends an "Account Verified" event to Segment. +func (service *Service) TrackAccountVerified(userID uuid.UUID, email string) { + traits := segment.NewTraits() + traits.SetEmail(email) + + service.enqueueMessage(segment.Identify{ + UserId: userID.String(), + Traits: traits, + }) + + props := segment.NewProperties() + props.Set("email", email) + + service.enqueueMessage(segment.Track{ + UserId: userID.String(), + Event: eventAccountVerified, + Properties: props, + }) +} + // TrackEvent sends an arbitrary event associated with user ID to Segment. // It is used for tracking occurrences of client-side events. func (service *Service) TrackEvent(eventName string, userID uuid.UUID) { @@ -178,9 +201,28 @@ func (service *Service) TrackEvent(eventName string, userID uuid.UUID) { service.log.Error("Invalid client-triggered event", zap.String("eventName", eventName)) return } - service.enqueueMessage(segment.Track{ UserId: userID.String(), Event: eventName, }) } + +// TrackLinkEvent sends an arbitrary event and link associated with user ID to Segment. +// It is used for tracking occurrences of client-side events. +func (service *Service) TrackLinkEvent(eventName string, userID uuid.UUID, link string) { + + // do not track if the event name is an invalid client-side event + if !service.clientEvents[eventName] { + service.log.Error("Invalid client-triggered event", zap.String("eventName", eventName)) + return + } + + props := segment.NewProperties() + props.Set("link", link) + + service.enqueueMessage(segment.Track{ + UserId: userID.String(), + Event: eventName, + Properties: props, + }) +} diff --git a/satellite/console/consoleweb/consoleapi/analytics.go b/satellite/console/consoleweb/consoleapi/analytics.go index c47f0725c..286d28853 100644 --- a/satellite/console/consoleweb/consoleapi/analytics.go +++ b/satellite/console/consoleweb/consoleapi/analytics.go @@ -38,6 +38,7 @@ func NewAnalytics(log *zap.Logger, service *console.Service, a *analytics.Servic type eventTriggeredBody struct { EventName string `json:"eventName"` + Link string `json:"link"` } // EventTriggered tracks the occurrence of an arbitrary event on the client. @@ -61,7 +62,11 @@ func (a *Analytics) EventTriggered(w http.ResponseWriter, r *http.Request) { return } - a.analytics.TrackEvent(et.EventName, userID) + if et.Link != "" { + a.analytics.TrackLinkEvent(et.EventName, userID, et.Link) + } else { + a.analytics.TrackEvent(et.EventName, userID) + } w.WriteHeader(http.StatusOK) } diff --git a/satellite/console/service.go b/satellite/console/service.go index f4909ef03..4813056e2 100644 --- a/satellite/console/service.go +++ b/satellite/console/service.go @@ -698,6 +698,8 @@ func (s *Service) ActivateAccount(ctx context.Context, activationToken string) ( s.log.Debug(fmt.Sprintf("could not add promotional coupon for user %s", user.ID.String()), zap.Error(Error.Wrap(err))) } + s.analytics.TrackAccountVerified(user.ID, user.Email) + return nil } diff --git a/web/satellite/src/api/analytics.ts b/web/satellite/src/api/analytics.ts index a1192e6ef..0f48e3611 100644 --- a/web/satellite/src/api/analytics.ts +++ b/web/satellite/src/api/analytics.ts @@ -29,4 +29,17 @@ export class AnalyticsHttpApi { throw new Error('Can not track event'); } + + public async linkEventTriggered(eventName: string, link: string): Promise { + const path = `${this.ROOT_PATH}/event`; + const body = { + eventName: eventName, + link: link, + }; + const response = await this.http.post(path, JSON.stringify(body)); + if (response.ok) { + return; + } + throw new Error('Can not track event'); + } } diff --git a/web/satellite/src/components/common/GeneratePassphrase.vue b/web/satellite/src/components/common/GeneratePassphrase.vue index 79d6d7596..e6250cea2 100644 --- a/web/satellite/src/components/common/GeneratePassphrase.vue +++ b/web/satellite/src/components/common/GeneratePassphrase.vue @@ -85,6 +85,9 @@ import VButton from '@/components/common/VButton.vue'; import BackIcon from '@/../static/images/accessGrants/back.svg'; import WarningIcon from '@/../static/images/accessGrants/warning.svg'; +import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames'; +import { AnalyticsHttpApi } from '@/api/analytics'; + @Component({ components: { WarningIcon, @@ -100,6 +103,7 @@ export default class GeneratePassphrase extends Vue { public readonly setParentPassphrase: (passphrase: string) => void; @Prop({ default: false }) public readonly isLoading: boolean; + private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi(); public isGenerateState: boolean = true; public isCreateState: boolean = false; @@ -130,6 +134,8 @@ export default class GeneratePassphrase extends Vue { return; } + this.analytics.eventTriggered(AnalyticsEvent.PASSPHRASE_CREATED); + this.onButtonClick(); } diff --git a/web/satellite/src/components/header/resourcesDropdown/ResourcesDropdown.vue b/web/satellite/src/components/header/resourcesDropdown/ResourcesDropdown.vue index 57642ccc1..0f18793b7 100644 --- a/web/satellite/src/components/header/resourcesDropdown/ResourcesDropdown.vue +++ b/web/satellite/src/components/header/resourcesDropdown/ResourcesDropdown.vue @@ -12,6 +12,9 @@ import SupportIcon from '@/../static/images/header/support.svg'; import { RouteConfig } from '@/router'; +import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames'; +import { AnalyticsHttpApi } from '@/api/analytics'; + @Component({ components: { DocsIcon, @@ -20,12 +23,26 @@ import { RouteConfig } from '@/router'; }, }) export default class ResourcesDropdown extends Vue { + + private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi(); /** * Indicates if current route is onboarding tour. */ public get isOnboardingTour(): boolean { return this.$route.path.includes(RouteConfig.OnboardingTour.path); } + + public onDocsIconClick(): void { + this.analytics.linkEventTriggered(AnalyticsEvent.EXTERNAL_LINK_CLICKED, "https://documentation.storj.io"); + } + + public onCommunityIconClick(): void { + this.analytics.linkEventTriggered(AnalyticsEvent.EXTERNAL_LINK_CLICKED, "https://storj.io/community/"); + } + + public onSupportIconClick(): void { + this.analytics.linkEventTriggered(AnalyticsEvent.EXTERNAL_LINK_CLICKED, "mailto:support@storj.io"); + } } diff --git a/web/satellite/src/components/header/resourcesDropdown/resourcesDropdown.html b/web/satellite/src/components/header/resourcesDropdown/resourcesDropdown.html index 572eadf8e..69151a7c3 100644 --- a/web/satellite/src/components/header/resourcesDropdown/resourcesDropdown.html +++ b/web/satellite/src/components/header/resourcesDropdown/resourcesDropdown.html @@ -7,6 +7,7 @@ href="https://documentation.storj.io" target="_blank" rel="noopener noreferrer" + @click="onDocsIconClick" >

Docs

@@ -16,6 +17,7 @@ href="https://storj.io/community/" target="_blank" rel="noopener noreferrer" + @click="onCommunityIconClick" >

Community

@@ -25,6 +27,7 @@ href="mailto:support@storj.io" target="_blank" rel="noopener noreferrer" + @click="onSupportIconClick" >

Support

diff --git a/web/satellite/src/utils/constants/analyticsEventNames.ts b/web/satellite/src/utils/constants/analyticsEventNames.ts index 9d3dd61db..accb222f3 100644 --- a/web/satellite/src/utils/constants/analyticsEventNames.ts +++ b/web/satellite/src/utils/constants/analyticsEventNames.ts @@ -4,4 +4,6 @@ // Make sure these event names match up with the client-side event names in satellite/analytics/service.go export enum AnalyticsEvent { GATEWAY_CREDENTIALS_CREATED = 'Credentials Created', + PASSPHRASE_CREATED = 'Passphrase Created', + EXTERNAL_LINK_CLICKED = 'External Link Clicked', }