Skip to content

Commit ab858ef

Browse files
rathovarun1032Varun Rathoregemini-code-assist[bot]
authored
feat(rc):Add exposurePercent to ExperimentValues (#1201)
* Add exposurePercent to ExperimentValues * fix comments * fix build * Apply suggestions from code review Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * fix build * fix build * fix build * fix build * fix build * fix build * fix build * resolved comments * resolved comments * resolved comments * resolved comments * Update TemplateResponse.java * Update Template.java --------- Co-authored-by: Varun Rathore <varunrathore@google.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent d04e15b commit ab858ef

5 files changed

Lines changed: 172 additions & 67 deletions

File tree

src/main/java/com/google/firebase/remoteconfig/ParameterValue.java

Lines changed: 74 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,12 @@
2626
import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterValueResponse;
2727
import com.google.firebase.remoteconfig.internal.TemplateResponse.PersonalizationValueResponse;
2828
import com.google.firebase.remoteconfig.internal.TemplateResponse.RolloutValueResponse;
29-
30-
import java.util.ArrayList;
3129
import java.util.List;
3230
import java.util.Objects;
3331

34-
/**
35-
* Represents a Remote Config parameter value that can be used in a {@link Template}.
36-
*/
32+
/** Represents a Remote Config parameter value
33+
* that can be used in a
34+
* {@link Template}. */
3735
public abstract class ParameterValue {
3836

3937
/**
@@ -82,17 +80,18 @@ public static PersonalizationValue ofPersonalization(String personalizationId) {
8280
*
8381
* @param experimentId The experiment ID.
8482
* @param variantValues The list of experiment variant values.
83+
* @param exposurePercent The exposure percentage of the experiment.
8584
* @return A {@link ParameterValue.ExperimentValue} instance.
8685
*/
87-
public static ExperimentValue ofExperiment(String experimentId,
88-
List<ExperimentVariantValue> variantValues) {
89-
return new ExperimentValue(experimentId, variantValues);
86+
public static ExperimentValue ofExperiment(
87+
String experimentId, List<ExperimentVariantValue> variantValues, double exposurePercent) {
88+
return new ExperimentValue(experimentId, variantValues, exposurePercent);
9089
}
9190

9291
abstract ParameterValueResponse toParameterValueResponse();
9392

9493
static ParameterValue fromParameterValueResponse(
95-
@NonNull ParameterValueResponse parameterValueResponse) {
94+
@NonNull ParameterValueResponse parameterValueResponse) {
9695
checkNotNull(parameterValueResponse);
9796
if (parameterValueResponse.isUseInAppDefault()) {
9897
return ParameterValue.inAppDefault();
@@ -102,7 +101,7 @@ static ParameterValue fromParameterValueResponse(
102101
// Protobuf serialization does not set values for fields on the wire when
103102
// they are equal to the default value for the field type. When deserializing,
104103
// can appear as the value not being set. Explicitly handle default value for
105-
// the percent field since 0 is a valid value.
104+
// the percent field since 0 is a valid value.
106105
double percent = 0;
107106
if (rv.getPercent() != null) {
108107
percent = rv.getPercent();
@@ -115,18 +114,26 @@ static ParameterValue fromParameterValueResponse(
115114
}
116115
if (parameterValueResponse.getExperimentValue() != null) {
117116
ExperimentValueResponse ev = parameterValueResponse.getExperimentValue();
118-
List<ExperimentVariantValue> variantValues = ev.getExperimentVariantValues().stream()
119-
.map(evv -> new ExperimentVariantValue(
120-
evv.getVariantId(), evv.getValue(), evv.getNoChange()))
121-
.collect(toList());
122-
return ParameterValue.ofExperiment(ev.getExperimentId(), variantValues);
117+
List<ExperimentVariantValue> variantValues =
118+
ev.getExperimentVariantValues().stream()
119+
.map(
120+
evv ->
121+
new ExperimentVariantValue(
122+
evv.getVariantId(), evv.getValue(), evv.getNoChange()))
123+
.collect(toList());
124+
// Handle null exposurePercent by defaulting to 0
125+
double exposurePercent = 0;
126+
if (ev.getExposurePercent() != null) {
127+
exposurePercent = ev.getExposurePercent();
128+
}
129+
return ParameterValue.ofExperiment(
130+
ev.getExperimentId(), variantValues, exposurePercent);
123131
}
124132
return ParameterValue.of(parameterValueResponse.getValue());
125133
}
126134

127135
/**
128-
* Represents an explicit Remote Config parameter value with a value that the
129-
* parameter is set to.
136+
* Represents an explicit Remote Config parameter value with a value that the parameter is set to.
130137
*/
131138
public static final class Explicit extends ParameterValue {
132139

@@ -147,8 +154,7 @@ public String getValue() {
147154

148155
@Override
149156
ParameterValueResponse toParameterValueResponse() {
150-
return new ParameterValueResponse()
151-
.setValue(this.value);
157+
return new ParameterValueResponse().setValue(this.value);
152158
}
153159

154160
@Override
@@ -169,9 +175,7 @@ public int hashCode() {
169175
}
170176
}
171177

172-
/**
173-
* Represents an in app default parameter value.
174-
*/
178+
/** Represents an in app default parameter value. */
175179
public static final class InAppDefault extends ParameterValue {
176180

177181
@Override
@@ -191,9 +195,7 @@ public boolean equals(Object o) {
191195
}
192196
}
193197

194-
/**
195-
* Represents a Rollout value.
196-
*/
198+
/** Represents a Rollout value. */
197199
public static final class RolloutValue extends ParameterValue {
198200
private final String rolloutId;
199201
private final String value;
@@ -224,8 +226,8 @@ public String getValue() {
224226
}
225227

226228
/**
227-
* Gets the rollout percentage representing the exposure of rollout value
228-
* in the target audience.
229+
* Gets the rollout percentage representing the exposure of rollout value in the target
230+
* audience.
229231
*
230232
* @return Percentage of audience exposed to the rollout
231233
*/
@@ -235,11 +237,12 @@ public double getPercent() {
235237

236238
@Override
237239
ParameterValueResponse toParameterValueResponse() {
238-
return new ParameterValueResponse().setRolloutValue(
240+
return new ParameterValueResponse()
241+
.setRolloutValue(
239242
new RolloutValueResponse()
240-
.setRolloutId(this.rolloutId)
241-
.setValue(this.value)
242-
.setPercent(this.percent));
243+
.setRolloutId(this.rolloutId)
244+
.setValue(this.value)
245+
.setPercent(this.percent));
243246
}
244247

245248
@Override
@@ -252,8 +255,8 @@ public boolean equals(Object o) {
252255
}
253256
RolloutValue that = (RolloutValue) o;
254257
return Double.compare(that.percent, percent) == 0
255-
&& Objects.equals(rolloutId, that.rolloutId)
256-
&& Objects.equals(value, that.value);
258+
&& Objects.equals(rolloutId, that.rolloutId)
259+
&& Objects.equals(value, that.value);
257260
}
258261

259262
@Override
@@ -262,9 +265,7 @@ public int hashCode() {
262265
}
263266
}
264267

265-
/**
266-
* Represents a Personalization value.
267-
*/
268+
/** Represents a Personalization value. */
268269
public static final class PersonalizationValue extends ParameterValue {
269270
private final String personalizationId;
270271

@@ -283,9 +284,9 @@ public String getPersonalizationId() {
283284

284285
@Override
285286
ParameterValueResponse toParameterValueResponse() {
286-
return new ParameterValueResponse().setPersonalizationValue(
287-
new PersonalizationValueResponse()
288-
.setPersonalizationId(this.personalizationId));
287+
return new ParameterValueResponse()
288+
.setPersonalizationValue(
289+
new PersonalizationValueResponse().setPersonalizationId(this.personalizationId));
289290
}
290291

291292
@Override
@@ -306,9 +307,7 @@ public int hashCode() {
306307
}
307308
}
308309

309-
/**
310-
* Represents a specific variant within an Experiment.
311-
*/
310+
/** Represents a specific variant within an Experiment. */
312311
public static final class ExperimentVariantValue {
313312
private final String variantId;
314313
private final String value;
@@ -384,8 +383,8 @@ public boolean equals(Object o) {
384383
}
385384
ExperimentVariantValue that = (ExperimentVariantValue) o;
386385
return noChange == that.noChange
387-
&& Objects.equals(variantId, that.variantId)
388-
&& Objects.equals(value, that.value);
386+
&& Objects.equals(variantId, that.variantId)
387+
&& Objects.equals(value, that.value);
389388
}
390389

391390
@Override
@@ -394,16 +393,17 @@ public int hashCode() {
394393
}
395394
}
396395

397-
/**
398-
* Represents an Experiment value.
399-
*/
396+
/** Represents an Experiment value. */
400397
public static final class ExperimentValue extends ParameterValue {
401398
private final String experimentId;
402399
private final List<ExperimentVariantValue> variantValues;
400+
private final double exposurePercent;
403401

404-
private ExperimentValue(String experimentId, List<ExperimentVariantValue> variantValues) {
402+
private ExperimentValue(
403+
String experimentId, List<ExperimentVariantValue> variantValues, double exposurePercent) {
405404
this.experimentId = experimentId;
406405
this.variantValues = variantValues;
406+
this.exposurePercent = exposurePercent;
407407
}
408408

409409
/**
@@ -415,6 +415,15 @@ public String getExperimentId() {
415415
return experimentId;
416416
}
417417

418+
/**
419+
* Gets the exposure percentage of the experiment linked to this value.
420+
*
421+
* @return Exposure percentage of the experiment linked to this value.
422+
*/
423+
public double getExposurePercent() {
424+
return exposurePercent;
425+
}
426+
418427
/**
419428
* Gets a collection of variant values served by the experiment.
420429
*
@@ -426,16 +435,21 @@ public List<ExperimentVariantValue> getExperimentVariantValues() {
426435

427436
@Override
428437
ParameterValueResponse toParameterValueResponse() {
429-
List<ExperimentVariantValueResponse> variantValueResponses = variantValues.stream()
430-
.map(variantValue -> new ExperimentVariantValueResponse()
431-
.setVariantId(variantValue.getVariantId())
432-
.setValue(variantValue.getValue())
433-
.setNoChange(variantValue.getNoChange()))
434-
.collect(toList());
435-
return new ParameterValueResponse().setExperimentValue(
438+
List<ExperimentVariantValueResponse> variantValueResponses =
439+
variantValues.stream()
440+
.map(
441+
variantValue ->
442+
new ExperimentVariantValueResponse()
443+
.setVariantId(variantValue.getVariantId())
444+
.setValue(variantValue.getValue())
445+
.setNoChange(variantValue.getNoChange()))
446+
.collect(toList());
447+
return new ParameterValueResponse()
448+
.setExperimentValue(
436449
new ExperimentValueResponse()
437-
.setExperimentId(this.experimentId)
438-
.setExperimentVariantValues(variantValueResponses));
450+
.setExperimentId(this.experimentId)
451+
.setExperimentVariantValues(variantValueResponses)
452+
.setExposurePercent(this.exposurePercent));
439453
}
440454

441455
@Override
@@ -448,12 +462,13 @@ public boolean equals(Object o) {
448462
}
449463
ExperimentValue that = (ExperimentValue) o;
450464
return Objects.equals(experimentId, that.experimentId)
451-
&& Objects.equals(variantValues, that.variantValues);
465+
&& Objects.equals(variantValues, that.variantValues)
466+
&& Double.compare(that.exposurePercent, exposurePercent) == 0;
452467
}
453468

454469
@Override
455470
public int hashCode() {
456-
return Objects.hash(experimentId, variantValues);
471+
return Objects.hash(experimentId, variantValues, exposurePercent);
457472
}
458473
}
459474
}

src/main/java/com/google/firebase/remoteconfig/Template.java

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public Template(String etag) {
6060
this((String) null);
6161
}
6262

63-
Template(@NonNull TemplateResponse templateResponse) {
63+
Template(@NonNull TemplateResponse templateResponse) throws FirebaseRemoteConfigException {
6464
checkNotNull(templateResponse);
6565
this.parameters = new HashMap<>();
6666
this.conditions = new ArrayList<>();
@@ -86,6 +86,7 @@ public Template(String etag) {
8686
if (templateResponse.getVersion() != null) {
8787
this.version = new Version(templateResponse.getVersion());
8888
}
89+
validateExperimentExposurePercents(this.parameters, this.parameterGroups);
8990
this.etag = templateResponse.getEtag();
9091
}
9192

@@ -278,4 +279,59 @@ public boolean equals(Object o) {
278279
public int hashCode() {
279280
return Objects.hash(etag, parameters, conditions, parameterGroups, version);
280281
}
282+
283+
private void validateExperimentExposurePercents(
284+
Map<String, Parameter> parameters,
285+
Map<String, ParameterGroup> parameterGroups) throws FirebaseRemoteConfigException {
286+
Map<String, Double> experimentExposurePercents = new HashMap<>();
287+
validateParameters(parameters, experimentExposurePercents);
288+
if (parameterGroups != null) {
289+
for (ParameterGroup group : parameterGroups.values()) {
290+
validateParameters(group.getParameters(), experimentExposurePercents);
291+
}
292+
}
293+
}
294+
295+
private void validateParameters(
296+
Map<String, Parameter> parameters,
297+
Map<String, Double> experimentExposurePercents) throws FirebaseRemoteConfigException {
298+
if (parameters == null) {
299+
return;
300+
}
301+
for (Map.Entry<String, Parameter> entry : parameters.entrySet()) {
302+
Parameter parameter = entry.getValue();
303+
String parameterName = entry.getKey();
304+
checkExposurePercent(parameter.getDefaultValue(), parameterName, experimentExposurePercents);
305+
if (parameter.getConditionalValues() != null) {
306+
for (ParameterValue value : parameter.getConditionalValues().values()) {
307+
checkExposurePercent(value, parameterName, experimentExposurePercents);
308+
}
309+
}
310+
}
311+
}
312+
313+
private void checkExposurePercent(
314+
ParameterValue value,
315+
String parameterName,
316+
Map<String, Double> experimentExposurePercents) throws FirebaseRemoteConfigException {
317+
if (value instanceof ParameterValue.ExperimentValue) {
318+
ParameterValue.ExperimentValue experimentValue = (ParameterValue.ExperimentValue) value;
319+
Double exposurePercent = experimentValue.getExposurePercent();
320+
if (exposurePercent != null) {
321+
// Enforce range [0, 100]
322+
if (exposurePercent < 0 || exposurePercent > 100) {
323+
return;
324+
}
325+
// Enforce consistency for the same experimentId
326+
String experimentId = experimentValue.getExperimentId();
327+
if (experimentExposurePercents.containsKey(experimentId)) {
328+
if (!Objects.equals(experimentExposurePercents.get(experimentId), exposurePercent)) {
329+
return;
330+
}
331+
} else {
332+
experimentExposurePercents.put(experimentId, exposurePercent);
333+
}
334+
}
335+
}
336+
}
281337
}

src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,9 @@ public static final class ExperimentValueResponse {
288288
@Key("variantValue")
289289
private List<ExperimentVariantValueResponse> experimentVariantValues;
290290

291+
@Key("exposurePercent")
292+
private Double exposurePercent;
293+
291294
public String getExperimentId() {
292295
return experimentId;
293296
}
@@ -296,6 +299,10 @@ public List<ExperimentVariantValueResponse> getExperimentVariantValues() {
296299
return experimentVariantValues;
297300
}
298301

302+
public Double getExposurePercent() {
303+
return exposurePercent;
304+
}
305+
299306
public ExperimentValueResponse setExperimentId(String experimentId) {
300307
this.experimentId = experimentId;
301308
return this;
@@ -306,6 +313,11 @@ public ExperimentValueResponse setExperimentVariantValues(
306313
this.experimentVariantValues = experimentVariantValues;
307314
return this;
308315
}
316+
317+
public ExperimentValueResponse setExposurePercent(Double exposurePercent) {
318+
this.exposurePercent = exposurePercent;
319+
return this;
320+
}
309321
}
310322

311323
/**

0 commit comments

Comments
 (0)