Skip to content

feat: Support inline context for custom and migration events #55

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

Merged
merged 4 commits into from
May 8, 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
4 changes: 2 additions & 2 deletions lib/shared/common/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ If you wish to clean your working directory between builds, you can clean it by
./gradlew clean
```

If you wish to use your generated SDK artifact by another Maven/Gradle project such as [java-server-sdk](https://github.com/launchdarkly/java-server-sdk), you will likely want to publish the artifact to your local Maven repository so that your other project can access it.
If you wish to use your generated SDK artifact by another Maven/Gradle project such as [java-server-sdk](https://github.com/launchdarkly/java-server-sdk), you will likely want to publish the artifact to your local Maven repository so that your other project can access it. You can append `-SNAPSHOT` to the version in [gradle.properties](gradle.properties) and update any dependancies to test the snapshot version.
```
./gradlew publishToMavenLocal
./gradlew publishToMavenLocal -PskipSigning=true
```

### Testing
Expand Down
1 change: 1 addition & 0 deletions lib/shared/common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,6 @@ nexusPublishing {
}

signing {
setRequired({ findProperty("skipSigning") != "true" })
sign(publishing.publications["mavenJava"])
}
4 changes: 2 additions & 2 deletions lib/shared/internal/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ If you wish to clean your working directory between builds, you can clean it by
./gradlew clean
```

