Skip to content

Commit 38ab05f

Browse files
committed
Bug 2003370 - Use and react to Gecko preferences stored values
This patch adds functionality to use the new GeckoPref's PreviousState. It adds: * `set_gecko_prefs_original_values` for external handling of setting prefs back to original values * Mechanisms to return to a previous states when: * `on_experiment_updated` * Certain situations and as determined in `will_pref_experiment_change` * `on_experiment_ended` * `on_opt_out`
1 parent 40275a1 commit 38ab05f

File tree

10 files changed

+694
-51
lines changed

10 files changed

+694
-51
lines changed

components/nimbus/android/src/test/java/org/mozilla/experiments/nimbus/NimbusTests.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import org.mozilla.experiments.nimbus.internal.GeckoPrefHandler
4343
import org.mozilla.experiments.nimbus.internal.GeckoPrefState
4444
import org.mozilla.experiments.nimbus.internal.JsonObject
4545
import org.mozilla.experiments.nimbus.internal.NimbusException
46+
import org.mozilla.experiments.nimbus.internal.OriginalGeckoPref
4647
import org.mozilla.experiments.nimbus.internal.PrefBranch
4748
import org.mozilla.experiments.nimbus.internal.PrefEnrollmentData
4849
import org.mozilla.experiments.nimbus.internal.PrefUnenrollReason
@@ -860,6 +861,7 @@ class NimbusTests {
860861
),
861862
),
862863
var setValues: List<GeckoPrefState>? = null,
864+
var originalGeckoPrefs: List<OriginalGeckoPref>? = null,
863865
) : GeckoPrefHandler {
864866
override fun getPrefsWithState(): Map<String, Map<String, GeckoPrefState>> {
865867
return internalMap
@@ -868,6 +870,10 @@ class NimbusTests {
868870
override fun setGeckoPrefsState(newPrefsState: List<GeckoPrefState>) {
869871
setValues = newPrefsState
870872
}
873+
874+
override fun setGeckoPrefsOriginalValues(originalGeckoPrefs: List<OriginalGeckoPref>) {
875+
originalGeckoPrefs = originalGeckoPrefs
876+
}
871877
}
872878

873879
@Test
@@ -889,6 +895,21 @@ class NimbusTests {
889895
assertEquals("42", handler.setValues?.get(0)?.enrollmentValue?.prefValue)
890896
}
891897

898+
@Test
899+
fun `GeckoPrefHandler setGeckoPrefsOriginalValues function`() {
900+
val handler = TestGeckoPrefHandler()
901+
val originalValues = listOf(
902+
OriginalGeckoPref(
903+
pref = "pref.number",
904+
branch = PrefBranch.DEFAULT,
905+
value = "1",
906+
),
907+
)
908+
handler.setGeckoPrefsOriginalValues(originalValues)
909+
assertEquals(1, handler.originalGeckoPrefs?.size)
910+
assertEquals("pref.number", handler.originalGeckoPrefs?.get(0)?.pref)
911+
}
912+
892913
@Test
893914
fun `unenroll for gecko pref functions`() {
894915
val handler = TestGeckoPrefHandler()

components/nimbus/src/enrollment.rs

Lines changed: 132 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44
#[cfg(feature = "stateful")]
5-
use crate::stateful::gecko_prefs::{OriginalGeckoPref, PrefUnenrollReason};
5+
use crate::stateful::gecko_prefs::{GeckoPrefStore, OriginalGeckoPref, PrefUnenrollReason};
66
use crate::{
77
defaults::Defaults,
88
error::{debug, warn, NimbusError, Result},
@@ -11,6 +11,8 @@ use crate::{
1111
SLUG_REPLACEMENT_PATTERN,
1212
};
1313
use serde_derive::*;
14+
#[cfg(feature = "stateful")]
15+
use std::sync::Arc;
1416
use std::{
1517
collections::{HashMap, HashSet},
1618
fmt::{Display, Formatter, Result as FmtResult},
@@ -154,6 +156,24 @@ pub enum PreviousState {
154156
GeckoPref(PreviousGeckoPrefState),
155157
}
156158

159+
#[cfg(feature = "stateful")]
160+
impl PreviousState {
161+
#[cfg(feature = "stateful")]
162+
pub(crate) fn on_revert_to_previous_state(
163+
&self,
164+
#[cfg(feature = "stateful")] gecko_pref_store: &Option<Arc<GeckoPrefStore>>,
165+
) {
166+
match self {
167+
PreviousState::GeckoPref(previous_gecko_pref_state) => {
168+
if let Some(store) = gecko_pref_store {
169+
store.handler.set_gecko_prefs_original_values(
170+
previous_gecko_pref_state.original_values.clone(),
171+
)
172+
}
173+
}
174+
}
175+
}
176+
}
157177
// Every experiment has an ExperimentEnrollment, even when we aren't enrolled.
158178
// ⚠️ Attention : Changes to this type should be accompanied by a new test ⚠️
159179
// ⚠️ in `src/stateful/tests/test_enrollment_bw_compat.rs` below, and may require a DB migration. ⚠️
@@ -241,6 +261,7 @@ impl ExperimentEnrollment {
241261
available_randomization_units: &AvailableRandomizationUnits,
242262
updated_experiment: &Experiment,
243263
targeting_helper: &NimbusTargetingHelper,
264+
#[cfg(feature = "stateful")] gecko_pref_store: &Option<Arc<GeckoPrefStore>>,
244265
out_enrollment_events: &mut Vec<EnrollmentChangeEvent>,
245266
) -> Result<Self> {
246267
Ok(match &self.status {
@@ -274,6 +295,14 @@ impl ExperimentEnrollment {
274295
"Existing experiment enrollment '{}' is now disqualified (global opt-out)",
275296
&self.slug
276297
);
298+
#[cfg(feature = "stateful")]
299+
if let EnrollmentStatus::Enrolled {
300+
previous_state: Some(previous_state),
301+
..
302+
} = &self.status
303+
{
304+
previous_state.on_revert_to_previous_state(gecko_pref_store);
305+
}
277306
let updated_enrollment =
278307
self.disqualify_from_enrolled(DisqualifiedReason::OptOut);
279308
out_enrollment_events.push(updated_enrollment.get_change_event());
@@ -294,6 +323,17 @@ impl ExperimentEnrollment {
294323
updated_experiment,
295324
targeting_helper,
296325
)?;
326+
327+
#[cfg(feature = "stateful")]
328+
if self.will_pref_experiment_change(updated_experiment, &evaluated_enrollment) {
329+
if let EnrollmentStatus::Enrolled {
330+
previous_state: Some(previous_state),
331+
..
332+
} = &self.status
333+
{
334+
previous_state.on_revert_to_previous_state(gecko_pref_store);
335+
}
336+
}
297337
match evaluated_enrollment.status {
298338
EnrollmentStatus::Error { .. } => {
299339
let updated_enrollment =
@@ -378,6 +418,7 @@ impl ExperimentEnrollment {
378418
/// from the database after `PREVIOUS_ENROLLMENTS_GC_TIME`.
379419
fn on_experiment_ended(
380420
&self,
421+
#[cfg(feature = "stateful")] gecko_prefs: &Option<Arc<GeckoPrefStore>>,
381422
out_enrollment_events: &mut Vec<EnrollmentChangeEvent>,
382423
) -> Option<Self> {
383424
debug!(
@@ -391,6 +432,16 @@ impl ExperimentEnrollment {
391432
| EnrollmentStatus::WasEnrolled { .. }
392433
| EnrollmentStatus::Error { .. } => return None, // We were never enrolled anyway, simply delete the enrollment record from the DB.
393434
};
435+
436+
#[cfg(feature = "stateful")]
437+
if let EnrollmentStatus::Enrolled {
438+
previous_state: Some(previous_state),
439+
..
440+
} = &self.status
441+
{
442+
previous_state.on_revert_to_previous_state(gecko_prefs);
443+
}
444+
394445
let enrollment = Self {
395446
slug: self.slug.clone(),
396447
status: EnrollmentStatus::WasEnrolled {
@@ -408,9 +459,18 @@ impl ExperimentEnrollment {
408459
pub(crate) fn on_explicit_opt_out(
409460
&self,
410461
out_enrollment_events: &mut Vec<EnrollmentChangeEvent>,
462+
#[cfg(feature = "stateful")] gecko_prefs: &Option<Arc<GeckoPrefStore>>,
411463
) -> ExperimentEnrollment {
412-
match self.status {
413-
EnrollmentStatus::Enrolled { .. } => {
464+
match &self.status {
465+
EnrollmentStatus::Enrolled {
466+
#[cfg(feature = "stateful")]
467+
previous_state,
468+
..
469+
} => {
470+
#[cfg(feature = "stateful")]
471+
if let Some(previous_state) = previous_state {
472+
previous_state.on_revert_to_previous_state(gecko_prefs);
473+
}
414474
let enrollment = self.disqualify_from_enrolled(DisqualifiedReason::OptOut);
415475
out_enrollment_events.push(enrollment.get_change_event());
416476
enrollment
@@ -561,6 +621,57 @@ impl ExperimentEnrollment {
561621
| EnrollmentStatus::Error { .. } => self.clone(),
562622
}
563623
}
624+
625+
#[cfg(feature = "stateful")]
626+
pub(crate) fn will_pref_experiment_change(
627+
&self,
628+
updated_experiment: &Experiment,
629+
updated_enrollment: &ExperimentEnrollment,
630+
) -> bool {
631+
let (original_feature_id, original_branch_slug) = match &self.status {
632+
EnrollmentStatus::Enrolled {
633+
previous_state: Some(PreviousState::GeckoPref(previous_state)),
634+
branch,
635+
..
636+
} => (&previous_state.feature_id, branch),
637+
// Can't change if it isn't a pref experiment
638+
_ => {
639+
return false;
640+
}
641+
};
642+
643+
let updated_branch_slug = match &updated_enrollment.status {
644+
EnrollmentStatus::Enrolled { branch, .. } => branch,
645+
// If we are no longer going to be enrolled, then a change happened
646+
_ => {
647+
return true;
648+
}
649+
};
650+
651+
// Branch changed
652+
if updated_branch_slug != original_branch_slug {
653+
return true;
654+
}
655+
656+
let Some(branch) = updated_experiment.get_branch(updated_branch_slug) else {
657+
return true;
658+
};
659+
660+
// Feature changed
661+
match &branch.feature {
662+
Some(updated_feature) => {
663+
if updated_feature.feature_id != *original_feature_id {
664+
return true;
665+
}
666+
}
667+
None => {
668+
return true;
669+
}
670+
};
671+
672+
// ToDo: In review ask about checking variable - unsure of what that corresponds to.
673+
false
674+
}
564675
}
565676

566677
// ⚠️ Attention : Changes to this type should be accompanied by a new test ⚠️
@@ -656,6 +767,7 @@ impl<'a> EnrollmentsEvolver<'a> {
656767
prev_experiments: &[E],
657768
next_experiments: &[Experiment],
658769
prev_enrollments: &[ExperimentEnrollment],
770+
#[cfg(feature = "stateful")] gecko_pref_store: &Option<Arc<GeckoPrefStore>>,
659771
) -> Result<(Vec<ExperimentEnrollment>, Vec<EnrollmentChangeEvent>)>
660772
where
661773
E: ExperimentMetadata + Clone,
@@ -677,6 +789,8 @@ impl<'a> EnrollmentsEvolver<'a> {
677789
&prev_rollouts,
678790
&next_rollouts,
679791
&ro_enrollments,
792+
#[cfg(feature = "stateful")]
793+
gecko_pref_store,
680794
)?;
681795

682796
enrollments.extend(next_ro_enrollments);
@@ -701,6 +815,8 @@ impl<'a> EnrollmentsEvolver<'a> {
701815
&prev_experiments,
702816
&next_experiments,
703817
&prev_enrollments,
818+
#[cfg(feature = "stateful")]
819+
gecko_pref_store,
704820
)?;
705821

706822
enrollments.extend(next_exp_enrollments);
@@ -717,6 +833,7 @@ impl<'a> EnrollmentsEvolver<'a> {
717833
prev_experiments: &[E],
718834
next_experiments: &[Experiment],
719835
prev_enrollments: &[ExperimentEnrollment],
836+
#[cfg(feature = "stateful")] gecko_pref_store: &Option<Arc<GeckoPrefStore>>,
720837
) -> Result<(Vec<ExperimentEnrollment>, Vec<EnrollmentChangeEvent>)>
721838
where
722839
E: ExperimentMetadata + Clone,
@@ -756,6 +873,8 @@ impl<'a> EnrollmentsEvolver<'a> {
756873
next_experiments_map.get(slug).copied(),
757874
Some(prev_enrollment),
758875
&mut enrollment_events,
876+
#[cfg(feature = "stateful")]
877+
gecko_pref_store,
759878
) {
760879
Ok(enrollment) => enrollment,
761880
Err(e) => {
@@ -857,6 +976,8 @@ impl<'a> EnrollmentsEvolver<'a> {
857976
Some(next_experiment),
858977
prev_enrollment,
859978
&mut enrollment_events,
979+
#[cfg(feature = "stateful")]
980+
gecko_pref_store,
860981
) {
861982
Ok(enrollment) => enrollment,
862983
Err(e) => {
@@ -950,6 +1071,7 @@ impl<'a> EnrollmentsEvolver<'a> {
9501071
next_experiment: Option<&Experiment>,
9511072
prev_enrollment: Option<&ExperimentEnrollment>,
9521073
out_enrollment_events: &mut Vec<EnrollmentChangeEvent>, // out param containing the events we'd like to emit to glean.
1074+
#[cfg(feature = "stateful")] gecko_pref_store: &Option<Arc<GeckoPrefStore>>,
9531075
) -> Result<Option<ExperimentEnrollment>>
9541076
where
9551077
E: ExperimentMetadata + Clone,
@@ -978,16 +1100,20 @@ impl<'a> EnrollmentsEvolver<'a> {
9781100
out_enrollment_events,
9791101
)?),
9801102
// Experiment deleted remotely.
981-
(Some(_), None, Some(enrollment)) => {
982-
enrollment.on_experiment_ended(out_enrollment_events)
983-
}
1103+
(Some(_), None, Some(enrollment)) => enrollment.on_experiment_ended(
1104+
#[cfg(feature = "stateful")]
1105+
gecko_pref_store,
1106+
out_enrollment_events,
1107+
),
9841108
// Known experiment.
9851109
(Some(_), Some(experiment), Some(enrollment)) => {
9861110
Some(enrollment.on_experiment_updated(
9871111
is_user_participating,
9881112
self.available_randomization_units,
9891113
experiment,
9901114
&targeting_helper,
1115+
#[cfg(feature = "stateful")]
1116+
gecko_pref_store,
9911117
out_enrollment_events,
9921118
)?)
9931119
}

components/nimbus/src/nimbus.udl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ callback interface GeckoPrefHandler {
130130
record<string, record<string, GeckoPrefState>> get_prefs_with_state();
131131

132132
void set_gecko_prefs_state(sequence<GeckoPrefState> new_prefs_state);
133+
134+
void set_gecko_prefs_original_values(sequence<OriginalGeckoPref> original_gecko_prefs);
135+
133136
};
134137

135138
dictionary GeckoPref {

components/nimbus/src/stateful/enrollment.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
use std::sync::Arc;
2+
13
/* This Source Code Form is subject to the terms of the Mozilla Public
24
* License, v. 2.0. If a copy of the MPL was not distributed with this
35
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
46
use crate::enrollment::Participation;
7+
use crate::stateful::gecko_prefs::GeckoPrefStore;
58
use crate::stateful::persistence::{
69
DB_KEY_EXPERIMENT_PARTICIPATION, DB_KEY_ROLLOUT_PARTICIPATION,
710
DEFAULT_EXPERIMENT_PARTICIPATION, DEFAULT_ROLLOUT_PARTICIPATION,
@@ -27,6 +30,7 @@ impl EnrollmentsEvolver<'_> {
2730
db: &Database,
2831
writer: &mut Writer,
2932
next_experiments: &[Experiment],
33+
gecko_pref_store: &Option<Arc<GeckoPrefStore>>,
3034
) -> Result<Vec<EnrollmentChangeEvent>> {
3135
// Get separate participation states from the db
3236
let is_participating_in_experiments = get_experiment_participation(db, writer)?;
@@ -47,6 +51,7 @@ impl EnrollmentsEvolver<'_> {
4751
&prev_experiments,
4852
next_experiments,
4953
&prev_enrollments,
54+
gecko_pref_store,
5055
)?;
5156
let next_enrollments = map_enrollments(&next_enrollments);
5257
// Write the changes to the Database.
@@ -134,13 +139,14 @@ pub fn opt_out(
134139
db: &Database,
135140
writer: &mut Writer,
136141
experiment_slug: &str,
142+
gecko_prefs: &Option<Arc<GeckoPrefStore>>,
137143
) -> Result<Vec<EnrollmentChangeEvent>> {
138144
let mut events = vec![];
139145
let enr_store = db.get_store(StoreId::Enrollments);
140146
if let Ok(Some(existing_enrollment)) =
141147
enr_store.get::<ExperimentEnrollment, Writer>(writer, experiment_slug)
142148
{
143-
let updated_enrollment = &existing_enrollment.on_explicit_opt_out(&mut events);
149+
let updated_enrollment = &existing_enrollment.on_explicit_opt_out(&mut events, gecko_prefs);
144150
enr_store.put(writer, experiment_slug, updated_enrollment)?;
145151
} else {
146152
events.push(EnrollmentChangeEvent {

components/nimbus/src/stateful/gecko_prefs.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ pub trait GeckoPrefHandler: Send + Sync {
131131

132132
/// Used to set the state for each pref based on enrollments
133133
fn set_gecko_prefs_state(&self, new_prefs_state: Vec<GeckoPrefState>);
134+
135+
/// Used to set back to the original state for each pref based on the original gecko value
136+
fn set_gecko_prefs_original_values(&self, original_gecko_prefs: Vec<OriginalGeckoPref>);
134137
}
135138

136139
#[derive(Default)]

0 commit comments

Comments
 (0)