Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix First App Open not always being able to be triggered #496

Merged
merged 1 commit into from
Jan 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion MixpanelDemo/MixpanelDemo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
Mixpanel.initialize(token: "MIXPANEL_TOKEN")
Mixpanel.mainInstance().loggingEnabled = true


return true
}
}
Expand Down
43 changes: 23 additions & 20 deletions MixpanelDemo/MixpanelDemoTests/MixpanelAutomaticEventsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class MixpanelAutomaticEventsTests: MixpanelBaseTests {
waitForTrackingQueue(testMixpanel)

let event = eventQueue(token: testMixpanel.apiToken).last
let people1 = peopleQueue(token: testMixpanel.apiToken)[0]["$add"] as! InternalProperties
let people2 = peopleQueue(token: testMixpanel.apiToken)[1]["$add"] as! InternalProperties
let people1 = peopleQueue(token: testMixpanel.apiToken)[1]["$add"] as! InternalProperties
let people2 = peopleQueue(token: testMixpanel.apiToken)[2]["$add"] as! InternalProperties
XCTAssertEqual((people1["$ae_total_app_sessions"] as? NSNumber)?.intValue, 1, "total app sessions should be added by 1")
XCTAssertNotNil((people2["$ae_total_app_session_length"], "should have session length in $add queue"))
XCTAssertNotNil(event, "Should have an event")
Expand All @@ -42,16 +42,14 @@ class MixpanelAutomaticEventsTests: MixpanelBaseTests {

waitForTrackingQueue(testMixpanel)
let event = eventQueue(token: testMixpanel.apiToken).last
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 1, "automatic events should be accumulated if check decide is offline(decideInstance.automaticEventsEnabled is nil)")
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 2, "automatic events should be accumulated if check decide is offline(decideInstance.automaticEventsEnabled is nil)")
XCTAssertEqual(event?["event"] as? String, "$ae_session", "should be app session event")
removeDBfile(testMixpanel.apiToken)
}

func testDiscardAutomaticEventsIftrackAutomaticEventsEnabledIsFalse() {
let testMixpanel = Mixpanel.initialize(token: randomId(), flushInterval: 60)
let testMixpanel = Mixpanel.initialize(token: randomId(), flushInterval: 60, trackAutomaticEvents: false)
testMixpanel.minimumSessionDuration = 0;
testMixpanel.trackAutomaticEventsEnabled = false
MixpanelPersistence.saveAutomacticEventsEnabledFlag(value: true, fromDecide: true, apiToken: testMixpanel.apiToken)
testMixpanel.automaticEvents.perform(#selector(AutomaticEvents.appWillResignActive(_:)),
with: Notification(name: Notification.Name(rawValue: "test")))
waitForTrackingQueue(testMixpanel)
Expand All @@ -62,12 +60,10 @@ class MixpanelAutomaticEventsTests: MixpanelBaseTests {
func testFlushAutomaticEventsIftrackAutomaticEventsEnabledIsTrue() {
let testMixpanel = Mixpanel.initialize(token: randomId(), flushInterval: 60)
testMixpanel.minimumSessionDuration = 0;
testMixpanel.trackAutomaticEventsEnabled = true
MixpanelPersistence.saveAutomacticEventsEnabledFlag(value: false, fromDecide: true, apiToken: testMixpanel.apiToken)
testMixpanel.automaticEvents.perform(#selector(AutomaticEvents.appWillResignActive(_:)),
with: Notification(name: Notification.Name(rawValue: "test")))
waitForTrackingQueue(testMixpanel)
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 1, "automatic events should be tracked")
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 2, "automatic events should be tracked")

flushAndWaitForTrackingQueue(testMixpanel)
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 0, "automatic events should be flushed")
Expand All @@ -94,7 +90,7 @@ class MixpanelAutomaticEventsTests: MixpanelBaseTests {
testMixpanel.automaticEvents.perform(#selector(AutomaticEvents.appWillResignActive(_:)),
with: Notification(name: Notification.Name(rawValue: "test")))
waitForTrackingQueue(testMixpanel)
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 1, "automatic events should be tracked")
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 2, "automatic events should be tracked")

