Skip to content

[FSSDK-9433]: Adds support to override sdkName and sdkVersion for events #512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/Implementation/Events/BatchEventBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import Foundation

class BatchEventBuilder {
class BatchEventBuilder {
static private var logger = OPTLoggerFactory.getLogger()

// MARK: - Impression Event
Expand Down
14 changes: 12 additions & 2 deletions Sources/ODP/OptimizelySdkSettings.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2022, Optimizely, Inc. and contributors
// Copyright 2022-2023, Optimizely, Inc. and contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -36,15 +36,25 @@ public struct OptimizelySdkSettings {
/// - timeoutForSegmentFetchInSecs: The timeout in seconds of odp segment fetch (optional. default = 10) - OS default timeout will be used if this is set to zero.
/// - timeoutForOdpEventInSecs: The timeout in seconds of odp event dispatch (optional. default = 10) - OS default timeout will be used if this is set to zero.
/// - disableOdp: Set this flag to true (default = false) to disable ODP features
/// - sdkName: Set this flag to override sdkName included in events
/// - sdkVersion: Set this flag to override sdkVersion included in events
public init(segmentsCacheSize: Int = 100,
segmentsCacheTimeoutInSecs: Int = 600,
timeoutForSegmentFetchInSecs: Int = 10,
timeoutForOdpEventInSecs: Int = 10,
disableOdp: Bool = false) {
disableOdp: Bool = false,
sdkName: String? = nil,
sdkVersion: String? = nil) {
self.segmentsCacheSize = segmentsCacheSize
self.segmentsCacheTimeoutInSecs = segmentsCacheTimeoutInSecs
self.timeoutForSegmentFetchInSecs = timeoutForSegmentFetchInSecs
self.timeoutForOdpEventInSecs = timeoutForOdpEventInSecs
self.disableOdp = disableOdp
if let _sdkName = sdkName, _sdkName != "" {
Utils.swiftSdkClientName = _sdkName
}
if let _sdkVersion = sdkVersion, _sdkVersion != "" {
Utils.sdkVersion = _sdkVersion
}
}
}
2 changes: 1 addition & 1 deletion Sources/Optimizely/OptimizelyClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,7 @@ extension OptimizelyClient {

func fetchQualifiedSegments(userId: String,
options: [OptimizelySegmentOption],
completionHandler: @escaping ([String]?, OptimizelyError?) -> Void) {
completionHandler: @escaping ([String]?, OptimizelyError?) -> Void) {
odpManager.fetchQualifiedSegments(userId: userId,
options: options,
completionHandler: completionHandler)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Utils/Utils.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2019-2021, Optimizely, Inc. and contributors
// Copyright 2019-2021, 2023 Optimizely, Inc. and contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -28,7 +28,7 @@ class Utils {

// from auto-generated variable OPTIMIZELYSDKVERSION
static var sdkVersion: String = OPTIMIZELYSDKVERSION
static let swiftSdkClientName = "swift-sdk"
static var swiftSdkClientName = "swift-sdk"

static var os: String {
#if os(iOS)
Expand Down
29 changes: 28 additions & 1 deletion Tests/OptimizelyTests-APIs/OptimizelyClientTests_ODP.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2021, Optimizely, Inc. and contributors
// Copyright 2021, 2023 Optimizely, Inc. and contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -28,6 +28,11 @@ class OptimizelyClientTests_ODP: XCTestCase {
try! optimizely.start(datafile: datafile)
}

override func tearDown() {
Utils.sdkVersion = OPTIMIZELYSDKVERSION
Utils.swiftSdkClientName = "swift-sdk"
}

// MARK: - ODP configuration

func testSdkSettings_default() {
Expand Down Expand Up @@ -85,6 +90,20 @@ class OptimizelyClientTests_ODP: XCTestCase {
XCTAssertEqual([:], odpManager.eventData as! [String: String])
}

func testSendOdpEvent_customClientNameAndVersion() {
let odpEventManager = MockOdpEventManager(sdkKey: "any")
let odpManager = OdpManager(sdkKey: "any", disable: false, cacheSize: 12, cacheTimeoutInSecs: 123, eventManager: odpEventManager)

let datafile = OTUtils.loadJSONDatafile("decide_audience_segments")!
let tmpOptimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey, odpManager: odpManager, settings: OptimizelySdkSettings(sdkName: "flutter-sdk", sdkVersion: "1234"))
try! tmpOptimizely.start(datafile: datafile)

try? tmpOptimizely.sendOdpEvent(type: "t1", action: "a1", identifiers: ["k1": "v1"], data: ["k21": "v2", "k22": true, "k23": 3.5, "k24": nil])

XCTAssertEqual(odpEventManager.receivedData["data_source_version"] as! String, "1234")
XCTAssertEqual(odpEventManager.receivedData["data_source"] as! String, "flutter-sdk")
}

func testSendOdpEvent_error() {
var optimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey)

Expand Down Expand Up @@ -237,4 +256,12 @@ extension OptimizelyClientTests_ODP {
}
}

class MockOdpEventManager: OdpEventManager {
var receivedData: [String: Any?]!

override func dispatch(_ event: OdpEvent) {
self.receivedData = event.data
}
}

}
143 changes: 142 additions & 1 deletion Tests/OptimizelyTests-Common/BatchEventBuilderTests_Events.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2019-2021, Optimizely, Inc. and contributors
// Copyright 2019-2021, 2023 Optimizely, Inc. and contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -35,6 +35,11 @@ class BatchEventBuilderTests_Events: XCTestCase {
project = optimizely.config!.project!
}

