Repository: Unleash/unleash-client-java
Pull request #202 (2023-06-30 11:58:14)

feat: Added impression data events
Adds two new events "VariantImpressionEvent" and
"IsEnabledImpressionEvent".

Extends UnleashSubscriber with a new impression() method which can be overridden to subscribe to events of super type ImpressionEvent.

The data included in the events match what is documented at

https://docs.getunleash.io/reference/impression-data




Patch:
diff --git a/src/main/java/io/getunleash/DefaultUnleash.java b/src/main/java/io/getunleash/DefaultUnleash.java
index f0a2e51c..f0373e21 100644
--- a/src/main/java/io/getunleash/DefaultUnleash.java
+++ b/src/main/java/io/getunleash/DefaultUnleash.java
@@ -3,8 +3,7 @@ package io.getunleash;
 import static io.getunleash.Variant.DISABLED_VARIANT;
 import static java.util.Optional.ofNullable;
 
-import io.getunleash.event.EventDispatcher;
-import io.getunleash.event.ToggleEvaluated;
+import io.getunleash.event.*;
 import io.getunleash.lang.Nullable;
 import io.getunleash.metric.UnleashMetricService;
 import io.getunleash.metric.UnleashMetricServiceImpl;
@@ -140,9 +139,18 @@ public class DefaultUnleash implements Unleash {
         boolean enabled = checkEnabled(toggleName, context, fallbackAction);
         count(toggleName, enabled);
         eventDispatcher.dispatch(new ToggleEvaluated(toggleName, enabled));
+        dispatchEnabledImpressionDataIfNeeded("isEnabled", toggleName, enabled, context);
         return enabled;
     }
 
