Skip to content

Commit ff89bcf

Browse files
mnoman09aliabbasrizvi
authored andcommitted
(feat): Feature Management Activate and GetVariation Listener (#272)
1 parent 222a7c5 commit ff89bcf

13 files changed

+530
-32
lines changed

core-api/src/main/java/com/optimizely/ab/Optimizely.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import com.optimizely.ab.event.internal.BuildVersionInfo;
3636
import com.optimizely.ab.event.internal.EventFactory;
3737
import com.optimizely.ab.event.internal.payload.EventBatch.ClientEngine;
38+
import com.optimizely.ab.notification.DecisionNotification;
3839
import com.optimizely.ab.notification.NotificationCenter;
3940
import org.slf4j.Logger;
4041
import org.slf4j.LoggerFactory;
@@ -217,7 +218,7 @@ private Variation activate(@Nonnull ProjectConfig projectConfig,
217218
}
218219
Map<String, ?> copiedAttributes = copyAttributes(attributes);
219220
// bucket the user to the given experiment and dispatch an impression event
220-
Variation variation = decisionService.getVariation(experiment, userId, copiedAttributes);
221+
Variation variation = getVariation(experiment, userId, copiedAttributes);
221222
if (variation == null) {
222223
logger.info("Not activating user \"{}\" for experiment \"{}\".", userId, experiment.getKey());
223224
return null;
@@ -389,7 +390,7 @@ public Boolean isFeatureEnabled(@Nonnull String featureKey,
389390
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, userId, copiedAttributes);
390391

391392
if (featureDecision.variation != null) {
392-
if (featureDecision.decisionSource.equals(FeatureDecision.DecisionSource.EXPERIMENT)) {
393+
if (featureDecision.decisionSource.equals(FeatureDecision.DecisionSource.FEATURE_TEST)) {
393394
sendImpression(
394395
projectConfig,
395396
featureDecision.experiment,
@@ -718,8 +719,25 @@ public Variation getVariation(@Nonnull Experiment experiment,
718719
@Nonnull String userId,
719720
@Nonnull Map<String, ?> attributes) throws UnknownExperimentException {
720721
Map<String, ?> copiedAttributes = copyAttributes(attributes);
722+
Variation variation = decisionService.getVariation(experiment, userId, copiedAttributes);
723+
724+
String notificationType = NotificationCenter.DecisionNotificationType.AB_TEST.toString();
725+
726+
if (getProjectConfig().getExperimentFeatureKeyMapping().get(experiment.getId()) != null) {
727+
notificationType = NotificationCenter.DecisionNotificationType.FEATURE_TEST.toString();
728+
}
721729

722-
return decisionService.getVariation(experiment, userId, copiedAttributes);
730+
DecisionNotification decisionNotification = DecisionNotification.newExperimentDecisionNotificationBuilder()
731+
.withUserId(userId)
732+
.withAttributes(copiedAttributes)
733+
.withExperimentKey(experiment.getKey())
734+
.withVariation(variation)
735+
.withType(notificationType)
736+
.build();
737+
738+
notificationCenter.sendNotifications(decisionNotification);
739+
740+
return variation;
723741
}
724742

725743
@Nullable
@@ -754,8 +772,8 @@ public Variation getVariation(@Nonnull String experimentKey,
754772
// if we're unable to retrieve the associated experiment, return null
755773
return null;
756774
}
757-
Map<String, ?> copiedAttributes = copyAttributes(attributes);
758-
return decisionService.getVariation(experiment, userId, copiedAttributes);
775+
776+
return getVariation(experiment, userId, attributes);
759777
}
760778

761779
/**

core-api/src/main/java/com/optimizely/ab/bucketing/DecisionService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ public FeatureDecision getVariationForFeature(@Nonnull FeatureFlag featureFlag,
167167
Variation variation = this.getVariation(experiment, userId, filteredAttributes);
168168
if (variation != null) {
169169
return new FeatureDecision(experiment, variation,
170-
FeatureDecision.DecisionSource.EXPERIMENT);
170+
FeatureDecision.DecisionSource.FEATURE_TEST);
171171
}
172172
}
173173
} else {

core-api/src/main/java/com/optimizely/ab/bucketing/FeatureDecision.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,19 @@ public class FeatureDecision {
4040
public DecisionSource decisionSource;
4141

4242
public enum DecisionSource {
43-
EXPERIMENT,
44-
ROLLOUT
43+
FEATURE_TEST("feature-test"),
44+
ROLLOUT("rollout");
45+
46+
private final String key;
47+
48+
DecisionSource(String key) {
49+
this.key = key;
50+
}
51+
52+
@Override
53+
public String toString() {
54+
return key;
55+
}
4556
}
4657

4758
/**

core-api/src/main/java/com/optimizely/ab/config/ProjectConfig.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public String toString() {
9191
private final Map<String, EventType> eventNameMapping;
9292
private final Map<String, Experiment> experimentKeyMapping;
9393
private final Map<String, FeatureFlag> featureKeyMapping;
94+
private final Map<String, List<String>> experimentFeatureKeyMapping;
9495

9596
// id to entity mappings
9697
private final Map<String, Audience> audienceIdMapping;
@@ -216,6 +217,9 @@ public ProjectConfig(String accountId,
216217
this.experimentIdMapping = ProjectConfigUtils.generateIdMapping(this.experiments);
217218
this.groupIdMapping = ProjectConfigUtils.generateIdMapping(groups);
218219
this.rolloutIdMapping = ProjectConfigUtils.generateIdMapping(this.rollouts);
220+
221+
// Generate experiment to featureFlag list mapping to identify if experiment is AB-Test experiment or Feature-Test Experiment.
222+
this.experimentFeatureKeyMapping = ProjectConfigUtils.generateExperimentFeatureMapping(this.featureFlags);
219223
}
220224

221225
/**
@@ -419,6 +423,10 @@ public Map<String, FeatureFlag> getFeatureKeyMapping() {
419423
return featureKeyMapping;
420424
}
421425

426+
public Map<String, List<String>> getExperimentFeatureKeyMapping() {
427+
return experimentFeatureKeyMapping;
428+
}
429+
422430
public ConcurrentHashMap<String, ConcurrentHashMap<String, String>> getForcedVariationMapping() {
423431
return forcedVariationMapping;
424432
}

core-api/src/main/java/com/optimizely/ab/config/ProjectConfigUtils.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,22 @@ public static <T extends IdMapped> Map<String, T> generateIdMapping(List<T> name
4848
return Collections.unmodifiableMap(nameMapping);
4949
}
5050

51+
/**
52+
* Helper method for creating convenience mappings of ExperimentID to featureFlags it is included in.
53+
*/
54+
public static Map<String, List<String>> generateExperimentFeatureMapping(List<FeatureFlag> featureFlags) {
55+
Map<String, List<String>> experimentFeatureMap = new HashMap<>();
56+
for (FeatureFlag featureFlag : featureFlags) {
57+
for (String experimentId : featureFlag.getExperimentIds()) {
58+
if (experimentFeatureMap.containsKey(experimentId)) {
59+
experimentFeatureMap.get(experimentId).add(featureFlag.getKey());
60+
} else {
61+
ArrayList<String> featureFlagKeysList = new ArrayList<>();
62+
featureFlagKeysList.add(featureFlag.getKey());
63+
experimentFeatureMap.put(experimentId, featureFlagKeysList);
64+
}
65+
}
66+
}
67+
return Collections.unmodifiableMap(experimentFeatureMap);
68+
}
5169
}

core-api/src/main/java/com/optimizely/ab/notification/ActivateNotificationListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import javax.annotation.Nonnull;
2525
import java.util.Map;
2626

27-
27+
@Deprecated
2828
public abstract class ActivateNotificationListener implements NotificationListener, ActivateNotificationListenerInterface {
2929

3030
/**

core-api/src/main/java/com/optimizely/ab/notification/ActivateNotificationListenerInterface.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import javax.annotation.Nonnull;
2424
import java.util.Map;
2525

26+
@Deprecated
2627
public interface ActivateNotificationListenerInterface {
2728
/**
2829
* onActivate called when an activate was triggered
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/****************************************************************************
2+
* Copyright 2019, Optimizely, Inc. and contributors *
3+
* *
4+
* Licensed under the Apache License, Version 2.0 (the "License"); *
5+
* you may not use this file except in compliance with the License. *
6+
* You may obtain a copy of the License at *
7+
* *
8+
* http://www.apache.org/licenses/LICENSE-2.0 *
9+
* *
10+
* Unless required by applicable law or agreed to in writing, software *
11+
* distributed under the License is distributed on an "AS IS" BASIS, *
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13+
* See the License for the specific language governing permissions and *
14+
* limitations under the License. *
15+
***************************************************************************/
16+
17+
package com.optimizely.ab.notification;
18+
19+
20+
import com.optimizely.ab.bucketing.FeatureDecision;
21+
import com.optimizely.ab.config.FeatureVariable;
22+
import com.optimizely.ab.config.Variation;
23+
24+
import javax.annotation.Nonnull;
25+
import javax.annotation.Nullable;
26+
import java.util.HashMap;
27+
import java.util.Map;
28+
29+
public class DecisionNotification {
30+
protected String type;
31+
protected String userId;
32+
protected Map<String, ?> attributes;
33+
protected Map<String, ?> decisionInfo;
34+
35+
protected DecisionNotification() {
36+
}
37+
38+
protected DecisionNotification(@Nonnull String type,
39+
@Nonnull String userId,
40+
@Nullable Map<String, ?> attributes,
41+
@Nonnull Map<String, ?> decisionInfo) {
42+
this.type = type;
43+
this.userId = userId;
44+
if (attributes == null) {
45+
attributes = new HashMap<>();
46+
}
47+
this.attributes = attributes;
48+
this.decisionInfo = decisionInfo;
49+
}
50+
51+
public String getType() {
52+
return type;
53+
}
54+
55+
public String getUserId() {
56+
return userId;
57+
}
58+
59+
public Map<String, ?> getAttributes() {
60+
return attributes;
61+
}
62+
63+
public Map<String, ?> getDecisionInfo() {
64+
return decisionInfo;
65+
}
66+
67+
public static ExperimentDecisionNotificationBuilder newExperimentDecisionNotificationBuilder() {
68+
return new ExperimentDecisionNotificationBuilder();
69+
}
70+
71+
public static class ExperimentDecisionNotificationBuilder {
72+
public final static String EXPERIMENT_KEY = "experiment_key";
73+
public final static String VARIATION_KEY = "variation_key";
74+
75+
private String type;
76+
private String experimentKey;
77+
private Variation variation;
78+
private String userId;
79+
private Map<String, ?> attributes;
80+
private Map<String, Object> decisionInfo;
81+
82+
public ExperimentDecisionNotificationBuilder withUserId(String userId) {
83+
this.userId = userId;
84+
return this;
85+
}
86+
87+
public ExperimentDecisionNotificationBuilder withAttributes(Map<String, ?> attributes) {
88+
this.attributes = attributes;
89+
return this;
90+
}
91+
92+
public ExperimentDecisionNotificationBuilder withExperimentKey(String experimentKey) {
93+
this.experimentKey = experimentKey;
94+
return this;
95+
}
96+
97+
public ExperimentDecisionNotificationBuilder withType(String type) {
98+
this.type = type;
99+
return this;
100+
}
101+
102+
public ExperimentDecisionNotificationBuilder withVariation(Variation variation) {
103+
this.variation = variation;
104+
return this;
105+
}
106+
107+
public DecisionNotification build() {
108+
decisionInfo = new HashMap<>();
109+
decisionInfo.put(EXPERIMENT_KEY, experimentKey);
110+
decisionInfo.put(VARIATION_KEY, variation != null ? variation.getKey() : null);
111+
112+
return new DecisionNotification(
113+
type,
114+
userId,
115+
attributes,
116+
decisionInfo);
117+
}
118+
}
119+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/****************************************************************************
2+
* Copyright 2019, Optimizely, Inc. and contributors *
3+
* *
4+
* Licensed under the Apache License, Version 2.0 (the "License"); *
5+
* you may not use this file except in compliance with the License. *
6+
* You may obtain a copy of the License at *
7+
* *
8+
* http://www.apache.org/licenses/LICENSE-2.0 *
9+
* *
10+
* Unless required by applicable law or agreed to in writing, software *
11+
* distributed under the License is distributed on an "AS IS" BASIS, *
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13+
* See the License for the specific language governing permissions and *
14+
* limitations under the License. *
15+
***************************************************************************/
16+
17+
package com.optimizely.ab.notification;
18+
19+
import javax.annotation.Nonnull;
20+
21+
public interface DecisionNotificationListener {
22+
23+
/**
24+
* onDecision called when an activate was triggered
25+
*
26+
* @param decisionNotification - The decision notification object containing:
27+
* type - The notification type.
28+
* userId - The userId passed to the API.
29+
* attributes - The attribute map passed to the API.
30+
* decisionInfo - The decision information containing all parameters passed in API.
31+
*/
32+
void onDecision(@Nonnull DecisionNotification decisionNotification);
33+
}

0 commit comments

Comments
 (0)