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

Improve push tracking #879

Merged
merged 13 commits into from Feb 11, 2020
30 changes: 15 additions & 15 deletions HelloMixpanel/HelloMixpanel.xcodeproj/project.pbxproj
Expand Up @@ -1632,7 +1632,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = E8FVX7QLET;
GCC_PRECOMPILE_PREFIX_HEADER = NO;
Expand Down Expand Up @@ -1675,7 +1675,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = E8FVX7QLET;
GCC_PRECOMPILE_PREFIX_HEADER = NO;
Expand Down Expand Up @@ -1876,7 +1876,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = E8FVX7QLET;
GCC_PRECOMPILE_PREFIX_HEADER = NO;
Expand Down Expand Up @@ -1965,7 +1965,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = E8FVX7QLET;
GCC_C_LANGUAGE_STANDARD = gnu11;
Expand Down Expand Up @@ -2007,7 +2007,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = E8FVX7QLET;
ENABLE_NS_ASSERTIONS = NO;
Expand Down Expand Up @@ -2047,7 +2047,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = E8FVX7QLET;
ENABLE_NS_ASSERTIONS = NO;
Expand Down Expand Up @@ -2083,7 +2083,7 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEBUG_INFORMATION_FORMAT = dwarf;
DEFINES_MODULE = NO;
DEVELOPMENT_TEAM = E8FVX7QLET;
Expand Down Expand Up @@ -2125,7 +2125,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = NO;
DEVELOPMENT_TEAM = E8FVX7QLET;
Expand Down Expand Up @@ -2165,7 +2165,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = NO;
DEVELOPMENT_TEAM = E8FVX7QLET;
Expand Down Expand Up @@ -2334,7 +2334,7 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = E8FVX7QLET;
GCC_PREPROCESSOR_DEFINITIONS = (
Expand Down Expand Up @@ -2371,7 +2371,7 @@
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = E8FVX7QLET;
ENABLE_NS_ASSERTIONS = NO;
Expand Down Expand Up @@ -2406,7 +2406,7 @@
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = E8FVX7QLET;
ENABLE_NS_ASSERTIONS = NO;
Expand Down Expand Up @@ -2440,7 +2440,7 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = E8FVX7QLET;
FRAMEWORK_SEARCH_PATHS = "";
Expand Down Expand Up @@ -2478,7 +2478,7 @@
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = E8FVX7QLET;
ENABLE_NS_ASSERTIONS = NO;
Expand Down Expand Up @@ -2514,7 +2514,7 @@
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = E8FVX7QLET;
ENABLE_NS_ASSERTIONS = NO;
Expand Down
12 changes: 12 additions & 0 deletions HelloMixpanel/HelloMixpanel/AppDelegate.m
Expand Up @@ -40,6 +40,18 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
// Set some super properties, which will be added to every tracked event
[self.mixpanel registerSuperProperties:@{@"Plan": @"Premium"}];

// Set a profile property so a profile is created
[self.mixpanel.people setOnce:@{@"$name": @"Demo User"}];

// Track a test event
[self.mixpanel track:@"HelloMixpanel"];

// Identify using the generated distinctId so people queue is flushed
[self.mixpanel identify:[self.mixpanel distinctId]];

// Force a flush to make debugging easier
[self.mixpanel flush];

if ([UNUserNotificationCenter class]) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
Expand Down
4 changes: 3 additions & 1 deletion Mixpanel/MPNotificationServiceExtension.m
Expand Up @@ -26,6 +26,9 @@ - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];

// Track $push_notification_received event
[Mixpanel trackPushNotificationEventFromRequest:request event:@"$push_notification_received" properties:@{}];

// Setup the category first since it's faster and less likely to cause time to expire
[self getCategoryIdentifier:request.content withCompletion:^(NSString *categoryIdentifier) {
NSLog(@"%@ Using \"%@\" as categoryIdentifier", self, categoryIdentifier);
Expand All @@ -44,7 +47,6 @@ - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request
self.contentHandler(self.bestAttemptContent);

}];

}];
}

