Skip to content

Commit 2f64936

Browse files
authored
[FSSDK-9433]: Adds support to override sdkName and sdkVersion for events (#512)
* Adds support to override sdkName and sdkVersion for events
1 parent 64bd9a4 commit 2f64936

File tree

7 files changed

+191
-11
lines changed

7 files changed

+191
-11
lines changed

Sources/Implementation/Events/BatchEventBuilder.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import Foundation
1818

19-
class BatchEventBuilder {
19+
class BatchEventBuilder {
2020
static private var logger = OPTLoggerFactory.getLogger()
2121

2222
// MARK: - Impression Event

Sources/ODP/OptimizelySdkSettings.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2022, Optimizely, Inc. and contributors
2+
// Copyright 2022-2023, Optimizely, Inc. and contributors
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -36,15 +36,25 @@ public struct OptimizelySdkSettings {
3636
/// - timeoutForSegmentFetchInSecs: The timeout in seconds of odp segment fetch (optional. default = 10) - OS default timeout will be used if this is set to zero.
3737
/// - timeoutForOdpEventInSecs: The timeout in seconds of odp event dispatch (optional. default = 10) - OS default timeout will be used if this is set to zero.
3838
/// - disableOdp: Set this flag to true (default = false) to disable ODP features
39+
/// - sdkName: Set this flag to override sdkName included in events
40+
/// - sdkVersion: Set this flag to override sdkVersion included in events
3941
public init(segmentsCacheSize: Int = 100,
4042
segmentsCacheTimeoutInSecs: Int = 600,
4143
timeoutForSegmentFetchInSecs: Int = 10,
4244
timeoutForOdpEventInSecs: Int = 10,
43-
disableOdp: Bool = false) {
45+
disableOdp: Bool = false,
46+
sdkName: String? = nil,
47+
sdkVersion: String? = nil) {
4448
self.segmentsCacheSize = segmentsCacheSize
4549
self.segmentsCacheTimeoutInSecs = segmentsCacheTimeoutInSecs
4650
self.timeoutForSegmentFetchInSecs = timeoutForSegmentFetchInSecs
4751
self.timeoutForOdpEventInSecs = timeoutForOdpEventInSecs
4852
self.disableOdp = disableOdp
53+
if let _sdkName = sdkName, _sdkName != "" {
54+
Utils.swiftSdkClientName = _sdkName
55+
}
56+
if let _sdkVersion = sdkVersion, _sdkVersion != "" {
57+
Utils.sdkVersion = _sdkVersion
58+
}
4959
}
5060
}

Sources/Optimizely/OptimizelyClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -959,7 +959,7 @@ extension OptimizelyClient {
959959

960960
func fetchQualifiedSegments(userId: String,
961961
options: [OptimizelySegmentOption],
962-
completionHandler: @escaping ([String]?, OptimizelyError?) -> Void) {
962+
completionHandler: @escaping ([String]?, OptimizelyError?) -> Void) {
963963
odpManager.fetchQualifiedSegments(userId: userId,
964964
options: options,
965965
completionHandler: completionHandler)

Sources/Utils/Utils.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2019-2021, Optimizely, Inc. and contributors
2+
// Copyright 2019-2021, 2023 Optimizely, Inc. and contributors
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@ class Utils {
2828

2929
// from auto-generated variable OPTIMIZELYSDKVERSION
3030
static var sdkVersion: String = OPTIMIZELYSDKVERSION
31-
static let swiftSdkClientName = "swift-sdk"
31+
static var swiftSdkClientName = "swift-sdk"
3232

3333
static var os: String {
3434
#if os(iOS)

Tests/OptimizelyTests-APIs/OptimizelyClientTests_ODP.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2021, Optimizely, Inc. and contributors
2+
// Copyright 2021, 2023 Optimizely, Inc. and contributors
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -28,6 +28,11 @@ class OptimizelyClientTests_ODP: XCTestCase {
2828
try! optimizely.start(datafile: datafile)
2929
}
3030

31+
override func tearDown() {
32+
Utils.sdkVersion = OPTIMIZELYSDKVERSION
33+
Utils.swiftSdkClientName = "swift-sdk"
34+
}
35+
3136
// MARK: - ODP configuration
3237

3338
func testSdkSettings_default() {
@@ -85,6 +90,20 @@ class OptimizelyClientTests_ODP: XCTestCase {
8590
XCTAssertEqual([:], odpManager.eventData as! [String: String])
8691
}
8792

93+
func testSendOdpEvent_customClientNameAndVersion() {
94+
let odpEventManager = MockOdpEventManager(sdkKey: "any")
95+
let odpManager = OdpManager(sdkKey: "any", disable: false, cacheSize: 12, cacheTimeoutInSecs: 123, eventManager: odpEventManager)
96+
97+
let datafile = OTUtils.loadJSONDatafile("decide_audience_segments")!
98+
let tmpOptimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey, odpManager: odpManager, settings: OptimizelySdkSettings(sdkName: "flutter-sdk", sdkVersion: "1234"))
99+
try! tmpOptimizely.start(datafile: datafile)
100+
101+
try? tmpOptimizely.sendOdpEvent(type: "t1", action: "a1", identifiers: ["k1": "v1"], data: ["k21": "v2", "k22": true, "k23": 3.5, "k24": nil])
102+
103+
XCTAssertEqual(odpEventManager.receivedData["data_source_version"] as! String, "1234")
104+
XCTAssertEqual(odpEventManager.receivedData["data_source"] as! String, "flutter-sdk")
105+
}
106+
88107
func testSendOdpEvent_error() {
89108
var optimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey)
90109

@@ -237,4 +256,12 @@ extension OptimizelyClientTests_ODP {
237256
}
238257
}
239258

259+
class MockOdpEventManager: OdpEventManager {
260+
var receivedData: [String: Any?]!
261+
262+
override func dispatch(_ event: OdpEvent) {
263+
self.receivedData = event.data
264+
}
265+
}
266+
240267
}

Tests/OptimizelyTests-Common/BatchEventBuilderTests_Events.swift

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2019-2021, Optimizely, Inc. and contributors
2+
// Copyright 2019-2021, 2023 Optimizely, Inc. and contributors
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -35,6 +35,11 @@ class BatchEventBuilderTests_Events: XCTestCase {
3535
project = optimizely.config!.project!
3636
}
3737

38+
override func tearDown() {
39+
Utils.sdkVersion = OPTIMIZELYSDKVERSION
40+
Utils.swiftSdkClientName = "swift-sdk"
41+
}
42+
3843
func testCreateImpressionEvent() {
3944
let expVariationId = "10416523162"
4045
let expCampaignId = "10420273929"
@@ -99,6 +104,80 @@ class BatchEventBuilderTests_Events: XCTestCase {
99104
XCTAssertNil(de["value"])
100105
}
101106

107+
func testCreateImpressionEventCustomClientNameAndVersion() {
108+
// Needed custom instances to avoid breaking original tests
109+
let eventDispatcher = MockEventDispatcher()
110+
let optimizely = OTUtils.createOptimizely(datafileName: "audience_targeting",
111+
clearUserProfileService: true,
112+
eventDispatcher: eventDispatcher,
113+
settings: OptimizelySdkSettings(sdkName: "flutter-sdk", sdkVersion: "1234"))!
114+
115+
let expVariationId = "10416523162"
116+
let expCampaignId = "10420273929"
117+
let expExperimentId = "10390977714"
118+
119+
let attributes: [String: Any] = [
120+
"s_foo": "foo",
121+
"b_true": true,
122+
"i_42": 42,
123+
"d_4_2": 4.2
124+
]
125+
126+
_ = try! optimizely.activate(experimentKey: experimentKey,
127+
userId: userId,
128+
attributes: attributes)
129+
130+
// Skipped getFirstEventJSON as it uses the class level optimizely instance
131+
optimizely.eventLock.sync{}
132+
let rawEvent: EventForDispatch = eventDispatcher.events.first!
133+
let event = try! JSONSerialization.jsonObject(with: rawEvent.body, options: .allowFragments) as! [String: Any]
134+
135+
XCTAssertEqual((event["revision"] as! String), project.revision)
136+
XCTAssertEqual((event["account_id"] as! String), project.accountId)
137+
XCTAssertEqual(event["client_version"] as! String, "1234")
138+
XCTAssertEqual(event["project_id"] as! String, project.projectId)
139+
XCTAssertEqual(event["client_name"] as! String, "flutter-sdk")
140+
XCTAssertEqual(event["anonymize_ip"] as! Bool, project.anonymizeIP)
141+
XCTAssertEqual(event["enrich_decisions"] as! Bool, true)
142+
143+
let visitor = (event["visitors"] as! Array<Dictionary<String, Any>>)[0]
144+
145+
XCTAssertEqual(visitor["visitor_id"] as! String, userId)
146+
147+
let snapshot = (visitor["snapshots"] as! Array<Dictionary<String, Any>>)[0]
148+
149+
// attributes contents are tested separately in "BatchEventBuilder_Attributes.swift"
150+
let eventAttributes = visitor["attributes"] as! Array<Dictionary<String, Any>>
151+
XCTAssertEqual(eventAttributes.count, attributes.count)
152+
153+
let decision = (snapshot["decisions"] as! Array<Dictionary<String, Any>>)[0]
154+
155+
XCTAssertEqual(decision["variation_id"] as! String, expVariationId)
156+
XCTAssertEqual(decision["campaign_id"] as! String, expCampaignId)
157+
XCTAssertEqual(decision["experiment_id"] as! String, expExperimentId)
158+
159+
let metaData = decision["metadata"] as! Dictionary<String, Any>
160+
XCTAssertEqual(metaData["rule_type"] as! String, Constants.DecisionSource.experiment.rawValue)
161+
XCTAssertEqual(metaData["rule_key"] as! String, "ab_running_exp_audience_combo_exact_foo_or_true__and__42_or_4_2")
162+
XCTAssertEqual(metaData["flag_key"] as! String, "")
163+
XCTAssertEqual(metaData["variation_key"] as! String, "all_traffic_variation")
164+
XCTAssertTrue(metaData["enabled"] as! Bool)
165+
166+
let de = (snapshot["events"] as! Array<Dictionary<String, Any>>)[0]
167+
168+
XCTAssertEqual(de["entity_id"] as! String, expCampaignId)
169+
XCTAssertEqual(de["key"] as! String, "campaign_activated")
170+
let expTimestamp = Int64((Date.timeIntervalSinceReferenceDate + Date.timeIntervalBetween1970AndReferenceDate) * 1000)
171+
// divide by 1000 to ignore small time difference
172+
XCTAssertEqual((de["timestamp"] as! Int64)/1000, expTimestamp / 1000)
173+
// cannot validate randomly-generated string. check if long enough.
174+
XCTAssert((de["uuid"] as! String).count > 20)
175+
// event tags are tested separately below
176+
XCTAssert((de["tags"] as! Dictionary<String, Any>).count==0)
177+
XCTAssertNil(de["revenue"])
178+
XCTAssertNil(de["value"])
179+
}
180+
102181
func testCreateImpressionEventWithoutVariation() {
103182
let attributes: [String: Any] = [
104183
"s_foo": "foo",
@@ -196,6 +275,68 @@ class BatchEventBuilderTests_Events: XCTestCase {
196275
XCTAssertNil(de["value"])
197276
}
198277

278+
func testCreateConversionEventCustomClientNameAndVersion() {
279+
// Needed custom instances to avoid breaking original tests
280+
let eventDispatcher = MockEventDispatcher()
281+
let optimizely = OTUtils.createOptimizely(datafileName: "audience_targeting",
282+
clearUserProfileService: true,
283+
eventDispatcher: eventDispatcher,
284+
settings: OptimizelySdkSettings(sdkName: "flutter-sdk", sdkVersion: "1234"))!
285+
286+
let eventKey = "event_single_targeted_exp"
287+
let eventId = "10404198135"
288+
289+
let attributes: [String: Any] = ["s_foo": "bar"]
290+
let eventTags: [String: Any] = ["browser": "chrome"]
291+
292+
try! optimizely.track(eventKey: eventKey,
293+
userId: userId,
294+
attributes: attributes,
295+
eventTags: eventTags)
296+
297+
// Skipped getFirstEventJSON as it uses the class level optimizely instance
298+
optimizely.eventLock.sync{}
299+
let rawEvent: EventForDispatch = eventDispatcher.events.first!
300+
let event = try! JSONSerialization.jsonObject(with: rawEvent.body, options: .allowFragments) as! [String: Any]
301+
302+
XCTAssertEqual(event["revision"] as! String, project.revision)
303+
XCTAssertEqual(event["account_id"] as! String, project.accountId)
304+
XCTAssertEqual(event["client_version"] as! String, "1234")
305+
XCTAssertEqual(event["project_id"] as! String, project.projectId)
306+
XCTAssertEqual(event["client_name"] as! String, "flutter-sdk")
307+
XCTAssertEqual(event["anonymize_ip"] as! Bool, project.anonymizeIP)
308+
XCTAssertEqual(event["enrich_decisions"] as! Bool, true)
309+
310+
let visitor = (event["visitors"] as! Array<Dictionary<String, Any>>)[0]
311+
312+
XCTAssertEqual(visitor["visitor_id"] as! String, userId)
313+
314+
let snapshot = (visitor["snapshots"] as! Array<Dictionary<String, Any>>)[0]
315+
316+
// attributes contents are tested separately in "BatchEventBuilder_Attributes.swift"
317+
let eventAttributes = visitor["attributes"] as! Array<Dictionary<String, Any>>
318+
XCTAssertEqual(eventAttributes[0]["key"] as! String, "s_foo")
319+
XCTAssertEqual(eventAttributes[0]["value"] as! String, "bar")
320+
321+
let decisions = snapshot["decisions"]
322+
323+
XCTAssertNil(decisions)
324+
325+
let de = (snapshot["events"] as! Array<Dictionary<String, Any>>)[0]
326+
327+
XCTAssertEqual(de["entity_id"] as! String, eventId)
328+
XCTAssertEqual(de["key"] as! String, eventKey)
329+
let expTimestamp = Int64((Date.timeIntervalSinceReferenceDate + Date.timeIntervalBetween1970AndReferenceDate) * 1000)
330+
// divide by 1000 to ignore small time difference
331+
XCTAssertEqual((de["timestamp"] as! Int64)/1000, expTimestamp / 1000)
332+
// cannot validate randomly-generated string. check if long enough.
333+
XCTAssert((de["uuid"] as!String).count > 20)
334+
// {tags, revenue, value} are tested separately below
335+
XCTAssertEqual((de["tags"] as! Dictionary<String, Any>)["browser"] as! String, "chrome")
336+
XCTAssertNil(de["revenue"])
337+
XCTAssertNil(de["value"])
338+
}
339+
199340
func testCreateConversionEventWhenEventKeyInvalid() {
200341
let eventKey = "not-existing-key"
201342

Tests/TestUtils/OTUtils.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2019, 2021, Optimizely, Inc. and contributors
2+
// Copyright 2019, 2021, 2023 Optimizely, Inc. and contributors
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -137,7 +137,8 @@ class OTUtils {
137137
static func createOptimizely(datafileName: String,
138138
clearUserProfileService: Bool,
139139
eventDispatcher: OPTEventDispatcher?=nil,
140-
logger: OPTLogger?=nil) -> OptimizelyClient? {
140+
logger: OPTLogger?=nil,
141+
settings: OptimizelySdkSettings?=nil) -> OptimizelyClient? {
141142

142143
guard let datafile = OTUtils.loadJSONDatafile(datafileName) else { return nil }
143144
let userProfileService = clearUserProfileService ? createClearUserProfileService() : nil
@@ -147,7 +148,8 @@ class OTUtils {
147148
let optimizely = OptimizelyClient(sdkKey: randomSdkKey,
148149
logger: logger,
149150
eventDispatcher: eventDispatcher,
150-
userProfileService: userProfileService)
151+
userProfileService: userProfileService,
152+
settings: settings)
151153
do {
152154
try optimizely.start(datafile: datafile, doFetchDatafileBackground: false)
153155
return optimizely

0 commit comments

Comments
 (0)