Skip to content

Commit

Permalink
feat: implemented support for tracking
Browse files Browse the repository at this point in the history
Signed-off-by: Bernd Warmuth <bernd.warmuth@dynatrace.com>
  • Loading branch information
Bernd Warmuth committed Nov 25, 2024
1 parent 234062c commit 7831008
Show file tree
Hide file tree
Showing 9 changed files with 366 additions and 25 deletions.
6 changes: 5 additions & 1 deletion src/main/java/dev/openfeature/sdk/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@
/**
* Interface used to resolve flags of varying types.
*/
public interface Client extends Features, EventBus<Client> {
public interface Client extends Features, Tracking, EventBus<Client> {
ClientMetadata getMetadata();

/**
* Return an optional client-level evaluation context.
*
* @return {@link EvaluationContext}
*/
EvaluationContext getEvaluationContext();

/**
* Set the client-level evaluation context.
*
* @param ctx Client level context.
*/
Client setEvaluationContext(EvaluationContext ctx);
Expand All @@ -30,12 +32,14 @@ public interface Client extends Features, EventBus<Client> {

/**
* Fetch the hooks associated to this client.
*
* @return A list of {@link Hook}s.
*/
List<Hook> getHooks();

/**
* Returns the current state of the associated provider.
*
* @return the provider state
*/
ProviderState getProviderState();
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/dev/openfeature/sdk/FeatureProvider.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.openfeature.sdk;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;

Expand Down Expand Up @@ -71,4 +72,14 @@ default ProviderState getState() {
return ProviderState.READY;
}

/**
* Feature provider implementations can opt in for to support Tracking by implementing this method.
*
* @param eventName The name of the tracking event
* @param context Evaluation context used in flag evaluation (Optional)
* @param details Data pertinent to a particular tracking event (Optional)
*/
default void track(String eventName, @Nullable EvaluationContext context, @Nullable TrackingEventDetails details) {

}
}
45 changes: 45 additions & 0 deletions src/main/java/dev/openfeature/sdk/MutableTrackingEventDetails.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package dev.openfeature.sdk;

import dev.openfeature.sdk.internal.ExcludeFromGeneratedCoverageReport;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.Delegate;

import java.util.Map;
import java.util.function.Function;

/**
* MutableTrackingEventDetails represents data pertinent to a particular tracking event.
*/
@EqualsAndHashCode
@ToString
public class MutableTrackingEventDetails implements TrackingEventDetails {


@Getter
private final float target;
@Delegate(excludes = MutableTrackingEventDetails.DelegateExclusions.class)
private final MutableStructure structure;

public MutableTrackingEventDetails() {
this.target = 0f;
this.structure = new MutableStructure();
}

public MutableTrackingEventDetails(final float target) {
this.target = target;
this.structure = new MutableStructure();
}


@SuppressWarnings("all")
private static class DelegateExclusions {
@ExcludeFromGeneratedCoverageReport
public <T extends Structure> Map<String, Value> merge(Function<Map<String, Value>, Structure> newStructure,
Map<String, Value> base,
Map<String, Value> overriding) {
return null;
}
}
}
102 changes: 80 additions & 22 deletions src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
package dev.openfeature.sdk;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.function.Consumer;

import dev.openfeature.sdk.exceptions.ExceptionUtils;
import dev.openfeature.sdk.exceptions.FatalError;
import dev.openfeature.sdk.exceptions.GeneralError;
Expand All @@ -19,6 +11,16 @@
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

/**
* OpenFeature Client implementation.
* You should not instantiate this or reference this class.
Expand All @@ -28,8 +30,8 @@
* @deprecated // TODO: eventually we will make this non-public. See issue #872
*/
@Slf4j
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize", "PMD.UnusedLocalVariable",
"unchecked", "rawtypes" })
@SuppressWarnings({"PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize", "PMD.UnusedLocalVariable",
"unchecked", "rawtypes"})
@Deprecated() // TODO: eventually we will make this non-public. See issue #872
public class OpenFeatureClient implements Client {

Expand Down Expand Up @@ -67,11 +69,56 @@ public OpenFeatureClient(
this.hookSupport = new HookSupport();
}

/**
* {@inheritDoc}
*/
@Override
public ProviderState getProviderState() {
return openfeatureApi.getFeatureProviderStateManager(domain).getState();
}

/**
* {@inheritDoc}
*/
@Override
public void track(String trackingEventName) {
Objects.requireNonNull(trackingEventName);
invokeTrack(trackingEventName, null, null);
}


/**
* {@inheritDoc}
*/
@Override
public void track(String trackingEventName, EvaluationContext context) {
Objects.requireNonNull(trackingEventName);
Objects.requireNonNull(context);
invokeTrack(trackingEventName, context, null);
}

/**
* {@inheritDoc}
*/
@Override
public void track(String trackingEventName, TrackingEventDetails details) {
Objects.requireNonNull(trackingEventName);
Objects.requireNonNull(details);
invokeTrack(trackingEventName, null, details);
}

/**
* {@inheritDoc}
*/
@Override
public void track(String trackingEventName, EvaluationContext context, TrackingEventDetails details) {
Objects.requireNonNull(trackingEventName);
Objects.requireNonNull(context);
Objects.requireNonNull(details);
invokeTrack(trackingEventName, mergeEvaluationContext(context), details);
}


/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -115,7 +162,7 @@ public EvaluationContext getEvaluationContext() {
}

private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key, T defaultValue,
EvaluationContext ctx, FlagEvaluationOptions options) {
EvaluationContext ctx, FlagEvaluationOptions options) {
FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(options,
() -> FlagEvaluationOptions.builder().build());
Map<String, Object> hints = Collections.unmodifiableMap(flagOptions.getHookHints());
Expand Down Expand Up @@ -183,6 +230,17 @@ private static <T> void enrichDetailsWithErrorDefaults(T defaultValue, FlagEvalu
details.setReason(Reason.ERROR.toString());
}

private void invokeTrack(String trackingEventName,
@Nullable EvaluationContext context,
@Nullable TrackingEventDetails details) {
if ("".equals(trackingEventName)) {
throw new IllegalArgumentException("trackingEventName cannot be empty");
}
openfeatureApi.getFeatureProviderStateManager(domain)
.getProvider()
.track(trackingEventName, mergeEvaluationContext(context), details);
}

/**
* Merge invocation contexts with API, transaction and client contexts.
* Does not merge before context.
Expand Down Expand Up @@ -244,7 +302,7 @@ public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationConte

@Override
public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return getBooleanDetails(key, defaultValue, ctx, options).getValue();
}

Expand All @@ -260,7 +318,7 @@ public FlagEvaluationDetails<Boolean> getBooleanDetails(String key, Boolean defa

@Override
public FlagEvaluationDetails<Boolean> getBooleanDetails(String key, Boolean defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.BOOLEAN, key, defaultValue, ctx, options);
}

Expand All @@ -276,7 +334,7 @@ public String getStringValue(String key, String defaultValue, EvaluationContext

@Override
public String getStringValue(String key, String defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return getStringDetails(key, defaultValue, ctx, options).getValue();
}

Expand All @@ -292,7 +350,7 @@ public FlagEvaluationDetails<String> getStringDetails(String key, String default

@Override
public FlagEvaluationDetails<String> getStringDetails(String key, String defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.STRING, key, defaultValue, ctx, options);
}

Expand All @@ -308,7 +366,7 @@ public Integer getIntegerValue(String key, Integer defaultValue, EvaluationConte

@Override
public Integer getIntegerValue(String key, Integer defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return getIntegerDetails(key, defaultValue, ctx, options).getValue();
}

Expand All @@ -324,7 +382,7 @@ public FlagEvaluationDetails<Integer> getIntegerDetails(String key, Integer defa

@Override
public FlagEvaluationDetails<Integer> getIntegerDetails(String key, Integer defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.INTEGER, key, defaultValue, ctx, options);
}

Expand All @@ -340,7 +398,7 @@ public Double getDoubleValue(String key, Double defaultValue, EvaluationContext

@Override
public Double getDoubleValue(String key, Double defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options).getValue();
}

Expand All @@ -356,7 +414,7 @@ public FlagEvaluationDetails<Double> getDoubleDetails(String key, Double default

@Override
public FlagEvaluationDetails<Double> getDoubleDetails(String key, Double defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options);
}

Expand All @@ -372,7 +430,7 @@ public Value getObjectValue(String key, Value defaultValue, EvaluationContext ct

@Override
public Value getObjectValue(String key, Value defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return getObjectDetails(key, defaultValue, ctx, options).getValue();
}

Expand All @@ -383,13 +441,13 @@ public FlagEvaluationDetails<Value> getObjectDetails(String key, Value defaultVa

@Override
public FlagEvaluationDetails<Value> getObjectDetails(String key, Value defaultValue,
EvaluationContext ctx) {
EvaluationContext ctx) {
return getObjectDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build());
}

@Override
public FlagEvaluationDetails<Value> getObjectDetails(String key, Value defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.OBJECT, key, defaultValue, ctx, options);
}

Expand Down
38 changes: 38 additions & 0 deletions src/main/java/dev/openfeature/sdk/Tracking.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package dev.openfeature.sdk;

/**
* Interface for Tracking events.
*/
public interface Tracking {
/**
* Performs tracking of a particular action or application state.
*
* @param trackingEventName Event name to track
*/
void track(String trackingEventName);

/**
* Performs tracking of a particular action or application state.
*
* @param trackingEventName Event name to track
* @param context Evaluation context used in flag evaluation
*/
void track(String trackingEventName, EvaluationContext context);

/**
* Performs tracking of a particular action or application state.
*
* @param trackingEventName Event name to track
* @param details Data pertinent to a particular tracking event
*/
void track(String trackingEventName, TrackingEventDetails details);

/**
* Performs tracking of a particular action or application state.
*
* @param trackingEventName Event name to track
* @param context Evaluation context used in flag evaluation
* @param details Data pertinent to a particular tracking event
*/
void track(String trackingEventName, EvaluationContext context, TrackingEventDetails details);
}
7 changes: 7 additions & 0 deletions src/main/java/dev/openfeature/sdk/TrackingEventDetails.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dev.openfeature.sdk;

/**
* Data pertinent to a particular tracking event.
*/
public interface TrackingEventDetails extends Structure {
}
Loading

0 comments on commit 7831008

Please sign in to comment.