From d10c64bac90ea9bb03baabfdaa8043603b3b8db3 Mon Sep 17 00:00:00 2001 From: sharekris Date: Wed, 29 Jul 2015 14:01:16 -0700 Subject: [PATCH] Update Kahuna integration and SDK Squashed commit of the following: commit fae7de26601890eb56948bf23a4132f0c64eba22 Author: sharekris Date: Wed Jul 29 13:48:49 2015 -0700 Podfile.lock changed to use 2.0.3 after running make build. commit 0d777e823a139e0c75ad62fc50855994e440f4d2 Merge: f2e88e8 12165d5 Author: SimpliRay Date: Wed Jul 29 10:27:25 2015 -0700 Merge pull request #1 from sharekris/updating_code_for_2.0.2 Updating code for 2.0.3 commit 12165d52362b697cc89111b86a9c22c713ab4524 Author: sharekris Date: Tue Jul 28 17:06:28 2015 -0700 Removed tab. commit f99b41841f006aea4a22bce3c22215a33cf02ce0 Author: sharekris Date: Tue Jul 28 16:39:56 2015 -0700 Changed to version 2.0.3 as that is the latest. commit 62c767323ef97efaab539c315e78f97867b95b67 Author: sharekris Date: Tue Jul 28 16:35:31 2015 -0700 Added call to setSDKWrapper method to send the wrapper name and version to our server. Modified the track event with quantity and revenue to trackEvent with count and value only when both revenue and quantity are specified in the properties. commit b9d621b39dcb3c613b02277be8b04c3d50ab1e87 Author: sharekris Date: Tue Jul 28 12:25:05 2015 -0700 Removed commented out tests. commit b2dc396bffa956fa16cffa7e8dd387100bf33868 Author: sharekris Date: Mon Jul 27 19:24:08 2015 -0700 Added OCMokito tests for Kahuna Integration. Made some code changes in SEGKahunaIntegration to allow OCMokito to mock the Kahuna object. Created SEGKahunaDefines.h header file and moved all static string definitions into that file. commit d34eb4aa2482b4f26730dfa530ccabd04d68c7e1 Author: sharekris Date: Thu Jul 23 15:25:24 2015 -0700 Modified getUserCredentials to use createUserCredentials. The latter is what we want to use since we want the request to identify detect non-overlaps. If we do a getUserCredential then it will always be over lapping credentials. commit 59d2467213eb62bab230acceb1a7e6adb3e43d3b Author: sharekris Date: Mon Jul 20 13:35:38 2015 -0700 Modified the Kahuna wrapper to use Kahuna instead of KahunaAnalytics SDK. Replaced the API setUserCredentialsWithKey with addCredential and then calling loginWithCredentials. --- Analytics.xcodeproj/project.pbxproj | 24 +- .../Kahuna/SEGKahunaIntegration.h | 2 + .../Kahuna/SEGKahunaIntegration.m | 91 ++++---- Analytics/Integrations/SEGKahunaDefines.h | 28 +++ Podfile.lock | 6 +- .../Kahuna/SEGKahunaIntegrationTests.m | 205 ++++++++++++++++++ scripts/integrations.json | 4 +- 7 files changed, 307 insertions(+), 53 deletions(-) create mode 100644 Analytics/Integrations/SEGKahunaDefines.h create mode 100644 iOS Tests/Integrations/Kahuna/SEGKahunaIntegrationTests.m diff --git a/Analytics.xcodeproj/project.pbxproj b/Analytics.xcodeproj/project.pbxproj index 991a52bca..51ef25562 100644 --- a/Analytics.xcodeproj/project.pbxproj +++ b/Analytics.xcodeproj/project.pbxproj @@ -23,12 +23,13 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 019892FF1B67121A00ED875F /* SEGKahunaDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 019892FE1B67121A00ED875F /* SEGKahunaDefines.h */; }; + 01FB19751B66881F000483CC /* SEGKahunaIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 01FB19741B66881F000483CC /* SEGKahunaIntegrationTests.m */; }; 3208C0861A64F6B200D6014F /* AMPARCMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 3208C0811A64F6B200D6014F /* AMPARCMacros.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3208C0871A64F6B200D6014F /* AMPConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 3208C0821A64F6B200D6014F /* AMPConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3208C0881A64F6B200D6014F /* AMPDeviceInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 3208C0831A64F6B200D6014F /* AMPDeviceInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3208C0891A64F6B200D6014F /* AMPLocationManagerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3208C0841A64F6B200D6014F /* AMPLocationManagerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3208C08A1A64F6B200D6014F /* Amplitude.h in Headers */ = {isa = PBXBuildFile; fileRef = 3208C0851A64F6B200D6014F /* Amplitude.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 320AFEE91A840DEB00929778 /* KahunaAnalytics.h in Headers */ = {isa = PBXBuildFile; fileRef = 320AFEE81A840DEB00929778 /* KahunaAnalytics.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32225231199AD6D200478B1A /* SEGReachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 3222522F199AD6D200478B1A /* SEGReachability.h */; }; 32225232199AD6D200478B1A /* SEGReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 32225230199AD6D200478B1A /* SEGReachability.m */; }; 32225233199AD6D200478B1A /* SEGReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 32225230199AD6D200478B1A /* SEGReachability.m */; }; @@ -236,6 +237,7 @@ 6E85F3901B2F922F00763913 /* CountlyDB.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E85F38F1B2F922F00763913 /* CountlyDB.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6E85F3921B2F974700763913 /* Localytics.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E85F3911B2F974700763913 /* Localytics.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6E85F3941B2F980B00763913 /* MPLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E85F3931B2F980B00763913 /* MPLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6E865BE41B697BBB00F80367 /* Kahuna.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E865BE31B697BBB00F80367 /* Kahuna.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6E8F058C1B42321800305E99 /* SEGOptimizelyIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E8F058B1B42321800305E99 /* SEGOptimizelyIntegrationTests.m */; }; 6E92C1E71B3DD5260049B5A8 /* SEGLocalyticsIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E92C1E61B3DD5260049B5A8 /* SEGLocalyticsIntegrationTests.m */; }; 6EB2963E1B4C3B2F00805337 /* SEGTapstreamIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6EB2963D1B4C3B2F00805337 /* SEGTapstreamIntegrationTests.m */; }; @@ -322,6 +324,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 019892FE1B67121A00ED875F /* SEGKahunaDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SEGKahunaDefines.h; sourceTree = ""; }; + 01FB19741B66881F000483CC /* SEGKahunaIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SEGKahunaIntegrationTests.m; path = Integrations/Kahuna/SEGKahunaIntegrationTests.m; sourceTree = ""; }; 095FDFE35C86958DB4798F57 /* Pods-Analytics.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Analytics.release.xcconfig"; path = "Pods/Target Support Files/Pods-Analytics/Pods-Analytics.release.xcconfig"; sourceTree = ""; }; 23CD79ED9C3E478C83CB7A34 /* libPods-AnalyticsTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AnalyticsTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 24AE4A015CBF4E95B18D10A7 /* libPods-iOS Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-iOS Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -330,7 +334,6 @@ 3208C0831A64F6B200D6014F /* AMPDeviceInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AMPDeviceInfo.h; path = "Pods/Amplitude-iOS/Amplitude/AMPDeviceInfo.h"; sourceTree = ""; }; 3208C0841A64F6B200D6014F /* AMPLocationManagerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AMPLocationManagerDelegate.h; path = "Pods/Amplitude-iOS/Amplitude/AMPLocationManagerDelegate.h"; sourceTree = ""; }; 3208C0851A64F6B200D6014F /* Amplitude.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Amplitude.h; path = "Pods/Amplitude-iOS/Amplitude/Amplitude.h"; sourceTree = ""; }; - 320AFEE81A840DEB00929778 /* KahunaAnalytics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = KahunaAnalytics.h; path = Pods/KahunaSDK/Kahuna/KahunaAnalytics.h; sourceTree = ""; }; 3222522F199AD6D200478B1A /* SEGReachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SEGReachability.h; path = Helpers/SEGReachability.h; sourceTree = ""; }; 32225230199AD6D200478B1A /* SEGReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SEGReachability.m; path = Helpers/SEGReachability.m; sourceTree = ""; }; 32385FD81A2DF0CA008E09B2 /* MPCategoryHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MPCategoryHelpers.h; path = Pods/Mixpanel/Mixpanel/MPCategoryHelpers.h; sourceTree = ""; }; @@ -539,6 +542,7 @@ 6E85F38F1B2F922F00763913 /* CountlyDB.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = CountlyDB.h; path = Pods/Countly/CountlyDB.h; sourceTree = ""; }; 6E85F3911B2F974700763913 /* Localytics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Localytics.h; path = "Pods/Localytics/Localytics-iOS-3.3.0/Localytics.h"; sourceTree = ""; }; 6E85F3931B2F980B00763913 /* MPLogger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MPLogger.h; path = Pods/Mixpanel/Mixpanel/MPLogger.h; sourceTree = ""; }; + 6E865BE31B697BBB00F80367 /* Kahuna.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Kahuna.h; path = Pods/Kahuna/Kahuna.framework/Headers/Kahuna.h; sourceTree = ""; }; 6E8F058B1B42321800305E99 /* SEGOptimizelyIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SEGOptimizelyIntegrationTests.m; path = Integrations/Optimizely/SEGOptimizelyIntegrationTests.m; sourceTree = ""; }; 6E92C1E61B3DD5260049B5A8 /* SEGLocalyticsIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SEGLocalyticsIntegrationTests.m; path = Integrations/Localytics/SEGLocalyticsIntegrationTests.m; sourceTree = ""; }; 6EB2963D1B4C3B2F00805337 /* SEGTapstreamIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SEGTapstreamIntegrationTests.m; path = Integrations/Tapstream/SEGTapstreamIntegrationTests.m; sourceTree = ""; }; @@ -676,6 +680,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 01FB19711B667B47000483CC /* Kahuna */ = { + isa = PBXGroup; + children = ( + 01FB19741B66881F000483CC /* SEGKahunaIntegrationTests.m */, + ); + name = Kahuna; + sourceTree = ""; + }; 3242C99C1946A3E100FBE441 /* iOS Tests */ = { isa = PBXGroup; children = ( @@ -753,6 +765,7 @@ children = ( 32F638191A8134A600C65643 /* SEGKahunaIntegration.h */, 32F6381A1A8134A600C65643 /* SEGKahunaIntegration.m */, + 019892FE1B67121A00ED875F /* SEGKahunaDefines.h */, ); name = Kahuna; sourceTree = ""; @@ -779,6 +792,7 @@ 6E16FFF31B33AD0600B4F7FE /* Integrations */ = { isa = PBXGroup; children = ( + 01FB19711B667B47000483CC /* Kahuna */, 6EB4681F1B39CDBD00A0E4B9 /* Amplitude */, 6EF6652D1B585DC900DC7EA1 /* AppsFlyer */, 6E5146981B3B571D00F8F87C /* Bugsnag */, @@ -905,6 +919,7 @@ D3D35174186242B4005586E7 /* Headers */ = { isa = PBXGroup; children = ( + 6E865BE31B697BBB00F80367 /* Kahuna.h */, 6E2AFA401B55852400C8A14E /* MOInbox.h */, 6E2AFA411B55852400C8A14E /* MOInboxViewController.h */, 6E2AFA3C1B55850C00C8A14E /* MOEHelperConstants.h */, @@ -980,7 +995,6 @@ 325DCE2E1A8D875100D4142E /* BugsnagNotifier.h */, 325DCE2F1A8D875100D4142E /* BugsnagOSXNotifier.h */, 325DCE301A8D875100D4142E /* BugsnagSink.h */, - 320AFEE81A840DEB00929778 /* KahunaAnalytics.h */, 3208C0811A64F6B200D6014F /* AMPARCMacros.h */, 3208C0821A64F6B200D6014F /* AMPConstants.h */, 3208C0831A64F6B200D6014F /* AMPDeviceInfo.h */, @@ -1389,7 +1403,6 @@ 6E165A1A1B54771A002C1C40 /* GAITrackedViewController.h in Headers */, 6E165A1B1B54771A002C1C40 /* GAITracker.h in Headers */, 6E85F3901B2F922F00763913 /* CountlyDB.h in Headers */, - 320AFEE91A840DEB00929778 /* KahunaAnalytics.h in Headers */, 6E85F3711B2F792100763913 /* TSAppEventSourceImpl.h in Headers */, 32385FE31A2DF0FB008E09B2 /* MPUITableViewBinding.h in Headers */, 3252EA881999D6CC0056C32A /* Taplytics.h in Headers */, @@ -1400,6 +1413,7 @@ 32CEE41E19F5B6DB007758F2 /* TaplyticsOptions.h in Headers */, 32385FDF1A2DF0E7008E09B2 /* MPEventBinding.h in Headers */, DAEB6FF11AC9EBCA003E7FB3 /* SEGApptimizeIntegration.h in Headers */, + 6E865BE41B697BBB00F80367 /* Kahuna.h in Headers */, 32CCD99C1919A4F4007B63BA /* Analytics.h in Headers */, EA25892317C6979D000CEF61 /* SEGAnalytics.h in Headers */, D371F32E185942BB0053337D /* Crittercism.h in Headers */, @@ -1563,6 +1577,7 @@ 32430A761914186C004EFF59 /* SEGLocation.h in Headers */, D331BD351860F5F0007CB22F /* TSLogLevel.h in Headers */, D3EDB66218C315F100A88A7E /* SEGLocalyticsIntegration.h in Headers */, + 019892FF1B67121A00ED875F /* SEGKahunaDefines.h in Headers */, D331BD361860F5F0007CB22F /* TSPlatform.h in Headers */, D3EDB65C18C315F100A88A7E /* SEGCrittercismIntegration.h in Headers */, D3EDB66618C315F100A88A7E /* SEGSegmentioIntegration.h in Headers */, @@ -1830,6 +1845,7 @@ 3242C9A31946A3E100FBE441 /* iOS_Tests.m in Sources */, 3242C9B21946ADA900FBE441 /* SEGAnalyticsIntegration.m in Sources */, 32A69C241979CF4A00D69943 /* SEGOptimizelyIntegration.m in Sources */, + 01FB19751B66881F000483CC /* SEGKahunaIntegrationTests.m in Sources */, 3242C9B71946AFCC00FBE441 /* SEGCountlyIntegration.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Analytics/Integrations/Kahuna/SEGKahunaIntegration.h b/Analytics/Integrations/Kahuna/SEGKahunaIntegration.h index 4b123c33d..7bed126e2 100644 --- a/Analytics/Integrations/Kahuna/SEGKahunaIntegration.h +++ b/Analytics/Integrations/Kahuna/SEGKahunaIntegration.h @@ -14,6 +14,7 @@ @property (nonatomic, assign) BOOL valid; @property (nonatomic, assign) BOOL initialized; @property (nonatomic, copy) NSDictionary *settings; +@property Class kahunaClass; @end @@ -22,6 +23,7 @@ @property (nonatomic) NSDictionary *pushInfo; @property (nonatomic) UIApplicationState applicationState; @property (nonatomic) BOOL kahunaInitialized; +@property Class kahunaClass; + (instancetype)sharedInstance; - (void)didFinishLaunching:(NSNotification *)userInfo; diff --git a/Analytics/Integrations/Kahuna/SEGKahunaIntegration.m b/Analytics/Integrations/Kahuna/SEGKahunaIntegration.m index 1a39fce28..81cbce7e5 100644 --- a/Analytics/Integrations/Kahuna/SEGKahunaIntegration.m +++ b/Analytics/Integrations/Kahuna/SEGKahunaIntegration.m @@ -1,8 +1,9 @@ // KahunaIntegration.m // Copyright (c) 2014 Segment.io. All rights reserved. +#import "SEGKahunaDefines.h" #import "SEGKahunaIntegration.h" -#import "KahunaAnalytics.h" +#import "Kahuna.h" #import "SEGAnalyticsUtils.h" #import "SEGAnalytics.h" #import @@ -17,26 +18,6 @@ void (*selOriginalApplicationDidReceiveRemoteNotificationWithFetchCompletionHandler)(id, SEL, id, id, void (^)(UIBackgroundFetchResult result)); void (*selOriginalApplicationHandleActionWithIdentifierWithFetchCompletionHandler)(id, SEL, id, id, id, void (^)()); -static NSString *const KAHUNA_VIEWED_PRODUCT_CATEGORY = @"Viewed Product Category"; -static NSString *const KAHUNA_VIEWED_PRODUCT = @"Viewed Product"; -static NSString *const KAHUNA_ADDED_PRODUCT = @"Added Product"; -static NSString *const KAHUNA_COMPLETED_ORDER = @"Completed Order"; - -static NSString *const KAHUNA_LAST_VIEWED_CATEGORY = @"Last Viewed Category"; -static NSString *const KAHUNA_CATEGORIES_VIEWED = @"Categories Viewed"; -static NSString *const KAHUNA_LAST_PRODUCT_VIEWED_NAME = @"Last Product Viewed Name"; -static NSString *const KAHUNA_LAST_PRODUCT_VIEWED_ID = @"Last Produced Viewed Id"; -static NSString *const KAHUNA_LAST_PRODUCT_ADDED_TO_CART_NAME = @"Last Product Added To Cart Name"; -static NSString *const KAHUNA_LAST_PRODUCT_ADDED_TO_CART_CATEGORY = @"Last Product Added To Cart Category"; -static NSString *const KAHUNA_LAST_PURCHASE_DISCOUNT = @"Last Purchase Discount"; - -static NSString *const KAHUNA_CATEGORY = @"category"; -static NSString *const KAHUNA_NAME = @"name"; -static NSString *const KAHUNA_ID = @"id"; -static NSString *const KAHUNA_DISCOUNT = @"discount"; -static NSString *const KAHUNA_NONE = @"None"; - - @implementation SEGKahunaIntegration @synthesize initialized, valid, name, settings; @@ -53,6 +34,7 @@ - (id)init self.name = @"Kahuna"; self.valid = NO; self.initialized = NO; + self.kahunaClass = [Kahuna class]; _kahunaCredentialsKeys = [NSSet setWithObjects:KAHUNA_CREDENTIAL_USERNAME, KAHUNA_CREDENTIAL_EMAIL, @@ -73,10 +55,14 @@ - (void)start // We just need one call to launchWithKey and not multiple. The "start" method is called // everytime the app comes to foreground. if ([SEGKahunaPushMonitor sharedInstance].kahunaInitialized == NO) { - [KahunaAnalytics launchWithKey:apiKey]; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wundeclared-selector" + [self.kahunaClass performSelector:@selector(setSDKWrapper:withVersion:) withObject:SEGMENT withObject:[SEGAnalytics version]]; +#pragma GCC diagnostic pop + [self.kahunaClass launchWithKey:apiKey]; // If we have recorded any push user info, then if ([SEGKahunaPushMonitor sharedInstance].pushInfo != nil) { - [KahunaAnalytics handleNotification:[SEGKahunaPushMonitor sharedInstance].pushInfo withApplicationState:[SEGKahunaPushMonitor sharedInstance].applicationState]; + [self.kahunaClass handleNotification:[SEGKahunaPushMonitor sharedInstance].pushInfo withApplicationState:[SEGKahunaPushMonitor sharedInstance].applicationState]; [SEGKahunaPushMonitor sharedInstance].pushInfo = nil; } @@ -106,27 +92,27 @@ - (void)validate - (void)identify:(NSString *)userId traits:(NSDictionary *)traits options:(NSDictionary *)options { NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init]; + KahunaUserCredentials *credentials = [self.kahunaClass createUserCredentials]; if (KAHUNA_NOT_STRING_NULL_EMPTY(userId)) { - [KahunaAnalytics setUserCredentialsWithKey:KAHUNA_CREDENTIAL_USER_ID andValue:userId]; + [credentials addCredential:KAHUNA_CREDENTIAL_USER_ID withValue:userId]; } // We will go through each of the above keys, and try to see if the traits has that key. If it does, then we will add the key:value as a credential. // All other traits is being tracked as an attribute. for (NSString *eachKey in traits) { if (!KAHUNA_NOT_STRING_NULL_EMPTY(eachKey)) continue; - NSString *eachValue = [traits objectForKey:eachKey]; if (KAHUNA_NOT_STRING_NULL_EMPTY(eachValue)) { // Check if this is a Kahuna credential key. if ([_kahunaCredentialsKeys containsObject:eachKey]) { - [KahunaAnalytics setUserCredentialsWithKey:eachKey andValue:eachValue]; + [credentials addCredential:eachKey withValue:eachValue]; } else { [attributes setValue:eachValue forKey:eachKey]; } } else if ([eachValue isKindOfClass:[NSNumber class]]) { // Check if this is a Kahuna credential key. if ([_kahunaCredentialsKeys containsObject:eachKey]) { - [KahunaAnalytics setUserCredentialsWithKey:eachKey andValue:[NSString stringWithFormat:@"%@", eachValue]]; + [credentials addCredential:eachKey withValue:[NSString stringWithFormat:@"%@", eachValue]]; } else { [attributes setValue:[NSString stringWithFormat:@"%@", eachValue] forKey:eachKey]; } @@ -139,10 +125,16 @@ - (void)identify:(NSString *)userId traits:(NSDictionary *)traits options:(NSDic } } } - + + NSError *error = nil; + [self.kahunaClass loginWithCredentials:credentials error:&error]; + if (error) { + NSLog(@"Kahuna-Segment Login Error : %@", error.description); + } + // Track the attributes if we have any items in it. if (attributes.count > 0) { - [KahunaAnalytics setUserAttributes:attributes]; + [self.kahunaClass setUserAttributes:attributes]; } } @@ -164,14 +156,15 @@ - (void)track:(NSString *)event properties:(NSDictionary *)properties options:(N } } - // Get the count and value from quantity and revenue. - long value = (long)([revenue doubleValue] * 100); - long count = [quantity longValue]; - - if (count + value > 0) { - [KahunaAnalytics trackEvent:event withCount:count andValue:value]; + // If we get revenue and quantity in the properties, then no matter what we will try to extract the numbers they hold and trackEvent with Count and Value. + if (revenue && quantity) { + // Get the count and value from quantity and revenue. + long value = (long)([revenue doubleValue] * 100); + long count = [quantity longValue]; + + [self.kahunaClass trackEvent:event withCount:count andValue:value]; } else { - [KahunaAnalytics trackEvent:event]; + [self.kahunaClass trackEvent:event]; } NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init]; @@ -195,7 +188,7 @@ - (void)track:(NSString *)event properties:(NSDictionary *)properties options:(N // If we have collected any attributes, then we will call the setUserAttributes API if (attributes.count > 0) { - [KahunaAnalytics setUserAttributes:attributes]; + [self.kahunaClass setUserAttributes:attributes]; } } @@ -204,7 +197,7 @@ - (void)addViewedProductCategoryElements:(NSMutableDictionary *__autoreleasing * id value = properties[KAHUNA_CATEGORY]; if (value && ([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]])) { [(*attributes)setValue:value forKey:KAHUNA_LAST_VIEWED_CATEGORY]; - NSDictionary *existingAttributes = [KahunaAnalytics getUserAttributes]; + NSDictionary *existingAttributes = [self.kahunaClass getUserAttributes]; id categoriesViewed = [existingAttributes valueForKey:KAHUNA_CATEGORIES_VIEWED]; if (categoriesViewed && [categoriesViewed isKindOfClass:[NSString class]]) { NSMutableArray *aryOfCategoriesViewed = [[categoriesViewed componentsSeparatedByString:@","] mutableCopy]; @@ -272,18 +265,18 @@ - (void)screen:(NSString *)screenTitle properties:(NSDictionary *)properties opt - (void)registerForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken options:(NSDictionary *)options { - [KahunaAnalytics setDeviceToken:deviceToken]; + [self.kahunaClass setDeviceToken:deviceToken]; } - (void)reset { - [KahunaAnalytics logout]; + [self.kahunaClass logout]; } @end // This class is responsible for getting the 'UIApplicationDidFinishLaunchingNotification' notification. It is received by the -// method didFinishLaunching and it calls [KahunaAnalytics handleNotification API. +// method didFinishLaunching and it calls [self.kahunaClass handleNotification API. @implementation SEGKahunaPushMonitor + (instancetype)sharedInstance @@ -291,11 +284,21 @@ + (instancetype)sharedInstance static SEGKahunaPushMonitor *instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - instance = [[SEGKahunaPushMonitor alloc] init]; + instance = [[SEGKahunaPushMonitor alloc] init]; }); return instance; } +- (id) init { + self = [super init]; + if (self) { + self.kahunaClass = [Kahuna class]; + return self; + } + + return nil; +} + - (void)didFinishLaunching:(NSNotification *)notificationPayload { NSDictionary *userInfo = notificationPayload.userInfo; @@ -397,7 +400,7 @@ - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotif { @try { if ([SEGKahunaPushMonitor sharedInstance].kahunaInitialized) { - [KahunaAnalytics handleNotificationRegistrationFailure:error]; + [self.kahunaClass handleNotificationRegistrationFailure:error]; } if (selOriginalApplicationDidFailToRegisterForRemoteNotificationsWithError) { @@ -471,7 +474,7 @@ - (void)pushReceived:(NSDictionary *)userInfo { // When we get this notification, check if kahuna is initialized. If not store it for future use. if ([SEGKahunaPushMonitor sharedInstance].kahunaInitialized) { - [KahunaAnalytics handleNotification:userInfo withApplicationState:[UIApplication sharedApplication].applicationState]; + [self.kahunaClass handleNotification:userInfo withApplicationState:[UIApplication sharedApplication].applicationState]; } else { [SEGKahunaPushMonitor sharedInstance].pushInfo = userInfo; [SEGKahunaPushMonitor sharedInstance].applicationState = [UIApplication sharedApplication].applicationState; diff --git a/Analytics/Integrations/SEGKahunaDefines.h b/Analytics/Integrations/SEGKahunaDefines.h new file mode 100644 index 000000000..b3654bb08 --- /dev/null +++ b/Analytics/Integrations/SEGKahunaDefines.h @@ -0,0 +1,28 @@ +// SEGKahunaDefines.h +// Copyright (c) 2014 Segment.io. All rights reserved. + +#ifndef Analytics_SEGKahunaDefines_h +#define Analytics_SEGKahunaDefines_h + +static NSString *const KAHUNA_VIEWED_PRODUCT_CATEGORY = @"Viewed Product Category"; +static NSString *const KAHUNA_VIEWED_PRODUCT = @"Viewed Product"; +static NSString *const KAHUNA_ADDED_PRODUCT = @"Added Product"; +static NSString *const KAHUNA_COMPLETED_ORDER = @"Completed Order"; + +static NSString *const KAHUNA_LAST_VIEWED_CATEGORY = @"Last Viewed Category"; +static NSString *const KAHUNA_CATEGORIES_VIEWED = @"Categories Viewed"; +static NSString *const KAHUNA_LAST_PRODUCT_VIEWED_NAME = @"Last Product Viewed Name"; +static NSString *const KAHUNA_LAST_PRODUCT_VIEWED_ID = @"Last Produced Viewed Id"; +static NSString *const KAHUNA_LAST_PRODUCT_ADDED_TO_CART_NAME = @"Last Product Added To Cart Name"; +static NSString *const KAHUNA_LAST_PRODUCT_ADDED_TO_CART_CATEGORY = @"Last Product Added To Cart Category"; +static NSString *const KAHUNA_LAST_PURCHASE_DISCOUNT = @"Last Purchase Discount"; + +static NSString *const KAHUNA_CATEGORY = @"category"; +static NSString *const KAHUNA_NAME = @"name"; +static NSString *const KAHUNA_ID = @"id"; +static NSString *const KAHUNA_DISCOUNT = @"discount"; +static NSString *const KAHUNA_NONE = @"None"; +static NSString *const SEGMENT = @"segment"; + + +#endif diff --git a/Podfile.lock b/Podfile.lock index 458bf4b61..81f51d9e1 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -13,7 +13,7 @@ PODS: - FlurrySDK/FlurrySDK (6.5.0) - GoogleAnalytics (3.12.0) - GoogleIDFASupport (3.12.0) - - KahunaSDK (1.0.570) + - Kahuna (2.0.3) - Localytics (3.3.0) - Mixpanel (2.8.1): - Mixpanel/Mixpanel (= 2.8.1) @@ -45,7 +45,7 @@ DEPENDENCIES: - FlurrySDK (= 6.5.0) - GoogleAnalytics (= 3.12.0) - GoogleIDFASupport (= 3.12.0) - - KahunaSDK (= 1.0.570) + - Kahuna (= 2.0.3) - Localytics (= 3.3.0) - Mixpanel (= 2.8.1) - MoEngage-iOS-SDK (= 1.4.3) @@ -69,7 +69,7 @@ SPEC CHECKSUMS: FlurrySDK: 7e749c1bd22f4fb0a863663e38c58bc841980b25 GoogleAnalytics: ef2e0b800ef3c1e62688cdfc959e0d3132bc246c GoogleIDFASupport: 0dd25ffdd152fd8e3deefd72978b42ef818ef0fa - KahunaSDK: ea15fdf9757c4d9bf639bf190888e20b688a30ed + Kahuna: 06c3dea6aee22f5ae9c3eb6a6b9ae703cd5dd5cb Localytics: 64ada284cde3f2b30fb04272d5d735d49657d0ab Mixpanel: 93c4825025e6380ced273ed7178496282e385077 MoEngage-iOS-SDK: 81be7a433349a7d535c905a08629359eeefe0952 diff --git a/iOS Tests/Integrations/Kahuna/SEGKahunaIntegrationTests.m b/iOS Tests/Integrations/Kahuna/SEGKahunaIntegrationTests.m new file mode 100644 index 000000000..a18581ee2 --- /dev/null +++ b/iOS Tests/Integrations/Kahuna/SEGKahunaIntegrationTests.m @@ -0,0 +1,205 @@ +// +// SEGKahunaIntegrationTests.m +// Analytics +// +// Copyright (c) 2015 Segment.io. All rights reserved. +// + +#import +#import "SEGKahunaDefines.h" +#import "SEGKahunaIntegration.h" +#import + +#define HC_SHORTHAND +#import + +#define MOCKITO_SHORTHAND +#import + + +@interface SEGKahunaIntegrationTests : XCTestCase + +@property SEGKahunaIntegration *integration; +@property Class kahunaClassMock; +@property Class nserrorClassMock; +@property NSError *nserrorMock; +@property Kahuna *kahunaMock; +@property KahunaUserCredentials *kahunaCredentialsMock; + +@end + + +@implementation SEGKahunaIntegrationTests + +- (void)setUp +{ + [super setUp]; + + _kahunaMock = mock([Kahuna class]); + _kahunaClassMock = mockClass([Kahuna class]); + _nserrorMock = mock([NSError class]); + _nserrorClassMock = mockClass([NSError class]); + _kahunaCredentialsMock = mock([KahunaUserCredentials class]); + + [given([_kahunaClassMock sharedInstance]) willReturn:_kahunaMock]; + [given([_kahunaClassMock createUserCredentials]) willReturn:_kahunaCredentialsMock]; + [given([_nserrorClassMock errorWithDomain:anything() code:anything() userInfo:anything()]) willReturn:_nserrorMock]; + + _integration = [[SEGKahunaIntegration alloc] init]; + [_integration setKahunaClass:_kahunaClassMock]; +} + +- (void)testStart +{ + [_integration updateSettings:@{ @"apiKey" : @"foo" }]; + + XCTAssertTrue(_integration.valid); + [verifyCount(_kahunaClassMock, times(1)) launchWithKey:@"foo"]; +} + +- (void)testReset +{ + [_integration reset]; + + [verifyCount(_kahunaClassMock, times(1)) logout]; +} + +- (void)testIdentify +{ + [_integration identify:@"foo" traits:@{ @"bar" : @"baz" } options:@{}]; + + // Verify that Add Credential was called once on the KahunaCredentialsMock object. + [verifyCount(_kahunaCredentialsMock, times(1)) addCredential:KAHUNA_CREDENTIAL_USER_ID withValue:@"foo"]; + + [[verifyCount(_kahunaClassMock, times(1)) withMatcher:anything() forArgument:1] loginWithCredentials:_kahunaCredentialsMock error:nil]; + [verifyCount(_kahunaClassMock, times(1)) setUserAttributes:@{ @"bar" : @"baz" }]; +} + +- (void)testIdentifyWithNoTraits +{ + [_integration identify:@"foo" traits:@{} options:@{}]; + + // Verify that Add Credential was called once on the KahunaCredentialsMock object. + [verifyCount(_kahunaCredentialsMock, times(1)) addCredential:KAHUNA_CREDENTIAL_USER_ID withValue:@"foo"]; + + [[verifyCount(_kahunaClassMock, times(1)) withMatcher:anything() forArgument:1] loginWithCredentials:_kahunaCredentialsMock error:nil]; + [verifyCount(_kahunaClassMock, never()) setUserAttributes:anything()]; +} + +- (void)testIdentifyWithNoCredentialsAndNoTraits +{ + [_integration identify:nil traits:@{} options:@{}]; + + // Verify that Add Credential was called once on the KahunaCredentialsMock object. + [verifyCount(_kahunaCredentialsMock, never()) addCredential:anything() withValue:anything()]; + + [[verifyCount(_kahunaClassMock, times(1)) withMatcher:anything() forArgument:1] loginWithCredentials:_kahunaCredentialsMock error:nil]; + [verifyCount(_kahunaClassMock, never()) setUserAttributes:anything()]; +} + +- (void)testIdentifyWithMultipleCredentialsAndTraits +{ + [_integration identify:@"foo" traits:@{ @"bar" : @"baz", KAHUNA_CREDENTIAL_EMAIL : @"segkah@gmail.com", @"moon" : @"drake" } options:@{}]; + + // Verify that Add Credential was called twice on the KahunaCredentialsMock object. + [verifyCount(_kahunaCredentialsMock, times(1)) addCredential:KAHUNA_CREDENTIAL_USER_ID withValue:@"foo"]; + [verifyCount(_kahunaCredentialsMock, times(1)) addCredential:KAHUNA_CREDENTIAL_EMAIL withValue:@"segkah@gmail.com"]; + + [[verifyCount(_kahunaClassMock, times(1)) withMatcher:anything() forArgument:1] loginWithCredentials:_kahunaCredentialsMock error:nil]; + [verifyCount(_kahunaClassMock, times(1)) setUserAttributes:@{ @"bar" : @"baz", @"moon" : @"drake" }]; +} + +- (void)testTrack +{ + [_integration track:@"foo" properties:@{} options:nil]; + + [verifyCount(_kahunaClassMock, times(1)) trackEvent:@"foo"]; +} + +- (void)testTrackWithRevenueButNoQuantity +{ + [_integration track:@"foo" properties:@{ @"revenue" : @10 } options:nil]; + + [verifyCount(_kahunaClassMock, times(1)) trackEvent:@"foo"]; + [verifyCount(_kahunaClassMock, never()) trackEvent:@"foo" withCount:anything() andValue:anything()]; +} + +- (void)testTrackWithQuantityButNoRevenue +{ + [_integration track:@"foo" properties:@{ @"quantity" : @10 } options:nil]; + + [verifyCount(_kahunaClassMock, times(1)) trackEvent:@"foo"]; + [verifyCount(_kahunaClassMock, never()) trackEvent:@"foo" withCount:anything() andValue:anything()]; +} + +- (void)testTrackWithQuantityAndRevenue +{ + [_integration track:@"foo" properties:@{ @"revenue" : @10, @"quantity" : @4 } options:nil]; + + [verifyCount(_kahunaClassMock, never()) trackEvent:anything()]; + [verifyCount(_kahunaClassMock, times(1)) trackEvent:@"foo" withCount:4 andValue:1000]; +} + +- (void)testTrackWithQuantityRevenueAndProperties +{ + [_integration track:@"foo" + properties:@{@"productId" : @"bar", + @"quantity" : @10, + @"receipt" : @"baz", + @"revenue" : @5 + } options:@{}]; + + [verifyCount(_kahunaClassMock, times(1)) trackEvent:@"foo" withCount:10 andValue:500]; +} + +- (void)testTrackWithPropertyViewedCategory +{ + [_integration track:KAHUNA_VIEWED_PRODUCT_CATEGORY properties:@{ KAHUNA_CATEGORY : @"shirts" } options:nil]; + + [verifyCount(_kahunaClassMock, times(1)) setUserAttributes:@{KAHUNA_LAST_VIEWED_CATEGORY : @"shirts", KAHUNA_CATEGORIES_VIEWED : @"shirts" }]; + [verifyCount(_kahunaClassMock, times(1)) trackEvent:KAHUNA_VIEWED_PRODUCT_CATEGORY]; +} + +- (void)testTrackWithPropertyViewedProduct +{ + [_integration track:KAHUNA_VIEWED_PRODUCT properties:@{ KAHUNA_NAME : @"gopher shirts" } options:nil]; + + [verifyCount(_kahunaClassMock, times(1)) setUserAttributes:@{KAHUNA_LAST_PRODUCT_VIEWED_NAME : @"gopher shirts", + KAHUNA_CATEGORIES_VIEWED : KAHUNA_NONE, + KAHUNA_LAST_VIEWED_CATEGORY : KAHUNA_NONE }]; + [verifyCount(_kahunaClassMock, times(1)) trackEvent:KAHUNA_VIEWED_PRODUCT]; + +} + +- (void)testTrackWithPropertyAddedProduct +{ + [_integration track:KAHUNA_ADDED_PRODUCT properties:@{ KAHUNA_NAME : @"gopher shirts" } options:nil]; + + [verifyCount(_kahunaClassMock, times(1)) setUserAttributes:@{KAHUNA_LAST_PRODUCT_ADDED_TO_CART_NAME : @"gopher shirts", + KAHUNA_LAST_PRODUCT_ADDED_TO_CART_CATEGORY : KAHUNA_NONE }]; + [verifyCount(_kahunaClassMock, times(1)) trackEvent:KAHUNA_ADDED_PRODUCT]; +} + +- (void)testTrackWithPropertyCompletedOrder +{ + [_integration track:KAHUNA_COMPLETED_ORDER properties:@{ KAHUNA_DISCOUNT : @15.0 } options:nil]; + + [verifyCount(_kahunaClassMock, times(1)) setUserAttributes:@{KAHUNA_LAST_PURCHASE_DISCOUNT : @15.0 }]; + [verifyCount(_kahunaClassMock, times(1)) trackEvent:KAHUNA_COMPLETED_ORDER]; +} + +- (void)testScreen +{ + [_integration setSettings:@{ @"trackAllPages" : @1 }]; + + [_integration screen:@"foo" properties:@{} options:@{}]; + [verifyCount(_kahunaClassMock, times(1)) trackEvent:@"Viewed foo Screen"]; +} + +- (void)testScreenWithNoTrackAllPagesSettings +{ + [_integration screen:@"foo" properties:@{} options:@{}]; + [verifyCount(_kahunaClassMock, never()) trackEvent:anything()]; +} + +@end diff --git a/scripts/integrations.json b/scripts/integrations.json index be4aae714..589bfb5af 100644 --- a/scripts/integrations.json +++ b/scripts/integrations.json @@ -62,8 +62,8 @@ { "name": "Kahuna", "dependencies": [{ - "name": "KahunaSDK", - "version": "1.0.570" + "name": "Kahuna", + "version": "2.0.3" }] }, {