Skip to content

Commit 64ad644

Browse files
authored
feat: add tracking as per spec (#1228)
feat: add tracking as per spec --------- Signed-off-by: Bernd Warmuth <[email protected]>
1 parent c5ad1b4 commit 64ad644

12 files changed

+576
-36
lines changed

README.md

+22-11
Original file line numberDiff line numberDiff line change
@@ -120,17 +120,18 @@ See [here](https://javadoc.io/doc/dev.openfeature/sdk/latest/) for the Javadocs.
120120

121121
## 🌟 Features
122122

123-
| Status | Features | Description |
124-
| ------ |-----------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
125-
|| [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. |
126-
|| [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
127-
|| [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
128-
|| [Logging](#logging) | Integrate with popular logging packages. |
129-
|| [Domains](#domains) | Logically bind clients with providers. |
130-
|| [Eventing](#eventing) | React to state changes in the provider or flag management system. |
131-
|| [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
132-
|| [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context) for a transaction (e.g. an HTTP request or a thread). |
133-
|| [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |
123+
| Status | Features | Description |
124+
| ------ |---------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
125+
|| [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. |
126+
|| [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
127+
|| [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
128+
|| [Tracking](#tracking) | Associate user actions with feature flag evaluations. |
129+
|| [Logging](#logging) | Integrate with popular logging packages. |
130+
|| [Domains](#domains) | Logically bind clients with providers. |
131+
|| [Eventing](#eventing) | React to state changes in the provider or flag management system. |
132+
|| [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
133+
|| [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context) for a transaction (e.g. an HTTP request or a thread). |
134+
|| [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |
134135

135136
<sub>Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌</sub>
136137

@@ -215,6 +216,16 @@ Once you've added a hook as a dependency, it can be registered at the global, cl
215216
FlagEvaluationOptions.builder().hook(new ExampleHook()).build());
216217
```
217218

219+
### Tracking
220+
221+
The [tracking API](https://openfeature.dev/specification/sections/tracking/) allows you to use OpenFeature abstractions to associate user actions with feature flag evaluations.
222+
This is essential for robust experimentation powered by feature flags. Note that, unlike methods that handle feature flag evaluations, calling `track(...)` may throw an `IllegalArgumentException` if an empty string is passed as the `trackingEventName`.
223+
224+
```java
225+
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
226+
api.getClient().track("visited-promo-page", new MutableTrackingEventDetails(99.77).add("currency", "USD"));
227+
```
228+
218229
### Logging
219230

220231
The Java SDK uses SLF4J. See the [SLF4J manual](https://slf4j.org/manual.html) for complete documentation.

src/main/java/dev/openfeature/sdk/Client.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@
55
/**
66
* Interface used to resolve flags of varying types.
77
*/
8-
public interface Client extends Features, EventBus<Client> {
8+
public interface Client extends Features, Tracking, EventBus<Client> {
99
ClientMetadata getMetadata();
1010

1111
/**
1212
* Return an optional client-level evaluation context.
13+
*
1314
* @return {@link EvaluationContext}
1415
*/
1516
EvaluationContext getEvaluationContext();
1617

1718
/**
1819
* Set the client-level evaluation context.
20+
*
1921
* @param ctx Client level context.
2022
*/
2123
Client setEvaluationContext(EvaluationContext ctx);
@@ -30,12 +32,14 @@ public interface Client extends Features, EventBus<Client> {
3032

3133
/**
3234
* Fetch the hooks associated to this client.
35+
*
3336
* @return A list of {@link Hook}s.
3437
*/
3538
List<Hook> getHooks();
3639

3740
/**
3841
* Returns the current state of the associated provider.
42+
*
3943
* @return the provider state
4044
*/
4145
ProviderState getProviderState();

src/main/java/dev/openfeature/sdk/FeatureProvider.java

+10
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,14 @@ default ProviderState getState() {
7171
return ProviderState.READY;
7272
}
7373

74+
/**
75+
* Feature provider implementations can opt in for to support Tracking by implementing this method.
76+
*
77+
* @param eventName The name of the tracking event
78+
* @param context Evaluation context used in flag evaluation (Optional)
79+
* @param details Data pertinent to a particular tracking event (Optional)
80+
*/
81+
default void track(String eventName, EvaluationContext context, TrackingEventDetails details) {
82+
83+
}
7484
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package dev.openfeature.sdk;
2+
3+
import dev.openfeature.sdk.internal.ExcludeFromGeneratedCoverageReport;
4+
import lombok.experimental.Delegate;
5+
6+
import java.util.Map;
7+
import java.util.Optional;
8+
import java.util.function.Function;
9+
10+
11+
/**
12+
* ImmutableTrackingEventDetails represents data pertinent to a particular tracking event.
13+
*/
14+
public class ImmutableTrackingEventDetails implements TrackingEventDetails {
15+
16+
@Delegate(excludes = DelegateExclusions.class)
17+
private final ImmutableStructure structure;
18+
19+
private final Number value;
20+
21+
public ImmutableTrackingEventDetails() {
22+
this.value = null;
23+
this.structure = new ImmutableStructure();
24+
}
25+
26+
public ImmutableTrackingEventDetails(final Number value) {
27+
this.value = value;
28+
this.structure = new ImmutableStructure();
29+
}
30+
31+
public ImmutableTrackingEventDetails(final Number value, final Map<String, Value> attributes) {
32+
this.value = value;
33+
this.structure = new ImmutableStructure(attributes);
34+
}
35+
36+
/**
37+
* Returns the optional tracking value.
38+
*/
39+
public Optional<Number> getValue() {
40+
return Optional.ofNullable(value);
41+
}
42+
43+
44+
@SuppressWarnings("all")
45+
private static class DelegateExclusions {
46+
@ExcludeFromGeneratedCoverageReport
47+
public <T extends Structure> Map<String, Value> merge(Function<Map<String, Value>, Structure> newStructure,
48+
Map<String, Value> base,
49+
Map<String, Value> overriding) {
50+
return null;
51+
}
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package dev.openfeature.sdk;
2+
3+
import dev.openfeature.sdk.internal.ExcludeFromGeneratedCoverageReport;
4+
import lombok.EqualsAndHashCode;
5+
import lombok.ToString;
6+
import lombok.experimental.Delegate;
7+
8+
import java.time.Instant;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.Optional;
12+
import java.util.function.Function;
13+
14+
/**
15+
* MutableTrackingEventDetails represents data pertinent to a particular tracking event.
16+
*/
17+
@EqualsAndHashCode
18+
@ToString
19+
public class MutableTrackingEventDetails implements TrackingEventDetails {
20+
21+
private final Number value;
22+
@Delegate(excludes = MutableTrackingEventDetails.DelegateExclusions.class)
23+
private final MutableStructure structure;
24+
25+
public MutableTrackingEventDetails() {
26+
this.value = null;
27+
this.structure = new MutableStructure();
28+
}
29+
30+
public MutableTrackingEventDetails(final Number value) {
31+
this.value = value;
32+
this.structure = new MutableStructure();
33+
}
34+
35+
/**
36+
* Returns the optional tracking value.
37+
*/
38+
public Optional<Number> getValue() {
39+
return Optional.ofNullable(value);
40+
}
41+
42+
// override @Delegate methods so that we can use "add" methods and still return MutableTrackingEventDetails,
43+
// not Structure
44+
public MutableTrackingEventDetails add(String key, Boolean value) {
45+
this.structure.add(key, value);
46+
return this;
47+
}
48+
49+
public MutableTrackingEventDetails add(String key, String value) {
50+
this.structure.add(key, value);
51+
return this;
52+
}
53+
54+
public MutableTrackingEventDetails add(String key, Integer value) {
55+
this.structure.add(key, value);
56+
return this;
57+
}
58+
59+
public MutableTrackingEventDetails add(String key, Double value) {
60+
this.structure.add(key, value);
61+
return this;
62+
}
63+
64+
public MutableTrackingEventDetails add(String key, Instant value) {
65+
this.structure.add(key, value);
66+
return this;
67+
}
68+
69+
public MutableTrackingEventDetails add(String key, Structure value) {
70+
this.structure.add(key, value);
71+
return this;
72+
}
73+
74+
public MutableTrackingEventDetails add(String key, List<Value> value) {
75+
this.structure.add(key, value);
76+
return this;
77+
}
78+
79+
public MutableTrackingEventDetails add(String key, Value value) {
80+
this.structure.add(key, value);
81+
return this;
82+
}
83+
84+
85+
@SuppressWarnings("all")
86+
private static class DelegateExclusions {
87+
@ExcludeFromGeneratedCoverageReport
88+
public <T extends Structure> Map<String, Value> merge(Function<Map<String, Value>, Structure> newStructure,
89+
Map<String, Value> base,
90+
Map<String, Value> overriding) {
91+
return null;
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)