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:
Родитель
e189a73377
Коммит
6d39fd2350
|
@ -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 {
|
||||
|
|
Загрузка…
Ссылка в новой задаче