Skip to content

Commit 13ad91f

Browse files
feat: add tracking support (#36)
**Requirements** - [x] I have added test coverage for new or changed functionality - [x] I have followed the repository's [pull request submission guidelines](../blob/main/CONTRIBUTING.md#submitting-pull-requests) - [x] I have validated my changes against all supported platform versions **Related issues** https://launchdarkly.atlassian.net/browse/SDK-1115 https://openfeature.dev/specification/sections/tracking#61-tracking-api https://github.com/open-feature/java-sdk/blob/main/CHANGELOG.md#1130-2024-12-07 **Describe the solution you've provided** From openfeature sdk v1.7.0 till now (v1.14.1), [tracking](https://github.com/open-feature/java-sdk/blob/main/CHANGELOG.md#1130-2024-12-07) seems to be only change needed from my first glance, so I implement that in the provider. <!-- ld-jira-link --> --- Related Jira issue: [SDK-1115: OpenFeature Tracking Support - Java](https://launchdarkly.atlassian.net/browse/SDK-1115) <!-- end-ld-jira-link -->
2 parents 25ad7d1 + fa69598 commit 13ad91f

File tree

4 files changed

+152
-25
lines changed

4 files changed

+152
-25
lines changed

build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,14 @@ nexusPublishing {
107107
dependencies {
108108
implementation group: 'com.launchdarkly', name: 'launchdarkly-java-server-sdk', version: '[7.1.0, 8.0.0)'
109109

110-
implementation 'dev.openfeature:sdk:[1.7.0,2.0.0)'
110+
implementation 'dev.openfeature:sdk:[1.14.0,2.0.0)'
111111

112112
// Use JUnit test framework
113113
testImplementation(platform('org.junit:junit-bom:5.10.0'))
114114
testImplementation('org.junit.jupiter:junit-jupiter')
115115
testImplementation "org.mockito:mockito-core:3.+"
116+
117+
testImplementation 'org.awaitility:awaitility:4.2.0'
116118
}
117119

118120
test {

src/main/java/com/launchdarkly/openfeature/serverprovider/Provider.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,38 @@ public void shutdown() {
222222
}
223223
}
224224

225+
@Override
226+
public void track(String eventName, EvaluationContext ctx, TrackingEventDetails details) {
227+
if (ctx == null) {
228+
logger.info(
229+
"The 'track' method was called with a null EvaluationContext. " +
230+
"No 'track' event will be sent to LaunchDarkly. " +
231+
"The LaunchDarkly SDK requires a non-null EvaluationContext.");
232+
return;
233+
}
234+
235+
if (details != null) {
236+
Double metricValue = null;
237+
if (details.getValue().isPresent()) {
238+
metricValue = (Double) details.getValue().get();
239+
}
240+
// Convert the Structure portion of the TrackingEventDetails into a key value
241+
// map.
242+
// This will not put the metricValue extracted above into the map.
243+
LDValue data = valueConverter.toLdValue(new Value(details));
244+
245+
if (metricValue != null) {
246+
client.trackMetric(eventName, evaluationContextConverter.toLdContext(ctx), data, metricValue);
247+
} else if (!data.isNull() && data.size() > 0) {
248+
client.trackData(eventName, evaluationContextConverter.toLdContext(ctx), data);
249+
} else {
250+
client.track(eventName, evaluationContextConverter.toLdContext(ctx));
251+
}
252+
} else {
253+
client.track(eventName, evaluationContextConverter.toLdContext(ctx));
254+
}
255+
}
256+
225257
/**
226258
* Get the LaunchDarkly client associated with this provider.
227259
* <p>

src/main/java/com/launchdarkly/openfeature/serverprovider/Version.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ abstract class Version {
44
private Version() {}
55

66
// This constant is updated automatically by our Gradle script during a release, if the project version has changed
7-
static final String SDK_VERSION = "0.2.0";
7+
static final String SDK_VERSION = "1.0.1";
88
}

src/test/java/com/launchdarkly/openfeature/serverprovider/ProviderTest.java

Lines changed: 116 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
import org.junit.jupiter.api.Test;
1010

1111
import static org.mockito.Mockito.*;
12+
13+
import org.awaitility.Awaitility;
14+
1215
import static org.junit.jupiter.api.Assertions.*;
1316

1417
public class ProviderTest {
@@ -31,19 +34,24 @@ public void itCanDoABooleanEvaluation() {
3134
EvaluationContext evaluationContext = new ImmutableContext("user-key");
3235

3336
when(mockedLdClient.boolVariationDetail("the-key", LDContext.create("user-key"), false))
34-
.thenReturn(EvaluationDetail.fromValue(true, 12, EvaluationReason.fallthrough()));
37+
.thenReturn(EvaluationDetail.fromValue(true, 12, EvaluationReason.fallthrough()));
3538

3639
OpenFeatureAPI.getInstance().setProvider(ldProvider);
3740

41+
Awaitility.await().forever().until(() -> OpenFeatureAPI
42+
.getInstance()
43+
.getClient()
44+
.getBooleanValue("the-key", false, evaluationContext));
45+
3846
assertTrue(OpenFeatureAPI
39-
.getInstance()
40-
.getClient()
41-
.getBooleanValue("the-key", false, evaluationContext));
47+
.getInstance()
48+
.getClient()
49+
.getBooleanValue("the-key", false, evaluationContext));
4250

4351
FlagEvaluationDetails<Boolean> detailed = OpenFeatureAPI
44-
.getInstance()
45-
.getClient()
46-
.getBooleanDetails("the-key", false, evaluationContext);
52+
.getInstance()
53+
.getClient()
54+
.getBooleanDetails("the-key", false, evaluationContext);
4755

4856
assertEquals(true, detailed.getValue());
4957
assertEquals("12", detailed.getVariant());
@@ -55,20 +63,25 @@ public void itCanDoAStringEvaluation() {
5563
EvaluationContext evaluationContext = new ImmutableContext("user-key");
5664

5765
when(mockedLdClient.stringVariationDetail("the-key", LDContext.create("user-key"), "default"))
58-
.thenReturn(EvaluationDetail
59-
.fromValue("evaluated", 17, EvaluationReason.off()));
66+
.thenReturn(EvaluationDetail
67+
.fromValue("evaluated", 17, EvaluationReason.off()));
6068

6169
OpenFeatureAPI.getInstance().setProvider(ldProvider);
6270

71+
Awaitility.await().forever().until(() -> OpenFeatureAPI
72+
.getInstance()
73+
.getClient()
74+
.getStringValue("the-key", "default", evaluationContext).equals("evaluated"));
75+
6376
assertEquals("evaluated", OpenFeatureAPI
64-
.getInstance()
65-
.getClient()
66-
.getStringValue("the-key", "default", evaluationContext));
77+
.getInstance()
78+
.getClient()
79+
.getStringValue("the-key", "default", evaluationContext));
6780

6881
FlagEvaluationDetails<String> detailed = OpenFeatureAPI
69-
.getInstance()
70-
.getClient()
71-
.getStringDetails("the-key", "default", evaluationContext);
82+
.getInstance()
83+
.getClient()
84+
.getStringDetails("the-key", "default", evaluationContext);
7285

7386
assertEquals("evaluated", detailed.getValue());
7487
assertEquals("17", detailed.getVariant());
@@ -80,18 +93,24 @@ public void itCanDoADoubleEvaluation() {
8093
EvaluationContext evaluationContext = new ImmutableContext("user-key");
8194

8295
when(mockedLdClient.doubleVariationDetail("the-key", LDContext.create("user-key"), 0.0))
83-
.thenReturn(EvaluationDetail.fromValue(1.0, 42, EvaluationReason.targetMatch()));
96+
.thenReturn(EvaluationDetail.fromValue(1.0, 42, EvaluationReason.targetMatch()));
8497

8598
OpenFeatureAPI.getInstance().setProvider(ldProvider);
99+
100+
Awaitility.await().forever().until(() -> OpenFeatureAPI
101+
.getInstance()
102+
.getClient()
103+
.getDoubleValue("the-key", 0.0, evaluationContext) != 0.0);
104+
86105
assertEquals(1.0, OpenFeatureAPI
87-
.getInstance()
88-
.getClient()
89-
.getDoubleValue("the-key", 0.0, evaluationContext), 0.00001);
106+
.getInstance()
107+
.getClient()
108+
.getDoubleValue("the-key", 0.0, evaluationContext), 0.00001);
90109

91110
FlagEvaluationDetails<Double> detailed = OpenFeatureAPI
92-
.getInstance()
93-
.getClient()
94-
.getDoubleDetails("the-key", 0.0, evaluationContext);
111+
.getInstance()
112+
.getClient()
113+
.getDoubleDetails("the-key", 0.0, evaluationContext);
95114

96115
assertEquals(1.0, detailed.getValue(), 0.00001);
97116
assertEquals("42", detailed.getVariant());
@@ -122,8 +141,82 @@ public void itCanDoAValueEvaluation() {
122141
.getObjectDetails("the-key", new Value(), evaluationContext);
123142

124143
assertEquals("aValue", detailed.getValue().asStructure().getValue("aKey").asString());
125-
126144
assertEquals("84", detailed.getVariant());
127145
assertEquals("TARGETING_MATCH", detailed.getReason());
128146
}
147+
148+
@Test
149+
public void itCanTrackNoTrackingEventDetails() {
150+
EvaluationContext evaluationContext = new ImmutableContext("user-key");
151+
EvaluationContextConverter evaluationContextConverter = new EvaluationContextConverter(null);
152+
153+
OpenFeatureAPI.getInstance().setProvider(ldProvider);
154+
155+
OpenFeatureAPI
156+
.getInstance()
157+
.getClient().track("metric-key", evaluationContext);
158+
159+
verify(mockedLdClient).track(
160+
"metric-key",
161+
evaluationContextConverter.toLdContext(evaluationContext));
162+
}
163+
164+
@Test
165+
public void itCanTrackEmptyTrackingEventDetails() {
166+
EvaluationContext evaluationContext = new ImmutableContext("user-key");
167+
EvaluationContextConverter evaluationContextConverter = new EvaluationContextConverter(null);
168+
169+
TrackingEventDetails trackingEventDetails = new MutableTrackingEventDetails(null);
170+
171+
OpenFeatureAPI.getInstance().setProvider(ldProvider);
172+
173+
OpenFeatureAPI
174+
.getInstance()
175+
.getClient().track("metric-key", evaluationContext, trackingEventDetails);
176+
177+
verify(mockedLdClient).track(
178+
"metric-key",
179+
evaluationContextConverter.toLdContext(evaluationContext));
180+
}
181+
182+
@Test
183+
public void itCanTrackTrackingEventDetailsWithNoValue() {
184+
EvaluationContext evaluationContext = new ImmutableContext("user-key");
185+
EvaluationContextConverter evaluationContextConverter = new EvaluationContextConverter(null);
186+
ValueConverter valueConverter = new ValueConverter(null);
187+
188+
TrackingEventDetails trackingEventDetails = new MutableTrackingEventDetails(null).add("currency", "USD");
189+
190+
OpenFeatureAPI.getInstance().setProvider(ldProvider);
191+
192+
OpenFeatureAPI
193+
.getInstance()
194+
.getClient().track("metric-key", evaluationContext, trackingEventDetails);
195+
196+
verify(mockedLdClient).trackData(
197+
"metric-key",
198+
evaluationContextConverter.toLdContext(evaluationContext),
199+
valueConverter.toLdValue(new Value(trackingEventDetails)));
200+
}
201+
202+
@Test
203+
public void itCanTrackFullTrackingEventDetails() {
204+
EvaluationContext evaluationContext = new ImmutableContext("user-key");
205+
EvaluationContextConverter evaluationContextConverter = new EvaluationContextConverter(null);
206+
ValueConverter valueConverter = new ValueConverter(null);
207+
208+
TrackingEventDetails trackingEventDetails = new MutableTrackingEventDetails(99.77).add("currency", "USD");
209+
210+
OpenFeatureAPI.getInstance().setProvider(ldProvider);
211+
212+
OpenFeatureAPI
213+
.getInstance()
214+
.getClient().track("metric-key", evaluationContext, trackingEventDetails);
215+
216+
verify(mockedLdClient).trackMetric(
217+
"metric-key",
218+
evaluationContextConverter.toLdContext(evaluationContext),
219+
valueConverter.toLdValue(new Value(trackingEventDetails)),
220+
99.77);
221+
}
129222
}

0 commit comments

Comments
 (0)