Skip to content

Commit fed5f2f

Browse files
Merge pull request #24 from optimizely/eventProcessor
Simple Event processor, queue (can replace), event dispatcher, ImpressionEvent factory, sending events in unit tests
2 parents 1de7815 + fb1e1f1 commit fed5f2f

File tree

15 files changed

+846
-0
lines changed

15 files changed

+846
-0
lines changed

examples/main.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"fmt"
55

66
"github.com/optimizely/go-sdk/optimizely/client"
7+
"github.com/optimizely/go-sdk/optimizely/event"
8+
"time"
79
)
810

911
func main() {
@@ -12,4 +14,17 @@ func main() {
1214
}
1315
client := optimizelyFactory.Client()
1416
fmt.Printf("Is feature enabled? %v", client.IsFeatureEnabled("go_sdk", "mike", nil))
17+
18+
processor := event.NewEventProcessor(100, 100)
19+
20+
impression := event.UserEvent{}
21+
22+
processor.ProcessImpression(impression)
23+
24+
_, ok := processor.(*event.QueueingEventProcessor)
25+
26+
if ok {
27+
time.Sleep(1000 * time.Millisecond)
28+
fmt.Println("\nending")
29+
}
1530
}

optimizely/config/datafileProjectConfig/config.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,38 @@ type DatafileProjectConfig struct {
3333
experimentMap map[string]entities.Experiment
3434
experimentKeyToIDMap map[string]string
3535
featureMap map[string]entities.Feature
36+
attributeKeyToIDMap map[string]string
37+
eventMap map[string]entities.Event
38+
projectID string
39+
revision string
40+
accountID string
41+
anonymizeIP bool
42+
botFiltering bool
43+
44+
}
45+
46+
func (config DatafileProjectConfig) GetProjectID() string {
47+
return config.projectID
48+
}
49+
50+
func (config DatafileProjectConfig) GetRevision() string {
51+
return config.revision
52+
}
53+
54+
func (config DatafileProjectConfig) GetAccountID() string {
55+
return config.accountID
56+
}
57+
58+
func (config DatafileProjectConfig) GetAnonymizeIP() bool {
59+
return config.anonymizeIP
60+
}
61+
62+
func (config DatafileProjectConfig) GetAttributeID(key string) string {
63+
return config.attributeKeyToIDMap[key]
64+
}
65+
66+
func (config DatafileProjectConfig) GetBotFiltering() bool {
67+
return config.botFiltering
3668
}
3769

3870
// NewDatafileProjectConfig initializes a new datafile from a json byte array using the default JSON datafile parser
@@ -53,6 +85,16 @@ func NewDatafileProjectConfig(jsonDatafile []byte) *DatafileProjectConfig {
5385
return config
5486
}
5587

