From 8e2774acc2e61b043adb2133b4f9db79d1a375b8 Mon Sep 17 00:00:00 2001 From: Harsh Raghav Date: Wed, 17 May 2023 01:48:24 +0530 Subject: [PATCH 1/5] Feature: MEG prioritisation and Weightage implementation done --- Demo/VWO Demo/VWOManager.swift | 3 +- VWO/MEG/CampaignGroupMapper.h | 7 + VWO/MEG/CampaignGroupMapper.m | 60 ++++++- VWO/MEG/Group.h | 14 ++ VWO/MEG/Group.m | 145 +++++++++++++++- VWO/MEG/MEGManager.h | 19 ++ VWO/MEG/MEGManager.m | 110 ++++++++++++ VWO/MEG/Mapping.h | 20 +++ VWO/MEG/Mapping.m | 63 +++++++ VWO/MEG/MutuallyExclusiveGroups.h | 2 + VWO/MEG/MutuallyExclusiveGroups.m | 178 +++++++++++++++++-- VWO/MEG/Pair.h | 24 +++ VWO/MEG/Pair.m | 22 +++ VWO/MEG/PriorityQualificationWinnerResult.h | 19 ++ VWO/MEG/PriorityQualificationWinnerResult.m | 70 ++++++++ VWO/MEG/Response.h | 20 +++ VWO/MEG/Response.m | 24 +++ VWO/MEG/Weight.h | 17 ++ VWO/MEG/Weight.m | 47 +++++ VWO/MEG/Winner.h | 25 +++ VWO/MEG/Winner.m | 137 +++++++++++++++ VWO/MEG/WinnerManager.h | 14 ++ VWO/MEG/WinnerManager.m | 181 ++++++++++++++++++++ VWO/VWO.m | 33 +--- VWO/VWOConstants.h | 6 + VWO/VWOConstants.m | 8 + VWO/VWOSegmentEvaluator.h | 2 + VWO/VWOUserDefaults.h | 3 + 28 files changed, 1224 insertions(+), 49 deletions(-) create mode 100644 VWO/MEG/MEGManager.h create mode 100644 VWO/MEG/MEGManager.m create mode 100644 VWO/MEG/Mapping.h create mode 100644 VWO/MEG/Mapping.m create mode 100644 VWO/MEG/Pair.h create mode 100644 VWO/MEG/Pair.m create mode 100644 VWO/MEG/PriorityQualificationWinnerResult.h create mode 100644 VWO/MEG/PriorityQualificationWinnerResult.m create mode 100644 VWO/MEG/Response.h create mode 100644 VWO/MEG/Response.m create mode 100644 VWO/MEG/Weight.h create mode 100644 VWO/MEG/Weight.m create mode 100644 VWO/MEG/Winner.h create mode 100644 VWO/MEG/Winner.m create mode 100644 VWO/MEG/WinnerManager.h create mode 100644 VWO/MEG/WinnerManager.m diff --git a/Demo/VWO Demo/VWOManager.swift b/Demo/VWO Demo/VWOManager.swift index 4db40e3f..e28deb29 100644 --- a/Demo/VWO Demo/VWOManager.swift +++ b/Demo/VWO Demo/VWOManager.swift @@ -29,7 +29,8 @@ class VWOManager { // VWO.pushCustomDimension(customDimensionKey: "userId", customDimensionValue: "userName") hud.hide(animated: false) SCLAlertView().showSuccess("Success", subTitle: "VWO launched successfully \(apiKey)") - + var str: String? = nil; + VWO.variationNameFor(testKey: str ?? "") } }, failure: { (errorString) in diff --git a/VWO/MEG/CampaignGroupMapper.h b/VWO/MEG/CampaignGroupMapper.h index 1c628423..c2c8a735 100644 --- a/VWO/MEG/CampaignGroupMapper.h +++ b/VWO/MEG/CampaignGroupMapper.h @@ -7,6 +7,7 @@ // #import +#import "Group.h" NS_ASSUME_NONNULL_BEGIN @@ -14,6 +15,12 @@ NS_ASSUME_NONNULL_BEGIN + (NSDictionary *)getCampaignGroups: (NSDictionary *)jsonObject; + (NSDictionary *)createAndGetGroups: (NSDictionary *)jsonObject; ++ (void)preparePriority: (NSDictionary *)source destination:(Group *)destination; ++ (void)prepareEt:(NSDictionary *)source destination:(Group *)destination; ++ (void)prepareCampaigns:(NSDictionary *)source destination:(Group *)destination; ++ (void)prepareWeight:(NSDictionary *)source destination:(Group *)destination; ++ (NSDictionary *)getGroups: (NSDictionary *)jsonObject; + @end NS_ASSUME_NONNULL_END diff --git a/VWO/MEG/CampaignGroupMapper.m b/VWO/MEG/CampaignGroupMapper.m index 95f4fd58..3920808c 100644 --- a/VWO/MEG/CampaignGroupMapper.m +++ b/VWO/MEG/CampaignGroupMapper.m @@ -17,6 +17,9 @@ @implementation CampaignGroupMapper static NSString * const KEY_GROUPS = @"groups"; static NSString * const KEY_NAME = @"name"; static NSString * const KEY_CAMPAIGNS = @"campaigns"; +static NSString * const KEY_PRIORITY = @"p"; +static NSString * const KEY_ET = @"et"; +static NSString * const KEY_WEIGHT = @"wt"; float m = 1.0; @@ -49,17 +52,18 @@ + (NSDictionary *)createAndGetGroups: (NSDictionary *)jsonObject{ NSString *key = itrJsonGroups[index]; NSDictionary *objGroup = jsonGroups[key]; - NSArray *arrCampaigns = objGroup[KEY_CAMPAIGNS]; NSString *groupName = objGroup[KEY_NAME]; Group *group = [[Group alloc]init]; group.name = groupName; - group.Id = key.intValue; +// group.Id = key.intValue; + + [self prepareWeight:objGroup destination:group]; + [self prepareCampaigns:objGroup destination:group]; + [self prepareEt:objGroup destination:group]; + [self preparePriority:objGroup destination:group]; - 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); @@ -74,6 +78,52 @@ + (NSDictionary *)createAndGetGroups: (NSDictionary *)jsonObject{ return groups; } ++ (void)preparePriority: (NSDictionary *)source destination:(Group *)destination { + if (![source objectForKey:KEY_PRIORITY]) return; + + NSArray *priority = [source objectForKey:KEY_PRIORITY]; + NSLog(@"priority should be given to these campaigns -> %@", priority); + for (int pIndex = 0; pIndex < priority.count; ++pIndex) { + [destination addPriority:[priority objectAtIndex:pIndex]]; + } +} + ++ (void)prepareEt:(NSDictionary *)source destination:(Group *)destination { + if (![source objectForKey:KEY_ET]) return; + + int et = [[source objectForKey:KEY_ET] intValue]; + [destination addEt:et]; +} + ++ (void)prepareCampaigns:(NSDictionary *)source destination:(Group *)destination { + if (![source objectForKey:KEY_CAMPAIGNS]) return; + + NSArray *arrCampaigns = [source objectForKey:KEY_CAMPAIGNS]; + for (int index = 0; index < arrCampaigns.count; index++) { + [destination addCampaign:[arrCampaigns objectAtIndex:index]]; + } +} + ++ (void)prepareWeight:(NSDictionary *)source destination:(Group *)destination { + if (![source objectForKey:KEY_WEIGHT]) return; + + NSLog(@"------------------------------------------------------------"); + NSLog(@"preparing for -> %@", destination.name); + NSLog(@"found weight sent from server, preparing the weight value for later usage."); + NSLog(@"NOTE: these weight will only be applied if no priority campaign exist."); + + NSDictionary *weights = [source objectForKey:KEY_WEIGHT]; + NSEnumerator *enumerator = [weights keyEnumerator]; + NSString *campaign; + while ((campaign = [enumerator nextObject])) { + NSNumber *weightNumber = [weights objectForKey:campaign]; + int weight = [weightNumber intValue]; + [destination addWeight:campaign weight:weight]; + } + + NSLog(@"------------------------------------------------------------"); +} + + (NSDictionary *)getGroups: (NSDictionary *)jsonObject{ NSDictionary *jsonGroups = nil; diff --git a/VWO/MEG/Group.h b/VWO/MEG/Group.h index 23d2ea4d..79c8bb5b 100644 --- a/VWO/MEG/Group.h +++ b/VWO/MEG/Group.h @@ -19,11 +19,25 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic,strong)NSMutableDictionary *weightMap; @property(nonatomic,strong)NSNumber *weight; +- (instancetype)init; +- (NSMutableArray *)getPriorityCampaigns; +- (void)addPriority:(NSString *)p; +- (int)getEt; +- (void)addEt:(int)et; +- (NSUInteger) getCampaignSize; +- (NSString *)getPriorityCampaign; +- (BOOL)isNotAdvanceMEGAllocation; +- (void)createWeightMap; +- (void)createWeightMapFromProvidedValues; +- (void) createEquallyDistributedWeightMap; +- (void)addWeight:(NSString *)campaign weight:(NSInteger)weight; - (NSString *) getCampaignForRespectiveWeight: (NSNumber *) weight; - (NSMutableArray *) getCampaigns; - (NSString *) getNameOnlyIfPresent: (NSString *) toSearch; - (NSString *) getOnlyIfPresent: (NSString *) toSearch; - (void) addCampaign: (NSString *) campaign; +- (BOOL)hasInPriority:(NSString *)campaign; +- (BOOL)doesNotHaveInPriority:(NSString *)campaign; @end NS_ASSUME_NONNULL_END diff --git a/VWO/MEG/Group.m b/VWO/MEG/Group.m index a8a3f8cb..bf610ef4 100644 --- a/VWO/MEG/Group.m +++ b/VWO/MEG/Group.m @@ -7,11 +7,64 @@ // #import "Group.h" +#import "Weight.h" #import "CampaignUniquenessTracker.h" #import "MutuallyExclusiveGroups.h" +@interface Group () + +// Private instance variable +@property (nonatomic, strong) NSMutableArray *priorityCampaigns; + +/** +* Type of allocation: +* 1 - Random +* 2 - Advance +*

+* DOC: https://confluence.wingify.com/pages/viewpage.action?spaceKey=VWOENG&title=Mutually+Exclusive+Weights+and+Prioritization+in+Mobile+App+Testing +*/ +@property (nonatomic,assign) int et; + +// Using NSMutableArray to maintain the insertion order +@property (nonatomic, strong) NSMutableArray *weightMapFromServer; + +@end + @implementation Group +const int VALUE_ET_INVALID = -1; +const int VALUE_ET_RANDOM = 1; +const int VALUE_ET_ADVANCE = 2; +NSString *VALUE_INVALID_PRIORITY_CAMPAIGN = @"InvalidCampaign"; + +- (instancetype)init +{ + self = [super init]; + if (self) { + // Initialize private properties + _priorityCampaigns = [[NSMutableArray alloc] init]; + _et= VALUE_ET_INVALID; + _weightMapFromServer = [[NSMutableArray alloc] init]; + } + return self; +} + +- (NSMutableArray *)getPriorityCampaigns { + return _priorityCampaigns; +} + +- (void)addPriority:(NSString *)p { + [_priorityCampaigns addObject:p]; +} + +- (int)getEt { + return _et; +} + +- (void)addEt:(int)et { + _et = et; +} + - (NSUInteger) getCampaignSize { if(_campaignList == nil){ @@ -36,9 +89,44 @@ - (NSUInteger) getCampaignSize { } +- (NSString *)getPriorityCampaign { + [MutuallyExclusiveGroups log:[NSString stringWithFormat:@"will try to check for priority campaign against campaign list in group -> %@", self.name]]; + + // check if et is advance as priority is not + if ([self isNotAdvanceMEGAllocation]) { + [MutuallyExclusiveGroups log:[NSString stringWithFormat:@"et ( %d ) is not advance type, priority campaigns ( p ) will not be applicable.", self.et]]; + return VALUE_INVALID_PRIORITY_CAMPAIGN; + } + + if (self.priorityCampaigns.count == 0) { + [MutuallyExclusiveGroups log:@"et is advance but the priority array is empty."]; + return VALUE_INVALID_PRIORITY_CAMPAIGN; + } + + [MutuallyExclusiveGroups log:[NSString stringWithFormat:@"there are %lu priorityCampaigns in %@", (unsigned long)self.priorityCampaigns.count, self.name]]; + + for (NSString *priorityCampaign in self.priorityCampaigns) { + if ([self.campaignList containsObject:priorityCampaign]) { + [MutuallyExclusiveGroups log:[NSString stringWithFormat:@"priority campaign >> %@ << found in -> %@", priorityCampaign, self.name]]; + return priorityCampaign; + } else { + [MutuallyExclusiveGroups log:[NSString stringWithFormat:@"priority campaign >> %@ << doesn't exist in %@", priorityCampaign, self.name]]; + } + } + + [MutuallyExclusiveGroups log:@"priority campaign not defined, caller should continue with normal MEG logic."]; + + // we found nothing + return VALUE_INVALID_PRIORITY_CAMPAIGN; + } + +- (BOOL)isNotAdvanceMEGAllocation { + return (_et != VALUE_ET_ADVANCE); +} + - (void) calculateWeight { - float total = 100; // because 100% + float total = 100; NSUInteger totalCampaigns = self.campaignList.count; @@ -180,7 +268,31 @@ - (NSString *) getCampaignForRespectiveWeight: (NSNumber *) weight{ -- (void) createWeightMap { +- (void)createWeightMap { + if (![self isNotAdvanceMEGAllocation]) { + [MutuallyExclusiveGroups log:[NSString stringWithFormat:@"not using weight from the server, preparing EQUAL allocation because et = %d [ NOTE: et=1->Random, et=2 -> Advance ]", self.et]]; + + [self createEquallyDistributedWeightMap]; + } else { + [MutuallyExclusiveGroups log:@"weight is received from the server, preparing WEIGHTED allocation."]; + [self createWeightMapFromProvidedValues]; + } +} + +- (void)createWeightMapFromProvidedValues { + if (self.weightMap == nil) { + self.weightMap = [[NSMutableDictionary alloc] init]; + } + + [MutuallyExclusiveGroups log:@"morphing weighted allocation data to existing MEG weight format"]; + for (int index = 0; index < [self.weightMapFromServer count]; index++) { + Weight *weight = self.weightMapFromServer[index]; + [self.weightMap setObject:weight.getRange forKey:weight.getCampaign]; + } +} + + +- (void) createEquallyDistributedWeightMap { if (_weightMap == nil) { @@ -213,5 +325,34 @@ - (void) createWeightMap { } +- (void)addWeight:(NSString *)campaign weight:(NSInteger)weight { + + NSLog(@"adding priority weight -> %ld for campaign -> %@", (long)weight, campaign); + + NSMutableArray *weightRange = [NSMutableArray new]; + if (self.weightMapFromServer.count == 0) { + [weightRange addObject:@(0)]; // will start at 0 + [weightRange addObject:@(weight)]; // end + } else { + // last weight's end will be this weight's start + Weight *lastWeight = self.weightMapFromServer.lastObject; + // add range + [weightRange addObject: ([lastWeight getRangeEnd])]; // start will be the end of last entry + NSInteger endWeight = [[lastWeight getRangeEnd] intValue] + weight; + [weightRange addObject: [NSNumber numberWithInteger:endWeight]]; // end will be start + current weight + } + + Weight *weightObject = [[Weight alloc] init:campaign range:weightRange]; + [self.weightMapFromServer addObject:weightObject]; + NSLog(@"campaign %@ range %@ to %@", [weightObject getCampaign], [weightObject getRangeStart], [weightObject getRangeEnd]); +} + +- (BOOL)hasInPriority:(NSString *)campaign { + return [_priorityCampaigns containsObject:campaign]; +} + +- (BOOL)doesNotHaveInPriority:(NSString *)campaign { + return ![self hasInPriority:campaign]; +} @end diff --git a/VWO/MEG/MEGManager.h b/VWO/MEG/MEGManager.h new file mode 100644 index 00000000..73974b04 --- /dev/null +++ b/VWO/MEG/MEGManager.h @@ -0,0 +1,19 @@ +// +// MEGManager.h +// Pods +// +// Created by Harsh Raghav on 15/05/23. +// + +#import "WinnerManager.h" + +@interface MEGManager : NSObject + +//@property (nonatomic, strong) VWO *sSharedInstance; +//@property (nonatomic, strong) VWOLocalData *mVWOLocalData; +@property (nonatomic, strong) WinnerManager *winnerManager; + +//- (instancetype)initWithSharedInstance:(VWO *)sharedInstance; +- (NSString *)getCampaign:(NSString *)userId args:(NSDictionary *)args; + +@end diff --git a/VWO/MEG/MEGManager.m b/VWO/MEG/MEGManager.m new file mode 100644 index 00000000..9b794951 --- /dev/null +++ b/VWO/MEG/MEGManager.m @@ -0,0 +1,110 @@ +// +// MEGManager.m +// VWO +// +// Created by Harsh Raghav on 15/05/23. +// + +#import +#import "MEGManager.h" +#import "MutuallyExclusiveGroups.h" +#import "VWOLogger.h" +#import "VWOCampaign.h" +#import "Group.h" +#import "CampaignGroupMapper.h" +#import "VWOConstants.h" +#import "VWOUserDefaults.h" +#import "WinnerManager.h" +#import "Response.h" +#import "VWOController.h" + +static NSString * const CAMPAIGN_GROUPS = @"groups"; + +@implementation MEGManager + +- (instancetype)init{ + if (self = [super init]) { + self.winnerManager = [[WinnerManager alloc] init]; + } + return self; +} + +- (void)iLog:(NSString *)message { +// [MutuallyExclusiveGroups log:message]; +} +- (NSString *)getCampaign:(NSString *)userId args:(NSDictionary *)args { + + [self iLog:@"trying to figure out MEG winner campaign."]; + + if (userId == nil || [userId length]==0) { + userId = [VWOController.shared getUserId]; + } + + NSMutableDictionary *megGroupsData = [[NSMutableDictionary alloc] init]; + if (megGroupsData == nil) return nil; // MEG data not found + + VWOCampaignArray * campaignsData = [VWOController.shared getCampaignData]; + if (campaignsData == nil || [campaignsData count] == 0) return nil; // MEG data not found + + if (campaignsData != nil && campaignsData.count > 0) { + for (int i = 0; i < campaignsData.count; i++) { + @try { + VWOCampaign *groupDataItem = campaignsData[i]; + if ([groupDataItem type] == CAMPAIGN_GROUPS) { + [megGroupsData setObject:groupDataItem forKey:@"groups"]; + break; + } + } + @catch (NSException *exception) { + VWOLogDebug(@"MutuallyExclusive %@", exception); + + } + } + } + + NSDictionary *mappedData = [CampaignGroupMapper createAndGetGroups: megGroupsData]; + WinnerManager *winnerManager = [[WinnerManager alloc] init]; + Response *localResponse = [winnerManager getSavedDetailsFor:userId args:args]; + if ([localResponse shouldServePreviousWinnerCampaign]) { + // user doesn't exist, should continue processing + NSString *savedWinnerCampaign = [localResponse winnerCampaign]; + NSString *servingNull = (savedWinnerCampaign == nil) ? @"null" : savedWinnerCampaign; + return savedWinnerCampaign; + } + + MutuallyExclusiveGroups *meg = [[MutuallyExclusiveGroups alloc] initMutuallyExclusiveGroups:userId]; + [meg addGroups:mappedData]; + + NSString *winner = [meg getCampaign:args jsonData:campaignsData]; + [winnerManager save:userId winnerCampaign:winner args:args]; + return winner; +} + +- (NSDictionary *)getMEGData:(NSArray *)campaignsData { + NSMutableDictionary *megGroupsData = [[NSMutableDictionary alloc] init]; + + // last item is always the MEG data + NSInteger campaignDataLastIndex = campaignsData.count - 1; + for (NSInteger i = campaignDataLastIndex; i >= 0; i--) { + @try { + NSDictionary *groupDataItem = campaignsData[i]; + + if (![groupDataItem objectForKey:@"campaign_type"]) { + return nil; + } + + NSString *cType = [groupDataItem objectForKey:@"campaign_type"] ?: @""; + if ([ConstGroups isEqualToString:cType]) { + megGroupsData = [NSMutableDictionary dictionaryWithDictionary:groupDataItem]; + break; + } + } + @catch (NSException *exception) { +// [VWOLog eWithMessage:[NSString stringWithFormat:@"%@ %@", VWOLog.DATA_LOGS, exception.description] printLogs:YES]; + } + } + + return megGroupsData; +} + +@end diff --git a/VWO/MEG/Mapping.h b/VWO/MEG/Mapping.h new file mode 100644 index 00000000..956fc39a --- /dev/null +++ b/VWO/MEG/Mapping.h @@ -0,0 +1,20 @@ +// +// Mapping.h +// Pods +// +// Created by Harsh Raghav on 12/05/23. +// + +@interface Mapping : NSObject + +@property(nonatomic, assign)NSString *group; +@property(nonatomic, assign)NSString *testKey; +@property(nonatomic, assign)NSString *winnerCampaign; + +- (void)setGroup:(NSString *)group; +- (void)setTestKey:(NSString *)testKey; +- (void)setWinnerCampaign:(NSString *)winnerCampaign; +- (NSDictionary *)getAsJson; +- (BOOL)isSameAs:(Mapping *)mapping; + +@end diff --git a/VWO/MEG/Mapping.m b/VWO/MEG/Mapping.m new file mode 100644 index 00000000..8aa93122 --- /dev/null +++ b/VWO/MEG/Mapping.m @@ -0,0 +1,63 @@ +// +// Mapping.m +// VWO +// +// Created by Harsh Raghav on 12/05/23. +// + +#import +#import "Mapping.h" +#import "VWOConstants.h" + +@implementation Mapping + +- (void)setGroup:(NSString *)group { + _group = group; +} + +- (void)setTestKey:(NSString *)testKey { + _testKey = testKey; +} + +- (void)setWinnerCampaign:(NSString *)winnerCampaign { + _winnerCampaign = winnerCampaign; +} + +- (BOOL)isSameAs:(Mapping *)mapping { + if(_group != mapping.group){ + return false; + } + if(_testKey != mapping.testKey){ + return false; + } + if(_winnerCampaign != mapping.winnerCampaign){ + return false; + } + return true; +} + +- (NSDictionary *)getAsJson { + NSMutableDictionary *json = [[NSMutableDictionary alloc] init]; + + if (_group == nil) { + [json setObject:@"" forKey:KEY_GROUP]; + } else { + [json setObject:_group forKey:KEY_GROUP]; + } + + if (_testKey == nil) { + [json setObject:@"" forKey:KEY_TEST_KEY]; + } else { + [json setObject:_testKey forKey:KEY_TEST_KEY]; + } + + if (_winnerCampaign == nil) { + [json setObject:@"" forKey:KEY_WINNER_CAMPAIGN]; + } else { + [json setObject:_winnerCampaign forKey:KEY_WINNER_CAMPAIGN]; + } + + return [NSDictionary dictionaryWithDictionary:json]; +} + +@end diff --git a/VWO/MEG/MutuallyExclusiveGroups.h b/VWO/MEG/MutuallyExclusiveGroups.h index 687af7a2..4a2e270b 100644 --- a/VWO/MEG/MutuallyExclusiveGroups.h +++ b/VWO/MEG/MutuallyExclusiveGroups.h @@ -7,6 +7,7 @@ // #import +#import "PriorityQualificationWinnerResult.h" NS_ASSUME_NONNULL_BEGIN @@ -22,6 +23,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)getTestKeyFromCampaignId: (NSString *)campaignId campaignsData: (NSArray *)campaignsData; - (NSString *)getCampaignFromSpecificGroup: (NSString *)groupName; - (NSString *)getGroupNameFromGroupId: (int)groupId; +- (PriorityQualificationWinnerResult *)isQualifiedAsWinner:(NSString *)priorityCampaignId isGroupPassedByUser:(BOOL)isGroupPassedByUser; - (NSString *)getCampaignFromCampaignId: (NSString *)userId campaign: (NSString *)campaign; - (NSString *)getCampaignIfPresent: (NSString *)campaignKey; - (NSNumber *)getNormalizedValue: (NSNumber *)murmurHash; diff --git a/VWO/MEG/MutuallyExclusiveGroups.m b/VWO/MEG/MutuallyExclusiveGroups.m index e9e7cc1c..f7d5360f 100644 --- a/VWO/MEG/MutuallyExclusiveGroups.m +++ b/VWO/MEG/MutuallyExclusiveGroups.m @@ -12,6 +12,9 @@ #include #import "Group.h" #import "VWOLogger.h" +#import "PriorityQualificationWinnerResult.h" +#import "VWOSegmentEvaluator.h" +#import "VWOController.h" @implementation MutuallyExclusiveGroups @@ -78,8 +81,6 @@ - (NSString *)calculateTheWinnerCampaign: (NSDictionary *)args jsonData: (NSArra // there must be at least one type of id // either GROUP or CAMPAIGN - VWOLogDebug(@"MutuallyExclusive The groupId and campaignId both are null"); - return nil; } @@ -96,8 +97,7 @@ - (NSString *)calculateTheWinnerCampaign: (NSDictionary *)args jsonData: (NSArra // if there is no sign of group we can simply use the campaign matching logic - - campaign = [self getCampaignFromCampaignId: userId campaign: campaignId]; + campaign = [self getCampaignFromCampaignId: userId campaign: campaignId]; VWOLogDebug(@"MutuallyExclusive Campaign selected from the mutually exclusive group is [ %@ ]",campaign); @@ -155,7 +155,7 @@ - (NSString *)getCampaignIdFromTestKey: (NSString *)testKey campaignsData: (NSAr @try { NSDictionary *groupDataItem = campaignsData[i]; - VWOCampaign *groupData = groupDataItem; //[groupDataItem objectForKey:CAMPAIGN_TYPE] ; + VWOCampaign *groupData = [[VWOCampaign alloc] initWithDictionary:groupDataItem]; if([[groupData type] isEqual:TYPE_VISUAL_AB]){ if([[groupData testKey] isEqual: testKey]){ @@ -195,7 +195,7 @@ - (NSString *)getTestKeyFromCampaignId: (NSString *)campaignId campaignsData: (N @try { NSDictionary *groupDataItem = campaignsData[i]; - VWOCampaign *groupData = groupDataItem; + VWOCampaign *groupData = [[VWOCampaign alloc] initWithDictionary:groupDataItem]; if([[groupData type] isEqual:TYPE_VISUAL_AB]){ if([[NSString stringWithFormat:@"%d",[groupData iD]] isEqual: [NSString stringWithFormat:@"%@",campaignId]]){ @@ -253,9 +253,146 @@ - (NSString *)getCampaignFromSpecificGroup: (NSString *)groupName{ Group *interestedGroup = CAMPAIGN_GROUPS[groupName]; if (interestedGroup == nil) return nil; + + // evaluate all the priority campaigns + NSLog(@"----------- { BEGIN } Priority Campaign Evaluation -----------"); + NSArray *priorityCampaignsInGroup = [interestedGroup getPriorityCampaigns]; + if (priorityCampaignsInGroup.count == 0) { + NSLog(@"> there are 0 priority campaigns"); + } + for (int i = 0; i < priorityCampaignsInGroup.count; i++) { + NSString *priorityCampaign = priorityCampaignsInGroup[i]; + NSLog(@"now evaluating priority campaign ( p ) @ index %d -> %@", i, priorityCampaign); + PriorityQualificationWinnerResult *result = [self isQualifiedAsWinner:priorityCampaign isGroupPassedByUser:YES]; + if ([result isQualified]) { + NSLog(@"found a winner campaign from the priority campaign list -> %@", priorityCampaign); + return priorityCampaign; + } + } + NSLog(@"----------- { END } Priority Campaign Evaluation -----------"); + + NSLog(@"none of the priority campaigns are qualified as winners, next will try to check for weighted campaign."); + return [interestedGroup getCampaignForRespectiveWeight:normalizedValue]; +} + +- (PriorityQualificationWinnerResult *)isQualifiedAsWinner:(NSString *)priorityCampaignId isGroupPassedByUser:(BOOL)isGroupPassedByUser { + + BOOL priorityIsNull = ([priorityCampaignId isEqual: @""]); + if (priorityIsNull) { + VWOLogDebug(@"the passed priority campaign id is null, will not qualify."); + + PriorityQualificationWinnerResult *result = [[PriorityQualificationWinnerResult alloc] init]; + result.qualified = NO; + result.groupInPriority = isGroupPassedByUser; + result.priorityCampaignFound = NO; + return result; + } + + @try { + + VWOCampaignArray * vwoData = [VWOController.shared getCampaignData]; + if (vwoData == nil || [vwoData count] == 0){ + VWOLogDebug(@"INCONSISTENT STATE detected, local data for VWO is not present."); + + PriorityQualificationWinnerResult *result = [[PriorityQualificationWinnerResult alloc] init]; + result.qualified = NO; + result.groupInPriority = isGroupPassedByUser; + result.priorityCampaignFound = NO; + return result; + + } + + VWOLogDebug(@"> evaluating each campaign from campaign list to check if they are qualified"); + BOOL isPriorityCampaignFoundLocally = NO; + for (int i = 0; i < vwoData.count; i++) { + VWOCampaign *campaign = vwoData[i]; + + BOOL isPriorityCampaignValid = [self isPriorityValid:campaign priorityCampaignId:priorityCampaignId]; + + if (!isGroupPassedByUser && !isPriorityCampaignValid) { + // skip + VWOLogDebug(@"will not evaluate -> %@ as it is redundant to do so", [campaign iD]); + continue; + } + + if (isPriorityCampaignValid) { + // avoid assigning false once it is true because we want to know that + // if campaignId and priorityCampaignId matched at some point + isPriorityCampaignFoundLocally = YES; + } + + if ([self isSegmentationValid:campaign] && [self isVariationValid:campaign] && isPriorityCampaignValid) { + + PriorityQualificationWinnerResult *result = [[PriorityQualificationWinnerResult alloc] init]; + result.qualified = YES; + result.groupInPriority = isGroupPassedByUser; + result.priorityCampaignFound = YES; + return result; + } + + // at this point the campaign did not qualify so if no group was passed then we can stop this loop + // this optimizes our runtime cost as { null } will be returned after loop cases are exhausted + if (!isGroupPassedByUser) { + break; + } + + PriorityQualificationWinnerResult *result = [[PriorityQualificationWinnerResult alloc] init]; + result.qualified = NO; + result.groupInPriority = isGroupPassedByUser; + result.priorityCampaignFound = isPriorityCampaignFoundLocally; + return result; + } + } + @catch (NSException *exception) { + PriorityQualificationWinnerResult *result = [[PriorityQualificationWinnerResult alloc] init]; + result.qualified = NO; + result.groupInPriority = isGroupPassedByUser; + result.priorityCampaignFound = NO; + return result; + } + PriorityQualificationWinnerResult *result = [[PriorityQualificationWinnerResult alloc] init]; + result.qualified = NO; + result.groupInPriority = isGroupPassedByUser; + result.priorityCampaignFound = NO; + return result; +} - return [interestedGroup getCampaignForRespectiveWeight:normalizedValue]; +- (BOOL) isVariationValid:(VWOCampaign *)campaign { + BOOL isVariationNotNull = [campaign variation] != nil; + BOOL isVariationValid = (isVariationNotNull && [[campaign variation] iD] > 0); + if (isVariationValid) { + VWOLogDebug(@"VALID | variation id -> %ld", [[campaign variation] iD]); + } else { + if (isVariationNotNull) { + VWOLogDebug(@"INVALID | variation id -> %ld", [[campaign variation] iD]); + } + } + return isVariationValid; +} +- (BOOL) isSegmentationValid:(VWOCampaign *)campaign { + VWOSegment *segmentObject = [[VWOSegment alloc] initWithDictionary:[campaign segmentObject]]; + BOOL isSegmentationValid = NO; + if(segmentObject){ + VWOSegmentEvaluator *segmentEvaluator = [[VWOSegmentEvaluator alloc] init]; + isSegmentationValid = [segmentEvaluator evaluate:segmentObject]; + } + if (isSegmentationValid) { + VWOLogDebug(@"VALID | segmentation checks"); + } else { + VWOLogDebug(@"INVALID | segmentation checks"); + } + return isSegmentationValid; +} + +- (BOOL)isPriorityValid:(VWOCampaign *)campaign priorityCampaignId:(NSString *)priorityCampaignId { + BOOL isSameAsPriority = ([campaign iD] == [priorityCampaignId longLongValue]); + if (isSameAsPriority) { + VWOLogDebug(@"VALID | campaignId -> %@ priorityCampaignId -> %@", [campaign iD], priorityCampaignId); + } else { + VWOLogDebug(@"INVALID | campaignId -> %@ priorityCampaignId -> %@", [campaign iD], priorityCampaignId); + } + return isSameAsPriority; } -(NSString *)getGroupNameFromGroupId: (int)groupId{ @@ -307,11 +444,30 @@ - (NSString *)getCampaignFromCampaignId: (NSString *)userId campaign: (NSString Group *interestedGroup = CAMPAIGN_GROUPS[campaignFoundInGroup]; - if (interestedGroup == nil) + if (interestedGroup == nil) return nil; // basic null check because NSDictionary is being used - return nil; // basic null check because NSDictionary is being used + // check if this campaign is in priority list + // if not found there's no point in evaluating the list + if ([interestedGroup hasInPriority:campaign]) { - + VWOLogDebug(@"%@ found in priority campaign list.", campaign); + + // evaluate priority campaigns + // here the campaign is the priorityCampaign because we are targeting the specific campaign + PriorityQualificationWinnerResult *result = [self isQualifiedAsWinner:campaign isGroupPassedByUser:NO]; + if ([result isQualified]) { + VWOLogDebug(@"winner campaign found from the priority campaign list -> %@", campaign); + return campaign; + } + + // check if we found the related campaign and still unqualified + if ([result isPriorityCampaignFound] && [result isNotQualified]) { + VWOLogDebug(@"priority campaign was found but was not qualified for winning, will simply return { null } from this point."); + return nil; + } + } else { + VWOLogDebug(@"priority campaigns does not have campaign -> %@, skipping redundant checks for optimization.", campaign); + } NSString *finalCampaign = [interestedGroup getCampaignForRespectiveWeight: normalizedValue]; @@ -393,7 +549,7 @@ + (void)log: (NSString *)message{ if (IS_LOGS_SHOWN) { - NSLog(TAG, message); +// NSLog(TAG, message); } diff --git a/VWO/MEG/Pair.h b/VWO/MEG/Pair.h new file mode 100644 index 00000000..9165d48b --- /dev/null +++ b/VWO/MEG/Pair.h @@ -0,0 +1,24 @@ +// +// Pair.h +// Pods +// +// Created by Harsh Raghav on 16/05/23. +// + +#import +#import "Mapping.h" + +typedef NS_ENUM(NSInteger, LocalUserSearchRemark) { + ShouldReturnNull, + NotFoundForPassedArgs, + ShouldReturnWinnerCampaign +}; + +@interface Pair : NSObject + +@property (nonatomic, strong) NSNumber *first; +@property (nonatomic, strong) id second; + +- (instancetype)initWithFirst:(NSNumber *)first second:(id)second; + +@end diff --git a/VWO/MEG/Pair.m b/VWO/MEG/Pair.m new file mode 100644 index 00000000..a051be35 --- /dev/null +++ b/VWO/MEG/Pair.m @@ -0,0 +1,22 @@ +// +// Pair.m +// VWO +// +// Created by Harsh Raghav on 16/05/23. +// + +#import +#import "Pair.h" + +@implementation Pair + +- (instancetype)initWithFirst:(NSNumber *)first second:(id)second { + self = [super init]; + if (self) { + _first = first; + _second = second; + } + return self; +} + +@end diff --git a/VWO/MEG/PriorityQualificationWinnerResult.h b/VWO/MEG/PriorityQualificationWinnerResult.h new file mode 100644 index 00000000..6225cf18 --- /dev/null +++ b/VWO/MEG/PriorityQualificationWinnerResult.h @@ -0,0 +1,19 @@ +// +// PriorityQualificationWinnerResult.h +// Pods +// +// Created by Harsh Raghav on 05/05/23. +// + +@interface PriorityQualificationWinnerResult : NSObject + +- (BOOL)isGroupInPriority; +- (BOOL)isPriorityCampaignFound; +- (BOOL)isQualified; +- (BOOL)isNotQualified; +- (void)setGroupInPriority:(BOOL)groupInPriority; +- (void)setPriorityCampaignFound:(BOOL)priorityCampaignFound; +- (void)setQualified:(BOOL)qualified; +- (BOOL)shouldContinueWithFurtherChecks; + +@end diff --git a/VWO/MEG/PriorityQualificationWinnerResult.m b/VWO/MEG/PriorityQualificationWinnerResult.m new file mode 100644 index 00000000..58f9f8d0 --- /dev/null +++ b/VWO/MEG/PriorityQualificationWinnerResult.m @@ -0,0 +1,70 @@ +// +// PriorityQualificationWinnerResult.m +// VWO +// +// Created by Harsh Raghav on 05/05/23. +// + +#import "PriorityQualificationWinnerResult.h" + +@interface PriorityQualificationWinnerResult () + +// Private instance variable + +/** +* To identify whether this check was done for {groupId} or just the {test_key}. +*/ +@property (nonatomic, assign) BOOL isGroupInPriority; + +/** + * Will be set true when all the conditions are met. When this flag is set to true, all the conditions are + * satisfied and the related campaign can be a winner. + */ +@property (nonatomic, assign) BOOL isQualified; + +/** + * Will be true if the priority campaign was found, this will be helpful for optimization + * when a campaign was found but was not qualified as a winner. + */ +@property (nonatomic, assign) BOOL isPriorityCampaignFound; + +@end + +@implementation PriorityQualificationWinnerResult + + +- (BOOL)isGroupInPriority { + return _isGroupInPriority; +} + +- (BOOL)isPriorityCampaignFound { + return _isPriorityCampaignFound; +} + +- (BOOL)isQualified { + return _isQualified; +} + +- (BOOL)isNotQualified { + return !_isQualified; +} + +- (void)setGroupInPriority:(BOOL)groupInPriority { + _isGroupInPriority = groupInPriority; +} + +- (void)setPriorityCampaignFound:(BOOL)priorityCampaignFound { + _isPriorityCampaignFound = priorityCampaignFound; +} + +- (void)setQualified:(BOOL)qualified { + _isQualified = qualified; +} + +- (BOOL)shouldContinueWithFurtherChecks { + // if true will continue with the unequal weight distribution + // if false will return null from that point itself. + return (_isGroupInPriority && !_isQualified); +} + +@end diff --git a/VWO/MEG/Response.h b/VWO/MEG/Response.h new file mode 100644 index 00000000..f3f4d6cf --- /dev/null +++ b/VWO/MEG/Response.h @@ -0,0 +1,20 @@ +// +// Response.h +// Pods +// +// Created by Harsh Raghav on 12/05/23. +// + +@interface Response : NSObject + +@property (nonatomic, assign) BOOL shouldServePreviousWinnerCampaign; +@property (nonatomic, assign) BOOL isNewUser; +@property (nonatomic, strong) NSString *winnerCampaign; + +- (BOOL)shouldServePreviousWinnerCampaign; +- (void)setShouldServePreviousWinnerCampaign:(BOOL)shouldServePreviousWinnerCampaign; +- (void)setWinnerCampaign:(NSString *)winnerCampaig; +- (BOOL)isNewUser; +- (void)setNewUser:(BOOL)isNewUser; + +@end diff --git a/VWO/MEG/Response.m b/VWO/MEG/Response.m new file mode 100644 index 00000000..9aed1efb --- /dev/null +++ b/VWO/MEG/Response.m @@ -0,0 +1,24 @@ +// +// Response.m +// VWO +// +// Created by Harsh Raghav on 12/05/23. +// + +#import +#import "Response.h" + +@implementation Response +- (void)setShouldServePreviousWinnerCampaign:(BOOL)shouldServePreviousWinnerCampaign { + _shouldServePreviousWinnerCampaign = shouldServePreviousWinnerCampaign; +} + +- (void)setWinnerCampaign:(NSString *)winnerCampaign { + _winnerCampaign = winnerCampaign; +} + +- (void)setNewUser:(BOOL)isNewUser { + _isNewUser = isNewUser; +} + +@end diff --git a/VWO/MEG/Weight.h b/VWO/MEG/Weight.h new file mode 100644 index 00000000..dee440ce --- /dev/null +++ b/VWO/MEG/Weight.h @@ -0,0 +1,17 @@ +// +// Weight.h +// Pods +// +// Created by Harsh Raghav on 04/05/23. +// + + +@interface Weight : NSObject + +- (instancetype)init:(NSString *)UserId range:(NSArray *)range; +- (NSString *)getCampaign; +- (NSNumber *)getRangeStart; +- (NSNumber *)getRangeEnd; +- (NSArray *)getRange; + +@end diff --git a/VWO/MEG/Weight.m b/VWO/MEG/Weight.m new file mode 100644 index 00000000..cdf1645f --- /dev/null +++ b/VWO/MEG/Weight.m @@ -0,0 +1,47 @@ +// +// Weight.m +// VWO +// +// Created by Harsh Raghav on 04/05/23. +// + +#import "Weight.h" + +@interface Weight () + +// Private instance variable +@property (nonatomic, strong) NSString *campaign; +@property (nonatomic, strong) NSArray *range; + +@end + +@implementation Weight + +- (instancetype)init:(NSString *)UserId range:(NSArray *)range +{ + self = [super init]; + if (self) { + // Initialize private properties + _campaign = UserId; + _range = range; + } + return self; +} + +- (NSString *)getCampaign { + return _campaign; +} + +- (NSNumber *)getRangeStart { + return _range[0]; +} + +- (NSNumber *)getRangeEnd { + return _range[1]; +} + +- (NSArray *)getRange { + return _range; +} + +@end diff --git a/VWO/MEG/Winner.h b/VWO/MEG/Winner.h new file mode 100644 index 00000000..b3d13b50 --- /dev/null +++ b/VWO/MEG/Winner.h @@ -0,0 +1,25 @@ +// +// Winner.h +// Pods +// +// Created by Harsh Raghav on 05/05/23. +// + +#import +#import "Mapping.h" +#import "Pair.h" + +@interface Winner : NSObject + +extern NSString const *ID_GROUP; +extern NSString const *TEST_KEY; +extern NSString const *KEY_USER; + +@property (nonatomic, copy) NSString *user; + +- (Winner *)fromJSONObject:(NSDictionary *)jsonObject; +- (void)addMapping:(Mapping *)mapping; +- (NSDictionary *)getJSONObject; +- (Pair *)getRemarkForUserArgs:(Mapping *)mapping args:(NSDictionary *)args; + +@end diff --git a/VWO/MEG/Winner.m b/VWO/MEG/Winner.m new file mode 100644 index 00000000..53595c09 --- /dev/null +++ b/VWO/MEG/Winner.m @@ -0,0 +1,137 @@ +// +// Winner.m +// VWO +// +// Created by Harsh Raghav on 05/05/23. +// + +#import +#import "VWOConstants.h" +#import "Winner.h" +#import "Pair.h" +#import "Mapping.h" +#import "VWOLogger.h" + +@implementation Winner +NSString *user; + +NSMutableArray *mappings; + +- (instancetype)init +{ + self = [super init]; + if (self) { + // Initialize private properties + mappings = [NSMutableArray array]; + } + return self; +} + +- (Winner *)fromJSONObject:(NSDictionary *)jsonObject { + Winner *winner = [[Winner alloc] init]; + + @try { + winner.user = [jsonObject objectForKey:KEY_USER]; + mappings = [[NSMutableArray alloc] init]; + + NSArray *jMappings = [jsonObject objectForKey:KEY_MAPPING]; + NSInteger jMappingSize = jMappings.count; + for (int i = 0; i < jMappingSize; i++) { + NSDictionary *jMapping = [jMappings objectAtIndex:i]; + + Mapping *_mapping = [[Mapping alloc] init]; + _mapping.testKey = [jMapping objectForKey:KEY_TEST_KEY]; + _mapping.group = [jMapping objectForKey:KEY_GROUP]; + _mapping.winnerCampaign = [jMapping objectForKey:KEY_WINNER_CAMPAIGN]; + + [mappings addObject:_mapping]; + } + } @catch (NSException *exception) { + // Handle the exception + VWOLogDebug(@"MutuallyExclusive %@", exception); + } + + return winner; +} + +- (void)setUser:(NSString *)user { + _user = user; +} + +- (void)addMapping:(Mapping *)mapping { + NSLog(@"%@", [mapping getAsJson]); + + BOOL found = NO; + for (Mapping *m in mappings) { + if ([m isSameAs:mapping]) { + found = YES; + break; + } + } + + if (!found) { + [mappings addObject:mapping]; + } +} + +- (NSDictionary *)getJSONObject { + NSMutableDictionary *json = [NSMutableDictionary new]; + if (user != nil) { + [json setValue:user forKey:@"user"]; + } + + NSMutableArray *mappingArray = [NSMutableArray new]; + for (Mapping *mapping in mappings) { + NSDictionary *mappingJson = [mapping getAsJson]; + if (mappingJson != nil) { + [mappingArray addObject:mappingJson]; + } + } + + if (mappingArray.count > 0) { + [json setValue:mappingArray forKey:@"mapping"]; + } + + return json; +} + +- (Pair *)getRemarkForUserArgs:(Mapping *)mapping args:(NSDictionary *)args { + + BOOL isGroupIdPresent = ![args[ID_GROUP] isEqualToString:@""]; + BOOL isTestKeyPresent = ![args[TEST_KEY] isEqualToString:@""]; + + if (!isGroupIdPresent && !isTestKeyPresent) { + // there's no point in evaluating the stored values if both are null + // as this is a user error + LocalUserSearchRemark local = NotFoundForPassedArgs; + return [[Pair alloc] initWithFirst:@(NotFoundForPassedArgs) second:@""]; + } + + NSString *empty = @""; + + for (Mapping *m in mappings) { + + // because "" = null for mappings + NSString *group = [empty isEqualToString:[m group]] ? nil : [m group]; + + BOOL isGroupSame = [group isEqualToString:[mapping group]]; + BOOL isTestKeySame = [[m testKey] isEqualToString:[m testKey]]; + + if (isGroupIdPresent && isGroupSame) { + // cond 1. if { groupId } is PRESENT then there is no need to check for the { test_key } + if ([empty isEqualToString:[m winnerCampaign]]) { + return [[Pair alloc] initWithFirst:@(ShouldReturnNull) second:@""]; + } + return [[Pair alloc] initWithFirst:@(ShouldReturnWinnerCampaign) second:@""]; + } else if (!isGroupIdPresent && isTestKeySame) { + // cond 2. if { groupId } is NOT PRESENT then then check for the { test_key } + if ([empty isEqualToString:[m testKey]]) { + return [[Pair alloc] initWithFirst:@(ShouldReturnNull) second:@""]; + } + return [[Pair alloc] initWithFirst:@(ShouldReturnWinnerCampaign) second:[m winnerCampaign]]; + } + } + return [[Pair alloc] initWithFirst:@(NotFoundForPassedArgs) second:@""]; +} + +@end diff --git a/VWO/MEG/WinnerManager.h b/VWO/MEG/WinnerManager.h new file mode 100644 index 00000000..9c761875 --- /dev/null +++ b/VWO/MEG/WinnerManager.h @@ -0,0 +1,14 @@ +// +// WinnerManager.h +// Pods +// +// Created by Harsh Raghav on 09/05/23. +// +#import "Response.h" + +@interface WinnerManager : NSObject + +- (Response *)getSavedDetailsFor:(NSString *)userId args:(NSDictionary *)args; +- (BOOL)save:(NSString *)userId winnerCampaign:(NSString *)winnerCampaign args:(NSDictionary *)args; + +@end diff --git a/VWO/MEG/WinnerManager.m b/VWO/MEG/WinnerManager.m new file mode 100644 index 00000000..e714178c --- /dev/null +++ b/VWO/MEG/WinnerManager.m @@ -0,0 +1,181 @@ +// +// WinnerManager.m +// VWO +// +// Created by Harsh Raghav on 09/05/23. +// + +#import +#import "WinnerManager.h" +#import "Mapping.h" +#import "Winner.h" +#import "Pair.h" +#import "Response.h" +#import "VWOUserDefaults.h" + +@implementation WinnerManager + +static NSString *const KEY_SAVED_ARRAY_OF_WINNER_CAMPAIGNS = @"winner_mappings"; + +- (BOOL)isEmpty:(NSArray *)root { + return (root.count == 0); +} + +- (Response *)getSavedDetailsFor:(NSString *)userId args:(NSDictionary *)args { + @try { + // check if this user is present locally + NSString *previousWinnerLocalData = [VWOUserDefaults objectForKey:KEY_SAVED_ARRAY_OF_WINNER_CAMPAIGNS]; + NSInteger userIndex = [self getIndexIfUserExist:userId previousWinnerLocalData:previousWinnerLocalData]; + if (userIndex == -1) { + Response *response = [[Response alloc] init]; + response.newUser = YES; + return response; + } + NSArray *root = [NSJSONSerialization JSONObjectWithData:[previousWinnerLocalData dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil]; + NSDictionary *user = [root objectAtIndex:userIndex]; + Winner *winner = [[[Winner alloc] init] fromJSONObject:user]; + + // prepare groupId and test_key + NSString *groupId = [args objectForKey:ID_GROUP]; + NSString *testKey = nil; + + if (groupId == nil) { + // test_key will only be applicable when there is no groupId + testKey = [args objectForKey:TEST_KEY]; + } + + Mapping *mapping = [self prepareWinnerMappingUsing:groupId testKey:testKey winnerCampaign:nil]; + Pair *remarkWithResult = [winner getRemarkForUserArgs:mapping args:args]; + + LocalUserSearchRemark remark = remarkWithResult.first.integerValue; + if (remark == ShouldReturnWinnerCampaign) { + Response *response = [[Response alloc] init]; + response.newUser = NO; + response.shouldServePreviousWinnerCampaign = YES; + response.winnerCampaign = (NSString *)remarkWithResult.second; + return response; + } else if (remark == ShouldReturnNull) { + Response *response = [[Response alloc] init]; + response.newUser = NO; + response.shouldServePreviousWinnerCampaign = YES; + response.winnerCampaign = nil; + return response; + } else { + // treat this block as -> (Winner_LocalUserSearchRemark_NOT_FOUND_FOR_PASSED_ARGS) + // we did not find anything related to the provided args + // we should treat this like a new user and MEG should be applied. + Response *response = [[Response alloc] init]; + response.newUser = YES; + response.shouldServePreviousWinnerCampaign = NO; + response.winnerCampaign = nil; + return response; + } + } @catch (NSException *exception) { + return nil; + } +} + +- (BOOL)save:(NSString *)userId winnerCampaign:(NSString *)winnerCampaign args:(NSDictionary *)args { + @try { + [self saveThrowingException:userId winnerCampaign:winnerCampaign args:args]; + return YES; + } @catch (NSException *exception) { + return NO; + } +} + +- (void)saveThrowingException:(NSString *)userId winnerCampaign:(NSString *)winnerCampaign args:(NSDictionary *)args { + NSMutableArray *root = [[NSMutableArray alloc] init]; + + // check for existing data in the shared preferences + NSString *previousWinnerLocalData = [VWOUserDefaults objectForKey:KEY_SAVED_ARRAY_OF_WINNER_CAMPAIGNS]; + if (![previousWinnerLocalData isEqualToString:@""]) { + NSError *jsonError = nil; + root = [[NSMutableArray alloc] initWithArray:[NSJSONSerialization JSONObjectWithData:[previousWinnerLocalData dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&jsonError]]; + if (jsonError) { + return; + } + } + + // if there is groupId then campaign will be ignored altogether + NSString *groupId = [args objectForKey:ID_GROUP]; + NSString *testKey = nil; + + if (groupId == nil) { + // test_key will only be applicable when there is no groupId + testKey = [args objectForKey:TEST_KEY]; + } + + if ([root count] == 0) { + Winner *firstWinner = [self prepareWinnerUsing:userId winnerCampaign:winnerCampaign groupId:groupId testKey:testKey]; + [root addObject:[firstWinner getJSONObject]]; + [self storeLocally:root]; + return; + } + + NSInteger index = [self getIndexIfUserExist:userId previousWinnerLocalData:previousWinnerLocalData]; + if (index == -1) { + // this user didn't exist treat as new + Winner *firstWinner = [self prepareWinnerUsing:userId winnerCampaign:winnerCampaign groupId:groupId testKey:testKey]; + [root addObject:[firstWinner getJSONObject]]; + [self storeLocally:root]; + return; + } + + // existing user exist at index simply update that index + NSDictionary *current = [root objectAtIndex:index]; + Winner *currentWinner = [[[Winner alloc] init] fromJSONObject:current]; + + // try to add new values if it doesn't already exist + Mapping *mapping = [self prepareWinnerMappingUsing:groupId testKey:testKey winnerCampaign:winnerCampaign]; + [currentWinner addMapping:mapping]; + + // replace with new value just in case + [root replaceObjectAtIndex:index withObject:[currentWinner getJSONObject]]; + [self storeLocally:root]; +} + +- (void)storeLocally:(NSArray *)root { + NSError *error; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:root options:NSJSONWritingPrettyPrinted error:&error]; + NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + [VWOUserDefaults setObject:jsonString forKey:KEY_SAVED_ARRAY_OF_WINNER_CAMPAIGNS]; +} + +- (NSInteger)getIndexIfUserExist:(NSString *)userId previousWinnerLocalData:(NSString *)previousWinnerLocalData { + NSInteger index = -1; + NSError *error; + NSData *jsonData = [previousWinnerLocalData dataUsingEncoding:NSUTF8StringEncoding]; + NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error]; + if (error == nil) { + for (int i = 0; i < [jsonArray count]; i++) { + NSDictionary *current = [jsonArray objectAtIndex:i]; +// [jMapping objectForKey:KEY_TEST_KEY] + if ([[current objectForKey:KEY_USER] isEqualToString:userId]) { + index = i; + break; + } + } + } + return index; +} + +- (Winner *)prepareWinnerUsing:(NSString *)userId winnerCampaign:(NSString *)winnerCampaign groupId:(NSString *)groupId testKey:(NSString *)testKey { + Winner *winner = [[Winner alloc] init]; + [winner setUser:userId]; + + Mapping *mapping = [self prepareWinnerMappingUsing:groupId testKey:testKey winnerCampaign:winnerCampaign]; + [winner addMapping:mapping]; + + return winner; +} + +- (Mapping *)prepareWinnerMappingUsing:(NSString *)groupId testKey:(NSString *)testKey winnerCampaign:(NSString *)winnerCampaign { + Mapping *mapping = [[Mapping alloc] init]; + [mapping setGroup:groupId]; + [mapping setTestKey:testKey]; + [mapping setWinnerCampaign:winnerCampaign]; + return mapping; +} + +@end diff --git a/VWO/VWO.m b/VWO/VWO.m index 1cf80400..618e5a7c 100644 --- a/VWO/VWO.m +++ b/VWO/VWO.m @@ -13,6 +13,7 @@ #import "MutuallyExclusiveGroups.h" #import "Group.h" #import "VWOCampaign.h" +#import "MEGManager.h" static VWOLogLevel kLogLevel = VWOLogLevelError; static BOOL kOptOut = NO; @@ -202,36 +203,8 @@ + (nullable NSString *)variationNameForTestKey:(NSString *)campaignTestKey { } + (NSString *)getCampaign:(NSString *)userId args:(NSDictionary *)args { - - if (userId == nil || [userId length]==0) { - userId = [VWOController.shared getUserId]; - } - - VWOCampaignArray * campaignsData = [VWOController.shared getCampaignData]; - - 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 type] == CAMPAIGN_GROUPS) { - [megGroupsData setObject:groupDataItem forKey:@"groups"]; - break; - } - } - @catch (NSException *exception) { - VWOLogDebug(@"MutuallyExclusive %@", exception); - - } - } - } - - NSDictionary *mappedData = [CampaignGroupMapper createAndGetGroups: megGroupsData]; - - MutuallyExclusiveGroups *meg = [[MutuallyExclusiveGroups alloc] initMutuallyExclusiveGroups:userId]; - [meg addGroups:mappedData]; - return [meg getCampaign:args jsonData:campaignsData]; + MEGManager *megManager = [[MEGManager alloc] init]; + return [megManager getCampaign:userId args:args]; } + (void)trackConversion:(NSString *)goal { diff --git a/VWO/VWOConstants.h b/VWO/VWOConstants.h index a84490c1..9eb218e5 100644 --- a/VWO/VWOConstants.h +++ b/VWO/VWOConstants.h @@ -15,5 +15,11 @@ extern NSString const *ConstType; extern NSString const *ConstCampaigns; extern NSString const *ConstCollectionPrefix; extern NSString const *ConstAPIVersion; +extern NSString const *KEY_GROUP; +extern NSString const *KEY_TEST_KEY; +extern NSString const *KEY_WINNER_CAMPAIGN; +extern NSString const *KEY_USER; +extern NSString const *KEY_MAPPING; +extern NSString const *ID_GROUP; @end diff --git a/VWO/VWOConstants.m b/VWO/VWOConstants.m index e0a0904b..e8de1411 100644 --- a/VWO/VWOConstants.m +++ b/VWO/VWOConstants.m @@ -23,4 +23,12 @@ @implementation VWOConstants: NSObject //for making API calls NSString const *ConstAPIVersion = @"3"; +//for MEG +NSString const *KEY_GROUP = @"group"; +NSString const *KEY_TEST_KEY = @"test_key"; +NSString const *KEY_WINNER_CAMPAIGN = @"winner_campaign"; +NSString const *KEY_USER = @"user"; +NSString const *KEY_MAPPING = @"mapping"; +NSString const *ID_GROUP = @"groupId"; + @end diff --git a/VWO/VWOSegmentEvaluator.h b/VWO/VWOSegmentEvaluator.h index da2314e8..a03932de 100644 --- a/VWO/VWOSegmentEvaluator.h +++ b/VWO/VWOSegmentEvaluator.h @@ -8,6 +8,7 @@ #import #import "VWODevice.h" +#import "VWOSegment.h" NS_ASSUME_NONNULL_BEGIN @@ -24,6 +25,7 @@ NS_ASSUME_NONNULL_BEGIN @property uint screenHeight; - (BOOL)canUserBePartOfCampaignForSegment:(nullable NSDictionary *)segment; +- (BOOL)evaluate:(VWOSegment *) segment; + (VWOSegmentEvaluator *)makeEvaluator:(NSDictionary *)customVariables; @end diff --git a/VWO/VWOUserDefaults.h b/VWO/VWOUserDefaults.h index cc83dced..6e9ef6a2 100644 --- a/VWO/VWOUserDefaults.h +++ b/VWO/VWOUserDefaults.h @@ -25,6 +25,9 @@ NS_ASSUME_NONNULL_BEGIN @property (class, readonly) NSString *UUID; @property (class, readonly) NSString *CollectionPrefix; ++ (nullable id)objectForKey:(NSString *)key; ++ (void)setObject:(nullable id)value forKey:(NSString *)key; + + (void)setExcludedCampaign:(VWOCampaign *)campaign; + (BOOL)isTrackingUserForCampaign:(VWOCampaign *)campaign; From 3566d9f38117547231cbcece0f4f1bfe8586450c Mon Sep 17 00:00:00 2001 From: Harsh Raghav Date: Thu, 18 May 2023 08:32:39 +0530 Subject: [PATCH 2/5] uncommented group ID code --- VWO/MEG/CampaignGroupMapper.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VWO/MEG/CampaignGroupMapper.m b/VWO/MEG/CampaignGroupMapper.m index 3920808c..012c98ba 100644 --- a/VWO/MEG/CampaignGroupMapper.m +++ b/VWO/MEG/CampaignGroupMapper.m @@ -57,7 +57,7 @@ + (NSDictionary *)createAndGetGroups: (NSDictionary *)jsonObject{ Group *group = [[Group alloc]init]; group.name = groupName; -// group.Id = key.intValue; + group.Id = key.intValue; [self prepareWeight:objGroup destination:group]; [self prepareCampaigns:objGroup destination:group]; From d03e8a96632948ade6174d67a9ef7af619b895c6 Mon Sep 17 00:00:00 2001 From: Harsh Raghav Date: Thu, 18 May 2023 11:49:16 +0530 Subject: [PATCH 3/5] error fix and warning removal --- VWO/MEG/MEGManager.m | 1 - VWO/MEG/Winner.h | 4 ---- VWO/MEG/Winner.m | 7 ++++--- VWO/MEG/WinnerManager.m | 13 +++++++++---- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/VWO/MEG/MEGManager.m b/VWO/MEG/MEGManager.m index 9b794951..39b91a13 100644 --- a/VWO/MEG/MEGManager.m +++ b/VWO/MEG/MEGManager.m @@ -68,7 +68,6 @@ - (NSString *)getCampaign:(NSString *)userId args:(NSDictionary *)args { if ([localResponse shouldServePreviousWinnerCampaign]) { // user doesn't exist, should continue processing NSString *savedWinnerCampaign = [localResponse winnerCampaign]; - NSString *servingNull = (savedWinnerCampaign == nil) ? @"null" : savedWinnerCampaign; return savedWinnerCampaign; } diff --git a/VWO/MEG/Winner.h b/VWO/MEG/Winner.h index b3d13b50..33241baa 100644 --- a/VWO/MEG/Winner.h +++ b/VWO/MEG/Winner.h @@ -11,10 +11,6 @@ @interface Winner : NSObject -extern NSString const *ID_GROUP; -extern NSString const *TEST_KEY; -extern NSString const *KEY_USER; - @property (nonatomic, copy) NSString *user; - (Winner *)fromJSONObject:(NSDictionary *)jsonObject; diff --git a/VWO/MEG/Winner.m b/VWO/MEG/Winner.m index 53595c09..7b66b242 100644 --- a/VWO/MEG/Winner.m +++ b/VWO/MEG/Winner.m @@ -97,13 +97,14 @@ - (NSDictionary *)getJSONObject { - (Pair *)getRemarkForUserArgs:(Mapping *)mapping args:(NSDictionary *)args { - BOOL isGroupIdPresent = ![args[ID_GROUP] isEqualToString:@""]; - BOOL isTestKeyPresent = ![args[TEST_KEY] isEqualToString:@""]; + NSString *nonConstID_GROUP = [ID_GROUP copy]; + NSString *nonConstKEY_TEST_KEY = [KEY_TEST_KEY copy]; + BOOL isGroupIdPresent = ![args[nonConstID_GROUP] isEqualToString:@""]; + BOOL isTestKeyPresent = ![args[nonConstKEY_TEST_KEY] isEqualToString:@""]; if (!isGroupIdPresent && !isTestKeyPresent) { // there's no point in evaluating the stored values if both are null // as this is a user error - LocalUserSearchRemark local = NotFoundForPassedArgs; return [[Pair alloc] initWithFirst:@(NotFoundForPassedArgs) second:@""]; } diff --git a/VWO/MEG/WinnerManager.m b/VWO/MEG/WinnerManager.m index e714178c..9625497e 100644 --- a/VWO/MEG/WinnerManager.m +++ b/VWO/MEG/WinnerManager.m @@ -12,6 +12,7 @@ #import "Pair.h" #import "Response.h" #import "VWOUserDefaults.h" +#import "VWOConstants.h" @implementation WinnerManager @@ -36,12 +37,14 @@ - (Response *)getSavedDetailsFor:(NSString *)userId args:(NSDictionary Date: Tue, 23 May 2023 12:12:48 +0530 Subject: [PATCH 4/5] some code fixes added --- Demo/VWO Demo/AppDelegate.swift | 2 +- .../Sorting campaign/PhoneListVC.swift | 7 +++ Demo/VWO Demo/VWOManager.swift | 1 + VWO/MEG/Group.m | 2 +- VWO/MEG/MEGManager.m | 5 +- VWO/MEG/MutuallyExclusiveGroups.m | 56 +++++++++++-------- VWO/Models/VWOVariation.h | 1 + VWO/Models/VWOVariation.m | 9 ++- 8 files changed, 51 insertions(+), 32 deletions(-) diff --git a/Demo/VWO Demo/AppDelegate.swift b/Demo/VWO Demo/AppDelegate.swift index e9896339..0d0cac0d 100644 --- a/Demo/VWO Demo/AppDelegate.swift +++ b/Demo/VWO Demo/AppDelegate.swift @@ -22,7 +22,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // setCurrentViewController(vc: houseNav) setCurrentViewController(vc: phoneNav) -// VWOManager.launch("033cf474a02bb4aafea16dc0685c896a-11000006") + VWOManager.launch("1e3e3391505171af754858fc088f50ce-5021085") return true } diff --git a/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift b/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift index 846b56e8..b5285052 100644 --- a/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift +++ b/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift @@ -65,6 +65,13 @@ class PhoneListVC: UIViewController { break } tableView.reloadData() + + let TestKey = VWO.getCampaign("e57e8bd1-fb5f-478d-80d2-5127eb5d79f7", args: ["groupId":"36"]) +// let TestKey = VWO.getCampaign("e57e8bd1-fb5f-478d-80d2-5127eb5d79f7", args: ["test_key":"camp5Harsh", "groupId":"36"]) +// let TestKey = VWO.getCampaign("e57e8bd1-fb5f-478d-80d2-5127eb5d79f7", args: ["test_key":"camp5Harsh"]) + print("Harsh TestKey @%",TestKey) +// let TestKey = VWO.getCampaign("e57e8bd1-fb5f-478d-80d2-5127eb5d79f7", args: ["test_key":"ME123"]) +// VWO.getCampaign("HarshUserID", args: ["test_key":"ME123","groupId":"8"]) } } diff --git a/Demo/VWO Demo/VWOManager.swift b/Demo/VWO Demo/VWOManager.swift index e28deb29..57a7c130 100644 --- a/Demo/VWO Demo/VWOManager.swift +++ b/Demo/VWO Demo/VWOManager.swift @@ -31,6 +31,7 @@ class VWOManager { SCLAlertView().showSuccess("Success", subTitle: "VWO launched successfully \(apiKey)") var str: String? = nil; VWO.variationNameFor(testKey: str ?? "") + VWO.trackConversion("", value: 10.0) } }, failure: { (errorString) in diff --git a/VWO/MEG/Group.m b/VWO/MEG/Group.m index bf610ef4..e804a02c 100644 --- a/VWO/MEG/Group.m +++ b/VWO/MEG/Group.m @@ -269,7 +269,7 @@ - (NSString *) getCampaignForRespectiveWeight: (NSNumber *) weight{ - (void)createWeightMap { - if (![self isNotAdvanceMEGAllocation]) { + if ([self isNotAdvanceMEGAllocation]) { [MutuallyExclusiveGroups log:[NSString stringWithFormat:@"not using weight from the server, preparing EQUAL allocation because et = %d [ NOTE: et=1->Random, et=2 -> Advance ]", self.et]]; [self createEquallyDistributedWeightMap]; diff --git a/VWO/MEG/MEGManager.m b/VWO/MEG/MEGManager.m index 39b91a13..c2bd967c 100644 --- a/VWO/MEG/MEGManager.m +++ b/VWO/MEG/MEGManager.m @@ -17,8 +17,7 @@ #import "WinnerManager.h" #import "Response.h" #import "VWOController.h" - -static NSString * const CAMPAIGN_GROUPS = @"groups"; +#import "VWOConstants.h" @implementation MEGManager @@ -50,7 +49,7 @@ - (NSString *)getCampaign:(NSString *)userId args:(NSDictionary *)args { for (int i = 0; i < campaignsData.count; i++) { @try { VWOCampaign *groupDataItem = campaignsData[i]; - if ([groupDataItem type] == CAMPAIGN_GROUPS) { + if ([groupDataItem type] == ConstGroups) { [megGroupsData setObject:groupDataItem forKey:@"groups"]; break; } diff --git a/VWO/MEG/MutuallyExclusiveGroups.m b/VWO/MEG/MutuallyExclusiveGroups.m index f7d5360f..c4aa630d 100644 --- a/VWO/MEG/MutuallyExclusiveGroups.m +++ b/VWO/MEG/MutuallyExclusiveGroups.m @@ -149,13 +149,18 @@ - (NSString *)getCampaignIdFromTestKey: (NSString *)testKey campaignsData: (NSAr if (campaignsData.count== 0) return nil; + + VWOCampaignArray * campaignsArray = [VWOController.shared getCampaignData]; + if(campaignsArray.count==0) return nil; + + for (int i = 0; i < campaignsArray.count; i++) { - for (int i = 0; i < campaignsData.count; i++) { - + VWOCampaign *groupData = campaignsArray[i]; + NSLog(@"Testing MEG Priority groupCamp%@",groupData); +// VWOCampaign *groupData = [[VWOCampaign alloc] initWithDictionary:groupDataItem]; + @try { - NSDictionary *groupDataItem = campaignsData[i]; - VWOCampaign *groupData = [[VWOCampaign alloc] initWithDictionary:groupDataItem]; if([[groupData type] isEqual:TYPE_VISUAL_AB]){ if([[groupData testKey] isEqual: testKey]){ @@ -190,12 +195,14 @@ - (NSString *)getTestKeyFromCampaignId: (NSString *)campaignId campaignsData: (N if (campaignsData.count== 0) return nil; - - for (int i = 0; i < campaignsData.count; i++) { + VWOCampaignArray * campaignsArray = [VWOController.shared getCampaignData]; + if(campaignsArray.count==0) return nil; + + for (int i = 0; i < campaignsArray.count; i++) { @try { - NSDictionary *groupDataItem = campaignsData[i]; - VWOCampaign *groupData = [[VWOCampaign alloc] initWithDictionary:groupDataItem]; + VWOCampaign *groupData = campaignsArray[i]; +// VWOCampaign *groupData = [[VWOCampaign alloc] initWithDictionary:groupDataItem]; if([[groupData type] isEqual:TYPE_VISUAL_AB]){ if([[NSString stringWithFormat:@"%d",[groupData iD]] isEqual: [NSString stringWithFormat:@"%@",campaignId]]){ @@ -248,7 +255,7 @@ - (NSString *)getCampaignFromSpecificGroup: (NSString *)groupName{ NSNumber *normalizedValue = [self getNormalizedValue:murmurHash]; - VWOLogDebug(@"MutuallyExclusive Normalized value for user with userID -> %@ is ",userId,normalizedValue); + VWOLogDebug(@"MutuallyExclusive Normalized value for user with userID -> %@ is %@",userId,normalizedValue); Group *interestedGroup = CAMPAIGN_GROUPS[groupName]; @@ -332,15 +339,15 @@ - (PriorityQualificationWinnerResult *)isQualifiedAsWinner:(NSString *)priorityC // at this point the campaign did not qualify so if no group was passed then we can stop this loop // this optimizes our runtime cost as { null } will be returned after loop cases are exhausted - if (!isGroupPassedByUser) { - break; - } - - PriorityQualificationWinnerResult *result = [[PriorityQualificationWinnerResult alloc] init]; - result.qualified = NO; - result.groupInPriority = isGroupPassedByUser; - result.priorityCampaignFound = isPriorityCampaignFoundLocally; - return result; +// if (!isGroupPassedByUser) { +// break; +// } +// +// PriorityQualificationWinnerResult *result = [[PriorityQualificationWinnerResult alloc] init]; +// result.qualified = NO; +// result.groupInPriority = isGroupPassedByUser; +// result.priorityCampaignFound = isPriorityCampaignFoundLocally; +// return result; } } @catch (NSException *exception) { @@ -386,12 +393,13 @@ - (BOOL) isSegmentationValid:(VWOCampaign *)campaign { } - (BOOL)isPriorityValid:(VWOCampaign *)campaign priorityCampaignId:(NSString *)priorityCampaignId { - BOOL isSameAsPriority = ([campaign iD] == [priorityCampaignId longLongValue]); - if (isSameAsPriority) { - VWOLogDebug(@"VALID | campaignId -> %@ priorityCampaignId -> %@", [campaign iD], priorityCampaignId); - } else { - VWOLogDebug(@"INVALID | campaignId -> %@ priorityCampaignId -> %@", [campaign iD], priorityCampaignId); - } + NSLog(@"Testing MEG Priority %d %lld",[campaign iD],[priorityCampaignId longLongValue]); + BOOL isSameAsPriority = ([campaign iD] == [priorityCampaignId intValue]); +// if (isSameAsPriority) { +// VWOLogDebug(@"VALID | campaignId -> %@ priorityCampaignId -> %d", [campaign iD], [priorityCampaignId intValue]); +// } else { +// VWOLogDebug(@"INVALID | campaignId -> %@ priorityCampaignId -> %d", [campaign iD], [priorityCampaignId intValue]); +// } return isSameAsPriority; } diff --git a/VWO/Models/VWOVariation.h b/VWO/Models/VWOVariation.h index 8b8429be..33e4df48 100644 --- a/VWO/Models/VWOVariation.h +++ b/VWO/Models/VWOVariation.h @@ -13,6 +13,7 @@ NS_ASSUME_NONNULL_BEGIN @interface VWOVariation : NSObject @property(nonatomic, assign) int iD; +@property(nonatomic, assign) int weight; @property(atomic) NSString *name; @property(atomic, nullable) NSDictionary *changes; diff --git a/VWO/Models/VWOVariation.m b/VWO/Models/VWOVariation.m index 591a1e85..de757232 100644 --- a/VWO/Models/VWOVariation.m +++ b/VWO/Models/VWOVariation.m @@ -13,21 +13,23 @@ static NSString * kId = @"id"; static NSString * kName = @"name"; static NSString * kChanges = @"changes"; +static NSString * kWeight = @"weight"; @implementation VWOVariation -- (instancetype)initWith:(int)iD name:(NSString *)name changes:(NSDictionary * _Nullable)changes { +- (instancetype)initWith:(int)iD name:(NSString *)name changes:(NSDictionary * _Nullable)changes weight:(int)weight { NSParameterAssert(name); if (self = [self init]) { self.iD = iD; self.name = name; self.changes = changes; + self.weight = weight; } return self; } - (instancetype)initWithDictionary:(NSDictionary *) variationDict { - NSArray *missingKeys = [variationDict keysMissingFrom:@[kId, kName]]; + NSArray *missingKeys = [variationDict keysMissingFrom:@[kId, kName, kWeight]]; if (missingKeys.count > 0) { VWOLogException(@"Keys missing [%@] for Variation JSON %@", [missingKeys componentsJoinedByString:@", "], @@ -38,12 +40,13 @@ - (instancetype)initWithDictionary:(NSDictionary *) variationDict { int iD = [variationDict[kId] intValue]; NSString *name = variationDict[kName]; NSDictionary *changes = variationDict[kChanges]; + int weight = [variationDict[kWeight] intValue]; /* ** IMP ** In case of variation type control, Union of keys of all other variation are sent with nil values changes dictionary stores the value as [NSNull null], beacuse setting it to 'nil' would remove the key value pair */ - return [self initWith:iD name:name changes:changes]; + return [self initWith:iD name:name changes:changes weight:weight]; } - (BOOL)isControl { From 2b04d27bbe9be67c453085f524a8fdb56261b69d Mon Sep 17 00:00:00 2001 From: Parvesh Chauhan Date: Tue, 23 May 2023 15:39:56 +0530 Subject: [PATCH 5/5] segment issue fix --- Demo/Podfile.lock | 10 ++-- Demo/VWO Demo.xcodeproj/project.pbxproj | 4 +- .../Sorting campaign/PhoneListVC.swift | 2 +- VWO.xcodeproj/project.pbxproj | 52 ++++++++++++++++++- VWO/MEG/MutuallyExclusiveGroups.m | 11 ++-- VWO/VWOURL.m | 2 +- 6 files changed, 67 insertions(+), 14 deletions(-) diff --git a/Demo/Podfile.lock b/Demo/Podfile.lock index b80be442..2b137fd9 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.12.0): - - VWO/All (= 2.12.0) - - VWO/All (2.12.0): + - VWO (2.13.0): + - VWO/All (= 2.13.0) + - VWO/All (2.13.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: 1662974fcb0e06541218a16d807ebad78ae22cb3 + VWO: 133e9f3eea4c39c85e4cf85d46dfcb7cb5428b25 PODFILE CHECKSUM: 71bfa555223ee2b1ddab35d14fef462b5e6267e6 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/Demo/VWO Demo.xcodeproj/project.pbxproj b/Demo/VWO Demo.xcodeproj/project.pbxproj index 49aedb7b..c31c6141 100644 --- a/Demo/VWO Demo.xcodeproj/project.pbxproj +++ b/Demo/VWO Demo.xcodeproj/project.pbxproj @@ -512,7 +512,7 @@ DEVELOPMENT_TEAM = DBN26NC8KE; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "VWO Demo/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.3.7; PRODUCT_BUNDLE_IDENTIFIER = com.wingify.abtestapp; @@ -533,7 +533,7 @@ DEVELOPMENT_TEAM = DBN26NC8KE; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "VWO Demo/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.3.7; PRODUCT_BUNDLE_IDENTIFIER = com.wingify.abtestapp; diff --git a/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift b/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift index b5285052..45416993 100644 --- a/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift +++ b/Demo/VWO Demo/Sorting campaign/PhoneListVC.swift @@ -43,7 +43,7 @@ class PhoneListVC: UIViewController { // 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"]) +// let TestKey = VWO.getCampaign("9c3832ad-15f9-420a-93cd-a7f2cde0f7bc", args: ["test_key":"ME123","groupId":"8"]) destination.phone = phoneList[t] } diff --git a/VWO.xcodeproj/project.pbxproj b/VWO.xcodeproj/project.pbxproj index 1b1bdd9a..a5a5ae79 100644 --- a/VWO.xcodeproj/project.pbxproj +++ b/VWO.xcodeproj/project.pbxproj @@ -20,6 +20,18 @@ 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 */; }; + BA6899102A1C9BFE0003E538 /* WinnerManager.h in Headers */ = {isa = PBXBuildFile; fileRef = BA6899042A1C9BFE0003E538 /* WinnerManager.h */; }; + BA6899112A1C9BFE0003E538 /* Pair.m in Sources */ = {isa = PBXBuildFile; fileRef = BA6899052A1C9BFE0003E538 /* Pair.m */; }; + BA6899122A1C9BFE0003E538 /* PriorityQualificationWinnerResult.h in Headers */ = {isa = PBXBuildFile; fileRef = BA6899062A1C9BFE0003E538 /* PriorityQualificationWinnerResult.h */; }; + BA6899132A1C9BFE0003E538 /* PriorityQualificationWinnerResult.m in Sources */ = {isa = PBXBuildFile; fileRef = BA6899072A1C9BFE0003E538 /* PriorityQualificationWinnerResult.m */; }; + BA6899142A1C9BFE0003E538 /* Winner.m in Sources */ = {isa = PBXBuildFile; fileRef = BA6899082A1C9BFE0003E538 /* Winner.m */; }; + BA6899152A1C9BFE0003E538 /* Winner.h in Headers */ = {isa = PBXBuildFile; fileRef = BA6899092A1C9BFE0003E538 /* Winner.h */; }; + BA6899162A1C9BFE0003E538 /* WinnerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BA68990A2A1C9BFE0003E538 /* WinnerManager.m */; }; + BA6899172A1C9BFE0003E538 /* Response.h in Headers */ = {isa = PBXBuildFile; fileRef = BA68990B2A1C9BFE0003E538 /* Response.h */; }; + BA6899182A1C9BFE0003E538 /* Pair.h in Headers */ = {isa = PBXBuildFile; fileRef = BA68990C2A1C9BFE0003E538 /* Pair.h */; }; + BA6899192A1C9BFE0003E538 /* Weight.h in Headers */ = {isa = PBXBuildFile; fileRef = BA68990D2A1C9BFE0003E538 /* Weight.h */; }; + BA68991A2A1C9BFE0003E538 /* Weight.m in Sources */ = {isa = PBXBuildFile; fileRef = BA68990E2A1C9BFE0003E538 /* Weight.m */; }; + BA68991B2A1C9BFE0003E538 /* Response.m in Sources */ = {isa = PBXBuildFile; fileRef = BA68990F2A1C9BFE0003E538 /* Response.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 */; }; @@ -160,6 +172,18 @@ 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 = ""; }; + BA6899042A1C9BFE0003E538 /* WinnerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WinnerManager.h; sourceTree = ""; }; + BA6899052A1C9BFE0003E538 /* Pair.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pair.m; sourceTree = ""; }; + BA6899062A1C9BFE0003E538 /* PriorityQualificationWinnerResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PriorityQualificationWinnerResult.h; sourceTree = ""; }; + BA6899072A1C9BFE0003E538 /* PriorityQualificationWinnerResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PriorityQualificationWinnerResult.m; sourceTree = ""; }; + BA6899082A1C9BFE0003E538 /* Winner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Winner.m; sourceTree = ""; }; + BA6899092A1C9BFE0003E538 /* Winner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Winner.h; sourceTree = ""; }; + BA68990A2A1C9BFE0003E538 /* WinnerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WinnerManager.m; sourceTree = ""; }; + BA68990B2A1C9BFE0003E538 /* Response.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Response.h; sourceTree = ""; }; + BA68990C2A1C9BFE0003E538 /* Pair.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Pair.h; sourceTree = ""; }; + BA68990D2A1C9BFE0003E538 /* Weight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Weight.h; sourceTree = ""; }; + BA68990E2A1C9BFE0003E538 /* Weight.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Weight.m; sourceTree = ""; }; + BA68990F2A1C9BFE0003E538 /* Response.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Response.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; }; @@ -294,6 +318,18 @@ 7B165AF82936AEA70060EEE2 /* MEG */ = { isa = PBXGroup; children = ( + BA68990C2A1C9BFE0003E538 /* Pair.h */, + BA6899052A1C9BFE0003E538 /* Pair.m */, + BA6899062A1C9BFE0003E538 /* PriorityQualificationWinnerResult.h */, + BA6899072A1C9BFE0003E538 /* PriorityQualificationWinnerResult.m */, + BA68990B2A1C9BFE0003E538 /* Response.h */, + BA68990F2A1C9BFE0003E538 /* Response.m */, + BA68990D2A1C9BFE0003E538 /* Weight.h */, + BA68990E2A1C9BFE0003E538 /* Weight.m */, + BA6899092A1C9BFE0003E538 /* Winner.h */, + BA6899082A1C9BFE0003E538 /* Winner.m */, + BA6899042A1C9BFE0003E538 /* WinnerManager.h */, + BA68990A2A1C9BFE0003E538 /* WinnerManager.m */, 7B165B03293756EE0060EEE2 /* MutuallyExclusiveGroups.h */, 7B165B04293756EE0060EEE2 /* MutuallyExclusiveGroups.m */, 7B165B0729376B6E0060EEE2 /* Group.h */, @@ -537,6 +573,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + BA6899192A1C9BFE0003E538 /* Weight.h in Headers */, + BA6899182A1C9BFE0003E538 /* Pair.h in Headers */, 7B6B7E09294094AA00961493 /* VWOGroup.h in Headers */, E39815851F6291970035D519 /* VWOLogger.h in Headers */, E39815591F6291680035D519 /* VWOGoal.h in Headers */, @@ -556,10 +594,14 @@ E35484F51F713C2E00E14D2E /* NSURLSession+Synchronous.h in Headers */, E3529574206B720A00C90716 /* VWOSocketConnector.h in Headers */, E3E95E8D206D0B13006DCE6C /* VWOConfig.h in Headers */, + BA6899102A1C9BFE0003E538 /* WinnerManager.h in Headers */, 7B165B1129376BA30060EEE2 /* CampaignGroupMapper.h in Headers */, + BA6899152A1C9BFE0003E538 /* Winner.h in Headers */, + BA6899172A1C9BFE0003E538 /* Response.h in Headers */, E398155F1F6291680035D519 /* VWOVariation.h in Headers */, E39567A3206E5DA80030B30B /* VWOCampaignFetcher.h in Headers */, E3BBA3602068FA630046B2F2 /* VWOSocket.h in Headers */, + BA6899122A1C9BFE0003E538 /* PriorityQualificationWinnerResult.h in Headers */, E398157F1F6291970035D519 /* VWOController.h in Headers */, E39815931F6291C10035D519 /* VWOSegmentEvaluator.h in Headers */, E3D5F7FD1F6BD86C0011C43C /* VWOURL.h in Headers */, @@ -708,9 +750,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BA68991B2A1C9BFE0003E538 /* Response.m in Sources */, E344B2F11F87906900A4EEE6 /* VWOUserDefaults.m in Sources */, 7B165B1629383B1D0060EEE2 /* MurmurHash.m in Sources */, 7B165B06293756EE0060EEE2 /* MutuallyExclusiveGroups.m in Sources */, + BA6899132A1C9BFE0003E538 /* PriorityQualificationWinnerResult.m in Sources */, E3D29FC71FBEDAB300A11C61 /* NSDate+VWO.m in Sources */, E3871A1E212FE27C0033D86B /* NSString+MD5.m in Sources */, E36AC7191F75614E0041ACDF /* VWO.m in Sources */, @@ -721,6 +765,8 @@ 7B6B7E0A294094AA00961493 /* VWOGroup.m in Sources */, 7B165B0A29376B6E0060EEE2 /* Group.m in Sources */, E3E95E8E206D0B13006DCE6C /* VWOConfig.m in Sources */, + BA6899142A1C9BFE0003E538 /* Winner.m in Sources */, + BA6899112A1C9BFE0003E538 /* Pair.m in Sources */, E35484F61F713C2E00E14D2E /* NSURLSession+Synchronous.m in Sources */, E398159D1F6293BC0035D519 /* NSDictionary+VWO.m in Sources */, E39815861F6291970035D519 /* VWOLogger.m in Sources */, @@ -730,6 +776,7 @@ E3529575206B720A00C90716 /* VWOSocketConnector.m in Sources */, E3D5F7FE1F6BD86C0011C43C /* VWOURL.m in Sources */, E39815801F6291970035D519 /* VWOController.m in Sources */, + BA6899162A1C9BFE0003E538 /* WinnerManager.m in Sources */, 7B165B1229376BA30060EEE2 /* CampaignGroupMapper.m in Sources */, E398155C1F6291680035D519 /* VWOQueue.m in Sources */, E3F7CBA92004A07800CC8C03 /* VWOInfixEvaluator.m in Sources */, @@ -739,6 +786,7 @@ E39815581F6291680035D519 /* VWOCampaign.m in Sources */, E3F7CBB52004C91400CC8C03 /* VWOSegment.m in Sources */, E39815841F6291970035D519 /* VWOFile.m in Sources */, + BA68991A2A1C9BFE0003E538 /* Weight.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -927,7 +975,7 @@ ); INFOPLIST_FILE = VWO/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -959,7 +1007,7 @@ ); INFOPLIST_FILE = VWO/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/VWO/MEG/MutuallyExclusiveGroups.m b/VWO/MEG/MutuallyExclusiveGroups.m index c4aa630d..6564f09d 100644 --- a/VWO/MEG/MutuallyExclusiveGroups.m +++ b/VWO/MEG/MutuallyExclusiveGroups.m @@ -378,16 +378,21 @@ - (BOOL) isVariationValid:(VWOCampaign *)campaign { } - (BOOL) isSegmentationValid:(VWOCampaign *)campaign { - VWOSegment *segmentObject = [[VWOSegment alloc] initWithDictionary:[campaign segmentObject]]; - BOOL isSegmentationValid = NO; + BOOL isSegmentationValid = TRUE; + VWOSegmentEvaluator *segmentEvaluator = [[VWOSegmentEvaluator alloc] init]; + + NSArray *partialSegment = (NSArray *)[campaign segmentObject][@"partialSegments"]; + VWOSegment *segmentObject = [[VWOSegment alloc] initWithDictionary:partialSegment[0]]; + if(segmentObject){ - VWOSegmentEvaluator *segmentEvaluator = [[VWOSegmentEvaluator alloc] init]; isSegmentationValid = [segmentEvaluator evaluate:segmentObject]; } + if (isSegmentationValid) { VWOLogDebug(@"VALID | segmentation checks"); } else { VWOLogDebug(@"INVALID | segmentation checks"); + return FALSE; } return isSegmentationValid; } diff --git a/VWO/VWOURL.m b/VWO/VWOURL.m index 65ec97bf..8eb436d5 100644 --- a/VWO/VWOURL.m +++ b/VWO/VWOURL.m @@ -20,7 +20,7 @@ @implementation NSURLComponents (VWO) /// Creates URL component with scheme host and path. Eg: https://dacdn.visual.com/path + (instancetype)vwoComponentForPath:(NSString *)path isChinaCDN:(BOOL)isChinaCDN { NSURLComponents *components = [NSURLComponents new]; - [components setScheme:@"https"]; + [components setScheme:@"http"]; if (isChinaCDN) { [components setHost:@"cdn-cn.vwo-analytics.com"]; } else {