Skip to content

Commit 12bf48a

Browse files
this runs through the background and removes any experiments that are… (#302)
* this runs through the background and removes any experiments that are not in a fresh datafile * cleanup naming and add try catch * add unit tests for remove invalid experiments from user profile service * add more tests for removeInvalidExperimentsForAllUsers and cleanUserProfileService. fix typos * correct typos and remove extra blank lines
1 parent d962ac5 commit 12bf48a

File tree

11 files changed

+136
-4
lines changed

11 files changed

+136
-4
lines changed

OptimizelySDKShared/OptimizelySDKShared/OPTLYManagerBase.m

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#import "OPTLYLogger.h"
2121
#import "OPTLYLoggerMessages.h"
2222
#else
23+
#import <OptimizelySDKCore/OPTLYExperiment.h>
24+
#import <OptimizelySDKCore/OPTLYProjectConfig.h>
2325
#import <OptimizelySDKCore/OPTLYErrorHandler.h>
2426
#import <OptimizelySDKCore/OPTLYEventDispatcherBasic.h>
2527
#import <OptimizelySDKCore/OPTLYLogger.h>
@@ -67,6 +69,29 @@ @interface OPTLYManagerBase()
6769

6870
@implementation OPTLYManagerBase
6971

72+
- (void)cleanUserProfileService:(NSArray<OPTLYExperiment> *)experiments {
73+
if (experiments == nil) return;
74+
75+
if (_userProfileService != nil && [(NSObject *)_userProfileService respondsToSelector:@selector(removeInvalidExperimentsForAllUsers:)]) {
76+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
77+
if (self.userProfileService != nil) {
78+
NSMutableArray<NSString*> *ids = [[NSMutableArray alloc] initWithCapacity:experiments.count];
79+
for (int i = 0; i < experiments.count; i++) {
80+
NSString *exKey = ((OPTLYExperiment *)experiments[i]).experimentId;
81+
[ids addObject:exKey];
82+
}
83+
84+
@try {
85+
[(NSObject *)self.userProfileService performSelector:@selector(removeInvalidExperimentsForAllUsers:) withObject:ids];
86+
}
87+
@catch(NSException *e) {
88+
[self.logger logMessage:@"Error cleaning up user profile service" withLevel:OptimizelyLogLevelError];
89+
}
90+
}
91+
});
92+
}
93+
}
94+
7095
#pragma mark - Constructors
7196

7297
- (instancetype)init {
@@ -102,14 +127,22 @@ - (OPTLYClient *)initialize {
102127

103128
// attempt to get the cached datafile
104129
NSData *data = [self.datafileManager getSavedDatafile:nil];
105-
130+
BOOL cleanUserProfile = NO;
106131
// fall back to the datafile provided by the manager builder if we can't get the saved datafile
107132
if (data == nil) {
108133
data = self.datafile;
109134
[self.logger logMessage:OPTLYLoggerMessagesManagerBundledDataLoaded withLevel:OptimizelyLogLevelInfo];
110135
}
136+
else {
137+
// cleanup user profile service in background
138+
cleanUserProfile = YES;
139+
}
111140

112141
OPTLYClient *client = [self initializeWithDatafile:data];
142+
143+
if (cleanUserProfile) {
144+
[self cleanUserProfileService:client.optimizely.config.experiments];
145+
}
113146

114147
return client;
115148
}
@@ -138,13 +171,22 @@ - (void)initializeWithCallback:(void (^)(NSError * _Nullable, OPTLYClient * _Nul
138171
}
139172

140173
// fall back to the datafile provided by the manager builder if we can't get the saved datafile
174+
BOOL cleanUserProfileService = NO;
141175
if (data == nil) {
142176
data = self.datafile;
143177
[self.logger logMessage:OPTLYLoggerMessagesManagerBundledDataLoaded withLevel:OptimizelyLogLevelInfo];
144178
}
179+
else {
180+
// update user profile service in the background.
181+
cleanUserProfileService = YES;
182+
}
145183

146184
OPTLYClient *client = [self initializeWithDatafile:data];
147185

186+
if (cleanUserProfileService) {
187+
[self cleanUserProfileService:client.optimizely.config.experiments];
188+
}
189+
148190
if (callback) {
149191
callback(error, client);
150192
}

OptimizelySDKShared/OptimizelySDKSharedTests/OPTLYManagerTest.m

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
static NSString * const kClientEngine = @"tvos-sdk";
4242
#endif
4343

44+
@interface OPTLYManagerBase(Tests)
45+
- (void)cleanUserProfileService:(NSArray<OPTLYExperiment> *)experiments;
46+
@end
47+
4448
@interface OPTLYManagerTest : XCTestCase
4549
@property (nonatomic, strong) NSData *defaultDatafile;
4650
@property (nonatomic, strong) NSData *alternateDatafile;
@@ -74,13 +78,21 @@ - (void)testInitializeWithCachedDatafile
7478
builder.projectId = kAlternateProjectId;
7579
}]];
7680

81+
id managerMock = OCMPartialMock(manager);
82+
83+
/* run code under test */
84+
7785
// save the datafile
7886
[manager.datafileManager saveDatafile:self.alternateDatafile];
7987
OPTLYClient *client = [manager initialize];
8088

8189
[self isClientValid:client
8290
datafile:self.alternateDatafile];
8391
[self checkConfigIsUsingAlternativeDatafile:client.optimizely.config];
92+
93+
NSArray<OPTLYExperiment> *experiments = [[[manager getOptimizely] optimizely].config experiments];
94+
OCMVerify([managerMock cleanUserProfileService:experiments]);
95+
8496
}
8597

