Skip to content

Commit 19c512b

Browse files
authored
feat(ForcedDecisions): add forced-decisions APIs to OptimizelyUserContext (#431)
Add a set of new APIs for forced-decisions to OptimizelyUserContext: - setForcedDecision - getForcedDecision - removeForcedDecision
1 parent c084cbb commit 19c512b

22 files changed

+1131
-210
lines changed

.travis.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ stages:
66
- name: 'Source Clear'
77
- name: 'Lint markdown files'
88
- name: 'Trigger Integration Tests'
9-
if: env(RUN_COMPAT_SUITE) = true
9+
if: env(RUN_COMPAT_SUITE) = true and env(PREP) IS NOT present and env(RELEASE) IS NOT present
1010
- name: 'Lint'
1111
- name: 'Unit Tests'
12+
if: env(PREP) IS NOT present and env(RELEASE) IS NOT present
1213
- name: 'Prepare for release'
1314
if: env(PREP) = true and type = api
1415
- name: 'Release'

DemoSwiftApp/Samples/SamplesForAPI.swift

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class SamplesForAPI {
118118

119119
let user = optimizely.createUserContext(userId: "user_123", attributes: attributes)
120120

121-
let decision = user.decide(key: "show_coupon", options: [.includeReasons])
121+
var decision = user.decide(key: "show_coupon", options: [.includeReasons])
122122

123123
if let variationKey = decision.variationKey {
124124
print("[decide] flag decision to variation: \(variationKey)")
@@ -134,6 +134,41 @@ class SamplesForAPI {
134134
} catch {
135135
print("[track] error: \(error)")
136136
}
137+
138+
// Forced Decisions
139+
140+
let context1 = OptimizelyDecisionContext(flagKey: "flag-1")
141+
let context2 = OptimizelyDecisionContext(flagKey: "flag-1", ruleKey: "ab-test-1")
142+
let context3 = OptimizelyDecisionContext(flagKey: "flag-1", ruleKey: "delivery-1")
143+
let forced1 = OptimizelyForcedDecision(variationKey: "variation-a")
144+
let forced2 = OptimizelyForcedDecision(variationKey: "variation-b")
145+
let forced3 = OptimizelyForcedDecision(variationKey: "on")
146+
147+
// (1) set a forced decision for a flag
148+
149+
var success = user.setForcedDecision(context: context1, decision: forced1)
150+
decision = user.decide(key: "flag-1")
151+
152+
// (2) set a forced decision for an ab-test rule
153+
154+
success = user.setForcedDecision(context: context2, decision: forced2)
155+
decision = user.decide(key: "flag-1")
156+
157+
// (3) set a forced variation for a delivery rule
158+
159+
success = user.setForcedDecision(context: context3,
160+
decision: forced3)
161+
decision = user.decide(key: "flag-1")
162+
163+
// (4) get forced variations
164+
165+
let forcedDecision = user.getForcedDecision(context: context1)
166+
print("[ForcedDecision] variationKey = \(forcedDecision!.variationKey)")
167+
168+
// (5) remove forced variations
169+
170+
success = user.removeForcedDecision(context: context2)
171+
success = user.removeAllForcedDecisions()
137172
}
138173

139174
// MARK: - OptimizelyConfig

OptimizelySwiftSDK.xcodeproj/project.pbxproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
0B97DDA5249D4A28003DE606 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B97DD93249D327F003DE606 /* SemanticVersion.swift */; };
2626
0BAB9B0122567E34000DC388 /* (null) in Sources */ = {isa = PBXBuildFile; };
2727
3D8E460B7007B5B46630E046 /* libPods-OptimizelyTests-Common-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1BBBAD25A1207AF13647873E /* libPods-OptimizelyTests-Common-tvOS.a */; };
28+
6E0A72D426C5B9AE00FF92B5 /* OptimizelyUserContextTests_ForcedDecisions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0A72D326C5B9AE00FF92B5 /* OptimizelyUserContextTests_ForcedDecisions.swift */; };
29+
6E0A72D526C5B9AE00FF92B5 /* OptimizelyUserContextTests_ForcedDecisions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0A72D326C5B9AE00FF92B5 /* OptimizelyUserContextTests_ForcedDecisions.swift */; };
2830
6E12B1CA22C55A250005E9E6 /* optimizely_6372300739_v4.json in Resources */ = {isa = PBXBuildFile; fileRef = 6E75196222C5211100B2B157 /* optimizely_6372300739_v4.json */; };
2931
6E12B1CB22C55A250005E9E6 /* feature_rollout_toggle_on.json in Resources */ = {isa = PBXBuildFile; fileRef = 6E75196722C5211100B2B157 /* feature_rollout_toggle_on.json */; };
3032
6E12B1CC22C55A250005E9E6 /* feature_rollout_toggle_off.json in Resources */ = {isa = PBXBuildFile; fileRef = 6E75196822C5211100B2B157 /* feature_rollout_toggle_off.json */; };
@@ -569,6 +571,7 @@
569571
6E424D5426324C4D0081004A /* OptimizelyUserContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC6DD4024ABF89B0017D296 /* OptimizelyUserContext.swift */; };
570572
6E424D7626324DBD0081004A /* AtomicArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E424D7526324DBD0081004A /* AtomicArrayTests.swift */; };
571573
6E474C8D263C889E00ABDFF8 /* UserProfileServiceTests_MultiClients.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E474C8C263C889E00ABDFF8 /* UserProfileServiceTests_MultiClients.swift */; };
574+
6E554A8726F15C060038ECF7 /* decide_datafile.json in Resources */ = {isa = PBXBuildFile; fileRef = 6EF8DE0524B8DA58008B9488 /* decide_datafile.json */; };
572575
6E593FB625BB9C5500EC72BC /* OptimizelyClientTests_Decide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E593FB425BB9C5500EC72BC /* OptimizelyClientTests_Decide.swift */; };
573576
6E5AB69323F6130D007A82B1 /* OptimizelyClientTests_Init_Sync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5AB69123F6130C007A82B1 /* OptimizelyClientTests_Init_Sync.swift */; };
574577
6E5AB69423F6130D007A82B1 /* OptimizelyClientTests_Init_Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5AB69223F6130D007A82B1 /* OptimizelyClientTests_Init_Async.swift */; };
@@ -1297,6 +1300,7 @@
12971300
6E7E9B562523F8C6009E4426 /* OptimizelyUserContextTests_Decide_Legacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB97BCC24C89DFB00068883 /* OptimizelyUserContextTests_Decide_Legacy.swift */; };
12981301
6E7E9C2F25240D2E009E4426 /* OptimizelyUserContextTests_Objc.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E86CEB224FF20DE005DAFED /* OptimizelyUserContextTests_Objc.m */; };
12991302
6E7E9C4C25240D31009E4426 /* OptimizelyUserContextTests_Objc.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E86CEB224FF20DE005DAFED /* OptimizelyUserContextTests_Objc.m */; };
1303+
6E83350B26F2B188005F6FCA /* decide_datafile.json in Resources */ = {isa = PBXBuildFile; fileRef = 6EF8DE0524B8DA58008B9488 /* decide_datafile.json */; };
13001304
6E86CEA224FDC836005DAFED /* OptimizelyUserContext+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E86CEA124FDC836005DAFED /* OptimizelyUserContext+ObjC.swift */; };
13011305
6E86CEA324FDC836005DAFED /* OptimizelyUserContext+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E86CEA124FDC836005DAFED /* OptimizelyUserContext+ObjC.swift */; };
13021306
6E86CEA424FDC836005DAFED /* OptimizelyUserContext+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E86CEA124FDC836005DAFED /* OptimizelyUserContext+ObjC.swift */; };
@@ -1891,6 +1895,7 @@
18911895
456ABB3BBED880347915C4E4 /* Pods-OptimizelyTests-Common-tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OptimizelyTests-Common-tvOS.release.xcconfig"; path = "Target Support Files/Pods-OptimizelyTests-Common-tvOS/Pods-OptimizelyTests-Common-tvOS.release.xcconfig"; sourceTree = "<group>"; };
18921896
5151BD9C826F9B112DC548AB /* Pods-OptimizelyTests-Common-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OptimizelyTests-Common-iOS.release.xcconfig"; path = "Target Support Files/Pods-OptimizelyTests-Common-iOS/Pods-OptimizelyTests-Common-iOS.release.xcconfig"; sourceTree = "<group>"; };
18931897
5F38B9FBC88543893307E7F4 /* Pods-OptimizelyTests-Common-tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OptimizelyTests-Common-tvOS.debug.xcconfig"; path = "Target Support Files/Pods-OptimizelyTests-Common-tvOS/Pods-OptimizelyTests-Common-tvOS.debug.xcconfig"; sourceTree = "<group>"; };
1898+
6E0A72D326C5B9AE00FF92B5 /* OptimizelyUserContextTests_ForcedDecisions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptimizelyUserContextTests_ForcedDecisions.swift; sourceTree = "<group>"; };
18941899
6E14CD632423F80B00010234 /* OptimizelyTests-Batch-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "OptimizelyTests-Batch-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
18951900
6E20050726B4D28400278087 /* MockLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLogger.swift; sourceTree = "<group>"; };
18961901
6E27EC9A266EF11000B4A6D4 /* OptimizelyDecisionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptimizelyDecisionTests.swift; sourceTree = "<group>"; };
@@ -2606,6 +2611,7 @@
26062611
6E27EC9A266EF11000B4A6D4 /* OptimizelyDecisionTests.swift */,
26072612
6EC6DD6824AE94820017D296 /* OptimizelyUserContextTests.swift */,
26082613
6E2D34B8250AD14000A0CDFE /* OptimizelyUserContextTests_Decide.swift */,
2614+
6E0A72D326C5B9AE00FF92B5 /* OptimizelyUserContextTests_ForcedDecisions.swift */,
26092615
6E7E9B362523F8BF009E4426 /* OptimizelyUserContextTests_Decide_Reasons.swift */,
26102616
6EB97BCC24C89DFB00068883 /* OptimizelyUserContextTests_Decide_Legacy.swift */,
26112617
6E86CEB224FF20DE005DAFED /* OptimizelyUserContextTests_Objc.m */,
@@ -3426,6 +3432,7 @@
34263432
6E12B2A022C55A2A0005E9E6 /* simple_datafile.json in Resources */,
34273433
6E12B2D422C55A380005E9E6 /* 100_entities.json in Resources */,
34283434
6E12B28A22C55A2A0005E9E6 /* optimizely_6372300739_v4.json in Resources */,
3435+
6E83350B26F2B188005F6FCA /* decide_datafile.json in Resources */,
34293436
6E12B29E22C55A2A0005E9E6 /* ab_experiments.json in Resources */,
34303437
6E12B29622C55A2A0005E9E6 /* grouped_experiments.json in Resources */,
34313438
6E34A62F231ED04900BAE302 /* empty_datafile_new_project_id.json in Resources */,
@@ -3505,6 +3512,7 @@
35053512
6E12B22822C55A270005E9E6 /* simple_datafile.json in Resources */,
35063513
6E12B2C522C55A350005E9E6 /* 100_entities.json in Resources */,
35073514
6E12B21222C55A270005E9E6 /* optimizely_6372300739_v4.json in Resources */,
3515+
6E554A8726F15C060038ECF7 /* decide_datafile.json in Resources */,
35083516
6E12B22622C55A270005E9E6 /* ab_experiments.json in Resources */,
35093517
6E12B21E22C55A270005E9E6 /* grouped_experiments.json in Resources */,
35103518
6E34A62A231ED04900BAE302 /* empty_datafile_new_project_id.json in Resources */,
@@ -4338,6 +4346,7 @@
43384346
6E34A6202319EBB800BAE302 /* Notifications.swift in Sources */,
43394347
6E75173322C520D400B2B157 /* Constants.swift in Sources */,
43404348
6E7518A922C520D400B2B157 /* FeatureFlag.swift in Sources */,
4349+
6E0A72D526C5B9AE00FF92B5 /* OptimizelyUserContextTests_ForcedDecisions.swift in Sources */,
43414350
6E75173F22C520D400B2B157 /* MurmurHash3.swift in Sources */,
43424351
6E75189D22C520D400B2B157 /* Experiment.swift in Sources */,
43434352
6E27EC9C266EF11000B4A6D4 /* OptimizelyDecisionTests.swift in Sources */,
@@ -4553,6 +4562,7 @@
45534562
6E9B115922C5486E00C22D81 /* BatchEventBuilderTests_Attributes.swift in Sources */,
45544563
6E9B11AA22C5489200C22D81 /* OTUtils.swift in Sources */,
45554564
6E7518D322C520D400B2B157 /* AttributeValue.swift in Sources */,
4565+
6E0A72D426C5B9AE00FF92B5 /* OptimizelyUserContextTests_ForcedDecisions.swift in Sources */,
45564566
6EF41A332522BE1900EAADF1 /* OptimizelyUserContextTests_Decide.swift in Sources */,
45574567
6E27ECBE266FD78600B4A6D4 /* DecisionReasonsTests.swift in Sources */,
45584568
6E9B115E22C5486E00C22D81 /* DataStoreTests.swift in Sources */,

