Skip to content

Commit 0ee03da

Browse files
committed
wip
1 parent 1af87f0 commit 0ee03da

File tree

8 files changed

+159
-36
lines changed

8 files changed

+159
-36
lines changed

lib/sdk/server/contract-tests/service/src/main/java/sdktest/TestService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public class TestService {
3737
"event-sampling",
3838
"inline-context",
3939
"anonymous-redaction",
40-
"evaluation-hooks"
40+
"evaluation-hooks",
41+
"client-prereq-events"
4142
};
4243

4344
static final Gson gson = new GsonBuilder().serializeNulls().create();

lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/EvalResult.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import com.launchdarkly.sdk.LDValue;
77
import com.launchdarkly.sdk.LDValueType;
88

9+
import java.util.Collections;
10+
import java.util.List;
11+
912
import static com.launchdarkly.sdk.EvaluationDetail.NO_VARIATION;
1013

1114
/**
@@ -30,6 +33,9 @@ final class EvalResult {
3033
private final EvaluationDetail<String> asString;
3134
private final boolean forceReasonTracking;
3235

36+
// A list of prerequisites evaluation records evaluated as part of obtaining this result.
37+
private List<PrerequisiteEvalRecord> prerequisiteEvalRecords;
38+
3339
/**
3440
* Constructs an instance that wraps the specified EvaluationDetail and also precomputes
3541
* any appropriate type-specific variants (asBoolean, etc.).
@@ -100,6 +106,7 @@ private EvalResult(EvalResult from, EvaluationReason newReason) {
100106
this.asDouble = transformReason(from.asDouble, newReason);
101107
this.asString = transformReason(from.asString, newReason);
102108
this.forceReasonTracking = from.forceReasonTracking;
109+
this.prerequisiteEvalRecords = from.prerequisiteEvalRecords;
103110
}
104111

105112
private EvalResult(EvalResult from, boolean newForceTracking) {
@@ -109,6 +116,17 @@ private EvalResult(EvalResult from, boolean newForceTracking) {
109116
this.asDouble = from.asDouble;
110117
this.asString = from.asString;
111118
this.forceReasonTracking = newForceTracking;
119+
this.prerequisiteEvalRecords = from.prerequisiteEvalRecords;
120+
}
121+
122+
private EvalResult(EvalResult from, List<PrerequisiteEvalRecord> prerequisiteEvalRecords) {
123+
this.anyType = from.anyType;
124+
this.asBoolean = from.asBoolean;
125+
this.asInteger = from.asInteger;
126+
this.asDouble = from.asDouble;
127+
this.asString = from.asString;
128+
this.forceReasonTracking = from.forceReasonTracking;
129+
this.prerequisiteEvalRecords = prerequisiteEvalRecords;
112130
}
113131

114132
/**
@@ -208,6 +226,8 @@ public EvaluationDetail<String> getAsString() {
208226
* @return true if reason tracking is required for this result
209227
*/
210228
public boolean isForceReasonTracking() { return forceReasonTracking; }
229+
230+
public List<PrerequisiteEvalRecord> getPrerequisiteEvalRecords() { return prerequisiteEvalRecords; }
211231