88+
// GetEventByKey returns the event with the given key
89+
func (config DatafileProjectConfig) GetEventByKey(eventKey string) (entities.Event, error) {
90+
if event, ok := config.eventMap[eventKey]; ok {
91+
return event, nil
92+
}
93+
94+
errMessage := fmt.Sprintf("Event with key %s not found", eventKey)
95+
return entities.Event{}, errors.New(errMessage)
96+
}
97+
5698
// GetFeatureByKey returns the feature with the given key
5799
func (config DatafileProjectConfig) GetFeatureByKey(featureKey string) (entities.Feature, error) {
58100
if feature, ok := config.featureMap[featureKey]; ok {

optimizely/config/datafileProjectConfig/entities/entities.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type Experiment struct {
2828
// @TODO(mng): include audienceConditions
2929
ID string `json:"id"`
3030
Key string `json:"key"`
31+
LayerID string `json:"layerId"`
3132
Status string `json:"status"`
3233
Variations []Variation `json:"variations"`
3334
TrafficAllocation []trafficAllocation `json:"trafficAllocation"`
@@ -58,6 +59,12 @@ type Variation struct {
5859
FeatureEnabled bool `json:"featureEnabled"`
5960
}
6061

62+
type Event struct {
63+
ID string `json:"id"`
64+
Key string `json:"key"`
65+
ExperimentIds []string `json:"experimentIds"`
66+
}
67+
6168
// Datafile represents the datafile we get from Optimizely
6269
type Datafile struct {
6370
AccountID string `json:"accountId"`
@@ -66,6 +73,7 @@ type Datafile struct {
6673
BotFiltering bool `json:"botFiltering"`
6774
Experiments []Experiment `json:"experiments"`
6875
FeatureFlags []FeatureFlag `json:"featureFlags"`
76+
Events []Event `json:"events"`
6977
ProjectID string `json:"projectId"`
7078
Revision string `json:"revision"`
7179
Variables []string `json:"variables"`

optimizely/config/interface.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ import (
2323
// ProjectConfig contains the parsed project entities
2424
type ProjectConfig interface {
2525
GetFeatureByKey(string) (entities.Feature, error)
26+
GetProjectID() string
27+
GetRevision() string
28+
GetAccountID() string
29+
GetAnonymizeIP() bool
30+
GetAttributeID(key string) string // returns "" if there is no id
31+
GetBotFiltering() bool
32+
GetEventByKey(string) (entities.Event, error)
2633
}
2734

2835
// ProjectConfigManager manages the config

optimizely/entities/event.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package entities
2+
3+
type Event struct {
4+
ID string `json:"id"`
5+
Key string `json:"key"`
6+
ExperimentIds []string `json:"experimentIds"`
7+
}
8+

optimizely/entities/experiment.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type Variation struct {
2828
type Experiment struct {
2929
AudienceIds []string
3030
ID string
31+
LayerID string
3132
Key string
3233
Variations map[string]Variation
3334
TrafficAllocation []Range

optimizely/entities/user_context.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type UserAttributes struct {
3535
}
3636

3737
var floatType = reflect.TypeOf(float64(0))
38+
var intType = reflect.TypeOf(int64(0))
3839

3940
// GetString returns the string value for the specified attribute name in the attributes map. Returns error if not found.
4041
func (u UserAttributes) GetString(attrName string) (string, error) {
@@ -73,6 +74,18 @@ func (u UserAttributes) GetFloat(attrName string) (float64, error) {
7374
return 0, fmt.Errorf(`No float attribute named "%s"`, attrName)
7475
}
7576

77+
func (u UserAttributes) GetInt(attrName string) (int64, error) {
78+
if value, ok := u.Attributes[attrName]; ok {
79+
v := reflect.ValueOf(value)
80+
if v.Type().String() == "int64" || v.Type().ConvertibleTo(intType) {
81+
intValue := v.Convert(intType).Int()
82+
return intValue, nil
83+
}
84+
}
85+
86+
return 0, fmt.Errorf(`No int attribute named "%s"`, attrName)
87+
}
88+
7689
// GetBucketingID returns the bucketing ID to use for the given user
7790
func (u UserContext) GetBucketingID() (string, error) {
7891
// by default
@@ -90,3 +103,4 @@ func (u UserContext) GetBucketingID() (string, error) {
90103

91104
return bucketingID, nil
92105
}
106+

optimizely/event/dispatcher.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package event
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
)
9+
10+
type Dispatcher interface {
11+
DispatchEvent(event LogEvent, callback func(success bool))
12+
}
13+
14+
type HttpEventDispatcher struct {
15+
}
16+
17+
func (*HttpEventDispatcher) DispatchEvent(event LogEvent, callback func(success bool)) {
18+
// add to current batch or create new batch
19+
// does a batch have to contain a decision or can it just be impressions?
20+
21+
jsonValue, _ := json.Marshal(event.event)
22+
resp, err := http.Post( event.endPoint, "application/json", bytes.NewBuffer(jsonValue))
23+
fmt.Println(resp)
24+
fmt.Println(string(jsonValue))
25+
// also check response codes
26+
// resp.StatusCode == 400 is an error
27+
success := true
28+
29+
if err != nil {
30+
fmt.Println(err)
31+
success = false
32+
} else {
33+
if resp.StatusCode == 204 {
34+
success = true
35+
} else {
36+
fmt.Printf("invalid response %d", resp.StatusCode)
37+
success = false
38+
}
39+
}
40+
callback(success)
41+
42+
}
43+

optimizely/event/events.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package event
2+
3+
type EventContext struct {
4+
Revision string `json:"revision"`
5+
AccountID string `json:"account_id"`
6+
ClientVersion string `json:"client_version"`
7+
ProjectID string `json:"project_id"`
8+
ClientName string `json:"client_name"`
9+
AnonymizeIP bool `json:"anonymize_ip"`
10+
BotFiltering bool `json:"bot_filtering"`
11+
AttributeKeyToIdMap map[string]string `json:"attributeKeyToIdMap"`
12+
}
13+
14+
type UserEvent struct {
15+
Timestamp int64 `json:"timestamp"`
16+
Uuid string `json:"uuid"`
17+
EventContext EventContext
18+
VisitorID string
19+
Impression *ImpressionEvent
20+
Conversion *ConversionEvent
21+
}
22+
23+
type ImpressionEvent struct {
24+
EntityID string `json:"entity_id"`
25+
Key string `json:"key"`
26+
Attributes []VisitorAttribute
27+
VariationID string `json:"variation_id"`
28+
CampaignID string `json:"campaign_id"`
29+
ExperimentID string `json:"experiment_id"`
30+
}
31+
32+
type ConversionEvent struct {
33+
EntityID string `json:"entity_id"`
34+
Key string `json:"key"`
35+
Attributes []VisitorAttribute
36+
Tags map[string]interface{} `json:"tags,omitempty"`
37+
// these need to be pointers because 0 is a valid Revenue or Value.
38+
// 0 is equivalent to omitempty for json marshalling.
39+
Revenue *int64 `json:"revenue,omitempty"`
40+
Value *float64 `json:"value,omitempty"`
41+
}
42+
43+
type LogEvent struct {
44+
endPoint string
45+
event EventBatch
46+
}
47+
// Context about the event
48+
type EventBatch struct {
49+
Revision string `json:"revision"`
50+
AccountID string `json:"account_id"`
51+
ClientVersion string `json:"client_version"`
52+
Visitors []Visitor `json:"visitors"`
53+
ProjectID string `json:"project_id"`
54+
ClientName string `json:"client_name"`
55+
AnonymizeIP bool `json:"anonymize_ip"`
56+
EnrichDecisions bool `json:"enrich_decisions"`
57+
}
58+
59+
type Visitor struct {
60+
Attributes []VisitorAttribute `json:"attributes"`
61+
Snapshots []Snapshot `json:"snapshots"`
62+
VisitorID string `json:"visitor_id"`
63+
}
64+
65+
type VisitorAttribute struct {
66+
Value interface{} `json:"value"`
67+
Key string `json:"key"`
68+
AttributeType string `json:"type"`
69+
EntityID string `json:"entity_id"`
70+
}
71+
72+
type Snapshot struct {
73+
Decisions []Decision `json:"decisions"`
74+
Events []SnapshotEvent `json:"events"`
75+
}
76+
77+
type Decision struct {
78+
VariationID string `json:"variation_id"`
79+
CampaignID string `json:"campaign_id"`
80+
ExperimentID string `json:"experiment_id"`
81+
}
82+
83+
type SnapshotEvent struct {
84+
EntityID string `json:"entity_id"`
85+
Key string `json:"key"`
86+
Timestamp int64 `json:"timestamp"`
87+
Uuid string `json:"uuid"`
88+
Tags map[string]interface{} `json:"tags,omitempty"`
89+
Revenue *int64 `json:"revenue,omitempty"`
90+
Value *float64 `json:"value,omitempty"`
91+
}

0 commit comments

Comments
 (0)