Skip to content

Commit e02e14a

Browse files
authored
feat(abtest): support rules properties on exp allocation (#9)
* feat(abtest): support rules properties on exp allocation * fix: linter error * fix: remove hashmap * fix: update readme * fix: add supported properties * fix: add test cases for client implementation * fix: address comments
1 parent 6abd7be commit e02e14a

File tree

8 files changed

+246
-16
lines changed

8 files changed

+246
-16
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,22 @@ if (isAllocate) {
6262
// Init config same as feature-flag
6363
ABTestClient abTest = new ABTestClientImpl(config);
6464

65+
// Get all experiments allocated to this users within the universe id
6566
GetUniverseAllocationResponse universeAllocation = abTest.getUniverseAllocation(
6667
"qFA88l70U4RxscuJZFUwEZpHUUUF", "0b95519c-1b32-47fe-aa8c-55cb65d6f8c4");
6768

69+
// Create properties map fo filtered experiments
70+
// This is optional, can be passed as the last parameter to getExperiments
71+
// Supported properties are defined in the Backend
72+
HashMap<String, String> properties = new HashMap<String, String>(1) {{
73+
put("wh_code", "JK01");
74+
}};
75+
76+
// Get all experiments allocated to this users within the universe id
77+
GetUniverseAllocationResponse universeAllocation = abTest.getUniverseAllocation(
78+
"qFA88l70U4RxscuJZFUwEZpHUUUF", "0b95519c-1b32-47fe-aa8c-55cb65d6f8c4", properties);
79+
80+
// Get all experiments allocated to this users in all universe
6881
List<GetUniverseAllocationResponse> allUniverseAllocations = abTest.getAllUniverseAllocations(
6982
"qFA88l70U4RxscuJZFUwEZpHUUUF");
7083

src/main/java/com/sayurbox/kale/abtest/ABTestClient.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
import com.sayurbox.kale.abtest.client.GetUniverseAllocationResponse;
44

55
import java.util.List;
6+
import java.util.Map;
67

78
public interface ABTestClient {
89

910
List<GetUniverseAllocationResponse> getAllUniverseAllocations(String userId);
11+
1012
GetUniverseAllocationResponse getUniverseAllocation(String userId, String universeId);
1113

14+
List<GetUniverseAllocationResponse> getAllUniverseAllocations(String userId, Map<String, String> properties);
15+
16+
GetUniverseAllocationResponse getUniverseAllocation(String userId, String universeId, Map<String, String> properties);
1217
}

src/main/java/com/sayurbox/kale/abtest/ABTestClientImpl.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.sayurbox.kale.config.KaleConfig;
88

99
import java.util.List;
10+
import java.util.Map;
1011

1112
public class ABTestClientImpl extends KaleClientImpl implements ABTestClient {
1213

@@ -19,7 +20,7 @@ public GetUniverseAllocationResponse getUniverseAllocation(String userId, String
1920
GetUniverseAllocationCommand cmd = new GetUniverseAllocationCommand(this.circuitBreaker,
2021
this.httpClient, this.kaleConfig.getCircuitBreakerParams().isEnabled(),
2122
this.kaleConfig.getBaseUrl(),
22-
userId, universeId);
23+
userId, universeId, null);
2324
return cmd.execute();
2425
}
2526

@@ -28,7 +29,26 @@ public List<GetUniverseAllocationResponse> getAllUniverseAllocations(String user
2829
GetAllUniverseAllocationsCommand cmd = new GetAllUniverseAllocationsCommand(this.circuitBreaker,
2930
this.httpClient, this.kaleConfig.getCircuitBreakerParams().isEnabled(),
3031
this.kaleConfig.getBaseUrl(),
31-
userId);
32+
userId, null);
3233
return cmd.execute();
3334
}
35+
36+
@Override
37+
public List<GetUniverseAllocationResponse> getAllUniverseAllocations(String userId, Map<String, String> properties) {
38+
GetAllUniverseAllocationsCommand cmd = new GetAllUniverseAllocationsCommand(this.circuitBreaker,
39+
this.httpClient, this.kaleConfig.getCircuitBreakerParams().isEnabled(),
40+
this.kaleConfig.getBaseUrl(),
41+
userId, properties);
42+
return cmd.execute();
43+
}
44+
45+
@Override
46+
public GetUniverseAllocationResponse getUniverseAllocation(String userId, String universeId, Map<String, String> properties) {
47+
GetUniverseAllocationCommand cmd = new GetUniverseAllocationCommand(this.circuitBreaker,
48+
this.httpClient, this.kaleConfig.getCircuitBreakerParams().isEnabled(),
49+
this.kaleConfig.getBaseUrl(),
50+
userId, universeId, properties);
51+
return cmd.execute();
52+
}
53+
3454
}

