Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(go-feature-flag): Support Exporter Metadata #1167

Merged
merged 2 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.github.benmanes.caffeine.cache.Caffeine;
import dev.openfeature.sdk.ProviderEvaluation;
import java.util.Map;
import lombok.Builder;
import lombok.Getter;

Expand Down Expand Up @@ -90,4 +91,12 @@ 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,9 @@
import dev.openfeature.sdk.exceptions.TypeMismatchError;
import java.io.IOException;
import java.net.HttpURLConnection;
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 +71,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 +90,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 +224,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,28 @@
/** Events data. */
@Getter
public class Events {
private static final Map<String, String> meta = new HashMap<>();

static {
meta.put("provider", "java");
meta.put("openfeature", "true");
}
/**
* meta contains the metadata of the events to be sent along the events.
*/
private final Map<String, Object> meta = new HashMap<>();

/**
* list of events to be sent to the data collector to collect the evaluation data.
*/
private final List<Event> events;

public Events(List<Event> events) {
/**
* Constructor.
*
* @param events - list of events to be sent to the data collector to collect the evaluation data.
* @param exporterMetadata - metadata of the events to be sent along the 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,46 @@ 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
Loading