If you wish to use your generated SDK artifact by another Maven/Gradle project such as [java-server-sdk](https://github.com/launchdarkly/java-server-sdk), you will likely want to publish the artifact to your local Maven repository so that your other project can access it.
If you wish to use your generated SDK artifact by another Maven/Gradle project such as [java-server-sdk](https://github.com/launchdarkly/java-server-sdk), you will likely want to publish the artifact to your local Maven repository so that your other project can access it. You can append `-SNAPSHOT` to the version in [gradle.properties](gradle.properties) and update any dependancies to test the snapshot version.
```
./gradlew publishToMavenLocal
./gradlew publishToMavenLocal -PskipSigning=true
```

### Testing
Expand Down
1 change: 1 addition & 0 deletions lib/shared/internal/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,6 @@ nexusPublishing {
}

signing {
setRequired({ findProperty("skipSigning") != "true" })
sign(publishing.publications["mavenJava"])
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ private boolean writeOutputEvent(Event event, JsonWriter jw) throws IOException
jw.beginObject();
writeKindAndCreationDate(jw, "custom", event.getCreationDate());
jw.name("key").value(ce.getKey());
writeContextKeys(ce.getContext(), jw);
writeContext(ce.getContext(), jw, false);
writeLDValue("data", ce.getData(), jw);
if (ce.getMetricValue() != null) {
jw.name("metricValue");
Expand All @@ -104,7 +104,7 @@ private boolean writeOutputEvent(Event event, JsonWriter jw) throws IOException
} else if (event instanceof Event.MigrationOp) {
jw.beginObject();
writeKindAndCreationDate(jw, "migration_op", event.getCreationDate());
writeContextKeys(event.getContext(), jw);
writeContext(event.getContext(), jw, false);

Event.MigrationOp me = (Event.MigrationOp)event;
jw.name("operation").value(me.getOperation());
Expand Down Expand Up @@ -296,17 +296,6 @@ private void writeContext(LDContext context, JsonWriter jw, boolean redactAnonym
contextFormatter.write(context, jw, redactAnonymous);
}

private void writeContextKeys(LDContext context, JsonWriter jw) throws IOException {
jw.name("contextKeys").beginObject();
for (int i = 0; i < context.getIndividualContextCount(); i++) {
LDContext c = context.getIndividualContext(i);
if (c != null) {
jw.name(c.getKind().toString()).value(c.getKey());
}
}
jw.endObject();
}

private void writeLDValue(String key, LDValue value, JsonWriter jw) throws IOException {
if (value == null || value.isNull()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,12 @@ public static Matcher<JsonTestValue> isIdentifyEvent(Event sourceEvent, LDValue
);
}

public static Matcher<JsonTestValue> isMigrationEvent(Event sourceEvent, LDValue context) {
public static Matcher<JsonTestValue> isMigrationEvent(Event sourceEvent, LDValue inlineContext) {
// Doesn't fully test an event, but makes sure it is a specific event.
return allOf(
jsonProperty("kind", "migration_op"),
jsonProperty("creationDate", (double)sourceEvent.getCreationDate()),
hasContextKeys(sourceEvent)
hasInlineContext(inlineContext)
);
}

Expand Down Expand Up @@ -248,28 +248,18 @@ private static Matcher<JsonTestValue> isFeatureOrDebugEvent(Event.FeatureRequest
);
}

public static Matcher<JsonTestValue> isCustomEvent(Event.Custom sourceEvent) {
public static Matcher<JsonTestValue> isCustomEvent(Event.Custom sourceEvent, LDValue inlineContext) {
boolean hasData = sourceEvent.getData() != null && !sourceEvent.getData().isNull();
return allOf(
jsonProperty("kind", "custom"),
jsonProperty("creationDate", (double)sourceEvent.getCreationDate()),
jsonProperty("key", sourceEvent.getKey()),
hasContextKeys(sourceEvent),
hasInlineContext(inlineContext),
jsonProperty("data", hasData ? jsonEqualsValue(sourceEvent.getData()) : jsonUndefined()),
jsonProperty("metricValue", sourceEvent.getMetricValue() == null ? jsonUndefined() : jsonEqualsValue(sourceEvent.getMetricValue()))
);
}

public static Matcher<JsonTestValue> hasContextKeys(Event sourceEvent) {
ObjectBuilder b = LDValue.buildObject();
LDContext c = sourceEvent.getContext();
for (int i = 0; i < c.getIndividualContextCount(); i++) {
LDContext c1 = c.getIndividualContext(i);
b.put(c1.getKind().toString(), c1.getKey());
}
return jsonProperty("contextKeys", jsonEqualsValue(b.build()));
}

public static Matcher<JsonTestValue> hasInlineContext(LDValue inlineContext) {
return allOf(
jsonProperty("context", jsonEqualsValue(inlineContext)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ public void customEventIsQueuedWithUser() throws Exception {
}

assertThat(es.getEventsFromLastRequest(), contains(
isCustomEvent(ce)
isCustomEvent(ce, userJson)
));
}

Expand All @@ -419,7 +419,7 @@ public void customEventWithNullContextOrInvalidContextDoesNotCauseError() throws
}

assertThat(es.getEventsFromLastRequest(), contains(
isCustomEvent(event3)
isCustomEvent(event3, userJson)
));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ public void eventsAreFlushedAutomatically() throws Exception {
// sendEvent calls, they might be in two
List<JsonTestValue> payload1 = es.getEventsFromLastRequest();
if (payload1.size() == 1) {
assertThat(payload1, contains(isCustomEvent(event1)));
assertThat(es.getEventsFromLastRequest(), contains(isCustomEvent(event2)));
assertThat(payload1, contains(isCustomEvent(event1, userJson)));
assertThat(es.getEventsFromLastRequest(), contains(isCustomEvent(event2, userJson)));
} else {
assertThat(payload1, contains(isCustomEvent(event1), isCustomEvent(event2)));
assertThat(payload1, contains(isCustomEvent(event1, userJson), isCustomEvent(event2, userJson)));
}

Event.Custom event3 = customEvent(user, "event3").build();
ep.sendEvent(event3);
assertThat(es.getEventsFromLastRequest(), contains(isCustomEvent(event3)));
assertThat(es.getEventsFromLastRequest(), contains(isCustomEvent(event3, userJson)));
}

Event.Custom ce = customEvent(user, "eventkey").build();
Expand All @@ -62,7 +62,7 @@ public void eventsAreFlushedAutomatically() throws Exception {
}

assertThat(es.getEventsFromLastRequest(), contains(
isCustomEvent(ce)
isCustomEvent(ce, userJson)
));
}

Expand All @@ -85,7 +85,7 @@ public void eventsAreNotFlushedWhenNotConnected() throws Exception {
ep.setOffline(false);

List<JsonTestValue> payload1 = es.getEventsFromLastRequest();
assertThat(payload1, contains(isCustomEvent(event1), isCustomEvent(event2)));
assertThat(payload1, contains(isCustomEvent(event1, userJson), isCustomEvent(event2, userJson)));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,26 +45,6 @@ public void allAttributesAreSerialized() throws Exception {
defaultEventsConfig());
}

@Test
public void contextKeysAreSetInsteadOfContextWhenNotInlined() throws Exception {
testContextKeysSerialization(
LDContext.create("userkey"),
LDValue.buildObject().put("user", "userkey").build()
);

testContextKeysSerialization(
LDContext.create(ContextKind.of("kind1"), "key1"),
LDValue.buildObject().put("kind1", "key1").build()
);

testContextKeysSerialization(
LDContext.createMulti(
LDContext.create(ContextKind.of("kind1"), "key1"),
LDContext.create(ContextKind.of("kind2"), "key2")),
LDValue.buildObject().put("kind1", "key1").put("kind2", "key2").build()
);
}

@Test
public void allAttributesPrivateMakesAttributesPrivate() throws Exception {
// We test this behavior in more detail in EventContextFormatterTest, but here we're verifying that the
Expand Down Expand Up @@ -265,15 +245,15 @@ public void identifyEventIsSerialized() throws IOException {
@Test
public void customEventIsSerialized() throws IOException {
LDContext context = LDContext.builder("userkey").name("me").build();
LDValue contextKeysJson = LDValue.buildObject().put("user", context.getKey()).build();
LDValue contextJson = LDValue.buildObject().put("kind", "user").put("key", "userkey").put("name", "me").build();
EventOutputFormatter f = new EventOutputFormatter(defaultEventsConfig());

Event.Custom ceWithoutData = customEvent(context, "customkey").build();
LDValue ceJson1 = parseValue("{" +
"\"kind\":\"custom\"," +
"\"creationDate\":100000," +
"\"key\":\"customkey\"," +
"\"contextKeys\":" + contextKeysJson +
"\"context\":" + contextJson +
"}");
assertJsonEquals(ceJson1, getSingleOutputEvent(f, ceWithoutData));

Expand All @@ -282,7 +262,7 @@ public void customEventIsSerialized() throws IOException {
"\"kind\":\"custom\"," +
"\"creationDate\":100000," +
"\"key\":\"customkey\"," +
"\"contextKeys\":" + contextKeysJson + "," +
"\"context\":" + contextJson + "," +
"\"data\":\"thing\"" +
"}");
assertJsonEquals(ceJson2, getSingleOutputEvent(f, ceWithData));
Expand All @@ -292,7 +272,7 @@ public void customEventIsSerialized() throws IOException {
"\"kind\":\"custom\"," +
"\"creationDate\":100000," +
"\"key\":\"customkey\"," +
"\"contextKeys\":" + contextKeysJson + "," +
"\"context\":" + contextJson + "," +
"\"metricValue\":2.5" +
"}");
assertJsonEquals(ceJson3, getSingleOutputEvent(f, ceWithMetric));
Expand All @@ -303,7 +283,7 @@ public void customEventIsSerialized() throws IOException {
"\"kind\":\"custom\"," +
"\"creationDate\":100000," +
"\"key\":\"customkey\"," +
"\"contextKeys\":" + contextKeysJson + "," +
"\"context\":" + contextJson + "," +
"\"data\":\"thing\"," +
"\"metricValue\":2.5" +
"}");
Expand Down Expand Up @@ -408,8 +388,10 @@ public void migrationOpEventIsSerialized() throws IOException {
.put("reason", LDValue.buildObject()
.put("kind", "FALLTHROUGH")
.build()).build())
.put("contextKeys", LDValue.buildObject()
.put("user", "user-key")
.put("context", LDValue.buildObject()
.put("kind", "user")
.put("key", "user-key")
.put("name", "me")
.build())
.put("samplingRatio", 2)
.put("measurements", LDValue.buildArray()
Expand Down Expand Up @@ -495,8 +477,10 @@ public void migrationOpEventSerializationCanExcludeOptionalItems() throws IOExce
.put("reason", LDValue.buildObject()
.put("kind", "FALLTHROUGH")
.build()).build())
.put("contextKeys", LDValue.buildObject()
.put("user", "user-key")
.put("context", LDValue.buildObject()
.put("kind", "user")
.put("key", "user-key")
.put("name", "me")
.build())
.put("measurements", LDValue.buildArray()
.add(LDValue.buildObject()
Expand Down Expand Up @@ -705,25 +689,19 @@ private LDValue getSingleOutputEvent(EventOutputFormatter f, Event event) throws
return parseValue(w.toString()).get(0);
}

private void testContextKeysSerialization(LDContext context, LDValue expectedJsonValue) throws IOException {
EventsConfiguration config = makeEventsConfig(false, null);
EventOutputFormatter f = new EventOutputFormatter(config);

Event.Custom customEvent = customEvent(context, "eventkey").build();
LDValue outputEvent = getSingleOutputEvent(f, customEvent);
assertJsonEquals(expectedJsonValue, outputEvent.get("contextKeys"));
assertJsonEquals(LDValue.ofNull(), outputEvent.get("context"));
}

private void testInlineContextSerialization(LDContext context, LDValue expectedJsonValue, EventsConfiguration baseConfig) throws IOException {
EventsConfiguration config = makeEventsConfig(baseConfig.allAttributesPrivate, baseConfig.privateAttributes);
EventOutputFormatter f = new EventOutputFormatter(config);

Event.FeatureRequest featureEvent = featureEvent(context, FLAG_KEY).build();
LDValue outputEvent = getSingleOutputEvent(f, featureEvent);
Event.Custom customEvent = customEvent(context, FLAG_KEY).build();
LDValue outputEvent = getSingleOutputEvent(f, customEvent);
assertJsonEquals(LDValue.ofNull(), outputEvent.get("contextKeys"));
assertJsonEquals(expectedJsonValue, outputEvent.get("context"));

Event.FeatureRequest featureEvent = featureEvent(context, FLAG_KEY).build();
outputEvent = getSingleOutputEvent(f, featureEvent);
assertJsonEquals(LDValue.ofNull(), outputEvent.get("contextKeys"));
assertJsonEquals(expectedJsonValue, outputEvent.get("context"));

Event.Identify identifyEvent = identifyEvent(context);
outputEvent = getSingleOutputEvent(f, identifyEvent);
Expand Down