mirror of
https://github.com/hatchet-dev/hatchet.git
synced 2025-12-30 13:19:44 -06:00
feat(api): posthog telemetry (#374)
* 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 <belanger@sas.upenn.edu> * fix: typo --------- Co-authored-by: abelanger5 <belanger@sas.upenn.edu>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
go.mod
1
go.mod
@@ -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
|
||||
|
||||
2
go.sum
2
go.sum
@@ -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=
|
||||
|
||||
@@ -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"
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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"`
|
||||
@@ -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
|
||||
@@ -213,6 +231,8 @@ type ServerConfig struct {
|
||||
|
||||
Alerter errors.Alerter
|
||||
|
||||
Analytics analytics.Analytics
|
||||
|
||||
Encryption encryption.EncryptionService
|
||||
|
||||
Runtime ConfigFileRuntime
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -38,6 +39,7 @@ type Server struct {
|
||||
|
||||
l *zerolog.Logger
|
||||
a errors.Alerter
|
||||
analytics analytics.Analytics
|
||||
port int
|
||||
bindAddress string
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
12
pkg/analytics/analytics.go
Normal file
12
pkg/analytics/analytics.go
Normal file
@@ -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
pkg/analytics/posthog/posthog.go
Normal file
65
pkg/analytics/posthog/posthog.go
Normal file
@@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user