diff --git a/confidence/EventUploader.go b/confidence/EventUploader.go new file mode 100644 index 0000000..94f9109 --- /dev/null +++ b/confidence/EventUploader.go @@ -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() +} \ No newline at end of file diff --git a/confidence/confidence.go b/confidence/confidence.go index 7c39d71..f1132f0 100644 --- a/confidence/confidence.go +++ b/confidence/confidence.go @@ -3,6 +3,7 @@ package confidence import ( "context" "fmt" + "time" "net/http" "reflect" "strings" @@ -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 @@ -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, } diff --git a/confidence/confidence_context_test.go b/confidence/confidence_context_test.go new file mode 100644 index 0000000..9a17a57 --- /dev/null +++ b/confidence/confidence_context_test.go @@ -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{}), + } +} + diff --git a/confidence/confidence_internal_test.go b/confidence/confidence_internal_test.go index 9b71b0f..4bdc9b2 100644 --- a/confidence/confidence_internal_test.go +++ b/confidence/confidence_internal_test.go @@ -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) @@ -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) @@ -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) @@ -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") @@ -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{}) @@ -70,7 +70,7 @@ 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) @@ -78,7 +78,7 @@ func TestResolveNestedValue(t *testing.T) { 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) @@ -86,7 +86,7 @@ func TestResolveDoubleNestedValue(t *testing.T) { 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{}) @@ -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") @@ -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) @@ -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) @@ -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) diff --git a/confidence/models.go b/confidence/models.go index 6cb4d25..0da7486 100644 --- a/confidence/models.go +++ b/confidence/models.go @@ -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"` diff --git a/demo/GoDemoApp.go b/demo/GoDemoApp.go index 253842e..3556b2f 100644 --- a/demo/GoDemoApp.go +++ b/demo/GoDemoApp.go @@ -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) diff --git a/demo/go.sum b/demo/go.sum index 39c4ad6..6c6175d 100644 --- a/demo/go.sum +++ b/demo/go.sum @@ -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= diff --git a/provider/google-java-format.xml b/provider/google-java-format.xml deleted file mode 100644 index 2aa056d..0000000 --- a/provider/google-java-format.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/provider/misc.xml b/provider/misc.xml deleted file mode 100644 index 3d3ab27..0000000 --- a/provider/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/provider/provider.go b/provider/provider.go index c71579f..06ff769 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -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)) diff --git a/provider/provider.iml b/provider/provider.iml deleted file mode 100644 index 25ed3f6..0000000 --- a/provider/provider.iml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file