Skip to content

Commit

Permalink
[Rollouts] Set active rollout metadata after activating config (#12316)
Browse files Browse the repository at this point in the history
  • Loading branch information
ddnan authored and themiswang committed Feb 13, 2024
1 parent 5e4a8af commit cc1e08b
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 24 deletions.
6 changes: 6 additions & 0 deletions FirebaseRemoteConfig/Sources/FIRRemoteConfig.m
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,12 @@ - (void)activateWithCompletion:(FIRRemoteConfigActivateChangeCompletion)completi
// New config has been activated at this point
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069", @"Config activated.");
[strongSelf->_configContent activatePersonalization];
// Update activeRolloutMetadata
[strongSelf->_configContent activateRolloutMetadata];
// Update last active template version number in setting and userDefaults.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[strongSelf->_settings updateLastActiveTemplateVersion];
});
// Update experiments only for 3p namespace
NSString *namespace = [strongSelf->_FIRNamespace
substringToIndex:[strongSelf->_FIRNamespace rangeOfString:@":"].location];
Expand Down
9 changes: 7 additions & 2 deletions FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,10 @@
@property(nonatomic, readwrite, assign) NSString *lastETag;
/// The timestamp of the last eTag update.
@property(nonatomic, readwrite, assign) NSTimeInterval lastETagUpdateTime;
// Last fetched template version.
@property(nonatomic, readwrite, assign) NSString *lastTemplateVersion;
/// Last fetched template version.
@property(nonatomic, readwrite, assign) NSString *lastFetchedTemplateVersion;
/// Last active template version.
@property(nonatomic, readwrite, assign) NSString *lastActiveTemplateVersion;

#pragma mark Throttling properties

Expand Down Expand Up @@ -134,6 +136,9 @@
/// indicates a server issue.
- (void)updateRealtimeExponentialBackoffTime;

/// Update last active template version from last fetched template version.
- (void)updateLastActiveTemplateVersion;

/// Returns the difference between the Realtime backoff end time and the current time in a
/// NSTimeInterval format.
- (NSTimeInterval)getRealtimeBackoffInterval;
Expand Down
4 changes: 3 additions & 1 deletion FirebaseRemoteConfig/Sources/RCNConfigConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,7 @@ static NSString *const RCNFetchResponseKeyStateNoTemplate = @"NO_TEMPLATE";
static NSString *const RCNFetchResponseKeyStateNoChange = @"NO_CHANGE";
/// Template found, but evaluates to empty (e.g. all keys omitted).
static NSString *const RCNFetchResponseKeyStateEmptyConfig = @"EMPTY_CONFIG";
/// Template Version key
/// Fetched Template Version key
static NSString *const RCNFetchResponseKeyTemplateVersion = @"templateVersion";
/// Active Template Version key
static NSString *const RCNActiveKeyTemplateVersion = @"activeTemplateVersion";
3 changes: 3 additions & 0 deletions FirebaseRemoteConfig/Sources/RCNConfigContent.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ typedef NS_ENUM(NSInteger, RCNDBSource) {
/// Gets the active config and Personalization metadata.
- (NSDictionary *)getConfigAndMetadataForNamespace:(NSString *)FIRNamespace;

/// Sets the fetched rollout metadata to active and return the active rollout metadata.
- (NSArray<NSDictionary *> *)activateRolloutMetadata;

/// Returns the updated parameters between fetched and active config.
- (FIRRemoteConfigUpdate *)getConfigUpdateForNamespace:(NSString *)FIRNamespace;

Expand Down
19 changes: 19 additions & 0 deletions FirebaseRemoteConfig/Sources/RCNConfigContent.m
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ - (void)updateConfigContentWithResponse:(NSDictionary *)response
[self handleUpdateStateForConfigNamespace:currentNamespace
withEntries:response[RCNFetchResponseKeyEntries]];
[self handleUpdatePersonalization:response[RCNFetchResponseKeyPersonalizationMetadata]];
[self handleUpdateRolloutFetchedMetadata:response[RCNFetchResponseKeyRolloutMetadata]];
return;
}
}
Expand All @@ -290,6 +291,14 @@ - (void)activatePersonalization {
fromSource:RCNDBSourceActive];
}

- (NSArray<NSDictionary *> *)activateRolloutMetadata {
_activeRolloutMetadata = _fetchedRolloutMetadata;
[_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyActiveMetadata
value:_activeRolloutMetadata
completionHandler:nil];
return _activeRolloutMetadata;
}