Expand Down
10 changes: 10 additions & 0 deletions Mixpanel/Mixpanel.h
Expand Up @@ -806,6 +806,16 @@ extern NSString *const MPNotificationTypeTakeover;
*/
+ (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(10.0), macos(10.14), watchos(6.0)) API_UNAVAILABLE(tvos);

/*!
Internal utility for push notification-related event tracking using the project token from the push payload
*/
+ (void)trackPushNotificationEventFromRequest:(UNNotificationRequest *)request event:(NSString *)event properties:(NSDictionary *)additionalProperties;

/*!
Internal utility for push notification-related event tracking
*/
- (void)trackPushNotification:(NSDictionary *)userInfo event:(NSString *)event properties:(NSDictionary *)additionalProperties;

#if !MIXPANEL_NO_NOTIFICATION_AB_TEST_SUPPORT
#pragma mark - Mixpanel Notifications

Expand Down
100 changes: 70 additions & 30 deletions Mixpanel/Mixpanel.m
Expand Up @@ -200,7 +200,7 @@ - (instancetype)initWithToken:(NSString *)apiToken
[self setupAutomaticPushTracking];
NSDictionary *remoteNotification = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
if (remoteNotification) {
[self trackPushNotification:remoteNotification event:@"$app_open"];
[self trackPushNotification:remoteNotification event:@"$app_open" properties:@{}];
}
}
#endif
Expand Down Expand Up @@ -804,17 +804,25 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
}
}

