Skip to content

Commit e19c975

Browse files
Add holdout and experiment core
1 parent 746e815 commit e19c975

File tree

2 files changed

+323
-0
lines changed

2 files changed

+323
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
*
3+
* Copyright 2016-2019, 2021, Optimizely and contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.config;
18+
19+
import com.optimizely.ab.config.audience.AndCondition;
20+
import com.optimizely.ab.config.audience.AudienceIdCondition;
21+
import com.optimizely.ab.config.audience.Condition;
22+
import com.optimizely.ab.config.audience.EmptyCondition;
23+
import com.optimizely.ab.config.audience.NotCondition;
24+
import com.optimizely.ab.config.audience.OrCondition;
25+
26+
import java.util.List;
27+
import java.util.Map;
28+
29+
public interface ExperimentCore extends IdKeyMapped {
30+
String getLayerId();
31+
String getGroupId();
32+
List<String> getAudienceIds();
33+
Condition<AudienceIdCondition> getAudienceConditions();
34+
List<Variation> getVariations();
35+
List<TrafficAllocation> getTrafficAllocation();
36+
Map<String, Variation> getVariationKeyToVariationMap();
37+
Map<String, Variation> getVariationIdToVariationMap();
38+
Map<String, String> getUserIdToVariationKeyMap();
39+
}
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
/**
2+
*
3+
* Copyright 2016-2019, 2021, Optimizely and contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package com.optimizely.ab.config;
19+
20+
import com.fasterxml.jackson.annotation.JsonCreator;
21+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
22+
import com.fasterxml.jackson.annotation.JsonProperty;
23+
import com.optimizely.ab.annotations.VisibleForTesting;
24+
import com.optimizely.ab.config.audience.AndCondition;
25+
import com.optimizely.ab.config.audience.AudienceIdCondition;
26+
import com.optimizely.ab.config.audience.Condition;
27+
import com.optimizely.ab.config.audience.EmptyCondition;
28+
import com.optimizely.ab.config.audience.NotCondition;
29+
import com.optimizely.ab.config.audience.OrCondition;
30+
31+
import java.util.Collections;
32+
import java.util.List;
33+
import java.util.Map;
34+
35+
import javax.annotation.Nonnull;
36+
import javax.annotation.Nullable;
37+
import javax.annotation.concurrent.Immutable;
38+
39+
@Immutable
40+
@JsonIgnoreProperties(ignoreUnknown = true)
41+
public class Holdout implements ExperimentCore {
42+
43+
private final String id;
44+
private final String key;
45+
private final String status;
46+
private final String layerId;
47+
private final String groupId;
48+
49+
private final String AND = "AND";
50+
private final String OR = "OR";
51+
private final String NOT = "NOT";
52+
53+
private final List<String> audienceIds;
54+
private final Condition<AudienceIdCondition> audienceConditions;
55+
private final List<Variation> variations;
56+
private final List<TrafficAllocation> trafficAllocation;
57+
58+
private final Map<String, Variation> variationKeyToVariationMap;
59+
private final Map<String, Variation> variationIdToVariationMap;
60+
private final Map<String, String> userIdToVariationKeyMap;
61+
62+
public enum HoldoutStatus {
63+
RUNNING("Running"),
64+
DRAFT("Draft"),
65+
CONCLUDED("Concluded"),
66+
ARCHIVED("Archived");
67+
68+
private final String holdoutStatus;
69+
70+
HoldoutStatus(String holdoutStatus) {
71+
this.holdoutStatus = holdoutStatus;
72+
}
73+
74+
public String toString() {
75+
return holdoutStatus;
76+
}
77+
}
78+
79+
@VisibleForTesting
80+
public Holdout(String id, String key, String layerId) {
81+
this(id, key, null, layerId, Collections.emptyList(), null, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), "");
82+
}
83+
84+
@JsonCreator
85+
public Holdout(@JsonProperty("id") String id,
86+
@JsonProperty("key") String key,
87+
@JsonProperty("status") String status,
88+
@JsonProperty("layerId") String layerId,
89+
@JsonProperty("audienceIds") List<String> audienceIds,
90+
@JsonProperty("audienceConditions") Condition audienceConditions,
91+
@JsonProperty("variations") List<Variation> variations,
92+
@JsonProperty("forcedVariations") Map<String, String> userIdToVariationKeyMap,
93+
@JsonProperty("trafficAllocation") List<TrafficAllocation> trafficAllocation) {
94+
this(id, key, status, layerId, audienceIds, audienceConditions, variations, userIdToVariationKeyMap, trafficAllocation, "");
95+
}
96+
97+
public Holdout(@Nonnull String id,
98+
@Nonnull String key,
99+
@Nullable String status,
100+
@Nullable String layerId,
101+
@Nonnull List<String> audienceIds,
102+
@Nullable Condition audienceConditions,
103+
@Nonnull List<Variation> variations,
104+
@Nonnull Map<String, String> userIdToVariationKeyMap,
105+
@Nonnull List<TrafficAllocation> trafficAllocation,
106+
@Nonnull String groupId) {
107+
this.id = id;
108+
this.key = key;
109+
this.status = status == null ? HoldoutStatus.DRAFT.toString() : status;
110+
this.layerId = layerId;
111+
this.audienceIds = Collections.unmodifiableList(audienceIds);
112+
this.audienceConditions = audienceConditions;
113+
this.variations = Collections.unmodifiableList(variations);
114+
this.trafficAllocation = Collections.unmodifiableList(trafficAllocation);
115+
this.groupId = groupId;
116+
this.userIdToVariationKeyMap = userIdToVariationKeyMap;
117+
this.variationKeyToVariationMap = ProjectConfigUtils.generateNameMapping(variations);
118+
this.variationIdToVariationMap = ProjectConfigUtils.generateIdMapping(variations);
119+
}
120+
121+
public String getId() {
122+
return id;
123+
}
124+
125+
public String getKey() {
126+
return key;
127+
}
128+
129+
public String getStatus() {
130+
return status;
131+
}
132+
133+
public String getLayerId() {
134+
return layerId;
135+
}
136+
137+
public List<String> getAudienceIds() {
138+
return audienceIds;
139+
}
140+
141+
public Condition getAudienceConditions() {
142+
return audienceConditions;
143+
}
144+
145+
public List<Variation> getVariations() {
146+
return variations;
147+
}
148+
149+
public Map<String, Variation> getVariationKeyToVariationMap() {
150+
return variationKeyToVariationMap;
151+
}
152+
153+
public Map<String, Variation> getVariationIdToVariationMap() {
154+
return variationIdToVariationMap;
155+
}
156+
157+
public Map<String, String> getUserIdToVariationKeyMap() {
158+
return userIdToVariationKeyMap;
159+
}
160+
161+
public List<TrafficAllocation> getTrafficAllocation() {
162+
return trafficAllocation;
163+
}
164+
165+
public String getGroupId() {
166+
return groupId;
167+
}
168+
169+
public boolean isActive() {
170+
return status.equals(Holdout.HoldoutStatus.RUNNING.toString());
171+
}
172+
173+
public boolean isRunning() {
174+
return status.equals(Holdout.HoldoutStatus.RUNNING.toString());
175+
}
176+
177+
public String serializeConditions(Map<String, String> audiencesMap) {
178+
Condition condition = this.audienceConditions;
179+
return condition instanceof EmptyCondition ? "" : this.serialize(condition, audiencesMap);
180+
}
181+
182+
private String getNameFromAudienceId(String audienceId, Map<String, String> audiencesMap) {
183+
StringBuilder audienceName = new StringBuilder();
184+
if (audiencesMap != null && audiencesMap.get(audienceId) != null) {
185+
audienceName.append("\"" + audiencesMap.get(audienceId) + "\"");
186+
} else {
187+
audienceName.append("\"" + audienceId + "\"");
188+
}
189+
return audienceName.toString();
190+
}
191+
192+
private String getOperandOrAudienceId(Condition condition, Map<String, String> audiencesMap) {
193+
if (condition != null) {
194+
if (condition instanceof AudienceIdCondition) {
195+
return this.getNameFromAudienceId(condition.getOperandOrId(), audiencesMap);
196+
} else {
197+
return condition.getOperandOrId();
198+
}
199+
} else {
200+
return "";
201+
}
202+
}
203+
204+
public String serialize(Condition condition, Map<String, String> audiencesMap) {
205+
StringBuilder stringBuilder = new StringBuilder();
206+
List<Condition> conditions;
207+
208+
String operand = this.getOperandOrAudienceId(condition, audiencesMap);
209+
switch (operand){
210+
case (AND):
211+
conditions = ((AndCondition<?>) condition).getConditions();
212+
stringBuilder.append(this.getNameOrNextCondition(operand, conditions, audiencesMap));
213+
break;
214+
case (OR):
215+
conditions = ((OrCondition<?>) condition).getConditions();
216+
stringBuilder.append(this.getNameOrNextCondition(operand, conditions, audiencesMap));
217+
break;
218+
case (NOT):
219+
stringBuilder.append(operand + " ");
220+
Condition notCondition = ((NotCondition<?>) condition).getCondition();
221+
if (notCondition instanceof AudienceIdCondition) {
222+
stringBuilder.append(serialize(notCondition, audiencesMap));
223+
} else {
224+
stringBuilder.append("(" + serialize(notCondition, audiencesMap) + ")");
225+
}
226+
break;
227+
default:
228+
stringBuilder.append(operand);
229+
break;
230+
}
231+
232+
return stringBuilder.toString();
233+
}
234+
235+
public String getNameOrNextCondition(String operand, List<Condition> conditions, Map<String, String> audiencesMap) {
236+
StringBuilder stringBuilder = new StringBuilder();
237+
int index = 0;
238+
if (conditions.isEmpty()) {
239+
return "";
240+
} else if (conditions.size() == 1) {
241+
return serialize(conditions.get(0), audiencesMap);
242+
} else {
243+
for (Condition con : conditions) {
244+
index++;
245+
if (index + 1 <= conditions.size()) {
246+
if (con instanceof AudienceIdCondition) {
247+
String audienceName = this.getNameFromAudienceId(((AudienceIdCondition<?>) con).getAudienceId(),
248+
audiencesMap);
249+
stringBuilder.append( audienceName + " ");
250+
} else {
251+
stringBuilder.append("(" + serialize(con, audiencesMap) + ") ");
252+
}
253+
stringBuilder.append(operand);
254+
stringBuilder.append(" ");
255+
} else {
256+
if (con instanceof AudienceIdCondition) {
257+
String audienceName = this.getNameFromAudienceId(((AudienceIdCondition<?>) con).getAudienceId(),
258+
audiencesMap);
259+
stringBuilder.append(audienceName);
260+
} else {
261+
stringBuilder.append("(" + serialize(con, audiencesMap) + ")");
262+
}
263+
}
264+
}
265+
}
266+
return stringBuilder.toString();
267+
}
268+
269+
@Override
270+
public String toString() {
271+
return "Experiment{" +
272+
"id='" + id + '\'' +
273+
", key='" + key + '\'' +
274+
", groupId='" + groupId + '\'' +
275+
", status='" + status + '\'' +
276+
", audienceIds=" + audienceIds +
277+
", audienceConditions=" + audienceConditions +
278+
", variations=" + variations +
279+
", variationKeyToVariationMap=" + variationKeyToVariationMap +
280+
", userIdToVariationKeyMap=" + userIdToVariationKeyMap +
281+
", trafficAllocation=" + trafficAllocation +
282+
'}';
283+
}
284+
}

0 commit comments

Comments
 (0)