Skip to content

Commit

Permalink
Merge 88f0885 into 8b43600
Browse files Browse the repository at this point in the history
  • Loading branch information
jaeopt committed Apr 11, 2023
2 parents 8b43600 + 88f0885 commit 7ec4849
Show file tree
Hide file tree
Showing 11 changed files with 72 additions and 14 deletions.
1 change: 1 addition & 0 deletions Sources/Extensions/OptimizelyClient+Extension.swift
Expand Up @@ -51,6 +51,7 @@ extension OptimizelyClient {
/// - eventDispatcher: custom EventDispatcher (optional)
/// - datafileHandler: custom datafile handler (optional)
/// - userProfileService: custom UserProfileService (optional)
/// - odpManager: custom OdpManager (optional)
/// - periodicDownloadInterval: interval in secs for periodic background datafile download.
/// The recommended value is 10 * 60 secs (you can also set this to nil to use the recommended value).
/// Set this to 0 to disable periodic downloading.
Expand Down
2 changes: 1 addition & 1 deletion Sources/ODP/OdpEventApiManager.swift
Expand Up @@ -30,7 +30,7 @@ import Foundation
{"title":"Accepted","status":202,"timestamp":"2022-06-30T20:59:52.046Z"}
*/

public class OdpEventApiManager {
open class OdpEventApiManager {
let resourceTimeoutInSecs: Int?

/// OdpEventApiManager init
Expand Down
1 change: 1 addition & 0 deletions Sources/ODP/OdpEventManager.swift
Expand Up @@ -101,6 +101,7 @@ open class OdpEventManager {

// MARK: - dispatch

// open for FSC testing support
open func dispatch(_ event: OdpEvent) {
if eventQueue.count < maxQueueSize {
eventQueue.save(item: event)
Expand Down
26 changes: 22 additions & 4 deletions Sources/ODP/OdpManager.swift
Expand Up @@ -116,17 +116,35 @@ public class OdpManager {
/// - identifiers: a dictionary for identifiers.
/// - data: a dictionary for associated data. The default event data will be added to this data before sending to the ODP server.
/// - Throws: `OptimizelyError` if error is detected
func sendEvent(type: String, action: String, identifiers: [String: String], data: [String: Any?]) throws {
func sendEvent(type: String?, action: String, identifiers: [String: String], data: [String: Any?]) throws {
guard enabled else { throw OptimizelyError.odpNotEnabled }
guard odpConfig.eventQueueingAllowed else { throw OptimizelyError.odpNotIntegrated }
guard eventManager.isDataValidType(data) else { throw OptimizelyError.odpInvalidData }

if action.isEmpty { throw OptimizelyError.odpInvalidAction }

let typeUpdated = (type ?? "").isEmpty ? Constants.ODP.eventType : type!

// add vuid to all events by default

var identifiersWithVuid = identifiers
var identifiersUpdated = identifiers
if identifiers[Constants.ODP.keyForVuid] == nil {
identifiersWithVuid[Constants.ODP.keyForVuid] = vuidManager.vuid
identifiersUpdated[Constants.ODP.keyForVuid] = vuidManager.vuid
}

// replace aliases (fs-user-id, FS_USER_ID, FS-USER-ID) with "fs_user_id".

for (idKey, idValue) in identifiersUpdated {
if idKey == Constants.ODP.keyForUserId { break }

if [Constants.ODP.keyForUserId, Constants.ODP.keyForUserIdAlias].contains(idKey.lowercased()) {
identifiersUpdated.removeValue(forKey: idKey)
identifiersUpdated[Constants.ODP.keyForUserId] = idValue
break
}
}

eventManager.sendEvent(type: type, action: action, identifiers: identifiersWithVuid, data: data)
eventManager.sendEvent(type: typeUpdated, action: action, identifiers: identifiersUpdated, data: data)
}

func updateOdpConfig(apiKey: String?, apiHost: String?, segmentsToCheck: [String]) {
Expand Down
3 changes: 1 addition & 2 deletions Sources/ODP/OdpSegmentManager.swift
Expand Up @@ -16,7 +16,7 @@

import Foundation

public class OdpSegmentManager {
open class OdpSegmentManager {
var odpConfig = OdpConfig()
var segmentsCache: LruCache<String, [String]>
var apiMgr: OdpSegmentApiManager
Expand All @@ -33,7 +33,6 @@ public class OdpSegmentManager {
cacheTimeoutInSecs: Int,
apiManager: OdpSegmentApiManager? = nil,
resourceTimeoutInSecs: Int? = nil) {
self.odpConfig = odpConfig ?? OdpConfig()
self.apiMgr = apiManager ?? OdpSegmentApiManager(timeout: resourceTimeoutInSecs)

self.segmentsCache = LruCache<String, [String]>(size: cacheSize,
Expand Down
3 changes: 1 addition & 2 deletions Sources/Optimizely+Decide/OptimizelyClient+Decide.swift
Expand Up @@ -44,8 +44,7 @@ extension OptimizelyClient {
return createUserContext(userId: userId,
attributes: (attributes ?? [:]) as [String: Any])
}



/// Create a user context to be used internally without sending an ODP identify event.
///
/// - Parameters:
Expand Down
5 changes: 3 additions & 2 deletions Sources/Optimizely/OptimizelyClient.swift
Expand Up @@ -59,7 +59,7 @@ open class OptimizelyClient: NSObject {

var decisionService: OPTDecisionService!
public var notificationCenter: OPTNotificationCenter?
var odpManager: OdpManager!
public var odpManager: OdpManager!
let sdkSettings: OptimizelySdkSettings

// MARK: - Public interfaces
Expand All @@ -72,6 +72,7 @@ open class OptimizelyClient: NSObject {
/// - eventDispatcher: custom EventDispatcher (optional)
/// - datafileHandler: custom datafile handler (optional)
/// - userProfileService: custom UserProfileService (optional)
/// - odpManager: custom OdpManager (optional)
/// - defaultLogLevel: default log level (optional. default = .info)
/// - defaultDecisionOptions: default decision options (optional)
/// - settings: SDK configuration (optional)
Expand Down Expand Up @@ -941,7 +942,7 @@ extension OptimizelyClient {
action: String,
identifiers: [String: String] = [:],
data: [String: Any?] = [:]) throws {
try odpManager.sendEvent(type: type ?? Constants.ODP.eventType,
try odpManager.sendEvent(type: type,
action: action,
identifiers: identifiers,
data: data)
Expand Down
2 changes: 2 additions & 0 deletions Sources/Optimizely/OptimizelyError.swift
Expand Up @@ -88,6 +88,7 @@ public enum OptimizelyError: Error {
case odpNotIntegrated
case odpNotEnabled
case odpInvalidData
case odpInvalidAction
}

// MARK: - CustomStringConvertible
Expand Down Expand Up @@ -163,6 +164,7 @@ extension OptimizelyError: CustomStringConvertible, ReasonProtocol {
case .odpNotIntegrated: message = "ODP is not integrated."
case .odpNotEnabled: message = "ODP is not enabled."
case .odpInvalidData: message = "ODP data is not valid."
case .odpInvalidAction: message = "ODP action is not valid (cannot be empty)."
}

return message
Expand Down
1 change: 1 addition & 0 deletions Sources/Utils/Constants.swift
Expand Up @@ -26,6 +26,7 @@ struct Constants {
struct ODP {
static let keyForVuid = "vuid"
static let keyForUserId = "fs_user_id"
static let keyForUserIdAlias = "fs-user-id" // reserved (auto-converted to "fs_user_id")
static let eventType = "fullstack"
}

Expand Down
6 changes: 3 additions & 3 deletions Tests/OptimizelyTests-APIs/OptimizelyClientTests_ODP.swift
Expand Up @@ -79,7 +79,7 @@ class OptimizelyClientTests_ODP: XCTestCase {

try? optimizely.sendOdpEvent(action: "a2")

XCTAssertEqual("fullstack", odpManager.eventType)
XCTAssertEqual(nil, odpManager.eventType)
XCTAssertEqual("a2", odpManager.eventAction)
XCTAssertEqual([:], odpManager.eventIdentifiers)
XCTAssertEqual([:], odpManager.eventData as! [String: String])
Expand Down Expand Up @@ -165,7 +165,7 @@ class OptimizelyClientTests_ODP: XCTestCase {
XCTFail("Should accept all valid data value types.")
}
}

// MARK: - vuid

func testVuid() {
Expand Down Expand Up @@ -223,7 +223,7 @@ extension OptimizelyClientTests_ODP {
var apiHost: String?
var segmentsToCheck = [String]()

override func sendEvent(type: String, action: String, identifiers: [String : String], data: [String : Any?]) {
override func sendEvent(type: String?, action: String, identifiers: [String : String], data: [String : Any?]) {
self.eventType = type
self.eventAction = action
self.eventIdentifiers = identifiers
Expand Down
36 changes: 36 additions & 0 deletions Tests/OptimizelyTests-Common/OdpManagerTests.swift
Expand Up @@ -196,6 +196,42 @@ class OdpManagerTests: XCTestCase {

XCTAssertNil(eventManager.receivedType, "sendEvent requeut should be discarded if ODP disabled.")
}

func testSendEvent_emptyAction() {
do {
try manager.sendEvent(type: nil, action: "", identifiers: [:], data: [:])
XCTFail()
} catch OptimizelyError.odpInvalidAction {
XCTAssert(true)
} catch {
XCTFail("OptimizelyError expected if data has an empty action.")
}
}

func testSendEvent_emptyOrNilType() {
try? manager.sendEvent(type: nil, action: "a1", identifiers: [:], data: [:])
XCTAssertEqual(eventManager.receivedType, "fullstack")

try? manager.sendEvent(type: "", action: "a1", identifiers: [:], data: [:])
XCTAssertEqual(eventManager.receivedType, "fullstack")
}

func testSendEvent_aliasIdentifiers() {
try? manager.sendEvent(type: nil, action: "a1", identifiers: ["fs_user_id": "v1"], data: [:])
XCTAssertEqual(eventManager.receivedIdentifiers, ["fs_user_id": "v1", "vuid": manager.vuid])

try? manager.sendEvent(type: nil, action: "a1", identifiers: ["fs-user-id": "v1"], data: [:])
XCTAssertEqual(eventManager.receivedIdentifiers, ["fs_user_id": "v1", "vuid": manager.vuid])

try? manager.sendEvent(type: nil, action: "a1", identifiers: ["FS_USER_ID": "v1"], data: [:])
XCTAssertEqual(eventManager.receivedIdentifiers, ["fs_user_id": "v1", "vuid": manager.vuid])

try? manager.sendEvent(type: nil, action: "a1", identifiers: ["FS-USER-ID": "v1"], data: [:])
XCTAssertEqual(eventManager.receivedIdentifiers, ["fs_user_id": "v1", "vuid": manager.vuid])

try? manager.sendEvent(type: nil, action: "a1", identifiers: ["email": "e1", "FS-USER-ID": "v1"], data: [:])
XCTAssertEqual(eventManager.receivedIdentifiers, ["email": "e1", "fs_user_id": "v1", "vuid": manager.vuid])
}

// MARK: - updateConfig

Expand Down

0 comments on commit 7ec4849

Please sign in to comment.