Skip to content

Commit

Permalink
feat(api): posthog telemetry (#374)
Browse files Browse the repository at this point in the history
* feat: add posthog dep

* feat: posthog analytics

* feat: user events

* fix: nil tenant

* feat: tenant ident

* chore: linting

* Update pkg/analytics/posthog/posthog.go

Co-authored-by: abelanger5 <[email protected]>

* fix: typo

---------

Co-authored-by: abelanger5 <[email protected]>
  • Loading branch information
grutt and abelanger5 authored Apr 12, 2024
1 parent f16a9cd commit ca68eee
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 2 deletions.
12 changes: 12 additions & 0 deletions api/v1/server/handlers/tenants/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ func (t *TenantService) TenantCreate(ctx echo.Context, request gen.TenantCreateR
return nil, err
}

t.config.Analytics.Tenant(tenant.ID, map[string]interface{}{
"name": tenant.Name,
"slug": tenant.Slug,
})

t.config.Analytics.Enqueue(
"tenant:create",
user.ID,
&tenant.ID,
nil,
)

return gen.TenantCreate200JSONResponse(
*transformers.ToTenant(tenant),
), nil
Expand Down
6 changes: 6 additions & 0 deletions api/v1/server/handlers/tenants/create_invite.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ func (t *TenantService) TenantInviteCreate(ctx echo.Context, request gen.TenantI
return nil, err
}

t.config.Analytics.Enqueue("user-invite:create",
user.ID,
&invite.TenantID,
nil,
)

return gen.TenantInviteCreate201JSONResponse(
*transformers.ToTenantInviteLink(invite),
), nil
Expand Down
7 changes: 7 additions & 0 deletions api/v1/server/handlers/users/accept_invite.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,12 @@ func (u *UserService) TenantInviteAccept(ctx echo.Context, request gen.TenantInv
return nil, err
}

u.config.Analytics.Enqueue(
"user-invite:reject",
user.ID,
&invite.TenantID,
nil,
)

return nil, nil
}
10 changes: 10 additions & 0 deletions api/v1/server/handlers/users/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ func (u *UserService) UserCreate(ctx echo.Context, request gen.UserCreateRequest
return nil, err
}

u.config.Analytics.Enqueue(
"user:create",
user.ID,
nil,
map[string]interface{}{
"email": request.Body.Email,
"name": request.Body.Name,
},
)

return gen.UserCreate200JSONResponse(
*transformers.ToUser(user, false),
), nil
Expand Down
14 changes: 13 additions & 1 deletion api/v1/server/handlers/users/get_current.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,19 @@ func (u *UserService) UserGetCurrent(ctx echo.Context, request gen.UserGetCurren
hasPass = true
}

transformedUser := transformers.ToUser(user, hasPass)

u.config.Analytics.Enqueue(
"user:current",
user.ID,
nil,
map[string]interface{}{
"email": user.Email,
"name": transformedUser.Name,
},
)

return gen.UserGetCurrent200JSONResponse(
*transformers.ToUser(user, hasPass),
*transformedUser,
), nil
}
7 changes: 7 additions & 0 deletions api/v1/server/handlers/users/reject_invite.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,12 @@ func (u *UserService) TenantInviteReject(ctx echo.Context, request gen.TenantInv
return nil, err
}

u.config.Analytics.Enqueue(
"user-invite:accept",
user.ID,
&invite.TenantID,
nil,
)

