Skip to content
Open
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 @@ -206,17 +206,19 @@ private <T> T coerce(Object value, Class<T> targetType) {

static Map<String, Object> convertContext(EvaluationContext ctx) {
Map<String, Object> context = new HashMap<>();
if (ctx == null) {
return context;
}
String targetingKey = ctx.getTargetingKey();
if (targetingKey != null && !targetingKey.isEmpty()) {
context.put("targetingKey", targetingKey);
}
for (String key : ctx.keySet()) {
Value val = ctx.getValue(key);
context.put(key, unwrapValue(val));
if (ctx != null) {
String targetingKey = ctx.getTargetingKey();
if (targetingKey != null && !targetingKey.isEmpty()) {
context.put("targetingKey", targetingKey);
}
for (String key : ctx.keySet()) {
Value val = ctx.getValue(key);
context.put(key, unwrapValue(val));
}
}
// SDK-448: marks exposures fired through this provider. Set after the
// customer attributes so it can't be overridden; older SDKs ignore the key.
context.put("$openfeature_provider", true);
return context;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,46 @@ public void testExplicitTargetingKeyAttributeOverriddenByGetTargetingKey() {
assertEquals("user-123", captured.get("distinct_id"));
}

@SuppressWarnings("unchecked")
@Test
public void testContextIncludesOpenFeatureProviderMarker() {
SelectedVariant<Object> variant = new SelectedVariant<>("on", true, null, null, null);
ArgumentCaptor<Map<String, Object>> contextCaptor = ArgumentCaptor.forClass(Map.class);
when(mockFlagsProvider.getVariant(eq("flag"), any(SelectedVariant.class), contextCaptor.capture(), eq(true)))
.thenReturn(variant);

Map<String, Value> attrs = new HashMap<>();
attrs.put("distinct_id", new Value("user-123"));
provider.getBooleanEvaluation("flag", false, new ImmutableContext(attrs));

Map<String, Object> captured = contextCaptor.getValue();
assertEquals(Boolean.TRUE, captured.get("$openfeature_provider"));
assertEquals("user-123", captured.get("distinct_id"));
}

@SuppressWarnings("unchecked")
@Test
public void testOpenFeatureProviderMarkerCannotBeOverriddenByCustomerAttribute() {
SelectedVariant<Object> variant = new SelectedVariant<>("on", true, null, null, null);
ArgumentCaptor<Map<String, Object>> contextCaptor = ArgumentCaptor.forClass(Map.class);
when(mockFlagsProvider.getVariant(eq("flag"), any(SelectedVariant.class), contextCaptor.capture(), eq(true)))
.thenReturn(variant);

Map<String, Value> attrs = new HashMap<>();
attrs.put("distinct_id", new Value("user-123"));
attrs.put("$openfeature_provider", new Value(false));
provider.getBooleanEvaluation("flag", false, new ImmutableContext(attrs));

Map<String, Object> captured = contextCaptor.getValue();
assertEquals(Boolean.TRUE, captured.get("$openfeature_provider"));
}

@Test
public void testConvertContextIncludesMarkerForNullContext() {
Map<String, Object> context = MixpanelProvider.convertContext(null);
assertEquals(Boolean.TRUE, context.get("$openfeature_provider"));
}

// PROVIDER_NOT_READY

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,22 @@ protected void trackExposure(String distinctId, String flagKey, String variantKe
}
}

/**
* Context key set by the Mixpanel OpenFeature provider (SDK-448). When present and
* {@code true}, exposure events are tagged with {@code "$openfeature_provider": true}.
*/
static final String OPENFEATURE_PROVIDER_KEY = "$openfeature_provider";

/**
* Adds {@code "$openfeature_provider": true} to the exposure properties when the
* evaluation context was built by the Mixpanel OpenFeature provider.
*/
static void addOpenFeatureProviderProperty(Map<String, Object> context, JSONObject properties) {
if (context != null && Boolean.TRUE.equals(context.get(OPENFEATURE_PROVIDER_KEY))) {
properties.put(OPENFEATURE_PROVIDER_KEY, true);
}
}

// #endregion

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@ private void trackLocalExposure(Map<String, Object> context, String flagKey, Str

trackExposure(distinctIdObj.toString(), flagKey, variantKey, "local", properties -> {
properties.put("Variant fetch latency (ms)", latencyMs);
addOpenFeatureProviderProperty(context, properties);
}, experimentId, isExperimentActive, isQaTester);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ private void trackRemoteExposure(Map<String, Object> context, String flagKey, St
trackExposure(distinctIdObj.toString(), flagKey, variantKey, "remote", properties -> {
properties.put("Variant fetch start time", startTime);
properties.put("Variant fetch complete time", completeTime);
addOpenFeatureProviderProperty(context, properties);
}, experimentId, isExperimentActive, isQaTester);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,42 @@ public void testTrackExposureWhenVariantIsSelected() {
assertTrue(event.properties.getLong("Variant fetch latency (ms)") >= 0);
}

@Test
public void testExposureIncludesOpenFeatureProviderPropertyWhenContextMarkerSet() {
List<Variant> variants = Arrays.asList(new Variant("variant-a", "value-a", false, 1.0f));
List<Rollout> rollouts = Arrays.asList(new Rollout(1.0f));
String response = buildFlagsResponse(flagKey, distinctIdContextKey, variants, rollouts, null);

provider = createProviderWithResponse(response);

Map<String, Object> context = buildContext("user-123");
context.put("$openfeature_provider", true);
provider.startPollingForDefinitions();
provider.getVariantValue(flagKey, fallbackVariantValue, context);

assertEquals(1, eventSender.getEvents().size());
MockEventSender.ExposureEvent event = eventSender.getEvents().get(eventSender.getEvents().size() - 1);
assertEquals("variant-a", event.properties.getString("Variant name"));
assertTrue(event.properties.getBoolean("$openfeature_provider"));
}

@Test
public void testExposureOmitsOpenFeatureProviderPropertyWithoutContextMarker() {
List<Variant> variants = Arrays.asList(new Variant("variant-a", "value-a", false, 1.0f));
List<Rollout> rollouts = Arrays.asList(new Rollout(1.0f));
String response = buildFlagsResponse(flagKey, distinctIdContextKey, variants, rollouts, null);

provider = createProviderWithResponse(response);

Map<String, Object> context = buildContext("user-123");
provider.startPollingForDefinitions();
provider.getVariantValue(flagKey, fallbackVariantValue, context);

assertEquals(1, eventSender.getEvents().size());
MockEventSender.ExposureEvent event = eventSender.getEvents().get(eventSender.getEvents().size() - 1);
assertFalse(event.properties.has("$openfeature_provider"));
}

@Test
public void testDoNotTrackExposureWhenReturningFallback() {
// Empty flags - will return fallback
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,34 @@ public void testTrackExposureWhenVariantIsSelected() {
assertNotNull(event.properties.getString("Variant fetch complete time"));
}

@Test
public void testExposureIncludesOpenFeatureProviderPropertyWhenContextMarkerSet() {
String response = buildRemoteFlagsResponse("test-flag", "variant-a", "test-value");
provider = createProviderWithResponse(response);

Map<String, Object> context = buildContext("user-123");
context.put("$openfeature_provider", true);
provider.getVariantValue("test-flag", "fallback", context);

assertEquals(1, eventSender.getEvents().size());
MockEventSender.ExposureEvent event = eventSender.getEvents().get(eventSender.getEvents().size() - 1);
assertEquals("variant-a", event.properties.getString("Variant name"));
assertTrue(event.properties.getBoolean("$openfeature_provider"));
}

@Test
public void testExposureOmitsOpenFeatureProviderPropertyWithoutContextMarker() {
String response = buildRemoteFlagsResponse("test-flag", "variant-a", "test-value");
provider = createProviderWithResponse(response);

Map<String, Object> context = buildContext("user-123");
provider.getVariantValue("test-flag", "fallback", context);

assertEquals(1, eventSender.getEvents().size());
MockEventSender.ExposureEvent event = eventSender.getEvents().get(eventSender.getEvents().size() - 1);
assertFalse(event.properties.has("$openfeature_provider"));
}

@Test
public void testDoNotTrackExposureWhenReturningFallback() {
// Create provider that will throw IOException
Expand Down
Loading