diff --git a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderOptions.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderOptions.java index 4e77cb7f5..3239924f1 100644 --- a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderOptions.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderOptions.java @@ -4,6 +4,7 @@ import dev.openfeature.sdk.ProviderEvaluation; import lombok.Builder; import lombok.Getter; +import java.util.Map; /** GoFeatureFlagProviderOptions contains the options to initialise the provider. */ @Builder @@ -90,4 +91,13 @@ public class GoFeatureFlagProviderOptions { * retrieved in the cache. default: false */ private boolean disableDataCollection; + + /** + * (optional) exporterMetadata 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. + */ + private Map exporterMetadata; } diff --git a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/controller/GoFeatureFlagController.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/controller/GoFeatureFlagController.java index 184706e9d..a0003961d 100644 --- a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/controller/GoFeatureFlagController.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/controller/GoFeatureFlagController.java @@ -31,7 +31,10 @@ import dev.openfeature.sdk.exceptions.TypeMismatchError; import java.io.IOException; import java.net.HttpURLConnection; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import lombok.Builder; import lombok.extern.slf4j.Slf4j; @@ -69,6 +72,9 @@ public class GoFeatureFlagController { private final HttpUrl parsedEndpoint; + /** exporterMetadata contains the metadata to send to the collector API. */ + private Map exporterMetadata = new HashMap<>(); + /** * etag contains the etag of the configuration, if null, it means that the configuration has never * been retrieved. @@ -85,6 +91,7 @@ public class GoFeatureFlagController { @Builder private GoFeatureFlagController(final GoFeatureFlagProviderOptions options) throws InvalidOptions { this.apiKey = options.getApiKey(); + this.exporterMetadata = options.getExporterMetadata(); this.parsedEndpoint = HttpUrl.parse(options.getEndpoint()); if (this.parsedEndpoint == null) { @@ -218,7 +225,7 @@ public EvaluationResponse evaluateFlag( */ public void sendEventToDataCollector(List eventsList) { try { - Events events = new Events(eventsList); + Events events = new Events(eventsList, this.exporterMetadata); HttpUrl url = this.parsedEndpoint .newBuilder() .addEncodedPathSegment("v1") diff --git a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/hook/events/Events.java b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/hook/events/Events.java index 4b123ad5c..83781533d 100644 --- a/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/hook/events/Events.java +++ b/providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/hook/events/Events.java @@ -9,16 +9,15 @@ /** Events data. */ @Getter public class Events { - private static final Map meta = new HashMap<>(); - - static { - meta.put("provider", "java"); - meta.put("openfeature", "true"); - } - + private final Map meta = new HashMap<>(); private final List events; - public Events(List events) { + public Events(List events, Map exporterMetadata) { this.events = new ArrayList<>(events); + this.meta.put("provider", "java"); + this.meta.put("openfeature", true); + if (exporterMetadata != null) { + this.meta.putAll(exporterMetadata); + } } } diff --git a/providers/go-feature-flag/src/test/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderTest.java b/providers/go-feature-flag/src/test/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderTest.java index ea086f466..61705d98c 100644 --- a/providers/go-feature-flag/src/test/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderTest.java +++ b/providers/go-feature-flag/src/test/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderTest.java @@ -26,6 +26,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import lombok.SneakyThrows; @@ -44,6 +45,7 @@ @Slf4j class GoFeatureFlagProviderTest { private int publishEventsRequestsReceived = 0; + private Map exporterMetadata; private int flagChangeCallCounter = 0; private boolean flagChanged404 = false; @@ -67,6 +69,7 @@ public MockResponse dispatch(RecordedRequest request) { String requestBody = request.getBody().readString(StandardCharsets.UTF_8); Map map = requestMapper.readValue(requestBody, Map.class); publishEventsRequestsReceived = ((List) map.get("events")).size(); + exporterMetadata = ((Map) map.get("meta")); if (requestBody.contains("fail_500") && publishEventsRequestsReceived == 1) { return new MockResponse().setResponseCode(502); } @@ -944,6 +947,44 @@ void should_stop_calling_flag_change_if_receive_404() { assertEquals(1, this.flagChangeCallCounter); } + @SneakyThrows + @Test + void should_send_exporter_metadata() { + Map customExporterMetadata = new HashMap<>(); + customExporterMetadata.put("version", "1.0.0"); + customExporterMetadata.put("intTest", 1234567890); + customExporterMetadata.put("doubleTest", 12345.67890); + GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder() + .endpoint(this.baseUrl.toString()) + .timeout(1000) + .enableCache(true) + .flushIntervalMs(150L) + .exporterMetadata(customExporterMetadata) + .build()); + String providerName = this.testName; + OpenFeatureAPI.getInstance().setProviderAndWait(providerName, g); + Client client = OpenFeatureAPI.getInstance().getClient(providerName); + client.getBooleanDetails("bool_targeting_match", false, this.evaluationContext); + client.getBooleanDetails("bool_targeting_match", false, this.evaluationContext); + client.getBooleanDetails("bool_targeting_match", false, this.evaluationContext); + client.getBooleanDetails("bool_targeting_match", false, this.evaluationContext); + client.getBooleanDetails("bool_targeting_match", false, this.evaluationContext); + client.getBooleanDetails("bool_targeting_match", false, this.evaluationContext); + client.getBooleanDetails("bool_targeting_match", false, this.evaluationContext); + client.getBooleanDetails("bool_targeting_match", false, this.evaluationContext); + client.getBooleanDetails("bool_targeting_match", false, this.evaluationContext); + Thread.sleep(150); + + Map want = new HashMap<>(); + want.put("version", "1.0.0"); + want.put("intTest", 1234567890); + want.put("doubleTest", 12345.6789); + want.put("openfeature",true); + want.put("provider", "java"); + assertEquals(want, this.exporterMetadata, + "we should have the exporter metadata in the last event sent to the data collector"); + } + private String readMockResponse(String filename) throws Exception { URL url = getClass().getClassLoader().getResource("mock_responses/" + filename); assert url != null;