This PR contains all the work related to setting up this project as required to implement the [Assignment](https://repo.rock-n-code.com/rock-n-code/deep-linking-assignment/wiki/Assignment) on top, as intended. To summarise this work: - [x] created a new **Xcode** project; - [x] cloned the `Wikipedia` app and inserted it into the **Xcode** project; - [x] created the `Locations` app and also, its `Libraries` package; - [x] created the `Shared` package to share dependencies between the apps; - [x] added a `Makefile` file and implemented some **environment** and **help** commands. Co-authored-by: Javier Cicchelli <javier@rock-n-code.com> Reviewed-on: rock-n-code/deep-linking-assignment#1
124 lines
4.2 KiB
Swift
124 lines
4.2 KiB
Swift
import Foundation
|
|
|
|
@objc public protocol ABTestsPersisting: AnyObject {
|
|
func libraryValue(for key: String) -> NSCoding?
|
|
func setLibraryValue(_ value: NSCoding?, for key: String)
|
|
}
|
|
|
|
@objc(WMFABTestsController)
|
|
public class ABTestsController: NSObject {
|
|
|
|
enum ABTestsError: Error {
|
|
case invalidPercentage
|
|
}
|
|
|
|
struct ExperimentConfig {
|
|
let experiment: Experiment
|
|
let percentageKey: PercentageKey
|
|
let bucketKey: BucketKey
|
|
let bucketValueControl: BucketValue
|
|
let bucketValueTest: BucketValue
|
|
}
|
|
|
|
public enum Experiment {
|
|
case articleAsLivingDoc
|
|
|
|
var config: ExperimentConfig {
|
|
switch self {
|
|
case .articleAsLivingDoc:
|
|
return ABTestsController.articleAsLivingDocConfig
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum PercentageKey: String {
|
|
case articleAsLivingDocPercentKey
|
|
}
|
|
|
|
enum BucketKey: String {
|
|
case articleAsLivingDocBucketKey
|
|
}
|
|
|
|
public enum BucketValue: String {
|
|
case articleAsLivingDocTest = "LivingDoc_Test"
|
|
case articleAsLivingDocControl = "LivingDoc_Control"
|
|
}
|
|
|
|
private static let articleAsLivingDocConfig = ExperimentConfig(experiment: .articleAsLivingDoc, percentageKey: .articleAsLivingDocPercentKey, bucketKey: .articleAsLivingDocBucketKey, bucketValueControl: .articleAsLivingDocControl, bucketValueTest: .articleAsLivingDocTest)
|
|
|
|
private let persistanceService: ABTestsPersisting
|
|
|
|
@objc public init(persistanceService: ABTestsPersisting) {
|
|
self.persistanceService = persistanceService
|
|
super.init()
|
|
}
|
|
|
|
// this will only generate a new bucket as needed (i.e. if the percentage is different than the last time bucket was generated)
|
|
@discardableResult
|
|
func determineBucketForExperiment(_ experiment: Experiment, withPercentage percentage: NSNumber) throws -> BucketValue {
|
|
|
|
guard percentage.intValue >= 0 && percentage.intValue <= 100 else {
|
|
throw ABTestsError.invalidPercentage
|
|
}
|
|
|
|
// if we have previously generated a bucket with the same percentage, return that value
|
|
let maybeOldPercentage = percentageForExperiment(experiment)
|
|
let maybeOldBucket = bucketForExperiment(experiment)
|
|
|
|
if let oldPercentage = maybeOldPercentage,
|
|
let oldBucket = maybeOldBucket,
|
|
oldPercentage == percentage {
|
|
return oldBucket
|
|
}
|
|
|
|
// otherwise generate new bucket
|
|
let randomInt = Int.random(in: 1...100)
|
|
let isInTest = randomInt <= percentage.intValue
|
|
let bucket: BucketValue
|
|
|
|
switch experiment {
|
|
case .articleAsLivingDoc:
|
|
bucket = isInTest ? .articleAsLivingDocTest : .articleAsLivingDocControl
|
|
}
|
|
|
|
setBucket(bucket, forExperiment: experiment)
|
|
try setPercentage(percentage, forExperiment: experiment)
|
|
|
|
return bucket
|
|
}
|
|
|
|
// MARK: Persistence setters/getters
|
|
|
|
func percentageForExperiment(_ experiment: Experiment) -> NSNumber? {
|
|
|
|
let key = experiment.config.percentageKey.rawValue
|
|
return persistanceService.libraryValue(for: key) as? NSNumber
|
|
}
|
|
|
|
func setPercentage(_ percentage: NSNumber, forExperiment experiment: Experiment) throws {
|
|
|
|
guard percentage.intValue >= 0 && percentage.intValue <= 100 else {
|
|
throw ABTestsError.invalidPercentage
|
|
}
|
|
|
|
let key = experiment.config.percentageKey.rawValue
|
|
persistanceService.setLibraryValue(percentage, for: key)
|
|
}
|
|
|
|
public func bucketForExperiment(_ experiment: Experiment) -> BucketValue? {
|
|
|
|
let key = experiment.config.bucketKey.rawValue
|
|
guard let rawValue = persistanceService.libraryValue(for: key) as? String else {
|
|
return nil
|
|
}
|
|
|
|
return BucketValue(rawValue: rawValue)
|
|
}
|
|
|
|
func setBucket(_ bucket: BucketValue, forExperiment experiment: Experiment) {
|
|
|
|
let key = experiment.config.bucketKey.rawValue
|
|
persistanceService.setLibraryValue((bucket.rawValue as NSString), for: key)
|
|
}
|
|
}
|