#pragma mark State handling
- (void)handleNoChangeStateForConfigNamespace:(NSString *)currentNamespace {
if (!_fetchedConfig[currentNamespace]) {
Expand Down Expand Up @@ -353,6 +362,16 @@ - (void)handleUpdatePersonalization:(NSDictionary *)metadata {
[_DBManager insertOrUpdatePersonalizationConfig:metadata fromSource:RCNDBSourceFetched];
}

- (void)handleUpdateRolloutFetchedMetadata:(NSArray<NSDictionary *> *)metadata {
if (!metadata) {
return;
}
_fetchedRolloutMetadata = metadata;
[_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyFetchedMetadata
value:metadata
completionHandler:nil];
}

#pragma mark - getter/setter
- (NSDictionary *)fetchedConfig {
/// If this is the first time reading the fetchedConfig, we might still be reading it from the
Expand Down
2 changes: 1 addition & 1 deletion FirebaseRemoteConfig/Sources/RCNConfigFetch.m
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ - (instancetype)initWithContent:(RCNConfigContent *)content
_content = content;
_fetchSession = [self newFetchSession];
_options = options;
_templateVersionNumber = [self->_settings lastTemplateVersion];
_templateVersionNumber = [self->_settings lastFetchedTemplateVersion];
}
return self;
}
Expand Down
10 changes: 8 additions & 2 deletions FirebaseRemoteConfig/Sources/RCNConfigSettings.m
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ - (instancetype)initWithDatabaseManager:(RCNConfigDBManager *)manager
}

_isFetchInProgress = NO;
_lastTemplateVersion = [_userDefaultsManager lastTemplateVersion];
_lastFetchedTemplateVersion = [_userDefaultsManager lastFetchedTemplateVersion];
_lastActiveTemplateVersion = [_userDefaultsManager lastActiveTemplateVersion];
_realtimeExponentialBackoffRetryInterval =
[_userDefaultsManager currentRealtimeThrottlingRetryIntervalSeconds];
_realtimeExponentialBackoffThrottleEndTime = [_userDefaultsManager realtimeThrottleEndTime];
Expand Down Expand Up @@ -292,7 +293,7 @@ - (void)updateMetadataWithFetchSuccessStatus:(BOOL)fetchSuccess
[self updateLastFetchTimeInterval:[[NSDate date] timeIntervalSince1970]];
// Note: We expect the googleAppID to always be available.
_deviceContext = FIRRemoteConfigDeviceContextWithProjectIdentifier(_googleAppID);
[_userDefaultsManager setLastTemplateVersion:templateVersion];
[_userDefaultsManager setLastFetchedTemplateVersion:templateVersion];
}

[self updateMetadataTable];
Expand Down Expand Up @@ -377,6 +378,11 @@ - (void)updateMetadataTable {
[_DBManager insertMetadataTableWithValues:columnNameToValue completionHandler:nil];
}

- (void)updateLastActiveTemplateVersion {
_lastActiveTemplateVersion = _lastFetchedTemplateVersion;
[_userDefaultsManager setLastActiveTemplateVersion:_lastActiveTemplateVersion];
}

#pragma mark - fetch request

/// Returns a fetch request with the latest device and config change.
Expand Down
4 changes: 3 additions & 1 deletion FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ NS_ASSUME_NONNULL_BEGIN
/// Realtime retry count.
@property(nonatomic, assign) int realtimeRetryCount;
/// Last fetched template version.
@property(nonatomic, assign) NSString *lastTemplateVersion;
@property(nonatomic, assign) NSString *lastFetchedTemplateVersion;
/// Last active template version.
@property(nonatomic, assign) NSString *lastActiveTemplateVersion;

/// Designated initializer.
- (instancetype)initWithAppName:(NSString *)appName
Expand Down
19 changes: 17 additions & 2 deletions FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ - (void)setLastETag:(NSString *)lastETag {
}
}