src/main/java/com/sayurbox/kale/abtest/command/GetAllUniverseAllocationsCommand.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,24 @@
55
import com.sayurbox.kale.common.client.DataResponse;
66
import com.sayurbox.kale.common.command.KaleCommand;
77
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
8+
import okhttp3.MediaType;
89
import okhttp3.OkHttpClient;
910
import okhttp3.Request;
1011
import okhttp3.RequestBody;
1112
import okhttp3.Response;
1213

1314
import java.io.IOException;
15+
import java.lang.reflect.Type;
1416
import java.util.Collections;
1517
import java.util.List;
18+
import java.util.Map;
1619

1720
public class GetAllUniverseAllocationsCommand extends KaleCommand<List<GetUniverseAllocationResponse>> {
1821

1922
private final String userId;
23+
private final Map<String, String> properties;
24+
Type propertiesType = new TypeToken<Map<String, String>>() {
25+
}.getType();
2026

2127
private static final String ENDPOINT = "%s/v1/abtest/allocation/%s";
2228

@@ -25,10 +31,12 @@ public GetAllUniverseAllocationsCommand(
2531
OkHttpClient okHttpClient,
2632
boolean isCircuitBreakerEnabled,
2733
String baseUrl,
28-
String userId
34+
String userId,
35+
Map<String, String> properties
2936
) {
3037
super(circuitBreaker, okHttpClient, isCircuitBreakerEnabled, baseUrl);
3138
this.userId = userId;
39+
this.properties = properties;
3240
}
3341

3442
@Override
@@ -39,7 +47,9 @@ public List<GetUniverseAllocationResponse> getFallback() {
3947
@Override
4048
protected Request createRequest() {
4149
String url = String.format(ENDPOINT, baseUrl, userId);
42-
return new Request.Builder().post(RequestBody.create(null, new byte[]{})).url(url).build();
50+
MediaType contentType = MediaType.parse("application/json");
51+
52+
return new Request.Builder().post(RequestBody.create(contentType, gson.toJson(properties, propertiesType))).url(url).build();
4353
}
4454

4555
protected List<GetUniverseAllocationResponse> handleResponse(Response response) throws IOException {
@@ -48,7 +58,8 @@ protected List<GetUniverseAllocationResponse> handleResponse(Response response)
4858
return getFallback();
4959
}
5060
DataResponse<List<GetUniverseAllocationResponse>> t = gson.fromJson(body,
51-
new TypeToken<DataResponse<List<GetUniverseAllocationResponse>>>() {}.getType());
61+
new TypeToken<DataResponse<List<GetUniverseAllocationResponse>>>() {
62+
}.getType());
5263
return t.getData();
5364
}
5465
}

src/main/java/com/sayurbox/kale/abtest/command/GetUniverseAllocationCommand.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,24 @@
55
import com.sayurbox.kale.common.client.DataResponse;
66
import com.sayurbox.kale.common.command.KaleCommand;
77
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
8+
import okhttp3.MediaType;
89
import okhttp3.OkHttpClient;
910
import okhttp3.Request;
1011
import okhttp3.RequestBody;
1112
import okhttp3.Response;
1213

1314
import java.io.IOException;
15+
import java.lang.reflect.Type;
16+
import java.util.HashMap;
17+
import java.util.Map;
1418

