Skip to content

Commit f2b732f

Browse files
feat: Support Exporter Metadata (#614)
Signed-off-by: Thomas Poignant <[email protected]>
1 parent f6bd743 commit f2b732f

File tree

6 files changed

+50
-12
lines changed

6 files changed

+50
-12
lines changed

providers/go-feature-flag/pkg/controller/goff_api.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ type GoFeatureFlagApiOptions struct {
2222
// (This feature is available only if you are using GO Feature Flag relay proxy v1.7.0 or above)
2323
// Default: null
2424
APIKey string
25+
// ExporterMetadata (optional) If we set metadata, it will be sent with every data collection requests along with the events.
26+
ExporterMetadata map[string]interface{}
2527
}
2628

2729
type GoFeatureFlagAPI struct {
@@ -39,9 +41,10 @@ func NewGoFeatureFlagAPI(options GoFeatureFlagApiOptions) GoFeatureFlagAPI {
3941
func (g *GoFeatureFlagAPI) CollectData(events []model.FeatureEvent) error {
4042
u, _ := url.Parse(g.options.Endpoint)
4143
u.Path = path.Join(u.Path, "v1", "data", "collector")
44+
4245
reqBody := model.DataCollectorRequest{
4346
Events: events,
44-
Meta: map[string]string{"provider": "go", "openfeature": "true"},
47+
Meta: g.options.ExporterMetadata,
4548
}
4649

4750
jsonData, err := json.Marshal(reqBody)

providers/go-feature-flag/pkg/controller/goff_api_test.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ func Test_CollectDataAPI(t *testing.T) {
2828
name: "Valid api call",
2929
wantErr: assert.NoError,
3030
options: controller.GoFeatureFlagApiOptions{
31-
Endpoint: "http://localhost:1031",
32-
APIKey: "",
31+
Endpoint: "http://localhost:1031",
32+
APIKey: "",
33+
ExporterMetadata: map[string]interface{}{"openfeature": true, "provider": "go"},
3334
},
3435
events: []model.FeatureEvent{
3536
{
@@ -68,14 +69,15 @@ func Test_CollectDataAPI(t *testing.T) {
6869
headers.Set(controller.ContentTypeHeader, controller.ApplicationJson)
6970
return headers
7071
}(),
71-
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\"}}",
72+
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\"}}",
7273
},
7374
{
7475
name: "Valid api call with API Key",
7576
wantErr: assert.NoError,
7677
options: controller.GoFeatureFlagApiOptions{
77-
Endpoint: "http://localhost:1031",
78-
APIKey: "my-key",
78+
Endpoint: "http://localhost:1031",
79+
APIKey: "my-key",
80+
ExporterMetadata: map[string]interface{}{"openfeature": true, "provider": "go"},
7981
},
8082
events: []model.FeatureEvent{
8183
{
@@ -115,7 +117,7 @@ func Test_CollectDataAPI(t *testing.T) {
115117
headers.Set(controller.AuthorizationHeader, controller.BearerPrefix+"my-key")
116118
return headers
117119
}(),
118-
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\"}}",
120+
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\"}}",
119121
},
120122
{
121123
name: "Request failed",
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package model
22

33
type DataCollectorRequest struct {
4-
Events []FeatureEvent `json:"events"`
5-
Meta map[string]string `json:"meta"`
4+
Events []FeatureEvent `json:"events"`
5+
Meta map[string]interface{} `json:"meta"`
66
}

providers/go-feature-flag/pkg/provider.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,19 @@ func NewProviderWithContext(ctx context.Context, options ProviderOptions) (*Prov
5353
}))
5454
ofrepProvider := ofrep.NewProvider(options.Endpoint, ofrepOptions...)
5555
cacheCtrl := controller.NewCache(options.FlagCacheSize, options.FlagCacheTTL, options.DisableCache)
56+
57+
// Adding metadata to the GO Feature Flag provider to be sent to the exporter
58+
if options.ExporterMetadata == nil {
59+
options.ExporterMetadata = make(map[string]interface{})
60+
}
61+
options.ExporterMetadata["provider"] = "go"
62+
options.ExporterMetadata["openfeature"] = true
63+
5664
goffAPI := controller.NewGoFeatureFlagAPI(controller.GoFeatureFlagApiOptions{
57-
Endpoint: options.Endpoint,
58-
HTTPClient: options.HTTPClient,
59-
APIKey: options.APIKey,
65+
Endpoint: options.Endpoint,
66+
HTTPClient: options.HTTPClient,
67+
APIKey: options.APIKey,
68+
ExporterMetadata: options.ExporterMetadata,
6069
})
6170
dataCollectorManager := controller.NewDataCollectorManager(
6271
goffAPI,

providers/go-feature-flag/pkg/provider_options.go

+7
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ type ProviderOptions struct {
6262
// Use -1 if you want to deactivate polling.
6363
// default: 120000ms
6464
FlagChangePollingInterval time.Duration
65+
66+
// ExporterMetadata (optional) is the metadata we send to the GO Feature Flag relay proxy when we report the
67+
// evaluation data usage.
68+
//
69+
// ‼️Important: If you are using a GO Feature Flag relay proxy before version v1.41.0, the information of this
70+
// field will not be added to your feature events.
71+
ExporterMetadata map[string]interface{}
6572
}
6673

6774
func (o *ProviderOptions) Validation() error {

providers/go-feature-flag/pkg/provider_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package gofeatureflag_test
33
import (
44
"bytes"
55
"context"
6+
"encoding/json"
67
"fmt"
78
gofeatureflag "github.com/open-feature/go-sdk-contrib/providers/go-feature-flag/pkg"
9+
"github.com/open-feature/go-sdk-contrib/providers/go-feature-flag/pkg/model"
810
of "github.com/open-feature/go-sdk/openfeature"
911
"github.com/stretchr/testify/assert"
1012
"github.com/stretchr/testify/require"
@@ -36,11 +38,14 @@ type mockClient struct {
3638
callCount int
3739
collectorCallCount int
3840
flagChangeCallCount int
41+
collectorRequests []string
3942
}
4043

4144
func (m *mockClient) roundTripFunc(req *http.Request) *http.Response {
4245
if req.URL.Path == "/v1/data/collector" {
4346
m.collectorCallCount++
47+
bodyBytes, _ := io.ReadAll(req.Body)
48+
m.collectorRequests = append(m.collectorRequests, string(bodyBytes))
4449
return &http.Response{
4550
StatusCode: http.StatusOK,
4651
}
@@ -982,6 +987,7 @@ func TestProvider_DataCollectorHook(t *testing.T) {
982987
DisableCache: false,
983988
DataFlushInterval: 100 * time.Millisecond,
984989
DisableDataCollector: false,
990+
ExporterMetadata: map[string]interface{}{"toto": 123, "tata": "titi"},
985991
}
986992
provider, err := gofeatureflag.NewProvider(options)
987993
defer provider.Shutdown()
@@ -1003,6 +1009,17 @@ func TestProvider_DataCollectorHook(t *testing.T) {
10031009
time.Sleep(500 * time.Millisecond)
10041010
assert.Equal(t, 1, cli.callCount)
10051011
assert.Equal(t, 1, cli.collectorCallCount)
1012+
1013+
// convert cli.collectorRequests[0] to DataCollectorRequest
1014+
var dataCollectorRequest model.DataCollectorRequest
1015+
err = json.Unmarshal([]byte(cli.collectorRequests[0]), &dataCollectorRequest)
1016+
assert.NoError(t, err)
1017+
assert.Equal(t, map[string]interface{}{
1018+
"openfeature": true,
1019+
"provider": "go",
1020+
"tata": "titi",
1021+
"toto": float64(123),
1022+
}, dataCollectorRequest.Meta)
10061023
})
10071024

10081025
t.Run("DataCollectorHook is called for errors and call API", func(t *testing.T) {

0 commit comments

Comments
 (0)