return nil, nil
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/posthog/posthog-go v0.0.0-20240327112532-87b23fe11103 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posthog/posthog-go v0.0.0-20240327112532-87b23fe11103 h1:YEWdfKVtz5Db85b8RLIZ1IY3PLSB1fW49hvK2yIL6JU=
github.com/posthog/posthog-go v0.0.0-20240327112532-87b23fe11103/go.mod h1:QjlpryJtfYLrZF2GUkAhejH4E7WlDbdKkvOi5hLmkdg=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo=
github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
Expand Down
18 changes: 18 additions & 0 deletions internal/config/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import (
"github.com/hatchet-dev/hatchet/internal/repository/prisma/db"
"github.com/hatchet-dev/hatchet/internal/services/ingestor"
"github.com/hatchet-dev/hatchet/internal/validator"
"github.com/hatchet-dev/hatchet/pkg/analytics"
"github.com/hatchet-dev/hatchet/pkg/analytics/posthog"
"github.com/hatchet-dev/hatchet/pkg/client"
"github.com/hatchet-dev/hatchet/pkg/errors"
"github.com/hatchet-dev/hatchet/pkg/errors/sentry"
Expand Down Expand Up @@ -216,6 +218,21 @@ func GetServerConfigFromConfigfile(dc *database.Config, cf *server.ServerConfigF
alerter = errors.NoOpAlerter{}
}

var analyticsEmitter analytics.Analytics

if cf.Analytics.Posthog.Enabled {
analyticsEmitter, err = posthog.NewPosthogAnalytics(&posthog.PosthogAnalyticsOpts{
ApiKey: cf.Analytics.Posthog.ApiKey,
Endpoint: cf.Analytics.Posthog.Endpoint,
})

if err != nil {
return nil, nil, fmt.Errorf("could not create posthog analytics: %w", err)
}
} else {
analyticsEmitter = analytics.NoOpAnalytics{} // TODO
}

auth := server.AuthConfig{
ConfigFile: cf.Auth,
}
Expand Down Expand Up @@ -347,6 +364,7 @@ func GetServerConfigFromConfigfile(dc *database.Config, cf *server.ServerConfigF

return cleanup, &server.ServerConfig{
Alerter: alerter,
Analytics: analyticsEmitter,
Runtime: cf.Runtime,
Auth: auth,
Encryption: encryptionSvc,
Expand Down
25 changes: 25 additions & 0 deletions internal/config/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/hatchet-dev/hatchet/internal/msgqueue"
"github.com/hatchet-dev/hatchet/internal/services/ingestor"
"github.com/hatchet-dev/hatchet/internal/validator"
"github.com/hatchet-dev/hatchet/pkg/analytics"
"github.com/hatchet-dev/hatchet/pkg/client"
"github.com/hatchet-dev/hatchet/pkg/errors"
)
Expand All @@ -26,6 +27,8 @@ type ServerConfigFile struct {

Alerting AlertingConfigFile `mapstructure:"alerting" json:"alerting,omitempty"`

Analytics AnalyticsConfigFile `mapstructure:"analytics" json:"analytics,omitempty"`

Encryption EncryptionConfigFile `mapstructure:"encryption" json:"encryption,omitempty"`

Runtime ConfigFileRuntime `mapstructure:"runtime" json:"runtime,omitempty"`
Expand Down Expand Up @@ -86,6 +89,21 @@ type SentryConfigFile struct {
Environment string `mapstructure:"environment" json:"environment,omitempty" default:"development"`
}

type AnalyticsConfigFile struct {
Posthog PosthogConfigFile `mapstructure:"posthog" json:"posthog,omitempty"`
}

type PosthogConfigFile struct {
// Enabled controls whether the Posthog service is enabled for this Hatchet instance.
Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"`

// APIKey is the API key for the Posthog instance
ApiKey string `mapstructure:"apiKey" json:"apiKey,omitempty"`

// Endpoint is the endpoint for the Posthog instance
Endpoint string `mapstructure:"endpoint" json:"endpoint,omitempty"`
}

// Encryption options
type EncryptionConfigFile struct {
// MasterKeyset is the raw master keyset for the instance. This should be a base64-encoded JSON string. You must set
Expand Down Expand Up @@ -213,6 +231,8 @@ type ServerConfig struct {

Alerter errors.Alerter

Analytics analytics.Analytics

Encryption encryption.EncryptionService

Runtime ConfigFileRuntime
Expand Down Expand Up @@ -267,6 +287,11 @@ func BindAllEnv(v *viper.Viper) {
_ = v.BindEnv("alerting.sentry.dsn", "SERVER_ALERTING_SENTRY_DSN")
_ = v.BindEnv("alerting.sentry.environment", "SERVER_ALERTING_SENTRY_ENVIRONMENT")

// analytics options
_ = v.BindEnv("analytics.posthog.enabled", "SERVER_ANALYTICS_POSTHOG_ENABLED")
_ = v.BindEnv("analytics.posthog.apiKey", "SERVER_ANALYTICS_POSTHOG_API_KEY")
_ = v.BindEnv("analytics.posthog.endpoint", "SERVER_ANALYTICS_POSTHOG_ENDPOINT")

// encryption options
_ = v.BindEnv("encryption.masterKeyset", "SERVER_ENCRYPTION_MASTER_KEYSET")
_ = v.BindEnv("encryption.masterKeysetFile", "SERVER_ENCRYPTION_MASTER_KEYSET_FILE")
Expand Down
13 changes: 12 additions & 1 deletion internal/services/grpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/hatchet-dev/hatchet/internal/services/grpc/middleware"
"github.com/hatchet-dev/hatchet/internal/services/ingestor"
eventcontracts "github.com/hatchet-dev/hatchet/internal/services/ingestor/contracts"
"github.com/hatchet-dev/hatchet/pkg/analytics"
"github.com/hatchet-dev/hatchet/pkg/errors"
)

Expand All @@ -38,6 +39,7 @@ type Server struct {

l *zerolog.Logger
a errors.Alerter
analytics analytics.Analytics
port int
bindAddress string

Expand All @@ -55,6 +57,7 @@ type ServerOpts struct {
config *server.ServerConfig
l *zerolog.Logger
a errors.Alerter
analytics analytics.Analytics
port int
bindAddress string
ingestor ingestor.Ingestor
Expand All @@ -67,10 +70,11 @@ type ServerOpts struct {
func defaultServerOpts() *ServerOpts {
logger := logger.NewDefaultLogger("grpc")
a := errors.NoOpAlerter{}

analytics := analytics.NoOpAnalytics{}
return &ServerOpts{
l: &logger,
a: a,
analytics: analytics,
port: 7070,
bindAddress: "127.0.0.1",
insecure: false,
Expand All @@ -89,6 +93,12 @@ func WithAlerter(a errors.Alerter) ServerOpt {
}
}

func WithAnalytics(a analytics.Analytics) ServerOpt {
return func(opts *ServerOpts) {
opts.analytics = a
}
}

func WithBindAddress(bindAddress string) ServerOpt {
return func(opts *ServerOpts) {
opts.bindAddress = bindAddress
Expand Down Expand Up @@ -158,6 +168,7 @@ func NewServer(fs ...ServerOpt) (*Server, error) {
return &Server{
l: opts.l,
a: opts.a,
analytics: opts.analytics,
config: opts.config,
port: opts.port,
bindAddress: opts.bindAddress,
Expand Down
12 changes: 12 additions & 0 deletions pkg/analytics/analytics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package analytics

type Analytics interface {
Enqueue(event string, userId string, tenantId *string, data map[string]interface{})
Tenant(tenantId string, data map[string]interface{})
}

type NoOpAnalytics struct{}

func (a NoOpAnalytics) Enqueue(event string, userId string, tenantId *string, data map[string]interface{}) {
}
func (a NoOpAnalytics) Tenant(tenantId string, data map[string]interface{}) {}
65 changes: 65 additions & 0 deletions pkg/analytics/posthog/posthog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package posthog

import (
"fmt"

"github.com/posthog/posthog-go"
)

type PosthogAnalytics struct {
client *posthog.Client
}

type PosthogAnalyticsOpts struct {
ApiKey string
Endpoint string
}

func NewPosthogAnalytics(opts *PosthogAnalyticsOpts) (*PosthogAnalytics, error) {
if opts.ApiKey == "" || opts.Endpoint == "" {
return nil, fmt.Errorf("api key and endpoint are required if posthog is enabled")
}

phClient, err := posthog.NewWithConfig(
opts.ApiKey,
posthog.Config{
Endpoint: opts.Endpoint,
},
)

if err != nil {
return nil, fmt.Errorf("failed to create posthog client: %w", err)
}

return &PosthogAnalytics{
client: &phClient,
}, nil
}

func (p *PosthogAnalytics) Enqueue(event string, userId string, tenantId *string, data map[string]interface{}) {

var group posthog.Groups

if tenantId != nil {
group = posthog.NewGroups().Set("tenant", *tenantId)
}

var _ = (*p.client).Enqueue(posthog.Capture{
DistinctId: userId,
Event: event,
Properties: map[string]interface{}{
"$set": data,
},
Groups: group,
})
}

func (p *PosthogAnalytics) Tenant(tenantId string, data map[string]interface{}) {
var _ = (*p.client).Enqueue(posthog.GroupIdentify{
Type: "tenant",
Key: tenantId,
Properties: map[string]interface{}{
"$set": data,
},
})
}

0 comments on commit ca68eee

Please sign in to comment.