flushAndWaitForTrackingQueue(testMixpanel)
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 0, "automatic events should be flushed")
Expand All @@ -109,24 +105,31 @@ class MixpanelAutomaticEventsTests: MixpanelBaseTests {
XCTAssertEqual(appVersionValue as? String, savedVersionValue, "Saved version and current version need to be the same")
}

func testMultipleInstances() {
func testFirstAppShouldOnlyBeTrackedOnce() {
let testToken = randomId()
let mp = Mixpanel.initialize(token: testToken)
mp.minimumSessionDuration = 0;
waitForTrackingQueue(mp)
XCTAssertEqual(eventQueue(token: mp.apiToken).count, 1, "First app open should be tracked again")
flushAndWaitForTrackingQueue(mp)

let mp2 = Mixpanel.initialize(token: testToken)
mp2.minimumSessionDuration = 0;
waitForTrackingQueue(mp2)
XCTAssertEqual(eventQueue(token: mp2.apiToken).count, 0, "First app open should not be tracked again")
}

func testAutomaticEventsInMultipleInstances() {
// remove UserDefaults key and archive files to simulate first app open state
let defaults = UserDefaults(suiteName: "Mixpanel")
defaults?.removeObject(forKey: "MPFirstOpen")

let mp = Mixpanel.initialize(token: randomId())
mp.reset()
mp.minimumSessionDuration = 0;
waitForTrackingQueue(mp)
let mp2 = Mixpanel.initialize(token: randomId())
mp2.reset()
mp2.minimumSessionDuration = 0;

mp.automaticEvents.perform(#selector(AutomaticEvents.appDidBecomeActive(_:)),
with: Notification(name: Notification.Name(rawValue: "test")))
mp2.automaticEvents.perform(#selector(AutomaticEvents.appDidBecomeActive(_:)),
with: Notification(name: Notification.Name(rawValue: "test")))
mp.trackingQueue.sync { }
mp2.trackingQueue.sync { }
waitForTrackingQueue(mp2)

XCTAssertEqual(eventQueue(token: mp.apiToken).count, 1, "there should be only 1 event")
let appOpenEvent = eventQueue(token: mp.apiToken).last
Expand Down
2 changes: 2 additions & 0 deletions MixpanelDemo/MixpanelDemoTests/MixpanelBaseTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class MixpanelBaseTests: XCTestCase, MixpanelDelegate {
super.setUp()
stubCalls()
mixpanelWillFlush = false
let defaults = UserDefaults(suiteName: "Mixpanel")
defaults?.removeObject(forKey: "MPFirstOpen")

NSLog("finished test setup")
}
Expand Down
40 changes: 21 additions & 19 deletions MixpanelDemo/MixpanelDemoTests/MixpanelDemoTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class MixpanelDemoTests: MixpanelBaseTests {
XCTAssert(testMixpanel.flushInstance.flushRequest.networkConsecutiveFailures == 2,
"Network failures did not equal 2")

XCTAssert(eventQueue(token: testMixpanel.apiToken).count == 1,
XCTAssert(eventQueue(token: testMixpanel.apiToken).count == 2,
"Removed an event from the queue that was not sent")
removeDBfile(testMixpanel.apiToken)
}
Expand Down Expand Up @@ -122,20 +122,22 @@ class MixpanelDemoTests: MixpanelBaseTests {
testMixpanel.track(event: "event \(UInt(i))")
}
waitForTrackingQueue(testMixpanel)
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 50, "50 events should be queued up")
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 51, "51 events should be queued up")
flushAndWaitForTrackingQueue(testMixpanel)
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 50,
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 51,
"events should still be in the queue if flush fails")
removeDBfile(testMixpanel.apiToken)
}

