Skip to content

Commit

Permalink
feat(go-feature-flag): Support Exporter Metadata
Browse files Browse the repository at this point in the history
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
  • Loading branch information
thomaspoignant committed Jan 20, 2025
1 parent 969448a commit 08c2306
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String, Object> exporterMetadata;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -69,6 +72,9 @@ public class GoFeatureFlagController {

private final HttpUrl parsedEndpoint;

/** exporterMetadata contains the metadata to send to the collector API. */
private Map<String, Object> exporterMetadata = new HashMap<>();

/**
* etag contains the etag of the configuration, if null, it means that the configuration has never
* been retrieved.
Expand All @@ -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) {
Expand Down Expand Up @@ -218,7 +225,7 @@ public <T> EvaluationResponse<T> evaluateFlag(
*/
public void sendEventToDataCollector(List<Event> eventsList) {
try {
Events events = new Events(eventsList);
Events events = new Events(eventsList, this.exporterMetadata);
HttpUrl url = this.parsedEndpoint
.newBuilder()
.addEncodedPathSegment("v1")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@
/** Events data. */
@Getter
public class Events {
private static final Map<String, String> meta = new HashMap<>();

static {
meta.put("provider", "java");
meta.put("openfeature", "true");
}

private final Map<String, Object> meta = new HashMap<>();
private final List<Event> events;

public Events(List<Event> events) {
public Events(List<Event> events, Map<String, Object> exporterMetadata) {
this.events = new ArrayList<>(events);
this.meta.put("provider", "java");
this.meta.put("openfeature", true);
if (exporterMetadata != null) {
this.meta.putAll(exporterMetadata);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -44,6 +45,7 @@
@Slf4j
class GoFeatureFlagProviderTest {
private int publishEventsRequestsReceived = 0;
private Map exporterMetadata;
private int flagChangeCallCounter = 0;
private boolean flagChanged404 = false;

Expand All @@ -67,6 +69,7 @@ public MockResponse dispatch(RecordedRequest request) {
String requestBody = request.getBody().readString(StandardCharsets.UTF_8);
Map<String, Object> 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);
}
Expand Down Expand Up @@ -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<String, Object> 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<String, Object> 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;
Expand Down

0 comments on commit 08c2306

Please sign in to comment.