-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3aff042
commit 640feaf
Showing
30 changed files
with
924 additions
and
315 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import Foundation | ||
|
||
protocol DDNAEventTriggeredCampaignMetricStoreProtocol { | ||
func incrementETCExecutionCount(forVariantId variantId: UInt) | ||
func getETCExecutionCount(variantId: UInt) -> Int | ||
} | ||
|
||
@objc public class DDNAEventTriggeredCampaignMetricStore: NSObject, DDNAEventTriggeredCampaignMetricStoreProtocol { | ||
@objc public static let sharedInstance = DDNAEventTriggeredCampaignMetricStore() | ||
|
||
private let storePath: URL | ||
private var store: [String: Int] = [:] | ||
|
||
@objc public init(persistenceFilePath: URL = URL(fileURLWithPath: DDNASettings.getPrivateSettingsDirectoryPath()).appendingPathComponent("ddnaETCCountStore")) { | ||
storePath = persistenceFilePath | ||
|
||
super.init() | ||
|
||
readCountFromFile() | ||
} | ||
|
||
@objc public func incrementETCExecutionCount(forVariantId variantId: UInt) { | ||
let key = convertToKey(variantId) | ||
let previousValue = self.store[key] ?? 0 | ||
self.store[key] = previousValue + 1 | ||
self.writeCountToFile() | ||
} | ||
|
||
public func getETCExecutionCount(variantId: UInt) -> Int { | ||
return self.store[convertToKey(variantId)] ?? 0 | ||
} | ||
|
||
func writeCountToFile() { | ||
do { | ||
let data = try JSONSerialization.data(withJSONObject: store, options: .init()) | ||
try data.write(to: storePath) | ||
} catch { | ||
NSLog("Failed to write ETC count to file, campaign count has not been persisted.") | ||
} | ||
} | ||
|
||
func readCountFromFile() { | ||
do { | ||
if FileManager.default.fileExists(atPath: storePath.path) { | ||
let data = try Data(contentsOf: storePath) | ||
store = try (JSONSerialization.jsonObject(with: data, options: .init()) as? [String: Int]) ?? [:] | ||
} else { | ||
store = [:] | ||
} | ||
} catch { | ||
NSLog("Failed to read ETC count from file, campaign count has been reset.") | ||
store = [:] | ||
} | ||
} | ||
|
||
func convertToKey(_ intRepresentation: UInt) -> String { | ||
// Because deltaDNA uses UInts for keys here but Apple wants string keys to persist, | ||
// we need to convert it here. We can't use an array as we can't guarantee the Uints to | ||
// be in a particular range. | ||
return String(intRepresentation) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import Foundation | ||
@testable import DeltaDNA | ||
import XCTest | ||
|
||
class DDNAEventTriggeredCampaignMetricStoreTests: XCTestCase { | ||
var metricStore: DDNAEventTriggeredCampaignMetricStore! | ||
|
||
let tempFilePath = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent("metricStoreTestTemp") | ||
|
||
override func setUp() { | ||
metricStore = DDNAEventTriggeredCampaignMetricStore(persistenceFilePath: tempFilePath) | ||
} | ||
|
||
override func tearDownWithError() throws { | ||
if FileManager.default.fileExists(atPath: tempFilePath.path) { | ||
try FileManager.default.removeItem(at: tempFilePath) | ||
} | ||
} | ||
|
||
func test_gettingTheCountOfAnUnknownVariant_returns0() { | ||
XCTAssertEqual(metricStore.getETCExecutionCount(variantId: 1), 0) | ||
} | ||
|
||
func test_incrementingAPreviouslyUnknownVariant_setsCountTo1() { | ||
metricStore.incrementETCExecutionCount(forVariantId: 1) | ||
|
||
XCTAssertEqual(metricStore.getETCExecutionCount(variantId: 1), 1) | ||
} | ||
|
||
func test_incrementingAPreviouslySetVariant_returnsTheCurrentCount() { | ||
metricStore.incrementETCExecutionCount(forVariantId: 1) | ||
metricStore.incrementETCExecutionCount(forVariantId: 1) | ||
|
||
XCTAssertEqual(metricStore.getETCExecutionCount(variantId: 1), 2) | ||
} | ||
|
||
func test_countsForVariantsShouldPersist() { | ||
metricStore.incrementETCExecutionCount(forVariantId: 1) | ||
metricStore.incrementETCExecutionCount(forVariantId: 1) | ||
|
||
// Delete the cached metric store to force rereading from disk | ||
metricStore = nil | ||
metricStore = DDNAEventTriggeredCampaignMetricStore(persistenceFilePath: tempFilePath) | ||
|
||
XCTAssertEqual(metricStore.getETCExecutionCount(variantId: 1), 2) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import Foundation | ||
|
||
@objc public class DDNAExecutionCountTriggerCondition: NSObject, DDNATriggerCondition { | ||
let executionsRequiredCount: Int | ||
let metricStore: DDNAEventTriggeredCampaignMetricStoreProtocol | ||
let variantId: UInt | ||
|
||
init(executionsRequiredCount: Int, metricStore: DDNAEventTriggeredCampaignMetricStoreProtocol, variantId: UInt) { | ||
self.executionsRequiredCount = executionsRequiredCount | ||
self.variantId = variantId | ||
self.metricStore = metricStore | ||
} | ||
|
||
@objc public func canExecute() -> Bool { | ||
return executionsRequiredCount == metricStore.getETCExecutionCount(variantId: variantId) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import Foundation | ||
import XCTest | ||
@testable import DeltaDNA | ||
|
||
class DDNAExecutionCountTriggerConditionTests: XCTestCase { | ||
var mockMetricStore: DDNAEventTriggeredCampaignMetricStoreMock! | ||
|
||
override func setUp() { | ||
mockMetricStore = DDNAEventTriggeredCampaignMetricStoreMock() | ||
} | ||
|
||
func test_ifNotEnoughExecutionsHaveOccurred_cannotExecute() { | ||
let condition = DDNAExecutionCountTriggerCondition(executionsRequiredCount: 3, metricStore: mockMetricStore, variantId: 1) | ||
|
||
mockMetricStore.currentETCCount = 1 | ||
|
||
XCTAssertFalse(condition.canExecute()) | ||
} | ||
|
||
func test_ifEnoughExecutionsHaveOccurred_canExecute() { | ||
let condition = DDNAExecutionCountTriggerCondition(executionsRequiredCount: 3, metricStore: mockMetricStore, variantId: 1) | ||
|
||
mockMetricStore.currentETCCount = 3 | ||
|
||
XCTAssertTrue(condition.canExecute()) | ||
} | ||
|
||
func test_ifTooManyExecutionsHaveOccurred_cannotExecute() { | ||
let condition = DDNAExecutionCountTriggerCondition(executionsRequiredCount: 3, metricStore: mockMetricStore, variantId: 1) | ||
|
||
mockMetricStore.currentETCCount = 5 | ||
|
||
XCTAssertFalse(condition.canExecute()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import Foundation | ||
|
||
@objc public class DDNAExecutionRepeatTriggerCondition: NSObject, DDNATriggerCondition { | ||
let repeatInterval: Int | ||
let repeatTimesLimit: Int | ||
let metricStore: DDNAEventTriggeredCampaignMetricStoreProtocol | ||
let variantId: UInt | ||
|
||
init(repeatInterval: Int, repeatTimesLimit: Int, metricStore: DDNAEventTriggeredCampaignMetricStoreProtocol, variantId: UInt) { | ||
self.repeatInterval = repeatInterval | ||
self.repeatTimesLimit = repeatTimesLimit | ||
self.variantId = variantId | ||
self.metricStore = metricStore | ||
} | ||
|
||
@objc public func canExecute() -> Bool { | ||
let execCount = metricStore.getETCExecutionCount(variantId: variantId) | ||
|
||
let execCountIsOnInterval = execCount % repeatInterval == 0 | ||
let thereIsNoRepeatLimit = repeatTimesLimit < 1 | ||
let thereAreRepeatCountsRemaining = repeatTimesLimit * repeatInterval >= execCount | ||
|
||
return execCountIsOnInterval && (thereIsNoRepeatLimit || thereAreRepeatCountsRemaining) | ||
} | ||
} |
Oops, something went wrong.