8698
// If no datafile is cached, the client should be initialized with
@@ -94,11 +106,16 @@ - (void)testInitializeNoCachedDatafile
94106
}]];
95107
id partialMockManager = OCMPartialMock(manager);
96108

109+
NSArray<OPTLYExperiment> *experiments = [[NSArray<OPTLYExperiment> alloc] init];
110+
111+
[[partialMockManager reject] cleanUserProfileService:experiments];
112+
97113
OPTLYClient *client = [partialMockManager initialize];
98114

99115
[self isClientValid:client
100116
datafile:self.alternateDatafile];
101117
[self checkConfigIsUsingAlternativeDatafile:client.optimizely.config];
118+
102119
}
103120

104121
// If a datafile is cached, but there is an error loading the datafile,
@@ -414,6 +431,33 @@ - (void)testInitializeWithCallbackDownloadErrorNoDatafile
414431
[self waitForExpectationsWithTimeout:2 handler:nil];
415432
}
416433

434+
#pragma mark - testCleanUserProfileService
435+
436+
- (void)testCleanUserProfileService
437+
{
438+
// need to mock the manager bundled datafile load to read from the test bundle
439+
OPTLYManagerBasic *manager = [[OPTLYManagerBasic alloc] initWithBuilder:[OPTLYManagerBuilder builderWithBlock:^(OPTLYManagerBuilder * _Nullable builder) {
440+
builder.projectId = kProjectId;
441+
builder.datafile = self.alternateDatafile;
442+
}]];
443+
id partialMockManager = OCMPartialMock(manager);
444+
445+
NSArray<OPTLYExperiment> *experiments = [[NSArray<OPTLYExperiment> alloc] init];
446+
447+
OPTLYClient *client = [partialMockManager initialize];
448+
449+
[self isClientValid:client
450+
datafile:self.alternateDatafile];
451+
[self checkConfigIsUsingAlternativeDatafile:client.optimizely.config];
452+
453+
// pass in nil
454+
[manager cleanUserProfileService:nil];
455+
// pass in empty
456+
[manager cleanUserProfileService:experiments];
457+
// pass in experiements
458+
[manager cleanUserProfileService:client.optimizely.config.experiments];
459+
}
460+
417461
#pragma mark - isValidKeyString
418462

419463
-(void)testIsValidKeyString {

OptimizelySDKUniversal/generated-frameworks/Release-iOS-universal-SDK/OptimizelySDKiOS.framework/Headers/OPTLYLoggerMessages.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ extern NSString *const OPTLYLoggerMessagesDatafileFetchIntervalInvalid;
100100

101101
// ---- Datafile Versioning ----
102102
// warning
103-
extern NSString *const OPTLYLoggerMessagesInvalidDatafileVersion;
103+
extern NSString *const OPTLYLoggerMessagesDatafileVersion;
104104

105105
// ---- Event Builder ----
106106
// debug

OptimizelySDKUniversal/generated-frameworks/Release-tvOS-universal-SDK/OptimizelySDKTVOS.framework/Headers/OPTLYLoggerMessages.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ extern NSString *const OPTLYLoggerMessagesDatafileFetchIntervalInvalid;
100100

101101
// ---- Datafile Versioning ----
102102
// warning
103-
extern NSString *const OPTLYLoggerMessagesInvalidDatafileVersion;
103+
extern NSString *const OPTLYLoggerMessagesDatafileVersion;
104104

105105
// ---- Event Builder ----
106106
// debug

OptimizelySDKUserProfileService/OptimizelySDKUserProfileService/OPTLYUserProfileService.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,12 @@ __attribute((deprecated("Use OPTLYManager initWithBuilder method instead.")));
5252
**/
5353
- (void)removeAllUserExperimentRecords;
5454

55+
/**
56+
* Clean up and remove experiments that are not in the valid experiment list passed in.
57+
* This is called when initialized from a remote datafile to ensure that the UserProfileService
58+
* does not grow indefinitely.
59+
* @param validExperimentIds An array of valid experiment ids. If default user profile contains
60+
* experiments not in this list, they are removed from user profile service.
61+
**/
62+
- (void)removeInvalidExperimentsForAllUsers:(NSArray<NSString *> *)validExperimentIds;
5563
@end

OptimizelySDKUserProfileService/OptimizelySDKUserProfileService/OPTLYUserProfileService.m

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,26 @@ - (void)removeUserExperimentRecordsForUserId:(nonnull NSString *)userId {
156156
- (void)removeAllUserExperimentRecords {
157157
[self.dataStore removeAllUserData];
158158
}
159+
160+
- (void)removeInvalidExperimentsForAllUsers:(NSArray<NSString *> *)validExperimentIds {
161+
162+
NSMutableDictionary*userProfileService = [[self.dataStore getUserDataForType:OPTLYDataStoreDataTypeUserProfileService] mutableCopy];
163+
164+
for (NSString *key in userProfileService.allKeys) {
165+
NSMutableDictionary *userProfileDict = [userProfileService[key] mutableCopy];
166+
NSDictionary * bucketMap = userProfileDict[@"experiment_bucket_map"];
167+
NSMutableDictionary *newBucketMap = [bucketMap mutableCopy];
168+
for (NSString *exId in bucketMap.allKeys) {
169+
if (![validExperimentIds containsObject:exId]) {
170+
[newBucketMap removeObjectForKey:exId];
171+
}
172+
}
173+
userProfileDict[@"experiment_bucket_map"] = newBucketMap;
174+
userProfileService[key] = userProfileDict;
175+
}
176+
177+
[self.dataStore saveUserData:userProfileService type:OPTLYDataStoreDataTypeUserProfileService];
178+
}
159179

160180
@end
161181

0 commit comments

Comments
 (0)