- (void)trackPushNotification:(NSDictionary *)userInfo event:(NSString *)event
- (void)trackPushNotification:(NSDictionary *)userInfo event:(NSString *)event properties:(NSDictionary *)additionalProperties
{
MPLogInfo(@"%@ tracking push payload %@", self, userInfo);

id rawMp = userInfo[@"mp"];
if (rawMp) {
NSDictionary *mpPayload = [rawMp isKindOfClass:[NSDictionary class]] ? rawMp : @{};
NSMutableDictionary *properties = [mpPayload mutableCopy];

NSDictionary *mpPayload = [rawMp isKindOfClass:[NSDictionary class]] ? rawMp : nil;
// "token" and "distinct_id" are sent with the Mixpanel push payload but we don't need to track them
// they are handled upstream to initialize the mixpanel instance and "distinct_id" will be passed in
// explicitly in "additionalProperties"
[properties removeObjectForKey:@"token"];
[properties removeObjectForKey:@"distinct_id"];

// merge in additional properties we explicitly want to include
[properties addEntriesFromDictionary:additionalProperties];

if (mpPayload[@"m"] && mpPayload[@"c"]) {
NSMutableDictionary *properties = [mpPayload mutableCopy];
properties[@"campaign_id"] = mpPayload[@"c"];
properties[@"message_id"] = mpPayload[@"m"];
properties[@"message_type"] = @"push";
Expand All @@ -828,9 +836,41 @@ - (void)trackPushNotification:(NSDictionary *)userInfo event:(NSString *)event
}
}


+ (void)trackPushNotificationEventFromRequest:(UNNotificationRequest *)request event:(NSString *)event properties:(NSDictionary *)additionalProperties
{
NSDictionary* userInfo = request.content.userInfo;

id mpPayload = userInfo[@"mp"];
if (!mpPayload) {
NSLog(@"%@ Malformed mixpanel push payload, not tracking %@", self, event);
return;
}

NSString *distinctId = mpPayload[@"distinct_id"];
if (!distinctId) {
NSLog(@"%@ \"distinct_id\" not found in mixpanel push payload, not tracking %@", self, event);
return;
}

NSString *projectToken = mpPayload[@"token"];
if (!projectToken) {
NSLog(@"%@ \"token\" not found in mixpanel push payload, not tracking %@", self, event);
return;
}

NSMutableDictionary *properties = [additionalProperties mutableCopy];
[properties addEntriesFromDictionary:@{@"distinct_id": distinctId, @"$ios_notification_id": request.identifier}];

// Track using project token and distinct_id from push payload
Mixpanel *instance = [Mixpanel sharedInstanceWithToken:projectToken];
[instance trackPushNotification:userInfo event:event properties:properties];
[instance flush];
}

- (void)trackPushNotification:(NSDictionary *)userInfo
{
[self trackPushNotification:userInfo event:@"$campaign_received"];
[self trackPushNotification:userInfo event:@"$campaign_received" properties:@{}];
}
#endif

Expand Down Expand Up @@ -1874,34 +1914,26 @@ + (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNoti
return;
}

NSDictionary *userInfo = response.notification.request.content.userInfo;

// Initialize properties to track to Mixpanel
NSMutableDictionary *trackingProps = [[NSMutableDictionary alloc] init];
[trackingProps setValuesForKeysWithDictionary:@{
@"campaign_id": [userInfo valueForKeyPath:@"mp.c"],
@"message_id": [userInfo valueForKeyPath:@"mp.m"],
}];

UNNotificationRequest *request = response.notification.request;
NSDictionary *userInfo = request.content.userInfo;

MPLogInfo(@"%@ didReceiveNotificationResponse action: %@", self, response.actionIdentifier);

// If the notification was dismissed, just track and return
if ([response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier]) {
[instances.allKeys enumerateObjectsUsingBlock:^(NSString *token, NSUInteger idx, BOOL * _Nonnull stop) {
Mixpanel *instance = instances[token];
[instance track:@"$push_notification_dismissed" properties:trackingProps];
[instance flush];
}];
[Mixpanel trackPushNotificationEventFromRequest:request event:@"$push_notification_dismissed" properties:@{}];
completionHandler();
return;
}

// Initialize additonal properties to track to Mixpanel with the $push_notification_tap event
NSMutableDictionary *additionalTrackingProps = [[NSMutableDictionary alloc] init];

NSDictionary *ontap = nil;

if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) {
// The action that indicates the user opened the app from the notification interface.
[trackingProps setValue:@"notification" forKey:@"tap_target"];
additionalTrackingProps[@"$tap_target"] = @"notification";
if (userInfo[@"mp_ontap"]) {
ontap = userInfo[@"mp_ontap"];
}
Expand All @@ -1913,20 +1945,28 @@ + (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNoti
NSInteger idx = [[response.actionIdentifier stringByReplacingOccurrencesOfString:@"MP_ACTION_" withString:@""] integerValue];
NSDictionary *buttonDict = buttons[idx];
ontap = buttonDict[@"ontap"];
[trackingProps setValuesForKeysWithDictionary:@{
@"button_id": buttonDict[@"id"],
@"button_label": buttonDict[@"lbl"],
@"tap_target": @"button",
[additionalTrackingProps addEntriesFromDictionary:@{
@"$button_id": buttonDict[@"id"],
@"$button_label": buttonDict[@"lbl"],
@"$tap_target": @"button",
}];
}
}

// Track tap event to all Mixpanel instances
[instances.allKeys enumerateObjectsUsingBlock:^(NSString *token, NSUInteger idx, BOOL * _Nonnull stop) {
Mixpanel *instance = instances[token];
[instance track:@"$push_notification_tap" properties:trackingProps];
[instance flush];
}];
// Add additional tracking props
if (ontap != nil && ontap != (id)[NSNull null]) {
NSString *tapActionType = ontap[@"type"];
if (tapActionType != nil) {
additionalTrackingProps[@"$tap_action_type"] = tapActionType;
}
NSString *tapActionUri = ontap[@"uri"];
if (tapActionUri != nil) {
additionalTrackingProps[@"$tap_action_uri"] = tapActionUri;
}
}

// Track tap event
[Mixpanel trackPushNotificationEventFromRequest:request event:@"$push_notification_tap" properties:additionalTrackingProps];

if (ontap == nil || ontap == (id)[NSNull null]) {
// Default to homescreen if no ontap info
Expand Down