+    private void dispatchEnabledImpressionDataIfNeeded(
+            String eventType, String toggleName, boolean enabled, UnleashContext context) {
+        FeatureToggle toggle = featureRepository.getToggle(toggleName);
+        if (toggle != null && toggle.hasImpressionData()) {
+            eventDispatcher.dispatch(new IsEnabledImpressionEvent(toggleName, enabled, context));
+        }
+    }
+
     private boolean checkEnabled(
             String toggleName,
             UnleashContext context,
@@ -209,9 +217,19 @@ public class DefaultUnleash implements Unleash {
         metricService.countVariant(toggleName, variant.getName());
         // Should count yes/no also when getting variant.
         metricService.count(toggleName, enabled);
+        dispatchVariantImpressionDataIfNeeded(toggleName, variant.getName(), enabled, context);
         return variant;
     }
 
+    private void dispatchVariantImpressionDataIfNeeded(
+            String toggleName, String variantName, boolean enabled, UnleashContext context) {
+        FeatureToggle toggle = featureRepository.getToggle(toggleName);
+        if (toggle != null && toggle.hasImpressionData()) {
+            eventDispatcher.dispatch(
+                    new VariantImpressionEvent(toggleName, enabled, context, variantName));
+        }
+    }
+
     @Override
     public Variant getVariant(String toggleName) {
         return getVariant(toggleName, contextProvider.getContext());
diff --git a/src/main/java/io/getunleash/event/ImpressionEvent.java b/src/main/java/io/getunleash/event/ImpressionEvent.java
new file mode 100644
index 00000000..41dff673
--- /dev/null
+++ b/src/main/java/io/getunleash/event/ImpressionEvent.java
@@ -0,0 +1,31 @@
+package io.getunleash.event;
+
+import io.getunleash.UnleashContext;
+import java.util.UUID;
+
+public class ImpressionEvent implements UnleashEvent {
+    private String featureName;
+
+    private String eventId;
+    private boolean enabled;
+    private UnleashContext context;
+
+    public ImpressionEvent(String featureName, boolean enabled, UnleashContext context) {
+        this.featureName = featureName;
+        this.enabled = enabled;
+        this.eventId = UUID.randomUUID().toString();
+        this.context = context;
+    }
+
+    ImpressionEvent(String featureName, String eventId, boolean enabled, UnleashContext context) {
+        this.featureName = featureName;
+        this.eventId = eventId;
+        this.enabled = enabled;
+        this.context = context;
+    }
+
+    @Override
+    public void publishTo(UnleashSubscriber unleashSubscriber) {
+        unleashSubscriber.impression(this);
+    }
+}
diff --git a/src/main/java/io/getunleash/event/IsEnabledImpressionEvent.java b/src/main/java/io/getunleash/event/IsEnabledImpressionEvent.java
new file mode 100644
index 00000000..aa19ed37
--- /dev/null
+++ b/src/main/java/io/getunleash/event/IsEnabledImpressionEvent.java
@@ -0,0 +1,9 @@
+package io.getunleash.event;
+
+import io.getunleash.UnleashContext;
+
+public class IsEnabledImpressionEvent extends ImpressionEvent {
+    public IsEnabledImpressionEvent(String featureName, boolean enabled, UnleashContext context) {
+        super(featureName, enabled, context);
+    }
+}
diff --git a/src/main/java/io/getunleash/event/UnleashSubscriber.java b/src/main/java/io/getunleash/event/UnleashSubscriber.java
index 673095a6..118072de 100644
--- a/src/main/java/io/getunleash/event/UnleashSubscriber.java
+++ b/src/main/java/io/getunleash/event/UnleashSubscriber.java
@@ -38,4 +38,6 @@ public interface UnleashSubscriber {
     default void featuresBackedUp(FeatureCollection featureCollection) {}
 
     default void featuresBackupRestored(FeatureCollection featureCollection) {}
+
+    default void impression(ImpressionEvent impressionEvent) {}
 }
diff --git a/src/main/java/io/getunleash/event/VariantImpressionEvent.java b/src/main/java/io/getunleash/event/VariantImpressionEvent.java
new file mode 100644
index 00000000..5ba11546
--- /dev/null
+++ b/src/main/java/io/getunleash/event/VariantImpressionEvent.java
@@ -0,0 +1,13 @@
+package io.getunleash.event;
+
+import io.getunleash.UnleashContext;
+
+public class VariantImpressionEvent extends ImpressionEvent {
+    private String variantName;
+
+    public VariantImpressionEvent(
+            String featureName, boolean enabled, UnleashContext context, String variantName) {
+        super(featureName, enabled, context);
+        this.variantName = variantName;
+    }
+}


Test patch:
diff --git a/src/test/java/io/getunleash/event/ImpressionDataSubscriberTest.java b/src/test/java/io/getunleash/event/ImpressionDataSubscriberTest.java
new file mode 100644
index 00000000..48be6d8f
--- /dev/null
+++ b/src/test/java/io/getunleash/event/ImpressionDataSubscriberTest.java
@@ -0,0 +1,104 @@
+package io.getunleash.event;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import io.getunleash.DefaultUnleash;
+import io.getunleash.FeatureToggle;
+import io.getunleash.SynchronousTestExecutor;
+import io.getunleash.Unleash;
+import io.getunleash.repository.FeatureRepository;
+import io.getunleash.util.UnleashConfig;
+import io.getunleash.variant.VariantDefinition;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class ImpressionDataSubscriberTest {
+
+    private ImpressionTestSubscriber testSubscriber = new ImpressionTestSubscriber();
+
+    private FeatureRepository toggleRepository;
+
+    private UnleashConfig unleashConfig;
+
+    private Unleash unleash;
+
+    @BeforeEach
+    void setup() {
+        unleashConfig =
+                new UnleashConfig.Builder()
+                        .appName(SubscriberTest.class.getSimpleName())
+                        .instanceId(SubscriberTest.class.getSimpleName())
+                        .synchronousFetchOnInitialisation(true)
+                        .unleashAPI("http://localhost:4242/api")
+                        .subscriber(testSubscriber)
+                        .scheduledExecutor(new SynchronousTestExecutor())
+                        .build();
+        toggleRepository = mock(FeatureRepository.class);
+        unleash = new DefaultUnleash(unleashConfig, toggleRepository);
+    }
+
+    @Test
+    public void noEventsIfImpressionDataIsNotEnabled() {
+        String featureWithoutImpressionDataEnabled = "feature.with.no.impressionData";
+        when(toggleRepository.getToggle(featureWithoutImpressionDataEnabled))
+                .thenReturn(
+                        new FeatureToggle(
+                                featureWithoutImpressionDataEnabled, true, new ArrayList<>()));
+        unleash.isEnabled(featureWithoutImpressionDataEnabled);
+        assertThat(testSubscriber.isEnabledImpressions).isEqualTo(0);
+        assertThat(testSubscriber.variantImpressions).isEqualTo(0);
+    }
+
+    @Test
+    public void isEnabledEventWhenImpressionDataIsEnabled() {
+        String featureWithImpressionData = "feature.with.impressionData";
+        when(toggleRepository.getToggle(featureWithImpressionData))
+                .thenReturn(
+                        new FeatureToggle(
+                                featureWithImpressionData,
+                                true,
+                                new ArrayList<>(),
+                                new ArrayList<>(),
+                                true));
+        unleash.isEnabled(featureWithImpressionData);
+        assertThat(testSubscriber.isEnabledImpressions).isEqualTo(1);
+        assertThat(testSubscriber.variantImpressions).isEqualTo(0);
+    }
+
+    @Test
+    public void variantEventWhenVariantIsRequested() {
+        String featureWithImpressionData = "feature.with.impressionData";
+        VariantDefinition def = new VariantDefinition("blue", 1000, null, null);
+        List<VariantDefinition> variants = new ArrayList<>();
+        variants.add(def);
+        when(toggleRepository.getToggle(featureWithImpressionData))
+                .thenReturn(
+                        new FeatureToggle(
+                                featureWithImpressionData,
+                                true,
+                                new ArrayList<>(),
+                                variants,
+                                true));
+        unleash.getVariant(featureWithImpressionData);
+        assertThat(testSubscriber.isEnabledImpressions).isEqualTo(0);
+        assertThat(testSubscriber.variantImpressions).isEqualTo(1);
+    }
+
+    private class ImpressionTestSubscriber implements UnleashSubscriber {
+        private int variantImpressions;
+        private int isEnabledImpressions;
+
+        @Override
+        public void impression(ImpressionEvent impressionEvent) {
+            if (impressionEvent instanceof VariantImpressionEvent) {
+                variantImpressions++;
+            } else if (impressionEvent instanceof IsEnabledImpressionEvent) {
+                isEnabledImpressions++;
+            }
+        }
+    }
+}