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:
parent
a264a4422b
commit
16c98e1ecd
@ -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) {
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user