[fenix] Closes EXP-3078 Use Nimbus.fetchExperiments() more intentionally (https://github.com/mozilla-mobile/fenix/pull/28760)

* Add maybeFetchExperiments

* Move fetchExperiments from onCreate to after visual completeness

* Address reviewer comments / ktlint
pull/600/head
jhugman 1 year ago committed by GitHub
parent e36a2a4360
commit c5975be030

@ -60,6 +60,14 @@ mr2022:
sections-enabled:
type: json
description: This property provides a lookup table of whether or not the given section should be enabled.
nimbus-system:
description: "Configuration of the Nimbus System in Fenix.\n"
hasExposure: true
exposureDescription: ""
variables:
refresh-interval-foreground:
type: int
description: "The minimum interval in minutes between fetching experiment \nrecipes in the foreground.\n"
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

@ -76,6 +76,7 @@ import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.metrics.MetricServiceType
import org.mozilla.fenix.components.metrics.MozillaProductDetector
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.experiments.maybeFetchExperiments
import org.mozilla.fenix.ext.areNotificationsEnabledSafe
import org.mozilla.fenix.ext.containsQueryParameters
import org.mozilla.fenix.ext.getCustomGleanServerUrlIfAvailable
@ -377,6 +378,17 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
}
}
@OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
fun queueNimbusFetchInForeground() {
queue.runIfReadyOrQueue {
GlobalScope.launch(Dispatchers.IO) {
components.analytics.experiments.maybeFetchExperiments(
context = this@FenixApplication,
)
}
}
}
initQueue()
// We init these items in the visual completeness queue to avoid them initing in the critical
@ -386,6 +398,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
queueReviewPrompt()
queueRestoreLocale()
queueStorageMaintenance()
queueNimbusFetchInForeground()
}
private fun startMetricsIfEnabled() {
@ -480,7 +493,6 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
// Now viaduct (the RustHttp client) is initialized we can ask Nimbus to fetch
// experiments recipes from the server.
components.analytics.experiments.fetchExperiments()
}
}

@ -9,6 +9,7 @@ import mozilla.components.service.nimbus.NimbusApi
import mozilla.components.service.nimbus.NimbusAppInfo
import mozilla.components.service.nimbus.NimbusBuilder
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.experiments.nimbus.NimbusInterface
import org.mozilla.experiments.nimbus.internal.NimbusException
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.R
@ -16,6 +17,8 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.gleanplumb.CustomAttributeProvider
import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.nimbus.NimbusSystem
import org.mozilla.fenix.utils.Settings
/**
* The maximum amount of time the app launch will be blocked to load experiments from disk.
@ -87,3 +90,24 @@ fun NimbusException.isReportableError(): Boolean {
else -> true
}
}
/**
* Call `fetchExperiments` if the time since the last fetch is over a threshold.
*
* The threshold is given by the [NimbusSystem] feature object, defined in the
* `nimbus.fml.yaml` file.
*/
fun NimbusInterface.maybeFetchExperiments(
context: Context,
feature: NimbusSystem = FxNimbus.features.nimbusSystem.value(),
currentTimeMillis: Long = System.currentTimeMillis(),
) {
val lastFetchTimeMillis = context.settings().nimbusLastFetchTime
val minimumPeriodMinutes = feature.refreshIntervalForeground
val minimumPeriodMillis = minimumPeriodMinutes * Settings.ONE_MINUTE_MS
if (currentTimeMillis - lastFetchTimeMillis >= minimumPeriodMillis) {
context.settings().nimbusLastFetchTime = currentTimeMillis
fetchExperiments()
}
}