1519
public class GetUniverseAllocationCommand extends KaleCommand<GetUniverseAllocationResponse> {
1620

1721
private final String userId;
1822
private final String universeId;
23+
private final Map<String, String> properties;
24+
Type propertiesType = new TypeToken<HashMap<String, String>>() {
25+
}.getType();
1926

2027
private static final String ENDPOINT = "%s/v1/abtest/allocation/%s/%s";
2128

@@ -25,11 +32,13 @@ public GetUniverseAllocationCommand(
2532
boolean isCircuitBreakerEnabled,
2633
String baseUrl,
2734
String userId,
28-
String universeId
35+
String universeId,
36+
Map<String, String> properties
2937
) {
3038
super(circuitBreaker, okHttpClient, isCircuitBreakerEnabled, baseUrl);
3139
this.userId = userId;
3240
this.universeId = universeId;
41+
this.properties = properties;
3342
}
3443

3544
@Override
@@ -40,7 +49,9 @@ public GetUniverseAllocationResponse getFallback() {
4049
@Override
4150
protected Request createRequest() {
4251
String url = String.format(ENDPOINT, baseUrl, userId, universeId);
43-
return new Request.Builder().post(RequestBody.create(null, new byte[]{})).url(url).build();
52+
MediaType contentType = MediaType.parse("application/json");
53+
54+
return new Request.Builder().post(RequestBody.create(contentType, gson.toJson(properties, propertiesType))).url(url).build();
4455
}
4556

4657
protected GetUniverseAllocationResponse handleResponse(Response response) throws IOException {
@@ -49,7 +60,8 @@ protected GetUniverseAllocationResponse handleResponse(Response response) throws
4960
return getFallback();
5061
}
5162
DataResponse<GetUniverseAllocationResponse> t = gson.fromJson(body,
52-
new TypeToken<DataResponse<GetUniverseAllocationResponse>>() {}.getType());
63+
new TypeToken<DataResponse<GetUniverseAllocationResponse>>() {
64+
}.getType());
5365
return t.getData();
5466
}
5567
}

src/test/java/com/sayurbox/kale/abtest/ABTestClientImplTest.java

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66
import com.sayurbox.kale.config.KaleConfig;
77
import org.junit.Assert;
88
import org.junit.Before;
9+
import org.junit.Ignore;
910
import org.junit.Rule;
1011
import org.junit.Test;
1112

13+
import java.util.HashMap;
1214
import java.util.List;
1315

14-
import static com.github.tomakehurst.wiremock.client.WireMock.*;
1516
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
17+
import static com.github.tomakehurst.wiremock.client.WireMock.post;
18+
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
19+
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
1620

