diff --git a/providers/go-feature-flag/pkg/controller/goff_api.go b/providers/go-feature-flag/pkg/controller/goff_api.go index cf900a95d..772d4d983 100644 --- a/providers/go-feature-flag/pkg/controller/goff_api.go +++ b/providers/go-feature-flag/pkg/controller/goff_api.go @@ -22,6 +22,8 @@ type GoFeatureFlagApiOptions struct { // (This feature is available only if you are using GO Feature Flag relay proxy v1.7.0 or above) // Default: null APIKey string + // Metadata (optional) If we set metadata, it will be sent with every data collection requests along with the events. + Metadata map[string]interface{} } type GoFeatureFlagAPI struct { @@ -39,9 +41,10 @@ func NewGoFeatureFlagAPI(options GoFeatureFlagApiOptions) GoFeatureFlagAPI { func (g *GoFeatureFlagAPI) CollectData(events []model.FeatureEvent) error { u, _ := url.Parse(g.options.Endpoint) u.Path = path.Join(u.Path, "v1", "data", "collector") + reqBody := model.DataCollectorRequest{ Events: events, - Meta: map[string]string{"provider": "go", "openfeature": "true"}, + Meta: g.options.Metadata, } jsonData, err := json.Marshal(reqBody) diff --git a/providers/go-feature-flag/pkg/controller/goff_api_test.go b/providers/go-feature-flag/pkg/controller/goff_api_test.go index 21f578187..f28d98049 100644 --- a/providers/go-feature-flag/pkg/controller/goff_api_test.go +++ b/providers/go-feature-flag/pkg/controller/goff_api_test.go @@ -30,6 +30,7 @@ func Test_CollectDataAPI(t *testing.T) { options: controller.GoFeatureFlagApiOptions{ Endpoint: "http://localhost:1031", APIKey: "", + Metadata: map[string]interface{}{"openfeature": true, "provider": "go"}, }, events: []model.FeatureEvent{ { @@ -68,7 +69,7 @@ func Test_CollectDataAPI(t *testing.T) { headers.Set(controller.ContentTypeHeader, controller.ApplicationJson) return headers }(), - wantReqBody: "{\"events\":[{\"kind\":\"feature\",\"contextKind\":\"user\",\"userKey\":\"ABCD\",\"creationDate\":1722266324,\"key\":\"random-key\",\"variation\":\"variationA\",\"value\":\"YO\",\"default\":false,\"version\":\"\",\"source\":\"SERVER\"},{\"kind\":\"feature\",\"contextKind\":\"user\",\"userKey\":\"EFGH\",\"creationDate\":1722266324,\"key\":\"random-key\",\"variation\":\"variationA\",\"value\":\"YO\",\"default\":false,\"version\":\"\",\"source\":\"SERVER\"}],\"meta\":{\"openfeature\":\"true\",\"provider\":\"go\"}}", + wantReqBody: "{\"events\":[{\"kind\":\"feature\",\"contextKind\":\"user\",\"userKey\":\"ABCD\",\"creationDate\":1722266324,\"key\":\"random-key\",\"variation\":\"variationA\",\"value\":\"YO\",\"default\":false,\"version\":\"\",\"source\":\"SERVER\"},{\"kind\":\"feature\",\"contextKind\":\"user\",\"userKey\":\"EFGH\",\"creationDate\":1722266324,\"key\":\"random-key\",\"variation\":\"variationA\",\"value\":\"YO\",\"default\":false,\"version\":\"\",\"source\":\"SERVER\"}],\"meta\":{\"openfeature\":true,\"provider\":\"go\"}}", }, { name: "Valid api call with API Key", @@ -76,6 +77,7 @@ func Test_CollectDataAPI(t *testing.T) { options: controller.GoFeatureFlagApiOptions{ Endpoint: "http://localhost:1031", APIKey: "my-key", + Metadata: map[string]interface{}{"openfeature": true, "provider": "go"}, }, events: []model.FeatureEvent{ { @@ -115,7 +117,7 @@ func Test_CollectDataAPI(t *testing.T) { headers.Set(controller.AuthorizationHeader, controller.BearerPrefix+"my-key") return headers }(), - wantReqBody: "{\"events\":[{\"kind\":\"feature\",\"contextKind\":\"user\",\"userKey\":\"ABCD\",\"creationDate\":1722266324,\"key\":\"random-key\",\"variation\":\"variationA\",\"value\":\"YO\",\"default\":false,\"version\":\"\",\"source\":\"SERVER\"},{\"kind\":\"feature\",\"contextKind\":\"user\",\"userKey\":\"EFGH\",\"creationDate\":1722266324,\"key\":\"random-key\",\"variation\":\"variationA\",\"value\":\"YO\",\"default\":false,\"version\":\"\",\"source\":\"SERVER\"}],\"meta\":{\"openfeature\":\"true\",\"provider\":\"go\"}}", + wantReqBody: "{\"events\":[{\"kind\":\"feature\",\"contextKind\":\"user\",\"userKey\":\"ABCD\",\"creationDate\":1722266324,\"key\":\"random-key\",\"variation\":\"variationA\",\"value\":\"YO\",\"default\":false,\"version\":\"\",\"source\":\"SERVER\"},{\"kind\":\"feature\",\"contextKind\":\"user\",\"userKey\":\"EFGH\",\"creationDate\":1722266324,\"key\":\"random-key\",\"variation\":\"variationA\",\"value\":\"YO\",\"default\":false,\"version\":\"\",\"source\":\"SERVER\"}],\"meta\":{\"openfeature\":true,\"provider\":\"go\"}}", }, { name: "Request failed", diff --git a/providers/go-feature-flag/pkg/model/data_collector_request.go b/providers/go-feature-flag/pkg/model/data_collector_request.go index d49fef9ea..985d80275 100644 --- a/providers/go-feature-flag/pkg/model/data_collector_request.go +++ b/providers/go-feature-flag/pkg/model/data_collector_request.go @@ -1,6 +1,6 @@ package model type DataCollectorRequest struct { - Events []FeatureEvent `json:"events"` - Meta map[string]string `json:"meta"` + Events []FeatureEvent `json:"events"` + Meta map[string]interface{} `json:"meta"` } diff --git a/providers/go-feature-flag/pkg/provider.go b/providers/go-feature-flag/pkg/provider.go index 1ecacf9b0..3f8f78369 100644 --- a/providers/go-feature-flag/pkg/provider.go +++ b/providers/go-feature-flag/pkg/provider.go @@ -53,10 +53,19 @@ func NewProviderWithContext(ctx context.Context, options ProviderOptions) (*Prov })) ofrepProvider := ofrep.NewProvider(options.Endpoint, ofrepOptions...) cacheCtrl := controller.NewCache(options.FlagCacheSize, options.FlagCacheTTL, options.DisableCache) + + // Adding metadata to the GO Feature Flag provider to be sent to the exporter + if options.GOFeatureFlagMetadata == nil { + options.GOFeatureFlagMetadata = make(map[string]interface{}) + } + options.GOFeatureFlagMetadata["provider"] = "go" + options.GOFeatureFlagMetadata["openfeature"] = true + goffAPI := controller.NewGoFeatureFlagAPI(controller.GoFeatureFlagApiOptions{ Endpoint: options.Endpoint, HTTPClient: options.HTTPClient, APIKey: options.APIKey, + Metadata: options.GOFeatureFlagMetadata, }) dataCollectorManager := controller.NewDataCollectorManager( goffAPI, diff --git a/providers/go-feature-flag/pkg/provider_options.go b/providers/go-feature-flag/pkg/provider_options.go index 871568819..9366f8abc 100644 --- a/providers/go-feature-flag/pkg/provider_options.go +++ b/providers/go-feature-flag/pkg/provider_options.go @@ -62,6 +62,10 @@ type ProviderOptions struct { // Use -1 if you want to deactivate polling. // default: 120000ms FlagChangePollingInterval time.Duration + + // GOFeatureFlagMetadata (optional) is the metadata we send to the GO Feature Flag relay proxy when we report the + // evaluation data usage. + GOFeatureFlagMetadata map[string]interface{} } func (o *ProviderOptions) Validation() error { diff --git a/providers/go-feature-flag/pkg/provider_test.go b/providers/go-feature-flag/pkg/provider_test.go index aa832241a..1d633f738 100644 --- a/providers/go-feature-flag/pkg/provider_test.go +++ b/providers/go-feature-flag/pkg/provider_test.go @@ -3,8 +3,10 @@ package gofeatureflag_test import ( "bytes" "context" + "encoding/json" "fmt" gofeatureflag "github.com/open-feature/go-sdk-contrib/providers/go-feature-flag/pkg" + "github.com/open-feature/go-sdk-contrib/providers/go-feature-flag/pkg/model" of "github.com/open-feature/go-sdk/openfeature" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -36,11 +38,14 @@ type mockClient struct { callCount int collectorCallCount int flagChangeCallCount int + collectorRequests []string } func (m *mockClient) roundTripFunc(req *http.Request) *http.Response { if req.URL.Path == "/v1/data/collector" { m.collectorCallCount++ + bodyBytes, _ := io.ReadAll(req.Body) + m.collectorRequests = append(m.collectorRequests, string(bodyBytes)) return &http.Response{ StatusCode: http.StatusOK, } @@ -977,11 +982,12 @@ func TestProvider_DataCollectorHook(t *testing.T) { t.Run("DataCollectorHook is called for success and call API", func(t *testing.T) { cli := mockClient{} options := gofeatureflag.ProviderOptions{ - Endpoint: "https://gofeatureflag.org/", - HTTPClient: NewMockClient(cli.roundTripFunc), - DisableCache: false, - DataFlushInterval: 100 * time.Millisecond, - DisableDataCollector: false, + Endpoint: "https://gofeatureflag.org/", + HTTPClient: NewMockClient(cli.roundTripFunc), + DisableCache: false, + DataFlushInterval: 100 * time.Millisecond, + DisableDataCollector: false, + GOFeatureFlagMetadata: map[string]interface{}{"toto": 123, "tata": "titi"}, } provider, err := gofeatureflag.NewProvider(options) defer provider.Shutdown() @@ -1003,6 +1009,16 @@ func TestProvider_DataCollectorHook(t *testing.T) { time.Sleep(500 * time.Millisecond) assert.Equal(t, 1, cli.callCount) assert.Equal(t, 1, cli.collectorCallCount) + + // convert cli.collectorRequests[0] to DataCollectorRequest + var dataCollectorRequest model.DataCollectorRequest + err = json.Unmarshal([]byte(cli.collectorRequests[0]), &dataCollectorRequest) + assert.Equal(t, map[string]interface{}{ + "openfeature": true, + "provider": "go", + "tata": "titi", + "toto": float64(123), + }, dataCollectorRequest.Meta) }) t.Run("DataCollectorHook is called for errors and call API", func(t *testing.T) {