First use of Nimbus FML plugin (#23400)
* Consume Nimbus FML plugin * Convert Homescreen to use FML * Convert nimbusValidation to use FML * Convert legacy experiments to use the feature API and FML Remove dead helper code and documentation * Fixup failing test Co-authored-by: Grisha Kruglov <gkruglov@mozilla.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>upstream-sync
parent
e92fe26df7
commit
82a6f8cae4
@ -0,0 +1,59 @@
|
||||
{
|
||||
"search-term-groups": {
|
||||
"description": "A feature allowing the grouping of URLs around the search term that it came from.",
|
||||
"hasExposure": true,
|
||||
"exposureDescription": "",
|
||||
"variables": {
|
||||
"enabled": {
|
||||
"description": "If true, the feature shows up on the homescreen and on the new tab screen.",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"default-browser-message": {
|
||||
"description": "A small feature allowing experiments on the placement of a default browser message.",
|
||||
"hasExposure": true,
|
||||
"exposureDescription": "",
|
||||
"variables": {
|
||||
"message-location": {
|
||||
"description": "Where is the message to be put.",
|
||||
"enum": [
|
||||
"homescreen-banner",
|
||||
"settings",
|
||||
"app-menu-item"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nimbus-validation": {
|
||||
"description": "A feature that does not correspond to an application feature suitable for showing that Nimbus is working. This should never be used in production.",
|
||||
"hasExposure": true,
|
||||
"exposureDescription": "",
|
||||
"variables": {
|
||||
"settings-icon": {
|
||||
"description": "The drawable displayed in the app menu for Settings",
|
||||
"type": "string"
|
||||
},
|
||||
"settings-punctuation": {
|
||||
"description": "The emoji displayed in the Settings screen title.",
|
||||
"type": "string"
|
||||
},
|
||||
"settings-title": {
|
||||
"description": "The title of displayed in the Settings screen and app menu.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"homescreen": {
|
||||
"description": "The homescreen that the user goes to when they press home or new tab.",
|
||||
"hasExposure": true,
|
||||
"exposureDescription": "",
|
||||
"variables": {
|
||||
"sections-enabled": {
|
||||
"description": "This property provides a lookup table of whether or not the given section should be enabled. If the section is enabled, it should be toggleable in the settings screen, and on by default.",
|
||||
"type": "json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.components.metrics
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import mozilla.components.lib.dataprotect.SecurePrefsReliabilityExperiment
|
||||
import mozilla.components.service.nimbus.NimbusApi
|
||||
import org.mozilla.fenix.experiments.ExperimentBranch
|
||||
import org.mozilla.fenix.experiments.FeatureId
|
||||
import org.mozilla.fenix.ext.withExperiment
|
||||
|
||||
/**
|
||||
* Allows starting a quick test of ACs SecureAbove22Preferences that will emit Facts
|
||||
* for the basic operations and allow us to log them for later evaluation of APIs stability.
|
||||
*/
|
||||
class SecurePrefsTelemetry(
|
||||
private val appContext: Context,
|
||||
private val experiments: NimbusApi
|
||||
) {
|
||||
fun startTests() {
|
||||
// The Android Keystore is used to secure the shared prefs only on API 23+
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// These tests should run only if the experiment is live
|
||||
experiments.withExperiment(FeatureId.ANDROID_KEYSTORE) { experimentBranch ->
|
||||
// .. and this device is not in the control group.
|
||||
if (experimentBranch == ExperimentBranch.TREATMENT) {
|
||||
SecurePrefsReliabilityExperiment(appContext)()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.experiments
|
||||
|
||||
/**
|
||||
* Enums to identify features in the app. These will likely grow and shrink depending
|
||||
* on the experiments we want to perform.
|
||||
*
|
||||
* @property jsonName the kebab-case version of the feature id as represented in the Nimbus
|
||||
* experiment JSON.
|
||||
*/
|
||||
enum class FeatureId(val jsonName: String) {
|
||||
NIMBUS_VALIDATION("nimbus-validation"),
|
||||
ANDROID_KEYSTORE("fenix-android-keystore"),
|
||||
DEFAULT_BROWSER("fenix-default-browser"),
|
||||
HOME_PAGE("homescreen")
|
||||
}
|
||||
|
||||
/**
|
||||
* Experiment branches are becoming less interesting, though we collect some well
|
||||
* defined ones here.
|
||||
*/
|
||||
class ExperimentBranch {
|
||||
companion object {
|
||||
const val TREATMENT = "treatment"
|
||||
const val CONTROL = "control"
|
||||
const val A1 = "a1"
|
||||
const val A2 = "a2"
|
||||
const val DEFAULT_BROWSER_TOOLBAR_MENU = "default_browser_toolbar_menu"
|
||||
const val DEFAULT_BROWSER_NEW_TAB_BANNER = "default_browser_newtab_banner"
|
||||
const val DEFAULT_BROWSER_SETTINGS_MENU = "default_browser_settings_menu"
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.experiments
|
||||
|
||||
import android.content.Context
|
||||
import org.mozilla.experiments.nimbus.mapKeysAsEnums
|
||||
import org.mozilla.fenix.FeatureFlags
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.getVariables
|
||||
|
||||
/**
|
||||
* Component for exposing nimbus Feature Variables.
|
||||
* For more information see https://experimenter.info/feature-variables-and-me
|
||||
*
|
||||
* @param context - A [Context] for accessing the feature variables from nimbus.
|
||||
*/
|
||||
class NimbusFeatures(private val context: Context) {
|
||||
|
||||
val homeScreen: HomeScreenFeatures by lazy {
|
||||
HomeScreenFeatures(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that indicates which features should be active on the home screen.
|
||||
*/
|
||||
class HomeScreenFeatures(private val context: Context) {
|
||||
/**
|
||||
* `FeatureId.HOME_PAGE` feature; the complete JSON, is shown here:
|
||||
*
|
||||
* ```json
|
||||
* {
|
||||
* "sections-enabled": {
|
||||
* "topSites": true,
|
||||
* "recentlySaved": false,
|
||||
* "jumpBackIn": false,
|
||||
* "pocket": false,
|
||||
* "recentExplorations": false
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
/**
|
||||
* This enum accompanies the `FeatureId.HOME_PAGE` feature.
|
||||
*
|
||||
* These names here should match the names of entries in the JSON.
|
||||
*/
|
||||
@Suppress("EnumNaming")
|
||||
private enum class HomeScreenSection(val default: Boolean) {
|
||||
topSites(true),
|
||||
recentlySaved(true),
|
||||
jumpBackIn(true),
|
||||
pocket(true),
|
||||
recentExplorations(true);
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* CreateS a map with the corresponding default values for each sections.
|
||||
*/
|
||||
fun toMap(context: Context): Map<HomeScreenSection, Boolean> {
|
||||
return values().associate { section ->
|
||||
val value = if (section == pocket) {
|
||||
FeatureFlags.isPocketRecommendationsFeatureEnabled(context)
|
||||
} else {
|
||||
section.default
|
||||
}
|
||||
section to value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val homeScreenFeatures: Map<HomeScreenSection, Boolean> by lazy {
|
||||
val experiments = context.components.analytics.experiments
|
||||
val variables = experiments.getVariables(FeatureId.HOME_PAGE, false)
|
||||
val sections: Map<HomeScreenSection, Boolean> =
|
||||
variables.getBoolMap("sections-enabled")?.mapKeysAsEnums()
|
||||
?: HomeScreenSection.toMap(context)
|
||||
sections
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the recently tabs feature is active.
|
||||
*/
|
||||
fun isRecentlyTabsActive(): Boolean {
|
||||
return homeScreenFeatures[HomeScreenSection.jumpBackIn] == true
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the recently saved feature is active.
|
||||
*/
|
||||
fun isRecentlySavedActive(): Boolean {
|
||||
return homeScreenFeatures[HomeScreenSection.recentlySaved] == true
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the recently exploration feature is active.
|
||||
*/
|
||||
fun isRecentExplorationsActive(): Boolean {
|
||||
return homeScreenFeatures[HomeScreenSection.recentExplorations] == true
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the pocket recommendations feature is active.
|
||||
*/
|
||||
fun isPocketRecommendationsActive(): Boolean {
|
||||
return homeScreenFeatures[HomeScreenSection.pocket] == true
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the top sites feature is active.
|
||||
*/
|
||||
fun isTopSitesActive(): Boolean {
|
||||
return homeScreenFeatures[HomeScreenSection.topSites] == true
|
||||
}
|
||||
}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.ext
|
||||
|
||||
import mozilla.components.service.nimbus.NimbusApi
|
||||
import mozilla.components.support.base.log.logger.Logger
|
||||
import org.mozilla.experiments.nimbus.Variables
|
||||
import org.mozilla.fenix.experiments.FeatureId
|
||||
|
||||
/**
|
||||
* Gets the branch name of an experiment acting on the feature given `featureId`, and transforms it
|
||||
* with given closure.
|
||||
*
|
||||
* You are probably looking for `withVariables`.
|
||||
*
|
||||
* If we're enrolled in an experiment, the transform is passed the branch id/slug as a `String`.
|
||||
*
|
||||
* If we're not enrolled in the experiment, or the experiment is not valid then the transform
|
||||
* is passed a `null`.
|
||||
*/
|
||||
fun <T> NimbusApi.withExperiment(featureId: FeatureId, transform: (String?) -> T): T {
|
||||
return transform(withExperiment(featureId))
|
||||
}
|
||||
|
||||
/**
|
||||
* The synonym for [getExperimentBranch] to complement [withExperiment(String, (String?) -> T))].
|
||||
*
|
||||
* Short-hand for ` org.mozilla.experiments.nimbus.NimbusApi.getExperimentBranch`.
|
||||
*/
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
fun NimbusApi.withExperiment(featureId: FeatureId) =
|
||||
try {
|
||||
getExperimentBranch(featureId.jsonName)
|
||||
} catch (e: Throwable) {
|
||||
Logger.error("Failed to getExperimentBranch(${featureId.jsonName})", e)
|
||||
null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the variables needed to configure the feature given by `featureId`.
|
||||
*
|
||||
* @param featureId The feature id that identifies the feature under experiment.
|
||||
*
|
||||
* @param sendExposureEvent Passing `true` to this parameter will record the exposure event
|
||||
* automatically if the client is enrolled in an experiment for the given [featureId].
|
||||
* Passing `false` here indicates that the application will manually record the exposure
|
||||
* event by calling the `sendExposureEvent` function at the time of the exposure to the
|
||||
* feature.
|
||||
*
|
||||
* See [sendExposureEvent] for more information on manually recording the event.
|
||||
*
|
||||
* @return a [Variables] object used to configure the feature.
|
||||
*/
|
||||
fun NimbusApi.getVariables(featureId: FeatureId, sendExposureEvent: Boolean = true) =
|
||||
getVariables(featureId.jsonName, sendExposureEvent)
|
||||
|
||||
/**
|
||||
* A synonym for `getVariables(featureId, sendExposureEvent)`.
|
||||
*
|
||||
* This exists as a complement to the `withVariable(featureId, sendExposureEvent, transform)` method.
|
||||
*
|
||||
* @param featureId the id of the feature as it appears in `Experimenter`
|
||||
* @param sendExposureEvent by default `true`. This logs an event that the user was exposed to an experiment
|
||||
* involving this feature.
|
||||
* @return a `Variables` object providing typed accessors to a remotely configured JSON object.
|
||||
*/
|
||||
fun NimbusApi.withVariables(featureId: FeatureId, sendExposureEvent: Boolean = true) =
|
||||
getVariables(featureId, sendExposureEvent)
|
||||
|
||||
/**
|
||||
* Get a `Variables` object for this feature and use that to configure the feature itself or a
|
||||
* more type safe configuration object.
|
||||
*
|
||||
* @param featureId the id of the feature as it appears in `Experimenter`
|
||||
* @param sendExposureEvent by default `true`. This logs an event that the user was exposed to an experiment
|
||||
* involving this feature.
|
||||
*/
|
||||
fun <T> NimbusApi.withVariables(featureId: FeatureId, sendExposureEvent: Boolean = true, transform: (Variables) -> T) =
|
||||
transform(getVariables(featureId, sendExposureEvent))
|
||||
|
||||
/**
|
||||
* Records the `exposure` event in telemetry.
|
||||
*
|
||||
* This is a manual function to accomplish the same purpose as passing `true` as the
|
||||
* `sendExposureEvent` property of the `getVariables` function. It is intended to be used
|
||||
* when requesting feature variables must occur at a different time than the actual user's
|
||||
* exposure to the feature within the app.
|
||||
*
|
||||
* - Examples:
|
||||
* - If the `Variables` are needed at a different time than when the exposure to the feature
|
||||
* actually happens, such as constructing a menu happening at a different time than the
|
||||
* user seeing the menu.
|
||||
* - If `getVariables` is required to be called multiple times for the same feature and it is
|
||||
* desired to only record the exposure once, such as if `getVariables` were called
|
||||
* with every keystroke.
|
||||
*
|
||||
* In the case where the use of this function is required, then the `getVariables` function
|
||||
* should be called with `false` so that the exposure event is not recorded when the variables
|
||||
* are fetched.
|
||||
*
|
||||
* This function is safe to call even when there is no active experiment for the feature. The SDK
|
||||
* will ensure that an event is only recorded for active experiments.
|
||||
*
|
||||
* @param featureId string representing the id of the feature for which to record the exposure
|
||||
* event.
|
||||
*/
|
||||
fun NimbusApi.recordExposureEvent(featureId: FeatureId) =
|
||||
recordExposureEvent(featureId.jsonName)
|
@ -0,0 +1,89 @@
|
||||
---
|
||||
channels:
|
||||
- release
|
||||
- beta
|
||||
- nightly
|
||||
- developer
|
||||
features:
|
||||
homescreen:
|
||||
description: The homescreen that the user goes to when they press home or new tab.
|
||||
variables:
|
||||
sections-enabled:
|
||||
description: "This property provides a lookup table of whether or not the given section should be enabled.
|
||||
If the section is enabled, it should be toggleable in the settings screen, and on by default."
|
||||
type: Map<HomeScreenSection, Boolean>
|
||||
default:
|
||||
{
|
||||
"top-sites": true,
|
||||
"jump-back-in": true,
|
||||
"recently-saved": true,
|
||||
"recent-explorations": true,
|
||||
"pocket": true
|
||||
}
|
||||
defaults:
|
||||
- channel: nightly
|
||||
value: {
|
||||
"sections-enabled": {
|
||||
"top-sites": true,
|
||||
"jump-back-in": true,
|
||||
"recently-saved": true,
|
||||
"recent-explorations": true,
|
||||
"pocket": true
|
||||
}
|
||||
}
|
||||
nimbus-validation:
|
||||
description: "A feature that does not correspond to an application feature suitable for showing
|
||||
that Nimbus is working. This should never be used in production."
|
||||
variables:
|
||||
settings-title:
|
||||
description: The title of displayed in the Settings screen and app menu.
|
||||
type: Text
|
||||
default: browser_menu_settings
|
||||
settings-punctuation:
|
||||
description: The emoji displayed in the Settings screen title.
|
||||
type: String
|
||||
default: ""
|
||||
settings-icon:
|
||||
description: The drawable displayed in the app menu for Settings
|
||||
type: String
|
||||
default: mozac_ic_settings
|
||||
search-term-groups:
|
||||
description: A feature allowing the grouping of URLs around the search term that it came from.
|
||||
variables:
|
||||
enabled:
|
||||
description: If true, the feature shows up on the homescreen and on the new tab screen.
|
||||
type: Boolean
|
||||
default: false
|
||||
default-browser-message:
|
||||
description: A small feature allowing experiments on the placement of a default browser message.
|
||||
variables:
|
||||
message-location:
|
||||
description: Where is the message to be put.
|
||||
type: MessageSurfaceId
|
||||
default: homescreen-banner
|
||||
types:
|
||||
objects: {}
|
||||
enums:
|
||||
HomeScreenSection:
|
||||
description: The identifiers for the sections of the homescreen.
|
||||
variants:
|
||||
top-sites:
|
||||
description: The frecency and pinned sites.
|
||||
recently-saved:
|
||||
description: The sites the user has bookmarked recently.
|
||||
jump-back-in:
|
||||
description: The tabs the user was looking immediately before being interrupted.
|
||||
recent-explorations:
|
||||
description: The tab groups
|
||||
pocket:
|
||||
description: The pocket section. This should only be available in the US.
|
||||
MessageSurfaceId:
|
||||
description: The identity of a message surface, used in the default browser experiments
|
||||
variants:
|
||||
app-menu-item:
|
||||
description: An item in the default toolbar menu.
|
||||
settings:
|
||||
description: A setting in the settings screen.
|
||||
homescreen-banner:
|
||||
description: A banner in the homescreen.
|
||||
|
Loading…
Reference in New Issue