override func tearDown() {
Utils.sdkVersion = OPTIMIZELYSDKVERSION
Utils.swiftSdkClientName = "swift-sdk"
}

func testCreateImpressionEvent() {
let expVariationId = "10416523162"
let expCampaignId = "10420273929"
Expand Down Expand Up @@ -99,6 +104,80 @@ class BatchEventBuilderTests_Events: XCTestCase {
XCTAssertNil(de["value"])
}

func testCreateImpressionEventCustomClientNameAndVersion() {
// Needed custom instances to avoid breaking original tests
let eventDispatcher = MockEventDispatcher()
let optimizely = OTUtils.createOptimizely(datafileName: "audience_targeting",
clearUserProfileService: true,
eventDispatcher: eventDispatcher,
settings: OptimizelySdkSettings(sdkName: "flutter-sdk", sdkVersion: "1234"))!

let expVariationId = "10416523162"
let expCampaignId = "10420273929"
let expExperimentId = "10390977714"

let attributes: [String: Any] = [
"s_foo": "foo",
"b_true": true,
"i_42": 42,
"d_4_2": 4.2
]

_ = try! optimizely.activate(experimentKey: experimentKey,
userId: userId,
attributes: attributes)

// Skipped getFirstEventJSON as it uses the class level optimizely instance
optimizely.eventLock.sync{}
let rawEvent: EventForDispatch = eventDispatcher.events.first!
let event = try! JSONSerialization.jsonObject(with: rawEvent.body, options: .allowFragments) as! [String: Any]

XCTAssertEqual((event["revision"] as! String), project.revision)
XCTAssertEqual((event["account_id"] as! String), project.accountId)
XCTAssertEqual(event["client_version"] as! String, "1234")
XCTAssertEqual(event["project_id"] as! String, project.projectId)
XCTAssertEqual(event["client_name"] as! String, "flutter-sdk")
XCTAssertEqual(event["anonymize_ip"] as! Bool, project.anonymizeIP)
XCTAssertEqual(event["enrich_decisions"] as! Bool, true)

let visitor = (event["visitors"] as! Array<Dictionary<String, Any>>)[0]

XCTAssertEqual(visitor["visitor_id"] as! String, userId)

let snapshot = (visitor["snapshots"] as! Array<Dictionary<String, Any>>)[0]

// attributes contents are tested separately in "BatchEventBuilder_Attributes.swift"
let eventAttributes = visitor["attributes"] as! Array<Dictionary<String, Any>>
XCTAssertEqual(eventAttributes.count, attributes.count)

let decision = (snapshot["decisions"] as! Array<Dictionary<String, Any>>)[0]

XCTAssertEqual(decision["variation_id"] as! String, expVariationId)
XCTAssertEqual(decision["campaign_id"] as! String, expCampaignId)
XCTAssertEqual(decision["experiment_id"] as! String, expExperimentId)

let metaData = decision["metadata"] as! Dictionary<String, Any>
XCTAssertEqual(metaData["rule_type"] as! String, Constants.DecisionSource.experiment.rawValue)
XCTAssertEqual(metaData["rule_key"] as! String, "ab_running_exp_audience_combo_exact_foo_or_true__and__42_or_4_2")
XCTAssertEqual(metaData["flag_key"] as! String, "")
XCTAssertEqual(metaData["variation_key"] as! String, "all_traffic_variation")
XCTAssertTrue(metaData["enabled"] as! Bool)

let de = (snapshot["events"] as! Array<Dictionary<String, Any>>)[0]

XCTAssertEqual(de["entity_id"] as! String, expCampaignId)
XCTAssertEqual(de["key"] as! String, "campaign_activated")
let expTimestamp = Int64((Date.timeIntervalSinceReferenceDate + Date.timeIntervalBetween1970AndReferenceDate) * 1000)
// divide by 1000 to ignore small time difference
XCTAssertEqual((de["timestamp"] as! Int64)/1000, expTimestamp / 1000)
// cannot validate randomly-generated string. check if long enough.
XCTAssert((de["uuid"] as! String).count > 20)
// event tags are tested separately below
XCTAssert((de["tags"] as! Dictionary<String, Any>).count==0)
XCTAssertNil(de["revenue"])
XCTAssertNil(de["value"])
}

func testCreateImpressionEventWithoutVariation() {
let attributes: [String: Any] = [
"s_foo": "foo",
Expand Down Expand Up @@ -196,6 +275,68 @@ class BatchEventBuilderTests_Events: XCTestCase {
XCTAssertNil(de["value"])
}

func testCreateConversionEventCustomClientNameAndVersion() {
// Needed custom instances to avoid breaking original tests
let eventDispatcher = MockEventDispatcher()
let optimizely = OTUtils.createOptimizely(datafileName: "audience_targeting",
clearUserProfileService: true,
eventDispatcher: eventDispatcher,
settings: OptimizelySdkSettings(sdkName: "flutter-sdk", sdkVersion: "1234"))!

let eventKey = "event_single_targeted_exp"
let eventId = "10404198135"

let attributes: [String: Any] = ["s_foo": "bar"]
let eventTags: [String: Any] = ["browser": "chrome"]

try! optimizely.track(eventKey: eventKey,
userId: userId,
attributes: attributes,
eventTags: eventTags)

// Skipped getFirstEventJSON as it uses the class level optimizely instance
optimizely.eventLock.sync{}
let rawEvent: EventForDispatch = eventDispatcher.events.first!
let event = try! JSONSerialization.jsonObject(with: rawEvent.body, options: .allowFragments) as! [String: Any]

XCTAssertEqual(event["revision"] as! String, project.revision)
XCTAssertEqual(event["account_id"] as! String, project.accountId)
XCTAssertEqual(event["client_version"] as! String, "1234")
XCTAssertEqual(event["project_id"] as! String, project.projectId)
XCTAssertEqual(event["client_name"] as! String, "flutter-sdk")
XCTAssertEqual(event["anonymize_ip"] as! Bool, project.anonymizeIP)
XCTAssertEqual(event["enrich_decisions"] as! Bool, true)

let visitor = (event["visitors"] as! Array<Dictionary<String, Any>>)[0]

XCTAssertEqual(visitor["visitor_id"] as! String, userId)

let snapshot = (visitor["snapshots"] as! Array<Dictionary<String, Any>>)[0]

// attributes contents are tested separately in "BatchEventBuilder_Attributes.swift"
let eventAttributes = visitor["attributes"] as! Array<Dictionary<String, Any>>
XCTAssertEqual(eventAttributes[0]["key"] as! String, "s_foo")
XCTAssertEqual(eventAttributes[0]["value"] as! String, "bar")

let decisions = snapshot["decisions"]

XCTAssertNil(decisions)

let de = (snapshot["events"] as! Array<Dictionary<String, Any>>)[0]

XCTAssertEqual(de["entity_id"] as! String, eventId)
XCTAssertEqual(de["key"] as! String, eventKey)
let expTimestamp = Int64((Date.timeIntervalSinceReferenceDate + Date.timeIntervalBetween1970AndReferenceDate) * 1000)
// divide by 1000 to ignore small time difference
XCTAssertEqual((de["timestamp"] as! Int64)/1000, expTimestamp / 1000)
// cannot validate randomly-generated string. check if long enough.
XCTAssert((de["uuid"] as!String).count > 20)
// {tags, revenue, value} are tested separately below
XCTAssertEqual((de["tags"] as! Dictionary<String, Any>)["browser"] as! String, "chrome")
XCTAssertNil(de["revenue"])
XCTAssertNil(de["value"])
}

func testCreateConversionEventWhenEventKeyInvalid() {
let eventKey = "not-existing-key"

Expand Down
8 changes: 5 additions & 3 deletions Tests/TestUtils/OTUtils.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2019, 2021, Optimizely, Inc. and contributors
// Copyright 2019, 2021, 2023 Optimizely, Inc. and contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -137,7 +137,8 @@ class OTUtils {
static func createOptimizely(datafileName: String,
clearUserProfileService: Bool,
eventDispatcher: OPTEventDispatcher?=nil,
logger: OPTLogger?=nil) -> OptimizelyClient? {
logger: OPTLogger?=nil,
settings: OptimizelySdkSettings?=nil) -> OptimizelyClient? {

guard let datafile = OTUtils.loadJSONDatafile(datafileName) else { return nil }
let userProfileService = clearUserProfileService ? createClearUserProfileService() : nil
Expand All @@ -147,7 +148,8 @@ class OTUtils {
let optimizely = OptimizelyClient(sdkKey: randomSdkKey,
logger: logger,
eventDispatcher: eventDispatcher,
userProfileService: userProfileService)
userProfileService: userProfileService,
settings: settings)
do {
try optimizely.start(datafile: datafile, doFetchDatafileBackground: false)
return optimizely
Expand Down