satellite/analytics: Add analytics for user signed in, project created, and access grant created (#4073)

* satellite/analytics: Add analytics for user signed in, project created and access grant created events


Co-authored-by: Moby von Briesen <mobyvb@gmail.com>
This commit is contained in:
prerna-parashar 2021-04-08 10:34:23 -07:00 committed by GitHub
parent a264a4422b
commit 16c98e1ecd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 85 additions and 16 deletions

View File

@ -12,6 +12,9 @@ import (
const ( const (
eventAccountCreated = "Account Created" eventAccountCreated = "Account Created"
eventSignedIn = "Signed In"
eventProjectCreated = "Project Created"
eventAccessGrantCreated = "Access Grant Created"
gatewayCredentialsCreated = "Credentials Created" gatewayCredentialsCreated = "Credentials Created"
) )
@ -121,6 +124,49 @@ func (service *Service) TrackCreateUser(fields TrackCreateUserFields) {
}) })
} }
// TrackSignedIn sends an "Signed In" event to Segment.
func (service *Service) TrackSignedIn(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: eventSignedIn,
Properties: props,
})
}
// TrackProjectCreated sends an "Project Created" event to Segment.
func (service *Service) TrackProjectCreated(userID, projectID uuid.UUID, currentProjectCount int) {
props := segment.NewProperties()
props.Set("project_count", currentProjectCount)
props.Set("project_id", projectID.String())
service.enqueueMessage(segment.Track{
UserId: userID.String(),
Event: eventProjectCreated,
Properties: props,
})
}
// TrackAccessGrantCreated sends an "Access Grant Created" event to Segment.
func (service *Service) TrackAccessGrantCreated(userID uuid.UUID) {
service.enqueueMessage(segment.Track{
UserId: userID.String(),
Event: eventAccessGrantCreated,
})
}
// TrackEvent sends an arbitrary event associated with user ID to Segment. // TrackEvent sends an arbitrary event associated with user ID to Segment.
// It is used for tracking occurrences of client-side events. // It is used for tracking occurrences of client-side events.
func (service *Service) TrackEvent(eventName string, userID uuid.UUID) { func (service *Service) TrackEvent(eventName string, userID uuid.UUID) {

View File

@ -362,6 +362,15 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
) )
} }
{ // setup analytics service
peer.Analytics.Service = analytics.NewService(peer.Log.Named("analytics:service"), config.Analytics, config.Console.SatelliteName)
peer.Services.Add(lifecycle.Item{
Name: "analytics:service",
Close: peer.Analytics.Service.Close,
})
}
{ // setup metainfo { // setup metainfo
peer.Metainfo.Database = pointerDB peer.Metainfo.Database = pointerDB
peer.Metainfo.Metabase = metabaseDB peer.Metainfo.Metabase = metabaseDB
@ -555,15 +564,6 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
}) })
} }
{ // setup analytics service
peer.Analytics.Service = analytics.NewService(peer.Log.Named("analytics:service"), config.Analytics, config.Console.SatelliteName)
peer.Services.Add(lifecycle.Item{
Name: "analytics:service",
Close: peer.Analytics.Service.Close,
})
}
{ // setup console { // setup console
consoleConfig := config.Console consoleConfig := config.Console
peer.Console.Listener, err = net.Listen("tcp", consoleConfig.Address) peer.Console.Listener, err = net.Listen("tcp", consoleConfig.Address)
@ -583,6 +583,7 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
peer.DB.Buckets(), peer.DB.Buckets(),
peer.Marketing.PartnersService, peer.Marketing.PartnersService,
peer.Payments.Accounts, peer.Payments.Accounts,
peer.Analytics.Service,
consoleConfig.Config, consoleConfig.Config,
config.Payments.MinCoinPayment, config.Payments.MinCoinPayment,
) )

View File

