Change custom targeting attributes into JSONObject (#5229) r=travis

* Change custom targeting attributes into JSONObject

* Add changelog

* Address reviewer comments
This commit is contained in:
jhugman 2022-11-11 16:39:08 +00:00 коммит произвёл GitHub
Родитель e189a73377
Коммит 6d39fd2350
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 59 добавлений и 19 удалений

Просмотреть файл

@ -24,3 +24,6 @@ Use the template below to make assigning a version number during the release cut
### ✨ What's New ✨
- `active_experiments` is available to JEXL as a set containing slugs of all enrolled experiments ([#5227](https://github.com/mozilla/application-services/pull/5227))
- Added query method for behavioral targeting event store ([#5226](https://github.com/mozilla/application-services/pull/5226))
### ⚠️ Breaking Changes ⚠️
- Changed the type of `customTargetingAttributes` in `NimbusAppSettings` to a `JSONObject`. The change will be breaking only for Android. ([#5229](https://github.com/mozilla/application-services/pull/5229))

Просмотреть файл

@ -309,7 +309,7 @@ data class NimbusAppInfo(
*
* Example: mapOf("userType": "casual", "isFirstTime": "true")
*/
val customTargetingAttributes: Map<String, String> = mapOf()
val customTargetingAttributes: JSONObject = JSONObject()
)
/**

Просмотреть файл

@ -0,0 +1,26 @@
/* This Source Code Form is subject to the terms of the Mozilla
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Foundation
internal extension Dictionary where Key == String, Value == Any {
func stringify() throws -> String {
let data = try JSONSerialization.data(withJSONObject: self)
guard let s = String(data: data, encoding: .utf8) else {
throw NimbusError.JsonError(message: "Unable to encode")
}
return s
}
static func parse(jsonString string: String) throws -> [String: Any] {
guard let data = string.data(using: .utf8) else {
throw NimbusError.JsonError(message: "Unable to decode string into data")
}
let obj = try JSONSerialization.jsonObject(with: data)
guard let obj = obj as? [String: Any] else {
throw NimbusError.JsonError(message: "Unable to cast into JSONObject")
}
return obj
}
}

Просмотреть файл

@ -139,13 +139,10 @@ extension Nimbus: FeaturesInterface {
internal func getFeatureConfigVariablesJson(featureId: String) -> [String: Any]? {
do {
if let string = try nimbusClient.getFeatureConfigVariables(featureId: featureId),
let data = string.data(using: .utf8)
{
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} else {
guard let string = try nimbusClient.getFeatureConfigVariables(featureId: featureId) else {
return nil
}
return try Dictionary.parse(jsonString: string)
} catch NimbusError.DatabaseNotReady {
GleanMetrics.NimbusHealth.cacheNotReadyForFeature.record(
GleanMetrics.NimbusHealth.CacheNotReadyForFeatureExtra(
@ -324,8 +321,7 @@ extension Nimbus: GleanPlumbProtocol {
}
public func createMessageHelper(additionalContext: [String: Any]) throws -> GleanPlumbMessageHelper {
let data = try JSONSerialization.data(withJSONObject: additionalContext, options: [])
let string = String(data: data, encoding: .utf8)
let string = try additionalContext.stringify()
return try createMessageHelper(string: string)
}

Просмотреть файл

@ -164,7 +164,7 @@ public let remoteSettingsCollection = "nimbus-mobile-experiments"
/// The specifc context is there to capture any context that the SDK doesn't need to be explictly aware of.
///
public struct NimbusAppSettings {
public init(appName: String, channel: String, customTargetingAttributes: [String: String] = [String: String]()) {
public init(appName: String, channel: String, customTargetingAttributes: [String: Any] = [String: Any]()) {
self.appName = appName
self.channel = channel
self.customTargetingAttributes = customTargetingAttributes
@ -172,7 +172,7 @@ public struct NimbusAppSettings {
public let appName: String
public let channel: String
public let customTargetingAttributes: [String: String]
public let customTargetingAttributes: [String: Any]
}
/// This error reporter is passed to `Nimbus` and any errors that are caught are reported via this type.

Просмотреть файл

@ -95,7 +95,7 @@ public extension Nimbus {
debugTag: "Nimbus.rs",
installationDate: installationDateSinceEpoch,
homeDirectory: nil,
customTargetingAttributes: appSettings.customTargetingAttributes
customTargetingAttributes: try? appSettings.customTargetingAttributes.stringify()
)
}
}

Просмотреть файл

@ -9,7 +9,7 @@
//! provided by the consuming client.
//!
use serde_derive::*;
use std::collections::HashMap;
use serde_json::{Map, Value};
/// The `AppContext` object represents the parameters and characteristics of the
/// consuming application that we are interested in for targeting purposes. The
/// `app_name` and `channel` fields are not optional as they are expected
@ -52,5 +52,5 @@ pub struct AppContext {
pub installation_date: Option<i64>,
pub home_directory: Option<String>,
#[serde(flatten)]
pub custom_targeting_attributes: Option<HashMap<String, String>>,
pub custom_targeting_attributes: Option<Map<String, Value>>,
}

Просмотреть файл

@ -17,7 +17,7 @@ dictionary AppContext {
// the unix time, which is milliseconds since epoch
i64? installation_date;
string? home_directory;
record<DOMString, string>? custom_targeting_attributes;
JsonObject? custom_targeting_attributes;
};
dictionary EnrolledExperiment {

Просмотреть файл

@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use serde_json::{json, Map, Value};
use crate::enrollment::{EnrolledReason, EnrollmentStatus, NotEnrolledReason};
use crate::evaluator::{choose_branch, targeting};
use crate::{
@ -327,17 +329,17 @@ fn test_targeting() {
})
));
}
use std::collections::{HashMap, HashSet};
use std::collections::HashSet;
#[test]
fn test_targeting_custom_targeting_attributes() {
// Here's our valid jexl statement
let expression_statement =
"app_id == '1010' && (app_version == '4.4' || locale == \"en-US\") && is_first_run == 'true' && ios_version == '8.8'";
"app_id == '1010' && (app_version == '4.4' || locale == \"en-US\") && is_first_run == true && ios_version == '8.8'";
let mut custom_targeting_attributes = HashMap::new();
custom_targeting_attributes.insert("is_first_run".into(), "true".into());
custom_targeting_attributes.insert("ios_version".into(), "8.8".into());
let mut custom_targeting_attributes = Map::<String, Value>::new();
custom_targeting_attributes.insert("is_first_run".into(), json!(true));
custom_targeting_attributes.insert("ios_version".into(), json!("8.8"));
// A matching context that includes the appropriate specific context
let targeting_attributes = AppContext {
app_name: "nimbus_test".to_string(),

Просмотреть файл

@ -58,6 +58,7 @@
1BF50F1927B1E19500A9C8A5 /* FxAccountManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BBAC54127AE065300DAFEF2 /* FxAccountManagerTests.swift */; };
1BF50F1A27B1E19800A9C8A5 /* FxAccountMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BBAC53C27AE065300DAFEF2 /* FxAccountMocks.swift */; };
1BFC469827C99F250034E0A5 /* Metrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BFC469627C99F250034E0A5 /* Metrics.swift */; };
3963A5862919A541001ED4C3 /* Dictionary+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3963A5852919A541001ED4C3 /* Dictionary+.swift */; };
45CC574A28AD9C86006D55AA /* errorsupport.udl in Sources */ = {isa = PBXBuildFile; fileRef = 45CC574828AD9C31006D55AA /* errorsupport.udl */; };
/* End PBXBuildFile section */
@ -167,6 +168,7 @@
1BF50F2327B1E53E00A9C8A5 /* metrics.yaml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; name = metrics.yaml; path = ../../../components/nimbus/metrics.yaml; sourceTree = SOURCE_ROOT; };
1BF50F2B27B1EB7D00A9C8A5 /* sdk_generator.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = sdk_generator.sh; path = "../../../../../components/external/glean/glean-core/ios/sdk_generator.sh"; sourceTree = SOURCE_ROOT; };
1BFC469627C99F250034E0A5 /* Metrics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Metrics.swift; path = MozillaTestServices/Generated/Metrics.swift; sourceTree = SOURCE_ROOT; };
3963A5852919A541001ED4C3 /* Dictionary+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Dictionary+.swift"; path = "Nimbus/Dictionary+.swift"; sourceTree = "<group>"; };
45CC574528AD9C0B006D55AA /* errorFFI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = errorFFI.h; path = ../../../../components/support/error/ios/Generated/errorFFI.h; sourceTree = "<group>"; };
45CC574628AD9C0B006D55AA /* errorsupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = errorsupport.swift; path = ../../../../components/support/error/ios/Generated/errorsupport.swift; sourceTree = "<group>"; };
45CC574828AD9C31006D55AA /* errorsupport.udl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = errorsupport.udl; path = ../../../../components/support/error/src/errorsupport.udl; sourceTree = "<group>"; };
@ -199,6 +201,7 @@
1BF50EF327B1DD7D00A9C8A5 /* Generated */,
1B3BC98E27B1D9D600229CF6 /* nimbus.udl */,
1B3BC97527B1D9B700229CF6 /* Collections+.swift */,
3963A5852919A541001ED4C3 /* Dictionary+.swift */,
1B3BC97627B1D9B700229CF6 /* FeatureHolder.swift */,
1B3BC97B27B1D9B700229CF6 /* FeatureInterface.swift */,
1B3BC97927B1D9B700229CF6 /* FeatureVariables.swift */,
@ -635,6 +638,7 @@
1B3BC94D27B1D7B700229CF6 /* RustLog.swift in Sources */,
1BF50F1627B1E18000A9C8A5 /* KeychainWrapper.swift in Sources */,
1BF50F1227B1E17B00A9C8A5 /* FxAccountManager.swift in Sources */,
3963A5862919A541001ED4C3 /* Dictionary+.swift in Sources */,
1B3BC98D27B1D9B800229CF6 /* NimbusCreate.swift in Sources */,
1B3BC98727B1D9B800229CF6 /* Nimbus.swift in Sources */,
1B3BC98827B1D9B800229CF6 /* FeatureInterface.swift in Sources */,

Просмотреть файл

@ -373,6 +373,15 @@ class NimbusTests: XCTestCase {
)
XCTAssertNotNil(disqualificationEventExtras!["enrollment_id"], "Experiment enrollment id must not be nil")
}
func testNimbusCreateWithJson() throws {
let appSettings = NimbusAppSettings(appName: "test", channel: "nightly", customTargetingAttributes: ["is_first_run": false, "is_test": true])
let nimbus = try Nimbus.create(nil, appSettings: appSettings, dbPath: createDatabasePath())
let helper = try nimbus.createMessageHelper()
XCTAssertTrue(try helper.evalJexl(expression: "is_test"))
XCTAssertFalse(try helper.evalJexl(expression: "is_first_run"))
}
}
private extension Device {