- (NSString *)lastTemplateVersion {
- (NSString *)lastFetchedTemplateVersion {
NSDictionary *userDefaults = [self instanceUserDefaults];
if ([userDefaults objectForKey:RCNFetchResponseKeyTemplateVersion]) {
return [userDefaults objectForKey:RCNFetchResponseKeyTemplateVersion];
Expand All @@ -120,12 +120,27 @@ - (NSString *)lastTemplateVersion {
return @"0";
}

- (void)setLastTemplateVersion:(NSString *)templateVersion {
- (void)setLastFetchedTemplateVersion:(NSString *)templateVersion {
if (templateVersion) {
[self setInstanceUserDefaultsValue:templateVersion forKey:RCNFetchResponseKeyTemplateVersion];
}
}

- (NSString *)lastActiveTemplateVersion {
NSDictionary *userDefaults = [self instanceUserDefaults];
if ([userDefaults objectForKey:RCNActiveKeyTemplateVersion]) {
return [userDefaults objectForKey:RCNActiveKeyTemplateVersion];
}

return @"0";
}

- (void)setLastActiveTemplateVersion:(NSString *)templateVersion {
if (templateVersion) {
[self setInstanceUserDefaultsValue:templateVersion forKey:RCNActiveKeyTemplateVersion];
}
}

- (NSTimeInterval)lastETagUpdateTime {
NSNumber *lastETagUpdateTime =
[[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNamelastETagUpdateTime];
Expand Down
77 changes: 68 additions & 9 deletions FirebaseRemoteConfig/Tests/Unit/RCNConfigContentTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,9 @@ - (void)testConfigUpdate_noChange_emptyResponse {

// populate fetched config
NSMutableDictionary *fetchResponse =
[self createFetchResponseWithConfigEntries:@{@"key1" : @"value1"} p13nMetadata:nil];
[self createFetchResponseWithConfigEntries:@{@"key1" : @"value1"}
p13nMetadata:nil
rolloutMetadata:nil];
[_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace];

// active config is the same as fetched config
Expand Down Expand Up @@ -365,7 +367,8 @@ - (void)testConfigUpdate_paramAdded_returnsNewKey {
// fetch response has new param
NSMutableDictionary *fetchResponse =
[self createFetchResponseWithConfigEntries:@{@"key1" : @"value1", newParam : @"value2"}
p13nMetadata:nil];
p13nMetadata:nil
rolloutMetadata:nil];
[_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace];

FIRRemoteConfigUpdate *update = [_configContent getConfigUpdateForNamespace:namespace];
Expand All @@ -391,7 +394,9 @@ - (void)testConfigUpdate_paramValueChanged_returnsUpdatedKey {

// fetch response contains updated value
NSMutableDictionary *fetchResponse =
[self createFetchResponseWithConfigEntries:@{existingParam : updatedValue} p13nMetadata:nil];
[self createFetchResponseWithConfigEntries:@{existingParam : updatedValue}
p13nMetadata:nil
rolloutMetadata:nil];
[_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace];

FIRRemoteConfigUpdate *update = [_configContent getConfigUpdateForNamespace:namespace];
Expand All @@ -417,7 +422,9 @@ - (void)testConfigUpdate_paramDeleted_returnsDeletedKey {

// fetch response does not contain existing param
NSMutableDictionary *fetchResponse =
[self createFetchResponseWithConfigEntries:@{newParam : value1} p13nMetadata:nil];
[self createFetchResponseWithConfigEntries:@{newParam : value1}
p13nMetadata:nil
rolloutMetadata:nil];
[_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace];

FIRRemoteConfigUpdate *update = [_configContent getConfigUpdateForNamespace:namespace];
Expand All @@ -437,7 +444,8 @@ - (void)testConfigUpdate_p13nMetadataUpdated_returnsKey {
// popuate fetched config
NSMutableDictionary *fetchResponse =
[self createFetchResponseWithConfigEntries:@{existingParam : value1}
p13nMetadata:@{existingParam : oldMetadata}];
p13nMetadata:@{existingParam : oldMetadata}
rolloutMetadata:nil];
[_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace];

// populate active config with the same content
Expand All @@ -461,6 +469,51 @@ - (void)testConfigUpdate_p13nMetadataUpdated_returnsKey {
XCTAssertTrue([[update updatedKeys] containsObject:existingParam]);
}

- (void)testConfigUpdate_rolloutMetadataUpdated_returnsKey {
NSString *namespace = @"test_namespace";
NSString *key = @"key1";
NSString *value1 = @"value1";
NSString *value2 = @"value2";
NSString *rolloutId1 = @"1";
NSString *variantId1 = @"A";
NSString *variantId2 = @"B";
NSArray *rolloutMetadata = @[
@{@"rollout_id" : rolloutId1, @"variant_id" : variantId1, @"affected_parameter_keys" : @[ key ]}
];
// variant_id changed
NSArray *updatedRolloutMetadata = @[
@{@"rollout_id" : rolloutId1, @"variant_id" : variantId2, @"affected_parameter_keys" : @[ key ]}
];

// Populate fetched config
NSMutableDictionary *fetchResponse = [self createFetchResponseWithConfigEntries:@{key : value1}
p13nMetadata:nil
rolloutMetadata:rolloutMetadata];
[_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace];
// populate active config with the same content
NSArray<NSDictionary *> *result = [_configContent activateRolloutMetadata];
XCTAssertEqualObjects(rolloutMetadata, result);
FIRRemoteConfigValue *rcValue1 =
[[FIRRemoteConfigValue alloc] initWithData:[value1 dataUsingEncoding:NSUTF8StringEncoding]
source:FIRRemoteConfigSourceRemote];

NSDictionary *namespaceToConfig = @{namespace : @{key : rcValue1}};
[_configContent copyFromDictionary:namespaceToConfig
toSource:RCNDBSourceActive
forNamespace:namespace];
// New fetch response has updated rollout metadata
NSMutableDictionary *fetchResponse2 =
[self createFetchResponseWithConfigEntries:@{key : value2}
p13nMetadata:nil
rolloutMetadata:updatedRolloutMetadata];
[_configContent updateConfigContentWithResponse:fetchResponse2 forNamespace:namespace];

FIRRemoteConfigUpdate *update = [_configContent getConfigUpdateForNamespace:namespace];

XCTAssertTrue([update updatedKeys].count == 1);
XCTAssertTrue([[update updatedKeys] containsObject:key]);
}

- (void)testConfigUpdate_valueSourceChanged_returnsKey {
NSString *namespace = @"test_namespace";
NSString *existingParam = @"key1";
Expand All @@ -477,7 +530,9 @@ - (void)testConfigUpdate_valueSourceChanged_returnsKey {

// fetch response contains same key->value
NSMutableDictionary *fetchResponse =
[self createFetchResponseWithConfigEntries:@{existingParam : value1} p13nMetadata:nil];
[self createFetchResponseWithConfigEntries:@{existingParam : value1}
p13nMetadata:nil
rolloutMetadata:nil];
[_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace];

FIRRemoteConfigUpdate *update = [_configContent getConfigUpdateForNamespace:namespace];
Expand All @@ -489,14 +544,18 @@ - (void)testConfigUpdate_valueSourceChanged_returnsKey {
#pragma mark - Test Helpers

- (NSMutableDictionary *)createFetchResponseWithConfigEntries:(NSDictionary *)config
p13nMetadata:(NSDictionary *)metadata {
p13nMetadata:(NSDictionary *)p13nMetadata
rolloutMetadata:(NSArray *)rolloutMetadata {
NSMutableDictionary *fetchResponse = [[NSMutableDictionary alloc]
initWithObjectsAndKeys:RCNFetchResponseKeyStateUpdate, RCNFetchResponseKeyState, nil];
if (config) {
[fetchResponse setValue:config forKey:RCNFetchResponseKeyEntries];
}
if (metadata) {
[fetchResponse setValue:metadata forKey:RCNFetchResponseKeyPersonalizationMetadata];
if (p13nMetadata) {
[fetchResponse setValue:p13nMetadata forKey:RCNFetchResponseKeyPersonalizationMetadata];
}
if (rolloutMetadata) {
[fetchResponse setValue:rolloutMetadata forKey:RCNFetchResponseKeyRolloutMetadata];
}
return fetchResponse;
}
Expand Down
27 changes: 21 additions & 6 deletions FirebaseRemoteConfig/Tests/Unit/RCNUserDefaultsManagerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,17 @@ - (void)testUserDefaultsTemplateVersionWriteAndRead {
[[RCNUserDefaultsManager alloc] initWithAppName:AppName
bundleID:[NSBundle mainBundle].bundleIdentifier
namespace:FQNamespace1];
[manager setLastTemplateVersion:@"1"];
XCTAssertEqual([manager lastTemplateVersion], @"1");
[manager setLastFetchedTemplateVersion:@"1"];
XCTAssertEqual([manager lastFetchedTemplateVersion], @"1");
}

- (void)testUserDefaultsActiveTemplateVersionWriteAndRead {
RCNUserDefaultsManager* manager =
[[RCNUserDefaultsManager alloc] initWithAppName:AppName
bundleID:[NSBundle mainBundle].bundleIdentifier
namespace:FQNamespace1];
[manager setLastActiveTemplateVersion:@"1"];
XCTAssertEqual([manager lastActiveTemplateVersion], @"1");
}

- (void)testUserDefaultsRealtimeThrottleEndTimeWriteAndRead {
Expand Down Expand Up @@ -229,10 +238,16 @@ - (void)testUserDefaultsForMultipleNamespaces {
XCTAssertEqual([manager2 realtimeRetryCount], 2);

/// Fetch template version.
[manager1 setLastTemplateVersion:@"1"];
[manager2 setLastTemplateVersion:@"2"];
XCTAssertEqualObjects([manager1 lastTemplateVersion], @"1");
XCTAssertEqualObjects([manager2 lastTemplateVersion], @"2");
[manager1 setLastFetchedTemplateVersion:@"1"];
[manager2 setLastFetchedTemplateVersion:@"2"];
XCTAssertEqualObjects([manager1 lastFetchedTemplateVersion], @"1");
XCTAssertEqualObjects([manager2 lastFetchedTemplateVersion], @"2");

/// Active template version.
[manager1 setLastActiveTemplateVersion:@"1"];
[manager2 setLastActiveTemplateVersion:@"2"];
XCTAssertEqualObjects([manager1 lastActiveTemplateVersion], @"1");
XCTAssertEqualObjects([manager2 lastActiveTemplateVersion], @"2");
}

- (void)testUserDefaultsReset {
Expand Down

0 comments on commit cc1e08b

Please sign in to comment.