1/* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5"use strict"; 6 7const { BaseAction } = ChromeUtils.import( 8 "resource://normandy/actions/BaseAction.jsm" 9); 10ChromeUtils.defineModuleGetter( 11 this, 12 "TelemetryEnvironment", 13 "resource://gre/modules/TelemetryEnvironment.jsm" 14); 15ChromeUtils.defineModuleGetter( 16 this, 17 "PreferenceRollouts", 18 "resource://normandy/lib/PreferenceRollouts.jsm" 19); 20ChromeUtils.defineModuleGetter( 21 this, 22 "PrefUtils", 23 "resource://normandy/lib/PrefUtils.jsm" 24); 25ChromeUtils.defineModuleGetter( 26 this, 27 "ActionSchemas", 28 "resource://normandy/actions/schemas/index.js" 29); 30ChromeUtils.defineModuleGetter( 31 this, 32 "TelemetryEvents", 33 "resource://normandy/lib/TelemetryEvents.jsm" 34); 35 36var EXPORTED_SYMBOLS = ["PreferenceRollbackAction"]; 37 38class PreferenceRollbackAction extends BaseAction { 39 get schema() { 40 return ActionSchemas["preference-rollback"]; 41 } 42 43 async _run(recipe) { 44 const { rolloutSlug } = recipe.arguments; 45 const rollout = await PreferenceRollouts.get(rolloutSlug); 46 47 if (PreferenceRollouts.GRADUATION_SET.has(rolloutSlug)) { 48 // graduated rollouts can't be rolled back 49 TelemetryEvents.sendEvent( 50 "unenrollFailed", 51 "preference_rollback", 52 rolloutSlug, 53 { 54 reason: "in-graduation-set", 55 enrollmentId: 56 rollout?.enrollmentId ?? TelemetryEvents.NO_ENROLLMENT_ID_MARKER, 57 } 58 ); 59 throw new Error( 60 `Cannot rollback rollout in graduation set "${rolloutSlug}".` 61 ); 62 } 63 64 if (!rollout) { 65 this.log.debug(`Rollback ${rolloutSlug} not applicable, skipping`); 66 return; 67 } 68 69 switch (rollout.state) { 70 case PreferenceRollouts.STATE_ACTIVE: { 71 this.log.info(`Rolling back ${rolloutSlug}`); 72 rollout.state = PreferenceRollouts.STATE_ROLLED_BACK; 73 for (const { preferenceName, previousValue } of rollout.preferences) { 74 PrefUtils.setPref(preferenceName, previousValue, { 75 branch: "default", 76 }); 77 } 78 await PreferenceRollouts.update(rollout); 79 TelemetryEvents.sendEvent( 80 "unenroll", 81 "preference_rollback", 82 rolloutSlug, 83 { 84 reason: "rollback", 85 enrollmentId: 86 rollout.enrollmentId || TelemetryEvents.NO_ENROLLMENT_ID_MARKER, 87 } 88 ); 89 TelemetryEnvironment.setExperimentInactive(rolloutSlug); 90 break; 91 } 92 case PreferenceRollouts.STATE_ROLLED_BACK: { 93 // The rollout has already been rolled back, so nothing to do here. 94 break; 95 } 96 case PreferenceRollouts.STATE_GRADUATED: { 97 // graduated rollouts can't be rolled back 98 TelemetryEvents.sendEvent( 99 "unenrollFailed", 100 "preference_rollback", 101 rolloutSlug, 102 { 103 reason: "graduated", 104 enrollmentId: 105 rollout.enrollmentId || TelemetryEvents.NO_ENROLLMENT_ID_MARKER, 106 } 107 ); 108 throw new Error( 109 `Cannot rollback already graduated rollout ${rolloutSlug}` 110 ); 111 } 112 default: { 113 throw new Error( 114 `Unexpected state when rolling back ${rolloutSlug}: ${rollout.state}` 115 ); 116 } 117 } 118 } 119 120 async _finalize() { 121 await PreferenceRollouts.saveStartupPrefs(); 122 } 123} 124