diff --git a/alias.go b/alias.go index 7e1c20f..db1200f 100644 --- a/alias.go +++ b/alias.go @@ -13,6 +13,17 @@ type Alias struct { Alias string DistinctId string Timestamp time.Time + properties Properties +} + +func (msg Alias) SetProperty(name string, value interface{}) Properties { + if msg.properties == nil { + msg.properties = Properties{} + } + + msg.properties.Set(name, value) + + return msg.properties } func (msg Alias) internal() { @@ -40,10 +51,11 @@ func (msg Alias) Validate() error { } type AliasInApiProperties struct { - DistinctId string `json:"distinct_id"` - Alias string `json:"alias"` - Lib string `json:"$lib"` - LibVersion string `json:"$lib_version"` + DistinctId string `json:"distinct_id"` + Alias string `json:"alias"` + Lib string `json:"$lib"` + LibVersion string `json:"$lib_version"` + GeoIPDisable bool `json:"$geoip_disable,omitempty"` } type AliasInApi struct { @@ -61,18 +73,27 @@ func (msg Alias) APIfy() APIMessage { library := "posthog-go" libraryVersion := getVersion() + properties := AliasInApiProperties{ + DistinctId: msg.DistinctId, + Alias: msg.Alias, + Lib: library, + LibVersion: libraryVersion, + } + + if msg.properties != nil { + geoIPDisable, exist := msg.properties.Get(GeoIPDisableKey) + if exist { + properties.GeoIPDisable = geoIPDisable.(bool) + } + } + apified := AliasInApi{ Type: msg.Type, Event: "$create_alias", Library: library, LibraryVersion: libraryVersion, Timestamp: msg.Timestamp, - Properties: AliasInApiProperties{ - DistinctId: msg.DistinctId, - Alias: msg.Alias, - Lib: library, - LibVersion: libraryVersion, - }, + Properties: properties, } return apified diff --git a/capture.go b/capture.go index b54acde..6b2ef1f 100644 --- a/capture.go +++ b/capture.go @@ -10,6 +10,7 @@ type Capture struct { // the application, its value is always overwritten by the library. Type string + Uuid string DistinctId string Event string Timestamp time.Time @@ -18,6 +19,16 @@ type Capture struct { SendFeatureFlags bool } +func (msg Capture) SetProperty(name string, value interface{}) Properties { + if msg.Properties == nil { + msg.Properties = Properties{} + } + + msg.Properties.Set(name, value) + + return msg.Properties +} + func (msg Capture) internal() { panic(unimplementedError) } @@ -48,6 +59,7 @@ type CaptureInApi struct { LibraryVersion string `json:"library_version"` Timestamp time.Time `json:"timestamp"` + Uuid string `json:"uuid"` DistinctId string `json:"distinct_id"` Event string `json:"event"` Properties Properties `json:"properties"` @@ -71,6 +83,7 @@ func (msg Capture) APIfy() APIMessage { } apified := CaptureInApi{ + Uuid: msg.Uuid, Type: msg.Type, Library: library, LibraryVersion: libraryVersion, diff --git a/config.go b/config.go index 4624d69..ac0a65b 100644 --- a/config.go +++ b/config.go @@ -60,6 +60,10 @@ type Config struct { // which is independent from the number of embedded messages. BatchSize int + // This parameter allow you stop using the GeoIP plugin or feature, + // which enriches your event data with geo information based on IP addresses. + DisableGeoIP *bool + // When set to true the client will send more frequent and detailed messages // to its logger. Verbose bool @@ -108,6 +112,14 @@ const DefaultFeatureFlagRequestTimeout = 3 * time.Second // was explicitly set. const DefaultBatchSize = 250 +// This constant sets the default value for DisableGeoIP and it is true, because same behaviour in nodejs client. +const DefaultDisableGeoIP = true + +func defaultGeoIPDisable() *bool { + val := DefaultDisableGeoIP + return &val +} + // Verifies that fields that don't have zero-values are set to valid values, // returns an error describing the problem if a field was invalid. func (c *Config) validate() error { @@ -165,6 +177,10 @@ func makeConfig(c Config) Config { c.RetryAfter = DefaultBacko().Duration } + if c.DisableGeoIP == nil { + c.DisableGeoIP = defaultGeoIPDisable() + } + if c.uid == nil { c.uid = uid } diff --git a/config_test.go b/config_test.go index 7775431..363c3af 100644 --- a/config_test.go +++ b/config_test.go @@ -44,3 +44,38 @@ func TestConfigInvalidBatchSize(t *testing.T) { t.Error("invalid field error reported:", e) } } + +func TestConfigDisableGeoIP(t *testing.T) { + positiveVal := true + negativeVal := false + + c := Config{} + + if c.DisableGeoIP != nil { + t.Error("invalid value for DisableGeoIP:", c.DisableGeoIP) + } + + c = makeConfig(c) + + if *c.DisableGeoIP != positiveVal { + t.Error("invalid value for DisableGeoIP:", *c.DisableGeoIP) + } + + c = Config{ + DisableGeoIP: &positiveVal, + } + + c = makeConfig(c) + + if *c.DisableGeoIP != positiveVal { + t.Error("invalid value for DisableGeoIP:", *c.DisableGeoIP) + } + + c.DisableGeoIP = &negativeVal + + c = makeConfig(c) + + if *c.DisableGeoIP != negativeVal { + t.Error("invalid value for DisableGeoIP:", *c.DisableGeoIP) + } +} diff --git a/group_identify.go b/group_identify.go index 52cc210..0d8d61d 100644 --- a/group_identify.go +++ b/group_identify.go @@ -14,6 +14,16 @@ type GroupIdentify struct { Properties Properties } +func (msg GroupIdentify) SetProperty(name string, value interface{}) Properties { + if msg.Properties == nil { + msg.Properties = Properties{} + } + + msg.Properties.Set(name, value) + + return msg.Properties +} + func (msg GroupIdentify) internal() { panic(unimplementedError) } @@ -51,7 +61,11 @@ type GroupIdentifyInApi struct { func (msg GroupIdentify) APIfy() APIMessage { library := "posthog-go" - myProperties := Properties{}.Set("$lib", library).Set("$lib_version", getVersion()) + if msg.Properties == nil { + msg.Properties = Properties{} + } + + myProperties := msg.Properties.Set("$lib", library).Set("$lib_version", getVersion()) myProperties.Set("$group_type", msg.Type).Set("$group_key", msg.Key).Set("$group_set", msg.Properties) distinctId := fmt.Sprintf("$%s_%s", msg.Type, msg.Key) diff --git a/identify.go b/identify.go index 2a6399f..08fd9d3 100644 --- a/identify.go +++ b/identify.go @@ -15,6 +15,16 @@ type Identify struct { Properties Properties } +func (msg Identify) SetProperty(name string, value interface{}) Properties { + if msg.Properties == nil { + msg.Properties = Properties{} + } + + msg.Properties.Set(name, value) + + return msg.Properties +} + func (msg Identify) internal() { panic(unimplementedError) } @@ -46,7 +56,11 @@ type IdentifyInApi struct { func (msg Identify) APIfy() APIMessage { library := "posthog-go" - myProperties := Properties{}.Set("$lib", library).Set("$lib_version", getVersion()) + if msg.Properties == nil { + msg.Properties = Properties{} + } + + myProperties := msg.Properties.Set("$lib", library).Set("$lib_version", getVersion()) apified := IdentifyInApi{ Type: msg.Type, diff --git a/message.go b/message.go index 2edd4ed..b3115e6 100644 --- a/message.go +++ b/message.go @@ -37,6 +37,7 @@ type Message interface { // nil if the message is valid, or an error describing what went wrong. Validate() error APIfy() APIMessage + SetProperty(name string, value interface{}) Properties // internal is an unexposed interface function to ensure only types defined within this package can satisfy the Message interface. Invoking this method will panic. internal() diff --git a/posthog.go b/posthog.go index 8af24ea..b28e839 100644 --- a/posthog.go +++ b/posthog.go @@ -178,15 +178,30 @@ func (c *client) Enqueue(msg Message) (err error) { case Alias: m.Type = "alias" m.Timestamp = makeTimestamp(m.Timestamp, ts) + if _, ok := m.properties[GeoIPDisableKey]; !ok { + if c.Config.DisableGeoIP != nil { + m.properties = m.SetProperty(GeoIPDisableKey, *c.Config.DisableGeoIP) + } + } msg = m case Identify: m.Type = "identify" m.Timestamp = makeTimestamp(m.Timestamp, ts) + if _, ok := m.Properties[GeoIPDisableKey]; !ok { + if c.Config.DisableGeoIP != nil { + m.Properties = m.SetProperty(GeoIPDisableKey, *c.Config.DisableGeoIP) + } + } msg = m case GroupIdentify: m.Timestamp = makeTimestamp(m.Timestamp, ts) + if _, ok := m.Properties[GeoIPDisableKey]; !ok { + if c.Config.DisableGeoIP != nil { + m.Properties = m.SetProperty(GeoIPDisableKey, *c.Config.DisableGeoIP) + } + } msg = m case Capture: @@ -216,6 +231,11 @@ func (c *client) Enqueue(msg Message) (err error) { } m.Properties["$active_feature_flags"] = featureKeys } + if _, ok := m.Properties[GeoIPDisableKey]; !ok { + if c.Config.DisableGeoIP != nil { + m.Properties = m.SetProperty(GeoIPDisableKey, *c.Config.DisableGeoIP) + } + } msg = m default: diff --git a/posthog_test.go b/posthog_test.go index f0bf009..00d2e1e 100644 --- a/posthog_test.go +++ b/posthog_test.go @@ -17,6 +17,52 @@ import ( "time" ) +func TestClient_Enqueue(t *testing.T) { + c := New("") + + err := c.Enqueue(GroupIdentify{ + DistinctId: "1", + Type: "abc", + Key: "def", + }) + if err != nil { + t.Error("enqueue failed:", err) + } + + err = c.Enqueue(Identify{ + DistinctId: "1", + Type: "abc", + }) + if err != nil { + t.Error("enqueue failed:", err) + } + + err = c.Enqueue(Capture{ + DistinctId: "1", + Event: "abc", + }) + if err != nil { + t.Error("enqueue failed:", err) + } + + err = c.Enqueue(Capture{ + DistinctId: "1", + Event: "abc", + Properties: NewProperties().Set(GeoIPDisableKey, false), + }) + if err != nil { + t.Error("enqueue failed:", err) + } + + err = c.Enqueue(Alias{ + DistinctId: "1", + Alias: "abc", + }) + if err != nil { + t.Error("enqueue failed:", err) + } +} + // Helper type used to implement the io.Reader interface on function values. type readFunc func([]byte) (int, error) @@ -73,6 +119,8 @@ var _ Message = (*testErrorMessage)(nil) type testErrorMessage struct{} type testAPIErrorMessage struct{} +func (m testErrorMessage) SetProperty(name string, value interface{}) Properties { return Properties{} } + func (m testErrorMessage) internal() { } @@ -342,6 +390,8 @@ type customMessage struct { type customAPIMessage struct { } +func (c *customMessage) SetProperty(name string, value interface{}) Properties { return Properties{} } + func (c *customMessage) internal() { } diff --git a/properties.go b/properties.go index 49d7fbe..99387bc 100644 --- a/properties.go +++ b/properties.go @@ -1,5 +1,7 @@ package posthog +const GeoIPDisableKey = "$geoip_disable" + // This type is used to represent properties in messages that support it. // It is a free-form object so the application can set any value it sees fit but // a few helper method are defined to make it easier to instantiate properties with @@ -22,3 +24,8 @@ func (p Properties) Set(name string, value interface{}) Properties { p[name] = value return p } + +func (p Properties) Get(name string) (interface{}, bool) { + value, ok := p[name] + return value, ok +}