Skip to content

Commit 899b562

Browse files
authored
Standardize support for Firebase products that integrate with Remote Config. (#7094)
1 parent d7950dd commit 899b562

File tree

4 files changed

+146
-38
lines changed

4 files changed

+146
-38
lines changed

FirebaseRemoteConfig/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# v7.5.0
22
- [fixed] Fixed bug that was incorrectly flagging ABT experiment payloads as invalid. (#7184)
3+
- [changed] Standardize support for Firebase products that integrate with Remote Config. (#7094)
34

45
# v7.1.0
56
- [changed] Add support for other Firebase products to integrate with Remote Config. (#6692)

FirebaseRemoteConfig/Sources/RCNPersonalization.h

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,28 @@
1919
NS_ASSUME_NONNULL_BEGIN
2020

2121
static NSString *const kAnalyticsOriginPersonalization = @"fp";
22-
static NSString *const kAnalyticsPullEvent = @"_fpc";
23-
static NSString *const kArmKey = @"_fpid";
24-
static NSString *const kArmValue = @"_fpct";
22+
23+
static NSString *const kExternalEvent = @"personalization_assignment";
24+
static NSString *const kExternalRcParameterParam = @"arm_key";
25+
static NSString *const kExternalArmValueParam = @"arm_value";
2526
static NSString *const kPersonalizationId = @"personalizationId";
27+
static NSString *const kExternalPersonalizationIdParam = @"personalization_id";
28+
static NSString *const kArmIndex = @"armIndex";
29+
static NSString *const kExternalArmIndexParam = @"arm_index";
30+
static NSString *const kGroup = @"group";
31+
static NSString *const kExternalGroupParam = @"group";
32+
33+
static NSString *const kInternalEvent = @"_fpc";
34+
static NSString *const kChoiceId = @"choiceId";
35+
static NSString *const kInternalChoiceIdParam = @"_fpid";
2636

2737
@interface RCNPersonalization : NSObject
2838

2939
/// Analytics connector
3040
@property(nonatomic, strong) id<FIRAnalyticsInterop> _Nullable analytics;
3141

42+
@property(atomic, strong) NSMutableDictionary *loggedChoiceIds;
43+
3244
- (instancetype)init NS_UNAVAILABLE;
3345

3446
/// Designated initializer.
@@ -37,7 +49,7 @@ static NSString *const kPersonalizationId = @"personalizationId";
3749

3850
/// Called when an arm is pulled from Remote Config. If the arm is personalized, log information to
3951
/// Google in another thread.
40-
- (void)logArmActive:(NSString *)key config:(NSDictionary *)config;
52+
- (void)logArmActive:(NSString *)rcParameter config:(NSDictionary *)config;
4153

4254
@end
4355

FirebaseRemoteConfig/Sources/RCNPersonalization.m

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,48 @@ - (instancetype)initWithAnalytics:(id<FIRAnalyticsInterop> _Nullable)analytics {
2525
self = [super init];
2626
if (self) {
2727
self->_analytics = analytics;
28+
self->_loggedChoiceIds = [[NSMutableDictionary alloc] init];
2829
}
2930
return self;
3031
}
3132

32-
- (void)logArmActive:(NSString *)key config:(NSDictionary *)config {
33+
- (void)logArmActive:(NSString *)rcParameter config:(NSDictionary *)config {
3334
NSDictionary *ids = config[RCNFetchResponseKeyPersonalizationMetadata];
3435
NSDictionary<NSString *, FIRRemoteConfigValue *> *values = config[RCNFetchResponseKeyEntries];
35-
if (ids.count < 1 || values.count < 1 || !values[key]) {
36+
if (ids.count < 1 || values.count < 1 || !values[rcParameter]) {
3637
return;
3738
}
3839

39-
NSDictionary *metadata = ids[key];
40-
if (!metadata || metadata[kPersonalizationId] == nil) {
40+
NSDictionary *metadata = ids[rcParameter];
41+
if (!metadata) {
4142
return;
4243
}
4344

45+
NSString *choiceId = metadata[kChoiceId];
46+
if (choiceId == nil) {
47+
return;
48+
}
49+
50+
// Listeners like logArmActive() are dispatched to a serial queue, so loggedChoiceIds should
51+
// contain any previously logged RC parameter / choice ID pairs.
52+
if (self->_loggedChoiceIds[rcParameter] == choiceId) {
53+
return;
54+
}
55+
self->_loggedChoiceIds[rcParameter] = choiceId;
56+
4457
[self->_analytics logEventWithOrigin:kAnalyticsOriginPersonalization
45-
name:kAnalyticsPullEvent
58+
name:kExternalEvent
4659
parameters:@{
47-
kArmKey : metadata[kPersonalizationId],
48-
kArmValue : values[key].stringValue
60+
kExternalRcParameterParam : rcParameter,
61+
kExternalArmValueParam : values[rcParameter].stringValue,
62+
kExternalPersonalizationIdParam : metadata[kPersonalizationId],
63+
kExternalArmIndexParam : metadata[kArmIndex],
64+
kExternalGroupParam : metadata[kGroup]
4965
}];
66+
67+
[self->_analytics logEventWithOrigin:kAnalyticsOriginPersonalization
68+
name:kInternalEvent
69+
parameters:@{kInternalChoiceIdParam : choiceId}];
5070
}
5171

5272
@end

FirebaseRemoteConfig/Tests/Unit/RCNPersonalizationTest.m

Lines changed: 102 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,22 @@ - (void)setUp {
6161
initWithData:[@"value3" dataUsingEncoding:NSUTF8StringEncoding]
6262
source:FIRRemoteConfigSourceRemote]
6363
},
64-
RCNFetchResponseKeyPersonalizationMetadata :
65-
@{@"key1" : @{kPersonalizationId : @"id1"}, @"key2" : @{kPersonalizationId : @"id2"}}
64+
RCNFetchResponseKeyPersonalizationMetadata : @{
65+
@"key1" : @{
66+
kPersonalizationId : @"p13n1",
67+
kArmIndex : @0,
68+
kChoiceId : @"id1",
69+
kGroup : @"BASELINE"
70+
},
71+
@"key2" :
72+
@{kPersonalizationId : @"p13n2", kArmIndex : @1, kChoiceId : @"id2", kGroup : @"P13N"}
73+
}
6674
};
6775

6876
_fakeLogs = [[NSMutableArray alloc] init];
6977
_analyticsMock = OCMProtocolMock(@protocol(FIRAnalyticsInterop));
7078
OCMStub([_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
71-
name:kAnalyticsPullEvent
79+
name:[OCMArg isKindOfClass:[NSString class]]
7280
parameters:[OCMArg isKindOfClass:[NSDictionary class]]])
7381
.andDo(^(NSInvocation *invocation) {
7482
__unsafe_unretained NSDictionary *bundle;
@@ -108,7 +116,10 @@ - (void)testNonPersonalizationKey {
108116

109117
OCMVerify(never(),
110118
[_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
111-
name:kAnalyticsPullEvent
119+
name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
120+
return [value isEqualToString:kExternalEvent] ||
121+
[value isEqualToString:kInternalEvent];
122+
}]
112123
parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
113124
XCTAssertEqual([_fakeLogs count], 0);
114125
}
@@ -118,51 +129,106 @@ - (void)testSinglePersonalizationKey {
118129

119130
[_personalization logArmActive:@"key1" config:_configContainer];
120131

121-
OCMVerify(times(1),
132+
OCMVerify(times(2),
122133
[_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
123-
name:kAnalyticsPullEvent
134+
name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
135+
return [value isEqualToString:kExternalEvent] ||
136+
[value isEqualToString:kInternalEvent];
137+
}]
124138
parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
125-
XCTAssertEqual([_fakeLogs count], 1);
139+
XCTAssertEqual([_fakeLogs count], 2);
140+
141+
NSDictionary *logParams = @{
142+
kExternalRcParameterParam : @"key1",
143+
kExternalArmValueParam : @"value1",
144+
kExternalPersonalizationIdParam : @"p13n1",
145+
kExternalArmIndexParam : @0,
146+
kExternalGroupParam : @"BASELINE"
147+
};
148+
XCTAssertEqualObjects(_fakeLogs[0], logParams);
126149

127-
NSDictionary *params = @{kArmKey : @"id1", kArmValue : @"value1"};
128-
XCTAssertEqualObjects(_fakeLogs[0], params);
150+
NSDictionary *internalLogParams = @{kInternalChoiceIdParam : @"id1"};
151+
XCTAssertEqualObjects(_fakeLogs[1], internalLogParams);
129152
}
130153

131154
- (void)testMultiplePersonalizationKeys {
132155
[_fakeLogs removeAllObjects];
133156

134157
[_personalization logArmActive:@"key1" config:_configContainer];
135158
[_personalization logArmActive:@"key2" config:_configContainer];
159+
[_personalization logArmActive:@"key1" config:_configContainer];
136160

137-
OCMVerify(times(2),
161+
OCMVerify(times(4),
138162
[_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
139-
name:kAnalyticsPullEvent
163+
name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
164+
return [value isEqualToString:kExternalEvent] ||
165+
[value isEqualToString:kInternalEvent];
166+
}]
140167
parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
141-
XCTAssertEqual([_fakeLogs count], 2);
168+
XCTAssertEqual([_fakeLogs count], 4);
169+
170+
NSDictionary *logParams1 = @{
171+
kExternalRcParameterParam : @"key1",
172+
kExternalArmValueParam : @"value1",
173+
kExternalPersonalizationIdParam : @"p13n1",
174+
kExternalArmIndexParam : @0,
175+
kExternalGroupParam : @"BASELINE"
176+
};
177+
XCTAssertEqualObjects(_fakeLogs[0], logParams1);
178+
179+
NSDictionary *internalLogParams1 = @{kInternalChoiceIdParam : @"id1"};
180+
XCTAssertEqualObjects(_fakeLogs[1], internalLogParams1);
142181

143-
NSDictionary *params1 = @{kArmKey : @"id1", kArmValue : @"value1"};
144-
XCTAssertEqualObjects(_fakeLogs[0], params1);
182+
NSDictionary *logParams2 = @{
183+
kExternalRcParameterParam : @"key2",
184+
kExternalArmValueParam : @"value2",
185+
kExternalPersonalizationIdParam : @"p13n2",
186+
kExternalArmIndexParam : @1,
187+
kExternalGroupParam : @"P13N"
188+
};
189+
XCTAssertEqualObjects(_fakeLogs[2], logParams2);
145190

146-
NSDictionary *params2 = @{kArmKey : @"id2", kArmValue : @"value2"};
147-
XCTAssertEqualObjects(_fakeLogs[1], params2);
191+
NSDictionary *internalLogParams2 = @{kInternalChoiceIdParam : @"id2"};
192+
XCTAssertEqualObjects(_fakeLogs[3], internalLogParams2);
148193
}
149194

150195
- (void)testRemoteConfigIntegration {
151196
[_fakeLogs removeAllObjects];
152197

153198
FIRRemoteConfigFetchAndActivateCompletion fetchAndActivateCompletion =
154199
^void(FIRRemoteConfigFetchAndActivateStatus status, NSError *error) {
155-
OCMVerify(times(2), [self->_analyticsMock
200+
OCMVerify(times(4), [self->_analyticsMock
156201
logEventWithOrigin:kAnalyticsOriginPersonalization
157-
name:kAnalyticsPullEvent
202+
name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
203+
return [value isEqualToString:kExternalEvent] ||
204+
[value isEqualToString:kInternalEvent];
205+
}]
158206
parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
159-
XCTAssertEqual([self->_fakeLogs count], 2);
160-
161-
NSDictionary *params1 = @{kArmKey : @"id1", kArmValue : @"value1"};
162-
XCTAssertEqualObjects(self->_fakeLogs[0], params1);
163-
164-
NSDictionary *params2 = @{kArmKey : @"id2", kArmValue : @"value2"};
165-
XCTAssertEqualObjects(self->_fakeLogs[1], params2);
207+
XCTAssertEqual([self->_fakeLogs count], 4);
208+
209+
NSDictionary *logParams1 = @{
210+
kExternalRcParameterParam : @"key1",
211+
kExternalArmValueParam : @"value1",
212+
kExternalPersonalizationIdParam : @"p13n1",
213+
kExternalArmIndexParam : @0,
214+
kExternalGroupParam : @"BASELINE"
215+
};
216+
XCTAssertEqualObjects(self->_fakeLogs[0], logParams1);
217+
218+
NSDictionary *internalLogParams1 = @{kInternalChoiceIdParam : @"id1"};
219+
XCTAssertEqualObjects(self->_fakeLogs[1], internalLogParams1);
220+
221+
NSDictionary *logParams2 = @{
222+
kExternalRcParameterParam : @"key1",
223+
kExternalArmValueParam : @"value1",
224+
kExternalPersonalizationIdParam : @"p13n1",
225+
kExternalArmIndexParam : @0,
226+
kExternalGroupParam : @"BASELINE"
227+
};
228+
XCTAssertEqualObjects(self->_fakeLogs[2], logParams2);
229+
230+
NSDictionary *internalLogParams2 = @{kInternalChoiceIdParam : @"id2"};
231+
XCTAssertEqualObjects(self->_fakeLogs[3], internalLogParams2);
166232
};
167233

168234
[_configInstance fetchAndActivateWithCompletionHandler:fetchAndActivateCompletion];
@@ -190,8 +256,17 @@ + (id)mockResponseHandler {
190256
NSDictionary *response = @{
191257
RCNFetchResponseKeyState : RCNFetchResponseKeyStateUpdate,
192258
RCNFetchResponseKeyEntries : @{@"key1" : @"value1", @"key2" : @"value2", @"key3" : @"value3"},
193-
RCNFetchResponseKeyPersonalizationMetadata :
194-
@{@"key1" : @{kPersonalizationId : @"id1"}, @"key2" : @{kPersonalizationId : @"id2"}}
259+
RCNFetchResponseKeyPersonalizationMetadata : @{
260+
@"key1" : @{
261+
kPersonalizationId : @"p13n1",
262+
kArmIndex : @0,
263+
kChoiceId : @"id1",
264+
kGroup : @"BASELINE"
265+
},
266+
@"key2" :
267+
@{kPersonalizationId : @"p13n2", kArmIndex : @1, kChoiceId : @"id2", kGroup : @"P13N"}
268+
}
269+
195270
};
196271
return [OCMArg invokeBlockWithArgs:[NSJSONSerialization dataWithJSONObject:response
197272
options:0

0 commit comments

Comments
 (0)