Skip to content

Commit d772ee2

Browse files
[FSSDK-11454] Swift - Add SDK Multi-Region Support for Data Hosting (#606)
* [FSSDK-11454] Swift - Add SDK Multi-Region Support for Data Hosting * Fix test errors * Fix errors * Fix missing arg error * Implement copilot reviews * Fix errors * Fix region error * feat(EventForDispatch): add Objective-C initializer * Add test cases for invalid region * Implemented comments * Remove unused region arg * Implement suggested cleanup and change * Remove duplicate method * Remove unnecessary getEndpoint method --------- Co-authored-by: muzahidul-opti <[email protected]>
1 parent 6fd56c3 commit d772ee2

File tree

11 files changed

+456
-13
lines changed

11 files changed

+456
-13
lines changed

Sources/Data Model/DispatchEvents/BatchEvent.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ struct BatchEvent: Codable, Equatable {
2525
let clientName: String
2626
let anonymizeIP: Bool
2727
let enrichDecisions: Bool
28+
let region: String
2829

2930
enum CodingKeys: String, CodingKey {
3031
case revision
@@ -35,6 +36,7 @@ struct BatchEvent: Codable, Equatable {
3536
case clientName = "client_name"
3637
case anonymizeIP = "anonymize_ip"
3738
case enrichDecisions = "enrich_decisions"
39+
case region
3840
}
3941

4042
func getEventAttribute(key: String) -> EventAttribute? {

Sources/Data Model/DispatchEvents/EventForDispatch.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ import Foundation
1818

1919
@objcMembers public class EventForDispatch: NSObject, Codable {
2020
public static var eventEndpoint = "https://logx.optimizely.com/v1/events"
21+
public static var euEventEndpoint = "https://eu.logx.optimizely.com/v1/events"
2122

2223
public let url: URL
2324
public let body: Data
2425

25-
public init(url: URL? = nil, body: Data) {
26-
self.url = url ?? URL(string: EventForDispatch.eventEndpoint)!
26+
public init(url: URL? = nil, body: Data, region: Region = .US) {
27+
let endpoint = url?.absoluteString ?? (region == .US ? Self.eventEndpoint : Self.euEventEndpoint)
28+
self.url = URL(string: endpoint)!
2729
self.body = body
2830
}
2931

Sources/Data Model/Project.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616

1717
import Foundation
1818

19+
/// Optimizely region identifiers
20+
public enum Region: String, Codable, Equatable {
21+
case US
22+
case EU
23+
}
24+
1925
protocol ProjectProtocol {
2026
func evaluateAudience(audienceId: String, user: OptimizelyUserContext) throws -> Bool
2127
}
@@ -48,6 +54,8 @@ struct Project: Codable, Equatable {
4854
var environmentKey: String?
4955
// Holdouts
5056
var holdouts: [Holdout]
57+
// Region
58+
var region: Region?
5159
let logger = OPTLoggerFactory.getLogger()
5260

5361
// Required since logger is not decodable
@@ -57,7 +65,7 @@ struct Project: Codable, Equatable {
5765
// V3
5866
case anonymizeIP
5967
// V4
60-
case rollouts, integrations, typedAudiences, featureFlags, botFiltering, sendFlagDecisions, sdkKey, environmentKey, holdouts
68+
case rollouts, integrations, typedAudiences, featureFlags, botFiltering, sendFlagDecisions, sdkKey, environmentKey, holdouts, region
6169
}
6270

6371
init(from decoder: Decoder) throws {
@@ -88,6 +96,8 @@ struct Project: Codable, Equatable {
8896
environmentKey = try container.decodeIfPresent(String.self, forKey: .environmentKey)
8997
// Holdouts - defaults to empty array if key is not present
9098
holdouts = try container.decodeIfPresent([Holdout].self, forKey: .holdouts) ?? []
99+
// Region - defaults to US if not present
100+
region = try container.decodeIfPresent(Region.self, forKey: .region)
91101
}
92102

93103
// Required since logger is not equatable
@@ -97,7 +107,9 @@ struct Project: Codable, Equatable {
97107
lhs.accountId == rhs.accountId && lhs.events == rhs.events && lhs.revision == rhs.revision &&
98108
lhs.anonymizeIP == rhs.anonymizeIP && lhs.rollouts == rhs.rollouts &&
99109
lhs.integrations == rhs.integrations && lhs.typedAudiences == rhs.typedAudiences &&
100-
lhs.featureFlags == rhs.featureFlags && lhs.botFiltering == rhs.botFiltering && lhs.sendFlagDecisions == rhs.sendFlagDecisions && lhs.sdkKey == rhs.sdkKey && lhs.environmentKey == rhs.environmentKey
110+
lhs.featureFlags == rhs.featureFlags && lhs.botFiltering == rhs.botFiltering &&
111+
lhs.sendFlagDecisions == rhs.sendFlagDecisions && lhs.sdkKey == rhs.sdkKey &&
112+
lhs.environmentKey == rhs.environmentKey && lhs.region == rhs.region
101113
}
102114
}
103115

Sources/Data Model/ProjectConfig.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,13 @@ extension ProjectConfig {
215215

216216
extension ProjectConfig {
217217

218+
/**
219+
* Get the region value. Defaults to US if not specified in the project.
220+
*/
221+
public var region: Region {
222+
return project.region ?? .US
223+
}
224+
218225
/**
219226
* Get sendFlagDecisions value.
220227
*/

Sources/Extensions/ArrayEventForDispatch+Extension.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ extension Array where Element == EventForDispatch {
4545
var url: URL?
4646
var projectId: String?
4747
var revision: String?
48+
var region: String?
4849

4950
let checkUrl = { (event: EventForDispatch) -> Bool in
5051
if url == nil {
@@ -69,10 +70,18 @@ extension Array where Element == EventForDispatch {
6970
}
7071
return revision == batchEvent.revision
7172
}
73+
74+
let checkRegion = { (batchEvent: BatchEvent) -> Bool in
75+
if region == nil {
76+
region = batchEvent.region
77+
return region != nil
78+
}
79+
return region == batchEvent.region
80+
}
7281

7382
for event in self {
7483
if let batchEvent = try? JSONDecoder().decode(BatchEvent.self, from: event.body) {
75-
if !checkUrl(event) || !checkProjectId(batchEvent) || !checkRevision(batchEvent) {
84+
if !checkUrl(event) || !checkProjectId(batchEvent) || !checkRevision(batchEvent) || !checkRegion(batchEvent) {
7685
break
7786
}
7887

@@ -101,12 +110,13 @@ extension Array where Element == EventForDispatch {
101110
projectID: base.projectID,
102111
clientName: base.clientName,
103112
anonymizeIP: base.anonymizeIP,
104-
enrichDecisions: true)
113+
enrichDecisions: true,
114+
region: base.region)
105115

106116
guard let data = try? JSONEncoder().encode(batchEvent) else {
107117
return nil
108118
}
109-
110-
return EventForDispatch(url: url, body: data)
119+
120+
return EventForDispatch(url: url, body: data, region: Region(rawValue: base.region) ?? .US)
111121
}
112122
}

Sources/Implementation/Events/BatchEventBuilder.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class BatchEventBuilder {
8787
attributes: OptimizelyAttributes?,
8888
decisions: [Decision]?,
8989
dispatchEvents: [DispatchEvent]) -> Data? {
90+
let eventRegion = config.region
9091
let snapShot = Snapshot(decisions: decisions, events: dispatchEvents)
9192

9293
let eventAttributes = getEventAttributes(config: config, attributes: attributes)
@@ -100,9 +101,13 @@ class BatchEventBuilder {
100101
projectID: config.project.projectId,
101102
clientName: Utils.swiftSdkClientName,
102103
anonymizeIP: config.project.anonymizeIP,
103-
enrichDecisions: true)
104+
enrichDecisions: true,
105+
region: eventRegion.rawValue)
106+
107+
let data = try? JSONEncoder().encode(batchEvent)
108+
let eventForDispatch = EventForDispatch(url: nil, body: data ?? Data(), region: eventRegion)
104109

105-
return try? JSONEncoder().encode(batchEvent)
110+
return eventForDispatch.body
106111
}
107112

108113
// MARK: - Event Tags

Sources/Optimizely+Decide/OptimizelyUserContext+ObjC.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import Foundation
3535
public init(optimizely: OptimizelyClient, userId: String, attributes: [String: Any]? = nil) {
3636
userContext = OptimizelyUserContext(optimizely: optimizely, userId: userId, attributes: attributes)
3737
}
38-
38+
3939
public init(user: OptimizelyUserContext) {
4040
self.userContext = user
4141
}

Sources/Optimizely/OptimizelyClient+ObjC.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,3 +546,10 @@ extension OptimizelyClient {
546546
}
547547

548548
}
549+
550+
// MARK: - EventForDispatch Objective-C initializer
551+
extension EventForDispatch {
552+
@objc public convenience init(url: URL? = nil, body: Data) {
553+
self.init(url: url, body: body, region: .US)
554+
}
555+
}

Tests/OptimizelyTests-Batch-iOS/EventDispatcherTests_Batch.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -950,7 +950,8 @@ extension EventDispatcherTests_Batch {
950950
projectID: testProjectId,
951951
clientName: kClientName,
952952
anonymizeIP: kAnonymizeIP,
953-
enrichDecisions: kEnrichDecision)
953+
enrichDecisions: kEnrichDecision,
954+
region: "US")
954955
}
955956

956957
func dispatchMultipleEvents(_ events: [(url: String, event: BatchEvent)]) {

0 commit comments

Comments
 (0)