Sources/Data Model/ProjectConfig.swift

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ class ProjectConfig {
3838
var featureFlagKeys = [String]()
3939
var rolloutIdMap = [String: Rollout]()
4040
var allExperiments = [Experiment]()
41-
41+
var flagVariationsMap = [String: [Variation]]()
42+
4243
// MARK: - Init
4344

4445
init(datafile: Data) throws {
@@ -122,7 +123,39 @@ class ProjectConfig {
122123
project.rollouts.forEach { map[$0.id] = $0 }
123124
return map
124125
}()
126+
127+
// all variations for each flag
128+
// - datafile does not contain a separate entity for this.
129+
// - we collect variations used in each rule (experiment rules and delivery rules)
130+
131+
self.flagVariationsMap = {
132+
var map = [String: [Variation]]()
133+
134+
project.featureFlags.forEach { flag in
135+
var variations = [Variation]()
136+
137+
getAllRulesForFlag(flag).forEach { rule in
138+
rule.variations.forEach { variation in
139+
if variations.filter({ $0.id == variation.id }).first == nil {
140+
variations.append(variation)
141+
}
142+
}
143+
}
144+
map[flag.key] = variations
145+
}
146+
147+
return map
148+
}()
149+
150+
}
151+
152+
func getAllRulesForFlag(_ flag: FeatureFlag) -> [Experiment] {
153+
var rules = flag.experimentIds.compactMap { experimentIdMap[$0] }
154+
let rollout = self.rolloutIdMap[flag.rolloutId]
155+
rules.append(contentsOf: rollout?.experiments ?? [])
156+
return rules
125157
}
158+
126159
}
127160

128161
// MARK: - Project Change Observer
@@ -296,7 +329,7 @@ extension ProjectConfig {
296329
func isFeatureExperiment(id: String) -> Bool {
297330
return !(experimentFeatureMap[id]?.isEmpty ?? true)
298331
}
299-
332+
300333
/**
301334
* Get forced variation for a given experiment key and user id.
302335
*/

0 commit comments

Comments
 (0)