Bladeren bron

Standardize support for Firebase products that integrate with Remote Config. (#7094)

vic-flair 5 jaren geleden
bovenliggende
commit
899b562685

+ 1 - 0
FirebaseRemoteConfig/CHANGELOG.md

@@ -1,5 +1,6 @@
 # v7.5.0
 - [fixed] Fixed bug that was incorrectly flagging ABT experiment payloads as invalid. (#7184)
+- [changed] Standardize support for Firebase products that integrate with Remote Config. (#7094)
 
 # v7.1.0
 - [changed] Add support for other Firebase products to integrate with Remote Config. (#6692)

+ 16 - 4
FirebaseRemoteConfig/Sources/RCNPersonalization.h

@@ -19,16 +19,28 @@
 NS_ASSUME_NONNULL_BEGIN
 
 static NSString *const kAnalyticsOriginPersonalization = @"fp";
-static NSString *const kAnalyticsPullEvent = @"_fpc";
-static NSString *const kArmKey = @"_fpid";
-static NSString *const kArmValue = @"_fpct";
+
+static NSString *const kExternalEvent = @"personalization_assignment";
+static NSString *const kExternalRcParameterParam = @"arm_key";
+static NSString *const kExternalArmValueParam = @"arm_value";
 static NSString *const kPersonalizationId = @"personalizationId";
+static NSString *const kExternalPersonalizationIdParam = @"personalization_id";
+static NSString *const kArmIndex = @"armIndex";
+static NSString *const kExternalArmIndexParam = @"arm_index";
+static NSString *const kGroup = @"group";
+static NSString *const kExternalGroupParam = @"group";
+
+static NSString *const kInternalEvent = @"_fpc";
+static NSString *const kChoiceId = @"choiceId";
+static NSString *const kInternalChoiceIdParam = @"_fpid";
 
 @interface RCNPersonalization : NSObject
 
 /// Analytics connector
 @property(nonatomic, strong) id<FIRAnalyticsInterop> _Nullable analytics;
 
+@property(atomic, strong) NSMutableDictionary *loggedChoiceIds;
+
 - (instancetype)init NS_UNAVAILABLE;
 
 /// Designated initializer.
@@ -37,7 +49,7 @@ static NSString *const kPersonalizationId = @"personalizationId";
 
 /// Called when an arm is pulled from Remote Config. If the arm is personalized, log information to
 /// Google in another thread.
-- (void)logArmActive:(NSString *)key config:(NSDictionary *)config;
+- (void)logArmActive:(NSString *)rcParameter config:(NSDictionary *)config;
 
 @end
 

+ 27 - 7
FirebaseRemoteConfig/Sources/RCNPersonalization.m

@@ -25,28 +25,48 @@
   self = [super init];
   if (self) {
     self->_analytics = analytics;
+    self->_loggedChoiceIds = [[NSMutableDictionary alloc] init];
   }
   return self;
 }
 
-- (void)logArmActive:(NSString *)key config:(NSDictionary *)config {
+- (void)logArmActive:(NSString *)rcParameter config:(NSDictionary *)config {
   NSDictionary *ids = config[RCNFetchResponseKeyPersonalizationMetadata];
   NSDictionary<NSString *, FIRRemoteConfigValue *> *values = config[RCNFetchResponseKeyEntries];
-  if (ids.count < 1 || values.count < 1 || !values[key]) {
+  if (ids.count < 1 || values.count < 1 || !values[rcParameter]) {
     return;
   }
 
-  NSDictionary *metadata = ids[key];
-  if (!metadata || metadata[kPersonalizationId] == nil) {
+  NSDictionary *metadata = ids[rcParameter];
+  if (!metadata) {
     return;
   }
 
+  NSString *choiceId = metadata[kChoiceId];
+  if (choiceId == nil) {
+    return;
+  }
+
+  // Listeners like logArmActive() are dispatched to a serial queue, so loggedChoiceIds should
+  // contain any previously logged RC parameter / choice ID pairs.
+  if (self->_loggedChoiceIds[rcParameter] == choiceId) {
+    return;
+  }
+  self->_loggedChoiceIds[rcParameter] = choiceId;
+
   [self->_analytics logEventWithOrigin:kAnalyticsOriginPersonalization
-                                  name:kAnalyticsPullEvent
+                                  name:kExternalEvent
                             parameters:@{
-                              kArmKey : metadata[kPersonalizationId],
-                              kArmValue : values[key].stringValue
+                              kExternalRcParameterParam : rcParameter,
+                              kExternalArmValueParam : values[rcParameter].stringValue,
+                              kExternalPersonalizationIdParam : metadata[kPersonalizationId],
+                              kExternalArmIndexParam : metadata[kArmIndex],
+                              kExternalGroupParam : metadata[kGroup]
                             }];
+
+  [self->_analytics logEventWithOrigin:kAnalyticsOriginPersonalization
+                                  name:kInternalEvent
+                            parameters:@{kInternalChoiceIdParam : choiceId}];
 }
 
 @end

+ 102 - 27
FirebaseRemoteConfig/Tests/Unit/RCNPersonalizationTest.m

@@ -61,14 +61,22 @@
           initWithData:[@"value3" dataUsingEncoding:NSUTF8StringEncoding]
                 source:FIRRemoteConfigSourceRemote]
     },
-    RCNFetchResponseKeyPersonalizationMetadata :
-        @{@"key1" : @{kPersonalizationId : @"id1"}, @"key2" : @{kPersonalizationId : @"id2"}}
+    RCNFetchResponseKeyPersonalizationMetadata : @{
+      @"key1" : @{
+        kPersonalizationId : @"p13n1",
+        kArmIndex : @0,
+        kChoiceId : @"id1",
+        kGroup : @"BASELINE"
+      },
+      @"key2" :
+          @{kPersonalizationId : @"p13n2", kArmIndex : @1, kChoiceId : @"id2", kGroup : @"P13N"}
+    }
   };
 
   _fakeLogs = [[NSMutableArray alloc] init];
   _analyticsMock = OCMProtocolMock(@protocol(FIRAnalyticsInterop));
   OCMStub([_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
-                                        name:kAnalyticsPullEvent
+                                        name:[OCMArg isKindOfClass:[NSString class]]
                                   parameters:[OCMArg isKindOfClass:[NSDictionary class]]])
       .andDo(^(NSInvocation *invocation) {
         __unsafe_unretained NSDictionary *bundle;
@@ -108,7 +116,10 @@
 
   OCMVerify(never(),
             [_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
-                                          name:kAnalyticsPullEvent
+                                          name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
+                                            return [value isEqualToString:kExternalEvent] ||
+                                                   [value isEqualToString:kInternalEvent];
+                                          }]
                                     parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
   XCTAssertEqual([_fakeLogs count], 0);
 }
@@ -118,14 +129,26 @@
 
   [_personalization logArmActive:@"key1" config:_configContainer];
 
-  OCMVerify(times(1),
+  OCMVerify(times(2),
             [_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
-                                          name:kAnalyticsPullEvent
+                                          name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
+                                            return [value isEqualToString:kExternalEvent] ||
+                                                   [value isEqualToString:kInternalEvent];
+                                          }]
                                     parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
-  XCTAssertEqual([_fakeLogs count], 1);
+  XCTAssertEqual([_fakeLogs count], 2);
+
+  NSDictionary *logParams = @{
+    kExternalRcParameterParam : @"key1",
+    kExternalArmValueParam : @"value1",
+    kExternalPersonalizationIdParam : @"p13n1",
+    kExternalArmIndexParam : @0,
+    kExternalGroupParam : @"BASELINE"
+  };
+  XCTAssertEqualObjects(_fakeLogs[0], logParams);
 
-  NSDictionary *params = @{kArmKey : @"id1", kArmValue : @"value1"};
-  XCTAssertEqualObjects(_fakeLogs[0], params);
+  NSDictionary *internalLogParams = @{kInternalChoiceIdParam : @"id1"};
+  XCTAssertEqualObjects(_fakeLogs[1], internalLogParams);
 }
 
 - (void)testMultiplePersonalizationKeys {
@@ -133,18 +156,40 @@
 
   [_personalization logArmActive:@"key1" config:_configContainer];
   [_personalization logArmActive:@"key2" config:_configContainer];
+  [_personalization logArmActive:@"key1" config:_configContainer];
 
-  OCMVerify(times(2),
+  OCMVerify(times(4),
             [_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
-                                          name:kAnalyticsPullEvent
+                                          name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
+                                            return [value isEqualToString:kExternalEvent] ||
+                                                   [value isEqualToString:kInternalEvent];
+                                          }]
                                     parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
-  XCTAssertEqual([_fakeLogs count], 2);
+  XCTAssertEqual([_fakeLogs count], 4);
+
+  NSDictionary *logParams1 = @{
+    kExternalRcParameterParam : @"key1",
+    kExternalArmValueParam : @"value1",
+    kExternalPersonalizationIdParam : @"p13n1",
+    kExternalArmIndexParam : @0,
+    kExternalGroupParam : @"BASELINE"
+  };
+  XCTAssertEqualObjects(_fakeLogs[0], logParams1);
+
+  NSDictionary *internalLogParams1 = @{kInternalChoiceIdParam : @"id1"};
+  XCTAssertEqualObjects(_fakeLogs[1], internalLogParams1);
 
-  NSDictionary *params1 = @{kArmKey : @"id1", kArmValue : @"value1"};
-  XCTAssertEqualObjects(_fakeLogs[0], params1);
+  NSDictionary *logParams2 = @{
+    kExternalRcParameterParam : @"key2",
+    kExternalArmValueParam : @"value2",
+    kExternalPersonalizationIdParam : @"p13n2",
+    kExternalArmIndexParam : @1,
+    kExternalGroupParam : @"P13N"
+  };
+  XCTAssertEqualObjects(_fakeLogs[2], logParams2);
 
-  NSDictionary *params2 = @{kArmKey : @"id2", kArmValue : @"value2"};
-  XCTAssertEqualObjects(_fakeLogs[1], params2);
+  NSDictionary *internalLogParams2 = @{kInternalChoiceIdParam : @"id2"};
+  XCTAssertEqualObjects(_fakeLogs[3], internalLogParams2);
 }
 
 - (void)testRemoteConfigIntegration {
@@ -152,17 +197,38 @@
 
   FIRRemoteConfigFetchAndActivateCompletion fetchAndActivateCompletion =
       ^void(FIRRemoteConfigFetchAndActivateStatus status, NSError *error) {
-        OCMVerify(times(2), [self->_analyticsMock
+        OCMVerify(times(4), [self->_analyticsMock
                                 logEventWithOrigin:kAnalyticsOriginPersonalization
-                                              name:kAnalyticsPullEvent
+                                              name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
+                                                return [value isEqualToString:kExternalEvent] ||
+                                                       [value isEqualToString:kInternalEvent];
+                                              }]
                                         parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
-        XCTAssertEqual([self->_fakeLogs count], 2);
-
-        NSDictionary *params1 = @{kArmKey : @"id1", kArmValue : @"value1"};
-        XCTAssertEqualObjects(self->_fakeLogs[0], params1);
-
-        NSDictionary *params2 = @{kArmKey : @"id2", kArmValue : @"value2"};
-        XCTAssertEqualObjects(self->_fakeLogs[1], params2);
+        XCTAssertEqual([self->_fakeLogs count], 4);
+
+        NSDictionary *logParams1 = @{
+          kExternalRcParameterParam : @"key1",
+          kExternalArmValueParam : @"value1",
+          kExternalPersonalizationIdParam : @"p13n1",
+          kExternalArmIndexParam : @0,
+          kExternalGroupParam : @"BASELINE"
+        };
+        XCTAssertEqualObjects(self->_fakeLogs[0], logParams1);
+
+        NSDictionary *internalLogParams1 = @{kInternalChoiceIdParam : @"id1"};
+        XCTAssertEqualObjects(self->_fakeLogs[1], internalLogParams1);
+
+        NSDictionary *logParams2 = @{
+          kExternalRcParameterParam : @"key1",
+          kExternalArmValueParam : @"value1",
+          kExternalPersonalizationIdParam : @"p13n1",
+          kExternalArmIndexParam : @0,
+          kExternalGroupParam : @"BASELINE"
+        };
+        XCTAssertEqualObjects(self->_fakeLogs[2], logParams2);
+
+        NSDictionary *internalLogParams2 = @{kInternalChoiceIdParam : @"id2"};
+        XCTAssertEqualObjects(self->_fakeLogs[3], internalLogParams2);
       };
 
   [_configInstance fetchAndActivateWithCompletionHandler:fetchAndActivateCompletion];
@@ -190,8 +256,17 @@
   NSDictionary *response = @{
     RCNFetchResponseKeyState : RCNFetchResponseKeyStateUpdate,
     RCNFetchResponseKeyEntries : @{@"key1" : @"value1", @"key2" : @"value2", @"key3" : @"value3"},
-    RCNFetchResponseKeyPersonalizationMetadata :
-        @{@"key1" : @{kPersonalizationId : @"id1"}, @"key2" : @{kPersonalizationId : @"id2"}}
+    RCNFetchResponseKeyPersonalizationMetadata : @{
+      @"key1" : @{
+        kPersonalizationId : @"p13n1",
+        kArmIndex : @0,
+        kChoiceId : @"id1",
+        kGroup : @"BASELINE"
+      },
+      @"key2" :
+          @{kPersonalizationId : @"p13n2", kArmIndex : @1, kChoiceId : @"id2", kGroup : @"P13N"}
+    }
+
   };
   return [OCMArg invokeBlockWithArgs:[NSJSONSerialization dataWithJSONObject:response
                                                                      options:0