Skip to content

Commit

Permalink
feat: Support Exporter Metadata (#614)
Browse files Browse the repository at this point in the history
Signed-off-by: Thomas Poignant <[email protected]>
  • Loading branch information
thomaspoignant authored Jan 23, 2025
1 parent f6bd743 commit f2b732f
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 12 deletions.
5 changes: 4 additions & 1 deletion providers/go-feature-flag/pkg/controller/goff_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ExporterMetadata (optional) If we set metadata, it will be sent with every data collection requests along with the events.
ExporterMetadata map[string]interface{}
}

type GoFeatureFlagAPI struct {
Expand All @@ -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.ExporterMetadata,
}

jsonData, err := json.Marshal(reqBody)
Expand Down
14 changes: 8 additions & 6 deletions providers/go-feature-flag/pkg/controller/goff_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ func Test_CollectDataAPI(t *testing.T) {
name: "Valid api call",
wantErr: assert.NoError,
options: controller.GoFeatureFlagApiOptions{
Endpoint: "http://localhost:1031",
APIKey: "",
Endpoint: "http://localhost:1031",
APIKey: "",
ExporterMetadata: map[string]interface{}{"openfeature": true, "provider": "go"},
},
events: []model.FeatureEvent{
{
Expand Down Expand Up @@ -68,14 +69,15 @@ 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",
wantErr: assert.NoError,
options: controller.GoFeatureFlagApiOptions{
Endpoint: "http://localhost:1031",
APIKey: "my-key",
Endpoint: "http://localhost:1031",
APIKey: "my-key",
ExporterMetadata: map[string]interface{}{"openfeature": true, "provider": "go"},
},
events: []model.FeatureEvent{
{
Expand Down Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions providers/go-feature-flag/pkg/model/data_collector_request.go
Original file line number Diff line number Diff line change
@@ -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"`
}
15 changes: 12 additions & 3 deletions providers/go-feature-flag/pkg/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.ExporterMetadata == nil {
options.ExporterMetadata = make(map[string]interface{})
}
options.ExporterMetadata["provider"] = "go"
options.ExporterMetadata["openfeature"] = true

goffAPI := controller.NewGoFeatureFlagAPI(controller.GoFeatureFlagApiOptions{
Endpoint: options.Endpoint,
HTTPClient: options.HTTPClient,
APIKey: options.APIKey,
Endpoint: options.Endpoint,
HTTPClient: options.HTTPClient,
APIKey: options.APIKey,
ExporterMetadata: options.ExporterMetadata,
})
dataCollectorManager := controller.NewDataCollectorManager(
goffAPI,
Expand Down
7 changes: 7 additions & 0 deletions providers/go-feature-flag/pkg/provider_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ type ProviderOptions struct {
// Use -1 if you want to deactivate polling.
// default: 120000ms
FlagChangePollingInterval time.Duration

// ExporterMetadata (optional) is the metadata we send to the GO Feature Flag relay proxy when we report the
// evaluation data usage.
//
// ‼️Important: If you are using a GO Feature Flag relay proxy before version v1.41.0, the information of this
// field will not be added to your feature events.
ExporterMetadata map[string]interface{}
}

func (o *ProviderOptions) Validation() error {
Expand Down
17 changes: 17 additions & 0 deletions providers/go-feature-flag/pkg/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -982,6 +987,7 @@ func TestProvider_DataCollectorHook(t *testing.T) {
DisableCache: false,
DataFlushInterval: 100 * time.Millisecond,
DisableDataCollector: false,
ExporterMetadata: map[string]interface{}{"toto": 123, "tata": "titi"},
}
provider, err := gofeatureflag.NewProvider(options)
defer provider.Shutdown()
Expand All @@ -1003,6 +1009,17 @@ 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.NoError(t, err)
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) {
Expand Down

0 comments on commit f2b732f

Please sign in to comment.