Skip to content

Commit b953aa7

Browse files
Add eventID so that events can be timed independently of the event name.
1 parent 3d19f1f commit b953aa7

File tree

2 files changed

+87
-23
lines changed

2 files changed

+87
-23
lines changed

Sources/MixpanelInstance.swift

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ public protocol MixpanelDelegate: AnyObject {
3737

3838
public typealias Properties = [String: MixpanelType]
3939
typealias InternalProperties = [String: Any]
40-
typealias EventID = String
41-
typealias TimedEvents = [EventID: TimeInterval]
40+
typealias TimedEventID = String
41+
typealias TimedEvents = [TimedEventID: TimeInterval]
4242
typealias Queue = [InternalProperties]
4343

4444
protocol AppLifecycle {
@@ -1004,6 +1004,7 @@ extension MixpanelInstance {
10041004
}
10051005

10061006
extension MixpanelInstance {
1007+
10071008
// MARK: - Track
10081009

10091010
/**
@@ -1019,13 +1020,30 @@ extension MixpanelInstance {
10191020
- parameter properties: properties dictionary
10201021
*/
10211022
public func track(event: String?, properties: Properties? = nil) {
1023+
track(event: event, withID: nil, properties: properties)
1024+
}
1025+
1026+
/**
1027+
Tracks an event with properties.
1028+
Properties are optional and can be added only if needed.
1029+
1030+
Properties will allow you to segment your events in your Mixpanel reports.
1031+
Property keys must be String objects and the supported value types need to conform to MixpanelType.
1032+
MixpanelType can be either String, Int, UInt, Double, Float, Bool, [MixpanelType], [String: MixpanelType], Date, URL, or NSNull.
1033+
If the event is being timed, the timer will stop and be added as a property.
1034+
1035+
- parameter event: event name
1036+
- parameter withID: eventID used to link timed events previously timed using `time(eventID)`
1037+
- parameter properties: properties dictionary
1038+
*/
1039+
public func track(event: String?, withID eventID: String?, properties: Properties? = nil) {
10221040
if hasOptedOutTracking() {
10231041
return
10241042
}
10251043

10261044
let epochInterval = Date().timeIntervalSince1970
10271045

1028-
trackingQueue.async { [weak self, event, properties, epochInterval] in
1046+
trackingQueue.async { [weak self, event, eventID, properties, epochInterval] in
10291047
guard let self = self else {
10301048
return
10311049
}
@@ -1044,6 +1062,7 @@ extension MixpanelInstance {
10441062
alias: nil,
10451063
hadPersistedDistinctId: self.hadPersistedDistinctId)
10461064
let timedEventsSnapshot = self.trackInstance.track(event: event,
1065+
eventID: eventID,
10471066
properties: properties,
10481067
timedEvents: shadowTimedEvents,
10491068
superProperties: shadowSuperProperties,
@@ -1172,10 +1191,36 @@ extension MixpanelInstance {
11721191

11731192
*/
11741193
public func time(event: String) {
1194+
time(eventID: event)
1195+
}
1196+
1197+
/**
1198+
Starts a timer that will be stopped and added as a property when a
1199+
corresponding event with the identidal eventID is tracked.
1200+
1201+
This method is intended to be used in advance of events that have
1202+
a duration. For example, if a developer were to track an "Image Upload" event
1203+
she might want to also know how long the upload took. Calling this method
1204+
before the upload code would implicitly cause the `track`
1205+
call to record its duration.
1206+
1207+
- precondition:
1208+
// begin timing the image upload:
1209+
mixpanelInstance.time(eventID:"some-unique-id")
1210+
// upload the image:
1211+
self.uploadImageWithSuccessHandler() { _ in
1212+
// track the event
1213+
mixpanelInstance.track("Image Upload", withID: "some-unique-id")
1214+
}
1215+
1216+
- parameter eventID: the id of the event to be timed
1217+
1218+
*/
1219+
public func time(eventID: String) {
11751220
let startTime = Date().timeIntervalSince1970
1176-
trackingQueue.async { [weak self, startTime, event] in
1221+
trackingQueue.async { [weak self, startTime, eventID] in
11771222
guard let self = self else { return }
1178-
let timedEvents = self.trackInstance.time(event: event, timedEvents: self.timedEvents, startTime: startTime)
1223+
let timedEvents = self.trackInstance.time(eventID: eventID, timedEvents: self.timedEvents, startTime: startTime)
11791224
self.readWriteLock.write {
11801225
self.timedEvents = timedEvents
11811226
}
@@ -1189,12 +1234,21 @@ extension MixpanelInstance {
11891234
- parameter event: the name of the event to be tracked that was passed to time(event:)
11901235
*/
11911236
public func eventElapsedTime(event: String) -> Double {
1237+
eventElapsedTime(eventID: event)
1238+
}
1239+
1240+
/**
1241+
Retrieves the time elapsed for the event given it's ID since time(eventID:) was called.
1242+
1243+
- parameter event: the id of the event to be tracked that was passed to time(eventID:)
1244+
*/
1245+
public func eventElapsedTime(eventID: String) -> Double {
11921246
var timedEvents = TimedEvents()
11931247
self.readWriteLock.read {
11941248
timedEvents = self.timedEvents
11951249
}
11961250

1197-
if let startTime = timedEvents[event] {
1251+
if let startTime = timedEvents[eventID] {
11981252
return Date().timeIntervalSince1970 - startTime
11991253
}
12001254
return 0
@@ -1219,10 +1273,19 @@ extension MixpanelInstance {
12191273
- parameter event: the name of the event to clear the timer for
12201274
*/
12211275
public func clearTimedEvent(event: String) {
1222-
trackingQueue.async { [weak self, event] in
1276+
clearTimedEvent(eventId: event)
1277+
}
1278+
1279+
/**
1280+
Clears the event timer for the provided eventID.
1281+
1282+
- parameter event: the id of the event to clear the timer for
1283+
*/
1284+
public func clearTimedEvent(eventId: String) {
1285+
trackingQueue.async { [weak self, eventId] in
12231286
guard let self = self else { return }
12241287

1225-
let updatedTimedEvents = self.trackInstance.clearTimedEvent(event: event, timedEvents: self.timedEvents)
1288+
let updatedTimedEvents = self.trackInstance.clearTimedEvent(eventId: eventId, timedEvents: self.timedEvents)
12261289
MixpanelPersistence.saveTimedEvents(timedEvents: updatedTimedEvents, instanceName: self.name)
12271290
}
12281291
}

Sources/Track.swift

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,29 +32,30 @@ class Track {
3232
}
3333

3434
func track(event: String?,
35+
eventID: TimedEventID?,
3536
properties: Properties? = nil,
3637
timedEvents: TimedEvents,
3738
superProperties: InternalProperties,
3839
mixpanelIdentity: MixpanelIdentity,
3940
epochInterval: Double) -> TimedEvents {
40-
var ev = "mp_event"
41-
if let event = event {
42-
ev = event
41+
var eventName = "mp_event"
42+
if let event {
43+
eventName = event
4344
} else {
4445
Logger.info(message: "mixpanel track called with empty event parameter. using 'mp_event'")
4546
}
46-
if !(mixpanelInstance?.trackAutomaticEventsEnabled ?? false) && ev.hasPrefix("$ae_") {
47+
if !(mixpanelInstance?.trackAutomaticEventsEnabled ?? false) && eventName.hasPrefix("$ae_") {
4748
return timedEvents
4849
}
49-
let eventID = ev
50+
let timedEventID = eventID ?? eventName
5051
assertPropertyTypes(properties)
5152
#if DEBUG
52-
if !ev.hasPrefix("$") {
53+
if !eventName.hasPrefix("$") {
5354
UserDefaults.standard.set(true, forKey: InternalKeys.mpDebugTrackedKey)
5455
}
5556
#endif
5657
let epochMilliseconds = round(epochInterval * 1000)
57-
let eventStartTime = timedEvents[eventID]
58+
let eventStartTime = timedEvents[timedEventID]
5859
var p = InternalProperties()
5960
AutomaticProperties.automaticPropertiesLock.read {
6061
p += AutomaticProperties.properties
@@ -63,7 +64,7 @@ class Track {
6364
p["time"] = epochMilliseconds
6465
var shadowTimedEvents = timedEvents
6566
if let eventStartTime = eventStartTime {
66-
shadowTimedEvents.removeValue(forKey: eventID)
67+
shadowTimedEvents.removeValue(forKey: timedEventID)
6768
p["$duration"] = Double(String(format: "%.3f", epochInterval - eventStartTime))
6869
}
6970
p["distinct_id"] = mixpanelIdentity.distinctID
@@ -82,7 +83,7 @@ class Track {
8283
p += properties
8384
}
8485

85-
var trackEvent: InternalProperties = ["event": ev, "properties": p]
86+
var trackEvent: InternalProperties = ["event": eventName, "properties": p]
8687
metadata.toDict().forEach { (k, v) in trackEvent[k] = v }
8788

8889
self.mixpanelPersistence.saveEntity(trackEvent, type: .events)
@@ -140,16 +141,16 @@ class Track {
140141
update(&superProperties)
141142
}
142143

143-
func time(event: String?, timedEvents: TimedEvents, startTime: Double) -> TimedEvents {
144+
func time(eventID: TimedEventID, timedEvents: TimedEvents, startTime: TimeInterval) -> TimedEvents {
144145
if mixpanelInstance?.hasOptedOutTracking() ?? false {
145146
return timedEvents
146147
}
147148
var updatedTimedEvents = timedEvents
148-
guard let event = event, !event.isEmpty else {
149+
guard !eventID.isEmpty else {
149150
Logger.error(message: "mixpanel cannot time an empty event")
150151
return updatedTimedEvents
151152
}
152-
updatedTimedEvents[event] = startTime
153+
updatedTimedEvents[eventID] = startTime
153154
return updatedTimedEvents
154155
}
155156

@@ -159,13 +160,13 @@ class Track {
159160
return updatedTimedEvents
160161
}
161162

162-
func clearTimedEvent(event: String?, timedEvents: TimedEvents) -> TimedEvents {
163+
func clearTimedEvent(eventId: TimedEventID, timedEvents: TimedEvents) -> TimedEvents {
163164
var updatedTimedEvents = timedEvents
164-
guard let event = event, !event.isEmpty else {
165+
guard !eventId.isEmpty else {
165166
Logger.error(message: "mixpanel cannot clear an empty timed event")
166167
return updatedTimedEvents
167168
}
168-
updatedTimedEvents.removeValue(forKey: event)
169+
updatedTimedEvents.removeValue(forKey: eventId)
169170
return updatedTimedEvents
170171
}
171172
}

0 commit comments

Comments
 (0)