@ -22,6 +22,7 @@ import (
"storj.io/storj/satellite" "storj.io/storj/satellite"
"storj.io/storj/satellite/accounting" "storj.io/storj/satellite/accounting"
"storj.io/storj/satellite/accounting/live" "storj.io/storj/satellite/accounting/live"
"storj.io/storj/satellite/analytics"
"storj.io/storj/satellite/console" "storj.io/storj/satellite/console"
"storj.io/storj/satellite/console/consoleauth" "storj.io/storj/satellite/console/consoleauth"
"storj.io/storj/satellite/console/consoleweb/consoleql" "storj.io/storj/satellite/console/consoleweb/consoleql"
@ -59,6 +60,8 @@ func TestGraphqlMutation(t *testing.T) {
}, },
) )
analyticsService := analytics.NewService(log, analytics.Config{}, "test-satellite")
redis, err := testredis.Mini(ctx) redis, err := testredis.Mini(ctx)
require.NoError(t, err) require.NoError(t, err)
defer ctx.Check(redis.Close) defer ctx.Check(redis.Close)
@ -108,6 +111,7 @@ func TestGraphqlMutation(t *testing.T) {
db.Buckets(), db.Buckets(),
partnersService, partnersService,
paymentsService.Accounts(), paymentsService.Accounts(),
analyticsService,
console.Config{PasswordCost: console.TestPasswordCost, DefaultProjectLimit: 5}, console.Config{PasswordCost: console.TestPasswordCost, DefaultProjectLimit: 5},
5000, 5000,
) )

View File

@ -19,6 +19,7 @@ import (
"storj.io/storj/satellite" "storj.io/storj/satellite"
"storj.io/storj/satellite/accounting" "storj.io/storj/satellite/accounting"
"storj.io/storj/satellite/accounting/live" "storj.io/storj/satellite/accounting/live"
"storj.io/storj/satellite/analytics"
"storj.io/storj/satellite/console" "storj.io/storj/satellite/console"
"storj.io/storj/satellite/console/consoleauth" "storj.io/storj/satellite/console/consoleauth"
"storj.io/storj/satellite/console/consoleweb/consoleql" "storj.io/storj/satellite/console/consoleweb/consoleql"
@ -43,6 +44,8 @@ func TestGraphqlQuery(t *testing.T) {
}, },
) )
analyticsService := analytics.NewService(log, analytics.Config{}, "test-satellite")
redis, err := testredis.Mini(ctx) redis, err := testredis.Mini(ctx)
require.NoError(t, err) require.NoError(t, err)
defer ctx.Check(redis.Close) defer ctx.Check(redis.Close)
@ -92,6 +95,7 @@ func TestGraphqlQuery(t *testing.T) {
db.Buckets(), db.Buckets(),
partnersService, partnersService,
paymentsService.Accounts(), paymentsService.Accounts(),
analyticsService,
console.Config{PasswordCost: console.TestPasswordCost, DefaultProjectLimit: 5}, console.Config{PasswordCost: console.TestPasswordCost, DefaultProjectLimit: 5},
5000, 5000,
) )

View File

