Skip to content

Commit a176082

Browse files
authored
Revert "Revert cf4f4a8 (#6954)" (#6957)
This reverts commit 0cbf0dc.
1 parent a882e39 commit a176082

File tree

19 files changed

+649
-152
lines changed

19 files changed

+649
-152
lines changed

components/nimbus/android/src/main/java/org/mozilla/experiments/nimbus/Nimbus.kt

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,30 @@ open class Nimbus(
141141

142142
private val nimbusClient: NimbusClientInterface
143143

144+
override var experimentParticipation: Boolean
145+
get() = nimbusClient.getExperimentParticipation()
146+
set(active) {
147+
dbScope.launch {
148+
nimbusClient.setExperimentParticipation(active)
149+
applyPendingExperimentsOnThisThread()
150+
}
151+
}
152+
153+
override var rolloutParticipation: Boolean
154+
get() = nimbusClient.getRolloutParticipation()
155+
set(active) {
156+
dbScope.launch {
157+
nimbusClient.setRolloutParticipation(active)
158+
applyPendingExperimentsOnThisThread()
159+
}
160+
}
161+
144162
override var globalUserParticipation: Boolean
145163
get() = nimbusClient.getGlobalUserParticipation()
146164
set(active) {
147165
dbScope.launch {
148-
setGlobalUserParticipationOnThisThread(active)
166+
setExperimentParticipationOnThisThread(active)
167+
setRolloutParticipationOnThisThread(active)
149168
}
150169
}
151170

@@ -400,13 +419,36 @@ open class Nimbus(
400419

401420
@WorkerThread
402421
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
403-
internal fun setGlobalUserParticipationOnThisThread(active: Boolean) = withCatchAll("setGlobalUserParticipation") {
404-
val enrolmentChanges = nimbusClient.setGlobalUserParticipation(active)
405-
if (enrolmentChanges.isNotEmpty()) {
406-
recordExperimentTelemetryEvents(enrolmentChanges)
407-
postEnrolmentCalculation()
422+
internal fun setExperimentParticipationOnThisThread(active: Boolean) =
423+
withCatchAll("setExperimentParticipation") {
424+
val enrolmentChanges = nimbusClient.setExperimentParticipation(active)
425+
if (enrolmentChanges.isNotEmpty()) {
426+
recordExperimentTelemetryEvents(enrolmentChanges)
427+
postEnrolmentCalculation()
428+
}
429+
}
430+
431+
@WorkerThread
432+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
433+
internal fun setRolloutParticipationOnThisThread(active: Boolean) =
434+
withCatchAll("setRolloutParticipation") {
435+
val enrolmentChanges = nimbusClient.setRolloutParticipation(active)
436+
if (enrolmentChanges.isNotEmpty()) {
437+
recordExperimentTelemetryEvents(enrolmentChanges)
438+
postEnrolmentCalculation()
439+
}
440+
}
441+
442+
@WorkerThread
443+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
444+
internal fun setGlobalUserParticipationOnThisThread(active: Boolean) =
445+
withCatchAll("setGlobalUserParticipation") {
446+
val enrolmentChanges = nimbusClient.setGlobalUserParticipation(active)
447+
if (enrolmentChanges.isNotEmpty()) {
448+
recordExperimentTelemetryEvents(enrolmentChanges)
449+
postEnrolmentCalculation()
450+
}
408451
}
409-
}
410452

411453
override fun optOut(experimentId: String) {
412454
dbScope.launch {

components/nimbus/android/src/main/java/org/mozilla/experiments/nimbus/NimbusInterface.kt

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,27 @@ interface NimbusInterface : FeaturesInterface, NimbusMessagingInterface, NimbusE
198198
*/
199199
fun resetTelemetryIdentifiers() = Unit
200200

201-
/**
202-
* Control the opt out for all experiments at once. This is likely a user action.
203-
*/
204201
var globalUserParticipation: Boolean
205-
get() = false
202+
get() = experimentParticipation && rolloutParticipation
203+
set(value) {
204+
experimentParticipation = value
205+
rolloutParticipation = value
206+
}
207+
208+
/**
209+
* Control the opt out for experiments. This is likely a user action.
210+
* When set to false, the user will be opted out of all experiments but not rollouts.
211+
*/
212+
var experimentParticipation: Boolean
213+
get() = true
214+
set(_) = Unit
215+
216+
/**
217+
* Control the opt out for rollouts. This is likely a user action.
218+
* When set to false, the user will be opted out of all rollouts but not experiments.
219+
*/
220+
var rolloutParticipation: Boolean
221+
get() = true
206222
set(_) = Unit
207223

208224
override val events: NimbusEventStore

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -450,8 +450,8 @@ class NimbusTests {
450450
NimbusEvents.disqualification.testGetValue(),
451451
)
452452

453-
// Opt out of all experiments
454-
nimbus.setGlobalUserParticipationOnThisThread(false)
453+
// Opt out of experiments but keep rollouts
454+
nimbus.setExperimentParticipationOnThisThread(false)
455455

456456
// Use the Glean test API to check that the valid event is present
457457
assertNotNull("Event must have a value", NimbusEvents.disqualification.testGetValue())

components/nimbus/src/enrollment.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,21 @@ impl Display for NotEnrolledReason {
7575
}
7676
}
7777

78+
#[derive(Serialize, Deserialize, Debug, Clone)]
79+
pub struct Participation {
80+
pub in_experiments: bool,
81+
pub in_rollouts: bool,
82+
}
83+
84+
impl Default for Participation {
85+
fn default() -> Self {
86+
Self {
87+
in_experiments: true,
88+
in_rollouts: true,
89+
}
90+
}
91+
}
92+
7893
// These are types we use internally for managing disqualifications.
7994

8095
// ⚠️ Attention : Changes to this type should be accompanied by a new test ⚠️
@@ -583,7 +598,7 @@ impl<'a> EnrollmentsEvolver<'a> {
583598

584599
pub(crate) fn evolve_enrollments<E>(
585600
&mut self,
586-
is_user_participating: bool,
601+
participation: Participation,
587602
prev_experiments: &[E],
588603
next_experiments: &[Experiment],
589604
prev_enrollments: &[ExperimentEnrollment],
@@ -604,7 +619,7 @@ impl<'a> EnrollmentsEvolver<'a> {
604619
let next_rollouts = filter_experiments(next_experiments, ExperimentMetadata::is_rollout);
605620

606621
let (next_ro_enrollments, ro_events) = self.evolve_enrollment_recipes(
607-
is_user_participating,
622+
participation.in_rollouts,
608623
&prev_rollouts,
609624
&next_rollouts,
610625
&ro_enrollments,
@@ -628,7 +643,7 @@ impl<'a> EnrollmentsEvolver<'a> {
628643
.collect();
629644

630645
let (next_exp_enrollments, exp_events) = self.evolve_enrollment_recipes(
631-
is_user_participating,
646+
participation.in_experiments,
632647
&prev_experiments,
633648
&next_experiments,
634649
&prev_enrollments,

components/nimbus/src/nimbus.udl

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,26 @@ interface NimbusClient {
239239
[Throws=NimbusError]
240240
sequence<AvailableExperiment> get_available_experiments();
241241

242+
/// Getter and setter for user's participation in experiments only.
243+
/// Possible values are:
244+
/// * `true`: the user will enroll in experiments as usual.
245+
/// * `false`: the user will not enroll in new experiments, and opt out of all existing ones.
246+
[Throws=NimbusError]
247+
boolean get_experiment_participation();
248+
249+
[Throws=NimbusError]
250+
sequence<EnrollmentChangeEvent> set_experiment_participation(boolean opt_in);
251+
252+
/// Getter and setter for user's participation in rollouts.
253+
/// Possible values are:
254+
/// * `true`: the user will enroll in rollouts as usual.
255+
/// * `false`: the user will not enroll in new rollouts, and opt out of all existing ones.
256+
[Throws=NimbusError]
257+
boolean get_rollout_participation();
258+
259+
[Throws=NimbusError]
260+
sequence<EnrollmentChangeEvent> set_rollout_participation(boolean opt_in);
261+
242262
/// Getter and setter for global user participation (applies to both experiments and rollouts).
243263
/// For simplicity, the getter returns the experiment participation value.
244264
[Throws=NimbusError]
@@ -256,7 +276,7 @@ interface NimbusClient {
256276
/// Toggles the enablement of the fetch. If `false`, then calling `fetch_experiments`
257277
/// returns immediately, having not done any fetching from remote settings.
258278
/// This is only useful for QA, and should not be used in production: use
259-
/// `set_global_user_participation` instead.
279+
/// `set_experiment_participation` or `set_rollout_participation` instead.
260280
[Throws=NimbusError]
261281
void set_fetch_enabled(boolean flag);
262282

components/nimbus/src/stateful/enrollment.rs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
/* This Source Code Form is subject to the terms of the Mozilla Public
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/. */
4+
use crate::enrollment::Participation;
5+
use crate::stateful::persistence::{
6+
DB_KEY_EXPERIMENT_PARTICIPATION, DB_KEY_ROLLOUT_PARTICIPATION,
7+
DEFAULT_EXPERIMENT_PARTICIPATION, DEFAULT_ROLLOUT_PARTICIPATION,
8+
};
49
use crate::{
510
enrollment::{
611
map_enrollments, EnrollmentChangeEvent, EnrollmentChangeEventType, EnrollmentsEvolver,
@@ -14,9 +19,6 @@ use crate::{
1419
EnrolledExperiment, EnrollmentStatus, Experiment,
1520
};
1621

17-
const DB_KEY_GLOBAL_USER_PARTICIPATION: &str = "user-opt-in";
18-
const DEFAULT_GLOBAL_USER_PARTICIPATION: bool = true;
19-
2022
impl EnrollmentsEvolver<'_> {
2123
/// Convenient wrapper around `evolve_enrollments` that fetches the current state of experiments,
2224
/// enrollments and user participation from the database.
@@ -26,15 +28,22 @@ impl EnrollmentsEvolver<'_> {
2628
writer: &mut Writer,
2729
next_experiments: &[Experiment],
2830
) -> Result<Vec<EnrollmentChangeEvent>> {
29-
// Get the state from the db.
30-
let is_user_participating = get_global_user_participation(db, writer)?;
31+
// Get separate participation states from the db
32+
let is_participating_in_experiments = get_experiment_participation(db, writer)?;
33+
let is_participating_in_rollouts = get_rollout_participation(db, writer)?;
34+
35+
let participation = Participation {
36+
in_experiments: is_participating_in_experiments,
37+
in_rollouts: is_participating_in_rollouts,
38+
};
39+
3140
let experiments_store = db.get_store(StoreId::Experiments);
3241
let enrollments_store = db.get_store(StoreId::Enrollments);
3342
let prev_experiments: Vec<Experiment> = experiments_store.collect_all(writer)?;
3443
let prev_enrollments: Vec<ExperimentEnrollment> = enrollments_store.collect_all(writer)?;
3544
// Calculate the changes.
3645
let (next_enrollments, enrollments_change_events) = self.evolve_enrollments(
37-
is_user_participating,
46+
participation,
3847
&prev_experiments,
3948
next_experiments,
4049
&prev_enrollments,
@@ -171,26 +180,41 @@ pub fn unenroll_for_pref(
171180
Ok(events)
172181
}
173182

174-
pub fn get_global_user_participation<'r>(
183+
pub fn get_experiment_participation<'r>(
175184
db: &Database,
176185
reader: &'r impl Readable<'r>,
177186
) -> Result<bool> {
178187
let store = db.get_store(StoreId::Meta);
179-
let opted_in = store.get::<bool, _>(reader, DB_KEY_GLOBAL_USER_PARTICIPATION)?;
188+
let opted_in = store.get::<bool, _>(reader, DB_KEY_EXPERIMENT_PARTICIPATION)?;
189+
if let Some(opted_in) = opted_in {
190+
Ok(opted_in)
191+
} else {
192+
Ok(DEFAULT_EXPERIMENT_PARTICIPATION)
193+
}
194+
}
195+
196+
pub fn get_rollout_participation<'r>(db: &Database, reader: &'r impl Readable<'r>) -> Result<bool> {
197+
let store = db.get_store(StoreId::Meta);
198+
let opted_in = store.get::<bool, _>(reader, DB_KEY_ROLLOUT_PARTICIPATION)?;
180199
if let Some(opted_in) = opted_in {
181200
Ok(opted_in)
182201
} else {
183-
Ok(DEFAULT_GLOBAL_USER_PARTICIPATION)
202+
Ok(DEFAULT_ROLLOUT_PARTICIPATION)
184203
}
185204
}
186205

187-
pub fn set_global_user_participation(
206+
pub fn set_experiment_participation(
188207
db: &Database,
189208
writer: &mut Writer,
190209
opt_in: bool,
191210
) -> Result<()> {
192211
let store = db.get_store(StoreId::Meta);
193-
store.put(writer, DB_KEY_GLOBAL_USER_PARTICIPATION, &opt_in)
212+
store.put(writer, DB_KEY_EXPERIMENT_PARTICIPATION, &opt_in)
213+
}
214+
215+
pub fn set_rollout_participation(db: &Database, writer: &mut Writer, opt_in: bool) -> Result<()> {
216+
let store = db.get_store(StoreId::Meta);
217+
store.put(writer, DB_KEY_ROLLOUT_PARTICIPATION, &opt_in)
194218
}
195219

196220
/// Reset unique identifiers in response to application-level telemetry reset.

components/nimbus/src/stateful/nimbus_client.rs

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ use crate::{
2626
client::{create_client, SettingsClient},
2727
dbcache::DatabaseCache,
2828
enrollment::{
29-
get_global_user_participation, opt_in_with_branch, opt_out,
30-
reset_telemetry_identifiers, set_global_user_participation, unenroll_for_pref,
29+
get_experiment_participation, get_rollout_participation, opt_in_with_branch, opt_out,
30+
reset_telemetry_identifiers, set_experiment_participation, set_rollout_participation,
31+
unenroll_for_pref,
3132
},
3233
gecko_prefs::{
3334
GeckoPref, GeckoPrefHandler, GeckoPrefState, GeckoPrefStore, PrefBranch,
@@ -252,30 +253,73 @@ impl NimbusClient {
252253
.ok_or(NimbusError::NoSuchExperiment(slug))
253254
}
254255

255-
pub fn get_global_user_participation(&self) -> Result<bool> {
256+
pub fn get_experiment_participation(&self) -> Result<bool> {
256257
let db = self.db()?;
257258
let reader = db.read()?;
258-
get_global_user_participation(db, &reader)
259+
get_experiment_participation(db, &reader)
259260
}
260261

261-
pub fn set_global_user_participation(
262+
pub fn get_rollout_participation(&self) -> Result<bool> {
263+
let db = self.db()?;
264+
let reader = db.read()?;
265+
get_rollout_participation(db, &reader)
266+
}
267+
268+
pub fn set_experiment_participation(
262269
&self,
263270
user_participating: bool,
264271
) -> Result<Vec<EnrollmentChangeEvent>> {
265272
let db = self.db()?;
266273
let mut writer = db.write()?;
267274
let mut state = self.mutable_state.lock().unwrap();
268-
set_global_user_participation(db, &mut writer, user_participating)?;
275+
set_experiment_participation(db, &mut writer, user_participating)?;
269276

270277
let existing_experiments: Vec<Experiment> =
271278
db.get_store(StoreId::Experiments).collect_all(&writer)?;
272-
// We pass the existing experiments as "updated experiments"
273-
// to the evolver.
274279
let events = self.evolve_experiments(db, &mut writer, &mut state, &existing_experiments)?;
275280
self.end_initialize(db, writer, &mut state)?;
276281
Ok(events)
277282
}
278283

284+
pub fn set_rollout_participation(
285+
&self,
286+
user_participating: bool,
287+
) -> Result<Vec<EnrollmentChangeEvent>> {
288+
let db = self.db()?;
289+
let mut writer = db.write()?;
290+
let mut state = self.mutable_state.lock().unwrap();
291+
set_rollout_participation(db, &mut writer, user_participating)?;
292+
293+
let existing_experiments: Vec<Experiment> =
294+
db.get_store(StoreId::Experiments).collect_all(&writer)?;
295+
let events = self.evolve_experiments(db, &mut writer, &mut state, &existing_experiments)?;
296+
self.end_initialize(db, writer, &mut state)?;
297+
Ok(events)
298+
}
299+
300+
// DEPRECATED: Will be removed once Firefox is using the
301+
// new get_experiment_participation and get_rollout_participation
302+
pub fn get_global_user_participation(&self) -> Result<bool> {
303+
// For simplicity, just return the experiment participation value
304+
self.get_experiment_participation()
305+
}
306+
307+
// DEPRECATED: Will be removed once Firefox is using the
308+
// new set_experiment_participation and set_rollout_participation
309+
pub fn set_global_user_participation(
310+
&self,
311+
user_participating: bool,
312+
) -> Result<Vec<EnrollmentChangeEvent>> {
313+
// Call both set_experiment_participation and set_rollout_participation with the same value
314+
let experiment_events = self.set_experiment_participation(user_participating)?;
315+
let rollout_events = self.set_rollout_participation(user_participating)?;
316+
317+
// Combine the events from both calls
318+
let mut all_events = experiment_events;
319+
all_events.extend(rollout_events);
320+
Ok(all_events)
321+
}
322+
279323
pub fn get_active_experiments(&self) -> Result<Vec<EnrolledExperiment>> {
280324
self.database_cache.get_active_experiments()
281325
}

0 commit comments

Comments
 (0)