@ -76,6 +76,8 @@ class Settings(private val appContext: Context) : PreferencesHolder {
private const val INACTIVE_TAB_MINIMUM_TO_SHOW_AUTO_CLOSE_DIALOG = 20
const val FOUR_HOURS_MS = 60 * 60 * 4 * 1000L
const val ONE_MINUTE_MS = 60 * 1000L
const val ONE_HOUR_MS = 60 * ONE_MINUTE_MS
const val ONE_DAY_MS = 60 * 60 * 24 * 1000L
const val TWO_DAYS_MS = 2 * ONE_DAY_MS
const val THREE_DAYS_MS = 3 * ONE_DAY_MS
@ -402,6 +404,11 @@ class Settings(private val appContext: Context) : PreferencesHolder {
default = true,
)
var nimbusLastFetchTime: Long by longPreference(
appContext.getPreferenceKey(R.string.pref_key_nimbus_last_fetch),
default = 0L,
)
/**
* Indicates the last time when the user was interacting with the [BrowserFragment],
* This is useful to determine if the user has to start on the [HomeFragment]

@ -70,6 +70,7 @@
<string name="pref_key_re_engagement_notification_shown" translatable="false">pref_key_re_engagement_notification_shown</string>
<string name="pref_key_re_engagement_notification_enabled" translatable="false">pref_key_re_engagement_notification_enabled</string>
<string name="pref_key_is_first_run" translatable="false">pref_key_is_first_run</string>
<string name="pref_key_nimbus_last_fetch" translatable="false">pref_key_nimbus_last_fetch</string>
<string name="pref_key_home_blocklist">pref_key_home_blocklist</string>
<!-- Data Choices -->

@ -0,0 +1,102 @@
/* 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.nimbus
import android.content.Context
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.slot
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mozilla.experiments.nimbus.NimbusInterface
import org.mozilla.fenix.experiments.maybeFetchExperiments
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.Settings
class NimbusSystemTest {
lateinit var context: Context
lateinit var nimbus: NimbusUnderTest
lateinit var settings: Settings
private val lastTimeSlot = slot<Long>()
// By default this comes from the generated Nimbus features.
val config = NimbusSystem(
refreshIntervalForeground = 60, /* minutes */
)
class NimbusUnderTest(override val context: Context) : NimbusInterface {
var isFetching = false
override fun fetchExperiments() {
isFetching = true
}
}
@Before
fun setUp() {
context = mockk(relaxed = true)
nimbus = NimbusUnderTest(context)
settings = mockk(relaxed = true)
every { context.settings() } returns settings
every { settings.nimbusLastFetchTime = capture(lastTimeSlot) } just runs
every { settings.nimbusLastFetchTime } returns 0L
assertFalse(nimbus.isFetching)
}
@Test
fun `GIVEN a nimbus object WHEN calling maybeFetchExperiments after an interval THEN call fetchExperiments`() {
val elapsedTime: Long = Settings.ONE_HOUR_MS + 1
nimbus.maybeFetchExperiments(
context,
config,
elapsedTime,
)
assertTrue(nimbus.isFetching)
assertEquals(elapsedTime, lastTimeSlot.captured)
}
@Test
fun `GIVEN a nimbus object WHEN calling maybeFetchExperiments at exactly an interval THEN call fetchExperiments`() {
val elapsedTime: Long = Settings.ONE_HOUR_MS
nimbus.maybeFetchExperiments(
context,
config,
elapsedTime,
)
assertTrue(nimbus.isFetching)
assertEquals(elapsedTime, lastTimeSlot.captured)
}
@Test
fun `GIVEN a nimbus object WHEN calling maybeFetchExperiments before an interval THEN do not call fetchExperiments`() {
val elapsedTime: Long = Settings.ONE_HOUR_MS - 1
nimbus.maybeFetchExperiments(
context,
config,
elapsedTime,
)
assertFalse(nimbus.isFetching)
}
@Test
fun `GIVEN a nimbus object WHEN calling maybeFetchExperiments at without an elapsedTime THEN call fetchExperiments`() {
// since elapsedTime = currentTimeMillis
nimbus.maybeFetchExperiments(
context,
config,
)
assertTrue(nimbus.isFetching)
}
}

@ -1,5 +1,16 @@
---
features:
nimbus-system:
description: |
Configuration of the Nimbus System in Fenix.
variables:
refresh-interval-foreground:
description: |
The minimum interval in minutes between fetching experiment
recipes in the foreground.
type: Int
default: 60 # 1 hour
messaging:
description: |
Configuration for the messaging system.

Loading…
Cancel
Save