Skip to content

Commit

Permalink
add basic event tracking for confidence and add contextual tests
Browse files Browse the repository at this point in the history
add tests for contextuals

move context calculations out of a go routine
  • Loading branch information
vahidlazio committed May 22, 2024
1 parent 4579c32 commit b385cd9
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 39 deletions.
33 changes: 33 additions & 0 deletions confidence/EventUploader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package confidence

import (
"bytes"
"context"
"encoding/json"
"net/http"
)

type EventUploader struct {
client http.Client
config APIConfig
}

func (e EventUploader) upload(ctx context.Context, request EventBatchRequest) {
jsonRequest, err := json.Marshal(request)
if err != nil {
return
}

payload := bytes.NewBuffer(jsonRequest)
req, err := http.NewRequestWithContext(ctx,
http.MethodPost, "https://events.eu.confidence.dev/v1/events:publish", payload)
if err != nil {
return
}

resp, err := e.client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
}
49 changes: 44 additions & 5 deletions confidence/confidence.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package confidence
import (
"context"
"fmt"
"time"
"net/http"
"reflect"
"strings"
Expand All @@ -18,24 +19,28 @@ type ContextProvider interface {
}

var (
SDK_ID = "SDK_ID_GO_PROVIDER"
SDK_ID = "SDK_ID_GO_CONFIDENCE"
SDK_VERSION = "0.1.8" // x-release-please-version
)

type Confidence struct {
parent ContextProvider
uploader EventUploader
contextMap map[string]interface{}
Config APIConfig
ResolveClient ResolveClient
}

func (e Confidence) GetContext() map[string]interface{} {
currentMap := e.contextMap
currentMap := map[string]interface{}{}
parentMap := make(map[string]interface{})
if e.parent != nil {
parentMap = e.parent.GetContext()
}
for key, value := range parentMap {
for key, value := range parentMap {
currentMap[key] = value
}
for key, value := range e.contextMap {
currentMap[key] = value
}
return currentMap
Expand Down Expand Up @@ -69,14 +74,48 @@ func NewConfidenceBuilder() ConfidenceBuilder {
}
}

func (e Confidence) putContext(key string, value interface{}) {
func (e Confidence) PutContext(key string, value interface{}) {
e.contextMap[key] = value
}

func (e Confidence) Track(ctx context.Context, eventName string, message map[string]interface{}) {
newMap := e.GetContext()

for key, value := range message {
newMap[key] = value
}

go func() {
currentTime := time.Now()
iso8601Time := currentTime.Format(time.RFC3339)
event := Event {
EventDefinition: fmt.Sprintf("eventDefinitions/%s", eventName),
EventTime: iso8601Time,
Payload: newMap,
}
batch := EventBatchRequest{
CclientSecret: e.Config.APIKey,
Sdk: sdk{SDK_ID, SDK_VERSION},
SendTime: iso8601Time,
Events: []Event{event},
}
e.uploader.upload(ctx, batch)
}()
}

func (e Confidence) WithContext(context map[string]interface{}) Confidence {
newMap := map[string]interface{}{}
for key, value := range e.GetContext() {
newMap[key] = value
}

for key, value := range context {
newMap[key] = value
}

return Confidence{
parent: &e,
contextMap: context,
contextMap: newMap,
Config: e.Config,
ResolveClient: e.ResolveClient,
}
Expand Down
43 changes: 43 additions & 0 deletions confidence/confidence_context_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package confidence

import (
"github.com/stretchr/testify/assert"
"testing"
)

func TestContextIsInConfidenceObject(t *testing.T) {
client := create_confidence(t, templateResponse())
client.PutContext("hello", "hey")
assert.Equal(t, client.GetContext(), map[string]interface{}{"hello": "hey"})
}

func TestWithContextIsInChildContext(t *testing.T) {
client := create_confidence(t, templateResponse())
client.PutContext("hello", "hey")
child := client.WithContext(map[string]interface{}{"west": "world"})
assert.Equal(t, child.GetContext(), map[string]interface{}{"hello": "hey", "west": "world"})
client.PutContext("hello2", "hey2")
assert.Equal(t, child.GetContext(), map[string]interface{}{"hello": "hey", "west": "world", "hello2": "hey2"})
}


func TestChildContextOverrideParentContext(t *testing.T) {
client := create_confidence(t, templateResponse())
client.PutContext("hello", "hey")
child := client.WithContext(map[string]interface{}{"hello": "boom"})
assert.Equal(t, child.GetContext(), map[string]interface{}{"hello": "boom"})
assert.Equal(t, client.GetContext(), map[string]interface{}{"hello": "hey"})
}

func create_confidence(t *testing.T, response ResolveResponse) *Confidence {
config := APIConfig{
APIKey: "apiKey",
Region: APIRegionGlobal,
}
return &Confidence{
Config: config,
ResolveClient: MockResolveClient{MockedResponse: response, MockedError: nil, TestingT: t},
contextMap: make(map[string]interface{}),
}
}

24 changes: 12 additions & 12 deletions confidence/confidence_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (r MockResolveClient) SendResolveRequest(_ context.Context,

func TestResolveBoolValue(t *testing.T) {
client := client(t, templateResponse(), nil)
client.putContext("targeting_key", "user1")
client.PutContext("targeting_key", "user1")
evalDetails := client.GetBoolFlag(context.Background(), "test-flag.boolean-key", false)

assert.Equal(t, true, evalDetails.Value)
Expand All @@ -34,7 +34,7 @@ func TestResolveBoolValue(t *testing.T) {

func TestResolveIntValue(t *testing.T) {
client := client(t, templateResponse(), nil)
client.putContext("targeting_key", "user1")
client.PutContext("targeting_key", "user1")

evalDetails := client.GetIntFlag(context.Background(), "test-flag.integer-key", 99)

Expand All @@ -43,7 +43,7 @@ func TestResolveIntValue(t *testing.T) {

func TestResolveDoubleValue(t *testing.T) {
client := client(t, templateResponse(), nil)
client.putContext("targeting_key", "user1")
client.PutContext("targeting_key", "user1")

evalDetails := client.GetDoubleFlag(context.Background(), "test-flag.double-key", 99.99)

Expand All @@ -52,7 +52,7 @@ func TestResolveDoubleValue(t *testing.T) {

func TestResolveStringValue(t *testing.T) {
client := client(t, templateResponse(), nil)
client.putContext("targeting_key", "user1")
client.PutContext("targeting_key", "user1")

evalDetails := client.GetStringFlag(context.Background(), "test-flag.string-key", "default")

Expand All @@ -61,7 +61,7 @@ func TestResolveStringValue(t *testing.T) {

func TestResolveObjectValue(t *testing.T) {
client := client(t, templateResponse(), nil)
client.putContext("targeting_key", "user1")
client.PutContext("targeting_key", "user1")

evalDetails := client.GetObjectFlag(context.Background(), "test-flag.struct-key", "default")
_, ok := evalDetails.Value.(map[string]interface{})
Expand All @@ -70,23 +70,23 @@ func TestResolveObjectValue(t *testing.T) {

func TestResolveNestedValue(t *testing.T) {
client := client(t, templateResponse(), nil)
client.putContext("targeting_key", "user1")
client.PutContext("targeting_key", "user1")

evalDetails := client.GetBoolFlag(context.Background(), "test-flag.struct-key.boolean-key", true)
assert.Equal(t, false, evalDetails.Value)
}

func TestResolveDoubleNestedValue(t *testing.T) {
client := client(t, templateResponse(), nil)
client.putContext("targeting_key", "user1")
client.PutContext("targeting_key", "user1")

evalDetails := client.GetBoolFlag(context.Background(), "test-flag.struct-key.nested-struct-key.nested-boolean-key", true)
assert.Equal(t, false, evalDetails.Value)
}

func TestResolveWholeFlagAsObject(t *testing.T) {
client := client(t, templateResponse(), nil)
client.putContext("targeting_key", "user1")
client.PutContext("targeting_key", "user1")

evalDetails := client.GetObjectFlag(context.Background(), "test-flag", "default")
_, ok := evalDetails.Value.(map[string]interface{})
Expand All @@ -95,7 +95,7 @@ func TestResolveWholeFlagAsObject(t *testing.T) {

func TestResolveWholeFlagAsObjectWithInts(t *testing.T) {
client := client(t, templateResponse(), nil)
client.putContext("targeting_key", "user1")
client.PutContext("targeting_key", "user1")

evalDetails := client.GetObjectFlag(context.Background(), "test-flag", "default")

Expand All @@ -113,7 +113,7 @@ func TestResolveWholeFlagAsObjectWithInts(t *testing.T) {

func TestResolveWithWrongType(t *testing.T) {
client := client(t, templateResponse(), nil)
client.putContext("targeting_key", "user1")
client.PutContext("targeting_key", "user1")

evalDetails := client.GetBoolFlag(context.Background(), "test-flag.integer-key", false)

Expand All @@ -124,7 +124,7 @@ func TestResolveWithWrongType(t *testing.T) {

func TestResolveWithUnexpectedFlag(t *testing.T) {
client := client(t, templateResponseWithFlagName("wrong-flag"), nil)
client.putContext("targeting_key", "user1")
client.PutContext("targeting_key", "user1")

evalDetails := client.GetBoolFlag(context.Background(), "test-flag.boolean-key", true)

Expand All @@ -136,7 +136,7 @@ func TestResolveWithUnexpectedFlag(t *testing.T) {

func TestResolveWithNonExistingFlag(t *testing.T) {
client := client(t, emptyResponse(), nil)
client.putContext("targeting_key", "user1")
client.PutContext("targeting_key", "user1")

evalDetails := client.GetBoolFlag(context.Background(), "test-flag.boolean-key", true)

Expand Down
13 changes: 13 additions & 0 deletions confidence/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ type ResolveClient interface {

var errFlagNotFound = errors.New("flag not found")

type EventBatchRequest struct {
CclientSecret string `json:"clientSecret"`
Sdk sdk `json:"sdk"`
SendTime string `json:"sendTime"`
Events []Event `json:"events"`
}

type Event struct {
EventDefinition string `json:"eventDefinition"`
EventTime string `json:"eventTime"`
Payload map[string]interface{} `json:"payload"`
}

type ResolveRequest struct {
ClientSecret string `json:"client_secret"`
Apply bool `json:"apply"`
Expand Down
5 changes: 5 additions & 0 deletions demo/GoDemoApp.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ func main() {

confidence := c.NewConfidenceBuilder().SetAPIConfig(c.APIConfig{APIKey: clientSecret}).Build()

confidence.PutContext("hello", "world")

for range make([]struct{}, 10) {
confidence.Track(context.Background(), "navigate", map[string]interface{}{"test": "value"})
}
provider := p.NewFlagProvider(confidence)

openfeature.SetProvider(provider)
Expand Down
11 changes: 11 additions & 0 deletions demo/go.sum
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
github.com/cucumber/gherkin/go/v26 v26.2.0/go.mod h1:t2GAPnB8maCT4lkHL99BDCVNzCh1d7dBhCLt150Nr/0=
github.com/cucumber/godog v0.14.0/go.mod h1:FX3rzIDybWABU4kuIXLZ/qtqEe1Ac5RdXmqvACJOces=
github.com/cucumber/messages/go/v21 v21.0.1/go.mod h1:zheH/2HS9JLVFukdrsPWoPdmUtmYQAQPLk7w5vWsk5s=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/open-feature/go-sdk v1.10.0 h1:druQtYOrN+gyz3rMsXp0F2jW1oBXJb0V26PVQnUGLbM=
github.com/open-feature/go-sdk v1.10.0/go.mod h1:+rkJhLBtYsJ5PZNddAgFILhRAAxwrJ32aU7UEUm4zQI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
6 changes: 0 additions & 6 deletions provider/google-java-format.xml

This file was deleted.

6 changes: 0 additions & 6 deletions provider/misc.xml

This file was deleted.

2 changes: 2 additions & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ func (e FlagProvider) StringEvaluation(ctx context.Context, flag string, default
}
}



func (e FlagProvider) FloatEvaluation(ctx context.Context, flag string, defaultValue float64,
evalCtx openfeature.FlattenedContext) openfeature.FloatResolutionDetail {
confidence := e.confidence.WithContext(processTargetingKey(evalCtx))
Expand Down
10 changes: 0 additions & 10 deletions provider/provider.iml

This file was deleted.

0 comments on commit b385cd9

Please sign in to comment.