1721
public class ABTestClientImplTest {
1822

@@ -100,4 +104,89 @@ public void getAllUniverseAllocations_HasErrorResponse() {
100104
Assert.assertNotNull(actual);
101105
Assert.assertEquals(0, actual.size());
102106
}
103-
}
107+
108+
109+
@Test
110+
public void getUniverseAllocationWithProperties_Success() {
111+
HashMap<String, String> properties = new HashMap<String, String>(1) {{
112+
put("wh_code", "JK01");
113+
}};
114+
115+
stubFor(post(urlEqualTo("/v1/abtest/allocation/user-003/universe-003"))
116+
.withRequestBody(WireMock.equalToJson("{\"wh_code\":\"JK01\"}"))
117+
.willReturn(aResponse()
118+
.withStatus(200)
119+
.withHeader("Content-Type", "application/json")
120+
.withBody("{\"data\":{\"user_id\":\"user-003\",\"universe_id\":\"universe-003\"," +
121+
"\"experiment_id\":\"experiment-003\",\"variant_id\":\"variant-003\"," +
122+
"\"configs\":[{\"key\":\"color\",\"value\":\"red\"}]}}")
123+
));
124+
GetUniverseAllocationResponse actual = abTestClient.getUniverseAllocation(
125+
"user-003", "universe-003", properties
126+
);
127+
128+
Assert.assertEquals("user-003", actual.getUserId());
129+
Assert.assertEquals("universe-003", actual.getUniverseId());
130+
Assert.assertEquals("experiment-003", actual.getExperimentId());
131+
Assert.assertEquals("variant-003", actual.getVariantId());
132+
Assert.assertEquals("color", actual.getConfigs().get(0).get("key"));
133+
Assert.assertEquals("red", actual.getConfigs().get(0).get("value"));
134+
}
135+
136+
@Test
137+
public void getAllUniverseAllocationsWithProperties_Success() {
138+
HashMap<String, String> properties = new HashMap<String, String>(1) {{
139+
put("wh_code", "JK01");
140+
}};
141+
142+
stubFor(post(urlEqualTo("/v1/abtest/allocation/user-003"))
143+
.withRequestBody(WireMock.equalToJson("{\"wh_code\":\"JK01\"}"))
144+
.willReturn(aResponse()
145+
.withStatus(200)
146+
.withHeader("Content-Type", "application/json")
147+
.withBody("{\"data\":[{\"user_id\":\"user-003\",\"universe_id\":\"universe-003\"," +
148+
"\"experiment_id\":\"experiment-003\",\"variant_id\":\"variant-003\"," +
149+
"\"configs\":[{\"key\":\"color\",\"value\":\"red\"}]}]}")
150+
));
151+
List<GetUniverseAllocationResponse> actual = abTestClient.getAllUniverseAllocations("user-003", properties);
152+
GetUniverseAllocationResponse alloc = actual.get(0);
153+
154+
Assert.assertEquals("user-003", alloc.getUserId());
155+
Assert.assertEquals("universe-003", alloc.getUniverseId());
156+
Assert.assertEquals("experiment-003", alloc.getExperimentId());
157+
Assert.assertEquals("variant-003", alloc.getVariantId());
158+
Assert.assertEquals("color", alloc.getConfigs().get(0).get("key"));
159+
Assert.assertEquals("red", alloc.getConfigs().get(0).get("value"));
160+
}
161+
162+
@Ignore("Real test on staging")
163+
@Test
164+
public void getUniverseAllocationRealTest_Success() {
165+
// TODO: change with staging endpoint
166+
KaleConfig cfg = new KaleConfig.Builder().withBaseUrl("http://localhost:9494")
167+
.build();
168+
abTestClient = new ABTestClientImpl(cfg);
169+
170+
HashMap<String, String> properties = new HashMap<>();
171+
properties.put("wh_code", "JK_01");
172+
173+
List<GetUniverseAllocationResponse> allocationsResponse = abTestClient.getAllUniverseAllocations("user-003", properties);
174+
Assert.assertNotEquals(0, allocationsResponse.size());
175+
176+
allocationsResponse.stream().filter(a -> a.getUniverseId().equals("cd6f7e9e-4abe-41c3-bd10-3afbd14afdb2")).forEach(a -> {
177+
Assert.assertEquals("user-003", a.getUserId());
178+
Assert.assertEquals("cd6f7e9e-4abe-41c3-bd10-3afbd14afdb2", a.getUniverseId());
179+
Assert.assertEquals("f4f56090-5a1d-49d6-899b-1ce5178f7d5d", a.getExperimentId());
180+
});
181+
182+
// Test without properties
183+
allocationsResponse = abTestClient.getAllUniverseAllocations("user-003");
184+
Assert.assertNotEquals(0, allocationsResponse.size());
185+
186+
allocationsResponse.stream().filter(a -> a.getUniverseId().equals("cd6f7e9e-4abe-41c3-bd10-3afbd14afdb2")).forEach(a -> {
187+
Assert.assertEquals("user-003", a.getUserId());
188+
Assert.assertEquals("cd6f7e9e-4abe-41c3-bd10-3afbd14afdb2", a.getUniverseId());
189+
Assert.assertEquals("f4f56090-5a1d-49d6-899b-1ce5178f7d5d", a.getExperimentId());
190+
});
191+
}
192+
}

0 commit comments

Comments
 (0)