Skip to content

Commit

Permalink
feat: Support Exporter Metadata
Browse files Browse the repository at this point in the history
GO Feature Flag exporter is now supporting adding extra params as metadata.
Those params are static informations provided during the initialisation to be added in the meta field when calling the exporter.

In the GO Feature Flag exporter we will have those information available in the exporter.

Signed-off-by: Thomas Poignant <[email protected]>
  • Loading branch information
thomaspoignant committed Jan 14, 2025
1 parent 4077d5a commit 24277c3
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 10 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
// 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 {
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.Metadata,
}

jsonData, err := json.Marshal(reqBody)
Expand Down
6 changes: 4 additions & 2 deletions providers/go-feature-flag/pkg/controller/goff_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
{
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",
Metadata: 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"`
}
9 changes: 9 additions & 0 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.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,
Expand Down
4 changes: 4 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,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 {
Expand Down
26 changes: 21 additions & 5 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 @@ -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()
Expand All @@ -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) {
Expand Down

0 comments on commit 24277c3

Please sign in to comment.