Skip to content

Commit 765206b

Browse files
[FSSDK-11181] chore: add cmab uuid into impression event meta (#603)
1 parent 5baf45e commit 765206b

File tree

8 files changed

+75
-12
lines changed

8 files changed

+75
-12
lines changed

Sources/Data Model/DispatchEvents/BatchEvent.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,15 @@ struct DecisionMetadata: Codable, Equatable {
8585
let flagKey: String
8686
let variationKey: String
8787
let enabled: Bool
88+
var cmabUUID: String?
8889

8990
enum CodingKeys: String, CodingKey {
9091
case ruleType = "rule_type"
9192
case ruleKey = "rule_key"
9293
case flagKey = "flag_key"
9394
case variationKey = "variation_key"
9495
case enabled = "enabled"
96+
case cmabUUID = "cmab_uuid"
9597
}
9698
}
9799

Sources/Implementation/DefaultDecisionService.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ class DefaultDecisionService: OPTDecisionService {
400400
}
401401

402402
let flagExpDecision = getVariationForFeatureExperiments(config: config, featureFlag: featureFlag, user: user, userProfileTracker: userProfileTracker, isAsync: isAsync, options: options)
403+
403404
reasons.merge(flagExpDecision.reasons)
404405

405406
if let decision = flagExpDecision.result {
@@ -459,7 +460,7 @@ class DefaultDecisionService: OPTDecisionService {
459460
let featureDecision = FeatureDecision(experiment: experiment, variation: nil, source: Constants.DecisionSource.featureTest.rawValue)
460461
return DecisionResponse(result: featureDecision, reasons: reasons)
461462
} else if let variation = result.variation {
462-
let featureDecision = FeatureDecision(experiment: experiment, variation: variation, source: Constants.DecisionSource.featureTest.rawValue)
463+
let featureDecision = FeatureDecision(experiment: experiment, variation: variation, source: Constants.DecisionSource.featureTest.rawValue, cmabUUID: result.cmabUUID)
463464
return DecisionResponse(result: featureDecision, reasons: reasons)
464465
}
465466
}

Sources/Implementation/Events/BatchEventBuilder.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ class BatchEventBuilder {
2828
attributes: OptimizelyAttributes?,
2929
flagKey: String,
3030
ruleType: String,
31-
enabled: Bool) -> Data? {
31+
enabled: Bool,
32+
cmabUUID: String?) -> Data? {
3233

33-
let metaData = DecisionMetadata(ruleType: ruleType, ruleKey: experiment?.key ?? "", flagKey: flagKey, variationKey: variation?.key ?? "", enabled: enabled)
34+
let metaData = DecisionMetadata(ruleType: ruleType, ruleKey: experiment?.key ?? "", flagKey: flagKey, variationKey: variation?.key ?? "", enabled: enabled, cmabUUID: cmabUUID)
3435

3536
let decision = Decision(variationID: variation?.id ?? "",
3637
campaignID: experiment?.layerId ?? "",

Sources/Optimizely+Decide/OptimizelyClient+Decide.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,8 @@ extension OptimizelyClient {
314314
attributes: attributes,
315315
flagKey: feature.key,
316316
ruleType: ruleType,
317-
enabled: flagEnabled)
317+
enabled: flagEnabled,
318+
cmabUUID: flagDecision?.cmabUUID)
318319
decisionEventDispatched = true
319320
}
320321
}

Sources/Optimizely/OptimizelyClient.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,8 @@ open class OptimizelyClient: NSObject {
318318
attributes: attributes,
319319
flagKey: "",
320320
ruleType: Constants.DecisionSource.experiment.rawValue,
321-
enabled: true)
321+
enabled: true,
322+
cmabUUID: nil)
322323

323324
return variation.key
324325
}
@@ -452,7 +453,8 @@ open class OptimizelyClient: NSObject {
452453
attributes: attributes,
453454
flagKey: featureKey,
454455
ruleType: source,
455-
enabled: featureEnabled)
456+
enabled: featureEnabled,
457+
cmabUUID: pair?.cmabUUID)
456458
}
457459

