Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
- Added `list_credit_card_candidates()`, returning `CreditCardCandidate`: a card minus its encrypted fields, with `cc_number_last_4` and `has_cvv` in their place. Like `list_candidates()` in logins it never touches the key, so an application can list cards without a primary password prompt and call `get_credit_card()` for the single card whose number or CVV it needs. ([bug 2080209](https://bugzilla.mozilla.org/show_bug.cgi?id=2080209))
- Credit cards can store an optional CVV, encrypted like the number and handled transparently: `UpdatableCreditCardFields.cc_cvv` (cleartext in, defaults to none) and `CreditCard.cc_cvv` (decrypted out). A v6 schema upgrade adds the column; existing rows simply have no CVV. The CVV syncs as a new optional `cc-cvv` payload field; records written by older clients round-trip it through the mirror's unknown-fields handling, so their edits do not drop it. ([bug 2076154](https://bugzilla.mozilla.org/show_bug.cgi?id=2076154))

### Nimbus
- Disabling experiments or rollouts will submit an event. [#7656](https://github.com/mozilla/application-services/pull/7656)
- If requesting experiment or rollout participation on Android throws, the getter will return true instead of throwing. This aligns the Android behaviour with iOS. [#7656](https://github.com/mozilla/application-services/pull/7656)
- Enabling experiments or rollouts while they are already enabled no longer performs an update. [#7656](https://github.com/mozilla/application-services/pull/7656)

### Search

- Added APIs to `SearchEngineSelector` for a new `search-config-v3` collection.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,20 +164,34 @@ open class Nimbus(
override fun submitTargetingContext() {
org.mozilla.experiments.nimbus.GleanMetrics.Pings.nimbusTargetingContext.submit()
}

override fun recordRolloutsOptOut() {
NimbusEvents.rolloutsOptOut.record()
}

override fun recordStudiesOptOut() {
NimbusEvents.studiesOptOut.record()
}
}

private val nimbusClient: NimbusClientInterface

override var experimentParticipation: Boolean
get() = nimbusClient.getExperimentParticipation()
get() = withCatchAll("getExperimentParticipation") {
nimbusClient.getExperimentParticipation()
} ?: true

set(active) {
dbScope.launch {
setExperimentParticipationOnThisThread(active)
}
}

override var rolloutParticipation: Boolean
get() = nimbusClient.getRolloutParticipation()
get() = withCatchAll("getExperimentParticipation") {
nimbusClient.getRolloutParticipation()
} ?: true

set(active) {
dbScope.launch {
setRolloutParticipationOnThisThread(active)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1118,20 +1118,87 @@ class NimbusTests {
nimbus.getFeatureUpdateDispatcher()?.addCallback("foo", { calls++ })

nimbus.setExperimentParticipationOnThisThread(false)
assertEquals(
0,
NimbusEvents.rolloutsOptOut.testGetValue("events")?.size ?: 0,
)
assertEquals(
1,
NimbusEvents.studiesOptOut.testGetValue("events")?.size ?: 0,
)
assertCalls(1)

// The experiment won't re-enroll so there is no change.
nimbus.setExperimentParticipationOnThisThread(true)
assertCalls(1)

nimbus.setRolloutParticipationOnThisThread(false)
assertEquals(
1,
NimbusEvents.rolloutsOptOut.testGetValue("events")?.size ?: 0,
)
assertEquals(
1,
NimbusEvents.studiesOptOut.testGetValue("events")?.size ?: 0,
)
assertCalls(2)

// Rollout do re-enroll, so there is a change here.
nimbus.setRolloutParticipationOnThisThread(true)
assertCalls(3)
}

@Test
fun `setExperimentParticipation et al trigger telemetry`() {
fun getRolloutsOptOutCount() = NimbusEvents.rolloutsOptOut.testGetValue("events")?.size ?: 0
fun getStudiesOptOutCount() = NimbusEvents.studiesOptOut.testGetValue("events")?.size ?: 0

assertTrue(nimbus.experimentParticipation)
assertTrue(nimbus.rolloutParticipation)
assertEquals(0, getRolloutsOptOutCount())
assertEquals(0, getStudiesOptOutCount())

nimbus.setRolloutParticipationOnThisThread(true)
assertEquals(0, getRolloutsOptOutCount())
assertEquals(0, getStudiesOptOutCount())

nimbus.setRolloutParticipationOnThisThread(false)
assertEquals(1, getRolloutsOptOutCount())
assertEquals(0, getStudiesOptOutCount())

nimbus.setRolloutParticipationOnThisThread(false)
assertEquals(1, getRolloutsOptOutCount())
assertEquals(0, getStudiesOptOutCount())

nimbus.setRolloutParticipationOnThisThread(true)
assertEquals(1, getRolloutsOptOutCount())
assertEquals(0, getStudiesOptOutCount())

nimbus.setRolloutParticipationOnThisThread(false)
assertEquals(2, getRolloutsOptOutCount())
assertEquals(0, getStudiesOptOutCount())

nimbus.setExperimentParticipationOnThisThread(true)
assertEquals(2, getRolloutsOptOutCount())
assertEquals(0, getStudiesOptOutCount())

nimbus.setExperimentParticipationOnThisThread(false)
assertEquals(2, getRolloutsOptOutCount())
assertEquals(1, getStudiesOptOutCount())

nimbus.setExperimentParticipationOnThisThread(false)
assertEquals(2, getRolloutsOptOutCount())
assertEquals(1, getStudiesOptOutCount())

nimbus.setExperimentParticipationOnThisThread(true)
assertEquals(2, getRolloutsOptOutCount())
assertEquals(1, getStudiesOptOutCount())

nimbus.setExperimentParticipationOnThisThread(false)
assertEquals(2, getRolloutsOptOutCount())
assertEquals(2, getStudiesOptOutCount())
}

@Test
fun `resetTelemetryIdentifiers triggers callbacks`() {
val nimbus = createNimbus()
Expand Down
35 changes: 35 additions & 0 deletions components/nimbus/metrics.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,41 @@ nimbus_events:
- chumphreys@mozilla.com
- project-nimbus@mozilla.com
expires: never

rollouts_opt_out:
type: event
description: >
Emitted when a user opts out of rollouts (remote improvements).

bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=2081293
data_reviews:
- https://github.com/mozilla/application-services/pull/7656#pullrequestreview-5522772594

data_sensitivity:
- interaction
notification_emails:
- beth@mozilla.com
- project-nimbus@mozilla.com
expires: never

studies_opt_out:
type: event
description: >
Emitted when a user opts out of studies (experiments).

bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=2081305
data_reviews:
- https://github.com/mozilla/application-services/pull/7656#pullrequestreview-5522772594

data_sensitivity:
- interaction
notification_emails:
- beth@mozilla.com
- project-nimbus@mozilla.com
expires: never

nimbus_health:
cache_not_ready_for_feature:
type: event
Expand Down
4 changes: 4 additions & 0 deletions components/nimbus/src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ mod detail {
fn record_malformed_feature_config(&self, event: MalformedFeatureConfigExtraDef);

fn submit_targeting_context(&self);

fn record_rollouts_opt_out(&self);

fn record_studies_opt_out(&self);
}

#[derive(Clone)]
Expand Down
4 changes: 4 additions & 0 deletions components/nimbus/src/nimbus.udl
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ interface MetricsHandler {
void record_malformed_feature_config(MalformedFeatureConfigExtraDef event);

void submit_targeting_context();

void record_rollouts_opt_out();

void record_studies_opt_out();
};

dictionary DatabaseLoadExtraDef {
Expand Down
16 changes: 12 additions & 4 deletions components/nimbus/src/stateful/enrollment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,14 +439,22 @@ pub fn set_experiment_participation(
db: &Database,
writer: &mut Writer,
opt_in: bool,
) -> Result<()> {
) -> Result<bool> {
let old_value =
get_experiment_participation(db, writer).unwrap_or(DEFAULT_EXPERIMENT_PARTICIPATION);

let store = db.get_store(StoreId::Meta);
store.put(writer, DB_KEY_EXPERIMENT_PARTICIPATION, &opt_in)
store.put(writer, DB_KEY_EXPERIMENT_PARTICIPATION, &opt_in)?;

Ok(old_value)
}

pub fn set_rollout_participation(db: &Database, writer: &mut Writer, opt_in: bool) -> Result<()> {
pub fn set_rollout_participation(db: &Database, writer: &mut Writer, opt_in: bool) -> Result<bool> {
let old_value = get_rollout_participation(db, writer).unwrap_or(DEFAULT_ROLLOUT_PARTICIPATION);
let store = db.get_store(StoreId::Meta);
store.put(writer, DB_KEY_ROLLOUT_PARTICIPATION, &opt_in)
store.put(writer, DB_KEY_ROLLOUT_PARTICIPATION, &opt_in)?;

Ok(old_value)
}

/// Reset unique identifiers in response to application-level telemetry reset.
Expand Down
54 changes: 36 additions & 18 deletions components/nimbus/src/stateful/nimbus_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,15 +286,24 @@ impl NimbusClient {
let db = self.db()?;
let mut writer = db.write()?;
let mut state = self.mutable_state.lock().unwrap();
set_experiment_participation(db, &mut writer, user_participating)?;

let existing_experiments: Vec<Experiment> =
db.get_store(StoreId::Experiments).collect_all(&writer)?;
let events = self.evolve_experiments(db, &mut writer, &mut state, &existing_experiments)?;
let res = self.end_initialize(db, writer, &mut state);
self.record_enrollment_status_telemetry(&mut state)?;
res?;
Ok(events)
let was_participating = set_experiment_participation(db, &mut writer, user_participating)?;

if was_participating != user_participating {
if !user_participating {
self.metrics_handler.record_studies_opt_out();
}

let existing_experiments: Vec<Experiment> =
db.get_store(StoreId::Experiments).collect_all(&writer)?;
let events =
self.evolve_experiments(db, &mut writer, &mut state, &existing_experiments)?;
let res = self.end_initialize(db, writer, &mut state);
self.record_enrollment_status_telemetry(&mut state)?;
res?;
Ok(events)
} else {
Ok(vec![])
}
}

pub fn set_rollout_participation(
Expand All @@ -304,15 +313,24 @@ impl NimbusClient {
let db = self.db()?;
let mut writer = db.write()?;
let mut state = self.mutable_state.lock().unwrap();
set_rollout_participation(db, &mut writer, user_participating)?;

let existing_experiments: Vec<Experiment> =
db.get_store(StoreId::Experiments).collect_all(&writer)?;
let events = self.evolve_experiments(db, &mut writer, &mut state, &existing_experiments)?;
let res = self.end_initialize(db, writer, &mut state);
self.record_enrollment_status_telemetry(&mut state)?;
res?;
Ok(events)
let was_participating = set_rollout_participation(db, &mut writer, user_participating)?;

if was_participating != user_participating {
if !user_participating {
self.metrics_handler.record_rollouts_opt_out();
}

let existing_experiments: Vec<Experiment> =
db.get_store(StoreId::Experiments).collect_all(&writer)?;
let events =
self.evolve_experiments(db, &mut writer, &mut state, &existing_experiments)?;
let res = self.end_initialize(db, writer, &mut state);
self.record_enrollment_status_telemetry(&mut state)?;
res?;
Ok(events)
} else {
Ok(vec![])
}
}

pub fn get_active_experiments(&self) -> Result<Vec<EnrolledExperiment>> {
Expand Down
12 changes: 12 additions & 0 deletions components/nimbus/src/tests/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,8 @@ mod detail {
pub exposures: Vec<FeatureExposureExtraDef>,
pub malformeds: Vec<MalformedFeatureConfigExtraDef>,
pub submit_targeting_context_calls: u64,
pub rollouts_opt_out_events: u64,
pub studies_opt_out_events: u64,
}

impl TestMetrics {
Expand Down Expand Up @@ -867,6 +869,16 @@ mod detail {
let mut state = self.state.lock().unwrap();
state.submit_targeting_context_calls += 1;
}

fn record_rollouts_opt_out(&self) {
let mut state = self.state.lock().unwrap();
state.rollouts_opt_out_events += 1
}

fn record_studies_opt_out(&self) {
let mut state = self.state.lock().unwrap();
state.studies_opt_out_events += 1
}
}
}

Expand Down
5 changes: 3 additions & 2 deletions components/nimbus/src/tests/stateful/test_nimbus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -906,15 +906,16 @@ fn event_store_on_targeting_attributes_is_updated_after_an_event_is_recorded() -
"last_modified": 1_602_197_324_372i64,
}
]}))?;
client.set_experiments_locally(experiment_json)?;
client.set_experiments_locally(experiment_json.clone())?;
client.apply_pending_experiments()?;

let active_experiments = client.get_active_experiments()?;
assert_eq!(active_experiments.len(), 1);

client.record_event("app.foregrounded".to_string(), 1)?;

client.set_experiment_participation(true)?;
client.set_experiments_locally(experiment_json)?;
client.apply_pending_experiments()?;

// The number of non-zero days in our event store is 2, so the first experiment
// should be applied, but the second experiment will not be.
Expand Down
9 changes: 8 additions & 1 deletion components/nimbus/tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,17 @@ impl MetricsHandler for NoopMetricsHandler {
// do nothing
}

#[cfg(feature = "stateful")]
fn submit_targeting_context(&self) {
// do nothing
}

fn record_rollouts_opt_out(&self) {
// do nothing
}

fn record_studies_opt_out(&self) {
// do nothing
}
}

#[cfg(not(feature = "stateful"))]
Expand Down
8 changes: 8 additions & 0 deletions examples/nimbus/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,14 @@ fn main() -> Result<()> {
fn submit_targeting_context(&self) {
// do nothing
}

fn record_rollouts_opt_out(&self) {
// do nothing
}

fn record_studies_opt_out(&self) {
// do nothing
}
}

// We set the logging level to be `warn` here, meaning that only
Expand Down
Loading