Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(audienceeval): Multiple fixes #355

Merged
merged 10 commits into from
Jan 16, 2019
Merged
6 changes: 6 additions & 0 deletions OptimizelySDKCore/OptimizelySDKCore.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@
C77958C6219BFBC800B4CA89 /* OPTLYNSObject+Validation.m in Sources */ = {isa = PBXBuildFile; fileRef = C77958C1219BFBA000B4CA89 /* OPTLYNSObject+Validation.m */; };
C779881321CBC22A002AAEC8 /* OPTLYValidationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C779881221CBC22A002AAEC8 /* OPTLYValidationTest.m */; };
C779881421CBC22A002AAEC8 /* OPTLYValidationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C779881221CBC22A002AAEC8 /* OPTLYValidationTest.m */; };
C781DEA921E8A44400EF35EC /* audience_targeting.json in Resources */ = {isa = PBXBuildFile; fileRef = C781DEA821E8A44400EF35EC /* audience_targeting.json */; };
C781DEAA21E8A44400EF35EC /* audience_targeting.json in Resources */ = {isa = PBXBuildFile; fileRef = C781DEA821E8A44400EF35EC /* audience_targeting.json */; };
C78F98B7219ADEA600808062 /* OPTLYAudienceBaseCondition.h in Headers */ = {isa = PBXBuildFile; fileRef = C78F98B4219ADE9600808062 /* OPTLYAudienceBaseCondition.h */; settings = {ATTRIBUTES = (Public, ); }; };
C78F98B8219ADEA700808062 /* OPTLYAudienceBaseCondition.h in Headers */ = {isa = PBXBuildFile; fileRef = C78F98B4219ADE9600808062 /* OPTLYAudienceBaseCondition.h */; settings = {ATTRIBUTES = (Public, ); }; };
C78F98B9219ADEAB00808062 /* OPTLYAudienceBaseCondition.m in Sources */ = {isa = PBXBuildFile; fileRef = C78F98B5219ADE9600808062 /* OPTLYAudienceBaseCondition.m */; };
Expand Down Expand Up @@ -647,6 +649,7 @@
C77958C0219BFBA000B4CA89 /* OPTLYNSObject+Validation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OPTLYNSObject+Validation.h"; sourceTree = "<group>"; };
C77958C1219BFBA000B4CA89 /* OPTLYNSObject+Validation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OPTLYNSObject+Validation.m"; sourceTree = "<group>"; };
C779881221CBC22A002AAEC8 /* OPTLYValidationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OPTLYValidationTest.m; sourceTree = "<group>"; };
C781DEA821E8A44400EF35EC /* audience_targeting.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = audience_targeting.json; sourceTree = "<group>"; };
C78F98B4219ADE9600808062 /* OPTLYAudienceBaseCondition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OPTLYAudienceBaseCondition.h; sourceTree = "<group>"; };
C78F98B5219ADE9600808062 /* OPTLYAudienceBaseCondition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OPTLYAudienceBaseCondition.m; sourceTree = "<group>"; };
C7ACD4FD218C2E4A008EC52E /* typed_audience_datafile.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = typed_audience_datafile.json; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1197,6 +1200,7 @@
EA2FAB941DC6FDFA00B1D81B /* TestData */ = {
isa = PBXGroup;
children = (
C781DEA821E8A44400EF35EC /* audience_targeting.json */,
C7ACD4FD218C2E4A008EC52E /* typed_audience_datafile.json */,
EA2FAB951DC6FDFA00B1D81B /* BucketerTestsDatafile.json */,
3E92800D1F26AD4700214C58 /* BucketerTestsDatafile2.json */,
Expand Down Expand Up @@ -1705,6 +1709,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C781DEA921E8A44400EF35EC /* audience_targeting.json in Resources */,
EA2FABCC1DC6FDFA00B1D81B /* optimizely_6372300739.json in Resources */,
EA2FABD81DC6FDFA00B1D81B /* optimizely_7519590183.json in Resources */,
EA2FABD51DC6FDFA00B1D81B /* test_data_50_experiments.json in Resources */,
Expand All @@ -1731,6 +1736,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C781DEAA21E8A44400EF35EC /* audience_targeting.json in Resources */,
3E92800E1F26AD4700214C58 /* BucketerTestsDatafile2.json in Resources */,
EA2FABCD1DC6FDFA00B1D81B /* optimizely_6372300739.json in Resources */,
EA2FABD91DC6FDFA00B1D81B /* optimizely_7519590183.json in Resources */,
Expand Down
13 changes: 5 additions & 8 deletions OptimizelySDKCore/OptimizelySDKCore/OPTLYAudience.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,12 @@ - (void)setConditionsWithNSArray:(NSArray *)array {
}

- (nullable NSNumber *)evaluateConditionsWithAttributes:(NSDictionary<NSString *, NSObject *> *)attributes projectConfig:(nullable OPTLYProjectConfig *)config {
for (NSObject<OPTLYCondition> *condition in self.conditions) {
NSNumber *result = [condition evaluateConditionsWithAttributes:attributes projectConfig:config];
if (result != NULL && [result boolValue] == true) {
// if user satisfies any conditions, return true.
return [NSNumber numberWithBool:true];
}

NSObject<OPTLYCondition> *condition = (NSObject<OPTLYCondition> *)[self.conditions firstObject];
if (condition) {
return [condition evaluateConditionsWithAttributes:attributes projectConfig:config];
}
// if user doesn't satisfy any conditions, return false.
return [NSNumber numberWithBool:false];
return nil;
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,7 @@ - (nullable NSNumber *)evaluateConditionsWithAttributes:(NSDictionary<NSString *
}

OPTLYAudience *audience = [config getAudienceForId:self.audienceId];
BOOL areAttributesValid = [[audience evaluateConditionsWithAttributes:attributes projectConfig:config] boolValue];
if (areAttributesValid) {
return [NSNumber numberWithBool:true];;
}

return [NSNumber numberWithBool:false];
return [audience evaluateConditionsWithAttributes:attributes projectConfig:config];
}

@end
2 changes: 1 addition & 1 deletion OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
/// Condition name
@property (nonatomic, strong) NSString *name;
/// Condition type
@property (nonatomic, strong) NSString *type;
@property (nonatomic, strong, nullable) NSString<OPTLYOptional> *type;
/// Condition value
@property (nonatomic, strong, nullable) NSObject<OPTLYOptional> *value;
/// Condition match type
Expand Down
23 changes: 6 additions & 17 deletions OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,7 @@ + (OPTLYJSONKeyMapper*)keyMapper
}

+ (BOOL) isBaseConditionJSON:(NSData *)jsonData {
if (![jsonData isKindOfClass:[NSDictionary class]]) {
return false;
}
else {
NSDictionary *dict = (NSDictionary *)jsonData;

if (dict[OPTLYDatafileKeysConditionName] != nil &&
dict[OPTLYDatafileKeysConditionType] != nil) {
return true;
}
return false;
}
return [jsonData isKindOfClass:[NSDictionary class]];
}

-(nullable NSNumber *)evaluateMatchTypeExact:(NSDictionary<NSString *, NSObject *> *)attributes{
Expand Down Expand Up @@ -116,15 +105,15 @@ -(nullable NSNumber *)evaluateCustomMatchType:(NSDictionary<NSString *, NSObject
//Check if given type is the required type
return NULL;
}
else if (!self.match || [self.match isEqualToString:@""]){
//Check if given match is empty, if so, opt for legacy Exact Matching
self.match = OPTLYDatafileKeysMatchTypeExact;
}
else if (self.value == NULL && ![self.match isEqualToString:OPTLYDatafileKeysMatchTypeExists]){
//Check if given value is null, which is only acceptable if match type is Exists
return NULL;
}

if (!self.match || [self.match isEqualToString:@""]){
//Check if given match is empty, if so, opt for legacy Exact Matching
self.match = OPTLYDatafileKeysMatchTypeExact;
}

SWITCH(self.match){
CASE(OPTLYDatafileKeysMatchTypeExact) {
return [self evaluateMatchTypeExact: attributes];
Expand Down
95 changes: 42 additions & 53 deletions OptimizelySDKCore/OptimizelySDKCore/OPTLYCondition.m
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ @implementation OPTLYCondition

// need to check if the jsonArray is actually an array, otherwise, something is wrong with the audience condition
if (![jsonArray isKindOfClass:[NSArray class]]) {
if ([jsonArray isKindOfClass:[NSDictionary class]] && [OPTLYBaseCondition isBaseConditionJSON:((NSData *)jsonArray)]) {
if ([OPTLYBaseCondition isBaseConditionJSON:((NSData *)jsonArray)]) {
mutableJsonArray = [[NSMutableArray alloc] initWithArray:@[OPTLYDatafileKeysOrCondition,jsonArray]];
}
else {
Expand All @@ -61,11 +61,10 @@ @implementation OPTLYCondition
[mutableJsonArray insertObject:OPTLYDatafileKeysOrCondition atIndex:0];
}

if ([OPTLYBaseCondition isBaseConditionJSON:mutableJsonArray[1]]) { //base case condition

// generate all base conditions
NSMutableArray<OPTLYCondition> *conditions = (NSMutableArray<OPTLYCondition> *)[[NSMutableArray alloc] initWithCapacity:(mutableJsonArray.count - 1)];
for (int i = 1; i < mutableJsonArray.count; i++) {
NSMutableArray<OPTLYCondition> *conditions = (NSMutableArray<OPTLYCondition> *)[[NSMutableArray alloc] initWithCapacity:(mutableJsonArray.count - 1)];
for (int i = 1; i < mutableJsonArray.count; i++) {
if ([OPTLYBaseCondition isBaseConditionJSON: mutableJsonArray[i]]) {
// generate base condition
NSDictionary *info = mutableJsonArray[i];
NSError *err = nil;
OPTLYBaseCondition *condition = [[OPTLYBaseCondition alloc] initWithDictionary:info
Expand All @@ -78,41 +77,33 @@ @implementation OPTLYCondition
[conditions addObject:condition];
}
}
continue;
}

// return an (And/Or/Not) Condition handling the base conditions
NSObject<OPTLYCondition> *condition = [OPTLYCondition createConditionInstanceOfClass:mutableJsonArray[0]
withConditions:conditions];
return (NSArray<OPTLYCondition *><OPTLYCondition> *)@[condition];
}
else {

// further condition arrays to deserialize
NSMutableArray<OPTLYCondition> *subConditions = (NSMutableArray<OPTLYCondition> *)[[NSMutableArray alloc] initWithCapacity:(mutableJsonArray.count - 1)];
for (int i = 1; i < mutableJsonArray.count; i++) {
NSError *err = nil;
NSArray *deserializedJsonObject = [OPTLYCondition deserializeJSONArray:mutableJsonArray[i] error:&err];

if (err) {
*error = err;
return nil;
}

if (deserializedJsonObject != nil) {
[subConditions addObjectsFromArray:deserializedJsonObject];
}
NSError *err = nil;
NSArray *deserializedJsonObject = [OPTLYCondition deserializeJSONArray:mutableJsonArray[i] error:&err];
if (err) {
*error = err;
return nil;
}
if (deserializedJsonObject != nil) {
[conditions addObjectsFromArray:deserializedJsonObject];
}
NSObject<OPTLYCondition> *condition = [OPTLYCondition createConditionInstanceOfClass:mutableJsonArray[0]
withConditions:subConditions];
return (NSArray<OPTLYCondition *><OPTLYCondition> *)@[condition];
}

// return an (And/Or/Not) Condition handling the base conditions
NSObject<OPTLYCondition> *condition = [OPTLYCondition createConditionInstanceOfClass:mutableJsonArray[0]
withConditions:conditions];
return (NSArray<OPTLYCondition *><OPTLYCondition> *)@[condition];
}

// example jsonArray:
// "[\"and\", [\"or\", \"3468206642\", \"3988293898\"], [\"or\", \"3988293899\", \"3468206646\", \"3468206647\", \"3468206644\", \"3468206643\"]]"
+ (NSArray<OPTLYCondition> *)deserializeAudienceConditionsJSONArray:(NSArray *)jsonArray
error:(NSError * __autoreleasing *)error {

NSMutableArray *mutableJsonArray = [NSMutableArray new];
// need to check if the jsonArray is actually an array, otherwise, something is wrong with the audience condition
if (![jsonArray isKindOfClass:[NSArray class]]) {
NSError *err = [NSError errorWithDomain:OPTLYErrorHandlerMessagesDomain
Expand All @@ -123,44 +114,42 @@ @implementation OPTLYCondition
}
return nil;
}
if ([jsonArray count] == 0) {
return (NSArray<OPTLYCondition> *)@[];
}

if ([OPTLYAudienceBaseCondition isBaseConditionJSON:jsonArray[1]]) { //base case condition

// generate all base conditions
NSMutableArray<OPTLYCondition> *conditions = (NSMutableArray<OPTLYCondition> *)[[NSMutableArray alloc] initWithCapacity:(jsonArray.count - 1)];
for (int i = 1; i < jsonArray.count; i++) {
NSString *audienceId = jsonArray[i];
mutableJsonArray = [jsonArray mutableCopy];
// Should return 'OR' operator in case there is none
if (![mutableJsonArray[0] isEqualToString:OPTLYDatafileKeysAndCondition] && ![mutableJsonArray[0] isEqualToString:OPTLYDatafileKeysOrCondition] && ![mutableJsonArray[0] isEqualToString:OPTLYDatafileKeysNotCondition]) {
[mutableJsonArray insertObject:OPTLYDatafileKeysOrCondition atIndex:0];
}

NSMutableArray<OPTLYCondition> *conditions = (NSMutableArray<OPTLYCondition> *)[[NSMutableArray alloc] initWithCapacity:(mutableJsonArray.count - 1)];
for (int i = 1; i < mutableJsonArray.count; i++) {
if ([OPTLYAudienceBaseCondition isBaseConditionJSON: mutableJsonArray[i]]) {
// generate base condition
NSString *audienceId = mutableJsonArray[i];
OPTLYAudienceBaseCondition *condition = [OPTLYAudienceBaseCondition new];
condition.audienceId = audienceId;
[conditions addObject:condition];
continue;
}

// return an (And/Or/Not) Condition handling the base conditions
NSObject<OPTLYCondition> *condition = [OPTLYCondition createConditionInstanceOfClass:jsonArray[0]
withConditions:conditions];
return (NSArray<OPTLYCondition> *)@[condition];
}
else {

// further condition arrays to deserialize
NSMutableArray<OPTLYCondition> *subConditions = (NSMutableArray<OPTLYCondition> *)[[NSMutableArray alloc] initWithCapacity:(jsonArray.count - 1)];
for (int i = 1; i < jsonArray.count; i++) {
if ([mutableJsonArray[i] isKindOfClass:[NSArray class]]) {
NSError *err = nil;
NSArray *deserializedJsonObject = [OPTLYCondition deserializeAudienceConditionsJSONArray:jsonArray[i] error:&err];

NSArray *_tmpConditions = [OPTLYCondition deserializeAudienceConditionsJSONArray:(NSArray *)mutableJsonArray[i] error:&err];
[conditions addObject:[_tmpConditions firstObject]];
if (err) {
*error = err;
return nil;
}

if (deserializedJsonObject != nil) {
[subConditions addObjectsFromArray:deserializedJsonObject];
}
}
NSObject<OPTLYCondition> *condition = [OPTLYCondition createConditionInstanceOfClass:jsonArray[0]
withConditions:subConditions];
return (NSArray<OPTLYCondition> *)@[condition];
}
// return an (And/Or/Not) Condition handling the base conditions
NSObject<OPTLYCondition> *condition = [OPTLYCondition createConditionInstanceOfClass:mutableJsonArray[0]
withConditions:conditions];
return (NSArray<OPTLYCondition> *)@[condition];
}

+ (NSObject<OPTLYCondition> *)createConditionInstanceOfClass:(NSString *)conditionClass withConditions:(NSArray<OPTLYCondition> *)conditions {
Expand Down
12 changes: 4 additions & 8 deletions OptimizelySDKCore/OptimizelySDKCore/OPTLYExperiment.m
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,11 @@ - (void)setAudienceConditionsWithNSArray:(NSArray *)array {

- (nullable NSNumber *)evaluateConditionsWithAttributes:(NSDictionary<NSString *, NSObject *> *)attributes projectConfig:(nullable OPTLYProjectConfig *)config {

for (NSObject<OPTLYCondition> *condition in self.audienceConditions) {
NSNumber *result = [condition evaluateConditionsWithAttributes:attributes projectConfig:config];
if (result != NULL && [result boolValue] == true) {
// if user satisfies any conditions, return true.
return [NSNumber numberWithBool:true];
}
NSObject<OPTLYCondition> *condition = (NSObject<OPTLYCondition> *)[self.audienceConditions firstObject];
if (condition) {
return [condition evaluateConditionsWithAttributes:attributes projectConfig:config];
}
// if user doesn't satisfy any conditions, return false.
return [NSNumber numberWithBool:false];
return nil;
}

- (void)setGroupId:(NSString *)groupId {
Expand Down
Loading