Skip to content

Commit

Permalink
4.13.3
Browse files Browse the repository at this point in the history
  • Loading branch information
unity-thull committed Oct 6, 2021
1 parent 3aff042 commit 640feaf
Show file tree
Hide file tree
Showing 30 changed files with 924 additions and 315 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## [4.13.3](https://github.com/deltaDNA/ios-sdk/releases/tag/4.13.3) (2021-10-06)
### Fixed
- Event Triggered Campaigns will now respect repeat and interval triggers

## [4.13.2](https://github.com/deltaDNA/ios-sdk/releases/tag/4.13.2) (2021-07-06)
### Fixed
- Signal purchase generated events now include the correct `transactionServer` parameter
Expand Down
4 changes: 2 additions & 2 deletions DeltaDNA.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Pod::Spec.new do |spec|

# Spec Metadata
spec.name = "DeltaDNA"
spec.version = "4.13.2"
spec.version = "4.13.3"
spec.summary = "A gaming analytics platform."

spec.homepage = "https://deltadna.com"
Expand All @@ -18,7 +18,7 @@ Pod::Spec.new do |spec|
spec.platform = :ios

# Source Location
spec.source = { :http => "https://github.com/deltaDNA/ios-sdk/releases/download/4.13.2/DeltaDNA-4.13.2.zip" }
spec.source = { :http => "https://github.com/deltaDNA/ios-sdk/releases/download/4.13.3/DeltaDNA-4.13.3.zip" }

# Source Code
spec.vendored_frameworks = 'build/Frameworks/DeltaDNA.xcframework'
Expand Down
96 changes: 84 additions & 12 deletions DeltaDNA.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

22 changes: 9 additions & 13 deletions DeltaDNA.xcodeproj/xcshareddata/xcschemes/DeltaDNA iOS.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "636555F61C60F4BD004153E7"
BuildableName = "DeltaDNA.framework"
BlueprintName = "DeltaDNA iOS"
ReferencedContainer = "container:DeltaDNA.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO">
Expand All @@ -39,17 +48,6 @@
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "636555F61C60F4BD004153E7"
BuildableName = "DeltaDNA.framework"
BlueprintName = "DeltaDNA iOS"
ReferencedContainer = "container:DeltaDNA.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
Expand All @@ -70,8 +68,6 @@
ReferencedContainer = "container:DeltaDNA.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand Down
28 changes: 25 additions & 3 deletions DeltaDNA/DDNAEventTrigger.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#import "DDNAEventTrigger.h"
#import "DDNALog.h"
#import <DeltaDNA/DeltaDNA-Swift.h>

@interface DDNAEventTrigger ()

Expand All @@ -29,6 +30,7 @@ @interface DDNAEventTrigger ()
@property (nonatomic, strong) NSNumber *limit;
@property (nonatomic, strong) NSArray<NSDictionary *> *condition;
@property (nonatomic, assign) NSUInteger count;
@property (nonatomic, strong) NSArray<id<DDNATriggerCondition>> *campaignTriggerConditions;

@end

Expand All @@ -47,6 +49,13 @@ - (instancetype)initWithDictionary:(NSDictionary *)dictionary
self.limit = dictionary[@"limit"];
self.condition = [NSArray arrayWithArray:dictionary[@"condition"]];
self.count = 0;

NSDictionary *campaignConditionDictionary = dictionary[@"campaignExecutionConfig"];
if (campaignConditionDictionary != nil) {
DDNAEventTriggeredCampaignMetricStore *sharedMetricStore = [DDNAEventTriggeredCampaignMetricStore sharedInstance];
DDNATriggerConditionParser *parser = [[DDNATriggerConditionParser alloc] initWithMetricStore:sharedMetricStore variantId: self.variantId];
self.campaignTriggerConditions = [parser parseFromJSON: campaignConditionDictionary];
}
}
return self;
}
Expand Down Expand Up @@ -117,9 +126,22 @@ - (BOOL)respondsToEventSchema:(NSDictionary *)eventSchema
}
}

BOOL responding = (stack.count == 0) || ([stack.lastObject[@"b"] boolValue]);
if (responding) self.count++;
return responding;

if ((stack.count == 0) || ([stack.lastObject[@"b"] boolValue])) {
BOOL responding = self.campaignTriggerConditions.count == 0;
[[DDNAEventTriggeredCampaignMetricStore sharedInstance] incrementETCExecutionCountForVariantId:self.variantId];

for (id condition in self.campaignTriggerConditions) {
if ([condition canExecute]) {
responding = true;
}
}

if (responding) self.count++;
return responding;
} else {
return false;
}
}

- (NSNumber *)compareWithBoolOp:(NSString *)op left:(NSNumber *)left right:(NSNumber *)right
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ import XCTest
@testable import DeltaDNA

class DDNAEventTriggerTests: XCTestCase {
override func setUpWithError() throws {
// This isn't ideal, but is needed to reset the shared metric store between tests
// so we can do an integration test for the trigger conditions.
// It runs in the setup phase because we might have already polluted this file from other tests that
// don't mock the metric store.
let sharedMetricStorePath = URL(fileURLWithPath: DDNASettings.getPrivateSettingsDirectoryPath()).appendingPathComponent("ddnaETCCountStore")
if FileManager.default.fileExists(atPath: sharedMetricStorePath.path) {
try FileManager.default.removeItem(at: sharedMetricStorePath)
}
DDNAEventTriggeredCampaignMetricStore.sharedInstance.readCountFromFile()
}

func test_eventTrigger_buildsItselfFromAJsonDictionary() throws {
let response = [
Expand Down Expand Up @@ -246,6 +257,20 @@ class DDNAEventTriggerTests: XCTestCase {
XCTAssertTrue(cond(parameters: ["a":"b"], condition: [["p":"a"], ["i":5], ["o":"not equal to"]]))
}

func test_evaluatesCampaignTriggersAsExpected() {
let campaignTriggerConfig: [String : Any] = [
"showConditions": [
["executionsRequiredCount": "3"]
]
]
let json = ["campaignExecutionConfig": campaignTriggerConfig]
let trigger = DDNAEventTrigger(dictionary: json)!

XCTAssertFalse(trigger.responds(toEventSchema: [:]))
XCTAssertFalse(trigger.responds(toEventSchema: [:]))
XCTAssertTrue(trigger.responds(toEventSchema: [:]))
}

private func cond(parameters: [AnyHashable:Any], condition: [[String:Any]]) -> Bool {
let t: DDNAEventTrigger = DDNAEventTrigger(dictionary: ["condition" : condition])
return t.responds(toEventSchema: ["eventParams": parameters])
Expand Down
62 changes: 62 additions & 0 deletions DeltaDNA/DDNAEventTriggeredCampaignMetricStore.swift
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)
}
}
47 changes: 47 additions & 0 deletions DeltaDNA/DDNAEventTriggeredCampaignMetricStoreTests.swift
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)
}
}
17 changes: 17 additions & 0 deletions DeltaDNA/DDNAExecutionCountTriggerCondition.swift
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)
}
}
35 changes: 35 additions & 0 deletions DeltaDNA/DDNAExecutionCountTriggerConditionTests.swift
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())
}
}
25 changes: 25 additions & 0 deletions DeltaDNA/DDNAExecutionRepeatTriggerCondition.swift
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)
}
}
Loading

0 comments on commit 640feaf

Please sign in to comment.