Skip to content

Commit

Permalink
update Rust for recorded context to handle event store queries
Browse files Browse the repository at this point in the history
  • Loading branch information
jeddai committed Oct 8, 2024
1 parent c965aab commit 39936d2
Show file tree
Hide file tree
Showing 19 changed files with 586 additions and 98 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@

## ✨ What's New ✨

## ⚠️ Breaking Changes ⚠️

### Nimbus SDK ⛅️🔬🔭
- Added methods to `RecordedContext` for retrieving event queries and setting their values back to the foreign object ([#6322](https://github.com/mozilla/application-services/pull/6322)).

### Suggest
- Added support for Fakespot suggestions.
- Added support for recording metrics
Expand Down
32 changes: 22 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion components/nimbus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ name = "nimbus"
default=["stateful"]
rkv-safe-mode = ["dep:rkv"]
stateful-uniffi-bindings = []
stateful = ["rkv-safe-mode", "stateful-uniffi-bindings", "dep:remote_settings"]
stateful = ["rkv-safe-mode", "stateful-uniffi-bindings", "dep:remote_settings", "dep:regex"]

[dependencies]
anyhow = "1"
Expand All @@ -39,6 +39,7 @@ unicode-segmentation = "1.8.0"
error-support = { path = "../support/error" }
remote_settings = { path = "../remote_settings", optional = true }
cfg-if = "1.0.0"
regex = { version = "1.10.5", optional = true }

[build-dependencies]
uniffi = { workspace = true, features = ["build"] }
Expand All @@ -49,3 +50,4 @@ viaduct-reqwest = { path = "../support/viaduct-reqwest" }
env_logger = "0.10"
clap = "2.34"
tempfile = "3"
ctor = "0.2.2"
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import mozilla.telemetry.glean.config.Configuration
import mozilla.telemetry.glean.net.HttpStatus
import mozilla.telemetry.glean.net.PingUploader
import mozilla.telemetry.glean.testing.GleanTestRule
import org.json.JSONException
import org.json.JSONObject
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Ignore
Expand All @@ -37,7 +39,9 @@ import org.mozilla.experiments.nimbus.GleanMetrics.NimbusHealth
import org.mozilla.experiments.nimbus.internal.EnrollmentChangeEvent
import org.mozilla.experiments.nimbus.internal.EnrollmentChangeEventType
import org.mozilla.experiments.nimbus.internal.JsonObject
import org.mozilla.experiments.nimbus.internal.NimbusException
import org.mozilla.experiments.nimbus.internal.RecordedContext
import org.mozilla.experiments.nimbus.internal.validateEventQueries
import org.robolectric.RobolectricTestRunner
import java.util.Calendar
import java.util.concurrent.Executors
Expand Down Expand Up @@ -71,6 +75,7 @@ class NimbusTests {
private fun createNimbus(
coenrollingFeatureIds: List<String> = listOf(),
recordedContext: RecordedContext? = null,
block: Nimbus.() -> Unit = {},
) = Nimbus(
context = context,
appInfo = appInfo,
Expand All @@ -80,7 +85,7 @@ class NimbusTests {
observer = null,
delegate = nimbusDelegate,
recordedContext = recordedContext,
)
).also(block)

@get:Rule
val gleanRule = GleanTestRule(context)
Expand Down Expand Up @@ -734,21 +739,49 @@ class NimbusTests {
assertEquals("Event count must match", isReadyEvents.count(), 3)
}

@Test
fun `Nimbus records context if it's passed in`() {
class TestRecordedContext : RecordedContext {
var recordCount = 0
class TestRecordedContext(
private var eventQueries: MutableMap<String, Any>? = null,
) : RecordedContext {
var recorded = mutableListOf<JSONObject>()

override fun record() {
recordCount++
override fun getEventQueries(): JsonObject {
val queriesJson = JSONObject()
for ((key, value) in eventQueries ?: mapOf()) {
queriesJson.put(key, value)
}
return queriesJson
}

override fun toJson(): JsonObject {
val contextToRecord = JSONObject()
contextToRecord.put("enabled", true)
return contextToRecord
override fun setEventQueryValues(json: JsonObject) {
for (key in json.keys()) {
try {
eventQueries?.put(key, json.getDouble(key))
} catch (exception: JSONException) {
continue
}
}
}

override fun record() {
recorded.add(this.toJson())
}

override fun toJson(): JsonObject {
val contextToRecord = JSONObject()
contextToRecord.put("enabled", true)
val queries = this.getEventQueries()
for (key in queries.keys()) {
if (queries.get(key)::class == String::class) {
queries.remove(key)
}
}
contextToRecord.put("events", queries)
return contextToRecord
}
}

@Test
fun `Nimbus records context if it's passed in`() {
val context = TestRecordedContext()
val nimbus = createNimbus(recordedContext = context)

Expand All @@ -761,7 +794,42 @@ class NimbusTests {
job.join()
}

assertEquals(context.recordCount, 1)
assertEquals(context.recorded.size, 1)
}

@Test
fun `Nimbus recorded context event queries are run and the value is written back into the object`() {
val context = TestRecordedContext(
mutableMapOf(
"TEST_QUERY" to "'event'|eventSum('Days', 1, 0)",
),
)
val nimbus = createNimbus(recordedContext = context) {
recordEvent("event")
}

suspend fun getString(): String {
return testExperimentsJsonString(appInfo, packageName)
}

val job = nimbus.applyLocalExperiments(::getString)
runBlocking {
job.join()
}

assertEquals(context.recorded.size, 1)
assertEquals(context.recorded[0].getJSONObject("events").getDouble("TEST_QUERY"), 1.0, 0.0)
}

@Test
fun `Nimbus recorded context event queries are validated`() {
val context = TestRecordedContext(
mutableMapOf(
"FAILING_QUERY" to "'event'|eventSumThisWillFail('Days', 1, 0)",
),
)

assertThrows("Expected an error to be thrown", NimbusException::class.java, { validateEventQueries(context) })
}
}

Expand Down
11 changes: 11 additions & 0 deletions components/nimbus/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ pub enum NimbusError {
CirrusError(#[from] CirrusClientError),
#[error("UniFFI callback error: {0}")]
UniFFICallbackError(#[from] uniffi::UnexpectedUniFFICallbackError),
#[cfg(feature = "stateful")]
#[error("Regex error: {0}")]
RegexError(#[from] regex::Error),
}

#[cfg(feature = "stateful")]
Expand All @@ -81,6 +84,14 @@ pub enum BehaviorError {
IntervalParseError(String),
#[error("The event store is not available on the targeting attributes")]
MissingEventStore,
#[error("The recorded context is not available on the nimbus client")]
MissingRecordedContext,
#[error("EventQueryTypeParseError: {0} is not a valid EventQueryType")]
EventQueryTypeParseError(String),
#[error(r#"EventQueryParseError: "{0}" is not a valid EventQuery"#)]
EventQueryParseError(String),
#[error(r#"TypeError: "{0}" is not of type {1}"#)]
TypeError(String, String),
}

#[cfg(not(feature = "stateful"))]
Expand Down
16 changes: 15 additions & 1 deletion components/nimbus/src/nimbus.udl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ typedef extern RemoteSettingsConfig;
[External="remote_settings"]
typedef extern RemoteSettingsServer;

namespace nimbus {};
namespace nimbus {

/// A test utility used to validate event queries against the jexl evaluator.
///
/// This method should only be used in tests.
[Throws=NimbusError]
void validate_event_queries(RecordedContext recorded_context);
};

dictionary AppContext {
string app_name;
string app_id;
Expand Down Expand Up @@ -104,6 +112,7 @@ enum NimbusError {
"InvalidPath", "InternalError", "NoSuchExperiment", "NoSuchBranch",
"DatabaseNotReady", "VersionParsingError", "BehaviorError", "TryFromIntError",
"ParseIntError", "TransformParameterError", "ClientError", "UniFFICallbackError",
"RegexError",
};

[Custom]
Expand All @@ -113,6 +122,10 @@ typedef string JsonObject;
interface RecordedContext {
JsonObject to_json();

JsonObject get_event_queries();

void set_event_query_values(JsonObject json);

void record();
};

Expand Down Expand Up @@ -283,6 +296,7 @@ interface NimbusClient {

[Throws=NimbusError]
void dump_state_to_log();

};

interface NimbusTargetingHelper {
Expand Down
Loading

0 comments on commit 39936d2

Please sign in to comment.