Skip to content

Commit 213efab

Browse files
added revenue and value support, changed to UserContext for creating conversion and impression user events.
1 parent 7227d9e commit 213efab

File tree

7 files changed

+82
-29
lines changed

7 files changed

+82
-29
lines changed

examples/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func main() {
2121

2222
processor.ProcessImpression(impression)
2323

24-
_, ok := processor.(*event.InMemQueueEventProcessor)
24+
_, ok := processor.(*event.QueueingEventProcessor)
2525

2626
if ok {
2727
time.Sleep(1000 * time.Millisecond)

optimizely/entities/user_context.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type UserAttributes struct {
3333
}
3434

3535
var floatType = reflect.TypeOf(float64(0))
36+
var intType = reflect.TypeOf(int64(0))
3637

3738
// GetString returns the string value for the specified attribute name in the attributes map. Returns error if not found.
3839
func (u UserAttributes) GetString(attrName string) (string, error) {
@@ -70,3 +71,15 @@ func (u UserAttributes) GetFloat(attrName string) (float64, error) {
7071

7172
return 0, fmt.Errorf(`No float attribute named "%s"`, attrName)
7273
}
74+
75+
func (u UserAttributes) GetInt(attrName string) (int64, error) {
76+
if value, ok := u.Attributes[attrName]; ok {
77+
v := reflect.ValueOf(value)
78+
if v.Type().String() == "int64" || v.Type().ConvertibleTo(intType) {
79+
intValue := v.Convert(intType).Int()
80+
return intValue, nil
81+
}
82+
}
83+
84+
return 0, fmt.Errorf(`No int attribute named "%s"`, attrName)
85+
}

optimizely/event/events.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ type ConversionEvent struct {
3434
Key string `json:"key"`
3535
Attributes []VisitorAttribute
3636
Tags map[string]interface{} `json:"tags,omitempty"`
37-
Revenue int `json:"revenue,omitempty"`
38-
Value float32 `json:"value,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"`
3941
}
4042

4143
type LogEvent struct {
@@ -84,6 +86,6 @@ type DispatchEvent struct {
8486
Timestamp int64 `json:"timestamp"`
8587
Uuid string `json:"uuid"`
8688
Tags map[string]interface{} `json:"tags,omitempty"`
87-
Revenue int `json:"revenue,omitempty"`
88-
Value float32 `json:"value,omitempty"`
89+
Revenue *int64 `json:"revenue,omitempty"`
90+
Value *float64 `json:"value,omitempty"`
8991
}

optimizely/event/factory.go

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ const attributeType = "custom"
1414
const specialPrefix = "$opt_"
1515
const botFilteringKey = "$opt_bot_filtering"
1616
const eventEndPoint = "https://logx.optimizely.com/v1/events"
17+
const revenueKey = "revenue"
18+
const valueKey = "value"
19+
1720

1821
func createLogEvent(event EventBatch) LogEvent {
1922
return LogEvent{endPoint:eventEndPoint,event:event}
@@ -53,14 +56,13 @@ func createImpressionEvent(context EventContext, experiment entities.Experiment,
5356

5457
func CreateImpressionUserEvent(context EventContext, experiment entities.Experiment,
5558
variation entities.Variation,
56-
userId string,
57-
attributes map[string]interface{}) UserEvent {
59+
userContext entities.UserContext) UserEvent {
5860

59-
impression := createImpressionEvent(context, experiment, variation, attributes)
61+
impression := createImpressionEvent(context, experiment, variation, userContext.Attributes.Attributes)
6062

6163
userEvent := UserEvent{}
6264
userEvent.Timestamp = makeTimestamp()
63-
userEvent.VisitorID = userId
65+
userEvent.VisitorID = userContext.ID
6466
userEvent.Uuid = guuid.New().String()
6567
userEvent.Impression = &impression
6668
userEvent.EventContext = context
@@ -96,16 +98,28 @@ func createConversionEvent(attributeKeyToIdMap map[string]string, event entities
9698

9799
return conversion
98100
}
99-
func CreateConversionUserEvent(context EventContext, event entities.Event, userId string, attributes map[string]interface{}, attributeKeyToIdMap map[string]string, eventTags map[string]interface{}) UserEvent {
101+
func CreateConversionUserEvent(context EventContext, event entities.Event, userContext entities.UserContext, attributeKeyToIdMap map[string]string, eventTags map[string]interface{}) UserEvent {
100102

101103

102104
userEvent := UserEvent{}
103105
userEvent.Timestamp = makeTimestamp()
104-
userEvent.VisitorID = userId
106+
userEvent.VisitorID = userContext.ID
105107
userEvent.Uuid = guuid.New().String()
106108

107109
userEvent.EventContext = context
108-
conversion := createConversionEvent(attributeKeyToIdMap, event, attributes, eventTags, context.BotFiltering)
110+
conversion := createConversionEvent(attributeKeyToIdMap, event, userContext.Attributes.Attributes, eventTags, context.BotFiltering)
111+
// convert event tags to UserAttributes to pull out proper values
112+
tagAttributes := entities.UserAttributes{Attributes:eventTags}
113+
// get revenue if available
114+
revenue, ok := tagAttributes.GetInt(revenueKey)
115+
if ok == nil {
116+
conversion.Revenue = &revenue
117+
}
118+
// get value if available
119+
value, ok := tagAttributes.GetFloat(valueKey)
120+
if ok == nil {
121+
conversion.Value = &value
122+
}
109123
userEvent.Conversion = &conversion
110124

111125
return userEvent
@@ -119,6 +133,12 @@ func createConversionBatchEvent(userEvent UserEvent) EventBatch {
119133
dispatchEvent.EntityID = userEvent.Conversion.EntityID
120134
dispatchEvent.Uuid = userEvent.Uuid
121135
dispatchEvent.Tags = userEvent.Conversion.Tags
136+
if userEvent.Conversion.Revenue != nil {
137+
dispatchEvent.Revenue = userEvent.Conversion.Revenue
138+
}
139+
if userEvent.Conversion.Value != nil {
140+
dispatchEvent.Value = userEvent.Conversion.Value
141+
}
122142

123143
return createBatchEvent(userEvent, userEvent.Conversion.Attributes, [] Decision{}, []DispatchEvent{dispatchEvent})
124144
}

optimizely/event/factory_test.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func RandomString(len int) string {
4848
}
4949

5050
var userId = RandomString(10)
51+
var userContext = entities.UserContext{userId, entities.UserAttributes{make(map[string]interface{})}}
5152

5253
func BuildTestImpressionEvent() UserEvent {
5354
config := TestConfig{}
@@ -62,7 +63,7 @@ func BuildTestImpressionEvent() UserEvent {
6263
variation.Key = "variation_a"
6364
variation.ID = "15410990633"
6465

65-
logEvent := CreateImpressionUserEvent(context, experiment, variation, userId, make(map[string]interface{}))
66+
logEvent := CreateImpressionUserEvent(context, experiment, variation, userContext)
6667

6768
return logEvent
6869
}
@@ -71,20 +72,20 @@ func BuildTestConversionEvent() UserEvent {
7172
config := TestConfig{}
7273
context := CreateEventContext(config.GetProjectID(), config.GetRevision(), config.GetAccountID(), config.GetAnonymizeIP(), config.GetBotFiltering(), make(map[string]string))
7374

74-
logEvent := CreateConversionUserEvent(context, entities.Event{ExperimentIds: []string{"15402980349"}, ID: "15368860886", Key: "sample_conversion"}, userId, make(map[string]interface{}),make(map[string]string), make(map[string]interface{}))
75+
logEvent := CreateConversionUserEvent(context, entities.Event{ExperimentIds: []string{"15402980349"}, ID: "15368860886", Key: "sample_conversion"}, userContext,make(map[string]string), make(map[string]interface{}))
7576

7677
return logEvent
7778
}
7879

79-
func TestCreateImpressionEvent(t *testing.T) {
80+
func TestCreateAndSendImpressionEvent(t *testing.T) {
8081

8182
logEvent := BuildTestImpressionEvent()
8283

8384
processor := NewEventProcessor(100, 100)
8485

8586
processor.ProcessImpression(logEvent)
8687

87-
result, ok := processor.(*InMemQueueEventProcessor)
88+
result, ok := processor.(*QueueingEventProcessor)
8889

8990
if ok {
9091
assert.Equal(t, 1, result.EventsCount())
@@ -95,15 +96,15 @@ func TestCreateImpressionEvent(t *testing.T) {
9596
}
9697
}
9798

98-
func TestCreateConversionEvent(t *testing.T) {
99+
func TestCreateAndSendConversionEvent(t *testing.T) {
99100

100101
logEvent := BuildTestConversionEvent()
101102

102103
processor := NewEventProcessor(100, 100)
103104

104105
processor.ProcessImpression(logEvent)
105106

106-
result, ok := processor.(*InMemQueueEventProcessor)
107+
result, ok := processor.(*QueueingEventProcessor)
107108

108109
if ok {
109110
assert.Equal(t, 1, result.EventsCount())
@@ -112,4 +113,21 @@ func TestCreateConversionEvent(t *testing.T) {
112113

113114
assert.Equal(t, 0, result.EventsCount())
114115
}
116+
}
117+
118+
func TestCreateConversionEventRevenue(t *testing.T) {
119+
eventTags := map[string]interface{}{"revenue":55.0, "value":25.1}
120+
config := TestConfig{}
121+
context := CreateEventContext(config.GetProjectID(), config.GetRevision(), config.GetAccountID(), config.GetAnonymizeIP(), config.GetBotFiltering(), make(map[string]string))
122+
123+
logEvent := CreateConversionUserEvent(context, entities.Event{ExperimentIds: []string{"15402980349"}, ID: "15368860886", Key: "sample_conversion"}, userContext,make(map[string]string), eventTags)
124+
125+
assert.Equal(t, int64(55), *logEvent.Conversion.Revenue)
126+
assert.Equal(t, 25.1, *logEvent.Conversion.Value)
127+
128+
batch := createConversionBatchEvent(logEvent)
129+
assert.Equal(t, int64(55), *batch.Visitors[0].Snapshots[0].Events[0].Revenue)
130+
assert.Equal(t, 25.1, *batch.Visitors[0].Snapshots[0].Events[0].Value)
131+
132+
115133
}

optimizely/event/processor.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ type Processor interface {
1111
ProcessImpression(event UserEvent)
1212
}
1313

14-
// InMemQueueEventProcessor is used out of the box by the SDK
15-
type InMemQueueEventProcessor struct {
14+
// QueueingEventProcessor is used out of the box by the SDK
15+
type QueueingEventProcessor struct {
1616
MaxQueueSize int // max size of the queue before flush
1717
FlushInterval time.Duration // in milliseconds
1818
BatchSize int
@@ -23,13 +23,13 @@ type InMemQueueEventProcessor struct {
2323
}
2424

2525
func NewEventProcessor(queueSize int, flushInterval time.Duration ) Processor {
26-
p := &InMemQueueEventProcessor{MaxQueueSize: queueSize, FlushInterval:flushInterval, Q:NewInMemoryQueue(queueSize), EventDispatcher:&HttpEventDispatcher{}}
26+
p := &QueueingEventProcessor{MaxQueueSize: queueSize, FlushInterval:flushInterval, Q:NewInMemoryQueue(queueSize), EventDispatcher:&HttpEventDispatcher{}}
2727
p.StartTicker()
2828
return p
2929
}
3030

3131
// ProcessImpression processes the given impression event
32-
func (p *InMemQueueEventProcessor) ProcessImpression(event UserEvent) {
32+
func (p *QueueingEventProcessor) ProcessImpression(event UserEvent) {
3333
p.Q.Add(event)
3434

3535
if p.Q.Size() >= p.MaxQueueSize {
@@ -39,19 +39,19 @@ func (p *InMemQueueEventProcessor) ProcessImpression(event UserEvent) {
3939
}
4040
}
4141

42-
func (p *InMemQueueEventProcessor) EventsCount() int {
42+
func (p *QueueingEventProcessor) EventsCount() int {
4343
return p.Q.Size()
4444
}
4545

46-
func (p *InMemQueueEventProcessor) GetEvents(count int) []interface{} {
46+
func (p *QueueingEventProcessor) GetEvents(count int) []interface{} {
4747
return p.Q.Get(count)
4848
}
4949

50-
func (p *InMemQueueEventProcessor) Remove(count int) []interface{} {
50+
func (p *QueueingEventProcessor) Remove(count int) []interface{} {
5151
return p.Q.Remove(count)
5252
}
5353

54-
func (p *InMemQueueEventProcessor) StartTicker() {
54+
func (p *QueueingEventProcessor) StartTicker() {
5555
if p.Ticker != nil {
5656
return
5757
}
@@ -64,7 +64,7 @@ func (p *InMemQueueEventProcessor) StartTicker() {
6464
}
6565

6666
// ProcessImpression processes the given impression event
67-
func (p *InMemQueueEventProcessor) FlushEvents() {
67+
func (p *QueueingEventProcessor) FlushEvents() {
6868
// we flush when queue size is reached.
6969
// however, if there is a ticker cycle already processing, we should wait
7070
p.Mux.Lock()

optimizely/event/processor_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ func TestDefaultEventProcessor_ProcessImpression(t *testing.T) {
1414

1515
processor.ProcessImpression(impression)
1616

17-
result, ok := processor.(*InMemQueueEventProcessor)
17+
result, ok := processor.(*QueueingEventProcessor)
1818

1919
if ok {
2020
assert.Equal(t, 1, result.EventsCount())
@@ -38,7 +38,7 @@ func TestDefaultEventProcessor_ProcessImpressions(t *testing.T) {
3838
processor.ProcessImpression(impression)
3939
processor.ProcessImpression(impression)
4040

41-
result, ok := processor.(*InMemQueueEventProcessor)
41+
result, ok := processor.(*QueueingEventProcessor)
4242

4343
if ok {
4444
assert.Equal(t, 2, result.EventsCount())

0 commit comments

Comments
 (0)