From 78ad9fe42d7349157ab8ef6cb13f06c10760062b Mon Sep 17 00:00:00 2001 From: Harsh Raghav Date: Wed, 7 Dec 2022 15:19:26 +0530 Subject: [PATCH 1/3] MEG changes. --- Demo/Podfile.lock | 10 +- Demo/VWO Demo/VWOManager.swift | 2 + VWO.xcodeproj/project.pbxproj | 56 ++++ VWO/MEG/CampaignGroupMapper.h | 19 ++ VWO/MEG/CampaignGroupMapper.m | 87 ++++++ VWO/MEG/CampaignUniquenessTracker.h | 22 ++ VWO/MEG/CampaignUniquenessTracker.m | 47 ++++ VWO/MEG/Group.h | 25 ++ VWO/MEG/Group.m | 256 +++++++++++++++++ VWO/MEG/MurmurHash.h | 18 ++ VWO/MEG/MurmurHash.m | 175 ++++++++++++ VWO/MEG/MutuallyExclusiveGroups.h | 33 +++ VWO/MEG/MutuallyExclusiveGroups.m | 423 ++++++++++++++++++++++++++++ VWO/Models/VWOCampaign.h | 3 + VWO/Models/VWOCampaign.m | 66 +++-- VWO/Models/VWOGroup.h | 22 ++ VWO/Models/VWOGroup.m | 26 ++ VWO/VWO.h | 1 + VWO/VWO.m | 39 +++ VWO/VWOController.h | 3 + VWO/VWOController.m | 11 +- 21 files changed, 1319 insertions(+), 25 deletions(-) create mode 100644 VWO/MEG/CampaignGroupMapper.h create mode 100644 VWO/MEG/CampaignGroupMapper.m create mode 100644 VWO/MEG/CampaignUniquenessTracker.h create mode 100644 VWO/MEG/CampaignUniquenessTracker.m create mode 100644 VWO/MEG/Group.h create mode 100644 VWO/MEG/Group.m create mode 100644 VWO/MEG/MurmurHash.h create mode 100644 VWO/MEG/MurmurHash.m create mode 100644 VWO/MEG/MutuallyExclusiveGroups.h create mode 100644 VWO/MEG/MutuallyExclusiveGroups.m create mode 100644 VWO/Models/VWOGroup.h create mode 100644 VWO/Models/VWOGroup.m diff --git a/Demo/Podfile.lock b/Demo/Podfile.lock index 28c46868..2f355b14 100644 --- a/Demo/Podfile.lock +++ b/Demo/Podfile.lock @@ -4,9 +4,9 @@ PODS: - Socket.IO-Client-Swift (15.2.0): - Starscream (~> 3.1) - Starscream (3.1.1) - - VWO (2.8.0): - - VWO/All (= 2.8.0) - - VWO/All (2.8.0): + - VWO (2.11.0): + - VWO/All (= 2.11.0) + - VWO/All (2.11.0): - Socket.IO-Client-Swift (~> 15.2.0) DEPENDENCIES: @@ -30,8 +30,8 @@ SPEC CHECKSUMS: SCLAlertView: 6a77bb2edfc65e04dbe57725546cb4107a506b85 Socket.IO-Client-Swift: 1e3e3a1f09f3312a167f0d781eb2f383d477357c Starscream: 4bb2f9942274833f7b4d296a55504dcfc7edb7b0 - VWO: c12710457497bc88cdca575bd3151e246ac5a57c + VWO: cd8207460a018f8d38a5425e7c13e3b76fc63034 PODFILE CHECKSUM: 71bfa555223ee2b1ddab35d14fef462b5e6267e6 -COCOAPODS: 1.10.2 +COCOAPODS: 1.11.3 diff --git a/Demo/VWO Demo/VWOManager.swift b/Demo/VWO Demo/VWOManager.swift index 627685c8..f2eb6438 100644 --- a/Demo/VWO Demo/VWOManager.swift +++ b/Demo/VWO Demo/VWOManager.swift @@ -29,6 +29,8 @@ class VWOManager { // VWO.pushCustomDimension(customDimensionKey: "userId", customDimensionValue: "userName") hud.hide(animated: false) SCLAlertView().showSuccess("Success", subTitle: "VWO launched successfully \(apiKey)") + + } }, failure: { (errorString) in DispatchQueue.main.async { diff --git a/VWO.xcodeproj/project.pbxproj b/VWO.xcodeproj/project.pbxproj index f945e622..1b1bdd9a 100644 --- a/VWO.xcodeproj/project.pbxproj +++ b/VWO.xcodeproj/project.pbxproj @@ -8,6 +8,18 @@ /* Begin PBXBuildFile section */ 32C4822427A421CE007128E3 /* SocketIO in Frameworks */ = {isa = PBXBuildFile; productRef = 32C4822327A421CE007128E3 /* SocketIO */; }; + 7B165B05293756EE0060EEE2 /* MutuallyExclusiveGroups.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B165B03293756EE0060EEE2 /* MutuallyExclusiveGroups.h */; }; + 7B165B06293756EE0060EEE2 /* MutuallyExclusiveGroups.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B165B04293756EE0060EEE2 /* MutuallyExclusiveGroups.m */; }; + 7B165B0929376B6E0060EEE2 /* Group.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B165B0729376B6E0060EEE2 /* Group.h */; }; + 7B165B0A29376B6E0060EEE2 /* Group.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B165B0829376B6E0060EEE2 /* Group.m */; }; + 7B165B0D29376B8C0060EEE2 /* CampaignUniquenessTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B165B0B29376B8C0060EEE2 /* CampaignUniquenessTracker.h */; }; + 7B165B0E29376B8C0060EEE2 /* CampaignUniquenessTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B165B0C29376B8C0060EEE2 /* CampaignUniquenessTracker.m */; }; + 7B165B1129376BA30060EEE2 /* CampaignGroupMapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B165B0F29376BA30060EEE2 /* CampaignGroupMapper.h */; }; + 7B165B1229376BA30060EEE2 /* CampaignGroupMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B165B1029376BA30060EEE2 /* CampaignGroupMapper.m */; }; + 7B165B1529383B1D0060EEE2 /* MurmurHash.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B165B1329383B1D0060EEE2 /* MurmurHash.h */; }; + 7B165B1629383B1D0060EEE2 /* MurmurHash.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B165B1429383B1D0060EEE2 /* MurmurHash.m */; }; + 7B6B7E09294094AA00961493 /* VWOGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B6B7E07294094AA00961493 /* VWOGroup.h */; }; + 7B6B7E0A294094AA00961493 /* VWOGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B6B7E08294094AA00961493 /* VWOGroup.m */; }; E31714C71F838C2F0036CF07 /* VWOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E31714C61F838C2F0036CF07 /* VWOTests.swift */; }; E32079C120075516005CB5CD /* CampaignIDMissing.json in Resources */ = {isa = PBXBuildFile; fileRef = E32079C020075516005CB5CD /* CampaignIDMissing.json */; }; E32079C320075541005CB5CD /* CampaignStatusMissing.json in Resources */ = {isa = PBXBuildFile; fileRef = E32079C220075541005CB5CD /* CampaignStatusMissing.json */; }; @@ -136,6 +148,18 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 7B165B03293756EE0060EEE2 /* MutuallyExclusiveGroups.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MutuallyExclusiveGroups.h; sourceTree = ""; }; + 7B165B04293756EE0060EEE2 /* MutuallyExclusiveGroups.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MutuallyExclusiveGroups.m; sourceTree = ""; }; + 7B165B0729376B6E0060EEE2 /* Group.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Group.h; sourceTree = ""; }; + 7B165B0829376B6E0060EEE2 /* Group.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Group.m; sourceTree = ""; }; + 7B165B0B29376B8C0060EEE2 /* CampaignUniquenessTracker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CampaignUniquenessTracker.h; sourceTree = ""; }; + 7B165B0C29376B8C0060EEE2 /* CampaignUniquenessTracker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CampaignUniquenessTracker.m; sourceTree = ""; }; + 7B165B0F29376BA30060EEE2 /* CampaignGroupMapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CampaignGroupMapper.h; sourceTree = ""; }; + 7B165B1029376BA30060EEE2 /* CampaignGroupMapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CampaignGroupMapper.m; sourceTree = ""; }; + 7B165B1329383B1D0060EEE2 /* MurmurHash.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MurmurHash.h; sourceTree = ""; }; + 7B165B1429383B1D0060EEE2 /* MurmurHash.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MurmurHash.m; sourceTree = ""; }; + 7B6B7E07294094AA00961493 /* VWOGroup.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VWOGroup.h; sourceTree = ""; }; + 7B6B7E08294094AA00961493 /* VWOGroup.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VWOGroup.m; sourceTree = ""; }; E31714C51F838C2F0036CF07 /* VWOTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "VWOTests-Bridging-Header.h"; sourceTree = ""; }; E31714C61F838C2F0036CF07 /* VWOTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VWOTests.swift; sourceTree = ""; }; E319151E1F67F1B900FC1695 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; @@ -267,6 +291,23 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 7B165AF82936AEA70060EEE2 /* MEG */ = { + isa = PBXGroup; + children = ( + 7B165B03293756EE0060EEE2 /* MutuallyExclusiveGroups.h */, + 7B165B04293756EE0060EEE2 /* MutuallyExclusiveGroups.m */, + 7B165B0729376B6E0060EEE2 /* Group.h */, + 7B165B0829376B6E0060EEE2 /* Group.m */, + 7B165B0B29376B8C0060EEE2 /* CampaignUniquenessTracker.h */, + 7B165B0C29376B8C0060EEE2 /* CampaignUniquenessTracker.m */, + 7B165B0F29376BA30060EEE2 /* CampaignGroupMapper.h */, + 7B165B1029376BA30060EEE2 /* CampaignGroupMapper.m */, + 7B165B1329383B1D0060EEE2 /* MurmurHash.h */, + 7B165B1429383B1D0060EEE2 /* MurmurHash.m */, + ); + path = MEG; + sourceTree = ""; + }; E3529571206B704600C90716 /* Socket */ = { isa = PBXGroup; children = ( @@ -314,6 +355,7 @@ E39814D81F6288C80035D519 /* VWO */ = { isa = PBXGroup; children = ( + 7B165AF82936AEA70060EEE2 /* MEG */, E39814D91F6288C80035D519 /* VWO.h */, E36AC7181F75614E0041ACDF /* VWO.m */, E39815731F6291960035D519 /* VWOController.h */, @@ -382,6 +424,8 @@ E398154C1F6291680035D519 /* VWOVariation.m */, E3F7CBB22004C91400CC8C03 /* VWOSegment.h */, E3F7CBB32004C91400CC8C03 /* VWOSegment.m */, + 7B6B7E07294094AA00961493 /* VWOGroup.h */, + 7B6B7E08294094AA00961493 /* VWOGroup.m */, ); path = Models; sourceTree = ""; @@ -493,21 +537,26 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 7B6B7E09294094AA00961493 /* VWOGroup.h in Headers */, E39815851F6291970035D519 /* VWOLogger.h in Headers */, E39815591F6291680035D519 /* VWOGoal.h in Headers */, E3871A1D212FE27C0033D86B /* NSString+MD5.h in Headers */, E3D29FCA1FBEDE2A00A11C61 /* NSString+VWO.h in Headers */, E3D29FC61FBEDAB300A11C61 /* NSDate+VWO.h in Headers */, E39815811F6291970035D519 /* VWODevice.h in Headers */, + 7B165B1529383B1D0060EEE2 /* MurmurHash.h in Headers */, E398155B1F6291680035D519 /* VWOQueue.h in Headers */, E3F7CBAC2004A9B100CC8C03 /* VWOStack.h in Headers */, E388B6CE1F8F7D350023E11E /* VWOURLQueue.h in Headers */, E344B2F01F87906900A4EEE6 /* VWOUserDefaults.h in Headers */, E39815571F6291680035D519 /* VWOCampaign.h in Headers */, E39815831F6291970035D519 /* VWOFile.h in Headers */, + 7B165B0929376B6E0060EEE2 /* Group.h in Headers */, + 7B165B05293756EE0060EEE2 /* MutuallyExclusiveGroups.h in Headers */, E35484F51F713C2E00E14D2E /* NSURLSession+Synchronous.h in Headers */, E3529574206B720A00C90716 /* VWOSocketConnector.h in Headers */, E3E95E8D206D0B13006DCE6C /* VWOConfig.h in Headers */, + 7B165B1129376BA30060EEE2 /* CampaignGroupMapper.h in Headers */, E398155F1F6291680035D519 /* VWOVariation.h in Headers */, E39567A3206E5DA80030B30B /* VWOCampaignFetcher.h in Headers */, E3BBA3602068FA630046B2F2 /* VWOSocket.h in Headers */, @@ -516,6 +565,7 @@ E3D5F7FD1F6BD86C0011C43C /* VWOURL.h in Headers */, E3F7CBB42004C91400CC8C03 /* VWOSegment.h in Headers */, E398159C1F6293BC0035D519 /* NSDictionary+VWO.h in Headers */, + 7B165B0D29376B8C0060EEE2 /* CampaignUniquenessTracker.h in Headers */, E3F7CBA82004A07800CC8C03 /* VWOInfixEvaluator.h in Headers */, E39814E71F6288C80035D519 /* VWO.h in Headers */, ); @@ -659,6 +709,8 @@ buildActionMask = 2147483647; files = ( E344B2F11F87906900A4EEE6 /* VWOUserDefaults.m in Sources */, + 7B165B1629383B1D0060EEE2 /* MurmurHash.m in Sources */, + 7B165B06293756EE0060EEE2 /* MutuallyExclusiveGroups.m in Sources */, E3D29FC71FBEDAB300A11C61 /* NSDate+VWO.m in Sources */, E3871A1E212FE27C0033D86B /* NSString+MD5.m in Sources */, E36AC7191F75614E0041ACDF /* VWO.m in Sources */, @@ -666,15 +718,19 @@ E39567A4206E5DA80030B30B /* VWOCampaignFetcher.m in Sources */, E3BBA3612068FA630046B2F2 /* VWOSocket.m in Sources */, E39815941F6291C10035D519 /* VWOSegmentEvaluator.m in Sources */, + 7B6B7E0A294094AA00961493 /* VWOGroup.m in Sources */, + 7B165B0A29376B6E0060EEE2 /* Group.m in Sources */, E3E95E8E206D0B13006DCE6C /* VWOConfig.m in Sources */, E35484F61F713C2E00E14D2E /* NSURLSession+Synchronous.m in Sources */, E398159D1F6293BC0035D519 /* NSDictionary+VWO.m in Sources */, E39815861F6291970035D519 /* VWOLogger.m in Sources */, + 7B165B0E29376B8C0060EEE2 /* CampaignUniquenessTracker.m in Sources */, E39815821F6291970035D519 /* VWODevice.m in Sources */, E398155A1F6291680035D519 /* VWOGoal.m in Sources */, E3529575206B720A00C90716 /* VWOSocketConnector.m in Sources */, E3D5F7FE1F6BD86C0011C43C /* VWOURL.m in Sources */, E39815801F6291970035D519 /* VWOController.m in Sources */, + 7B165B1229376BA30060EEE2 /* CampaignGroupMapper.m in Sources */, E398155C1F6291680035D519 /* VWOQueue.m in Sources */, E3F7CBA92004A07800CC8C03 /* VWOInfixEvaluator.m in Sources */, E39815601F6291680035D519 /* VWOVariation.m in Sources */, diff --git a/VWO/MEG/CampaignGroupMapper.h b/VWO/MEG/CampaignGroupMapper.h new file mode 100644 index 00000000..1c628423 --- /dev/null +++ b/VWO/MEG/CampaignGroupMapper.h @@ -0,0 +1,19 @@ +// +// CampaignGroupMapper.h +// VWO +// +// Created by Harsh Raghav on 30/11/22. +// Copyright © 2022 vwo. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface CampaignGroupMapper : NSObject + ++ (NSDictionary *)getCampaignGroups: (NSDictionary *)jsonObject; ++ (NSDictionary *)createAndGetGroups: (NSDictionary *)jsonObject; +@end + +NS_ASSUME_NONNULL_END diff --git a/VWO/MEG/CampaignGroupMapper.m b/VWO/MEG/CampaignGroupMapper.m new file mode 100644 index 00000000..63f68bd9 --- /dev/null +++ b/VWO/MEG/CampaignGroupMapper.m @@ -0,0 +1,87 @@ +// +// CampaignGroupMapper.m +// VWO +// +// Created by Harsh Raghav on 30/11/22. +// Copyright © 2022 vwo. All rights reserved. +// + +#import "CampaignGroupMapper.h" +#import "VWOLogger.h" + +#import "Group.h" + +@implementation CampaignGroupMapper + +static NSString * const KEY_CAMPAIGN_GROUPS = @"campaignGroups"; +static NSString * const KEY_GROUPS = @"groups"; +static NSString * const KEY_NAME = @"name"; +static NSString * const KEY_CAMPAIGNS = @"campaigns"; + +float m = 1.0; + +//months, + ++ (NSDictionary *)getCampaignGroups: (NSDictionary *)jsonObject{ + + NSDictionary* jsonCampaignGroups = nil; + @try { + jsonCampaignGroups = jsonObject[KEY_CAMPAIGN_GROUPS]; + } + @catch (NSException *exception) { + //VWOLogError(NSString *format, ...) + //VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); + } + return jsonCampaignGroups; +} + + ++ (NSDictionary *)createAndGetGroups: (NSDictionary *)jsonObject{ + NSMutableDictionary *groups = [NSMutableDictionary new]; + @try{ + NSDictionary *jsonGroups = [self getGroups:jsonObject]; + + if(jsonGroups == nil) return groups; + + NSArray *itrJsonGroups = [jsonGroups allKeys]; + int index=0; + while (index < itrJsonGroups.count) { + NSString *key = itrJsonGroups[index]; + + NSDictionary *objGroup = jsonGroups[key]; + NSArray *arrCampaigns = objGroup[KEY_CAMPAIGNS]; + + NSString *groupName = objGroup[KEY_NAME]; + + Group *group = [[Group alloc]init]; + [group setName: groupName]; + [group setId: key.intValue]; + + for (int i = 0; i < arrCampaigns.count; i++) { + [group addCampaign:arrCampaigns[i]]; + } + + [groups setObject:group forKey:groupName]; + index++; + } + } + + @catch (NSException *exception) { + // VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); + } + return groups; +} + ++ (NSDictionary *)getGroups: (NSDictionary *)jsonObject{ + NSDictionary *jsonGroups = nil; + + @try { + jsonGroups = jsonObject[KEY_GROUPS]; + } + @catch (NSException *exception) { + // VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); + } + return jsonGroups; +} + +@end diff --git a/VWO/MEG/CampaignUniquenessTracker.h b/VWO/MEG/CampaignUniquenessTracker.h new file mode 100644 index 00000000..530abaa8 --- /dev/null +++ b/VWO/MEG/CampaignUniquenessTracker.h @@ -0,0 +1,22 @@ +// +// CampaignUniquenessTracker.h +// VWO +// +// Created by Harsh Raghav on 30/11/22. +// Copyright © 2022 vwo. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface CampaignUniquenessTracker : NSObject { + +} + +- (BOOL)groupContainsCampaign:(NSString *) campaign; +- (NSString *)getNameOfGroupFor:(NSString *) campaign; +- (void)addCampaignAsRegistered:(NSString *) campaign group:(NSString *) group; +@end + +NS_ASSUME_NONNULL_END diff --git a/VWO/MEG/CampaignUniquenessTracker.m b/VWO/MEG/CampaignUniquenessTracker.m new file mode 100644 index 00000000..5e34a33a --- /dev/null +++ b/VWO/MEG/CampaignUniquenessTracker.m @@ -0,0 +1,47 @@ +// +// CampaignUniquenessTracker.m +// VWO +// +// Created by Harsh Raghav on 30/11/22. +// Copyright © 2022 vwo. All rights reserved. +// + +#import "CampaignUniquenessTracker.h" + +@implementation CampaignUniquenessTracker + +-(id) init +{ + self = [super init]; + if(self) + { + //do something + } + return self; +} + +static NSMutableDictionary * CAMPAIGNS; + +- (BOOL)groupContainsCampaign:(NSString *) campaign{ + + if (CAMPAIGNS == nil) { + CAMPAIGNS = [NSMutableDictionary new]; + } + return (CAMPAIGNS[campaign] != nil); +} + +- (NSString *)getNameOfGroupFor:(NSString *) campaign{ + if (CAMPAIGNS == nil) { + CAMPAIGNS = [NSMutableDictionary new]; + } + return CAMPAIGNS[campaign]; +} +- (void)addCampaignAsRegistered:(NSString *) campaign group:(NSString *) group +{ + if (CAMPAIGNS == nil) { + CAMPAIGNS = [NSMutableDictionary new]; + } + [CAMPAIGNS setObject:campaign forKey:group]; +} + +@end diff --git a/VWO/MEG/Group.h b/VWO/MEG/Group.h new file mode 100644 index 00000000..bd58153c --- /dev/null +++ b/VWO/MEG/Group.h @@ -0,0 +1,25 @@ +// +// Group.h +// VWO +// +// Created by Harsh Raghav on 30/11/22. +// Copyright © 2022 vwo. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface Group : NSObject + +- (NSString *) getCampaignForRespectiveWeight: (NSNumber *) weight; +- (int) getId; + +- (NSString *) getNameOnlyIfPresent: (NSString *) toSearch; +- (NSString *) getOnlyIfPresent: (NSString *) toSearch; +- (void) addCampaign: (NSString *) campaign; +- (void) setName: (NSString *) Name; +- (void) setId: (int) Id; +@end + +NS_ASSUME_NONNULL_END diff --git a/VWO/MEG/Group.m b/VWO/MEG/Group.m new file mode 100644 index 00000000..5fccd430 --- /dev/null +++ b/VWO/MEG/Group.m @@ -0,0 +1,256 @@ +// +// Group.m +// VWO +// +// Created by Harsh Raghav on 30/11/22. +// Copyright © 2022 vwo. All rights reserved. +// + +#import "Group.h" +#import "CampaignUniquenessTracker.h" +#import "MutuallyExclusiveGroups.h" + +@implementation Group +static int ID = INT_MIN; +/** + + * Name of the group + + */ + +NSString *name = nil; +/** + + * The list of campaigns assigned for this group. + + */ + +NSMutableArray *campaignList; +/** + + * A simple key value based mechanism to check where our weight belongs to. + + */ + +NSMutableDictionary *weightMap = nil; +NSNumber *weight; + +- (int)getId { + + return ID; + +} + +- (NSString *) getName { + + return name; + +} + +- (void) setName: (NSString *) Name { + + name = Name; + +} + +- (void) setId: (int) Id { + + ID = Id; + +} + ++ (NSUInteger) getCampaignSize { + + if(campaignList == nil){ + + campaignList = [NSMutableArray new]; + + } + + return campaignList.count; + +} + ++ (NSMutableArray *) getCampaigns { + + if(campaignList == nil){ + + campaignList = [NSMutableArray new]; + + } + + return campaignList; + +} + + + +- (void) calculateWeight { + + float total = 100; // because 100% + + NSUInteger totalCampaigns = campaignList.count; + + weight = @(total/totalCampaigns); + +} + + + +- (void) addCampaign: (NSString *) campaign { + CampaignUniquenessTracker *campaignUniquenessTracker = [[CampaignUniquenessTracker alloc] init]; + if([campaignUniquenessTracker groupContainsCampaign:campaign]) { + [MutuallyExclusiveGroups log: [NSString stringWithFormat:@"%s/%@/%s/%@/%s/%@/%s", "addCampaign: could not add campaign [ ", campaign, " ] to group [ ", [self getName]," ] because it already belongs to group [ ", [campaignUniquenessTracker getNameOfGroupFor:campaign]," ]"]]; + + return; + + } + + [campaignUniquenessTracker addCampaignAsRegistered:campaign group:[self getName]]; + + if(campaignList == nil){ + + campaignList = [NSMutableArray new]; + + } + + [campaignList addObject:campaign]; + + [self calculateWeight]; + +} + +- (void) removeCampaign: (NSString *) campaign{ + + NSMutableArray *campaigns = [NSMutableArray new]; + + if (campaignList == nil) return; + + if (campaignList.count== 0) return; + + for (int i = 0; i < campaignList.count; i++) { + + NSString *value = campaignList[i]; + + if (value == campaign) continue; + + [campaigns addObject:value]; + + } + + [campaignList removeAllObjects]; + + [campaignList addObjectsFromArray:(campaigns)]; + + [self calculateWeight]; + +} + ++ (NSNumber *) getWeight { + return weight; +} + +- (NSString *) getOnlyIfPresent: (NSString *) toSearch{ + if (campaignList == nil) return nil; + + if (campaignList.count== 0) return nil; + + for (NSString *campaignId in campaignList) { + + if (toSearch == campaignId) return [NSString stringWithFormat:@"%d",ID]; + + } + + return nil; + +} + + + +- (NSString *) getNameOnlyIfPresent: (NSString *) toSearch{ + + if (campaignList == nil) return nil; + + + + if (campaignList.count== 0) return nil; + + for (NSString *campaignId in campaignList) { + + if (toSearch == campaignId) return name; + + } + + return nil; + +} + + + +- (NSString *) getCampaignForRespectiveWeight: (NSNumber *) weight{ + + [self createWeightMap]; + + NSArray *allKeys = [weightMap allKeys]; + + for(NSString *key in allKeys) { + + NSMutableArray *weightMaxMin = weightMap[key]; + + if (weightMaxMin == nil) continue; + + BOOL weightIsGreaterThanMin = (weight > weightMaxMin[0]); + + BOOL weightIsLessThanMax = (weight <= weightMaxMin[1]); + + if(weightIsGreaterThanMin && weightIsLessThanMax) { + + [MutuallyExclusiveGroups log:[NSString stringWithFormat:@"%s/%@/%s/%@/%s/%@/%s", "campaign [ " ,key ," ] found for the given weight [ " ,weight ," ] in group [ " ,[self getNameOnlyIfPresent: key] ," ]"]]; + + return key; + + } + + } + + return nil; + +} + + + +- (void) createWeightMap { + + if (weightMap == nil) { + + weightMap = [NSMutableDictionary new]; + + } + + + + NSNumber *weightBinValue = 0; + + + + // A 0 - 33.33 , B 33.33 - 66.33 , C 66.333, 100.0 + + for (int i = 0; i < campaignList.count; i++) { + + NSMutableArray *range = [NSMutableArray new]; + + [range addObject:weightBinValue]; + + weightBinValue = @(weightBinValue.intValue + weight.intValue); + + [range addObject:weightBinValue]; + + [weightMap setObject: range forKey: campaignList[i]]; + + } + +} + + + +@end diff --git a/VWO/MEG/MurmurHash.h b/VWO/MEG/MurmurHash.h new file mode 100644 index 00000000..4a488597 --- /dev/null +++ b/VWO/MEG/MurmurHash.h @@ -0,0 +1,18 @@ +// +// MurmurHash.h +// VWO +// +// Created by Harsh Raghav on 01/12/22. +// Copyright © 2022 vwo. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MurmurHash : NSObject + ++(int)hash32:(NSString *) text; +@end + +NS_ASSUME_NONNULL_END diff --git a/VWO/MEG/MurmurHash.m b/VWO/MEG/MurmurHash.m new file mode 100644 index 00000000..c065e55e --- /dev/null +++ b/VWO/MEG/MurmurHash.m @@ -0,0 +1,175 @@ +// +// MurmurHash.m +// VWO +// +// Created by Harsh Raghav on 01/12/22. +// Copyright © 2022 vwo. All rights reserved. +// + +#import "MurmurHash.h" + +@implementation MurmurHash + + /** Generates 32 bit hash from byte array of the given length and + * seed. + * + * @param data byte array to hash + * @param length length of the array to hash + * @param seed initial seed value + * @return 32 bit hash of the given array + */ ++ (int)hash32:(NSArray *)data length:(int)length seed:(int)seed{ + // 'm' and 'r' are mixing constants generated offline. + // They're not really 'magic', they just happen to work well. + const int m = 0x5bd1e995; + const int r = 24; + // Initialize the hash to a random value + int h = seed^length; + int length4 = length/4; + + for(int i=0; i> r; + k *= m; + h *= m; + h ^= k; + } + + // Handle the last few bytes of the input array + switch (length%4) { + case 3: h ^= [[data objectAtIndex:(length&~3)+2] intValue]&0xff<<16; + case 2: h ^= [[data objectAtIndex:(length&~3)+1] intValue]&0xff<<8; + case 1: h ^= [[data objectAtIndex:(length&~3)] intValue]&0xff; + h *= m; + } + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; +} + +/** Generates 32 bit hash from byte array with default seed value. + * + * @param data byte array to hash + * @param length length of the array to hash + * @return 32 bit hash of the given array + */ + ++ (int)hash32:(NSArray *)data length:(int)length{ + return [self hash32:data length:length seed:0x9747b28c]; +} + +/** Generates 32 bit hash from a string. + * + * @param text string to hash + * @return 32 bit hash of the given string + */ ++(int)hash32:(NSString *) text{ + NSArray* arrayOfStrings = [text componentsSeparatedByString:@""]; + return [self hash32:arrayOfStrings length:(int)[text length]]; +} + +/** Generates 32 bit hash from a substring. + * + * @param text string to hash + * @param from starting index + * @param length length of the substring to hash + * @return 32 bit hash of the given string + */ ++(int)hash32:(NSString *)text from:(int)from length:(int)length{ + return [self hash32:[text substringFromIndex:from]]; +} + +/** Generates 64 bit hash from byte array of the given length and seed. + * + * @param data byte array to hash + * @param length length of the array to hash + * @param seed initial seed value + * @return 64 bit hash of the given array + */ ++(long)hash64:(NSArray *)data length:(int)length seed:(int)seed { + const long m = 0xc6a4a7935bd1e995L; + const int r = 47; + // Initialize the hash to a random value + long h = (seed&0xffffffffl)^(length*m); + int length8 = length/8; + + for(int i=0; i> r; + k *= m; + h ^= k; + h *= m; + } + + // Handle the last few bytes of the input array + switch (length%4) { + case 7: h ^= ((uint64_t)[[data objectAtIndex:(length&~7)+6] longValue]&0xff) << 48; + case 6: h ^= ((uint64_t)[[data objectAtIndex:(length&~7)+5] longValue]&0xff) << 40; + case 5: h ^= ((uint64_t)[[data objectAtIndex:(length&~7)+4] longValue]&0xff) << 32; + case 4: h ^= [[data objectAtIndex:(length&~7)+3] longValue]&0xff << 24; + case 3: h ^= [[data objectAtIndex:(length&~7)+2] longValue]&0xff << 16; + case 2: h ^= [[data objectAtIndex:(length&~7)+1] longValue]&0xff << 8; + case 1: h ^= [[data objectAtIndex:(length&~7)] longValue]&0xff; + h *= m; + } + + h ^= h >> r; + h *= m; + h ^= h >> r; + + return h; +} + +/** Generates 64 bit hash from byte array with default seed value. + * + * @param data byte array to hash + * @param length length of the array to hash + * @return 64 bit hash of the given string + */ ++ (int)hash64:(NSArray *)data length:(int)length{ + return (int)[self hash64:data length:length seed:0xe17a1465]; +} + +/** Generates 64 bit hash from a string. + * + * @param text string to hash + * @return 64 bit hash of the given string + */ ++(int)hash64:(NSString *) text{ + NSArray* arrayOfStrings = [text componentsSeparatedByString:@""]; + return [self hash64:arrayOfStrings length:(int)[text length]]; +} + +/** Generates 64 bit hash from a substring. + * + * @param text string to hash + * @param from starting index + * @param length length of the substring to hash + * @return 64 bit hash of the given string + */ ++(int)hash64:(NSString *)text from:(int)from length:(int)length{ + return [self hash64:[text substringFromIndex:from]]; +} + +@end diff --git a/VWO/MEG/MutuallyExclusiveGroups.h b/VWO/MEG/MutuallyExclusiveGroups.h new file mode 100644 index 00000000..687af7a2 --- /dev/null +++ b/VWO/MEG/MutuallyExclusiveGroups.h @@ -0,0 +1,33 @@ +// +// MutuallyExclusiveGroups.h +// VWO +// +// Created by Harsh Raghav on 30/11/22. +// Copyright © 2022 vwo. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MutuallyExclusiveGroups : NSObject { + +} + +- (id)initMutuallyExclusiveGroups:(NSString *)userId; +- (void)addGroups:(NSDictionary *)groupHashMap; +- (NSString *)getCampaign: (NSDictionary *)args jsonData: (NSArray *)campaignsData; +- (NSString *)calculateTheWinnerCampaign: (NSDictionary *)args jsonData: (NSArray *)campaignsData; +- (NSString *)getCampaignIdFromTestKey: (NSString *)testKey campaignsData: (NSArray *)campaignsData; +- (NSString *)getTestKeyFromCampaignId: (NSString *)campaignId campaignsData: (NSArray *)campaignsData; +- (NSString *)getCampaignFromSpecificGroup: (NSString *)groupName; +- (NSString *)getGroupNameFromGroupId: (int)groupId; +- (NSString *)getCampaignFromCampaignId: (NSString *)userId campaign: (NSString *)campaign; +- (NSString *)getCampaignIfPresent: (NSString *)campaignKey; +- (NSNumber *)getNormalizedValue: (NSNumber *)murmurHash; +- (NSNumber *)getMurMurHash: (NSString *)userId; ++ (void)log: (NSString *)message; + +@end + +NS_ASSUME_NONNULL_END diff --git a/VWO/MEG/MutuallyExclusiveGroups.m b/VWO/MEG/MutuallyExclusiveGroups.m new file mode 100644 index 00000000..6cbfc2da --- /dev/null +++ b/VWO/MEG/MutuallyExclusiveGroups.m @@ -0,0 +1,423 @@ +// +// MutuallyExclusiveGroups.m +// VWO +// +// Created by Harsh Raghav on 30/11/22. +// Copyright © 2022 vwo. All rights reserved. +// + +#import "MutuallyExclusiveGroups.h" +#import "MurmurHash.h" + +#include +#import "Group.h" + +@implementation MutuallyExclusiveGroups + +static const BOOL IS_LOGS_SHOWN = true; + +static const NSString *TYPE_VISUAL_AB = @"VISUAL_AB"; + +static const NSString *ID_GROUP = @"groupId"; + +static const NSString *ID_CAMPAIGN = @"campaignKey"; + +static NSString * const CAMPAIGN_TEST_KEY = @"test_key"; + +static NSString * const CAMPAIGN_TYPE = @"type"; + +static NSString * const CAMPAIGN_ID = @"id"; + +static const NSString *TAG = @"MutuallyExclusiveGroups"; + +static NSString *userId; + +NSMutableDictionary *CAMPAIGN_GROUPS; + +NSMutableDictionary *USER_CAMPAIGN; + +- (id)initMutuallyExclusiveGroups:(NSString *)UserId + +{ + self = [super init]; + + if (self) { + userId = UserId; + } + + return self; +} + +- (void)addGroups:(NSDictionary *)groupHashMap{ + + if (CAMPAIGN_GROUPS == nil) { + CAMPAIGN_GROUPS = [NSMutableDictionary new]; + } + + [CAMPAIGN_GROUPS removeAllObjects]; + [CAMPAIGN_GROUPS addEntriesFromDictionary:groupHashMap]; +} + +- (NSString *)getCampaign: (NSDictionary *)args jsonData: (NSArray *)campaignsData{ + return [self calculateTheWinnerCampaign:args jsonData: campaignsData]; +} + +- (NSString *)calculateTheWinnerCampaign: (NSDictionary *)args jsonData: (NSArray *)campaignsData{ + if (args == nil) { + return nil; + } + + NSString *groupId = args[ID_GROUP]; + + NSString *testKey = args[CAMPAIGN_TEST_KEY]; + + NSString *campaignId = [self getCampaignIdFromTestKey: testKey campaignsData : campaignsData]; + + if(groupId == nil && campaignId == nil) { + + // there must be at least one type of id + // either GROUP or CAMPAIGN + + //VWOLog.w(VWOLog.MEG_LOGS, "The groupId and campaignId ; both are null.", false); + + return nil; + } + + NSString *campaign; + + NSString *TestKey; + + NSString *groupName; + + BOOL groupIdIsNotPresentInArgs = (groupId == nil || groupId.length == 0); + + if(groupIdIsNotPresentInArgs) { + + //VWOLog.i(VWOLog.MEG_LOGS, ID_GROUP + " was not found in the mapping so just picking the specific campaign [ " + campaignId + " ]", false); + + // if there is no sign of group we can simply use the campaign matching logic + + campaign = [self getCampaignFromCampaignId: userId campaign: campaignId]; + + //VWOLog.i(VWOLog.MEG_LOGS, "Campaign selected from the mutually exclusive group is [ " + campaign + " ]", false); + + TestKey = [self getTestKeyFromCampaignId: campaign campaignsData:campaignsData]; + + // VWOLog.i(VWOLog.MEG_LOGS, "Test-key of the campaign selected from the mutually exclusive group is [ " + TestKey + " ]", false); + + + + return TestKey; + + } + + // VWOLog.d(VWOLog.MEG_LOGS, "Because there was groupId present, we are going to prioritize it and get a campaign from that group", false); + + @try{ + + groupName = [self getGroupNameFromGroupId: groupId.intValue]; + + } + + @catch(NSException *exception) { + + // VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); + + return nil; + + } + + + + campaign = [self getCampaignFromSpecificGroup:groupName]; + + // VWOLog.i(VWOLog.MEG_LOGS, "Selected campaign from [ " + groupName + " ] is [ " + campaign + " ]", false); + + TestKey = [self getTestKeyFromCampaignId:campaign campaignsData:campaignsData]; + + // VWOLog.i(VWOLog.MEG_LOGS, "Test-key of the campaign selected from the mutually exclusive group is [ " + TestKey + " ]", false); + + return TestKey; + +} + + + +- (NSString *)getCampaignIdFromTestKey: (NSString *)testKey campaignsData: (NSArray *)campaignsData{ + + + + if (campaignsData == nil) return nil; + + + + if (campaignsData.count== 0) return nil; + + + + for (int i = 0; i < campaignsData.count; i++) { + + @try { + + NSDictionary *groupDataItem = campaignsData[i]; + + if(groupDataItem[CAMPAIGN_TYPE] == TYPE_VISUAL_AB) { + + if(groupDataItem[CAMPAIGN_TEST_KEY] == testKey){ + + return groupDataItem[CAMPAIGN_ID]; + + } + + } + + } + + @catch(NSException *exception) { + + // VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); + + } + + } + + return nil; + +} + + + +- (NSString *)getTestKeyFromCampaignId: (NSString *)campaignId campaignsData: (NSArray *)campaignsData{ + + + + if (campaignsData == nil) return nil; + + + + if (campaignsData.count== 0) return nil; + + + + for (int i = 0; i < campaignsData.count; i++) { + + @try { + + NSDictionary *groupDataItem = campaignsData[i]; + + if(groupDataItem[CAMPAIGN_TYPE] == TYPE_VISUAL_AB) { + + if(groupDataItem[CAMPAIGN_ID] == campaignId){ + + return groupDataItem[CAMPAIGN_TEST_KEY]; + + } + + } + + } + + @catch(NSException *exception) { + + // VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); + + } + + + + } + + return nil; + +} + + + +- (NSString *)getCampaignFromSpecificGroup: (NSString *)groupName{ + + + + if (groupName == nil) { + + // this should never happen unless the id of the group that doesn't exist is passed + + return nil; + + } + + + + NSNumber *murmurHash = [self getMurMurHash:userId]; + + // If the campaign-user mapping is present in the App storage, get the decision from there. Otherwise, go to the next step + + NSString *murmurHashString = [NSString stringWithFormat: @"%@", murmurHash]; + + if ([USER_CAMPAIGN objectForKey: murmurHashString]) return USER_CAMPAIGN[murmurHashString]; + + NSNumber *normalizedValue = [self getNormalizedValue:murmurHash]; + + // VWOLog.d(VWOLog.MEG_LOGS, "Normalized value for user with userID -> " + userId + " is [ " + normalizedValue + " ] ", false); + + Group *interestedGroup = CAMPAIGN_GROUPS[groupName]; + + if (interestedGroup == nil) return nil; + + return [interestedGroup getCampaignForRespectiveWeight:normalizedValue]; + +} + +-(NSString *)getGroupNameFromGroupId: (int)groupId{ + + NSArray *allKeys = [CAMPAIGN_GROUPS allKeys]; + + for(NSString *key in allKeys){ + Group *group = CAMPAIGN_GROUPS[key]; + + if(group == nil)return nil; + + if(groupId == [group getId]){ + //we found the group we have been searching for + return key; + } + } + return nil; +} + +- (NSString *)getCampaignFromCampaignId: (NSString *)userId campaign: (NSString *)campaign{ + + NSString *campaignFoundInGroup = [self getCampaignIfPresent:campaign]; + + if (campaignFoundInGroup == nil) { + + // VWOLog.i(VWOLog.MEG_LOGS, "The campaign key [ " + campaign + " ] is not present in any of the mutually exclusive groups.", false); + + return campaign; + + } + + else { + + // VWOLog.i(VWOLog.MEG_LOGS, "Found campaign [ " + campaign + " ] in mutually exclusive group [ " + campaignFoundInGroup + " ] ", false); + + } + + // Generate a random number/murmurhash corresponding to the User ID + + NSNumber *murmurHash = [self getMurMurHash:userId]; + + // If the campaign-user mapping is present in the App storage, get the decision from there. Otherwise, go to the next step + + NSString *murmurHashString = [NSString stringWithFormat: @"%@", murmurHash]; + + if ([USER_CAMPAIGN objectForKey: murmurHashString]) return USER_CAMPAIGN[murmurHashString]; + + NSNumber *normalizedValue = [self getNormalizedValue:murmurHash]; + + // VWOLog.d(VWOLog.MEG_LOGS, "Normalized value for user with userID -> " + userId + " is [ " + normalizedValue + " ] ", false); + + + + // this group has our campaign + + Group *interestedGroup = CAMPAIGN_GROUPS[campaignFoundInGroup]; + + + + if (interestedGroup == nil) + + return nil; // basic null check because NSDictionary is being used + + + + NSString *finalCampaign = [interestedGroup getCampaignForRespectiveWeight: normalizedValue]; + + + + if (campaign == finalCampaign) { + + return finalCampaign; + + } + + else { + + // VWOLog.i(VWOLog.MEG_LOGS, "Passed campaign : " + campaign + " does not match calculated campaign " + finalCampaign, false); + + } + + + + return nil; + +} + + + +- (NSString *)getCampaignIfPresent: (NSString *)campaignKey{ + + NSArray *allKeys = [CAMPAIGN_GROUPS allKeys]; + + for (NSString *key in allKeys) { + + Group *group = CAMPAIGN_GROUPS[key]; + + if (group == nil) return nil; + + NSString *foundCampaign = [group getOnlyIfPresent:campaignKey]; + + if (foundCampaign != nil) { + + // we should return name of the group + + // the reason being we need to use the weightage of the campaigns later on + + return key; + + } + + } + + return nil; + +} + + + +- (NSNumber *)getNormalizedValue: (NSNumber *)murmurHash{ + + int max = 100; + + double ratio = murmurHash.intValue / (int) pow(2,31); + + double multipliedValue = (max * ratio) + 1; + + int value = abs((int) floor(multipliedValue)); + + return [NSNumber numberWithInt:value]; + +} + + + +- (NSNumber *)getMurMurHash: (NSString *)userId{ + int hash = abs([MurmurHash hash32:userId]); + + return [NSNumber numberWithInt:hash]; + +} + + + ++ (void)log: (NSString *)message{ + + if (IS_LOGS_SHOWN) { + + NSLog(TAG, message); + + } + +} + + +@end diff --git a/VWO/Models/VWOCampaign.h b/VWO/Models/VWOCampaign.h index 1ed6445d..90f87918 100644 --- a/VWO/Models/VWOCampaign.h +++ b/VWO/Models/VWOCampaign.h @@ -9,6 +9,7 @@ #import #import "VWOGoal.h" #import "VWOVariation.h" +#import "VWOGroup.h" NS_ASSUME_NONNULL_BEGIN @@ -28,6 +29,8 @@ typedef NS_ENUM(NSInteger, CampaignStatus) { @property VWOVariation *variation; @property NSArray *goals; @property (nullable) NSDictionary *segmentObject; +@property (atomic) NSString *type; +@property VWOGroup * group; - (nullable instancetype)initWithDictionary:(NSDictionary *)campaignDict; - (nullable id)variationForKey:(NSString *)key; diff --git a/VWO/Models/VWOCampaign.m b/VWO/Models/VWOCampaign.m index adf9c8fb..51666245 100644 --- a/VWO/Models/VWOCampaign.m +++ b/VWO/Models/VWOCampaign.m @@ -9,24 +9,26 @@ #import "VWOCampaign.h" #import "NSDictionary+VWO.h" #import "VWOLogger.h" - -static NSString * kId = @"id"; -static NSString * kName = @"name"; -static NSString * kTrackUserOnLaunch = @"track_user_on_launch"; -static NSString * kStatus = @"status"; -static NSString * kSegmentObject = @"segment_object"; -static NSString * kGoals = @"goals"; -static NSString * kVariation = @"variations"; -static NSString * kTestKey = @"test_key"; - +#import "VWOGroup.h" + +static NSString * kId = @"id"; +static NSString * kName = @"name"; +static NSString * kTrackUserOnLaunch = @"track_user_on_launch"; +static NSString * kStatus = @"status"; +static NSString * kSegmentObject = @"segment_object"; +static NSString * kGoals = @"goals"; +static NSString * kVariation = @"variations"; +static NSString * kTestKey = @"test_key"; +static NSString * kType = @"type"; +static NSString * CAMPAIGN_GROUPS = @"groups"; @implementation VWOCampaign - (nullable instancetype)initWithDictionary:(NSDictionary *) campaignDict { NSParameterAssert(campaignDict); // Campaign ID and status are the must have keys for any type of campaign - NSArray *mustHaveKeys = @[kId, kStatus]; - NSArray *missingKeys = [campaignDict keysMissingFrom:mustHaveKeys]; + NSArray *mustHaveKeys = @[kId, kStatus, kType]; + NSArray *missingKeys = [campaignDict keysMissingFrom:mustHaveKeys]; if (missingKeys.count > 0) { VWOLogException(@"Keys missing [%@] for Campaign JSON {%@}", [missingKeys componentsJoinedByString:@", "], @@ -38,33 +40,49 @@ - (nullable instancetype)initWithDictionary:(NSDictionary *) campaignDict { self.iD = [campaignDict[kId] intValue]; NSString *statusString = campaignDict[kStatus]; + self.type = campaignDict[kType]; if ([statusString isEqualToString:@"EXCLUDED"]) { self.status = CampaignStatusExcluded; - self.name = campaignDict[kName]; + self.name = campaignDict[kName]; return self; } + + if([statusString isEqualToString:@"PAUSED"]) { + self.status = CampaignStatusPaused; + return self; + } + + //Here Campaign can only running + NSArray *mustHaveKeys = @[kName, kTrackUserOnLaunch, kGoals, kVariation]; - NSArray *missingKeys = [campaignDict keysMissingFrom:mustHaveKeys]; + + NSArray *missingKeys = [campaignDict keysMissingFrom:mustHaveKeys]; + if (missingKeys.count > 0) { + VWOLogException(@"Keys missing [%@] for Running Campaign JSON {%@}", + [missingKeys componentsJoinedByString:@", "], + campaignDict); + return nil; + } - self.status = CampaignStatusRunning; - self.name = campaignDict[kName]; - self.testKey = campaignDict[kTestKey]; + self.status = CampaignStatusRunning; + self.name = campaignDict[kName]; + self.testKey = campaignDict[kTestKey]; self.trackUserOnLaunch = [campaignDict[kTrackUserOnLaunch] boolValue]; - self.segmentObject = campaignDict[kSegmentObject]; - + self.segmentObject = campaignDict[kSegmentObject]; + //Goals NSMutableArray*goals = [NSMutableArray new]; NSArray *goalsDict = campaignDict[kGoals]; @@ -78,6 +96,9 @@ - (nullable instancetype)initWithDictionary:(NSDictionary *) campaignDict { self.variation = [[VWOVariation alloc] initWithDictionary:campaignDict[kVariation]]; } + if([self.type isEqualToString:CAMPAIGN_GROUPS]){ + self.group = [[VWOGroup alloc] initWithDictionary:campaignDict]; + } return self; } @@ -102,6 +123,13 @@ - (nullable VWOGoal *)goalForIdentifier:(NSString *)identifier { return nil; } +-(nullable VWOGroup *)groupForMeg{ + if([self.type isEqualToString:CAMPAIGN_GROUPS]){ + return self.group; + } + return nil; +} + - (NSString *)description { return [NSString stringWithFormat:@"%@(id: %d)", self.name, self.iD]; } diff --git a/VWO/Models/VWOGroup.h b/VWO/Models/VWOGroup.h new file mode 100644 index 00000000..d7fe2e54 --- /dev/null +++ b/VWO/Models/VWOGroup.h @@ -0,0 +1,22 @@ +// +// VWOGroup.h +// VWO +// +// Created by Harsh Raghav on 07/12/22. +// Copyright © 2022 vwo. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface VWOGroup : NSObject + +@property NSDictionary * campaignGroups; +@property NSDictionary * groups; + +- (instancetype)initWithDictionary:(NSDictionary *)groupDict + +@end + +NS_ASSUME_NONNULL_END diff --git a/VWO/Models/VWOGroup.m b/VWO/Models/VWOGroup.m new file mode 100644 index 00000000..8773482a --- /dev/null +++ b/VWO/Models/VWOGroup.m @@ -0,0 +1,26 @@ +// +// VWOGroup.m +// VWO +// +// Created by Harsh Raghav on 07/12/22. +// Copyright © 2022 vwo. All rights reserved. +// + +#import "VWOGroup.h" +@implementation VWOGroup +static NSString * kGroups = @"groups"; +static NSString * kCampaignGroups = @"campaignGroups"; + +- (instancetype)initWithDictionary:(NSDictionary *)groupDict { + NSParameterAssert(groupDict); + // NSArray *missingKeys = [goalDict keysMissingFrom:@[kId, kIdentifier]]; +// if (missingKeys.count > 0) { +// VWOLogException(@"Keys missing [%@] for Goal JSON {%@}", [missingKeys componentsJoinedByString:@", "], goalDict); +// return nil; +// } + self.campaignGroups = groupDict[kCampaignGroups] ; + self.groups = groupDict[kGroups] ; + return self; +} + +@end diff --git a/VWO/VWO.h b/VWO/VWO.h index ab8b32d5..be15d035 100644 --- a/VWO/VWO.h +++ b/VWO/VWO.h @@ -89,6 +89,7 @@ __deprecated_msg("Use launchForAPIKey:config:completion:failure instead"); failure:(nullable void (^)(NSString *error))failureBlock NS_SWIFT_NAME(launch(apiKey:config:completion:failure:)); ++ (NSString *)getCampaign:(NSString *)userId args:(NSDictionary *)args; + (void)launchSynchronouslyForAPIKey:(NSString *)apiKey timeout:(NSTimeInterval)timeout NS_SWIFT_NAME(launchSynchronously(apiKey:timeout:)) diff --git a/VWO/VWO.m b/VWO/VWO.m index d82154c3..dfc9f93c 100644 --- a/VWO/VWO.m +++ b/VWO/VWO.m @@ -9,9 +9,14 @@ #import "VWO.h" #import "VWOController.h" #import "VWOLogger.h" +#import "CampaignGroupMapper.h" +#import "MutuallyExclusiveGroups.h" +#import "Group.h" static VWOLogLevel kLogLevel = VWOLogLevelError; static BOOL kOptOut = NO; +static NSString * const CAMPAIGN_TYPE = @"type"; +static NSString * const CAMPAIGN_GROUPS = @"groups"; NSMutableDictionary *customVariables; NSString * const VWOUserStartedTrackingInCampaignNotification = @"VWOUserStartedTrackingInCampaignNotification"; @@ -125,6 +130,7 @@ + (id)objectForKey:(NSString *)key testKey:(NSString *)testKey{ dispatch_barrier_sync(VWOController.taskQueue, ^{ object = [VWOController.shared variationForKey:key testKey:testKey]; }); + return object; } + (id)objectForKey:(NSString *)key defaultValue:(nullable id)defaultValue{ @@ -194,6 +200,39 @@ + (nullable NSString *)variationNameForTestKey:(NSString *)campaignTestKey { } ++ (NSString *)getCampaign:(NSString *)userId args:(NSDictionary *)args { + + if (userId == nil || [userId length]==0) { + // use the (default | random) user id + userId = [VWOController.shared getUserId]; + } + + VWOCampaignArray * campaignsData = [VWOController.shared getCampaignData]; + +// VWOCampaign *megGroupsData = [[VWOCampaign alloc] initWithDictionary:]; + + if (campaignsData != nil && campaignsData.count > 0) { + for (int i = 0; i < campaignsData.count; i++) { + @try { + VWOCampaign *groupDataItem = campaignsData[i]; +// if (groupDataItem[CAMPAIGN_TYPE] == CAMPAIGN_GROUPS) { +// megGroupsData = groupDataItem; +// break; +// } + } + @catch (NSException *exception) { + // VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); + } + } + } + +// NSDictionary *mappedData = [CampaignGroupMapper createAndGetGroups: megGroupsData]; + + MutuallyExclusiveGroups *meg = [[MutuallyExclusiveGroups alloc] initMutuallyExclusiveGroups:userId]; +// [meg addGroups:mappedData]; + return [meg getCampaign:args jsonData:campaignsData]; +} + + (void)trackConversion:(NSString *)goal { NSParameterAssert(goal); dispatch_barrier_async(VWOController.taskQueue, ^{ diff --git a/VWO/VWOController.h b/VWO/VWOController.h index 2ff96eae..3da92079 100644 --- a/VWO/VWOController.h +++ b/VWO/VWOController.h @@ -7,6 +7,7 @@ // #import +#import "VWOCampaignFetcher.h" @class VWOSegmentEvaluator, VWOUserDefaults; @@ -38,6 +39,8 @@ static NSString *kVWOSDKversion = @"2.11.0"; - (void)trackConversion:(NSString *)goal withValue:(nullable NSNumber *)value; - (nullable id)variationForKey:(NSString *)key; - (nullable id)variationForKey:(NSString *)key testKey:(NSString *)testKey; +- (VWOCampaignArray *)getCampaignData; +-(NSString *) getUserId; - (nullable NSString *)variationNameForCampaignTestKey:(NSString *)campaignTestKey; - (void)pushCustomDimension:(NSString *) customDimensionKey withCustomDimensionValue:(NSString *) customDimensionValue; diff --git a/VWO/VWOController.m b/VWO/VWOController.m index 7675bdb6..ba11f25d 100644 --- a/VWO/VWOController.m +++ b/VWO/VWOController.m @@ -17,7 +17,6 @@ #import "VWOUserDefaults.h" #import #import "VWOConfig.h" -#import "VWOCampaignFetcher.h" #import "VWOSegmentEvaluator.h" static NSTimeInterval kMessageQueueFlushInterval = 10; @@ -63,6 +62,15 @@ + (dispatch_queue_t)taskQueue { return VWOController.shared->_vwoQueue; } +- (VWOCampaignArray *)getCampaignData{ + return _campaignList; +} + +-(NSString *) getUserId{ + NSString * userId = _vwoConfig.userID; + return userId; +} + - (void)launchWithAPIKey:(NSString *)apiKey config:(VWOConfig *)configNullable withTimeout:(NSNumber *)timeout @@ -312,6 +320,7 @@ - (id)variationForKey:(NSString *)key testKey:(NSString *)testKey{ VWOLogDebug(@"Got variation %@ for key %@", finalVariation, key); return finalVariation; } + - (nullable NSString *)variationNameForCampaignTestKey:(NSString *)campaignTestKey { if (!_initialised) { VWOLogWarning(@"variationNameForCampaignTestKey(%@) called before launching VWO", campaignTestKey); From 3274b9bdbf871b1105e24589f49d14d36aa26afc Mon Sep 17 00:00:00 2001 From: Parvesh Chauhan Date: Thu, 8 Dec 2022 18:48:01 +0530 Subject: [PATCH 2/3] changes made and bug fixes --- Demo/VWO Demo/AppDelegate.swift | 1 + .../Sorting campaign/PhoneListVC.swift | 2 + VWO/MEG/CampaignGroupMapper.m | 6 +- VWO/MEG/Group.m | 2 +- VWO/MEG/MurmurHash.m | 8 ++- VWO/MEG/MutuallyExclusiveGroups.m | 59 ++++++++++++------- VWO/Models/VWOCampaign.h | 1 + VWO/Models/VWOCampaign.m | 7 +++ VWO/Models/VWOGroup.h | 2 +- VWO/VWO.m | 19 +++--- VWO/VWOCampaignFetcher.m | 7 +++ 11 files changed, 79 insertions(+), 35 deletions(-) diff --git a/Demo/VWO Demo/AppDelegate.swift b/Demo/VWO Demo/AppDelegate.swift index b450657a..6f9c4e2a 100644 --- a/Demo/VWO Demo/AppDelegate.swift +++ b/Demo/VWO Demo/AppDelegate.swift @@ -22,6 +22,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // setCurrentViewController(vc: houseNav) setCurrentViewController(vc: phoneNav) + VWOManager.launch("a594fc28d7bd8e3438d8df0bcd37f2cc-647788") return true } diff --git a/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift b/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift index 32f66cc4..2620bac2 100644 --- a/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift +++ b/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift @@ -41,6 +41,8 @@ class PhoneListVC: UIViewController { let destination = segue.destination as! PhoneDetailVC let t = tableView.indexPathForSelectedRow!.row VWO.trackConversion("productView") + let abcd = VWO.getCampaign("userId", args: ["test_key":"37","groupId":"1"]) + print("mutual exp ...... \(abcd)") destination.phone = phoneList[t] } diff --git a/VWO/MEG/CampaignGroupMapper.m b/VWO/MEG/CampaignGroupMapper.m index 63f68bd9..9a011a0c 100644 --- a/VWO/MEG/CampaignGroupMapper.m +++ b/VWO/MEG/CampaignGroupMapper.m @@ -8,7 +8,7 @@ #import "CampaignGroupMapper.h" #import "VWOLogger.h" - +#import "VWOCampaign.h" #import "Group.h" @implementation CampaignGroupMapper @@ -76,9 +76,11 @@ + (NSDictionary *)getGroups: (NSDictionary *)jsonObject{ NSDictionary *jsonGroups = nil; @try { - jsonGroups = jsonObject[KEY_GROUPS]; + VWOCampaign *groupDict = [jsonObject objectForKey:KEY_GROUPS] ; + jsonGroups = groupDict.group.groups; } @catch (NSException *exception) { + NSLog(@"Group check exception %@",exception); // VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); } return jsonGroups; diff --git a/VWO/MEG/Group.m b/VWO/MEG/Group.m index 5fccd430..14b10d07 100644 --- a/VWO/MEG/Group.m +++ b/VWO/MEG/Group.m @@ -229,7 +229,7 @@ - (void) createWeightMap { - NSNumber *weightBinValue = 0; + NSNumber *weightBinValue = [NSNumber numberWithInt:0]; diff --git a/VWO/MEG/MurmurHash.m b/VWO/MEG/MurmurHash.m index c065e55e..3819a3d0 100644 --- a/VWO/MEG/MurmurHash.m +++ b/VWO/MEG/MurmurHash.m @@ -74,7 +74,13 @@ + (int)hash32:(NSArray *)data length:(int)length{ * @return 32 bit hash of the given string */ +(int)hash32:(NSString *) text{ - NSArray* arrayOfStrings = [text componentsSeparatedByString:@""]; +// NSArray* arrayOfStrings = [text componentsSeparatedByString:@""]; + + NSMutableArray *arrayOfStrings = [[NSMutableArray alloc] initWithCapacity:[text length]]; + for (int i=0; i < [text length]; i++) { + NSString *ichar = [NSString stringWithFormat:@"%C", [text characterAtIndex:i]]; + [arrayOfStrings addObject:ichar]; + } return [self hash32:arrayOfStrings length:(int)[text length]]; } diff --git a/VWO/MEG/MutuallyExclusiveGroups.m b/VWO/MEG/MutuallyExclusiveGroups.m index 6cbfc2da..fd37f1ff 100644 --- a/VWO/MEG/MutuallyExclusiveGroups.m +++ b/VWO/MEG/MutuallyExclusiveGroups.m @@ -8,7 +8,7 @@ #import "MutuallyExclusiveGroups.h" #import "MurmurHash.h" - +#import "VWOCampaign.h" #include #import "Group.h" @@ -160,16 +160,23 @@ - (NSString *)getCampaignIdFromTestKey: (NSString *)testKey campaignsData: (NSAr @try { NSDictionary *groupDataItem = campaignsData[i]; - - if(groupDataItem[CAMPAIGN_TYPE] == TYPE_VISUAL_AB) { - - if(groupDataItem[CAMPAIGN_TEST_KEY] == testKey){ - - return groupDataItem[CAMPAIGN_ID]; - - } - - } + VWOCampaign *groupData = groupDataItem; //[groupDataItem objectForKey:CAMPAIGN_TYPE] ; + + if([[groupData type] isEqual:TYPE_VISUAL_AB]){ + if([[groupData testKey] isEqual: testKey]){ + return [NSString stringWithFormat:@"%d",[groupData iD]]; + } + } + +// if( [[groupDataItem objectForKey:CAMPAIGN_TYPE] isEqual: TYPE_VISUAL_AB]) { +// +// if([[groupDataItem objectForKey:CAMPAIGN_TEST_KEY] isEqual: testKey]){ +// +// return [groupDataItem objectForKey:CAMPAIGN_ID]; +// +// } +// +// } } @@ -202,18 +209,26 @@ - (NSString *)getTestKeyFromCampaignId: (NSString *)campaignId campaignsData: (N for (int i = 0; i < campaignsData.count; i++) { @try { - NSDictionary *groupDataItem = campaignsData[i]; - - if(groupDataItem[CAMPAIGN_TYPE] == TYPE_VISUAL_AB) { - - if(groupDataItem[CAMPAIGN_ID] == campaignId){ - - return groupDataItem[CAMPAIGN_TEST_KEY]; - - } - - } + VWOCampaign *groupData = groupDataItem; + + if([[groupData type] isEqual:TYPE_VISUAL_AB]){ + if([[NSString stringWithFormat:@"%d",[groupData iD]] isEqual: [NSString stringWithFormat:@"%@",campaignId]]){ + return [groupData testKey]; + } + } + +// NSDictionary *groupDataItem = campaignsData[i]; +// +// if(groupDataItem[CAMPAIGN_TYPE] == TYPE_VISUAL_AB) { +// +// if(groupDataItem[CAMPAIGN_ID] == campaignId){ +// +// return groupDataItem[CAMPAIGN_TEST_KEY]; +// +// } +// +// } } diff --git a/VWO/Models/VWOCampaign.h b/VWO/Models/VWOCampaign.h index 90f87918..73ecc928 100644 --- a/VWO/Models/VWOCampaign.h +++ b/VWO/Models/VWOCampaign.h @@ -33,6 +33,7 @@ typedef NS_ENUM(NSInteger, CampaignStatus) { @property VWOGroup * group; - (nullable instancetype)initWithDictionary:(NSDictionary *)campaignDict; +-(nullable instancetype)setGroups:(NSDictionary *) campaignDict; - (nullable id)variationForKey:(NSString *)key; - (nullable id)testKey:(NSString *)testKey; - (nullable VWOGoal *)goalForIdentifier:(NSString *)identifier; diff --git a/VWO/Models/VWOCampaign.m b/VWO/Models/VWOCampaign.m index 51666245..f25a9dcc 100644 --- a/VWO/Models/VWOCampaign.m +++ b/VWO/Models/VWOCampaign.m @@ -102,6 +102,13 @@ - (nullable instancetype)initWithDictionary:(NSDictionary *) campaignDict { return self; } +-(nullable instancetype)setGroups:(NSDictionary *) campaignDict{ + self.type = CAMPAIGN_GROUPS; + self.group = [[VWOGroup alloc] initWithDictionary:campaignDict]; + + return self; +} + - (nullable id)testKey:(NSString *)testKey { NSParameterAssert(testKey); //If key does not exist then NSDictionary returns nil diff --git a/VWO/Models/VWOGroup.h b/VWO/Models/VWOGroup.h index d7fe2e54..3a2822f3 100644 --- a/VWO/Models/VWOGroup.h +++ b/VWO/Models/VWOGroup.h @@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN @property NSDictionary * campaignGroups; @property NSDictionary * groups; -- (instancetype)initWithDictionary:(NSDictionary *)groupDict +- (instancetype)initWithDictionary:(NSDictionary *)groupDict; @end diff --git a/VWO/VWO.m b/VWO/VWO.m index dfc9f93c..a01c61d1 100644 --- a/VWO/VWO.m +++ b/VWO/VWO.m @@ -12,6 +12,7 @@ #import "CampaignGroupMapper.h" #import "MutuallyExclusiveGroups.h" #import "Group.h" +#import "VWOCampaign.h" static VWOLogLevel kLogLevel = VWOLogLevelError; static BOOL kOptOut = NO; @@ -209,16 +210,18 @@ + (NSString *)getCampaign:(NSString *)userId args:(NSDictionary *)args { VWOCampaignArray * campaignsData = [VWOController.shared getCampaignData]; -// VWOCampaign *megGroupsData = [[VWOCampaign alloc] initWithDictionary:]; - +// VWOCampaign *megGroupsData = [[VWOCampaign alloc] initWithDictionary:args]; + NSMutableDictionary *megGroupsData = [[NSMutableDictionary alloc] init]; + if (campaignsData != nil && campaignsData.count > 0) { for (int i = 0; i < campaignsData.count; i++) { @try { VWOCampaign *groupDataItem = campaignsData[i]; -// if (groupDataItem[CAMPAIGN_TYPE] == CAMPAIGN_GROUPS) { -// megGroupsData = groupDataItem; -// break; -// } + if ([groupDataItem type] == CAMPAIGN_GROUPS) { + [megGroupsData setObject:groupDataItem forKey:@"groups"]; +// megGroupsData[@"groups"] = groupDataItem; + break; + } } @catch (NSException *exception) { // VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); @@ -226,10 +229,10 @@ + (NSString *)getCampaign:(NSString *)userId args:(NSDictionary *)args { } } -// NSDictionary *mappedData = [CampaignGroupMapper createAndGetGroups: megGroupsData]; + NSDictionary *mappedData = [CampaignGroupMapper createAndGetGroups: megGroupsData]; MutuallyExclusiveGroups *meg = [[MutuallyExclusiveGroups alloc] initMutuallyExclusiveGroups:userId]; -// [meg addGroups:mappedData]; + [meg addGroups:mappedData]; return [meg getCampaign:args jsonData:campaignsData]; } diff --git a/VWO/VWOCampaignFetcher.m b/VWO/VWOCampaignFetcher.m index 06c7762f..dfb6a739 100644 --- a/VWO/VWOCampaignFetcher.m +++ b/VWO/VWOCampaignFetcher.m @@ -104,8 +104,15 @@ + (nullable NSData *)getCampaignsFromNetworkWithTimeout:(NSNumber *)timeout + (VWOCampaignArray *)campaignsFromJSON:(NSArray *)jsonArray { NSMutableArray *newCampaignList = [NSMutableArray new]; for (NSDictionary *campaignDict in jsonArray) { + NSString *type = [campaignDict objectForKey:@"type"]; + VWOCampaign *aCampaign = [[VWOCampaign alloc] initWithDictionary:campaignDict]; if (aCampaign) [newCampaignList addObject:aCampaign]; + + if ([type isEqual: @"groups"]){ + VWOCampaign *aCampaign = [[VWOCampaign alloc] setGroups:campaignDict]; + if (aCampaign) [newCampaignList addObject:aCampaign]; + } } return newCampaignList; } From f6c9291cef711f03d12d19dac64d2227da01de72 Mon Sep 17 00:00:00 2001 From: Parvesh Chauhan Date: Mon, 12 Dec 2022 15:43:27 +0530 Subject: [PATCH 3/3] Mutual exclusive group fixes --- Demo/VWO Demo/AppDelegate.swift | 3 +- .../Sorting campaign/PhoneListVC.swift | 10 +- Demo/VWO Demo/VWOManager.swift | 2 +- VWO/MEG/CampaignGroupMapper.m | 17 ++- VWO/MEG/Group.h | 12 +- VWO/MEG/Group.m | 143 +++++++----------- VWO/MEG/MutuallyExclusiveGroups.m | 91 ++++------- VWO/VWO.m | 6 +- 8 files changed, 108 insertions(+), 176 deletions(-) diff --git a/Demo/VWO Demo/AppDelegate.swift b/Demo/VWO Demo/AppDelegate.swift index 6f9c4e2a..e9896339 100644 --- a/Demo/VWO Demo/AppDelegate.swift +++ b/Demo/VWO Demo/AppDelegate.swift @@ -22,7 +22,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // setCurrentViewController(vc: houseNav) setCurrentViewController(vc: phoneNav) - VWOManager.launch("a594fc28d7bd8e3438d8df0bcd37f2cc-647788") +// VWOManager.launch("033cf474a02bb4aafea16dc0685c896a-11000006") + return true } diff --git a/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift b/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift index 2620bac2..846b56e8 100644 --- a/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift +++ b/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift @@ -40,9 +40,11 @@ class PhoneListVC: UIViewController { print("\(segue.destination) \(segue.source)") let destination = segue.destination as! PhoneDetailVC let t = tableView.indexPathForSelectedRow!.row - VWO.trackConversion("productView") - let abcd = VWO.getCampaign("userId", args: ["test_key":"37","groupId":"1"]) - print("mutual exp ...... \(abcd)") + +// let TestKey = VWO.getCampaign("e57e8bd1-fb5f-478d-80d2-5127eb5d79f7", args: ["groupId":"8"]) +// let TestKey = VWO.getCampaign("e57e8bd1-fb5f-478d-80d2-5127eb5d79f7", args: ["test_key":"ME123"]) + let TestKey = VWO.getCampaign("9c3832ad-15f9-420a-93cd-a7f2cde0f7bc", args: ["test_key":"ME123","groupId":"8"]) + destination.phone = phoneList[t] } @@ -52,7 +54,7 @@ class PhoneListVC: UIViewController { @IBAction func reloadTapped(_ sender: Any) { - let variation = VWO.variationNameFor(testKey: "sorting") + let variation = VWO.variationNameFor(testKey: "METest") switch variation { case "Sort-Alphabetically": phoneList.sort(by: sortPhoneAlphabetically) diff --git a/Demo/VWO Demo/VWOManager.swift b/Demo/VWO Demo/VWOManager.swift index f2eb6438..4db40e3f 100644 --- a/Demo/VWO Demo/VWOManager.swift +++ b/Demo/VWO Demo/VWOManager.swift @@ -22,7 +22,7 @@ class VWOManager { VWO.logLevel = .debug let config = VWOConfig() // config.setCustomDimension(customDimensionKey: "userId", customDimensionValue: "userName") - config.userID = "userId" + config.userID = "9c3832ad-15f9-420a-93cd-a7f2cde0f7bc" // config.isChinaCDN = false VWO.launch(apiKey: apiKey, config: config, completion: { DispatchQueue.main.async { diff --git a/VWO/MEG/CampaignGroupMapper.m b/VWO/MEG/CampaignGroupMapper.m index 9a011a0c..95f4fd58 100644 --- a/VWO/MEG/CampaignGroupMapper.m +++ b/VWO/MEG/CampaignGroupMapper.m @@ -29,8 +29,8 @@ + (NSDictionary *)getCampaignGroups: (NSDictionary *)jsonObject{ jsonCampaignGroups = jsonObject[KEY_CAMPAIGN_GROUPS]; } @catch (NSException *exception) { - //VWOLogError(NSString *format, ...) - //VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); + VWOLogDebug(@"MutuallyExclusive %@", exception); + } return jsonCampaignGroups; } @@ -54,20 +54,22 @@ + (NSDictionary *)createAndGetGroups: (NSDictionary *)jsonObject{ NSString *groupName = objGroup[KEY_NAME]; Group *group = [[Group alloc]init]; - [group setName: groupName]; - [group setId: key.intValue]; + group.name = groupName; + group.Id = key.intValue; for (int i = 0; i < arrCampaigns.count; i++) { [group addCampaign:arrCampaigns[i]]; } - + VWOLogDebug(@"MutuallyExclusive Added Group Id %d", group.Id); + VWOLogDebug(@"MutuallyExclusive Added Group Campaign %@", group.getCampaigns); + [groups setObject:group forKey:groupName]; index++; } } @catch (NSException *exception) { - // VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); + VWOLogDebug(@"MutuallyExclusive error while adding groups %@", exception); } return groups; } @@ -80,8 +82,7 @@ + (NSDictionary *)getGroups: (NSDictionary *)jsonObject{ jsonGroups = groupDict.group.groups; } @catch (NSException *exception) { - NSLog(@"Group check exception %@",exception); - // VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); + VWOLogDebug(@"MutuallyExclusive %@", exception); } return jsonGroups; } diff --git a/VWO/MEG/Group.h b/VWO/MEG/Group.h index bd58153c..23d2ea4d 100644 --- a/VWO/MEG/Group.h +++ b/VWO/MEG/Group.h @@ -12,14 +12,18 @@ NS_ASSUME_NONNULL_BEGIN @interface Group : NSObject -- (NSString *) getCampaignForRespectiveWeight: (NSNumber *) weight; -- (int) getId; +@property(nonatomic,assign)int Id; +@property(nonatomic,strong)NSString *name; +@property(nonatomic,strong)NSMutableArray *campaignList; +@property(nonatomic,strong)NSMutableDictionary *weightMap; +@property(nonatomic,strong)NSNumber *weight; + +- (NSString *) getCampaignForRespectiveWeight: (NSNumber *) weight; +- (NSMutableArray *) getCampaigns; - (NSString *) getNameOnlyIfPresent: (NSString *) toSearch; - (NSString *) getOnlyIfPresent: (NSString *) toSearch; - (void) addCampaign: (NSString *) campaign; -- (void) setName: (NSString *) Name; -- (void) setId: (int) Id; @end NS_ASSUME_NONNULL_END diff --git a/VWO/MEG/Group.m b/VWO/MEG/Group.m index 14b10d07..a8a3f8cb 100644 --- a/VWO/MEG/Group.m +++ b/VWO/MEG/Group.m @@ -11,87 +11,38 @@ #import "MutuallyExclusiveGroups.h" @implementation Group -static int ID = INT_MIN; -/** - * Name of the group +- (NSUInteger) getCampaignSize { - */ + if(_campaignList == nil){ -NSString *name = nil; -/** - - * The list of campaigns assigned for this group. - - */ - -NSMutableArray *campaignList; -/** - - * A simple key value based mechanism to check where our weight belongs to. - - */ - -NSMutableDictionary *weightMap = nil; -NSNumber *weight; - -- (int)getId { - - return ID; - -} - -- (NSString *) getName { - - return name; - -} - -- (void) setName: (NSString *) Name { - - name = Name; - -} - -- (void) setId: (int) Id { - - ID = Id; - -} - -+ (NSUInteger) getCampaignSize { - - if(campaignList == nil){ - - campaignList = [NSMutableArray new]; + self.campaignList = [NSMutableArray new]; } - return campaignList.count; + return self.campaignList.count; } -+ (NSMutableArray *) getCampaigns { +- (NSMutableArray *) getCampaigns { - if(campaignList == nil){ + if(self.campaignList == nil){ - campaignList = [NSMutableArray new]; + self.campaignList = [NSMutableArray new]; } - return campaignList; + return self.campaignList; } - - - (void) calculateWeight { float total = 100; // because 100% - NSUInteger totalCampaigns = campaignList.count; + NSUInteger totalCampaigns = self.campaignList.count; - weight = @(total/totalCampaigns); + self.weight = @(total/totalCampaigns); } @@ -100,21 +51,21 @@ - (void) calculateWeight { - (void) addCampaign: (NSString *) campaign { CampaignUniquenessTracker *campaignUniquenessTracker = [[CampaignUniquenessTracker alloc] init]; if([campaignUniquenessTracker groupContainsCampaign:campaign]) { - [MutuallyExclusiveGroups log: [NSString stringWithFormat:@"%s/%@/%s/%@/%s/%@/%s", "addCampaign: could not add campaign [ ", campaign, " ] to group [ ", [self getName]," ] because it already belongs to group [ ", [campaignUniquenessTracker getNameOfGroupFor:campaign]," ]"]]; + [MutuallyExclusiveGroups log: [NSString stringWithFormat:@"%s/%@/%s/%@/%s/%@/%s", "addCampaign: could not add campaign [ ", campaign, " ] to group [ ", self.name," ] because it already belongs to group [ ", [campaignUniquenessTracker getNameOfGroupFor:campaign]," ]"]]; return; } - [campaignUniquenessTracker addCampaignAsRegistered:campaign group:[self getName]]; + [campaignUniquenessTracker addCampaignAsRegistered:campaign group:self.name]; - if(campaignList == nil){ + if(_campaignList == nil){ - campaignList = [NSMutableArray new]; + _campaignList = [NSMutableArray new]; } - [campaignList addObject:campaign]; + [self.campaignList addObject:campaign]; [self calculateWeight]; @@ -124,13 +75,13 @@ - (void) removeCampaign: (NSString *) campaign{ NSMutableArray *campaigns = [NSMutableArray new]; - if (campaignList == nil) return; + if (self.campaignList == nil) return; - if (campaignList.count== 0) return; + if (_campaignList.count== 0) return; - for (int i = 0; i < campaignList.count; i++) { + for (int i = 0; i < _campaignList.count; i++) { - NSString *value = campaignList[i]; + NSString *value = _campaignList[i]; if (value == campaign) continue; @@ -138,26 +89,28 @@ - (void) removeCampaign: (NSString *) campaign{ } - [campaignList removeAllObjects]; + [_campaignList removeAllObjects]; - [campaignList addObjectsFromArray:(campaigns)]; + [_campaignList addObjectsFromArray:(campaigns)]; [self calculateWeight]; } -+ (NSNumber *) getWeight { - return weight; +- (NSNumber *) getWeight { + return _weight; } - (NSString *) getOnlyIfPresent: (NSString *) toSearch{ - if (campaignList == nil) return nil; + if (_campaignList == nil) return nil; - if (campaignList.count== 0) return nil; + if (_campaignList.count== 0) return nil; - for (NSString *campaignId in campaignList) { + for (NSString *campaignId in _campaignList) { - if (toSearch == campaignId) return [NSString stringWithFormat:@"%d",ID]; + if([toSearch isEqual:[NSString stringWithFormat:@"%@",campaignId]]){ + return [NSString stringWithFormat:@"%d",_Id]; + } } @@ -169,15 +122,15 @@ - (NSString *) getOnlyIfPresent: (NSString *) toSearch{ - (NSString *) getNameOnlyIfPresent: (NSString *) toSearch{ - if (campaignList == nil) return nil; + if (_campaignList == nil) return nil; - if (campaignList.count== 0) return nil; + if (_campaignList.count== 0) return nil; - for (NSString *campaignId in campaignList) { + for (NSString *campaignId in _campaignList) { - if (toSearch == campaignId) return name; + if (toSearch == campaignId) return _name; } @@ -191,17 +144,25 @@ - (NSString *) getCampaignForRespectiveWeight: (NSNumber *) weight{ [self createWeightMap]; - NSArray *allKeys = [weightMap allKeys]; + NSArray *allKeys = [_weightMap allKeys]; for(NSString *key in allKeys) { - NSMutableArray *weightMaxMin = weightMap[key]; + NSMutableArray *weightMaxMin = _weightMap[key]; if (weightMaxMin == nil) continue; - - BOOL weightIsGreaterThanMin = (weight > weightMaxMin[0]); - - BOOL weightIsLessThanMax = (weight <= weightMaxMin[1]); + + BOOL weightIsGreaterThanMin = false; + int maxWeight = [weightMaxMin[0] intValue]; + if ([weight intValue] > maxWeight){ + weightIsGreaterThanMin = true; + } + + BOOL weightIsLessThanMax = false; + + if ([weight intValue] <= [weightMaxMin[1] intValue]){ + weightIsLessThanMax = true; + } if(weightIsGreaterThanMin && weightIsLessThanMax) { @@ -221,9 +182,9 @@ - (NSString *) getCampaignForRespectiveWeight: (NSNumber *) weight{ - (void) createWeightMap { - if (weightMap == nil) { + if (_weightMap == nil) { - weightMap = [NSMutableDictionary new]; + _weightMap = [NSMutableDictionary new]; } @@ -235,17 +196,17 @@ - (void) createWeightMap { // A 0 - 33.33 , B 33.33 - 66.33 , C 66.333, 100.0 - for (int i = 0; i < campaignList.count; i++) { + for (int i = 0; i < _campaignList.count; i++) { NSMutableArray *range = [NSMutableArray new]; [range addObject:weightBinValue]; - weightBinValue = @(weightBinValue.intValue + weight.intValue); + weightBinValue = @(weightBinValue.intValue + _weight.intValue); [range addObject:weightBinValue]; - [weightMap setObject: range forKey: campaignList[i]]; + [_weightMap setObject: range forKey: _campaignList[i]]; } diff --git a/VWO/MEG/MutuallyExclusiveGroups.m b/VWO/MEG/MutuallyExclusiveGroups.m index fd37f1ff..e9e7cc1c 100644 --- a/VWO/MEG/MutuallyExclusiveGroups.m +++ b/VWO/MEG/MutuallyExclusiveGroups.m @@ -11,6 +11,7 @@ #import "VWOCampaign.h" #include #import "Group.h" +#import "VWOLogger.h" @implementation MutuallyExclusiveGroups @@ -77,8 +78,7 @@ - (NSString *)calculateTheWinnerCampaign: (NSDictionary *)args jsonData: (NSArra // there must be at least one type of id // either GROUP or CAMPAIGN - - //VWOLog.w(VWOLog.MEG_LOGS, "The groupId and campaignId ; both are null.", false); + VWOLogDebug(@"MutuallyExclusive The groupId and campaignId both are null"); return nil; } @@ -92,26 +92,24 @@ - (NSString *)calculateTheWinnerCampaign: (NSDictionary *)args jsonData: (NSArra BOOL groupIdIsNotPresentInArgs = (groupId == nil || groupId.length == 0); if(groupIdIsNotPresentInArgs) { + VWOLogDebug(@"MutuallyExclusive The groupId was not found in the mapping so just picking the specific campaign [ %@ ]",campaignId); - //VWOLog.i(VWOLog.MEG_LOGS, ID_GROUP + " was not found in the mapping so just picking the specific campaign [ " + campaignId + " ]", false); // if there is no sign of group we can simply use the campaign matching logic campaign = [self getCampaignFromCampaignId: userId campaign: campaignId]; + + VWOLogDebug(@"MutuallyExclusive Campaign selected from the mutually exclusive group is [ %@ ]",campaign); - //VWOLog.i(VWOLog.MEG_LOGS, "Campaign selected from the mutually exclusive group is [ " + campaign + " ]", false); TestKey = [self getTestKeyFromCampaignId: campaign campaignsData:campaignsData]; - - // VWOLog.i(VWOLog.MEG_LOGS, "Test-key of the campaign selected from the mutually exclusive group is [ " + TestKey + " ]", false); - + VWOLogDebug(@"MutuallyExclusive Test-key of the campaign selected from the mutually exclusive group is [ %@ ]",TestKey); return TestKey; } - - // VWOLog.d(VWOLog.MEG_LOGS, "Because there was groupId present, we are going to prioritize it and get a campaign from that group", false); + VWOLogDebug(@"MutuallyExclusive Because there was groupId present, we are going to prioritize it and get a campaign from that group"); @try{ @@ -120,8 +118,7 @@ - (NSString *)calculateTheWinnerCampaign: (NSDictionary *)args jsonData: (NSArra } @catch(NSException *exception) { - - // VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); + VWOLogDebug(@"MutuallyExclusive %@",exception); return nil; @@ -130,12 +127,10 @@ - (NSString *)calculateTheWinnerCampaign: (NSDictionary *)args jsonData: (NSArra campaign = [self getCampaignFromSpecificGroup:groupName]; - - // VWOLog.i(VWOLog.MEG_LOGS, "Selected campaign from [ " + groupName + " ] is [ " + campaign + " ]", false); + VWOLogDebug(@"MutuallyExclusive Selected campaign from [ %@ ] is [ %@ ]",groupName,campaign); TestKey = [self getTestKeyFromCampaignId:campaign campaignsData:campaignsData]; - - // VWOLog.i(VWOLog.MEG_LOGS, "Test-key of the campaign selected from the mutually exclusive group is [ " + TestKey + " ]", false); + VWOLogDebug(@"MutuallyExclusive Test-key of the campaign selected from the mutually exclusive group is [ %@ ]",TestKey); return TestKey; @@ -167,22 +162,12 @@ - (NSString *)getCampaignIdFromTestKey: (NSString *)testKey campaignsData: (NSAr return [NSString stringWithFormat:@"%d",[groupData iD]]; } } - -// if( [[groupDataItem objectForKey:CAMPAIGN_TYPE] isEqual: TYPE_VISUAL_AB]) { -// -// if([[groupDataItem objectForKey:CAMPAIGN_TEST_KEY] isEqual: testKey]){ -// -// return [groupDataItem objectForKey:CAMPAIGN_ID]; -// -// } -// -// } + } @catch(NSException *exception) { - - // VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); + VWOLogDebug(@"MutuallyExclusive %@ ",exception); } @@ -217,24 +202,13 @@ - (NSString *)getTestKeyFromCampaignId: (NSString *)campaignId campaignsData: (N return [groupData testKey]; } } - -// NSDictionary *groupDataItem = campaignsData[i]; -// -// if(groupDataItem[CAMPAIGN_TYPE] == TYPE_VISUAL_AB) { -// -// if(groupDataItem[CAMPAIGN_ID] == campaignId){ -// -// return groupDataItem[CAMPAIGN_TEST_KEY]; -// -// } -// -// } + } @catch(NSException *exception) { - - // VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); + VWOLogDebug(@"MutuallyExclusive %@ ",exception); + } @@ -263,6 +237,8 @@ - (NSString *)getCampaignFromSpecificGroup: (NSString *)groupName{ NSNumber *murmurHash = [self getMurMurHash:userId]; + + NSLog(@"Murmur hash value %@",murmurHash); // If the campaign-user mapping is present in the App storage, get the decision from there. Otherwise, go to the next step @@ -272,7 +248,7 @@ - (NSString *)getCampaignFromSpecificGroup: (NSString *)groupName{ NSNumber *normalizedValue = [self getNormalizedValue:murmurHash]; - // VWOLog.d(VWOLog.MEG_LOGS, "Normalized value for user with userID -> " + userId + " is [ " + normalizedValue + " ] ", false); + VWOLogDebug(@"MutuallyExclusive Normalized value for user with userID -> %@ is ",userId,normalizedValue); Group *interestedGroup = CAMPAIGN_GROUPS[groupName]; @@ -290,9 +266,7 @@ -(NSString *)getGroupNameFromGroupId: (int)groupId{ Group *group = CAMPAIGN_GROUPS[key]; if(group == nil)return nil; - - if(groupId == [group getId]){ - //we found the group we have been searching for + if(groupId == group.Id){ return key; } } @@ -304,16 +278,15 @@ - (NSString *)getCampaignFromCampaignId: (NSString *)userId campaign: (NSString NSString *campaignFoundInGroup = [self getCampaignIfPresent:campaign]; if (campaignFoundInGroup == nil) { - - // VWOLog.i(VWOLog.MEG_LOGS, "The campaign key [ " + campaign + " ] is not present in any of the mutually exclusive groups.", false); + VWOLogDebug(@"MutuallyExclusive The campaign key [ %@ ] is not present in any of the mutually exclusive groups ",campaign); return campaign; } else { - - // VWOLog.i(VWOLog.MEG_LOGS, "Found campaign [ " + campaign + " ] in mutually exclusive group [ " + campaignFoundInGroup + " ] ", false); + + VWOLogDebug(@"MutuallyExclusive Found campaign [ %@ ] in mutually exclusive group [ %@ ] ",campaign,campaignFoundInGroup); } @@ -322,23 +295,18 @@ - (NSString *)getCampaignFromCampaignId: (NSString *)userId campaign: (NSString NSNumber *murmurHash = [self getMurMurHash:userId]; // If the campaign-user mapping is present in the App storage, get the decision from there. Otherwise, go to the next step - + VWOLogDebug(@"MutuallyExclusive Murmur hash for [%@] -> [%@]",userId,murmurHash); NSString *murmurHashString = [NSString stringWithFormat: @"%@", murmurHash]; if ([USER_CAMPAIGN objectForKey: murmurHashString]) return USER_CAMPAIGN[murmurHashString]; NSNumber *normalizedValue = [self getNormalizedValue:murmurHash]; - - // VWOLog.d(VWOLog.MEG_LOGS, "Normalized value for user with userID -> " + userId + " is [ " + normalizedValue + " ] ", false); - - - // this group has our campaign + VWOLogDebug(@"MutuallyExclusive Normalized value for user with userID [%@] -> [%@]",userId,normalizedValue); Group *interestedGroup = CAMPAIGN_GROUPS[campaignFoundInGroup]; - if (interestedGroup == nil) return nil; // basic null check because NSDictionary is being used @@ -348,20 +316,17 @@ - (NSString *)getCampaignFromCampaignId: (NSString *)userId campaign: (NSString NSString *finalCampaign = [interestedGroup getCampaignForRespectiveWeight: normalizedValue]; - - if (campaign == finalCampaign) { - + if([campaign isEqual:[NSString stringWithFormat:@"%@",finalCampaign]]){ + VWOLogDebug(@"MutuallyExclusive Campaign [%@] found for given weight [%@]",finalCampaign,normalizedValue); return finalCampaign; } else { - - // VWOLog.i(VWOLog.MEG_LOGS, "Passed campaign : " + campaign + " does not match calculated campaign " + finalCampaign, false); + VWOLogDebug(@"MutuallyExclusive Passed campaign : [%@] does not match calculated campaign [%@]",campaign,finalCampaign); } - return nil; @@ -403,7 +368,7 @@ - (NSNumber *)getNormalizedValue: (NSNumber *)murmurHash{ int max = 100; - double ratio = murmurHash.intValue / (int) pow(2,31); + double ratio = murmurHash.intValue / pow(2,31); double multipliedValue = (max * ratio) + 1; diff --git a/VWO/VWO.m b/VWO/VWO.m index a01c61d1..1cf80400 100644 --- a/VWO/VWO.m +++ b/VWO/VWO.m @@ -204,13 +204,11 @@ + (nullable NSString *)variationNameForTestKey:(NSString *)campaignTestKey { + (NSString *)getCampaign:(NSString *)userId args:(NSDictionary *)args { if (userId == nil || [userId length]==0) { - // use the (default | random) user id userId = [VWOController.shared getUserId]; } VWOCampaignArray * campaignsData = [VWOController.shared getCampaignData]; -// VWOCampaign *megGroupsData = [[VWOCampaign alloc] initWithDictionary:args]; NSMutableDictionary *megGroupsData = [[NSMutableDictionary alloc] init]; if (campaignsData != nil && campaignsData.count > 0) { @@ -219,12 +217,12 @@ + (NSString *)getCampaign:(NSString *)userId args:(NSDictionary *)args { VWOCampaign *groupDataItem = campaignsData[i]; if ([groupDataItem type] == CAMPAIGN_GROUPS) { [megGroupsData setObject:groupDataItem forKey:@"groups"]; -// megGroupsData[@"groups"] = groupDataItem; break; } } @catch (NSException *exception) { - // VWOLog.e(VWOLog.DATA_LOGS, exception, true, false); + VWOLogDebug(@"MutuallyExclusive %@", exception); + } } }