@ -24,6 +24,7 @@ import (
"storj.io/common/storj" "storj.io/common/storj"
"storj.io/common/uuid" "storj.io/common/uuid"
"storj.io/storj/satellite/accounting" "storj.io/storj/satellite/accounting"
"storj.io/storj/satellite/analytics"
"storj.io/storj/satellite/console/consoleauth" "storj.io/storj/satellite/console/consoleauth"
"storj.io/storj/satellite/payments" "storj.io/storj/satellite/payments"
"storj.io/storj/satellite/rewards" "storj.io/storj/satellite/rewards"
@ -93,6 +94,7 @@ type Service struct {
buckets Buckets buckets Buckets
partners *rewards.PartnersService partners *rewards.PartnersService
accounts payments.Accounts accounts payments.Accounts
analytics *analytics.Service
config Config config Config
@ -113,7 +115,7 @@ type PaymentsService struct {
} }
// NewService returns new instance of Service. // NewService returns new instance of Service.
func NewService(log *zap.Logger, signer Signer, store DB, projectAccounting accounting.ProjectAccounting, projectUsage *accounting.Service, buckets Buckets, partners *rewards.PartnersService, accounts payments.Accounts, config Config, minCoinPayment int64) (*Service, error) { func NewService(log *zap.Logger, signer Signer, store DB, projectAccounting accounting.ProjectAccounting, projectUsage *accounting.Service, buckets Buckets, partners *rewards.PartnersService, accounts payments.Accounts, analytics *analytics.Service, config Config, minCoinPayment int64) (*Service, error) {
if signer == nil { if signer == nil {
return nil, errs.New("signer can't be nil") return nil, errs.New("signer can't be nil")
} }
@ -137,6 +139,7 @@ func NewService(log *zap.Logger, signer Signer, store DB, projectAccounting acco
buckets: buckets, buckets: buckets,
partners: partners, partners: partners,
accounts: accounts, accounts: accounts,
analytics: analytics,
config: config, config: config,
minCoinPayment: minCoinPayment, minCoinPayment: minCoinPayment,
}, nil }, nil
@ -431,6 +434,7 @@ func (paymentService PaymentsService) TokenDeposit(ctx context.Context, amount i
} }
tx, err := paymentService.service.accounts.StorjTokens().Deposit(ctx, auth.User.ID, amount) tx, err := paymentService.service.accounts.StorjTokens().Deposit(ctx, auth.User.ID, amount)
return tx, Error.Wrap(err) return tx, Error.Wrap(err)
} }
@ -780,6 +784,8 @@ func (s *Service) Token(ctx context.Context, email, password string) (token stri
} }
s.auditLog(ctx, "login", &user.ID, user.Email) s.auditLog(ctx, "login", &user.ID, user.Email)
s.analytics.TrackSignedIn(user.ID, user.Email)
return token, nil return token, nil
} }
@ -990,7 +996,7 @@ func (s *Service) CreateProject(ctx context.Context, projectInfo ProjectInfo) (p
return nil, Error.Wrap(err) return nil, Error.Wrap(err)
} }
err = s.checkProjectLimit(ctx, auth.User.ID) currentProjectCount, err := s.checkProjectLimit(ctx, auth.User.ID)
if err != nil { if err != nil {
return nil, ErrProjLimit.Wrap(err) return nil, ErrProjLimit.Wrap(err)
} }
@ -1021,6 +1027,7 @@ func (s *Service) CreateProject(ctx context.Context, projectInfo ProjectInfo) (p
} }
} }
var projectID uuid.UUID
err = s.store.WithTx(ctx, func(ctx context.Context, tx DBTx) error { err = s.store.WithTx(ctx, func(ctx context.Context, tx DBTx) error {
p, err = tx.Projects().Insert(ctx, p, err = tx.Projects().Insert(ctx,
&Project{ &Project{
@ -1041,12 +1048,17 @@ func (s *Service) CreateProject(ctx context.Context, projectInfo ProjectInfo) (p
return Error.Wrap(err) return Error.Wrap(err)
} }
projectID = p.ID
return nil return nil
}) })
if err != nil { if err != nil {
return nil, Error.Wrap(err) return nil, Error.Wrap(err)
} }
s.analytics.TrackProjectCreated(auth.User.ID, projectID, currentProjectCount+1)
// ToDo: check if this is actually the right place. // ToDo: check if this is actually the right place.
err = s.accounts.Coupons().AddPromotionalCoupon(ctx, auth.User.ID) err = s.accounts.Coupons().AddPromotionalCoupon(ctx, auth.User.ID)
if err != nil { if err != nil {
@ -1276,6 +1288,8 @@ func (s *Service) CreateAPIKey(ctx context.Context, projectID uuid.UUID, name st
return nil, nil, Error.Wrap(err) return nil, nil, Error.Wrap(err)
} }
s.analytics.TrackAccessGrantCreated(auth.User.ID)
return info, key, nil return info, key, nil
} }
@ -1598,12 +1612,12 @@ func (s *Service) checkProjectCanBeDeleted(ctx context.Context, project uuid.UUI
} }
// checkProjectLimit is used to check if user is able to create a new project. // checkProjectLimit is used to check if user is able to create a new project.
func (s *Service) checkProjectLimit(ctx context.Context, userID uuid.UUID) (err error) { func (s *Service) checkProjectLimit(ctx context.Context, userID uuid.UUID) (currentProjects int, err error) {
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)
limit, err := s.store.Users().GetProjectLimit(ctx, userID) limit, err := s.store.Users().GetProjectLimit(ctx, userID)
if err != nil { if err != nil {
return Error.Wrap(err) return 0, Error.Wrap(err)
} }
if limit == 0 { if limit == 0 {
limit = s.config.DefaultProjectLimit limit = s.config.DefaultProjectLimit
@ -1611,14 +1625,14 @@ func (s *Service) checkProjectLimit(ctx context.Context, userID uuid.UUID) (err
projects, err := s.GetUsersProjects(ctx) projects, err := s.GetUsersProjects(ctx)
if err != nil { if err != nil {
return Error.Wrap(err) return 0, Error.Wrap(err)
} }
if len(projects) >= limit { if len(projects) >= limit {
return ErrProjLimit.New(projLimitErrMsg) return 0, ErrProjLimit.New(projLimitErrMsg)
} }
return nil return len(projects), nil
} }
// CreateRegToken creates new registration token. Needed for testing. // CreateRegToken creates new registration token. Needed for testing.