From fd66c46295968500084ad2181fe6bd40cde7a53d Mon Sep 17 00:00:00 2001 From: Kushagra Mishra Date: Wed, 29 May 2024 23:28:08 +0530 Subject: [PATCH 01/24] Added Support for persistence of user profiles with device Id --- CleverTapSDK/CTLocalDataStore.m | 8 ++++-- CleverTapSDK/CTUserInfoMigrator.h | 14 ++++++++++ CleverTapSDK/CTUserInfoMigrator.m | 46 +++++++++++++++++++++++++++++++ CleverTapSDK/CleverTap.m | 6 ++-- 4 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 CleverTapSDK/CTUserInfoMigrator.h create mode 100644 CleverTapSDK/CTUserInfoMigrator.m diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 15874d99..92ae7c42 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -11,6 +11,7 @@ #import "CTPreferences.h" #import "CTUtils.h" #import "CTUIUtils.h" +#import "CTUserInfoMigrator.h" static const void *const kProfileBackgroundQueueKey = &kProfileBackgroundQueueKey; static const double kProfilePersistenceIntervalSeconds = 30.f; @@ -45,6 +46,9 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:( _piiKeys = CLTAP_ENCRYPTION_PII_DATA; [self runOnBackgroundQueue:^{ @synchronized (self->localProfileForSession) { + // migrate to new persisted ct-accid-guid-userprofile + [CTUserInfoMigrator migrateUserInfoFileForAccountID:self->_config.accountId deviceID:self->_deviceInfo.deviceId]; + self->localProfileForSession = [self _inflateLocalProfile]; for (NSString* key in [profileValues allKeys]) { [self setProfileFieldWithKey:key andValue:profileValues[key]]; @@ -88,8 +92,6 @@ - (BOOL)inBackgroundQueue { - (void)changeUser { localProfileUpdateExpiryStore = [NSMutableDictionary new]; - localProfileForSession = [NSMutableDictionary dictionary]; - // this will remove the old profile from the file system [self _persistLocalProfileAsyncWithCompletion:nil]; [self clearStoredEvents]; } @@ -622,7 +624,7 @@ - (id)_getProfileFieldFromSessionCacheWithKey:(NSString *)key { } - (NSString *)profileFileName { - return [NSString stringWithFormat:@"clevertap-%@-userprofile.plist", self.config.accountId]; + return [NSString stringWithFormat:@"clevertap-%@-%@-userprofile.plist", self.config.accountId, _deviceInfo.deviceId]; } - (NSMutableDictionary *)_inflateLocalProfile { diff --git a/CleverTapSDK/CTUserInfoMigrator.h b/CleverTapSDK/CTUserInfoMigrator.h new file mode 100644 index 00000000..5285a96a --- /dev/null +++ b/CleverTapSDK/CTUserInfoMigrator.h @@ -0,0 +1,14 @@ +// +// CTUserInfoMigrator.h +// Pods +// +// Created by Kushagra Mishra on 29/05/24. +// + +#import + +@interface CTUserInfoMigrator : NSObject + ++ (void)migrateUserInfoFileForAccountID:(NSString *)acc_id deviceID:(NSString *)device_id; + +@end diff --git a/CleverTapSDK/CTUserInfoMigrator.m b/CleverTapSDK/CTUserInfoMigrator.m new file mode 100644 index 00000000..f49a8f1d --- /dev/null +++ b/CleverTapSDK/CTUserInfoMigrator.m @@ -0,0 +1,46 @@ +// +// CTUserInfoMigrator.m +// +// Created by Kushagra Mishra on 29/05/24. +// + +#import "CTUserInfoMigrator.h" + +@implementation CTUserInfoMigrator + ++ (void)migrateUserInfoFileForAccountID:(NSString *)acc_id deviceID:(NSString *)device_id { + NSFileManager *fileManager = [NSFileManager defaultManager]; + + // Get the path to the Documents directory + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); + NSString *libraryPath = [paths objectAtIndex:0]; + + // Construct the old plist file name and path + NSString *userProfileWithDeviceID = [NSString stringWithFormat:@"clevertap-%@-userprofile.plist", acc_id]; + NSString *userProfileWithDeviceIDPath = [libraryPath stringByAppendingPathComponent:userProfileWithDeviceID]; + + // Construct the new plist file name and path + NSString *userProfileWithDeviceIDAndGUID = [NSString stringWithFormat:@"clevertap-%@-%@-userprofile.plist", acc_id, device_id]; + NSString *userProfileWithDeviceIDAndGUIDPath = [libraryPath stringByAppendingPathComponent:userProfileWithDeviceIDAndGUID]; + + NSError *error = nil; + if ([fileManager fileExistsAtPath:userProfileWithDeviceIDAndGUIDPath]) { + NSLog(@"[CTUserInfo]: new Plist file exists %@", userProfileWithDeviceIDAndGUIDPath); + if ([fileManager fileExistsAtPath:userProfileWithDeviceIDAndGUIDPath]) { + [fileManager removeItemAtPath:userProfileWithDeviceIDPath error:&error]; + return; + } + } + + // Copy the plist file to the new location with the new name + if ([fileManager copyItemAtPath:userProfileWithDeviceIDPath toPath:userProfileWithDeviceIDAndGUIDPath error:&error]) { + NSLog(@"[CTUserInfo]: Plist file copied successfully to %@", userProfileWithDeviceIDAndGUIDPath); + [fileManager removeItemAtPath:userProfileWithDeviceIDPath error:&error]; + } else { + NSLog(@"[CTUserInfo]: Failed to copy plist file: %@", error.localizedDescription); + } +} + + + +@end diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index cf86d25f..266d9fba 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -2558,9 +2558,6 @@ - (void) _asyncSwitchUser:(NSDictionary *)properties withCachedGuid:(NSString *) // clear ARP and other context for the old user [self clearUserContext]; - // clear old profile data - [self.localDataStore changeUser]; - [self.sessionManager resetSession]; if (cachedGUID) { @@ -2571,6 +2568,9 @@ - (void) _asyncSwitchUser:(NSDictionary *)properties withCachedGuid:(NSString *) [self.deviceInfo forceNewDeviceID]; } + // clear old profile data + [self.localDataStore changeUser]; + [self recordDeviceErrors]; [[self delegateManager] notifyDelegatesDeviceIdDidChange:self.deviceInfo.deviceId]; From 19d992d167c60fbee27f0266dda07173c66111aa Mon Sep 17 00:00:00 2001 From: Kushagra Mishra Date: Thu, 30 May 2024 02:50:52 +0530 Subject: [PATCH 02/24] Fill the local session cache with the old userProfile values when switching the user --- CleverTapSDK/CTLocalDataStore.m | 10 ++++++++-- CleverTapSDK/CTUserInfoMigrator.m | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 92ae7c42..c13bcc48 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -14,7 +14,7 @@ #import "CTUserInfoMigrator.h" static const void *const kProfileBackgroundQueueKey = &kProfileBackgroundQueueKey; -static const double kProfilePersistenceIntervalSeconds = 30.f; +static const double kProfilePersistenceIntervalSeconds = 1.f; NSString* const kWR_KEY_EVENTS = @"local_events_cache"; NSString* const kLocalCacheLastSync = @"local_cache_last_sync"; NSString* const kLocalCacheExpiry = @"local_cache_expiry"; @@ -92,7 +92,13 @@ - (BOOL)inBackgroundQueue { - (void)changeUser { localProfileUpdateExpiryStore = [NSMutableDictionary new]; - [self _persistLocalProfileAsyncWithCompletion:nil]; + localProfileForSession = [NSMutableDictionary new]; + [self runOnBackgroundQueue:^{ + @synchronized (self->localProfileForSession) { + self->localProfileForSession = [self _inflateLocalProfile]; + } + }]; +// [self _persistLocalProfileAsyncWithCompletion:nil]; [self clearStoredEvents]; } diff --git a/CleverTapSDK/CTUserInfoMigrator.m b/CleverTapSDK/CTUserInfoMigrator.m index f49a8f1d..88ef8e45 100644 --- a/CleverTapSDK/CTUserInfoMigrator.m +++ b/CleverTapSDK/CTUserInfoMigrator.m @@ -26,14 +26,14 @@ + (void)migrateUserInfoFileForAccountID:(NSString *)acc_id deviceID:(NSString *) NSError *error = nil; if ([fileManager fileExistsAtPath:userProfileWithDeviceIDAndGUIDPath]) { NSLog(@"[CTUserInfo]: new Plist file exists %@", userProfileWithDeviceIDAndGUIDPath); - if ([fileManager fileExistsAtPath:userProfileWithDeviceIDAndGUIDPath]) { + if ([fileManager fileExistsAtPath:userProfileWithDeviceIDPath]) { [fileManager removeItemAtPath:userProfileWithDeviceIDPath error:&error]; return; } } // Copy the plist file to the new location with the new name - if ([fileManager copyItemAtPath:userProfileWithDeviceIDPath toPath:userProfileWithDeviceIDAndGUIDPath error:&error]) { + else if ([fileManager copyItemAtPath:userProfileWithDeviceIDPath toPath:userProfileWithDeviceIDAndGUIDPath error:&error]) { NSLog(@"[CTUserInfo]: Plist file copied successfully to %@", userProfileWithDeviceIDAndGUIDPath); [fileManager removeItemAtPath:userProfileWithDeviceIDPath error:&error]; } else { From eb392208bfa9e1bf80604eca2713cca34738fb80 Mon Sep 17 00:00:00 2001 From: Akash Malhotra <> Date: Thu, 30 May 2024 14:44:13 +0530 Subject: [PATCH 03/24] fixed a build issue --- CleverTapSDK.xcodeproj/project.pbxproj | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index 583bc56f..3e6020f2 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -269,6 +269,10 @@ 4EA64A2E296C1190001D9B22 /* CTRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EA64A2B296C1190001D9B22 /* CTRequest.m */; }; 4EA64A2F296C1190001D9B22 /* CTRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EA64A2B296C1190001D9B22 /* CTRequest.m */; }; 4EAF05022A495DD5009D9D61 /* CleverTapInstanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E1F154E27691CA0009387AE /* CleverTapInstanceTests.m */; }; + 4EB3638B2C087A8200C00AE2 /* CTUserInfoMigrator.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EB363892C087A8200C00AE2 /* CTUserInfoMigrator.m */; }; + 4EB3638C2C087A8200C00AE2 /* CTUserInfoMigrator.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EB363892C087A8200C00AE2 /* CTUserInfoMigrator.m */; }; + 4EB3638D2C087A8200C00AE2 /* CTUserInfoMigrator.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EB3638A2C087A8200C00AE2 /* CTUserInfoMigrator.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 4EB3638E2C087A8200C00AE2 /* CTUserInfoMigrator.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EB3638A2C087A8200C00AE2 /* CTUserInfoMigrator.h */; settings = {ATTRIBUTES = (Private, ); }; }; 4EB4C8BE2AAD91AC00B7F045 /* CTTriggerEvaluator.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EB4C8BC2AAD91AC00B7F045 /* CTTriggerEvaluator.h */; }; 4EB4C8BF2AAD91AC00B7F045 /* CTTriggerEvaluator.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EB4C8BD2AAD91AC00B7F045 /* CTTriggerEvaluator.m */; }; 4ECD88312ADC8A05003885CE /* CTSessionManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ECD88302ADC8A05003885CE /* CTSessionManagerTests.m */; }; @@ -768,6 +772,8 @@ 4EA64A25296C115E001D9B22 /* CTRequestFactory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTRequestFactory.m; sourceTree = ""; }; 4EA64A2A296C1190001D9B22 /* CTRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTRequest.h; sourceTree = ""; }; 4EA64A2B296C1190001D9B22 /* CTRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTRequest.m; sourceTree = ""; }; + 4EB363892C087A8200C00AE2 /* CTUserInfoMigrator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTUserInfoMigrator.m; sourceTree = ""; }; + 4EB3638A2C087A8200C00AE2 /* CTUserInfoMigrator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTUserInfoMigrator.h; sourceTree = ""; }; 4EB4C8BC2AAD91AC00B7F045 /* CTTriggerEvaluator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTTriggerEvaluator.h; sourceTree = ""; }; 4EB4C8BD2AAD91AC00B7F045 /* CTTriggerEvaluator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTTriggerEvaluator.m; sourceTree = ""; }; 4EC2D084278AAD8000F4DE54 /* IdentityManagementTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IdentityManagementTests.m; sourceTree = ""; }; @@ -1581,6 +1587,8 @@ D0CACF8D20B8A44C00A02327 /* CTCertificatePinning.m */, D0CACF9420B8A4F800A02327 /* CTPinnedNSURLSessionDelegate.h */, D0CACF9520B8A4F800A02327 /* CTPinnedNSURLSessionDelegate.m */, + 4EB3638A2C087A8200C00AE2 /* CTUserInfoMigrator.h */, + 4EB363892C087A8200C00AE2 /* CTUserInfoMigrator.m */, 071EB4AE217F6427008F0FAB /* CTInAppUtils.h */, 071EB47D217F6427008F0FAB /* CTInAppUtils.m */, 071EB486217F6427008F0FAB /* CTInAppFCManager.h */, @@ -1697,6 +1705,7 @@ 4E49AE54275D24570074A774 /* CTValidationResultStack.h in Headers */, 07BF465D217F7C88002E166D /* CTInAppDisplayViewControllerPrivate.h in Headers */, 6BD334F42AF7FC660099E33E /* CTTriggerRadius.h in Headers */, + 4EB3638E2C087A8200C00AE2 /* CTUserInfoMigrator.h in Headers */, 4E838C41299F419900ED0875 /* ContentMerger.h in Headers */, D0BD75A82417694F0006EE55 /* CTFeatureFlagsController.h in Headers */, 4E25E3CD278887A80008C888 /* CTLegacyIdentityRepo.h in Headers */, @@ -1780,6 +1789,7 @@ 4E8B816B2AD2B2FD00714BB4 /* CleverTapInternal.h in Headers */, 07B94541219EA34300D4C542 /* CleverTapInboxViewControllerPrivate.h in Headers */, 071EB4EE217F6427008F0FAB /* CTAlertViewController.h in Headers */, + 4EB3638D2C087A8200C00AE2 /* CTUserInfoMigrator.h in Headers */, 4E838C4629A0C94B00ED0875 /* CleverTap+CTVar.h in Headers */, D0CACF9620B8A4F800A02327 /* CTPinnedNSURLSessionDelegate.h in Headers */, 4E41FD92294F46510001FBED /* CTVar-Internal.h in Headers */, @@ -2239,6 +2249,7 @@ 6AA1357C2A2E467800EFF2C1 /* NSDictionary+Extensions.m in Sources */, D014B8EF20E2FAAD001E0780 /* CleverTapUTMDetail.m in Sources */, 4E41FD9D294F46510001FBED /* CTVarCache.m in Sources */, + 4EB3638C2C087A8200C00AE2 /* CTUserInfoMigrator.m in Sources */, 4E4E17852B50007D009E2F1E /* CTAES.m in Sources */, D014B91D20E2FBD6001E0780 /* CTCertificatePinning.m in Sources */, D014B90920E2FB71001E0780 /* CTLocalDataStore.m in Sources */, @@ -2431,6 +2442,7 @@ D01A0895207EC2D400423D6F /* CleverTapInstanceConfig.m in Sources */, 071EB4C8217F6427008F0FAB /* CTDismissButton.m in Sources */, 4EF0D5472AD84BCA0044C48F /* CTSessionManager.m in Sources */, + 4EB3638B2C087A8200C00AE2 /* CTUserInfoMigrator.m in Sources */, 4EB4C8BF2AAD91AC00B7F045 /* CTTriggerEvaluator.m in Sources */, 0701E9652372DE9C0034AAC2 /* CleverTapDisplayUnit.m in Sources */, 072F9E3E21B1368000BC6313 /* CTInboxIconMessageCell.m in Sources */, From d0fb2dfb7d0d03861783e9d3d245dcdb3acfe629 Mon Sep 17 00:00:00 2001 From: Kushagra Mishra Date: Tue, 4 Jun 2024 02:15:46 +0530 Subject: [PATCH 04/24] Refactor Event adapter and Trigger Adapter to start using profile attribute names --- CleverTapSDK/CTConstants.h | 6 ++ CleverTapSDK/CleverTap.m | 80 ++++++++++++++++++- .../InApps/CTInAppEvaluationManager.h | 1 + .../InApps/CTInAppEvaluationManager.m | 34 ++++++++ CleverTapSDK/InApps/Matchers/CTEventAdapter.h | 6 ++ CleverTapSDK/InApps/Matchers/CTEventAdapter.m | 14 ++++ .../InApps/Matchers/CTTriggerAdapter.h | 1 + .../InApps/Matchers/CTTriggerAdapter.m | 2 + .../InApps/Matchers/CTTriggersMatcher.m | 2 +- 9 files changed, 143 insertions(+), 3 deletions(-) diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index c21fd5b2..c6c5a4e4 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -50,6 +50,11 @@ extern NSString *const kSessionId; #define CLTAP_SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) #define CLTAP_APP_LAUNCHED_EVENT @"App Launched" #define CLTAP_CHARGED_EVENT @"Charged" +#define CLTAP_PROFILE @"profile" +#define CLTAP_USER_ATTRIBUTE_CHANGE @"_CTUserAttributeChange" +#define CLTAP_KEY_NEW_VALUE @"newValue" +#define CLTAP_KEY_OLD_VALUE @"oldValue" +#define CLTAP_KEY_PROFILE_ATTR_NAME @"profileAttrName" #define CLTAP_EVENT_NAME @"evtName" #define CLTAP_EVENT_DATA @"evtData" #define CLTAP_CHARGED_EVENT_ITEMS @"Items" @@ -241,6 +246,7 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY; #pragma mark Constants for Profile identifier keys #define CLTAP_PROFILE_IDENTIFIER_KEYS @[@"Identity", @"Email"] // LEGACY KEYS #define CLTAP_ALL_PROFILE_IDENTIFIER_KEYS @[@"Identity", @"Email", @"Phone"] +#define CLTAP_keysToSkipForUserAttributesEvaluation @[@"cc", @"tz", @"Carrier"] #pragma mark Constants for Encryption #define CLTAP_ENCRYPTION_LEVEL @"CleverTapEncryptionLevel" diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 266d9fba..d3e59b18 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -1945,7 +1945,7 @@ - (void)processEvent:(NSDictionary *)event withType:(CleverTapEventType)eventTyp #if !CLEVERTAP_NO_INAPP_SUPPORT // Evaluate the event only if it will be processed [self.dispatchQueueManager runSerialAsync:^{ - [self evaluateOnEvent:event]; + [self evaluateOnEvent:event withType: eventType]; }]; #endif @@ -1960,7 +1960,7 @@ - (void)processEvent:(NSDictionary *)event withType:(CleverTapEventType)eventTyp } } -- (void)evaluateOnEvent:(NSDictionary *)event { +- (void)evaluateOnEvent:(NSDictionary *)event withType:(CleverTapEventType)eventType { NSString *eventName = event[CLTAP_EVENT_NAME]; // Add the system properties for evaluation NSMutableDictionary *eventData = [[NSMutableDictionary alloc] initWithDictionary:[self generateAppFields]]; @@ -1970,6 +1970,13 @@ - (void)evaluateOnEvent:(NSDictionary *)event { if (eventName && [eventName isEqualToString:CLTAP_CHARGED_EVENT]) { NSArray *items = eventData[CLTAP_CHARGED_EVENT_ITEMS]; [self.inAppEvaluationManager evaluateOnChargedEvent:eventData andItems:items]; + } else if (eventType == CleverTapEventTypeProfile) { + // debugging :- UserAttributekey + // Creating a sample event dictionary to simulate a JSON object + NSDictionary *> *result = [self getUserAttributeChangeProperties:event]; + NSLog(@"User Attributes: %@", result); + [self.inAppEvaluationManager evaluateOnUserAttributeChange:result]; + } else if (eventName) { [self.inAppEvaluationManager evaluateOnEvent:eventName withProps:eventData]; } @@ -2923,6 +2930,75 @@ - (void)_handleMultiValueProfilePush:(NSDictionary*)customFields updatedMultiVal } } +- (NSMutableDictionary *)getLocalUserProfileFromCache:(NSDictionary *)event { + NSMutableDictionary *profile = [[NSMutableDictionary alloc] init]; + [self.dispatchQueueManager runSerialAsync:^{ + [CTProfileBuilder build:event completionHandler:^(NSDictionary *customFields, NSDictionary *systemFields, NSArray*errors) { + if (systemFields) { + [self.localDataStore setProfileFields:systemFields]; + } + NSMutableDictionary *profile = [[self.localDataStore generateBaseProfile] mutableCopy]; + if (customFields) { + CleverTapLogInternal(self.config.logLevel, @"%@: Constructed custom profile: %@", self, customFields); + [self.localDataStore setProfileFields:customFields]; + [profile addEntriesFromDictionary:customFields]; + } + }]; + }]; + return profile; +} + + +//Debugging +- (NSDictionary *> *)getUserAttributeChangeProperties:(NSDictionary *)event { + // Get the profile dictionary from the event + NSDictionary *PROFILE_FIELDS_IN_THIS_SESSION = @{ + @"Email": @"old_email@example.com", + @"Gender": @"F", + @"Name": @"Old Name", + @"Phone": @1234567890, + @"cc": @"US", + @"kush": @"old_kusha1", + @"kush2": @"old_kusha2", + @"kush3": @"old_kusha3", + @"kush4": @"old_kusha4", + @"score": @10, + @"tz": @"America/New_York", + @"Cust_Type": @"Silver" + }; + + //debug + NSDictionary *profile = event[@"profile"]; + if (!profile) { + return @{}; + } + + NSMutableDictionary *> *result = [NSMutableDictionary dictionary]; + NSMutableDictionary *oldProfile = [self getLocalUserProfileFromCache:event]; + // Iterate through all keys in the profile dictionary + for (NSString *key in profile) { + if ([CLTAP_keysToSkipForUserAttributesEvaluation isEqual: key]) { + continue; + } + id oldValue = oldProfile[key]; +// id oldValue = PROFILE_FIELDS_IN_THIS_SESSION[key]; + id newValue = profile[key]; + + // Only add to the result if oldValue and newValue are different + if (![oldValue isEqual:newValue]) { + NSDictionary *valueDict = @{ + @"oldValue": oldValue ?: [NSNull null], + @"newValue": newValue + }; + result[key] = valueDict; + } + } + + return [result copy]; +} + + + #pragma mark - User Action Events API diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.h b/CleverTapSDK/InApps/CTInAppEvaluationManager.h index 4b06163d..2dbea791 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.h +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.h @@ -34,6 +34,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)evaluateOnEvent:(NSString *)eventName withProps:(NSDictionary *)properties; - (void)evaluateOnChargedEvent:(NSDictionary *)chargeDetails andItems:(NSArray *)items; +- (void)evaluateOnUserAttributeChange:(NSDictionary *)properties; - (void)evaluateOnAppLaunchedClientSide; - (void)evaluateOnAppLaunchedServerSide:(NSArray *)appLaunchedNotifs; diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.m b/CleverTapSDK/InApps/CTInAppEvaluationManager.m index 175ed4e8..e12afe46 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.m +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.m @@ -91,6 +91,40 @@ - (void)evaluateOnEvent:(NSString *)eventName withProps:(NSDictionary *)properti [self evaluateClientSide:event]; } +/** + * Evaluates in-app notifications based on a profile event that corresponds to any profile attribute changes, incorporating the event name, + * additional properties associated with the event, the user's location and the profile attribute name. + * The key of an eventProperty is the profile attribute name that has been invoked in the profile event. + * + * This method creates an [EventAdapter] instance representing the specified event with the provided details, + * evaluates the event against server-side, and then proceeds to evaluate it client-side. + * + * @param properties Additional properties associated with the event, provided as a map. + * + * + * + * This array includes in-app notifications that meet the criteria for display. + */ + +-(void)evaluateOnUserAttributeChange:(NSDictionary *)profile { + + NSString *eventName = @"CustomerTypeChanged"; + + for (NSString *key in profile) { + NSDictionary *innerDictionary = profile[key]; + + NSString *newValue = innerDictionary[@"newValue"]; + NSString *oldValue = innerDictionary[@"oldValue"]; + + // Do something with key, newValue, and oldValue + NSLog(@"Key: %@, New Value: %@, Old Value: %@", key, newValue, oldValue); + CTEventAdapter *event = [[CTEventAdapter alloc] initWithprofileAttrName:key eventProperties:innerDictionary andLocation:self.location]; + [self evaluateServerSide:event]; + [self evaluateClientSide:event]; + } + +} + - (void)evaluateOnChargedEvent:(NSDictionary *)chargeDetails andItems:(NSArray *)items { CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:CLTAP_CHARGED_EVENT eventProperties:chargeDetails location:self.location andItems:items]; [self evaluateServerSide:event]; diff --git a/CleverTapSDK/InApps/Matchers/CTEventAdapter.h b/CleverTapSDK/InApps/Matchers/CTEventAdapter.h index dca2635c..9c9bb199 100644 --- a/CleverTapSDK/InApps/Matchers/CTEventAdapter.h +++ b/CleverTapSDK/InApps/Matchers/CTEventAdapter.h @@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, readonly) NSString *eventName; @property (nonatomic, assign, readonly) CLLocationCoordinate2D location; +@property (nonatomic, strong, readonly) NSString *profileAttrName; - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithEventName:(NSString *)eventName @@ -27,6 +28,11 @@ NS_ASSUME_NONNULL_BEGIN location:(CLLocationCoordinate2D)location andItems:(NSArray *)items; +- (instancetype)initWithprofileAttrName:(NSString *)profileAttrName + eventProperties:(NSDictionary *)eventProperties + andLocation:(CLLocationCoordinate2D)location; + + - (CTTriggerValue * _Nullable)propertyValueNamed:(NSString *)name; - (CTTriggerValue * _Nullable)itemValueNamed:(NSString *)name; diff --git a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m index 35df268e..724753ee 100644 --- a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m +++ b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m @@ -17,6 +17,7 @@ @interface CTEventAdapter() @property (nonatomic, strong) NSDictionary *eventProperties; @property (nonatomic, strong) NSArray *items; @property (nonatomic, assign) CLLocationCoordinate2D location; +@property (nonatomic, strong, nullable) NSString *profileAttrName; @end @@ -74,6 +75,19 @@ - (instancetype)initWithEventName:(NSString *)eventName return self; } +- (instancetype)initWithprofileAttrName:(NSString *)profileAttrName + eventProperties:(NSDictionary *)eventProperties + andLocation:(CLLocationCoordinate2D)location { + + if (self = [super init]) { + if ([profileAttrName isEqual: @"Cust_Type"]) + self.profileAttrName = @"Customer Type"; + self.eventProperties = eventProperties; + self.location = location; + } + return self; +} + - (CTTriggerValue *)propertyValueNamed:(NSString *)name { id propertyValue = [self getActualPropertyValue:name]; if (propertyValue) { diff --git a/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.h b/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.h index a500a4a8..6f326107 100644 --- a/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.h +++ b/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.h @@ -22,6 +22,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) NSInteger propertyCount; @property (nonatomic, readonly) NSInteger itemsCount; @property (nonatomic, readonly) NSInteger geoRadiusCount; +@property (nonatomic, strong, readonly) NSString *profileAttrName; - (CTTriggerCondition * _Nullable)propertyAtIndex:(NSInteger)index; - (CTTriggerCondition * _Nullable)itemAtIndex:(NSInteger)index; diff --git a/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.m b/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.m index 83bb7f0f..c6e2eb0a 100644 --- a/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.m +++ b/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.m @@ -17,6 +17,7 @@ @interface CTTriggerAdapter() @property (nonatomic, strong) NSArray *items; @property (nonatomic, strong) NSArray *geoRadius; +@property (nonatomic, strong) NSString *profileAttrName; @end @@ -28,6 +29,7 @@ - (instancetype)initWithJSON:(NSDictionary *)triggerJSON { self.properties = triggerJSON[@"eventProperties"]; self.items = triggerJSON[@"itemProperties"]; self.geoRadius = triggerJSON[@"geoRadius"]; + self.profileAttrName = triggerJSON[@"profileAttrName"]; } return self; } diff --git a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m index 1766d49c..6cd73251 100644 --- a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m +++ b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m @@ -30,7 +30,7 @@ - (BOOL)matchEventWhenTriggers:(NSArray *)whenTriggers event:(CTEventAdapter *)e } - (BOOL)match:(CTTriggerAdapter *)trigger event:(CTEventAdapter *)event { - if (![[event eventName] isEqualToString:[trigger eventName]]) { + if (![[event eventName] isEqualToString:[trigger eventName]] && ([event profileAttrName] == nil || ![[event profileAttrName] isEqualToString:[trigger profileAttrName]])) { return NO; } From a21f7d4de69f481ac5689acd780b822ae5974a57 Mon Sep 17 00:00:00 2001 From: Kushagra Mishra Date: Thu, 6 Jun 2024 04:20:55 +0530 Subject: [PATCH 05/24] Added support for User Attribute triggering for Profile Increment and Profile Decrement API --- CleverTapSDK/CTConstants.h | 12 +- CleverTapSDK/CTLocalDataStore.m | 4 +- CleverTapSDK/CTProfileBuilder.m | 10 -- CleverTapSDK/CleverTap.m | 154 ++++++++++-------- CleverTapSDK/InApps/Matchers/CTEventAdapter.m | 3 +- 5 files changed, 105 insertions(+), 78 deletions(-) diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index c6c5a4e4..822094b2 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -81,6 +81,16 @@ extern NSString *const kSessionId; #define CLTAP_NOTIFICATION_TAG @"W$" #define CLTAP_DATE_FORMAT @"yyyyMMdd" +// profile commands +static NSString *const kCLTAP_COMMAND_SET = @"$set"; +static NSString *const kCLTAP_COMMAND_ADD = @"$add"; +static NSString *const kCLTAP_COMMAND_REMOVE = @"$remove"; +static NSString *const kCLTAP_COMMAND_INCREMENT = @"$incr"; +static NSString *const kCLTAP_COMMAND_DECREMENT = @"$decr"; +static NSString *const kCLTAP_COMMAND_DELETE = @"$delete"; + +#define CLTAP_MULTIVAL_COMMANDS @[kCLTAP_COMMAND_SET, kCLTAP_COMMAND_ADD, kCLTAP_COMMAND_REMOVE] + #pragma mark Constants for App fields #define CLTAP_APP_VERSION @"Version" #define CLTAP_LATITUDE @"Latitude" @@ -246,7 +256,7 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY; #pragma mark Constants for Profile identifier keys #define CLTAP_PROFILE_IDENTIFIER_KEYS @[@"Identity", @"Email"] // LEGACY KEYS #define CLTAP_ALL_PROFILE_IDENTIFIER_KEYS @[@"Identity", @"Email", @"Phone"] -#define CLTAP_keysToSkipForUserAttributesEvaluation @[@"cc", @"tz", @"Carrier"] +#define CLTAP_KeysToSkipForUserAttributesEvaluation @[@"cc", @"tz", @"Carrier"] #pragma mark Constants for Encryption #define CLTAP_ENCRYPTION_LEVEL @"CleverTapEncryptionLevel" diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index c13bcc48..190b3647 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -623,7 +623,9 @@ - (id)_getProfileFieldFromSessionCacheWithKey:(NSString *)key { id val = nil; @synchronized (localProfileForSession) { - val = localProfileForSession[key]; + // CACHED VALUES HAVE a "user" PREFIX, SO PREPEND IT BEFORE SEARCHING CACHE + NSString *keyToSearch = [localProfileForSession.allKeys containsObject:key] ? key : [NSString stringWithFormat:@"user%@",key]; + val = localProfileForSession[keyToSearch]; } return val; diff --git a/CleverTapSDK/CTProfileBuilder.m b/CleverTapSDK/CTProfileBuilder.m index 65fc3454..6402a458 100644 --- a/CleverTapSDK/CTProfileBuilder.m +++ b/CleverTapSDK/CTProfileBuilder.m @@ -7,16 +7,6 @@ #import "CTLocalDataStore.h" #import "CTUtils.h" -// profile commands -static NSString *const kCLTAP_COMMAND_SET = @"$set"; -static NSString *const kCLTAP_COMMAND_ADD = @"$add"; -static NSString *const kCLTAP_COMMAND_REMOVE = @"$remove"; -static NSString *const kCLTAP_COMMAND_INCREMENT = @"$incr"; -static NSString *const kCLTAP_COMMAND_DECREMENT = @"$decr"; -static NSString *const kCLTAP_COMMAND_DELETE = @"$delete"; - -#define CLTAP_MULTIVAL_COMMANDS @[kCLTAP_COMMAND_SET, kCLTAP_COMMAND_ADD, kCLTAP_COMMAND_REMOVE] - @implementation CTProfileBuilder + (void)build:(NSDictionary *)profile completionHandler:(void(^ _Nonnull )(NSDictionary* _Nullable customFields, NSDictionary* _Nullable systemFields, NSArray* _Nullable errors))completion { diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index d3e59b18..9a453aeb 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -1818,6 +1818,14 @@ - (BOOL)_shouldDropEvent:(NSDictionary *)event withType:(CleverTapEventType)type } - (void)queueEvent:(NSDictionary *)event withType:(CleverTapEventType)type { + +#if !CLEVERTAP_NO_INAPP_SUPPORT + // Evaluate the event before processing + [self.dispatchQueueManager runSerialAsync:^{ + [self evaluateOnEvent:event withType: type]; + }]; +#endif + if ([self _shouldDropEvent:event withType:type]) { return; } @@ -1942,13 +1950,6 @@ - (void)processEvent:(NSDictionary *)event withType:(CleverTapEventType)eventTyp CleverTapLogDebug(self.config.logLevel, @"%@: New event processed: %@", self, [CTUtils jsonObjectToString:mutableEvent]); -#if !CLEVERTAP_NO_INAPP_SUPPORT - // Evaluate the event only if it will be processed - [self.dispatchQueueManager runSerialAsync:^{ - [self evaluateOnEvent:event withType: eventType]; - }]; -#endif - if (eventType == CleverTapEventTypeFetch) { [self flushQueue]; } else { @@ -1970,19 +1971,17 @@ - (void)evaluateOnEvent:(NSDictionary *)event withType:(CleverTapEventType)event if (eventName && [eventName isEqualToString:CLTAP_CHARGED_EVENT]) { NSArray *items = eventData[CLTAP_CHARGED_EVENT_ITEMS]; [self.inAppEvaluationManager evaluateOnChargedEvent:eventData andItems:items]; - } else if (eventType == CleverTapEventTypeProfile) { - // debugging :- UserAttributekey - // Creating a sample event dictionary to simulate a JSON object - NSDictionary *> *result = [self getUserAttributeChangeProperties:event]; - NSLog(@"User Attributes: %@", result); - [self.inAppEvaluationManager evaluateOnUserAttributeChange:result]; - } else if (eventName) { [self.inAppEvaluationManager evaluateOnEvent:eventName withProps:eventData]; } #endif } +- (void)_evaluateOnUserAttributeChange:(NSDictionary *)properties { + NSDictionary *> *changedProfileValues = [self getUserAttributeChangeProperties:properties]; + [self.inAppEvaluationManager evaluateOnUserAttributeChange:changedProfileValues]; +} + - (void)scheduleQueueFlush { CleverTapLogInternal(self.config.logLevel, @"%@: scheduling delayed queue flush", self); dispatch_async(dispatch_get_main_queue(), ^{ @@ -2732,6 +2731,7 @@ - (void)onUserLogin:(NSDictionary *_Nonnull)properties withCleverTapID:(NSString } - (void)profilePush:(NSDictionary *)properties { + [self _evaluateOnUserAttributeChange:properties]; [self.dispatchQueueManager runSerialAsync:^{ [CTProfileBuilder build:properties completionHandler:^(NSDictionary *customFields, NSDictionary *systemFields, NSArray*errors) { if (systemFields) { @@ -2803,7 +2803,13 @@ - (id)profileGet:(NSString *)propertyName { return [self.localDataStore getProfileFieldForKey:propertyName]; } +// private method to retrieve profile values from keys +- (id)profileGetLocalValues:(NSString *)propertyName { + return [self.localDataStore getProfileFieldForKey:propertyName]; +} + - (void)profileRemoveValueForKey:(NSString *)key { + // [self _evaluateOnUserAttributeChange:properties]; [self.dispatchQueueManager runSerialAsync:^{ [CTProfileBuilder buildRemoveValueForKey:key completionHandler:^(NSDictionary *customFields, NSDictionary *systemFields, NSArray*errors) { if (customFields && [[customFields allKeys] count] > 0) { @@ -2866,11 +2872,39 @@ - (void)profileRemoveMultiValues:(NSArray *)values forKey:(NSString }]; } +- (void)profileValueHandler:(NSNumber* _Nonnull)value forKey:(NSString *_Nonnull)key forCommandIdentifier:(NSString *_Nonnull)commandIdentifier completion:(void (^)(NSNumber * _Nullable))completion{ + __block NSNumber *updatedValue = nil; + if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_INCREMENT]) { + [CTProfileBuilder buildIncrementValueBy: value forKey: key + localDataStore: _localDataStore + completionHandler: ^(NSDictionary *_Nullable operatorDict, NSNumber * _Nullable updatedValueResponse, NSArray *_Nullable errors) { + updatedValue = updatedValueResponse; + [self _handleIncrementDecrementProfilePushForKey: key updatedValue: updatedValue operatorDict: operatorDict errors: errors]; + + if (completion) { + completion(updatedValue); + } + }]; + } + else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_DECREMENT]) { + [CTProfileBuilder buildDecrementValueBy: value forKey: key + localDataStore: _localDataStore + completionHandler: ^(NSDictionary *_Nullable operatorDict, NSNumber * _Nullable updatedValueResponse, NSArray *_Nullable errors) { + updatedValue = updatedValueResponse; + [self _handleIncrementDecrementProfilePushForKey: key updatedValue: updatedValue operatorDict: operatorDict errors: errors]; + + if (completion) { + completion(updatedValue); + } + }]; + } +} + - (void)profileIncrementValueBy:(NSNumber* _Nonnull)value forKey:(NSString *_Nonnull)key { [CTProfileBuilder buildIncrementValueBy: value forKey: key localDataStore: _localDataStore completionHandler: ^(NSDictionary *_Nullable operatorDict, NSNumber * _Nullable updatedValue, NSArray *_Nullable errors) { - [self _handleIncrementDecrementProfilePushForKey: key updatedValue: updatedValue operatorDict: operatorDict errors: errors]; + [self _evaluateOnUserAttributeChange:operatorDict]; }]; } @@ -2929,61 +2963,53 @@ - (void)_handleMultiValueProfilePush:(NSDictionary*)customFields updatedMultiVal [self.validationResultStack pushValidationResults:errors]; } } - -- (NSMutableDictionary *)getLocalUserProfileFromCache:(NSDictionary *)event { - NSMutableDictionary *profile = [[NSMutableDictionary alloc] init]; - [self.dispatchQueueManager runSerialAsync:^{ - [CTProfileBuilder build:event completionHandler:^(NSDictionary *customFields, NSDictionary *systemFields, NSArray*errors) { - if (systemFields) { - [self.localDataStore setProfileFields:systemFields]; - } - NSMutableDictionary *profile = [[self.localDataStore generateBaseProfile] mutableCopy]; - if (customFields) { - CleverTapLogInternal(self.config.logLevel, @"%@: Constructed custom profile: %@", self, customFields); - [self.localDataStore setProfileFields:customFields]; - [profile addEntriesFromDictionary:customFields]; - } - }]; - }]; - return profile; -} - - -//Debugging -- (NSDictionary *> *)getUserAttributeChangeProperties:(NSDictionary *)event { - // Get the profile dictionary from the event - NSDictionary *PROFILE_FIELDS_IN_THIS_SESSION = @{ - @"Email": @"old_email@example.com", - @"Gender": @"F", - @"Name": @"Old Name", - @"Phone": @1234567890, - @"cc": @"US", - @"kush": @"old_kusha1", - @"kush2": @"old_kusha2", - @"kush3": @"old_kusha3", - @"kush4": @"old_kusha4", - @"score": @10, - @"tz": @"America/New_York", - @"Cust_Type": @"Silver" - }; - - //debug - NSDictionary *profile = event[@"profile"]; - if (!profile) { +//debugging +- (NSDictionary *> *)getUserAttributeChangeProperties:(NSDictionary *)properties { + if (!properties) { return @{}; } NSMutableDictionary *> *result = [NSMutableDictionary dictionary]; - NSMutableDictionary *oldProfile = [self getLocalUserProfileFromCache:event]; - // Iterate through all keys in the profile dictionary - for (NSString *key in profile) { - if ([CLTAP_keysToSkipForUserAttributesEvaluation isEqual: key]) { + for (NSString *key in properties) { + if ([CLTAP_KeysToSkipForUserAttributesEvaluation containsObject:key]) { continue; } - id oldValue = oldProfile[key]; -// id oldValue = PROFILE_FIELDS_IN_THIS_SESSION[key]; - id newValue = profile[key]; - + id oldValue = [self profileGetLocalValues:key]; + id newValue = properties[key]; + __block NSNumber *newUpdatedValue = nil; + NSNumber *incrementValue = nil; + if ([newValue isKindOfClass:[NSDictionary class]]) { + NSDictionary *obj = (NSDictionary *)newValue; + NSString *commandIdentifier = obj.allKeys.firstObject; + if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_INCREMENT]) { + // Handle increment + __block NSNumber *newUpdatedValue = nil; + [self profileValueHandler:obj[commandIdentifier] forKey:key forCommandIdentifier:commandIdentifier completion:^(NSNumber * _Nullable updatedValue) { + newUpdatedValue = updatedValue; + }]; + newValue = newUpdatedValue; + } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_DECREMENT]) { + // Handle decrement + __block NSNumber *newUpdatedValue = nil; + [self profileValueHandler:obj[commandIdentifier] forKey:key forCommandIdentifier:commandIdentifier completion:^(NSNumber * _Nullable updatedValue) { + newUpdatedValue = updatedValue; + }]; + newValue = newUpdatedValue; + } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_DELETE]) { + // Handle delete + newValue = nil; + } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_SET] || + [commandIdentifier isEqualToString:kCLTAP_COMMAND_ADD] || + [commandIdentifier isEqualToString:kCLTAP_COMMAND_REMOVE]) { + // Handle set/add/remove +// newValue = [self handleMultiValuesForKey:key +// values:obj[commandIdentifier] +// commandIdentifier:commandIdentifier +// oldValue:profile[key]]; + } else { + NSLog(@"Unhandled command %@ in dictionary for key %@: %@", commandIdentifier, key, newValue); + } + } // Only add to the result if oldValue and newValue are different if (![oldValue isEqual:newValue]) { NSDictionary *valueDict = @{ diff --git a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m index 724753ee..a27c644c 100644 --- a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m +++ b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m @@ -80,8 +80,7 @@ - (instancetype)initWithprofileAttrName:(NSString *)profileAttrName andLocation:(CLLocationCoordinate2D)location { if (self = [super init]) { - if ([profileAttrName isEqual: @"Cust_Type"]) - self.profileAttrName = @"Customer Type"; + self.profileAttrName = profileAttrName; self.eventProperties = eventProperties; self.location = location; } From 584ad009a1c4dc58be3f6264e1268c5577898631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKushagra?= <“kushagra@clevertap.com”> Date: Tue, 11 Jun 2024 03:28:55 +0530 Subject: [PATCH 06/24] Refractored the CTEventAdapter constructor to incorporate profileAttr parameter --- CleverTapSDK/InApps/CTInAppEvaluationManager.m | 8 ++++---- CleverTapSDK/InApps/Matchers/CTEventAdapter.h | 7 ++++--- CleverTapSDK/InApps/Matchers/CTEventAdapter.m | 9 +++++---- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.m b/CleverTapSDK/InApps/CTInAppEvaluationManager.m index e12afe46..3df6abf4 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.m +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.m @@ -108,17 +108,17 @@ - (void)evaluateOnEvent:(NSString *)eventName withProps:(NSDictionary *)properti -(void)evaluateOnUserAttributeChange:(NSDictionary *)profile { - NSString *eventName = @"CustomerTypeChanged"; - for (NSString *key in profile) { NSDictionary *innerDictionary = profile[key]; NSString *newValue = innerDictionary[@"newValue"]; NSString *oldValue = innerDictionary[@"oldValue"]; - + NSString *eventName = [key stringByAppendingString:CLTAP_USER_ATTRIBUTE_CHANGE]; // Do something with key, newValue, and oldValue NSLog(@"Key: %@, New Value: %@, Old Value: %@", key, newValue, oldValue); - CTEventAdapter *event = [[CTEventAdapter alloc] initWithprofileAttrName:key eventProperties:innerDictionary andLocation:self.location]; + + CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:eventName profileAttrName:key eventProperties: innerDictionary andLocation:self.location]; + [self evaluateServerSide:event]; [self evaluateClientSide:event]; } diff --git a/CleverTapSDK/InApps/Matchers/CTEventAdapter.h b/CleverTapSDK/InApps/Matchers/CTEventAdapter.h index 9c9bb199..a863fdbb 100644 --- a/CleverTapSDK/InApps/Matchers/CTEventAdapter.h +++ b/CleverTapSDK/InApps/Matchers/CTEventAdapter.h @@ -28,9 +28,10 @@ NS_ASSUME_NONNULL_BEGIN location:(CLLocationCoordinate2D)location andItems:(NSArray *)items; -- (instancetype)initWithprofileAttrName:(NSString *)profileAttrName - eventProperties:(NSDictionary *)eventProperties - andLocation:(CLLocationCoordinate2D)location; +- (instancetype)initWithEventName:(NSString *)eventName + profileAttrName:(NSString *)profileAttrName + eventProperties:(NSDictionary *)eventProperties + andLocation:(CLLocationCoordinate2D)location; - (CTTriggerValue * _Nullable)propertyValueNamed:(NSString *)name; diff --git a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m index a27c644c..f772801a 100644 --- a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m +++ b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m @@ -75,11 +75,12 @@ - (instancetype)initWithEventName:(NSString *)eventName return self; } -- (instancetype)initWithprofileAttrName:(NSString *)profileAttrName - eventProperties:(NSDictionary *)eventProperties - andLocation:(CLLocationCoordinate2D)location { - +- (instancetype)initWithEventName:(NSString *)eventName + profileAttrName:(NSString *)profileAttrName + eventProperties:(NSDictionary *)eventProperties + andLocation:(CLLocationCoordinate2D)location{ if (self = [super init]) { + self.eventName = eventName; self.profileAttrName = profileAttrName; self.eventProperties = eventProperties; self.location = location; From 684948f7300d213bb7b4f1b16ffb9e41b270a979 Mon Sep 17 00:00:00 2001 From: Kushagra Date: Wed, 12 Jun 2024 03:26:01 +0530 Subject: [PATCH 07/24] Code cleaning --- CleverTapSDK/CTConstants.h | 1 + CleverTapSDK/CTValidator.m | 2 +- CleverTapSDK/CleverTap.m | 143 +++++++----------- .../InApps/CTInAppEvaluationManager.m | 18 --- 4 files changed, 56 insertions(+), 108 deletions(-) diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index 822094b2..c9b69052 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -80,6 +80,7 @@ extern NSString *const kSessionId; #define CLTAP_NOTIFICATION_CLICKED_TAG @"wzrk_cts" #define CLTAP_NOTIFICATION_TAG @"W$" #define CLTAP_DATE_FORMAT @"yyyyMMdd" +#define CLTAP_DATE_PREFIX @"$D_" // profile commands static NSString *const kCLTAP_COMMAND_SET = @"$set"; diff --git a/CleverTapSDK/CTValidator.m b/CleverTapSDK/CTValidator.m index 314ede39..a5329611 100644 --- a/CleverTapSDK/CTValidator.m +++ b/CleverTapSDK/CTValidator.m @@ -185,7 +185,7 @@ + (CTValidationResult *)cleanObjectValue:(NSObject *)o context:(CTValidatorConte } return vr; } else if ([o isKindOfClass:[NSDate class]]) { - NSString *date = [NSString stringWithFormat:@"$D_%d", (int) ((NSDate *) o).timeIntervalSince1970]; + NSString *date = [NSString stringWithFormat:@"%@%d",CLTAP_DATE_PREFIX, (int) ((NSDate *) o).timeIntervalSince1970]; [vr setObject:date]; return vr; diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 9a453aeb..6f638ec5 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -1818,14 +1818,6 @@ - (BOOL)_shouldDropEvent:(NSDictionary *)event withType:(CleverTapEventType)type } - (void)queueEvent:(NSDictionary *)event withType:(CleverTapEventType)type { - -#if !CLEVERTAP_NO_INAPP_SUPPORT - // Evaluate the event before processing - [self.dispatchQueueManager runSerialAsync:^{ - [self evaluateOnEvent:event withType: type]; - }]; -#endif - if ([self _shouldDropEvent:event withType:type]) { return; } @@ -1950,6 +1942,13 @@ - (void)processEvent:(NSDictionary *)event withType:(CleverTapEventType)eventTyp CleverTapLogDebug(self.config.logLevel, @"%@: New event processed: %@", self, [CTUtils jsonObjectToString:mutableEvent]); +#if !CLEVERTAP_NO_INAPP_SUPPORT + // Evaluate the event only if it will be processed + [self.dispatchQueueManager runSerialAsync:^{ + [self evaluateOnEvent:event withType: eventType]; + }]; +#endif + if (eventType == CleverTapEventTypeFetch) { [self flushQueue]; } else { @@ -1971,17 +1970,16 @@ - (void)evaluateOnEvent:(NSDictionary *)event withType:(CleverTapEventType)event if (eventName && [eventName isEqualToString:CLTAP_CHARGED_EVENT]) { NSArray *items = eventData[CLTAP_CHARGED_EVENT_ITEMS]; [self.inAppEvaluationManager evaluateOnChargedEvent:eventData andItems:items]; + } else if (eventType == CleverTapEventTypeProfile) { + NSDictionary *> *result = [self getUserAttributeChangeProperties:event]; + NSLog(@"User Attributes: %@", result); + [self.inAppEvaluationManager evaluateOnUserAttributeChange:result]; } else if (eventName) { [self.inAppEvaluationManager evaluateOnEvent:eventName withProps:eventData]; } #endif } -- (void)_evaluateOnUserAttributeChange:(NSDictionary *)properties { - NSDictionary *> *changedProfileValues = [self getUserAttributeChangeProperties:properties]; - [self.inAppEvaluationManager evaluateOnUserAttributeChange:changedProfileValues]; -} - - (void)scheduleQueueFlush { CleverTapLogInternal(self.config.logLevel, @"%@: scheduling delayed queue flush", self); dispatch_async(dispatch_get_main_queue(), ^{ @@ -2731,7 +2729,6 @@ - (void)onUserLogin:(NSDictionary *_Nonnull)properties withCleverTapID:(NSString } - (void)profilePush:(NSDictionary *)properties { - [self _evaluateOnUserAttributeChange:properties]; [self.dispatchQueueManager runSerialAsync:^{ [CTProfileBuilder build:properties completionHandler:^(NSDictionary *customFields, NSDictionary *systemFields, NSArray*errors) { if (systemFields) { @@ -2784,6 +2781,10 @@ - (NSString *)profileGetCleverTapID { return self.deviceInfo.deviceId; } +- (id)profileGetLocalValues:(NSString *)propertyName { + return [self.localDataStore getProfileFieldForKey:propertyName]; +} + - (NSString *)getAccountID { return self.config.accountId; } @@ -2803,13 +2804,7 @@ - (id)profileGet:(NSString *)propertyName { return [self.localDataStore getProfileFieldForKey:propertyName]; } -// private method to retrieve profile values from keys -- (id)profileGetLocalValues:(NSString *)propertyName { - return [self.localDataStore getProfileFieldForKey:propertyName]; -} - - (void)profileRemoveValueForKey:(NSString *)key { - // [self _evaluateOnUserAttributeChange:properties]; [self.dispatchQueueManager runSerialAsync:^{ [CTProfileBuilder buildRemoveValueForKey:key completionHandler:^(NSDictionary *customFields, NSDictionary *systemFields, NSArray*errors) { if (customFields && [[customFields allKeys] count] > 0) { @@ -2872,39 +2867,11 @@ - (void)profileRemoveMultiValues:(NSArray *)values forKey:(NSString }]; } -- (void)profileValueHandler:(NSNumber* _Nonnull)value forKey:(NSString *_Nonnull)key forCommandIdentifier:(NSString *_Nonnull)commandIdentifier completion:(void (^)(NSNumber * _Nullable))completion{ - __block NSNumber *updatedValue = nil; - if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_INCREMENT]) { - [CTProfileBuilder buildIncrementValueBy: value forKey: key - localDataStore: _localDataStore - completionHandler: ^(NSDictionary *_Nullable operatorDict, NSNumber * _Nullable updatedValueResponse, NSArray *_Nullable errors) { - updatedValue = updatedValueResponse; - [self _handleIncrementDecrementProfilePushForKey: key updatedValue: updatedValue operatorDict: operatorDict errors: errors]; - - if (completion) { - completion(updatedValue); - } - }]; - } - else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_DECREMENT]) { - [CTProfileBuilder buildDecrementValueBy: value forKey: key - localDataStore: _localDataStore - completionHandler: ^(NSDictionary *_Nullable operatorDict, NSNumber * _Nullable updatedValueResponse, NSArray *_Nullable errors) { - updatedValue = updatedValueResponse; - [self _handleIncrementDecrementProfilePushForKey: key updatedValue: updatedValue operatorDict: operatorDict errors: errors]; - - if (completion) { - completion(updatedValue); - } - }]; - } -} - - (void)profileIncrementValueBy:(NSNumber* _Nonnull)value forKey:(NSString *_Nonnull)key { [CTProfileBuilder buildIncrementValueBy: value forKey: key localDataStore: _localDataStore completionHandler: ^(NSDictionary *_Nullable operatorDict, NSNumber * _Nullable updatedValue, NSArray *_Nullable errors) { - [self _evaluateOnUserAttributeChange:operatorDict]; + [self _handleIncrementDecrementProfilePushForKey: key updatedValue: updatedValue operatorDict: operatorDict errors: errors]; }]; } @@ -2963,67 +2930,65 @@ - (void)_handleMultiValueProfilePush:(NSDictionary*)customFields updatedMultiVal [self.validationResultStack pushValidationResults:errors]; } } -//debugging -- (NSDictionary *> *)getUserAttributeChangeProperties:(NSDictionary *)properties { - if (!properties) { + +- (NSDictionary *> *)getUserAttributeChangeProperties:(NSDictionary *)event { + NSDictionary *profile = event[@"profile"]; + if (!profile) { return @{}; } NSMutableDictionary *> *result = [NSMutableDictionary dictionary]; - for (NSString *key in properties) { - if ([CLTAP_KeysToSkipForUserAttributesEvaluation containsObject:key]) { + for (NSString *key in profile) { + if ([CLTAP_KeysToSkipForUserAttributesEvaluation containsObject: key]) { continue; } id oldValue = [self profileGetLocalValues:key]; - id newValue = properties[key]; - __block NSNumber *newUpdatedValue = nil; - NSNumber *incrementValue = nil; - if ([newValue isKindOfClass:[NSDictionary class]]) { - NSDictionary *obj = (NSDictionary *)newValue; - NSString *commandIdentifier = obj.allKeys.firstObject; - if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_INCREMENT]) { - // Handle increment - __block NSNumber *newUpdatedValue = nil; - [self profileValueHandler:obj[commandIdentifier] forKey:key forCommandIdentifier:commandIdentifier completion:^(NSNumber * _Nullable updatedValue) { - newUpdatedValue = updatedValue; - }]; - newValue = newUpdatedValue; - } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_DECREMENT]) { - // Handle decrement - __block NSNumber *newUpdatedValue = nil; - [self profileValueHandler:obj[commandIdentifier] forKey:key forCommandIdentifier:commandIdentifier completion:^(NSNumber * _Nullable updatedValue) { - newUpdatedValue = updatedValue; - }]; - newValue = newUpdatedValue; - } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_DELETE]) { - // Handle delete - newValue = nil; - } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_SET] || - [commandIdentifier isEqualToString:kCLTAP_COMMAND_ADD] || - [commandIdentifier isEqualToString:kCLTAP_COMMAND_REMOVE]) { - // Handle set/add/remove + id newValue = profile[key]; +// __block NSNumber *newUpdatedValue = nil; +// NSNumber *incrementValue = nil; +// if ([newValue isKindOfClass:[NSDictionary class]]) { +// NSDictionary *obj = (NSDictionary *)newValue; +// NSString *commandIdentifier = obj.allKeys.firstObject; +// if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_INCREMENT]) { +// // Handle increment +// __block NSNumber *newUpdatedValue = nil; +// [self profileValueHandler:obj[commandIdentifier] forKey:key forCommandIdentifier:commandIdentifier completion:^(NSNumber * _Nullable updatedValue) { +// newUpdatedValue = updatedValue; +// }]; +// newValue = newUpdatedValue; +// } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_DECREMENT]) { +// // Handle decrement +// __block NSNumber *newUpdatedValue = nil; +// [self profileValueHandler:obj[commandIdentifier] forKey:key forCommandIdentifier:commandIdentifier completion:^(NSNumber * _Nullable updatedValue) { +// newUpdatedValue = updatedValue; +// }]; +// newValue = newUpdatedValue; +// } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_DELETE]) { +// // Handle delete +// newValue = nil; +// } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_SET] || +// [commandIdentifier isEqualToString:kCLTAP_COMMAND_ADD] || +// [commandIdentifier isEqualToString:kCLTAP_COMMAND_REMOVE]) { +// Handle set/add/remove // newValue = [self handleMultiValuesForKey:key // values:obj[commandIdentifier] // commandIdentifier:commandIdentifier // oldValue:profile[key]]; - } else { - NSLog(@"Unhandled command %@ in dictionary for key %@: %@", commandIdentifier, key, newValue); - } - } +// } else { +// NSLog(@"Unhandled command %@ in dictionary for key %@: %@", commandIdentifier, key, newValue); +// } +// } + // Only add to the result if oldValue and newValue are different - if (![oldValue isEqual:newValue]) { NSDictionary *valueDict = @{ @"oldValue": oldValue ?: [NSNull null], @"newValue": newValue }; result[key] = valueDict; - } } - return [result copy]; } - #pragma mark - User Action Events API diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.m b/CleverTapSDK/InApps/CTInAppEvaluationManager.m index 3df6abf4..95bf14da 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.m +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.m @@ -91,21 +91,6 @@ - (void)evaluateOnEvent:(NSString *)eventName withProps:(NSDictionary *)properti [self evaluateClientSide:event]; } -/** - * Evaluates in-app notifications based on a profile event that corresponds to any profile attribute changes, incorporating the event name, - * additional properties associated with the event, the user's location and the profile attribute name. - * The key of an eventProperty is the profile attribute name that has been invoked in the profile event. - * - * This method creates an [EventAdapter] instance representing the specified event with the provided details, - * evaluates the event against server-side, and then proceeds to evaluate it client-side. - * - * @param properties Additional properties associated with the event, provided as a map. - * - * - * - * This array includes in-app notifications that meet the criteria for display. - */ - -(void)evaluateOnUserAttributeChange:(NSDictionary *)profile { for (NSString *key in profile) { @@ -116,13 +101,10 @@ -(void)evaluateOnUserAttributeChange:(NSDictionary * NSString *eventName = [key stringByAppendingString:CLTAP_USER_ATTRIBUTE_CHANGE]; // Do something with key, newValue, and oldValue NSLog(@"Key: %@, New Value: %@, Old Value: %@", key, newValue, oldValue); - CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:eventName profileAttrName:key eventProperties: innerDictionary andLocation:self.location]; - [self evaluateServerSide:event]; [self evaluateClientSide:event]; } - } - (void)evaluateOnChargedEvent:(NSDictionary *)chargeDetails andItems:(NSArray *)items { From 631b1760cd3281004d0d611c01a809b69cddbd3e Mon Sep 17 00:00:00 2001 From: Kushagra Date: Wed, 12 Jun 2024 05:28:36 +0530 Subject: [PATCH 08/24] Refractored the logic for evaluating client side and server side in apps by passing them the array as argument --- .../InApps/CTInAppEvaluationManager.m | 60 ++++++++++++------- CleverTapSDK/InApps/Matchers/CTEventAdapter.h | 2 +- CleverTapSDK/InApps/Matchers/CTEventAdapter.m | 8 +-- 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.m b/CleverTapSDK/InApps/CTInAppEvaluationManager.m index 95bf14da..542d5379 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.m +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.m @@ -36,8 +36,8 @@ @interface CTInAppEvaluationManager() @property (nonatomic, strong) NSString *accountId; @property (nonatomic, strong) NSString *deviceId; -- (void)evaluateServerSide:(CTEventAdapter *)event; -- (void)evaluateClientSide:(CTEventAdapter *)event; +- (void)evaluateServerSide:(NSArray *)events; +- (void)evaluateClientSide:(NSArray *)events; - (NSMutableArray *)evaluate:(CTEventAdapter *)event withInApps:(NSArray *)inApps; @end @@ -87,35 +87,39 @@ - (void)evaluateOnEvent:(NSString *)eventName withProps:(NSDictionary *)properti } CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:eventName eventProperties:properties andLocation:self.location]; - [self evaluateServerSide:event]; - [self evaluateClientSide:event]; + NSArray *eventList = @[event]; + [self evaluateServerSide:eventList]; + [self evaluateClientSide:eventList]; } -(void)evaluateOnUserAttributeChange:(NSDictionary *)profile { - - for (NSString *key in profile) { - NSDictionary *innerDictionary = profile[key]; - - NSString *newValue = innerDictionary[@"newValue"]; - NSString *oldValue = innerDictionary[@"oldValue"]; + NSDictionary *appFields = self.appLaunchedProperties; + NSMutableArray *eventAdapterList = [NSMutableArray array]; + [profile enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) { NSString *eventName = [key stringByAppendingString:CLTAP_USER_ATTRIBUTE_CHANGE]; - // Do something with key, newValue, and oldValue - NSLog(@"Key: %@, New Value: %@, Old Value: %@", key, newValue, oldValue); - CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:eventName profileAttrName:key eventProperties: innerDictionary andLocation:self.location]; - [self evaluateServerSide:event]; - [self evaluateClientSide:event]; - } + CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:eventName profileAttrName:key eventProperties: value andLocation:self.location]; + NSMutableDictionary *mergedProperties = [event.eventProperties mutableCopy]; + [mergedProperties addEntriesFromDictionary:appFields]; + event.eventProperties = [mergedProperties copy]; + [eventAdapterList addObject:event]; + }]; + + [self evaluateServerSide:eventAdapterList]; + [self evaluateClientSide:eventAdapterList]; + } - (void)evaluateOnChargedEvent:(NSDictionary *)chargeDetails andItems:(NSArray *)items { CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:CLTAP_CHARGED_EVENT eventProperties:chargeDetails location:self.location andItems:items]; - [self evaluateServerSide:event]; - [self evaluateClientSide:event]; + NSArray *eventList = @[event]; + [self evaluateServerSide:eventList]; + [self evaluateClientSide:eventList]; } - (void)evaluateOnAppLaunchedClientSide { CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:CLTAP_APP_LAUNCHED_EVENT eventProperties:self.appLaunchedProperties andLocation:self.location]; - [self evaluateClientSide:event]; + NSArray *eventList = @[event]; + [self evaluateClientSide:eventList]; } - (void)evaluateOnAppLaunchedServerSide:(NSArray *)appLaunchedNotifs { @@ -132,8 +136,15 @@ - (void)evaluateOnAppLaunchedServerSide:(NSArray *)appLaunchedNotifs { } } -- (void)evaluateClientSide:(CTEventAdapter *)event { - NSMutableArray *eligibleInApps = [self evaluate:event withInApps:self.inAppStore.clientSideInApps]; +- (void)evaluateClientSide:(NSArray *)events { + NSMutableArray *eligibleInApps = [NSMutableArray array]; + for (CTEventAdapter *event in events) { + + // Only for CS In-Apps check if oldValue != newValue + if (![event.eventProperties[CLTAP_KEY_OLD_VALUE] isEqual:event.eventProperties[CLTAP_KEY_OLD_VALUE]]) { + [eligibleInApps addObjectsFromArray:[self evaluate:event withInApps:self.inAppStore.clientSideInApps]]; + } + } [self sortByPriority:eligibleInApps]; for (NSDictionary *inApp in eligibleInApps) { @@ -148,8 +159,11 @@ - (void)evaluateClientSide:(CTEventAdapter *)event { } } -- (void)evaluateServerSide:(CTEventAdapter *)event { - NSArray *eligibleInApps = [self evaluate:event withInApps:self.inAppStore.serverSideInApps]; +- (void)evaluateServerSide:(NSArray *)events { + NSMutableArray *eligibleInApps = [NSMutableArray array]; + for (CTEventAdapter *event in events) { + [eligibleInApps addObjectsFromArray:[self evaluate:event withInApps:self.inAppStore.serverSideInApps]]; + } BOOL updated = NO; for (NSDictionary *inApp in eligibleInApps) { NSString *campaignId = [CTInAppNotification inAppId:inApp]; diff --git a/CleverTapSDK/InApps/Matchers/CTEventAdapter.h b/CleverTapSDK/InApps/Matchers/CTEventAdapter.h index a863fdbb..a71ad180 100644 --- a/CleverTapSDK/InApps/Matchers/CTEventAdapter.h +++ b/CleverTapSDK/InApps/Matchers/CTEventAdapter.h @@ -17,6 +17,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, readonly) NSString *eventName; @property (nonatomic, assign, readonly) CLLocationCoordinate2D location; @property (nonatomic, strong, readonly) NSString *profileAttrName; +@property (nonatomic, strong, nonnull) NSMutableDictionary *eventProperties; - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithEventName:(NSString *)eventName @@ -33,7 +34,6 @@ NS_ASSUME_NONNULL_BEGIN eventProperties:(NSDictionary *)eventProperties andLocation:(CLLocationCoordinate2D)location; - - (CTTriggerValue * _Nullable)propertyValueNamed:(NSString *)name; - (CTTriggerValue * _Nullable)itemValueNamed:(NSString *)name; diff --git a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m index f772801a..e0804728 100644 --- a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m +++ b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m @@ -14,7 +14,6 @@ @interface CTEventAdapter() @property (nonatomic, strong) NSString *eventName; -@property (nonatomic, strong) NSDictionary *eventProperties; @property (nonatomic, strong) NSArray *items; @property (nonatomic, assign) CLLocationCoordinate2D location; @property (nonatomic, strong, nullable) NSString *profileAttrName; @@ -54,7 +53,7 @@ + (void)initialize { } - (instancetype)initWithEventName:(NSString *)eventName - eventProperties:(NSDictionary *)eventProperties + eventProperties:(NSMutableDictionary *)eventProperties andLocation:(CLLocationCoordinate2D)location{ if (self = [super init]) { self = [self initWithEventName:eventName eventProperties:eventProperties location:location andItems:@[]]; @@ -63,7 +62,7 @@ - (instancetype)initWithEventName:(NSString *)eventName } - (instancetype)initWithEventName:(NSString *)eventName - eventProperties:(NSDictionary *)eventProperties + eventProperties:(NSMutableDictionary *)eventProperties location:(CLLocationCoordinate2D)location andItems:(NSArray *)items { if (self = [super init]) { @@ -77,8 +76,9 @@ - (instancetype)initWithEventName:(NSString *)eventName - (instancetype)initWithEventName:(NSString *)eventName profileAttrName:(NSString *)profileAttrName - eventProperties:(NSDictionary *)eventProperties + eventProperties:(NSMutableDictionary *)eventProperties andLocation:(CLLocationCoordinate2D)location{ + if (self = [super init]) { self.eventName = eventName; self.profileAttrName = profileAttrName; From a16f1c38f528daf2bad7e47a5c30c0065e2cf144 Mon Sep 17 00:00:00 2001 From: Kushagra Date: Thu, 13 Jun 2024 15:38:07 +0530 Subject: [PATCH 09/24] Updated the logic for Profile Increment, Decrement and remove key --- CleverTapSDK/CTProfileBuilder.h | 2 + CleverTapSDK/CTProfileBuilder.m | 61 +++++++++++ CleverTapSDK/CleverTap.m | 103 +++++++++--------- .../InApps/CTInAppEvaluationManager.m | 8 +- 4 files changed, 119 insertions(+), 55 deletions(-) diff --git a/CleverTapSDK/CTProfileBuilder.h b/CleverTapSDK/CTProfileBuilder.h index 89765bb2..b3e93be5 100644 --- a/CleverTapSDK/CTProfileBuilder.h +++ b/CleverTapSDK/CTProfileBuilder.h @@ -23,4 +23,6 @@ + (void)buildDecrementValueBy:(NSNumber *_Nonnull)value forKey: (NSString *_Nonnull)key localDataStore:(CTLocalDataStore* _Nonnull)dataStore completionHandler:(void(^ _Nonnull )(NSDictionary *_Nullable operatorDict, NSNumber *_Nullable updatedValue, NSArray *_Nullable errors))completion; ++ (NSNumber *_Nullable)_getUpdatedValue:(NSNumber *_Nonnull)value forKey:(NSString *_Nonnull)key withCommand:(NSString *_Nonnull)command cachedValue:(id _Nullable)cachedValue; + @end diff --git a/CleverTapSDK/CTProfileBuilder.m b/CleverTapSDK/CTProfileBuilder.m index 6402a458..3f13802a 100644 --- a/CleverTapSDK/CTProfileBuilder.m +++ b/CleverTapSDK/CTProfileBuilder.m @@ -443,5 +443,66 @@ + (void)_handleIncrementDecrementValue:(NSNumber *_Nonnull)value forKey:(NSStrin completion(operatorDict, newValue, nil); } ++ (NSNumber *_Nullable)_getUpdatedValue:(NSNumber *_Nonnull)value forKey:(NSString *_Nonnull)key withCommand:(NSString *_Nonnull)command cachedValue:(id)cachedValue { + + NSNumber *newValue; + + if ([cachedValue isKindOfClass: [NSNumber class]]) { + + NSNumber *cachedNumber = (NSNumber*)cachedValue; + CFNumberType numberType = CFNumberGetType((CFNumberRef)cachedNumber); + + switch (numberType) { + case kCFNumberSInt8Type: + case kCFNumberSInt16Type: + case kCFNumberIntType: + case kCFNumberSInt32Type: + case kCFNumberSInt64Type: + case kCFNumberNSIntegerType: + case kCFNumberShortType: + if ([command isEqualToString: kCLTAP_COMMAND_INCREMENT]) { + newValue = [NSNumber numberWithInt: cachedNumber.intValue + value.intValue]; + } else { + newValue = [NSNumber numberWithInt: cachedNumber.intValue - value.intValue]; + } + break; + case kCFNumberLongType: + if ([command isEqualToString: kCLTAP_COMMAND_INCREMENT]) { + newValue = [NSNumber numberWithLong: cachedNumber.longValue + value.longValue]; + } else { + newValue = [NSNumber numberWithLong: cachedNumber.longValue - value.longValue]; + } + break; + case kCFNumberLongLongType: + if ([command isEqualToString: kCLTAP_COMMAND_INCREMENT]) { + newValue = [NSNumber numberWithLongLong: cachedNumber.longLongValue + value.longLongValue]; + } else { + newValue = [NSNumber numberWithLongLong: cachedNumber.longLongValue - value.longLongValue]; + } + break; + case kCFNumberFloatType: + case kCFNumberFloat32Type: + case kCFNumberFloat64Type: + case kCFNumberCGFloatType: + if ([command isEqualToString: kCLTAP_COMMAND_INCREMENT]) { + newValue = [NSNumber numberWithFloat: cachedNumber.floatValue + value.floatValue]; + } else { + newValue = [NSNumber numberWithFloat: cachedNumber.floatValue - value.floatValue]; + } + break; + case kCFNumberDoubleType: + if ([command isEqualToString: kCLTAP_COMMAND_INCREMENT]) { + newValue = [NSNumber numberWithDouble: cachedNumber.doubleValue + value.doubleValue]; + } else { + newValue = [NSNumber numberWithDouble: cachedNumber.doubleValue - value.doubleValue]; + } + break; + default: + break; + } + } + return newValue; +} + @end diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 6f638ec5..026ee3fd 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -2737,11 +2737,9 @@ - (void)profilePush:(NSDictionary *)properties { NSMutableDictionary *profile = [[self.localDataStore generateBaseProfile] mutableCopy]; if (customFields) { CleverTapLogInternal(self.config.logLevel, @"%@: Constructed custom profile: %@", self, customFields); - [self.localDataStore setProfileFields:customFields]; [profile addEntriesFromDictionary:customFields]; } [self cacheGUIDSforProfile:profile]; - #if !defined(CLEVERTAP_TVOS) // make sure Phone is a string and debug check for country code and phone format, but always send NSArray *profileAllKeys = [profile allKeys]; @@ -2811,7 +2809,6 @@ - (void)profileRemoveValueForKey:(NSString *)key { NSMutableDictionary *profile = [[self.localDataStore generateBaseProfile] mutableCopy]; NSString* _key = [customFields allKeys][0]; CleverTapLogInternal(self.config.logLevel, @"%@: removing key %@ from profile", self, _key); - [self.localDataStore removeProfileFieldForKey:_key]; [profile addEntriesFromDictionary:customFields]; NSMutableDictionary *event = [[NSMutableDictionary alloc] init]; @@ -2902,8 +2899,6 @@ - (void)_handleIncrementDecrementProfilePushForKey:(NSString *)key updatedValue: [profile addEntriesFromDictionary:operatorDict]; CleverTapLogInternal(self.config.logLevel, @"Created Increment/ Decrement profile push: %@", operatorDict); - [self.localDataStore setProfileFieldWithKey: key andValue: updatedValue]; - NSMutableDictionary *event = [[NSMutableDictionary alloc] init]; event[@"profile"] = profile; [self queueEvent:event withType:CleverTapEventTypeProfile]; @@ -2932,64 +2927,70 @@ - (void)_handleMultiValueProfilePush:(NSDictionary*)customFields updatedMultiVal } - (NSDictionary *> *)getUserAttributeChangeProperties:(NSDictionary *)event { - NSDictionary *profile = event[@"profile"]; + NSMutableDictionary *> *userAttributesChangeProperties = [NSMutableDictionary dictionary]; + NSMutableDictionary *fieldsToPersistLocally = [NSMutableDictionary dictionary]; + NSDictionary *profile = event[CLTAP_PROFILE]; if (!profile) { return @{}; } - - NSMutableDictionary *> *result = [NSMutableDictionary dictionary]; for (NSString *key in profile) { if ([CLTAP_KeysToSkipForUserAttributesEvaluation containsObject: key]) { continue; } id oldValue = [self profileGetLocalValues:key]; id newValue = profile[key]; -// __block NSNumber *newUpdatedValue = nil; -// NSNumber *incrementValue = nil; -// if ([newValue isKindOfClass:[NSDictionary class]]) { -// NSDictionary *obj = (NSDictionary *)newValue; -// NSString *commandIdentifier = obj.allKeys.firstObject; -// if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_INCREMENT]) { -// // Handle increment -// __block NSNumber *newUpdatedValue = nil; -// [self profileValueHandler:obj[commandIdentifier] forKey:key forCommandIdentifier:commandIdentifier completion:^(NSNumber * _Nullable updatedValue) { -// newUpdatedValue = updatedValue; -// }]; -// newValue = newUpdatedValue; -// } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_DECREMENT]) { -// // Handle decrement -// __block NSNumber *newUpdatedValue = nil; -// [self profileValueHandler:obj[commandIdentifier] forKey:key forCommandIdentifier:commandIdentifier completion:^(NSNumber * _Nullable updatedValue) { -// newUpdatedValue = updatedValue; -// }]; -// newValue = newUpdatedValue; -// } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_DELETE]) { -// // Handle delete -// newValue = nil; -// } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_SET] || -// [commandIdentifier isEqualToString:kCLTAP_COMMAND_ADD] || -// [commandIdentifier isEqualToString:kCLTAP_COMMAND_REMOVE]) { -// Handle set/add/remove -// newValue = [self handleMultiValuesForKey:key -// values:obj[commandIdentifier] -// commandIdentifier:commandIdentifier -// oldValue:profile[key]]; -// } else { -// NSLog(@"Unhandled command %@ in dictionary for key %@: %@", commandIdentifier, key, newValue); -// } -// } - - // Only add to the result if oldValue and newValue are different - NSDictionary *valueDict = @{ - @"oldValue": oldValue ?: [NSNull null], - @"newValue": newValue - }; - result[key] = valueDict; + NSMutableDictionary *properties = [NSMutableDictionary dictionary]; + if ([newValue isKindOfClass:[NSDictionary class]]) { + NSDictionary *obj = (NSDictionary *)newValue; + NSString *commandIdentifier = [[obj allKeys] firstObject]; + id value = [obj objectForKey:commandIdentifier]; + if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_INCREMENT] || + [commandIdentifier isEqualToString:kCLTAP_COMMAND_DECREMENT]) { + newValue = [CTProfileBuilder _getUpdatedValue:value forKey:key withCommand:commandIdentifier cachedValue:oldValue]; + } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_DELETE]) { + newValue = nil; + [self.localDataStore removeProfileFieldForKey:key]; + } + } else if ([newValue isKindOfClass:[NSString class]]) { + // Remove the date prefix before evaluation and persisting + NSString *newValueStr = (NSString *)newValue; + if ([newValueStr hasPrefix:CLTAP_DATE_PREFIX]) { + newValue = @([[newValueStr substringFromIndex:[CLTAP_DATE_PREFIX length]] longLongValue]); + } + } + if (oldValue != nil && ![oldValue isKindOfClass:[NSArray class]]) { + [properties setObject:oldValue forKey:CLTAP_KEY_OLD_VALUE]; + } + if (newValue != nil && ![newValue isKindOfClass:[NSArray class]]) { + [properties setObject:newValue forKey:CLTAP_KEY_NEW_VALUE]; + } + + // Skip evaluation if both newValue or oldValue are null + if ([properties count] > 0) { + [userAttributesChangeProperties setObject:properties forKey:key]; + } + // Need to persist only if the new profile value is not a null value + if (newValue != nil) { + [fieldsToPersistLocally setObject:newValue forKey:key]; + } } - return [result copy]; + [self updateProfileFieldsLocally:fieldsToPersistLocally]; + return userAttributesChangeProperties; } - +-(void) updateProfileFieldsLocally: (NSMutableDictionary *) fieldsToPersistLocally{ + [self.dispatchQueueManager runSerialAsync:^{ + [CTProfileBuilder build:fieldsToPersistLocally completionHandler:^(NSDictionary *customFields, NSDictionary *systemFields, NSArray*errors) { + if (systemFields) { + [self.localDataStore setProfileFields:systemFields]; + } + if (customFields) { + CleverTapLogInternal(self.config.logLevel, @"%@: Constructed custom profile: %@", self, customFields); + [self.localDataStore setProfileFields:customFields]; + } + }]; + }]; +} #pragma mark - User Action Events API diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.m b/CleverTapSDK/InApps/CTInAppEvaluationManager.m index 542d5379..99557cb7 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.m +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.m @@ -137,11 +137,11 @@ - (void)evaluateOnAppLaunchedServerSide:(NSArray *)appLaunchedNotifs { } - (void)evaluateClientSide:(NSArray *)events { - NSMutableArray *eligibleInApps = [NSMutableArray array]; + NSMutableArray *eligibleInApps = [NSMutableArray array]; for (CTEventAdapter *event in events) { - - // Only for CS In-Apps check if oldValue != newValue - if (![event.eventProperties[CLTAP_KEY_OLD_VALUE] isEqual:event.eventProperties[CLTAP_KEY_OLD_VALUE]]) { + id oldValue = [event.eventProperties objectForKey:CLTAP_KEY_OLD_VALUE]; + id newValue = [event.eventProperties objectForKey:CLTAP_KEY_NEW_VALUE]; + if (newValue == nil || newValue != oldValue){ [eligibleInApps addObjectsFromArray:[self evaluate:event withInApps:self.inAppStore.clientSideInApps]]; } } From 186a79cb0fe823c4796114e1a2f0ceb13e369322 Mon Sep 17 00:00:00 2001 From: Kushagra Date: Tue, 18 Jun 2024 16:50:57 +0530 Subject: [PATCH 10/24] Optimized code to persist profile only when the change in new profile is made --- CleverTapSDK/CleverTap.m | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 026ee3fd..aa8ba20b 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -2970,7 +2970,7 @@ - (void)_handleMultiValueProfilePush:(NSDictionary*)customFields updatedMultiVal [userAttributesChangeProperties setObject:properties forKey:key]; } // Need to persist only if the new profile value is not a null value - if (newValue != nil) { + if (newValue != nil && newValue != oldValue) { [fieldsToPersistLocally setObject:newValue forKey:key]; } } @@ -2981,9 +2981,6 @@ - (void)_handleMultiValueProfilePush:(NSDictionary*)customFields updatedMultiVal -(void) updateProfileFieldsLocally: (NSMutableDictionary *) fieldsToPersistLocally{ [self.dispatchQueueManager runSerialAsync:^{ [CTProfileBuilder build:fieldsToPersistLocally completionHandler:^(NSDictionary *customFields, NSDictionary *systemFields, NSArray*errors) { - if (systemFields) { - [self.localDataStore setProfileFields:systemFields]; - } if (customFields) { CleverTapLogInternal(self.config.logLevel, @"%@: Constructed custom profile: %@", self, customFields); [self.localDataStore setProfileFields:customFields]; From 40eafd56203e679aca5d36db98848e21e974ab65 Mon Sep 17 00:00:00 2001 From: Kushagra Date: Fri, 21 Jun 2024 10:59:28 +0530 Subject: [PATCH 11/24] Code cleaning and updated the persistence time to 30 seconds --- CleverTapSDK/CTLocalDataStore.m | 2 +- CleverTapSDK/CleverTap.m | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 190b3647..e11aedf9 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -14,7 +14,7 @@ #import "CTUserInfoMigrator.h" static const void *const kProfileBackgroundQueueKey = &kProfileBackgroundQueueKey; -static const double kProfilePersistenceIntervalSeconds = 1.f; +static const double kProfilePersistenceIntervalSeconds = 30.f; NSString* const kWR_KEY_EVENTS = @"local_events_cache"; NSString* const kLocalCacheLastSync = @"local_cache_last_sync"; NSString* const kLocalCacheExpiry = @"local_cache_expiry"; diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index aa8ba20b..8f3e4f12 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -1972,7 +1972,6 @@ - (void)evaluateOnEvent:(NSDictionary *)event withType:(CleverTapEventType)event [self.inAppEvaluationManager evaluateOnChargedEvent:eventData andItems:items]; } else if (eventType == CleverTapEventTypeProfile) { NSDictionary *> *result = [self getUserAttributeChangeProperties:event]; - NSLog(@"User Attributes: %@", result); [self.inAppEvaluationManager evaluateOnUserAttributeChange:result]; } else if (eventName) { [self.inAppEvaluationManager evaluateOnEvent:eventName withProps:eventData]; @@ -2731,12 +2730,11 @@ - (void)onUserLogin:(NSDictionary *_Nonnull)properties withCleverTapID:(NSString - (void)profilePush:(NSDictionary *)properties { [self.dispatchQueueManager runSerialAsync:^{ [CTProfileBuilder build:properties completionHandler:^(NSDictionary *customFields, NSDictionary *systemFields, NSArray*errors) { + NSMutableDictionary *profile = [[self.localDataStore generateBaseProfile] mutableCopy]; if (systemFields) { - [self.localDataStore setProfileFields:systemFields]; + [profile addEntriesFromDictionary:systemFields]; } - NSMutableDictionary *profile = [[self.localDataStore generateBaseProfile] mutableCopy]; if (customFields) { - CleverTapLogInternal(self.config.logLevel, @"%@: Constructed custom profile: %@", self, customFields); [profile addEntriesFromDictionary:customFields]; } [self cacheGUIDSforProfile:profile]; @@ -2981,6 +2979,10 @@ - (void)_handleMultiValueProfilePush:(NSDictionary*)customFields updatedMultiVal -(void) updateProfileFieldsLocally: (NSMutableDictionary *) fieldsToPersistLocally{ [self.dispatchQueueManager runSerialAsync:^{ [CTProfileBuilder build:fieldsToPersistLocally completionHandler:^(NSDictionary *customFields, NSDictionary *systemFields, NSArray*errors) { + if (systemFields) { + CleverTapLogInternal(self.config.logLevel, @"%@: Constructed system profile: %@", self, systemFields); + [self.localDataStore setProfileFields:systemFields]; + } if (customFields) { CleverTapLogInternal(self.config.logLevel, @"%@: Constructed custom profile: %@", self, customFields); [self.localDataStore setProfileFields:customFields]; From deb9943b1788d1af8c985e490a33d9ff9cdabd2f Mon Sep 17 00:00:00 2001 From: Kushagra Date: Fri, 21 Jun 2024 16:47:14 +0530 Subject: [PATCH 12/24] Adding Unit test cases for CTUserInfoMigrator file --- CleverTapSDK.xcodeproj/project.pbxproj | 4 + CleverTapSDKTests/CTUserInfoMigratorTest.m | 107 +++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 CleverTapSDKTests/CTUserInfoMigratorTest.m diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index 3e6020f2..215939c8 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -152,6 +152,7 @@ 07D8C08B21DDEC54006F5A1B /* CTCarouselImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = 07D8C08A21DDEC54006F5A1B /* CTCarouselImageView.h */; settings = {ATTRIBUTES = (Private, ); }; }; 07FD65A2223BC26300A845B7 /* CTCoverViewController~iphoneland.xib in Resources */ = {isa = PBXBuildFile; fileRef = 07FD65A1223BC26300A845B7 /* CTCoverViewController~iphoneland.xib */; }; 07FD65A4223BCB8200A845B7 /* CTCoverViewController~ipadland.xib in Resources */ = {isa = PBXBuildFile; fileRef = 07FD65A3223BCB8200A845B7 /* CTCoverViewController~ipadland.xib */; }; + 0B5564562C25946C00B87284 /* CTUserInfoMigratorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B5564552C25946C00B87284 /* CTUserInfoMigratorTest.m */; }; 1F1C18806B7F29B3374F2448 /* libPods-shared-CleverTapSDKTestsApp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E303560B5EE1D154C1E3D9EF /* libPods-shared-CleverTapSDKTestsApp.a */; }; 32394C1F29FA251E00956058 /* CTEventBuilderTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32394C1E29FA251E00956058 /* CTEventBuilderTest.m */; }; 32394C2129FA264B00956058 /* CTPreferencesTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32394C2029FA264B00956058 /* CTPreferencesTest.m */; }; @@ -700,6 +701,7 @@ 07D8C08A21DDEC54006F5A1B /* CTCarouselImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTCarouselImageView.h; sourceTree = ""; }; 07FD65A1223BC26300A845B7 /* CTCoverViewController~iphoneland.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = "CTCoverViewController~iphoneland.xib"; sourceTree = ""; }; 07FD65A3223BCB8200A845B7 /* CTCoverViewController~ipadland.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = "CTCoverViewController~ipadland.xib"; sourceTree = ""; }; + 0B5564552C25946C00B87284 /* CTUserInfoMigratorTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTUserInfoMigratorTest.m; sourceTree = ""; }; 0CA46771B6F202E37DAC9F70 /* Pods-shared-CleverTapSDKTestsApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-shared-CleverTapSDKTestsApp.debug.xcconfig"; path = "Target Support Files/Pods-shared-CleverTapSDKTestsApp/Pods-shared-CleverTapSDKTestsApp.debug.xcconfig"; sourceTree = ""; }; 129AEC403AFA828F591B756E /* Pods-shared-CleverTapSDKTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-shared-CleverTapSDKTests.release.xcconfig"; path = "Target Support Files/Pods-shared-CleverTapSDKTests/Pods-shared-CleverTapSDKTests.release.xcconfig"; sourceTree = ""; }; 32394C1E29FA251E00956058 /* CTEventBuilderTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTEventBuilderTest.m; sourceTree = ""; }; @@ -1405,6 +1407,7 @@ 6A59D20E2A3351A800531F9D /* LeanplumCTTest.m */, 4ECD88302ADC8A05003885CE /* CTSessionManagerTests.m */, 6BD851C82B45CD1800FA5298 /* CTMultiDelegateManager+Tests.h */, + 0B5564552C25946C00B87284 /* CTUserInfoMigratorTest.m */, ); path = CleverTapSDKTests; sourceTree = ""; @@ -2304,6 +2307,7 @@ 48C31A822B1DC5CF00CA2A90 /* CTInAppImagePrefetchManagerTest.m in Sources */, 4ECD88312ADC8A05003885CE /* CTSessionManagerTests.m in Sources */, 6A4427C52AA6515A0098866F /* CTTriggersMatcherTest.m in Sources */, + 0B5564562C25946C00B87284 /* CTUserInfoMigratorTest.m in Sources */, 4E2CF1442AC56D8F00441E8B /* CTEncryptionTests.m in Sources */, 32394C2729FA278C00956058 /* CTUriHelperTest.m in Sources */, 6A59D20D2A334B8500531F9D /* NSDictionaryExtensionsTest.m in Sources */, diff --git a/CleverTapSDKTests/CTUserInfoMigratorTest.m b/CleverTapSDKTests/CTUserInfoMigratorTest.m new file mode 100644 index 00000000..31e0a5cd --- /dev/null +++ b/CleverTapSDKTests/CTUserInfoMigratorTest.m @@ -0,0 +1,107 @@ +// +// CTUserInfoMigratorTests.m +// CleverTapSDKTests +// +// Created by Kushagra Mishra on 21/06/24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import +#import "CTUserInfoMigrator.h" +#import "XCTestCase+XCTestCase_Tests.h" + +@interface CTUserInfoMigratorTest : XCTestCase + +@property (nonatomic, strong) NSFileManager *fileManager; +@property (nonatomic, strong) NSString *libraryPath; + +@end + +@implementation CTUserInfoMigratorTest + + +- (void)setUp { + [super setUp]; + self.fileManager = [NSFileManager defaultManager]; + + // Get the path to the Library directory + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); + self.libraryPath = [paths objectAtIndex:0]; +} + +- (void)tearDown { + // Clean up any files created during the test + NSError *error = nil; + NSArray *contents = [self.fileManager contentsOfDirectoryAtPath:self.libraryPath error:&error]; + for (NSString *file in contents) { + if ([file containsString:@"clevertap-"]) { + NSString *filePath = [self.libraryPath stringByAppendingPathComponent:file]; + [self.fileManager removeItemAtPath:filePath error:&error]; + } + } + [super tearDown]; +} + +- (void)testMigrateUserInfoFileForAccountID_WhenOldFileExists_ShouldCopyToNewLocation { + NSString *acc_id = @"testAccID"; + NSString *device_id = @"testDeviceID"; + + // Create the old plist file + NSString *oldFileName = [NSString stringWithFormat:@"clevertap-%@-userprofile.plist", acc_id]; + NSString *oldFilePath = [self.libraryPath stringByAppendingPathComponent:oldFileName]; + [self.fileManager createFileAtPath:oldFilePath contents:[@"old content" dataUsingEncoding:NSUTF8StringEncoding] attributes:nil]; + + // Call the method to migrate the user info file + [CTUserInfoMigrator migrateUserInfoFileForAccountID:acc_id deviceID:device_id]; + + // Check that the old file has been copied to the new location + NSString *newFileName = [NSString stringWithFormat:@"clevertap-%@-%@-userprofile.plist", acc_id, device_id]; + NSString *newFilePath = [self.libraryPath stringByAppendingPathComponent:newFileName]; + XCTAssertTrue([self.fileManager fileExistsAtPath:newFilePath], @"New plist file should exist"); + + // Check that the old file has been deleted + XCTAssertFalse([self.fileManager fileExistsAtPath:oldFilePath], @"Old plist file should be deleted"); +} + +- (void)testMigrateUserInfoFileForAccountID_WhenNewFileExists_ShouldNotCopyAndDeleteOldFile { + NSString *acc_id = @"testAccID"; + NSString *device_id = @"testDeviceID"; + + // Create both old and new plist files + NSString *oldFileName = [NSString stringWithFormat:@"clevertap-%@-userprofile.plist", acc_id]; + NSString *oldFilePath = [self.libraryPath stringByAppendingPathComponent:oldFileName]; + [self.fileManager createFileAtPath:oldFilePath contents:[@"old content" dataUsingEncoding:NSUTF8StringEncoding] attributes:nil]; + + NSString *newFileName = [NSString stringWithFormat:@"clevertap-%@-%@-userprofile.plist", acc_id, device_id]; + NSString *newFilePath = [self.libraryPath stringByAppendingPathComponent:newFileName]; + [self.fileManager createFileAtPath:newFilePath contents:[@"new content" dataUsingEncoding:NSUTF8StringEncoding] attributes:nil]; + + // Call the method to migrate the user info file + [CTUserInfoMigrator migrateUserInfoFileForAccountID:acc_id deviceID:device_id]; + + // Check that the new file still exists + XCTAssertTrue([self.fileManager fileExistsAtPath:newFilePath], @"New plist file should exist"); + + // Check that the old file has been deleted + XCTAssertFalse([self.fileManager fileExistsAtPath:oldFilePath], @"Old plist file should be deleted"); +} + +- (void)testMigrateUserInfoFileForAccountID_WhenOldFileDoesNotExist_ShouldNotCreateNewFile { + NSString *acc_id = @"testAccID"; + NSString *device_id = @"testDeviceID"; + + // Ensure the old plist file does not exist + NSString *oldFileName = [NSString stringWithFormat:@"clevertap-%@-userprofile.plist", acc_id]; + NSString *oldFilePath = [self.libraryPath stringByAppendingPathComponent:oldFileName]; + [self.fileManager removeItemAtPath:oldFilePath error:nil]; + + // Call the method to migrate the user info file + [CTUserInfoMigrator migrateUserInfoFileForAccountID:acc_id deviceID:device_id]; + + // Check that the new file does not exist + NSString *newFileName = [NSString stringWithFormat:@"clevertap-%@-%@-userprofile.plist", acc_id, device_id]; + NSString *newFilePath = [self.libraryPath stringByAppendingPathComponent:newFileName]; + XCTAssertFalse([self.fileManager fileExistsAtPath:newFilePath], @"New plist file should not be created"); +} + +@end From 5646ee65a1706dab143c82c2104cdc076a720087 Mon Sep 17 00:00:00 2001 From: Kushagra Date: Wed, 26 Jun 2024 15:14:38 +0530 Subject: [PATCH 13/24] Code Cleaning and refactoring --- CleverTapSDK/CTConstants.h | 2 +- CleverTapSDK/CTLocalDataStore.m | 1 - CleverTapSDK/CTUserInfoMigrator.m | 42 +++------ CleverTapSDK/CleverTap.m | 86 +++++++++---------- .../InApps/CTInAppEvaluationManager.m | 5 +- CleverTapSDK/InApps/Matchers/CTEventAdapter.h | 2 +- CleverTapSDK/InApps/Matchers/CTEventAdapter.m | 6 +- 7 files changed, 63 insertions(+), 81 deletions(-) diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index c9b69052..6f71fe40 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -257,7 +257,7 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY; #pragma mark Constants for Profile identifier keys #define CLTAP_PROFILE_IDENTIFIER_KEYS @[@"Identity", @"Email"] // LEGACY KEYS #define CLTAP_ALL_PROFILE_IDENTIFIER_KEYS @[@"Identity", @"Email", @"Phone"] -#define CLTAP_KeysToSkipForUserAttributesEvaluation @[@"cc", @"tz", @"Carrier"] +#define CLTAP_SKIP_KEYS_USER_ATTRIBUTE_EVALUATION @[@"cc", @"tz", @"Carrier"] #pragma mark Constants for Encryption #define CLTAP_ENCRYPTION_LEVEL @"CleverTapEncryptionLevel" diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index e11aedf9..4d0b5b2d 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -98,7 +98,6 @@ - (void)changeUser { self->localProfileForSession = [self _inflateLocalProfile]; } }]; -// [self _persistLocalProfileAsyncWithCompletion:nil]; [self clearStoredEvents]; } diff --git a/CleverTapSDK/CTUserInfoMigrator.m b/CleverTapSDK/CTUserInfoMigrator.m index 88ef8e45..c291ab5a 100644 --- a/CleverTapSDK/CTUserInfoMigrator.m +++ b/CleverTapSDK/CTUserInfoMigrator.m @@ -1,46 +1,30 @@ -// -// CTUserInfoMigrator.m -// -// Created by Kushagra Mishra on 29/05/24. -// - #import "CTUserInfoMigrator.h" +#import "CTConstants.h" @implementation CTUserInfoMigrator + (void)migrateUserInfoFileForAccountID:(NSString *)acc_id deviceID:(NSString *)device_id { NSFileManager *fileManager = [NSFileManager defaultManager]; - - // Get the path to the Documents directory NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); NSString *libraryPath = [paths objectAtIndex:0]; - - // Construct the old plist file name and path - NSString *userProfileWithDeviceID = [NSString stringWithFormat:@"clevertap-%@-userprofile.plist", acc_id]; - NSString *userProfileWithDeviceIDPath = [libraryPath stringByAppendingPathComponent:userProfileWithDeviceID]; - - // Construct the new plist file name and path - NSString *userProfileWithDeviceIDAndGUID = [NSString stringWithFormat:@"clevertap-%@-%@-userprofile.plist", acc_id, device_id]; - NSString *userProfileWithDeviceIDAndGUIDPath = [libraryPath stringByAppendingPathComponent:userProfileWithDeviceIDAndGUID]; + NSString *userProfileWithAccountID = [NSString stringWithFormat:@"clevertap-%@-userprofile.plist", acc_id]; + NSString *userProfileWithAccountIDPath = [libraryPath stringByAppendingPathComponent:userProfileWithAccountID]; + NSString *userProfileWithAccountIDAndDeviceID = [NSString stringWithFormat:@"clevertap-%@-%@-userprofile.plist", acc_id, device_id]; + NSString *userProfileWithAccountIDAndDeviceIDPath = [libraryPath stringByAppendingPathComponent:userProfileWithAccountIDAndDeviceID]; NSError *error = nil; - if ([fileManager fileExistsAtPath:userProfileWithDeviceIDAndGUIDPath]) { - NSLog(@"[CTUserInfo]: new Plist file exists %@", userProfileWithDeviceIDAndGUIDPath); - if ([fileManager fileExistsAtPath:userProfileWithDeviceIDPath]) { - [fileManager removeItemAtPath:userProfileWithDeviceIDPath error:&error]; + if ([fileManager fileExistsAtPath:userProfileWithAccountIDAndDeviceIDPath]) { + CleverTapLogStaticInternal(@"[CTUserInfo]: The local updated file exists %@", userProfileWithAccountIDAndDeviceIDPath); + if ([fileManager fileExistsAtPath:userProfileWithAccountIDPath]) { + [fileManager removeItemAtPath:userProfileWithAccountIDPath error:&error]; return; } } - - // Copy the plist file to the new location with the new name - else if ([fileManager copyItemAtPath:userProfileWithDeviceIDPath toPath:userProfileWithDeviceIDAndGUIDPath error:&error]) { - NSLog(@"[CTUserInfo]: Plist file copied successfully to %@", userProfileWithDeviceIDAndGUIDPath); - [fileManager removeItemAtPath:userProfileWithDeviceIDPath error:&error]; + else if ([fileManager copyItemAtPath:userProfileWithAccountIDPath toPath:userProfileWithAccountIDAndDeviceIDPath error:&error]) { + CleverTapLogStaticInternal(@"[CTUserInfo]: Local file copied successfully to %@", userProfileWithAccountIDAndDeviceIDPath); + [fileManager removeItemAtPath:userProfileWithAccountIDPath error:&error]; } else { - NSLog(@"[CTUserInfo]: Failed to copy plist file: %@", error.localizedDescription); + CleverTapLogStaticInternal(@"[CTUserInfo]: Failed to copy local file: %@", error.localizedDescription); } } - - - @end diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 8f3e4f12..1e704ac5 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -2925,53 +2925,53 @@ - (void)_handleMultiValueProfilePush:(NSDictionary*)customFields updatedMultiVal } - (NSDictionary *> *)getUserAttributeChangeProperties:(NSDictionary *)event { - NSMutableDictionary *> *userAttributesChangeProperties = [NSMutableDictionary dictionary]; - NSMutableDictionary *fieldsToPersistLocally = [NSMutableDictionary dictionary]; - NSDictionary *profile = event[CLTAP_PROFILE]; - if (!profile) { - return @{}; + NSMutableDictionary *> *userAttributesChangeProperties = [NSMutableDictionary dictionary]; + NSMutableDictionary *fieldsToPersistLocally = [NSMutableDictionary dictionary]; + NSDictionary *profile = event[CLTAP_PROFILE]; + if (!profile) { + return @{}; + } + for (NSString *key in profile) { + if ([CLTAP_SKIP_KEYS_USER_ATTRIBUTE_EVALUATION containsObject: key]) { + continue; } - for (NSString *key in profile) { - if ([CLTAP_KeysToSkipForUserAttributesEvaluation containsObject: key]) { - continue; + id oldValue = [self profileGetLocalValues:key]; + id newValue = profile[key]; + NSMutableDictionary *properties = [NSMutableDictionary dictionary]; + if ([newValue isKindOfClass:[NSDictionary class]]) { + NSDictionary *obj = (NSDictionary *)newValue; + NSString *commandIdentifier = [[obj allKeys] firstObject]; + id value = [obj objectForKey:commandIdentifier]; + if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_INCREMENT] || + [commandIdentifier isEqualToString:kCLTAP_COMMAND_DECREMENT]) { + newValue = [CTProfileBuilder _getUpdatedValue:value forKey:key withCommand:commandIdentifier cachedValue:oldValue]; + } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_DELETE]) { + newValue = nil; + [self.localDataStore removeProfileFieldForKey:key]; } - id oldValue = [self profileGetLocalValues:key]; - id newValue = profile[key]; - NSMutableDictionary *properties = [NSMutableDictionary dictionary]; - if ([newValue isKindOfClass:[NSDictionary class]]) { - NSDictionary *obj = (NSDictionary *)newValue; - NSString *commandIdentifier = [[obj allKeys] firstObject]; - id value = [obj objectForKey:commandIdentifier]; - if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_INCREMENT] || - [commandIdentifier isEqualToString:kCLTAP_COMMAND_DECREMENT]) { - newValue = [CTProfileBuilder _getUpdatedValue:value forKey:key withCommand:commandIdentifier cachedValue:oldValue]; - } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_DELETE]) { - newValue = nil; - [self.localDataStore removeProfileFieldForKey:key]; - } - } else if ([newValue isKindOfClass:[NSString class]]) { - // Remove the date prefix before evaluation and persisting - NSString *newValueStr = (NSString *)newValue; - if ([newValueStr hasPrefix:CLTAP_DATE_PREFIX]) { - newValue = @([[newValueStr substringFromIndex:[CLTAP_DATE_PREFIX length]] longLongValue]); - } - } - if (oldValue != nil && ![oldValue isKindOfClass:[NSArray class]]) { - [properties setObject:oldValue forKey:CLTAP_KEY_OLD_VALUE]; - } - if (newValue != nil && ![newValue isKindOfClass:[NSArray class]]) { - [properties setObject:newValue forKey:CLTAP_KEY_NEW_VALUE]; - } - - // Skip evaluation if both newValue or oldValue are null - if ([properties count] > 0) { - [userAttributesChangeProperties setObject:properties forKey:key]; - } - // Need to persist only if the new profile value is not a null value - if (newValue != nil && newValue != oldValue) { - [fieldsToPersistLocally setObject:newValue forKey:key]; + } else if ([newValue isKindOfClass:[NSString class]]) { + // Remove the date prefix before evaluation and persisting + NSString *newValueStr = (NSString *)newValue; + if ([newValueStr hasPrefix:CLTAP_DATE_PREFIX]) { + newValue = @([[newValueStr substringFromIndex:[CLTAP_DATE_PREFIX length]] longLongValue]); } } + if (oldValue != nil && ![oldValue isKindOfClass:[NSArray class]]) { + [properties setObject:oldValue forKey:CLTAP_KEY_OLD_VALUE]; + } + if (newValue != nil && ![newValue isKindOfClass:[NSArray class]]) { + [properties setObject:newValue forKey:CLTAP_KEY_NEW_VALUE]; + } + + // Skip evaluation if both newValue or oldValue are null + if ([properties count] > 0) { + [userAttributesChangeProperties setObject:properties forKey:key]; + } + // Need to persist only if the new profile value is not a null value + if (newValue != nil && newValue != oldValue) { + [fieldsToPersistLocally setObject:newValue forKey:key]; + } + } [self updateProfileFieldsLocally:fieldsToPersistLocally]; return userAttributesChangeProperties; } diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.m b/CleverTapSDK/InApps/CTInAppEvaluationManager.m index 99557cb7..ed24d161 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.m +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.m @@ -97,10 +97,9 @@ -(void)evaluateOnUserAttributeChange:(NSDictionary * NSMutableArray *eventAdapterList = [NSMutableArray array]; [profile enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) { NSString *eventName = [key stringByAppendingString:CLTAP_USER_ATTRIBUTE_CHANGE]; + NSMutableDictionary *eventProperties = [NSMutableDictionary dictionaryWithDictionary:value]; + [eventProperties addEntriesFromDictionary:appFields]; CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:eventName profileAttrName:key eventProperties: value andLocation:self.location]; - NSMutableDictionary *mergedProperties = [event.eventProperties mutableCopy]; - [mergedProperties addEntriesFromDictionary:appFields]; - event.eventProperties = [mergedProperties copy]; [eventAdapterList addObject:event]; }]; diff --git a/CleverTapSDK/InApps/Matchers/CTEventAdapter.h b/CleverTapSDK/InApps/Matchers/CTEventAdapter.h index a71ad180..7bc79c75 100644 --- a/CleverTapSDK/InApps/Matchers/CTEventAdapter.h +++ b/CleverTapSDK/InApps/Matchers/CTEventAdapter.h @@ -17,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, readonly) NSString *eventName; @property (nonatomic, assign, readonly) CLLocationCoordinate2D location; @property (nonatomic, strong, readonly) NSString *profileAttrName; -@property (nonatomic, strong, nonnull) NSMutableDictionary *eventProperties; +@property (nonatomic, strong, nonnull) NSDictionary *eventProperties; - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithEventName:(NSString *)eventName diff --git a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m index e0804728..53ee18fa 100644 --- a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m +++ b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m @@ -53,7 +53,7 @@ + (void)initialize { } - (instancetype)initWithEventName:(NSString *)eventName - eventProperties:(NSMutableDictionary *)eventProperties + eventProperties:(NSDictionary *)eventProperties andLocation:(CLLocationCoordinate2D)location{ if (self = [super init]) { self = [self initWithEventName:eventName eventProperties:eventProperties location:location andItems:@[]]; @@ -62,7 +62,7 @@ - (instancetype)initWithEventName:(NSString *)eventName } - (instancetype)initWithEventName:(NSString *)eventName - eventProperties:(NSMutableDictionary *)eventProperties + eventProperties:(NSDictionary *)eventProperties location:(CLLocationCoordinate2D)location andItems:(NSArray *)items { if (self = [super init]) { @@ -76,7 +76,7 @@ - (instancetype)initWithEventName:(NSString *)eventName - (instancetype)initWithEventName:(NSString *)eventName profileAttrName:(NSString *)profileAttrName - eventProperties:(NSMutableDictionary *)eventProperties + eventProperties:(NSDictionary *)eventProperties andLocation:(CLLocationCoordinate2D)location{ if (self = [super init]) { From eae277d5a4a0127fdef54aad9bbe5362e3bbcfab Mon Sep 17 00:00:00 2001 From: kushCT Date: Thu, 27 Jun 2024 14:28:27 +0530 Subject: [PATCH 14/24] Added support for Profile type Events to be included for server side inapps --- CleverTapSDK/CTConstants.h | 2 +- CleverTapSDK/InApps/CTInAppEvaluationManager.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index 6f71fe40..015804c1 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -51,7 +51,7 @@ extern NSString *const kSessionId; #define CLTAP_APP_LAUNCHED_EVENT @"App Launched" #define CLTAP_CHARGED_EVENT @"Charged" #define CLTAP_PROFILE @"profile" -#define CLTAP_USER_ATTRIBUTE_CHANGE @"_CTUserAttributeChange" +#define CLTAP_USER_ATTRIBUTE_CHANGE @"_change" #define CLTAP_KEY_NEW_VALUE @"newValue" #define CLTAP_KEY_OLD_VALUE @"oldValue" #define CLTAP_KEY_PROFILE_ATTR_NAME @"profileAttrName" diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.m b/CleverTapSDK/InApps/CTInAppEvaluationManager.m index ed24d161..dde3c08a 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.m +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.m @@ -327,7 +327,7 @@ - (void)updateTTL:(NSMutableDictionary *)inApp { - (BatchHeaderKeyPathValues)onBatchHeaderCreationForQueue:(CTQueueType)queueType { // Evaluation is done for events only at the moment, // send the evaluated and suppressed ids in that queue header - if (queueType != CTQueueTypeEvents) { + if (queueType != CTQueueTypeEvents && queueType != CTQueueTypeProfile) { return [NSMutableDictionary new]; } From 45f5842d1f06168c63834a811b8a8c754d731fe5 Mon Sep 17 00:00:00 2001 From: kushCT Date: Tue, 2 Jul 2024 16:55:28 +0530 Subject: [PATCH 15/24] Refractored some files for code cleaning --- CleverTapSDK/CTLocalDataStore.h | 2 + CleverTapSDK/CTLocalDataStore.m | 71 +++++++++++++++++++ CleverTapSDK/CTProfileBuilder.m | 65 ++--------------- CleverTapSDK/CTUserInfoMigrator.m | 11 +-- CleverTapSDK/CleverTap.m | 69 +----------------- .../InApps/CTInAppEvaluationManager.m | 5 +- CleverTapSDK/InApps/Matchers/CTEventAdapter.m | 4 +- .../InApps/Matchers/CTTriggersMatcher.m | 5 +- 8 files changed, 92 insertions(+), 140 deletions(-) diff --git a/CleverTapSDK/CTLocalDataStore.h b/CleverTapSDK/CTLocalDataStore.h index 7dce5431..5761b0a7 100644 --- a/CleverTapSDK/CTLocalDataStore.h +++ b/CleverTapSDK/CTLocalDataStore.h @@ -34,6 +34,8 @@ - (id)getProfileFieldForKey:(NSString *)key; +- (NSDictionary *> *)getUserAttributeChangeProperties:(NSDictionary *)event; + - (void)persistLocalProfileIfRequired; - (NSDictionary*)generateBaseProfile; diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 4d0b5b2d..5df8d303 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -12,6 +12,9 @@ #import "CTUtils.h" #import "CTUIUtils.h" #import "CTUserInfoMigrator.h" +#import "CTDispatchQueueManager.h" +#import "CTMultiDelegateManager.h" +#import "CTProfileBuilder.h" static const void *const kProfileBackgroundQueueKey = &kProfileBackgroundQueueKey; static const double kProfilePersistenceIntervalSeconds = 30.f; @@ -30,6 +33,7 @@ @interface CTLocalDataStore() { @property (nonatomic, strong) CleverTapInstanceConfig *config; @property (nonatomic, strong) CTDeviceInfo *deviceInfo; @property (nonatomic, strong) NSArray *piiKeys; +@property (nonatomic, strong) CTDispatchQueueManager *dispatchQueueManager; @end @@ -518,6 +522,73 @@ - (id)getProfileFieldForKey:(NSString *)key { } } +- (NSDictionary *> *)getUserAttributeChangeProperties:(NSDictionary *)event { + NSMutableDictionary *> *userAttributesChangeProperties = [NSMutableDictionary dictionary]; + NSMutableDictionary *fieldsToPersistLocally = [NSMutableDictionary dictionary]; + NSDictionary *profile = event[CLTAP_PROFILE]; + if (!profile) { + return @{}; + } + for (NSString *key in profile) { + if ([CLTAP_SKIP_KEYS_USER_ATTRIBUTE_EVALUATION containsObject: key]) { + continue; + } + id oldValue = [self getProfileFieldForKey:key]; + id newValue = profile[key]; + NSMutableDictionary *properties = [NSMutableDictionary dictionary]; + if ([newValue isKindOfClass:[NSDictionary class]]) { + NSDictionary *obj = (NSDictionary *)newValue; + NSString *commandIdentifier = [[obj allKeys] firstObject]; + id value = [obj objectForKey:commandIdentifier]; + if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_INCREMENT] || + [commandIdentifier isEqualToString:kCLTAP_COMMAND_DECREMENT]) { + newValue = [CTProfileBuilder _getUpdatedValue:value forKey:key withCommand:commandIdentifier cachedValue:oldValue]; + } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_DELETE]) { + newValue = nil; + [self removeProfileFieldForKey:key]; + } + } else if ([newValue isKindOfClass:[NSString class]]) { + // Remove the date prefix before evaluation and persisting + NSString *newValueStr = (NSString *)newValue; + if ([newValueStr hasPrefix:CLTAP_DATE_PREFIX]) { + newValue = @([[newValueStr substringFromIndex:[CLTAP_DATE_PREFIX length]] longLongValue]); + } + } + if (oldValue != nil && ![oldValue isKindOfClass:[NSArray class]]) { + [properties setObject:oldValue forKey:CLTAP_KEY_OLD_VALUE]; + } + if (newValue != nil && ![newValue isKindOfClass:[NSArray class]]) { + [properties setObject:newValue forKey:CLTAP_KEY_NEW_VALUE]; + } + + // Skip evaluation if both newValue or oldValue are null + if ([properties count] > 0) { + [userAttributesChangeProperties setObject:properties forKey:key]; + } + // Need to persist only if the new profile value is not a null value + if (newValue != nil && newValue != oldValue) { + [fieldsToPersistLocally setObject:newValue forKey:key]; + } + } + [self updateProfileFieldsLocally:fieldsToPersistLocally]; + return userAttributesChangeProperties; +} + +-(void) updateProfileFieldsLocally: (NSMutableDictionary *) fieldsToPersistLocally{ + [self.dispatchQueueManager runSerialAsync:^{ + [CTProfileBuilder build:fieldsToPersistLocally completionHandler:^(NSDictionary *customFields, NSDictionary *systemFields, NSArray*errors) { + if (systemFields) { + CleverTapLogInternal(self.config.logLevel, @"%@: Constructed system profile: %@", self, systemFields); + [self setProfileFields:systemFields]; + } + if (customFields) { + CleverTapLogInternal(self.config.logLevel, @"%@: Constructed custom profile: %@", self, customFields); + [self setProfileFields:customFields]; + } + }]; + }]; +} + - (void)setProfileFields:(NSDictionary *)fields { [self setProfileFields:fields fromUpstream:NO]; } diff --git a/CleverTapSDK/CTProfileBuilder.m b/CleverTapSDK/CTProfileBuilder.m index 3f13802a..49d2a706 100644 --- a/CleverTapSDK/CTProfileBuilder.m +++ b/CleverTapSDK/CTProfileBuilder.m @@ -382,71 +382,18 @@ + (void)_handleIncrementDecrementValue:(NSNumber *_Nonnull)value forKey:(NSStrin NSDictionary* operatorDict = @{ key: @{command: value} }; - NSNumber *newValue; - id cachedValue = [dataStore getProfileFieldForKey: key]; - if ([cachedValue isKindOfClass: [NSNumber class]]) { - - NSNumber *cachedNumber = (NSNumber*)cachedValue; - CFNumberType numberType = CFNumberGetType((CFNumberRef)cachedNumber); - - switch (numberType) { - case kCFNumberSInt8Type: - case kCFNumberSInt16Type: - case kCFNumberIntType: - case kCFNumberSInt32Type: - case kCFNumberSInt64Type: - case kCFNumberNSIntegerType: - case kCFNumberShortType: - if ([command isEqualToString: kCLTAP_COMMAND_INCREMENT]) { - newValue = [NSNumber numberWithInt: cachedNumber.intValue + value.intValue]; - } else { - newValue = [NSNumber numberWithInt: cachedNumber.intValue - value.intValue]; - } - break; - case kCFNumberLongType: - if ([command isEqualToString: kCLTAP_COMMAND_INCREMENT]) { - newValue = [NSNumber numberWithLong: cachedNumber.longValue + value.longValue]; - } else { - newValue = [NSNumber numberWithLong: cachedNumber.longValue - value.longValue]; - } - break; - case kCFNumberLongLongType: - if ([command isEqualToString: kCLTAP_COMMAND_INCREMENT]) { - newValue = [NSNumber numberWithLongLong: cachedNumber.longLongValue + value.longLongValue]; - } else { - newValue = [NSNumber numberWithLongLong: cachedNumber.longLongValue - value.longLongValue]; - } - break; - case kCFNumberFloatType: - case kCFNumberFloat32Type: - case kCFNumberFloat64Type: - case kCFNumberCGFloatType: - if ([command isEqualToString: kCLTAP_COMMAND_INCREMENT]) { - newValue = [NSNumber numberWithFloat: cachedNumber.floatValue + value.floatValue]; - } else { - newValue = [NSNumber numberWithFloat: cachedNumber.floatValue - value.floatValue]; - } - break; - case kCFNumberDoubleType: - if ([command isEqualToString: kCLTAP_COMMAND_INCREMENT]) { - newValue = [NSNumber numberWithDouble: cachedNumber.doubleValue + value.doubleValue]; - } else { - newValue = [NSNumber numberWithDouble: cachedNumber.doubleValue - value.doubleValue]; - } - break; - default: - break; - } - } + NSNumber *newValue; + newValue = [self _getUpdatedValue:value forKey:key withCommand:command cachedValue:cachedValue]; completion(operatorDict, newValue, nil); } + (NSNumber *_Nullable)_getUpdatedValue:(NSNumber *_Nonnull)value forKey:(NSString *_Nonnull)key withCommand:(NSString *_Nonnull)command cachedValue:(id)cachedValue { - + NSDictionary* operatorDict = @{ + key: @{command: value} + }; NSNumber *newValue; - if ([cachedValue isKindOfClass: [NSNumber class]]) { NSNumber *cachedNumber = (NSNumber*)cachedValue; @@ -502,7 +449,7 @@ + (NSNumber *_Nullable)_getUpdatedValue:(NSNumber *_Nonnull)value forKey:(NSStri } } return newValue; + } - @end diff --git a/CleverTapSDK/CTUserInfoMigrator.m b/CleverTapSDK/CTUserInfoMigrator.m index c291ab5a..1cb682a4 100644 --- a/CleverTapSDK/CTUserInfoMigrator.m +++ b/CleverTapSDK/CTUserInfoMigrator.m @@ -13,16 +13,11 @@ + (void)migrateUserInfoFileForAccountID:(NSString *)acc_id deviceID:(NSString *) NSString *userProfileWithAccountIDAndDeviceIDPath = [libraryPath stringByAppendingPathComponent:userProfileWithAccountIDAndDeviceID]; NSError *error = nil; - if ([fileManager fileExistsAtPath:userProfileWithAccountIDAndDeviceIDPath]) { - CleverTapLogStaticInternal(@"[CTUserInfo]: The local updated file exists %@", userProfileWithAccountIDAndDeviceIDPath); - if ([fileManager fileExistsAtPath:userProfileWithAccountIDPath]) { - [fileManager removeItemAtPath:userProfileWithAccountIDPath error:&error]; - return; - } - } - else if ([fileManager copyItemAtPath:userProfileWithAccountIDPath toPath:userProfileWithAccountIDAndDeviceIDPath error:&error]) { + if ([fileManager fileExistsAtPath:userProfileWithAccountIDPath]) { + [fileManager copyItemAtPath:userProfileWithAccountIDPath toPath:userProfileWithAccountIDAndDeviceIDPath error:&error]; CleverTapLogStaticInternal(@"[CTUserInfo]: Local file copied successfully to %@", userProfileWithAccountIDAndDeviceIDPath); [fileManager removeItemAtPath:userProfileWithAccountIDPath error:&error]; + return; } else { CleverTapLogStaticInternal(@"[CTUserInfo]: Failed to copy local file: %@", error.localizedDescription); } diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 1e704ac5..80b903dd 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -1971,7 +1971,7 @@ - (void)evaluateOnEvent:(NSDictionary *)event withType:(CleverTapEventType)event NSArray *items = eventData[CLTAP_CHARGED_EVENT_ITEMS]; [self.inAppEvaluationManager evaluateOnChargedEvent:eventData andItems:items]; } else if (eventType == CleverTapEventTypeProfile) { - NSDictionary *> *result = [self getUserAttributeChangeProperties:event]; + NSDictionary *> *result = [self.localDataStore getUserAttributeChangeProperties:event]; [self.inAppEvaluationManager evaluateOnUserAttributeChange:result]; } else if (eventName) { [self.inAppEvaluationManager evaluateOnEvent:eventName withProps:eventData]; @@ -2924,73 +2924,6 @@ - (void)_handleMultiValueProfilePush:(NSDictionary*)customFields updatedMultiVal } } -- (NSDictionary *> *)getUserAttributeChangeProperties:(NSDictionary *)event { - NSMutableDictionary *> *userAttributesChangeProperties = [NSMutableDictionary dictionary]; - NSMutableDictionary *fieldsToPersistLocally = [NSMutableDictionary dictionary]; - NSDictionary *profile = event[CLTAP_PROFILE]; - if (!profile) { - return @{}; - } - for (NSString *key in profile) { - if ([CLTAP_SKIP_KEYS_USER_ATTRIBUTE_EVALUATION containsObject: key]) { - continue; - } - id oldValue = [self profileGetLocalValues:key]; - id newValue = profile[key]; - NSMutableDictionary *properties = [NSMutableDictionary dictionary]; - if ([newValue isKindOfClass:[NSDictionary class]]) { - NSDictionary *obj = (NSDictionary *)newValue; - NSString *commandIdentifier = [[obj allKeys] firstObject]; - id value = [obj objectForKey:commandIdentifier]; - if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_INCREMENT] || - [commandIdentifier isEqualToString:kCLTAP_COMMAND_DECREMENT]) { - newValue = [CTProfileBuilder _getUpdatedValue:value forKey:key withCommand:commandIdentifier cachedValue:oldValue]; - } else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_DELETE]) { - newValue = nil; - [self.localDataStore removeProfileFieldForKey:key]; - } - } else if ([newValue isKindOfClass:[NSString class]]) { - // Remove the date prefix before evaluation and persisting - NSString *newValueStr = (NSString *)newValue; - if ([newValueStr hasPrefix:CLTAP_DATE_PREFIX]) { - newValue = @([[newValueStr substringFromIndex:[CLTAP_DATE_PREFIX length]] longLongValue]); - } - } - if (oldValue != nil && ![oldValue isKindOfClass:[NSArray class]]) { - [properties setObject:oldValue forKey:CLTAP_KEY_OLD_VALUE]; - } - if (newValue != nil && ![newValue isKindOfClass:[NSArray class]]) { - [properties setObject:newValue forKey:CLTAP_KEY_NEW_VALUE]; - } - - // Skip evaluation if both newValue or oldValue are null - if ([properties count] > 0) { - [userAttributesChangeProperties setObject:properties forKey:key]; - } - // Need to persist only if the new profile value is not a null value - if (newValue != nil && newValue != oldValue) { - [fieldsToPersistLocally setObject:newValue forKey:key]; - } - } - [self updateProfileFieldsLocally:fieldsToPersistLocally]; - return userAttributesChangeProperties; -} - --(void) updateProfileFieldsLocally: (NSMutableDictionary *) fieldsToPersistLocally{ - [self.dispatchQueueManager runSerialAsync:^{ - [CTProfileBuilder build:fieldsToPersistLocally completionHandler:^(NSDictionary *customFields, NSDictionary *systemFields, NSArray*errors) { - if (systemFields) { - CleverTapLogInternal(self.config.logLevel, @"%@: Constructed system profile: %@", self, systemFields); - [self.localDataStore setProfileFields:systemFields]; - } - if (customFields) { - CleverTapLogInternal(self.config.logLevel, @"%@: Constructed custom profile: %@", self, customFields); - [self.localDataStore setProfileFields:customFields]; - } - }]; - }]; -} - #pragma mark - User Action Events API - (void)recordEvent:(NSString *)event { diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.m b/CleverTapSDK/InApps/CTInAppEvaluationManager.m index dde3c08a..acf48a31 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.m +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.m @@ -140,7 +140,10 @@ - (void)evaluateClientSide:(NSArray *)events { for (CTEventAdapter *event in events) { id oldValue = [event.eventProperties objectForKey:CLTAP_KEY_OLD_VALUE]; id newValue = [event.eventProperties objectForKey:CLTAP_KEY_NEW_VALUE]; - if (newValue == nil || newValue != oldValue){ + if (event.profileAttrName != nil) { + if (newValue == oldValue) { + continue; + } [eligibleInApps addObjectsFromArray:[self evaluate:event withInApps:self.inAppStore.clientSideInApps]]; } } diff --git a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m index 53ee18fa..301f551f 100644 --- a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m +++ b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m @@ -80,10 +80,8 @@ - (instancetype)initWithEventName:(NSString *)eventName andLocation:(CLLocationCoordinate2D)location{ if (self = [super init]) { - self.eventName = eventName; + self = [self initWithEventName:eventName eventProperties:eventProperties location:location andItems:@[]]; self.profileAttrName = profileAttrName; - self.eventProperties = eventProperties; - self.location = location; } return self; } diff --git a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m index 6cd73251..b6366b13 100644 --- a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m +++ b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m @@ -30,7 +30,10 @@ - (BOOL)matchEventWhenTriggers:(NSArray *)whenTriggers event:(CTEventAdapter *)e } - (BOOL)match:(CTTriggerAdapter *)trigger event:(CTEventAdapter *)event { - if (![[event eventName] isEqualToString:[trigger eventName]] && ([event profileAttrName] == nil || ![[event profileAttrName] isEqualToString:[trigger profileAttrName]])) { + + BOOL eventNameMatch = [[event eventName] isEqualToString:[trigger eventName]]; + BOOL profileAttrNameMatch = [event profileAttrName] != nil && [[event profileAttrName] isEqualToString:[trigger profileAttrName]]; + if (!eventNameMatch && !profileAttrNameMatch) { return NO; } From 6628d2f9da052fbbda9ce93138f4808f717d4101 Mon Sep 17 00:00:00 2001 From: kushCT Date: Tue, 2 Jul 2024 18:11:00 +0530 Subject: [PATCH 16/24] Moved the getUserAttribute and updating profile fields to LocalDataStoreFile --- CleverTapSDK/CTLocalDataStore.h | 4 +++- CleverTapSDK/CTLocalDataStore.m | 3 ++- CleverTapSDK/CleverTap.m | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CleverTapSDK/CTLocalDataStore.h b/CleverTapSDK/CTLocalDataStore.h index 5761b0a7..2bbd9c02 100644 --- a/CleverTapSDK/CTLocalDataStore.h +++ b/CleverTapSDK/CTLocalDataStore.h @@ -1,12 +1,14 @@ #import #import "CTDeviceInfo.h" +#import "CTDispatchQueueManager.h" @class CleverTapInstanceConfig; @class CleverTapEventDetail; @interface CTLocalDataStore : NSObject -- (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:(NSDictionary*)profileValues andDeviceInfo:(CTDeviceInfo*)deviceInfo; + +- (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:(NSDictionary*)profileValues andDeviceInfo:(CTDeviceInfo*)deviceInfo dispatchQueueManager:(CTDispatchQueueManager*)dispatchQueueManager; - (void)persistEvent:(NSDictionary *)event; diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 5df8d303..ffc2d16f 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -39,10 +39,11 @@ @interface CTLocalDataStore() { @implementation CTLocalDataStore -- (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:(NSDictionary*)profileValues andDeviceInfo:(CTDeviceInfo*)deviceInfo { +- (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:(NSDictionary*)profileValues andDeviceInfo:(CTDeviceInfo*)deviceInfo dispatchQueueManager:(CTDispatchQueueManager*)dispatchQueueManager { if (self = [super init]) { _config = config; _deviceInfo = deviceInfo; + self.dispatchQueueManager = dispatchQueueManager; localProfileUpdateExpiryStore = [NSMutableDictionary new]; _backgroundQueue = dispatch_queue_create([[NSString stringWithFormat:@"com.clevertap.profileBackgroundQueue:%@", _config.accountId] UTF8String], DISPATCH_QUEUE_SERIAL); dispatch_queue_set_specific(_backgroundQueue, kProfileBackgroundQueueKey, (__bridge void *)self, NULL); diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 80b903dd..8fdc64fd 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -460,11 +460,12 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig*)config andCleverTapID:( if (_deviceInfo.timeZone&& ![_deviceInfo.timeZone isEqualToString:@""]) { initialProfileValues[CLTAP_SYS_TZ] = _deviceInfo.timeZone; } - _localDataStore = [[CTLocalDataStore alloc] initWithConfig:_config profileValues:initialProfileValues andDeviceInfo: _deviceInfo]; self.dispatchQueueManager = [[CTDispatchQueueManager alloc]initWithConfig:_config]; self.delegateManager = [[CTMultiDelegateManager alloc] init]; + _localDataStore = [[CTLocalDataStore alloc] initWithConfig:_config profileValues:initialProfileValues andDeviceInfo: _deviceInfo dispatchQueueManager:_dispatchQueueManager]; + _lastAppLaunchedTime = [self eventGetLastTime:@"App Launched"]; self.validationResultStack = [[CTValidationResultStack alloc]initWithConfig: _config]; self.userSetLocation = kCLLocationCoordinate2DInvalid; From 646288150c6cc076f924e5d652e30926e610ed7e Mon Sep 17 00:00:00 2001 From: kushCT Date: Thu, 4 Jul 2024 14:03:06 +0530 Subject: [PATCH 17/24] Added unit testing for evaluate on User attributes method --- .../InApps/CTInAppEvaluationManagerTest.m | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m b/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m index cea7a903..c588159a 100644 --- a/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m +++ b/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m @@ -299,6 +299,43 @@ - (void)testEvaluateWithInApps { XCTAssertEqual([self.evaluationManager.triggerManager getTriggers:@"4"], 1); } +- (void)testEvaluateUserAttribute { + + self.helper.inAppStore.serverSideInApps = @[ + @{ + @"ti": @1, + @"whenTriggers": @[@{ + @"eventProperties": @[@{ + @"propertyName": @"newValue", + @"propertyValue": @"Gold", + }], + @"profileAttrName": @"Customer Type", + }] + }, + @{ + @"ti": @2, + @"whenTriggers": @[@{ + @"eventProperties": @[@{ + @"propertyName": @"newValue", + @"propertyValue": @"Premium", + }], + @"profileAttrName": @"Customer Type", + }] + }, + ]; + NSDictionary *profile = @{ + @"Customer Type": @{ + @"newValue": @"Gold", + @"oldValue": @"Premium" + } + }; + + + [self.evaluationManager evaluateOnUserAttributeChange:profile]; + XCTAssertEqualObjects((@[@1]), self.evaluationManager.evaluatedServerSideInAppIds); + XCTAssertNotEqualObjects((@[@2]), self.evaluationManager.evaluatedServerSideInAppIds); +} + - (void)testEvaluateCharged { self.helper.inAppStore.serverSideInApps = @[ @{ From ab7af0f402354e644a650f3da5cedb553bd6e7a8 Mon Sep 17 00:00:00 2001 From: kushCT Date: Thu, 4 Jul 2024 15:52:32 +0530 Subject: [PATCH 18/24] Added Unit tests for getUserAttributeChangeProperties --- CleverTapSDK.xcodeproj/project.pbxproj | 4 + CleverTapSDKTests/CTLocalDataStoreTests.m | 118 ++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 CleverTapSDKTests/CTLocalDataStoreTests.m diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index 215939c8..c61372da 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -153,6 +153,7 @@ 07FD65A2223BC26300A845B7 /* CTCoverViewController~iphoneland.xib in Resources */ = {isa = PBXBuildFile; fileRef = 07FD65A1223BC26300A845B7 /* CTCoverViewController~iphoneland.xib */; }; 07FD65A4223BCB8200A845B7 /* CTCoverViewController~ipadland.xib in Resources */ = {isa = PBXBuildFile; fileRef = 07FD65A3223BCB8200A845B7 /* CTCoverViewController~ipadland.xib */; }; 0B5564562C25946C00B87284 /* CTUserInfoMigratorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B5564552C25946C00B87284 /* CTUserInfoMigratorTest.m */; }; + 0B995A4A2C36AEDC00AF6006 /* CTLocalDataStoreTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B995A492C36AEDC00AF6006 /* CTLocalDataStoreTests.m */; }; 1F1C18806B7F29B3374F2448 /* libPods-shared-CleverTapSDKTestsApp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E303560B5EE1D154C1E3D9EF /* libPods-shared-CleverTapSDKTestsApp.a */; }; 32394C1F29FA251E00956058 /* CTEventBuilderTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32394C1E29FA251E00956058 /* CTEventBuilderTest.m */; }; 32394C2129FA264B00956058 /* CTPreferencesTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32394C2029FA264B00956058 /* CTPreferencesTest.m */; }; @@ -702,6 +703,7 @@ 07FD65A1223BC26300A845B7 /* CTCoverViewController~iphoneland.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = "CTCoverViewController~iphoneland.xib"; sourceTree = ""; }; 07FD65A3223BCB8200A845B7 /* CTCoverViewController~ipadland.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = "CTCoverViewController~ipadland.xib"; sourceTree = ""; }; 0B5564552C25946C00B87284 /* CTUserInfoMigratorTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTUserInfoMigratorTest.m; sourceTree = ""; }; + 0B995A492C36AEDC00AF6006 /* CTLocalDataStoreTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTLocalDataStoreTests.m; sourceTree = ""; }; 0CA46771B6F202E37DAC9F70 /* Pods-shared-CleverTapSDKTestsApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-shared-CleverTapSDKTestsApp.debug.xcconfig"; path = "Target Support Files/Pods-shared-CleverTapSDKTestsApp/Pods-shared-CleverTapSDKTestsApp.debug.xcconfig"; sourceTree = ""; }; 129AEC403AFA828F591B756E /* Pods-shared-CleverTapSDKTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-shared-CleverTapSDKTests.release.xcconfig"; path = "Target Support Files/Pods-shared-CleverTapSDKTests/Pods-shared-CleverTapSDKTests.release.xcconfig"; sourceTree = ""; }; 32394C1E29FA251E00956058 /* CTEventBuilderTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTEventBuilderTest.m; sourceTree = ""; }; @@ -1408,6 +1410,7 @@ 4ECD88302ADC8A05003885CE /* CTSessionManagerTests.m */, 6BD851C82B45CD1800FA5298 /* CTMultiDelegateManager+Tests.h */, 0B5564552C25946C00B87284 /* CTUserInfoMigratorTest.m */, + 0B995A492C36AEDC00AF6006 /* CTLocalDataStoreTests.m */, ); path = CleverTapSDKTests; sourceTree = ""; @@ -2306,6 +2309,7 @@ 32790959299F4B29001FE140 /* CTDeviceInfoTest.m in Sources */, 48C31A822B1DC5CF00CA2A90 /* CTInAppImagePrefetchManagerTest.m in Sources */, 4ECD88312ADC8A05003885CE /* CTSessionManagerTests.m in Sources */, + 0B995A4A2C36AEDC00AF6006 /* CTLocalDataStoreTests.m in Sources */, 6A4427C52AA6515A0098866F /* CTTriggersMatcherTest.m in Sources */, 0B5564562C25946C00B87284 /* CTUserInfoMigratorTest.m in Sources */, 4E2CF1442AC56D8F00441E8B /* CTEncryptionTests.m in Sources */, diff --git a/CleverTapSDKTests/CTLocalDataStoreTests.m b/CleverTapSDKTests/CTLocalDataStoreTests.m new file mode 100644 index 00000000..7760c9e2 --- /dev/null +++ b/CleverTapSDKTests/CTLocalDataStoreTests.m @@ -0,0 +1,118 @@ +// +// CTLocalDataStoreTests.m +// CleverTapSDKTests +// +// Created by Kushagra Mishra on 04/07/24. +// Copyright © 2024 CleverTap. All rights reserved. +// +#import +#import +#import "CTLocalDataStore.h" +#import "CTProfileBuilder.h" +#import "CTConstants.h" + +@interface CTLocalDataStoreTests : XCTestCase +@property (nonatomic, strong) CTLocalDataStore *dataStore; +@property (nonatomic, strong) id dataStoreMock; +@property (nonatomic, strong) id profileBuilderMock; +@end + +@implementation CTLocalDataStoreTests + +- (void)setUp { + [super setUp]; + CleverTapInstanceConfig *config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccount" accountToken:@"testToken" accountRegion:@"testRegion"]; + CTDeviceInfo *deviceInfo = [[CTDeviceInfo alloc] initWithConfig:config andCleverTapID:@"testDeviceInfo"]; + CTDispatchQueueManager *queueManager =[[CTDispatchQueueManager alloc]initWithConfig:config]; + self.dataStore = [[CTLocalDataStore alloc] initWithConfig:config profileValues:[NSMutableDictionary new] andDeviceInfo:deviceInfo dispatchQueueManager:queueManager]; +} + +- (void)tearDown { + self.dataStore = nil; + [super tearDown]; +} + +- (void)testGetUserAttributeChangePropertiesWithEmptyEvent { + NSDictionary *event = @{}; + NSDictionary *result = [self.dataStore getUserAttributeChangeProperties:event]; + XCTAssertEqual(result.count, 0); +} + +- (void)testGetUserAttributeChangePropertiesWithNoProfile { + NSDictionary *event = @{@"someKey": @"someValue"}; + NSDictionary *result = [self.dataStore getUserAttributeChangeProperties:event]; + XCTAssertEqual(result.count, 0); +} + +- (void)testGetUserAttributeChangePropertiesWithProfileUpdate { + NSDictionary *profile = @{ + @"name": @"John", + @"age": @"30", + @"cc": @"1234", // Should be skipped + @"tz": @"GMT", // Should be skipped + @"Carrier": @"Jio" // Should be skipped + }; + NSDictionary *event = @{CLTAP_PROFILE: profile}; + + // Mock old values for the keys + id mockOldValueForName = @"Jane"; + id mockOldValueForAge = @"25"; + + // Stub the method to return mock old values + CTLocalDataStore *dataStoreMock = OCMPartialMock(self.dataStore); + id mockGetProfileFieldForKeyName = OCMStub([dataStoreMock getProfileFieldForKey:@"name"]).andReturn(mockOldValueForName); + id mockGetProfileFieldForKeyAge = OCMStub([dataStoreMock getProfileFieldForKey:@"age"]).andReturn(mockOldValueForAge); + + // Call the method and get the result + NSDictionary *result = [dataStoreMock getUserAttributeChangeProperties:event]; + + // Print debug information + NSLog(@"Result: %@", result); + + // Verify the result dictionary + XCTAssertEqual(result.count, 2); + XCTAssertEqual(result[@"name"][CLTAP_KEY_OLD_VALUE], mockOldValueForName); + XCTAssertEqual(result[@"name"][CLTAP_KEY_NEW_VALUE], @"John"); + XCTAssertEqual(result[@"age"][CLTAP_KEY_OLD_VALUE], mockOldValueForAge); + XCTAssertEqual(result[@"age"][CLTAP_KEY_NEW_VALUE], @"30"); + + // Ensure skipped keys are not present in the result + XCTAssertNil(result[@"cc"]); + XCTAssertNil(result[@"tz"]); + XCTAssertNil(result[@"Carrier"]); + + // Verify the mock methods were called + OCMVerify(mockGetProfileFieldForKeyName); + OCMVerify(mockGetProfileFieldForKeyAge); +} + +- (void)testGetUserAttributeChangePropertiesWithIncrementCommand { + NSDictionary *profile = @{ + @"points": @{kCLTAP_COMMAND_INCREMENT: @10} + }; + NSDictionary *event = @{CLTAP_PROFILE: profile}; + + // Mock old and new values for the key + id mockOldValue = @20; + id mockNewValue = @30; + + // Stub the method to return mock old values and handle increment command + CTLocalDataStore *dataStoreMock = OCMPartialMock(self.dataStore); + id mockGetProfileFieldForKey = OCMStub([dataStoreMock getProfileFieldForKey:@"points"]).andReturn(mockOldValue); + + // Call the method and get the result + NSDictionary *result = [dataStoreMock getUserAttributeChangeProperties:event]; + + // Print debug information + NSLog(@"Result: %@", result); + + // Verify the result dictionary + XCTAssertEqual(result.count, 1); + XCTAssertEqual(result[@"points"][CLTAP_KEY_OLD_VALUE], mockOldValue); + XCTAssertEqual(result[@"points"][CLTAP_KEY_NEW_VALUE], mockNewValue); + + // Verify the mock methods were called + OCMVerify(mockGetProfileFieldForKey); +} + +@end From d4436b234bb551d3189c5e42d3aaeb392d7dec48 Mon Sep 17 00:00:00 2001 From: kushCT Date: Mon, 8 Jul 2024 16:29:58 +0530 Subject: [PATCH 19/24] Removed NSLogs --- CleverTapSDKTests/CTLocalDataStoreTests.m | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CleverTapSDKTests/CTLocalDataStoreTests.m b/CleverTapSDKTests/CTLocalDataStoreTests.m index 7760c9e2..0ccae529 100644 --- a/CleverTapSDKTests/CTLocalDataStoreTests.m +++ b/CleverTapSDKTests/CTLocalDataStoreTests.m @@ -66,9 +66,6 @@ - (void)testGetUserAttributeChangePropertiesWithProfileUpdate { // Call the method and get the result NSDictionary *result = [dataStoreMock getUserAttributeChangeProperties:event]; - // Print debug information - NSLog(@"Result: %@", result); - // Verify the result dictionary XCTAssertEqual(result.count, 2); XCTAssertEqual(result[@"name"][CLTAP_KEY_OLD_VALUE], mockOldValueForName); @@ -103,9 +100,6 @@ - (void)testGetUserAttributeChangePropertiesWithIncrementCommand { // Call the method and get the result NSDictionary *result = [dataStoreMock getUserAttributeChangeProperties:event]; - // Print debug information - NSLog(@"Result: %@", result); - // Verify the result dictionary XCTAssertEqual(result.count, 1); XCTAssertEqual(result[@"points"][CLTAP_KEY_OLD_VALUE], mockOldValue); From d2a8972e627890e0f0caf815c8e7877f93737977 Mon Sep 17 00:00:00 2001 From: kushCT Date: Thu, 11 Jul 2024 03:24:19 +0530 Subject: [PATCH 20/24] Attached "inapps_eval_profile" and "inapps_suppressed_profile" to the batch sent header for incorporating user profile changes --- CleverTapSDK/CTBatchSentDelegate.h | 3 +- CleverTapSDK/CTConstants.h | 2 + CleverTapSDK/CTMultiDelegateManager.h | 2 +- CleverTapSDK/CTMultiDelegateManager.m | 6 +- CleverTapSDK/CleverTap.m | 4 +- .../InApps/CTInAppEvaluationManager.m | 102 +++++++++++++++--- 6 files changed, 95 insertions(+), 24 deletions(-) diff --git a/CleverTapSDK/CTBatchSentDelegate.h b/CleverTapSDK/CTBatchSentDelegate.h index b7445a72..d9e5b96f 100644 --- a/CleverTapSDK/CTBatchSentDelegate.h +++ b/CleverTapSDK/CTBatchSentDelegate.h @@ -7,11 +7,12 @@ // #import +#import "CTQueueType.h" @protocol CTBatchSentDelegate @optional -- (void)onBatchSent:(NSArray *)batchWithHeader withSuccess:(BOOL)success; +- (void)onBatchSent:(NSArray *)batchWithHeader withSuccess:(BOOL)success withQueueType:(CTQueueType)queueType; @optional - (void)onAppLaunchedWithSuccess:(BOOL)success; diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index 015804c1..c054b48a 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -140,6 +140,8 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY; #define CLTAP_INAPP_SUPPRESSED_META_KEY @"inapps_suppressed" #define CLTAP_INAPP_SS_EVAL_STORAGE_KEY @"inapps_eval" #define CLTAP_INAPP_SUPPRESSED_STORAGE_KEY @"inapps_suppressed" +#define CLTAP_INAPP_SS_EVAL_STORAGE_KEY_PROFILE @"inapps_eval_profile" +#define CLTAP_INAPP_SUPPRESSED_STORAGE_KEY_PROFILE @"inapps_suppressed_profile" #define CLTAP_PREFS_INAPP_SESSION_MAX_KEY @"imc_max" #define CLTAP_PREFS_INAPP_LAST_DATE_KEY @"ict_date" diff --git a/CleverTapSDK/CTMultiDelegateManager.h b/CleverTapSDK/CTMultiDelegateManager.h index 5896688b..aa5e60e0 100644 --- a/CleverTapSDK/CTMultiDelegateManager.h +++ b/CleverTapSDK/CTMultiDelegateManager.h @@ -26,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)addBatchSentDelegate:(id)delegate; - (void)removeBatchSentDelegate:(id)delegate; -- (void)notifyDelegatesBatchDidSend:(NSArray *)batchWithHeader withSuccess:(BOOL)success; +- (void)notifyDelegatesBatchDidSend:(NSArray *)batchWithHeader withSuccess:(BOOL)success withQueueType:(CTQueueType)queueType; @end diff --git a/CleverTapSDK/CTMultiDelegateManager.m b/CleverTapSDK/CTMultiDelegateManager.m index c9c21a24..0a2e4f02 100644 --- a/CleverTapSDK/CTMultiDelegateManager.m +++ b/CleverTapSDK/CTMultiDelegateManager.m @@ -76,11 +76,11 @@ - (void)removeBatchSentDelegate:(id)delegate { [self.batchSentDelegates removeObject:delegate]; } -- (void)notifyDelegatesBatchDidSend:(NSArray *)batchWithHeader withSuccess:(BOOL)success { +- (void)notifyDelegatesBatchDidSend:(NSArray *)batchWithHeader withSuccess:(BOOL)success withQueueType:(CTQueueType)queueType{ NSNumber *isAppLaunched = nil; for (id batchSentDelegate in self.batchSentDelegates) { - if ([batchSentDelegate respondsToSelector:@selector(onBatchSent: withSuccess:)]) { - [batchSentDelegate onBatchSent:batchWithHeader withSuccess:success]; + if ([batchSentDelegate respondsToSelector:@selector(onBatchSent: withSuccess:withQueueType:)]) { + [batchSentDelegate onBatchSent:batchWithHeader withSuccess:success withQueueType:queueType]; } if ([batchSentDelegate respondsToSelector:@selector(onAppLaunchedWithSuccess:)]) { if (isAppLaunched == nil) { diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 8fdc64fd..6e921415 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -2214,7 +2214,7 @@ - (void)sendQueue:(NSMutableArray *)queue ofType:(CTQueueType)queueType { [self scheduleQueueFlush]; [self handleSendQueueFail]; - [self.delegateManager notifyDelegatesBatchDidSend:batchWithHeader withSuccess:NO]; + [self.delegateManager notifyDelegatesBatchDidSend:batchWithHeader withSuccess:NO withQueueType:queueType]; } if (!success || redirect) { @@ -2227,7 +2227,7 @@ - (void)sendQueue:(NSMutableArray *)queue ofType:(CTQueueType)queueType { [self parseResponse:responseData]; - [self.delegateManager notifyDelegatesBatchDidSend:batchWithHeader withSuccess:YES]; + [self.delegateManager notifyDelegatesBatchDidSend:batchWithHeader withSuccess:YES withQueueType:queueType]; CleverTapLogDebug(self.config.logLevel,@"%@: Successfully sent %lu events", self, (unsigned long)[batch count]); diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.m b/CleverTapSDK/InApps/CTInAppEvaluationManager.m index acf48a31..9e77d9f2 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.m +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.m @@ -23,6 +23,8 @@ @interface CTInAppEvaluationManager() @property (nonatomic, strong) NSMutableArray *evaluatedServerSideInAppIds; @property (nonatomic, strong) NSMutableArray *suppressedClientSideInApps; +@property (nonatomic, strong) NSMutableArray *evaluatedServerSideInAppIdsForProfile; +@property (nonatomic, strong) NSMutableArray *suppressedClientSideInAppsForProfile; @property BOOL hasAppLaunchedFailed; @property (nonatomic, strong) NSDictionary *appLaunchedProperties; @@ -36,7 +38,7 @@ @interface CTInAppEvaluationManager() @property (nonatomic, strong) NSString *accountId; @property (nonatomic, strong) NSString *deviceId; -- (void)evaluateServerSide:(NSArray *)events; +- (void)evaluateServerSide:(NSArray *)events withQueueType:(CTQueueType)queueType; - (void)evaluateClientSide:(NSArray *)events; - (NSMutableArray *)evaluate:(CTEventAdapter *)event withInApps:(NSArray *)inApps; @@ -68,6 +70,18 @@ - (instancetype)initWithAccountId:(NSString *)accountId if (savedSuppressedClientSideInApps) { self.suppressedClientSideInApps = [savedSuppressedClientSideInApps mutableCopy]; } + + self.evaluatedServerSideInAppIdsForProfile = [NSMutableArray new]; + NSArray *savedEvaluatedServerSideInAppIdsForProfile = [CTPreferences getObjectForKey:[self storageKeyWithSuffix:CLTAP_INAPP_SS_EVAL_STORAGE_KEY_PROFILE]]; + if (savedEvaluatedServerSideInAppIdsForProfile) { + self.evaluatedServerSideInAppIdsForProfile = [savedEvaluatedServerSideInAppIdsForProfile mutableCopy]; + } + + self.suppressedClientSideInAppsForProfile = [NSMutableArray new]; + NSArray *savedSuppressedClientSideInAppsForProfile = [CTPreferences getObjectForKey:[self storageKeyWithSuffix:CLTAP_INAPP_SUPPRESSED_STORAGE_KEY_PROFILE]]; + if (savedSuppressedClientSideInAppsForProfile) { + self.suppressedClientSideInAppsForProfile = [savedSuppressedClientSideInAppsForProfile mutableCopy]; + } self.inAppStore = inAppStore; self.triggersMatcher = [CTTriggersMatcher new]; @@ -88,7 +102,8 @@ - (void)evaluateOnEvent:(NSString *)eventName withProps:(NSDictionary *)properti CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:eventName eventProperties:properties andLocation:self.location]; NSArray *eventList = @[event]; - [self evaluateServerSide:eventList]; + CTQueueType queueType = CTQueueTypeEvents; + [self evaluateServerSide:eventList withQueueType:queueType]; [self evaluateClientSide:eventList]; } @@ -102,8 +117,8 @@ -(void)evaluateOnUserAttributeChange:(NSDictionary * CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:eventName profileAttrName:key eventProperties: value andLocation:self.location]; [eventAdapterList addObject:event]; }]; - - [self evaluateServerSide:eventAdapterList]; + CTQueueType queueType = CTQueueTypeProfile; + [self evaluateServerSide:eventAdapterList withQueueType:queueType]; [self evaluateClientSide:eventAdapterList]; } @@ -111,7 +126,8 @@ -(void)evaluateOnUserAttributeChange:(NSDictionary * - (void)evaluateOnChargedEvent:(NSDictionary *)chargeDetails andItems:(NSArray *)items { CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:CLTAP_CHARGED_EVENT eventProperties:chargeDetails location:self.location andItems:items]; NSArray *eventList = @[event]; - [self evaluateServerSide:eventList]; + CTQueueType queueType = CTQueueTypeEvents; + [self evaluateServerSide:eventList withQueueType:queueType]; [self evaluateClientSide:eventList]; } @@ -161,7 +177,7 @@ - (void)evaluateClientSide:(NSArray *)events { } } -- (void)evaluateServerSide:(NSArray *)events { +- (void)evaluateServerSide:(NSArray *)events withQueueType:(CTQueueType)queueType{ NSMutableArray *eligibleInApps = [NSMutableArray array]; for (CTEventAdapter *event in events) { [eligibleInApps addObjectsFromArray:[self evaluate:event withInApps:self.inAppStore.serverSideInApps]]; @@ -173,12 +189,22 @@ - (void)evaluateServerSide:(NSArray *)events { NSNumber *cid = [CTUtils numberFromString:campaignId]; if (cid) { updated = YES; - [self.evaluatedServerSideInAppIds addObject:cid]; + if (queueType == CTQueueTypeEvents){ + [self.evaluatedServerSideInAppIds addObject:cid]; + } + else if (queueType == CTQueueTypeProfile){ + [self.evaluatedServerSideInAppIdsForProfile addObject:cid]; + } } } } if (updated) { - [self saveEvaluatedServerSideInAppIds]; + if (queueType == CTQueueTypeEvents){ + [self saveEvaluatedServerSideInAppIds]; + } + else if (queueType == CTQueueTypeProfile){ + [self saveEvaluatedServerSideInAppIdsForProfile]; + } } } @@ -213,11 +239,17 @@ - (NSMutableArray *)evaluate:(CTEventAdapter *)event withInApps:(NSArray *)inApp return eligibleInApps; } -- (void)onBatchSent:(NSArray *)batchWithHeader withSuccess:(BOOL)success { +- (void)onBatchSent:(NSArray *)batchWithHeader withSuccess:(BOOL)success withQueueType:(CTQueueType)queueType{ if (success) { NSDictionary *header = batchWithHeader[0]; - [self removeSentEvaluatedServerSideInAppIds:header]; - [self removeSentSuppressedClientSideInApps:header]; + if (queueType == CTQueueTypeEvents) { + [self removeSentEvaluatedServerSideInAppIds:header]; + [self removeSentSuppressedClientSideInApps:header]; + } + else if (queueType == CTQueueTypeEvents) { + [self removeSentEvaluatedServerSideInAppIdsForProfile:header]; + [self removeSentSuppressedClientSideInAppsForProfile:header]; + } } } @@ -247,6 +279,25 @@ - (void)removeSentSuppressedClientSideInApps:(NSDictionary *)header { } } +- (void)removeSentEvaluatedServerSideInAppIdsForProfile:(NSDictionary *)header { + NSArray *inapps_eval = header[CLTAP_INAPP_SS_EVAL_STORAGE_KEY_PROFILE]; + if (inapps_eval && [inapps_eval count] > 0) { + NSUInteger len = inapps_eval.count > self.evaluatedServerSideInAppIdsForProfile.count ? self.evaluatedServerSideInAppIdsForProfile.count : inapps_eval.count; + [self.evaluatedServerSideInAppIdsForProfile removeObjectsInRange:NSMakeRange(0, len)]; + [self saveEvaluatedServerSideInAppIdsForProfile]; + } +} + +- (void)removeSentSuppressedClientSideInAppsForProfile:(NSDictionary *)header { + NSArray *suppresed_inapps = header[CLTAP_INAPP_SUPPRESSED_STORAGE_KEY_PROFILE]; + if (suppresed_inapps && [suppresed_inapps count] > 0) { + NSUInteger len = suppresed_inapps.count > self.suppressedClientSideInAppsForProfile.count ? self.suppressedClientSideInAppsForProfile.count : suppresed_inapps.count; + [self.suppressedClientSideInAppsForProfile removeObjectsInRange:NSMakeRange(0, len)]; + [self saveSuppressedClientSideInAppsForProfile]; + } +} + + - (BOOL)shouldSuppress:(NSDictionary *)inApp { return [inApp[CLTAP_INAPP_IS_SUPPRESSED] boolValue]; } @@ -333,15 +384,24 @@ - (BatchHeaderKeyPathValues)onBatchHeaderCreationForQueue:(CTQueueType)queueType if (queueType != CTQueueTypeEvents && queueType != CTQueueTypeProfile) { return [NSMutableDictionary new]; } - NSMutableDictionary *header = [NSMutableDictionary new]; - if ([self.evaluatedServerSideInAppIds count] > 0) { - header[CLTAP_INAPP_SS_EVAL_META_KEY] = self.evaluatedServerSideInAppIds; + if (queueType == CTQueueTypeProfile) { + + if ([self.evaluatedServerSideInAppIdsForProfile count] > 0) { + header[CLTAP_INAPP_SS_EVAL_STORAGE_KEY_PROFILE] = self.evaluatedServerSideInAppIdsForProfile; + } + if ([self.suppressedClientSideInAppsForProfile count] > 0) { + header[CLTAP_INAPP_SUPPRESSED_STORAGE_KEY_PROFILE] = self.suppressedClientSideInAppsForProfile; + } } - if ([self.suppressedClientSideInApps count] > 0) { - header[CLTAP_INAPP_SUPPRESSED_META_KEY] = self.suppressedClientSideInApps; + else { + if ([self.evaluatedServerSideInAppIds count] > 0) { + header[CLTAP_INAPP_SS_EVAL_META_KEY] = self.evaluatedServerSideInAppIds; + } + if ([self.suppressedClientSideInApps count] > 0) { + header[CLTAP_INAPP_SUPPRESSED_META_KEY] = self.suppressedClientSideInApps; + } } - return header; } @@ -353,6 +413,14 @@ - (void)saveSuppressedClientSideInApps { [CTPreferences putObject:self.suppressedClientSideInApps forKey:[self storageKeyWithSuffix:CLTAP_INAPP_SUPPRESSED_STORAGE_KEY]]; } +- (void)saveEvaluatedServerSideInAppIdsForProfile { + [CTPreferences putObject:self.evaluatedServerSideInAppIdsForProfile forKey:[self storageKeyWithSuffix:CLTAP_INAPP_SS_EVAL_STORAGE_KEY_PROFILE]]; +} + +- (void)saveSuppressedClientSideInAppsForProfile { + [CTPreferences putObject:self.suppressedClientSideInAppsForProfile forKey:[self storageKeyWithSuffix:CLTAP_INAPP_SUPPRESSED_STORAGE_KEY_PROFILE]]; +} + - (NSString *)storageKeyWithSuffix:(NSString *)suffix { return [NSString stringWithFormat:@"%@:%@:%@", self.accountId, suffix, self.deviceId]; } From f46b79112ae4e1105db70233ed8e2df78982ef1c Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Fri, 12 Jul 2024 12:32:02 +0300 Subject: [PATCH 21/24] Fix client-side evaluation for events --- CleverTapSDK/InApps/CTInAppEvaluationManager.m | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.m b/CleverTapSDK/InApps/CTInAppEvaluationManager.m index 9e77d9f2..9f70b6ef 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.m +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.m @@ -156,12 +156,10 @@ - (void)evaluateClientSide:(NSArray *)events { for (CTEventAdapter *event in events) { id oldValue = [event.eventProperties objectForKey:CLTAP_KEY_OLD_VALUE]; id newValue = [event.eventProperties objectForKey:CLTAP_KEY_NEW_VALUE]; - if (event.profileAttrName != nil) { - if (newValue == oldValue) { - continue; - } - [eligibleInApps addObjectsFromArray:[self evaluate:event withInApps:self.inAppStore.clientSideInApps]]; + if (event.profileAttrName != nil && newValue == oldValue) { + continue; } + [eligibleInApps addObjectsFromArray:[self evaluate:event withInApps:self.inAppStore.clientSideInApps]]; } [self sortByPriority:eligibleInApps]; From e45f193edd4e2dd9927805ab49ffc378c496e9d3 Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Fri, 12 Jul 2024 12:32:47 +0300 Subject: [PATCH 22/24] Fix batch header and sent --- CleverTapSDK/InApps/CTInAppEvaluationManager.m | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.m b/CleverTapSDK/InApps/CTInAppEvaluationManager.m index 9f70b6ef..de811be3 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.m +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.m @@ -244,7 +244,7 @@ - (void)onBatchSent:(NSArray *)batchWithHeader withSuccess:(BOOL)success withQue [self removeSentEvaluatedServerSideInAppIds:header]; [self removeSentSuppressedClientSideInApps:header]; } - else if (queueType == CTQueueTypeEvents) { + else if (queueType == CTQueueTypeProfile) { [self removeSentEvaluatedServerSideInAppIdsForProfile:header]; [self removeSentSuppressedClientSideInAppsForProfile:header]; } @@ -278,7 +278,7 @@ - (void)removeSentSuppressedClientSideInApps:(NSDictionary *)header { } - (void)removeSentEvaluatedServerSideInAppIdsForProfile:(NSDictionary *)header { - NSArray *inapps_eval = header[CLTAP_INAPP_SS_EVAL_STORAGE_KEY_PROFILE]; + NSArray *inapps_eval = header[CLTAP_INAPP_SS_EVAL_META_KEY]; if (inapps_eval && [inapps_eval count] > 0) { NSUInteger len = inapps_eval.count > self.evaluatedServerSideInAppIdsForProfile.count ? self.evaluatedServerSideInAppIdsForProfile.count : inapps_eval.count; [self.evaluatedServerSideInAppIdsForProfile removeObjectsInRange:NSMakeRange(0, len)]; @@ -287,7 +287,7 @@ - (void)removeSentEvaluatedServerSideInAppIdsForProfile:(NSDictionary *)header { } - (void)removeSentSuppressedClientSideInAppsForProfile:(NSDictionary *)header { - NSArray *suppresed_inapps = header[CLTAP_INAPP_SUPPRESSED_STORAGE_KEY_PROFILE]; + NSArray *suppresed_inapps = header[CLTAP_INAPP_SUPPRESSED_META_KEY]; if (suppresed_inapps && [suppresed_inapps count] > 0) { NSUInteger len = suppresed_inapps.count > self.suppressedClientSideInAppsForProfile.count ? self.suppressedClientSideInAppsForProfile.count : suppresed_inapps.count; [self.suppressedClientSideInAppsForProfile removeObjectsInRange:NSMakeRange(0, len)]; @@ -384,12 +384,11 @@ - (BatchHeaderKeyPathValues)onBatchHeaderCreationForQueue:(CTQueueType)queueType } NSMutableDictionary *header = [NSMutableDictionary new]; if (queueType == CTQueueTypeProfile) { - if ([self.evaluatedServerSideInAppIdsForProfile count] > 0) { - header[CLTAP_INAPP_SS_EVAL_STORAGE_KEY_PROFILE] = self.evaluatedServerSideInAppIdsForProfile; + header[CLTAP_INAPP_SS_EVAL_META_KEY] = self.evaluatedServerSideInAppIdsForProfile; } if ([self.suppressedClientSideInAppsForProfile count] > 0) { - header[CLTAP_INAPP_SUPPRESSED_STORAGE_KEY_PROFILE] = self.suppressedClientSideInAppsForProfile; + header[CLTAP_INAPP_SUPPRESSED_META_KEY] = self.suppressedClientSideInAppsForProfile; } } else { From 61223cee690ea8d9dfba59981b0ae78ea275ace0 Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Fri, 12 Jul 2024 12:33:47 +0300 Subject: [PATCH 23/24] Linting --- CleverTapSDK/CTUserInfoMigrator.m | 1 + CleverTapSDK/InApps/CTInAppEvaluationManager.m | 10 ++++------ CleverTapSDKTests/CTLocalDataStoreTests.m | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/CleverTapSDK/CTUserInfoMigrator.m b/CleverTapSDK/CTUserInfoMigrator.m index 1cb682a4..cbc48cdc 100644 --- a/CleverTapSDK/CTUserInfoMigrator.m +++ b/CleverTapSDK/CTUserInfoMigrator.m @@ -22,4 +22,5 @@ + (void)migrateUserInfoFileForAccountID:(NSString *)acc_id deviceID:(NSString *) CleverTapLogStaticInternal(@"[CTUserInfo]: Failed to copy local file: %@", error.localizedDescription); } } + @end diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.m b/CleverTapSDK/InApps/CTInAppEvaluationManager.m index de811be3..088d7e91 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.m +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.m @@ -102,8 +102,7 @@ - (void)evaluateOnEvent:(NSString *)eventName withProps:(NSDictionary *)properti CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:eventName eventProperties:properties andLocation:self.location]; NSArray *eventList = @[event]; - CTQueueType queueType = CTQueueTypeEvents; - [self evaluateServerSide:eventList withQueueType:queueType]; + [self evaluateServerSide:eventList withQueueType:CTQueueTypeEvents]; [self evaluateClientSide:eventList]; } @@ -117,8 +116,7 @@ -(void)evaluateOnUserAttributeChange:(NSDictionary * CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:eventName profileAttrName:key eventProperties: value andLocation:self.location]; [eventAdapterList addObject:event]; }]; - CTQueueType queueType = CTQueueTypeProfile; - [self evaluateServerSide:eventAdapterList withQueueType:queueType]; + [self evaluateServerSide:eventAdapterList withQueueType:CTQueueTypeProfile]; [self evaluateClientSide:eventAdapterList]; } @@ -178,8 +176,8 @@ - (void)evaluateClientSide:(NSArray *)events { - (void)evaluateServerSide:(NSArray *)events withQueueType:(CTQueueType)queueType{ NSMutableArray *eligibleInApps = [NSMutableArray array]; for (CTEventAdapter *event in events) { - [eligibleInApps addObjectsFromArray:[self evaluate:event withInApps:self.inAppStore.serverSideInApps]]; - } + [eligibleInApps addObjectsFromArray:[self evaluate:event withInApps:self.inAppStore.serverSideInApps]]; + } BOOL updated = NO; for (NSDictionary *inApp in eligibleInApps) { NSString *campaignId = [CTInAppNotification inAppId:inApp]; diff --git a/CleverTapSDKTests/CTLocalDataStoreTests.m b/CleverTapSDKTests/CTLocalDataStoreTests.m index 0ccae529..270d2804 100644 --- a/CleverTapSDKTests/CTLocalDataStoreTests.m +++ b/CleverTapSDKTests/CTLocalDataStoreTests.m @@ -23,7 +23,7 @@ - (void)setUp { [super setUp]; CleverTapInstanceConfig *config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccount" accountToken:@"testToken" accountRegion:@"testRegion"]; CTDeviceInfo *deviceInfo = [[CTDeviceInfo alloc] initWithConfig:config andCleverTapID:@"testDeviceInfo"]; - CTDispatchQueueManager *queueManager =[[CTDispatchQueueManager alloc]initWithConfig:config]; + CTDispatchQueueManager *queueManager = [[CTDispatchQueueManager alloc] initWithConfig:config]; self.dataStore = [[CTLocalDataStore alloc] initWithConfig:config profileValues:[NSMutableDictionary new] andDeviceInfo:deviceInfo dispatchQueueManager:queueManager]; } From 5d3ed9956f935d5e70c4c55a208c927e8db8873f Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Fri, 12 Jul 2024 12:34:10 +0300 Subject: [PATCH 24/24] Fix in-app evaluation tests --- .../InApps/CTInAppEvaluationManager+Tests.h | 4 +++ .../InApps/CTInAppEvaluationManagerTest.m | 25 +++++++++++-------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/CleverTapSDKTests/InApps/CTInAppEvaluationManager+Tests.h b/CleverTapSDKTests/InApps/CTInAppEvaluationManager+Tests.h index 3c305743..1ce0cf2f 100644 --- a/CleverTapSDKTests/InApps/CTInAppEvaluationManager+Tests.h +++ b/CleverTapSDKTests/InApps/CTInAppEvaluationManager+Tests.h @@ -14,6 +14,8 @@ @property (nonatomic, strong) CTInAppDisplayManager *inAppDisplayManager; @property (nonatomic, strong) NSMutableArray *suppressedClientSideInApps; @property (nonatomic, strong) NSMutableArray *evaluatedServerSideInAppIds; +@property (nonatomic, strong) NSMutableArray *evaluatedServerSideInAppIdsForProfile; +@property (nonatomic, strong) NSMutableArray *suppressedClientSideInAppsForProfile; @property (nonatomic, strong) NSDictionary *appLaunchedProperties; - (void)sortByPriority:(NSMutableArray *)inApps; - (NSMutableArray *)evaluate:(CTEventAdapter *)event withInApps:(NSArray *)inApps; @@ -24,6 +26,8 @@ - (void)onAppLaunchedWithSuccess:(BOOL)success; - (void)saveEvaluatedServerSideInAppIds; - (void)saveSuppressedClientSideInApps; +- (void)saveEvaluatedServerSideInAppIdsForProfile; +- (void)saveSuppressedClientSideInAppsForProfile; - (NSString *)storageKeyWithSuffix:(NSString *)suffix; @end diff --git a/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m b/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m index c588159a..289c6931 100644 --- a/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m +++ b/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m @@ -66,15 +66,20 @@ - (void)setUp { } - (void)tearDown { - // Clean up resources if needed - //self.evaluationManager = nil; + // Remove triggers for (int i = 1; i <= 4; i++) { [self.evaluationManager.triggerManager removeTriggers:[NSString stringWithFormat:@"%d", i]]; } + // Remove saved ids self.evaluationManager.evaluatedServerSideInAppIds = [NSMutableArray new]; [self.evaluationManager saveEvaluatedServerSideInAppIds]; self.evaluationManager.suppressedClientSideInApps = [NSMutableArray new]; [self.evaluationManager saveSuppressedClientSideInApps]; + + self.evaluationManager.evaluatedServerSideInAppIdsForProfile = [NSMutableArray new]; + [self.evaluationManager saveEvaluatedServerSideInAppIdsForProfile]; + self.evaluationManager.suppressedClientSideInAppsForProfile = [NSMutableArray new]; + [self.evaluationManager saveSuppressedClientSideInAppsForProfile]; [super tearDown]; } @@ -332,8 +337,8 @@ - (void)testEvaluateUserAttribute { [self.evaluationManager evaluateOnUserAttributeChange:profile]; - XCTAssertEqualObjects((@[@1]), self.evaluationManager.evaluatedServerSideInAppIds); - XCTAssertNotEqualObjects((@[@2]), self.evaluationManager.evaluatedServerSideInAppIds); + XCTAssertEqualObjects((@[@1]), self.evaluationManager.evaluatedServerSideInAppIdsForProfile); + XCTAssertNotEqualObjects((@[@2]), self.evaluationManager.evaluatedServerSideInAppIdsForProfile); } - (void)testEvaluateCharged { @@ -438,7 +443,7 @@ - (void)testEvaluationManagerCaching { CLTAP_INAPP_SUPPRESSED_META_KEY: @[@0] } ]; - [self.evaluationManager onBatchSent:batchWithHeaderAll withSuccess:YES]; + [self.evaluationManager onBatchSent:batchWithHeaderAll withSuccess:YES withQueueType:CTQueueTypeEvents]; XCTAssertEqualObjects((@[@3]), [self savedEvaluatedServerSideInAppIds]); XCTAssertEqual(1, [[self savedSuppressedClientSideInApps] count]); @@ -683,11 +688,11 @@ - (void)testOnBatchSentRemoveAll { CLTAP_INAPP_SUPPRESSED_META_KEY: @[@4, @5, @6] } ]; - [self.evaluationManager onBatchSent:batchWithHeaderAll withSuccess:NO]; + [self.evaluationManager onBatchSent:batchWithHeaderAll withSuccess:NO withQueueType:CTQueueTypeEvents]; XCTAssertEqualObjects((@[@1, @2, @3]), self.evaluationManager.evaluatedServerSideInAppIds); XCTAssertEqualObjects((@[@4, @5, @6]), self.evaluationManager.suppressedClientSideInApps); - [self.evaluationManager onBatchSent:batchWithHeaderAll withSuccess:YES]; + [self.evaluationManager onBatchSent:batchWithHeaderAll withSuccess:YES withQueueType:CTQueueTypeEvents]; XCTAssertEqualObjects((@[]), self.evaluationManager.evaluatedServerSideInAppIds); XCTAssertEqualObjects((@[]), self.evaluationManager.suppressedClientSideInApps); } @@ -708,18 +713,18 @@ - (void)testOnBatchSentRemoveElements { } ]; // If batch is not successful, do not remove elements - [self.evaluationManager onBatchSent:batchWithHeader withSuccess:NO]; + [self.evaluationManager onBatchSent:batchWithHeader withSuccess:NO withQueueType:CTQueueTypeEvents]; XCTAssertEqualObjects((@[@1, @2, @3]), self.evaluationManager.evaluatedServerSideInAppIds); XCTAssertEqualObjects((@[@4, @5, @6]), self.evaluationManager.suppressedClientSideInApps); // Remove only the first n elements in the batch - [self.evaluationManager onBatchSent:batchWithHeader withSuccess:YES]; + [self.evaluationManager onBatchSent:batchWithHeader withSuccess:YES withQueueType:CTQueueTypeEvents]; XCTAssertEqualObjects((@[@3]), self.evaluationManager.evaluatedServerSideInAppIds); XCTAssertEqualObjects((@[@5, @6]), self.evaluationManager.suppressedClientSideInApps); // Remove all elements, ensure no out of range exception // Current values are @[@3] and @[@5, @6] - [self.evaluationManager onBatchSent:batchWithHeaderAll withSuccess:YES]; + [self.evaluationManager onBatchSent:batchWithHeaderAll withSuccess:YES withQueueType:CTQueueTypeEvents]; XCTAssertEqualObjects((@[]), self.evaluationManager.evaluatedServerSideInAppIds); XCTAssertEqualObjects((@[]), self.evaluationManager.suppressedClientSideInApps); }