212232
/**
213233
* Returns a transformed copy of this EvalResult with a different evaluation reason.
@@ -226,6 +246,10 @@ public EvalResult withReason(EvaluationReason newReason) {
226246
public EvalResult withForceReasonTracking(boolean newValue) {
227247
return this.forceReasonTracking == newValue ? this : new EvalResult(this, newValue);
228248
}
249+
250+
public EvalResult withPrerequisiteEvalRecords(List<PrerequisiteEvalRecord> newValue) {
251+
return this.prerequisiteEvalRecords == newValue ? this : new EvalResult(this, newValue);
252+
}
229253

230254
@Override
231255
public boolean equals(Object other) {

lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/Evaluator.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ private static class EvaluatorState {
122122
private EvaluationReason.BigSegmentsStatus bigSegmentsStatus = null;
123123
private FeatureFlag originalFlag = null;
124124
private List<String> prerequisiteStack = null;
125+
private List<PrerequisiteEvalRecord> prerequisiteEvalRecords = null;
125126
private List<String> segmentStack = null;
126127
}
127128

@@ -145,22 +146,40 @@ EvalResult evaluate(FeatureFlag flag, LDContext context, @Nonnull EvaluationReco
145146

146147
EvaluatorState state = new EvaluatorState();
147148
state.originalFlag = flag;
149+
// allocate list capacity to avoid size increase during evaluation
150+
state.prerequisiteEvalRecords = new ArrayList<>(); // TODO: optimize when this is used, shouldn't allocate for flag with no prereqs
148151

149152
try {
150153
EvalResult result = evaluateInternal(flag, context, recorder, state);
151154

152155
if (state.bigSegmentsStatus != null) {
153-
return result.withReason(
156+
result = result.withReason(
154157
result.getReason().withBigSegmentsStatus(state.bigSegmentsStatus)
155158
);
156159
}
160+
161+
// TODO: these changes have reduced throughput, can we optimize this a bit. Perhaps by calling constructor
162+
// with all parameters instead of using multiple calls in this immutable style
163+
if (state.prerequisiteEvalRecords != null && !state.prerequisiteEvalRecords.isEmpty()) {
164+
result = result.withPrerequisiteEvalRecords(state.prerequisiteEvalRecords);
165+
}
166+
157167
return result;
158168
} catch (EvaluationException e) {
159169
logger.error("Could not evaluate flag \"{}\": {}", flag.getKey(), e.getMessage());
160170
return EvalResult.error(e.errorKind);
161171
}
162172
}
163173

174+
/**
175+
* Internal evaluation function that may be called multiple times during a flag evaluation.
176+
*
177+
* @param flag that to evaluate
178+
* @param context to use for evaluation
179+
* @param recorder that will be used to record evaluation events
180+
* @param state for mutable values needed during evaluation
181+
* @return the evaluation result
182+
*/
164183
private EvalResult evaluateInternal(FeatureFlag flag, LDContext context, @Nonnull EvaluationRecorder recorder, EvaluatorState state) {
165184
if (!flag.isOn()) {
166185
return EvaluatorHelpers.offResult(flag);
@@ -237,6 +256,7 @@ private EvalResult checkPrerequisites(FeatureFlag flag, LDContext context, @Nonn
237256
if (!prereqFeatureFlag.isOn() || prereqEvalResult.getVariationIndex() != prereq.getVariation()) {
238257
prereqOk = false;
239258
}
259+
state.prerequisiteEvalRecords.add(new PrerequisiteEvalRecord(prereqFeatureFlag, flag, prereqEvalResult));
240260
recorder.recordPrerequisiteEvaluation(prereqFeatureFlag, flag, context, prereqEvalResult);
241261
}
242262
if (!prereqOk) {

lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/FeatureFlagsState.java

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313

1414
import java.io.IOException;
1515
import java.util.HashMap;
16+
import java.util.List;
1617
import java.util.Map;
1718
import java.util.Objects;
19+
import java.util.stream.Collectors;
1820

1921
import static com.launchdarkly.sdk.server.JsonHelpers.gsonInstanceWithNullsAllowed;
2022

@@ -48,18 +50,20 @@ static class FlagMetadata {
4850
final boolean trackEvents;
4951
final boolean trackReason;
5052
final Long debugEventsUntilDate;
53+
final List<String> prerequisites;
5154

5255
FlagMetadata(LDValue value, Integer variation, EvaluationReason reason, Integer version,
53-
boolean trackEvents, boolean trackReason, Long debugEventsUntilDate) {
56+
boolean trackEvents, boolean trackReason, Long debugEventsUntilDate, List<String> prerequisites) {
5457
this.value = LDValue.normalize(value);
5558
this.variation = variation;
5659
this.reason = reason;
5760
this.version = version;
5861
this.trackEvents = trackEvents;
5962
this.trackReason = trackReason;
6063
this.debugEventsUntilDate = debugEventsUntilDate;
64+
this.prerequisites = prerequisites;
6165
}
62-
66+
6367
@Override
6468
public boolean equals(Object other) {
6569
if (other instanceof FlagMetadata) {
@@ -70,14 +74,15 @@ public boolean equals(Object other) {
7074
Objects.equals(version, o.version) &&
7175
trackEvents == o.trackEvents &&
7276
trackReason == o.trackReason &&
73-
Objects.equals(debugEventsUntilDate, o.debugEventsUntilDate);
77+
Objects.equals(debugEventsUntilDate, o.debugEventsUntilDate) &&
78+
Objects.equals(prerequisites, o.prerequisites);
7479
}
7580
return false;
7681
}
77-
82+
7883
@Override
7984
public int hashCode() {
80-
return Objects.hash(variation, version, trackEvents, trackReason, debugEventsUntilDate);
85+
return Objects.hash(value, variation, reason, version, trackEvents, trackReason, debugEventsUntilDate, prerequisites);
8186
}
8287
}
8388

@@ -216,9 +221,10 @@ public Builder add(
216221
EvaluationReason reason,
217222
int flagVersion,
218223
boolean trackEvents,
219-
Long debugEventsUntilDate
224+
Long debugEventsUntilDate,
225+
List<String> prerequisites
220226
) {
221-
return add(flagKey, value, variationIndex, reason, flagVersion, trackEvents, false, debugEventsUntilDate);
227+
return add(flagKey, value, variationIndex, reason, flagVersion, trackEvents, false, debugEventsUntilDate, prerequisites);
222228
}
223229

224230
/**
@@ -236,9 +242,10 @@ public Builder add(
236242
* @param flagVersion the current flag version
237243
* @param trackEvents true if full event tracking is turned on for this flag
238244
* @param trackReason true if evaluation reasons must be included due to experimentation
239-
* @param debugEventsUntilDate if set, event debugging is turned until this time (millisecond timestamp)
245+
* @param debugEventsUntilDate if set, event debugging is turned until this time (millisecond timestamp)
240246
* @return the builder
241247
*/
248+
// TODO: add param to docs
242249
public Builder add(
243250
String flagKey,
244251
LDValue value,
@@ -247,7 +254,8 @@ public Builder add(
247254
int flagVersion,
248255
boolean trackEvents,
249256
boolean trackReason,
250-
Long debugEventsUntilDate
257+
Long debugEventsUntilDate,
258+
List<String> prerequisites
251259
) {
252260
final boolean flagIsTracked = trackEvents ||
253261
(debugEventsUntilDate != null && debugEventsUntilDate > System.currentTimeMillis());
@@ -259,8 +267,8 @@ public Builder add(
259267
wantDetails ? Integer.valueOf(flagVersion) : null,
260268
trackEvents,
261269
trackReason,
262-
debugEventsUntilDate
263-
);
270+
debugEventsUntilDate,
271+
prerequisites);
264272
flagMetadata.put(flagKey, data);
265273
return this;
266274
}
@@ -274,7 +282,11 @@ Builder addFlag(DataModel.FeatureFlag flag, EvalResult eval) {
274282
flag.getVersion(),
275283
flag.isTrackEvents() || eval.isForceReasonTracking(),
276284
eval.isForceReasonTracking(),
277-
flag.getDebugEventsUntilDate()
285+
flag.getDebugEventsUntilDate(),
286+
eval.getPrerequisiteEvalRecords() == null ? null : eval.getPrerequisiteEvalRecords().stream()
287+
.filter(record -> record.prereqOfFlag.getKey() == flag.getKey()) // only include top level prereqs
288+
.map(record -> record.flag.getKey()) // map from prereq record to prereq key
289+
.collect(Collectors.toList())
278290
);
279291
}
280292

@@ -331,6 +343,14 @@ public void write(JsonWriter out, FeatureFlagsState state) throws IOException {
331343
out.name("debugEventsUntilDate");
332344
out.value(meta.debugEventsUntilDate.longValue());
333345
}
346+
if (meta.prerequisites != null && !meta.prerequisites.isEmpty()) {
347+
out.name("prerequisites");
348+
out.beginArray();
349+
for (String s: meta.prerequisites) {
350+
out.value(s);
351+
}
352+
out.endArray();
353+
}
334354
out.endObject();
335355
}
336356
out.endObject();
@@ -377,8 +397,8 @@ public FeatureFlagsState read(JsonReader in) throws IOException {
377397
m0.version,
378398
m0.trackEvents,
379399
m0.trackReason,
380-
m0.debugEventsUntilDate
381-
);
400+
m0.debugEventsUntilDate,
401+
m0.prerequisites);
382402
allFlagMetadata.put(e.getKey(), m1);
383403
}
384404
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.launchdarkly.sdk.server;
2+
3+
public class PrerequisiteEvalRecord {
4+
public final DataModel.FeatureFlag flag;
5+
public final DataModel.FeatureFlag prereqOfFlag;
6+
public final EvalResult result;
7+
8+
public PrerequisiteEvalRecord(DataModel.FeatureFlag flag, DataModel.FeatureFlag prereqOfFlag, EvalResult result) {
9+
this.flag = flag;
10+
this.prereqOfFlag = prereqOfFlag;
11+
this.result = result;
12+
}
13+
}

lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/Version.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,5 @@ abstract class Version {
44
private Version() {}
55

66
// This constant is updated automatically by our Gradle script during a release, if the project version has changed
7-
// x-release-please-start-version
87
static final String SDK_VERSION = "7.5.0";
9-
// x-release-please-end
108
}

0 commit comments

Comments
 (0)