func testFlushQueueContainsCorruptedEvent() {
let testMixpanel = Mixpanel.initialize(token: randomId(), flushInterval: 60)
stubTrack()
testMixpanel.mixpanelPersistence.saveEntity(["event": "bad event1", "properties": ["BadProp": Double.nan]], type: .events)
testMixpanel.mixpanelPersistence.saveEntity(["event": "bad event2", "properties": ["BadProp": Float.nan]], type: .events)
testMixpanel.mixpanelPersistence.saveEntity(["event": "bad event3", "properties": ["BadProp": Double.infinity]], type: .events)
testMixpanel.mixpanelPersistence.saveEntity(["event": "bad event4", "properties": ["BadProp": Float.infinity]], type: .events)
testMixpanel.trackingQueue.async {
testMixpanel.mixpanelPersistence.saveEntity(["event": "bad event1", "properties": ["BadProp": Double.nan]], type: .events)
testMixpanel.mixpanelPersistence.saveEntity(["event": "bad event2", "properties": ["BadProp": Float.nan]], type: .events)
testMixpanel.mixpanelPersistence.saveEntity(["event": "bad event3", "properties": ["BadProp": Double.infinity]], type: .events)
testMixpanel.mixpanelPersistence.saveEntity(["event": "bad event4", "properties": ["BadProp": Float.infinity]], type: .events)
}

for i in 0..<10 {
testMixpanel.track(event: "event \(UInt(i))")
Expand Down Expand Up @@ -185,7 +187,7 @@ class MixpanelDemoTests: MixpanelBaseTests {
testMixpanel.track(event: "event \(UInt(i))")
}
waitForTrackingQueue(testMixpanel)
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 10, "10 events should be queued up")
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 11, "11 events should be queued up")
flushAndWaitForTrackingQueue(testMixpanel)
for i in 0..<5 {
testMixpanel.track(event: "event \(UInt(i))")
Expand Down Expand Up @@ -220,7 +222,7 @@ class MixpanelDemoTests: MixpanelBaseTests {
testMixpanel.track(event: "e1")
waitForTrackingQueue(testMixpanel)
let eventsQueue = eventQueue(token: testMixpanel.apiToken)
XCTAssertTrue(eventsQueue.count == 1,
XCTAssertTrue(eventsQueue.count == 2 || eventsQueue.count == 1, // first app open should not be tracked for the second run,
"events should be sent right away with default distinct id")
#if MIXPANEL_UNIQUE_DISTINCT_ID
XCTAssertEqual((eventsQueue.last?["properties"] as? InternalProperties)?["distinct_id"] as? String,
Expand All @@ -236,7 +238,7 @@ class MixpanelDemoTests: MixpanelBaseTests {
var unidentifiedQueue = unIdentifiedPeopleQueue(token: testMixpanel.apiToken)
XCTAssertTrue(peopleQueue_value.isEmpty,
"people records should go to unidentified queue before identify:")
XCTAssertTrue(unidentifiedQueue.count == 1,
XCTAssertTrue(unidentifiedQueue.count == 2 || eventsQueue.count == 1, // first app open should not be tracked for the second run,
"unidentified people records not queued")
XCTAssertEqual(unidentifiedQueue.last?["$token"] as? String,
testMixpanel.apiToken,
Expand Down Expand Up @@ -622,7 +624,7 @@ class MixpanelDemoTests: MixpanelBaseTests {
"people distinct id failed to reset after archive")
XCTAssertTrue(testMixpanel2.currentSuperProperties().isEmpty,
"super properties failed to reset after archive")
XCTAssertTrue(eventQueue(token: testMixpanel2.apiToken).isEmpty,
XCTAssertTrue(eventQueue(token: testMixpanel2.apiToken).count == 1,
"events queue failed to reset after archive")
XCTAssertTrue(peopleQueue(token: testMixpanel2.apiToken).isEmpty,
"people queue failed to reset after archive")
Expand All @@ -645,7 +647,7 @@ class MixpanelDemoTests: MixpanelBaseTests {

let testMixpanel2 = Mixpanel.initialize(token: testToken, flushInterval: 60)
waitForTrackingQueue(testMixpanel2)
let properties: [String: Any] = eventQueue(token: testMixpanel2.apiToken)[0]["properties"] as! [String: Any]
let properties: [String: Any] = eventQueue(token: testMixpanel2.apiToken)[1]["properties"] as! [String: Any]

XCTAssertTrue(isBoolNumber(num: properties["p1"]! as! NSNumber),
"The bool value should be unarchived as bool")
Expand All @@ -663,7 +665,7 @@ class MixpanelDemoTests: MixpanelBaseTests {

func testArchive() {
let testToken = randomId()
let testMixpanel = Mixpanel.initialize(token: testToken, flushInterval: 60)
let testMixpanel = Mixpanel.initialize(token: testToken, flushInterval: 60, trackAutomaticEvents: false)
#if MIXPANEL_UNIQUE_DISTINCT_ID
XCTAssertEqual(testMixpanel.distinctId, testMixpanel.defaultDistinctId(),
"default distinct id archive failed")
Expand Down Expand Up @@ -691,7 +693,7 @@ class MixpanelDemoTests: MixpanelBaseTests {
waitForTrackingQueue(testMixpanel)
testMixpanel.timedEvents["e2"] = 5
testMixpanel.archive()
let testMixpanel2 = Mixpanel.initialize(token: testToken, flushInterval: 60)
let testMixpanel2 = Mixpanel.initialize(token: testToken, flushInterval: 60, trackAutomaticEvents: false)
waitForTrackingQueue(testMixpanel2)
sleep(1)
XCTAssertEqual(testMixpanel2.distinctId, "d1", "custom distinct archive failed")
Expand Down Expand Up @@ -719,11 +721,11 @@ class MixpanelDemoTests: MixpanelBaseTests {
"event was not successfully archived/unarchived or order is incorrect")
XCTAssertEqual(testMixpanel2.people.distinctId, "d1",
"custom people distinct id archive failed")
XCTAssertTrue(peopleQueue(token: testMixpanel2.apiToken).count == 1, "pending people queue archive failed")
XCTAssertTrue(peopleQueue(token: testMixpanel2.apiToken).count >= 1, "pending people queue archive failed")
XCTAssertEqual(testMixpanel2.timedEvents["e2"] as? Double, 5.0,
"timedEvents archive failed")

let testMixpanel3 = Mixpanel.initialize(token: testToken, flushInterval: 60)
let testMixpanel3 = Mixpanel.initialize(token: testToken, flushInterval: 60, trackAutomaticEvents: false)
XCTAssertEqual(testMixpanel3.distinctId, "d1", "expecting d1 as distinct id as initialised")
XCTAssertTrue(testMixpanel3.currentSuperProperties().count == 1,
"default super properties expected to have 1 item")
Expand All @@ -732,7 +734,7 @@ class MixpanelDemoTests: MixpanelBaseTests {
XCTAssertNotNil(testMixpanel3.people.distinctId,
"default people distinct id from no file failed")
XCTAssertNotNil(peopleQueue(token:testMixpanel3.apiToken), "default people queue from no file is nil")
XCTAssertTrue(peopleQueue(token:testMixpanel3.apiToken).count == 1, "default people queue expecting 1 item")
XCTAssertTrue(peopleQueue(token:testMixpanel3.apiToken).count >= 1, "default people queue expecting 1 item")
XCTAssertTrue(testMixpanel3.timedEvents.count == 1, "timedEvents expecting 1 item")
removeDBfile(testToken)
}
Expand All @@ -747,8 +749,8 @@ class MixpanelDemoTests: MixpanelBaseTests {
waitForTrackingQueue(testMixpanel)
sleep(1)
flushAndWaitForTrackingQueue(testMixpanel)
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 2, "delegate should have stopped flush")
XCTAssertTrue(peopleQueue(token: testMixpanel.apiToken).count == 1, "delegate should have stopped flush")
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 3, "delegate should have stopped flush")
XCTAssertTrue(peopleQueue(token: testMixpanel.apiToken).count == 2, "delegate should have stopped flush")
removeDBfile(testMixpanel.apiToken)
}

Expand Down
4 changes: 2 additions & 2 deletions MixpanelDemo/MixpanelDemoTests/MixpanelOptOutTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,10 @@ class MixpanelOptOutTests: MixpanelBaseTests {
let testMixpanel = Mixpanel.initialize(token: randomId())
testMixpanel.track(event: "a normal event")
waitForTrackingQueue(testMixpanel)
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 1, "events should be queued")
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 2, "events should be queued")
testMixpanel.optOutTracking()
waitForTrackingQueue(testMixpanel)
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 1, "When opted out, any events tracked before opted out should not be cleared")
XCTAssertTrue(eventQueue(token: testMixpanel.apiToken).count == 2, "When opted out, any events tracked before opted out should not be cleared")
removeDBfile(testMixpanel.apiToken)
}

Expand Down
4 changes: 2 additions & 2 deletions MixpanelDemo/MixpanelDemoTests/MixpanelPeopleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ class MixpanelPeopleTests: MixpanelBaseTests {
}
waitForTrackingQueue(testMixpanel)
sleep(1)
XCTAssertTrue(unIdentifiedPeopleQueue(token: testMixpanel.apiToken).count == 505)
var r: InternalProperties = unIdentifiedPeopleQueue(token: testMixpanel.apiToken).first!
XCTAssertTrue(unIdentifiedPeopleQueue(token: testMixpanel.apiToken).count == 506)
var r: InternalProperties = unIdentifiedPeopleQueue(token: testMixpanel.apiToken)[1]
XCTAssertEqual((r["$set"] as? InternalProperties)?["i"] as? Int, 0)
r = unIdentifiedPeopleQueue(token: testMixpanel.apiToken).last!
XCTAssertEqual((r["$set"] as? InternalProperties)?["i"] as? Int, 504)
Expand Down
28 changes: 12 additions & 16 deletions Sources/AutomaticEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,21 @@ class AutomaticEvents: NSObject, SKPaymentTransactionObserver, SKProductsRequest
var sessionLength: TimeInterval = 0
var sessionStartTime: TimeInterval = Date().timeIntervalSince1970
var hasAddedObserver = false
var firstAppOpen = false

let awaitingTransactionsWriteLock = DispatchQueue(label: "com.mixpanel.awaiting_transactions_writeLock", qos: .utility)

func initializeEvents() {
let firstOpenKey = "MPFirstOpen"
if let defaults = defaults, !defaults.bool(forKey: firstOpenKey) {
firstAppOpen = true
defaults.set(true, forKey: firstOpenKey)
defaults.synchronize()
func initializeEvents(apiToken: String) {
let legacyFirstOpenKey = "MPFirstOpen"
let firstOpenKey = "MPFirstOpen-\(apiToken)"
// do not track `$ae_first_open` again if the legacy key exist,
// but we will start using the key with the mixpanel token in favour of multiple instances support
if let defaults = defaults, !defaults.bool(forKey: legacyFirstOpenKey) {
if !defaults.bool(forKey: firstOpenKey) {
defaults.set(true, forKey: firstOpenKey)
defaults.synchronize()
delegate?.track(event: "$ae_first_open", properties: ["$ae_first_app_open_date": Date()])
delegate?.setOnce(properties: ["$ae_first_app_open_date": Date()])
}
}
if let defaults = defaults, let infoDict = Bundle.main.infoDictionary {
let appVersionKey = "MPAppVersion"
Expand Down Expand Up @@ -98,15 +103,6 @@ class AutomaticEvents: NSObject, SKPaymentTransactionObserver, SKProductsRequest

@objc func appDidBecomeActive(_ notification: Notification) {
sessionStartTime = Date().timeIntervalSince1970
if firstAppOpen {
if let allInstances = MixpanelManager.sharedInstance.getAllInstances() {
for instance in allInstances {
instance.track(event: "$ae_first_open", properties: ["$ae_first_app_open_date": Date()])
instance.setOnce(properties: ["$ae_first_app_open_date": Date()])
}
}
firstAppOpen = false
}
}

func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
Expand Down
Loading