458460
sendDecisionNotification(userId: userId,
@@ -817,7 +819,8 @@ extension OptimizelyClient {
817819
attributes: OptimizelyAttributes? = nil,
818820
flagKey: String,
819821
ruleType: String,
820-
enabled: Bool) {
822+
enabled: Bool,
823+
cmabUUID: String?) {
821824

822825
// non-blocking (event data serialization takes time)
823826
eventLock.async {
@@ -830,7 +833,8 @@ extension OptimizelyClient {
830833
attributes: attributes,
831834
flagKey: flagKey,
832835
ruleType: ruleType,
833-
enabled: enabled) else {
836+
enabled: enabled,
837+
cmabUUID: cmabUUID) else {
834838
self.logger.e(OptimizelyError.eventBuildFailure(DispatchEvent.activateEventKey))
835839
return
836840
}

Tests/OptimizelyTests-APIs/OptimizelyClientTests_Others.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ class OptimizelyClientTests_Others: XCTestCase {
289289
// set invalid (infinity) to attribute values, which will cause JSONEncoder.encode exception
290290
let attributes = ["testvar": Double.infinity]
291291

292-
optimizely.sendImpressionEvent(experiment: experiment, variation: variation, userId: kUserId, attributes: attributes, flagKey: "", ruleType: Constants.DecisionSource.rollout.rawValue, enabled: true)
292+
optimizely.sendImpressionEvent(experiment: experiment, variation: variation, userId: kUserId, attributes: attributes, flagKey: "", ruleType: Constants.DecisionSource.rollout.rawValue, enabled: true, cmabUUID: nil)
293293
XCTAssert(eventDispatcher.events.count == 0)
294294
}
295295

@@ -321,7 +321,7 @@ class OptimizelyClientTests_Others: XCTestCase {
321321
// force condition for sdk-not-ready
322322
optimizely.config = nil
323323

324-
optimizely.sendImpressionEvent(experiment: experiment, variation: variation, userId: kUserId, flagKey: experiment.key, ruleType: Constants.DecisionSource.rollout.rawValue, enabled: true)
324+
optimizely.sendImpressionEvent(experiment: experiment, variation: variation, userId: kUserId, flagKey: experiment.key, ruleType: Constants.DecisionSource.rollout.rawValue, enabled: true, cmabUUID: nil)
325325
XCTAssert(eventDispatcher.events.isEmpty, "event should not be sent out sdk is not configured properly")
326326

327327
optimizely.sendConversionEvent(eventKey: kEventKey, userId: kUserId)

Tests/OptimizelyTests-Common/BatchEventBuilderTests_Events.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ class BatchEventBuilderTests_Events: XCTestCase {
111111
XCTAssertEqual(metaData["rule_key"] as! String, "ab_running_exp_audience_combo_exact_foo_or_true__and__42_or_4_2")
112112
XCTAssertEqual(metaData["flag_key"] as! String, "")
113113
XCTAssertEqual(metaData["variation_key"] as! String, "all_traffic_variation")
114+
XCTAssertNil(metaData["cmab_uuid"])
114115
XCTAssertTrue(metaData["enabled"] as! Bool)
115116

116117
let de = (snapshot["events"] as! Array<Dictionary<String, Any>>)[0]
@@ -212,7 +213,7 @@ class BatchEventBuilderTests_Events: XCTestCase {
212213
let experiment = optimizely.config?.getExperiment(id: "10390977714")
213214

214215
optimizely.config?.project.sendFlagDecisions = true
215-
let event = BatchEventBuilder.createImpressionEvent(config: optimizely.config!, experiment: experiment!, variation: nil, userId: userId, attributes: attributes, flagKey: experiment!.key, ruleType: Constants.DecisionSource.featureTest.rawValue, enabled: false)
216+
let event = BatchEventBuilder.createImpressionEvent(config: optimizely.config!, experiment: experiment!, variation: nil, userId: userId, attributes: attributes, flagKey: experiment!.key, ruleType: Constants.DecisionSource.featureTest.rawValue, enabled: false, cmabUUID: "cmab_uuid_124")
216217
XCTAssertNotNil(event)
217218

218219
let visitor = (getEventJSON(data: event!)!["visitors"] as! Array<Dictionary<String, Any>>)[0]
@@ -224,14 +225,15 @@ class BatchEventBuilderTests_Events: XCTestCase {
224225
XCTAssertEqual(metaData["rule_key"] as! String, "ab_running_exp_audience_combo_exact_foo_or_true__and__42_or_4_2")
225226
XCTAssertEqual(metaData["flag_key"] as! String, "ab_running_exp_audience_combo_exact_foo_or_true__and__42_or_4_2")
226227
XCTAssertEqual(metaData["variation_key"] as! String, "")
228+
XCTAssertEqual(metaData["cmab_uuid"] as! String, "cmab_uuid_124")
227229
XCTAssertFalse(metaData["enabled"] as! Bool)
228230
optimizely.config?.project.sendFlagDecisions = nil
229231
}
230232

231233
func testCreateImpressionEventWithoutExperimentAndVariation() {
232234

233235
optimizely.config?.project.sendFlagDecisions = true
234-
let event = BatchEventBuilder.createImpressionEvent(config: optimizely.config!, experiment: nil, variation: nil, userId: userId, attributes: [String: Any](), flagKey: "feature_1", ruleType: Constants.DecisionSource.rollout.rawValue, enabled: true)
236+
let event = BatchEventBuilder.createImpressionEvent(config: optimizely.config!, experiment: nil, variation: nil, userId: userId, attributes: [String: Any](), flagKey: "feature_1", ruleType: Constants.DecisionSource.rollout.rawValue, enabled: true, cmabUUID: nil)
235237
XCTAssertNotNil(event)
236238

237239
let visitor = (getEventJSON(data: event!)!["visitors"] as! Array<Dictionary<String, Any>>)[0]
@@ -243,6 +245,7 @@ class BatchEventBuilderTests_Events: XCTestCase {
243245
XCTAssertEqual(metaData["rule_key"] as! String, "")
244246
XCTAssertEqual(metaData["flag_key"] as! String, "feature_1")
245247
XCTAssertEqual(metaData["variation_key"] as! String, "")
248+
XCTAssertEqual(metaData["cmab_uuid"] as? String, nil)
246249
XCTAssertTrue(metaData["enabled"] as! Bool)
247250
optimizely.config?.project.sendFlagDecisions = nil
248251
}

Tests/OptimizelyTests-Common/OptimizelyUserContextTests_Decide_CMAB.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,30 @@ class OptimizelyUserContextTests_Decide_CMAB: XCTestCase {
7272
XCTAssertTrue(self.mockCmabService.decisionCalled, "CMAB decision service was not called")
7373
XCTAssertEqual(self.mockCmabService.lastRuleId, "10390977673", "Expected CMAB rule id '10390977673' but got \(String(describing: self.mockCmabService.lastRuleId))")
7474

75+
// Verify impression event
76+
self.optimizely.eventLock.sync {}
77+
78+
guard let event = self.getFirstEventJSON(client: self.optimizely) else {
79+
XCTFail("No impression event found")
80+
expectation.fulfill()
81+
return
82+
}
83+
84+
let visitor = (event["visitors"] as! Array<Dictionary<String, Any>>)[0]
85+
let snapshot = (visitor["snapshots"] as! Array<Dictionary<String, Any>>)[0]
86+
let decision = (snapshot["decisions"] as! Array<Dictionary<String, Any>>)[0]
87+
let metaData = decision["metadata"] as! Dictionary<String, Any>
88+
89+
// Verify event metadata
90+
XCTAssertEqual(metaData["rule_type"] as! String, Constants.DecisionSource.featureTest.rawValue)
91+
XCTAssertEqual(metaData["rule_key"] as! String, "exp_with_audience")
92+
XCTAssertEqual(metaData["flag_key"] as! String, "feature_1")
93+
XCTAssertEqual(metaData["variation_key"] as! String, "a")
94+
XCTAssertEqual(metaData["cmab_uuid"] as? String, "test-uuid")
95+
XCTAssertTrue(metaData["enabled"] as! Bool)
96+
7597
expectation.fulfill()
98+
7699
}
77100

78101
wait(for: [expectation], timeout: 5) // Increased timeout for reliability
@@ -285,3 +308,31 @@ fileprivate class MockCmabService: DefaultCmabService {
285308
return .failure(CmabClientError.fetchFailed("No variation set"))
286309
}
287310
}
311+
312+
extension OptimizelyUserContextTests_Decide_CMAB {
313+
314+
func getFirstEvent(dispatcher: MockEventDispatcher) -> EventForDispatch? {
315+
optimizely.eventLock.sync{}
316+
return dispatcher.events.first
317+
}
318+
319+
func getFirstEventJSON(dispatcher: MockEventDispatcher) -> [String: Any]? {
320+
guard let event = getFirstEvent(dispatcher: dispatcher) else { return nil }
321+
322+
let json = try! JSONSerialization.jsonObject(with: event.body, options: .allowFragments) as! [String: Any]
323+
return json
324+
}
325+
326+
func getFirstEventJSON(client: OptimizelyClient) -> [String: Any]? {
327+
guard let event = getFirstEvent(dispatcher: client.eventDispatcher as! MockEventDispatcher) else { return nil }
328+
329+
let json = try! JSONSerialization.jsonObject(with: event.body, options: .allowFragments) as! [String: Any]
330+
return json
331+
}
332+
333+
func getEventJSON(data: Data) -> [String: Any]? {
334+
let json = try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
335+
return json
336+
}
337+
338+
}

0 commit comments

Comments
 (0)