Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Backfill metrics pt. 1 #1067

Merged
merged 7 commits into from
Mar 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions app/metrics.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# 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/.


$schema: moz://mozilla.org/schemas/glean/metrics/1-0-0

events:
app_opened:
type: event
description: >
A user opened the app
extra_keys:
source:
description: "The method used to open Fenix. Possible values are: `app_icon`, `custom_tab` or `link`"
bugs:
- 968
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/1067#issuecomment-474598673
notification_emails:
- telemetry-client-dev@mozilla.com
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will it be useful to have a Product/Team email here as well, to be aware of expiry?

expires: "2020-03-01"
search_bar_tapped:
type: event
description: >
A user tapped the search bar
extra_keys:
source:
description: "The view the user was on when they initiated the search (For example: `Home` or `Browser`)"
bugs:
- 959
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/1067#issuecomment-474598673
notification_emails:
- telemetry-client-dev@mozilla.com
expires: "2020-03-01"
entered_url:
type: event
description: >
A user entered a url
extra_keys:
autocomplete:
description: "A boolean that tells us whether the URL was autofilled by an Autocomplete suggestion"
bugs:
- 959
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/1067#issuecomment-474598673
notification_emails:
- telemetry-client-dev@mozilla.com
expires: "2020-03-01"
performed_search:
type: event
description: >
A user performed a search
extra_keys:
search_suggestion:
description: "A boolean that tells us whether or not the search term was suggested by the Awesomebar"
bugs:
- 959
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/1067#issuecomment-474598673
notification_emails:
- telemetry-client-dev@mozilla.com
expires: "2020-03-01"

metrics:
default_browser:
type: boolean
description: >
Is Fenix the default browser?
send_in_pings:
- metrics
bugs:
- 960
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/1067#issuecomment-474598673
notification_emails:
- telemetry-client-dev@mozilla.com
expires: "2020-03-01"
15 changes: 14 additions & 1 deletion app/src/main/java/org/mozilla/fenix/HomeActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import org.mozilla.fenix.settings.SettingsFragmentDirections

@SuppressWarnings("TooManyFunctions")
open class HomeActivity : AppCompatActivity() {
open val isCustomTab = false

val themeManager = DefaultThemeManager().also {
it.onThemeChange = { theme ->
setTheme(theme)
Expand Down Expand Up @@ -60,6 +62,18 @@ open class HomeActivity : AppCompatActivity() {
setSupportActionBar(navigationToolbar)
NavigationUI.setupWithNavController(navigationToolbar, navHost.navController, appBarConfiguration)

intent
?.let { SafeIntent(it) }
?.let {
when {
isCustomTab -> Event.OpenedApp.Source.CUSTOM_TAB
it.isLauncherIntent -> Event.OpenedApp.Source.APP_ICON
it.action == Intent.ACTION_VIEW -> Event.OpenedApp.Source.LINK
else -> null
}
}
?.also { components.analytics.metrics.track(Event.OpenedApp(it)) }

handleOpenedFromExternalSourceIfNecessary(intent)
}

Expand All @@ -69,7 +83,6 @@ open class HomeActivity : AppCompatActivity() {
if (components.core.sessionStorage.current() == null) {
navHost.navController.popBackStack(R.id.homeFragment, false)
}
components.analytics.metrics.track(Event.OpenedApp)
}

override fun onNewIntent(intent: Intent?) {
Expand Down
16 changes: 12 additions & 4 deletions app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import org.mozilla.fenix.IntentReceiverActivity
import org.mozilla.fenix.utils.ItsNotBrokenSnack
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FindInPageIntegration
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.toolbar.SearchAction
import org.mozilla.fenix.components.toolbar.SearchState
import org.mozilla.fenix.components.toolbar.ToolbarComponent
Expand Down Expand Up @@ -242,11 +243,18 @@ class BrowserFragment : Fragment(), BackHandler {
getAutoDisposeObservable<SearchAction>()
.subscribe {
when (it) {
is SearchAction.ToolbarTapped -> Navigation.findNavController(toolbarComponent.getView())
.navigate(
BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
requireComponents.core.sessionManager.selectedSession?.id)
is SearchAction.ToolbarTapped -> {
Navigation
.findNavController(toolbarComponent.getView())
.navigate(
BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
requireComponents.core.sessionManager.selectedSession?.id)
)

requireComponents.analytics.metrics.track(
Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)
)
}
is SearchAction.ToolbarMenuItemTapped -> handleToolbarItemInteraction(it)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,55 @@
package org.mozilla.fenix.components.metrics

import android.content.Context
import mozilla.components.service.glean.EventMetricType
import mozilla.components.service.glean.Glean
import mozilla.components.support.utils.Browsers
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.debug.GleanMetrics.Metrics
import org.mozilla.fenix.debug.GleanMetrics.Events

private class EventWrapper<T : Enum<T>>(
private val event: EventMetricType<T>,
private val keyMapper: ((String) -> T)? = null
) {
fun track(event: Event) {
val extras = if (keyMapper != null) {
event.extras?.mapKeys { keyMapper.invoke(it.key) }
} else {
null
}

this.event.record(extras)
}
}

private val Event.wrapper
get() = when (this) {
is Event.OpenedApp -> EventWrapper(Events.appOpened) { Events.appOpenedKeys.valueOf(it) }
is Event.SearchBarTapped -> EventWrapper(Events.searchBarTapped) { Events.searchBarTappedKeys.valueOf(it) }
is Event.EnteredUrl -> EventWrapper(Events.enteredUrl) { Events.enteredUrlKeys.valueOf(it) }
is Event.PerformedSearch -> EventWrapper(Events.performedSearch) { Events.performedSearchKeys.valueOf(it) }
else -> null
}

class GleanMetricsService(private val context: Context) : MetricsService {
override fun start() {
Glean.initialize(context)
Glean.setUploadEnabled(IsGleanEnabled)

Metrics.apply {
defaultBrowser.set(Browsers.all(context).isDefaultBrowser)
}
}

override fun track(event: Event) { }
override fun track(event: Event) {
event.wrapper?.track(event)
}

override fun shouldTrack(event: Event): Boolean = Settings.getInstance(context).isTelemetryEnabled
override fun shouldTrack(event: Event): Boolean {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glean does this internally based on a persistent internal flag that can be toggled using the Glean.setUploadEnabled() function. The idea was to toggle the flag when the user opted out of telemetry and let the glean library handle discarding recorded data and preventing upload.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@travis79 Good to know, the secondary case for this method is we need a way to allow/block some metrics from being sent to some providers to support Leanplum

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then that's a good case for doing this in the client app. Glean currently doesn't have a way to disable a single specific metric at run-time, only through the metrics.yaml file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It probably wouldn't be too hard to make that work -- but maybe it doesn't buy much...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@travis79 @mdboom If you look at the code in metrics.kt you can see how I have it setup. Right now I have a generic Event type for things that I want to track inside Fenix. When we track an event it dispatches each event to a MetricsService which decides whether or not it wants to track that event and then transforms is appropriately into a format that service expects.

return Settings.getInstance(context).isTelemetryEnabled && event.wrapper != null
}

companion object {
private const val IsGleanEnabled = BuildConfig.TELEMETRY
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,45 @@ import com.leanplum.annotations.Parser
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.utils.Settings

private val Event.name: String
private val Event.name: String?
get() = when (this) {
is Event.AddBookmark -> "E_Add_Bookmark"
is Event.RemoveBookmark -> "E_Remove_Bookmark"
is Event.OpenedBookmark -> "E_Opened_Bookmark"
is Event.OpenedApp -> "E_Opened_App"
is Event.OpenedAppFirstRun -> "E_Opened_App_FirstRun"
is Event.InteractWithSearchURLArea -> "E_Interact_With_Search_URL_Area"
is Event.SavedLoginandPassword -> "E_Saved_Login_and_Password"
is Event.FXANewSignup -> "E_FXA_New_Signup"
is Event.UserSignedInToFxA -> "E_User_Signed_In_To_FxA"
is Event.UserDownloadedFocus -> "E_User_Downloaded_Focus"
is Event.UserDownloadedLockbox -> "E_User_Downloaded_Lockbox"
is Event.UserDownloadedFennec -> "E_User_Downloaded_Fennec"
is Event.TrackingProtectionSettingsChanged -> "E_Tracking_Protection_Settings_Changed"
is Event.FXASyncedNewDevice -> "E_FXA_Synced_New_Device"
is Event.DismissedOnboarding -> "E_Dismissed_Onboarding"
is Event.Uninstall -> "E_Uninstall"
is Event.OpenNewNormalTab -> "E_Open_New_Normal_Tab"
is Event.OpenNewPrivateTab -> "E_Open_New_Private_Tab"
is Event.ShareStarted -> "E_Share_Started"
is Event.ShareCanceled -> "E_Share_Canceled"
is Event.ShareCompleted -> "E_Share_Completed"
is Event.ClosePrivateTabs -> "E_Close_Private_Tabs"
is Event.ClearedPrivateData -> "E_Cleared_Private_Data"
is Event.OpenedLoginManager -> "E_Opened_Login_Manager"
is Event.OpenedMailtoLink -> "E_Opened_Mailto_Link"
is Event.DownloadMediaSavedImage -> "E_Download_Media_Saved_Image"
is Event.UserUsedReaderView -> "E_User_Used_Reader_View"
is Event.UserDownloadedPocket -> "E_User_Downloaded_Pocket"
is Event.UserDownloadedSend -> "E_User_Downloaded_Send"
is Event.OpenedPocketStory -> "E_Opened_Pocket_Story"
is Event.DarkModeEnabled -> "E_Dark_Mode_Enabled"
}
is Event.AddBookmark -> "E_Add_Bookmark"
is Event.RemoveBookmark -> "E_Remove_Bookmark"
is Event.OpenedBookmark -> "E_Opened_Bookmark"
is Event.OpenedApp -> "E_Opened_App"
is Event.OpenedAppFirstRun -> "E_Opened_App_FirstRun"
is Event.InteractWithSearchURLArea -> "E_Interact_With_Search_URL_Area"
is Event.SavedLoginandPassword -> "E_Saved_Login_and_Password"
is Event.FXANewSignup -> "E_FXA_New_Signup"
is Event.UserSignedInToFxA -> "E_User_Signed_In_To_FxA"
is Event.UserDownloadedFocus -> "E_User_Downloaded_Focus"
is Event.UserDownloadedLockbox -> "E_User_Downloaded_Lockbox"
is Event.UserDownloadedFennec -> "E_User_Downloaded_Fennec"
is Event.TrackingProtectionSettingsChanged -> "E_Tracking_Protection_Settings_Changed"
is Event.FXASyncedNewDevice -> "E_FXA_Synced_New_Device"
is Event.DismissedOnboarding -> "E_Dismissed_Onboarding"
is Event.Uninstall -> "E_Uninstall"
is Event.OpenNewNormalTab -> "E_Open_New_Normal_Tab"
is Event.OpenNewPrivateTab -> "E_Open_New_Private_Tab"
is Event.ShareStarted -> "E_Share_Started"
is Event.ShareCanceled -> "E_Share_Canceled"
is Event.ShareCompleted -> "E_Share_Completed"
is Event.ClosePrivateTabs -> "E_Close_Private_Tabs"
is Event.ClearedPrivateData -> "E_Cleared_Private_Data"
is Event.OpenedLoginManager -> "E_Opened_Login_Manager"
is Event.OpenedMailtoLink -> "E_Opened_Mailto_Link"
is Event.DownloadMediaSavedImage -> "E_Download_Media_Saved_Image"
is Event.UserUsedReaderView -> "E_User_Used_Reader_View"
is Event.UserDownloadedPocket -> "E_User_Downloaded_Pocket"
is Event.UserDownloadedSend -> "E_User_Downloaded_Send"
is Event.OpenedPocketStory -> "E_Opened_Pocket_Story"
is Event.DarkModeEnabled -> "E_Dark_Mode_Enabled"

// Do not track these events in Leanplum
is Event.SearchBarTapped -> ""
is Event.EnteredUrl -> ""
is Event.PerformedSearch -> ""
}

class LeanplumMetricsService(private val application: Application) : MetricsService {
data class Token(val id: String, val token: String) {
Expand Down Expand Up @@ -84,12 +89,14 @@ class LeanplumMetricsService(private val application: Application) : MetricsServ
}

override fun track(event: Event) {
Leanplum.track(event.name, event.extras)
event.name?.also {
Leanplum.track(it, event.extras)
}
}

override fun shouldTrack(event: Event): Boolean {
return Settings.getInstance(application).isTelemetryEnabled &&
token.type != Token.Type.Invalid
token.type != Token.Type.Invalid && !event.name.isNullOrEmpty()
}

companion object {
Expand Down
27 changes: 25 additions & 2 deletions app/src/main/java/org/mozilla/fenix/components/metrics/Metrics.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ sealed class Event {
object AddBookmark : Event()
object RemoveBookmark : Event()
object OpenedBookmark : Event()
object OpenedApp : Event()

data class OpenedApp(val source: Source) : Event() {
enum class Source { APP_ICON, LINK, CUSTOM_TAB }
override val extras: Map<String, String>?
get() = hashMapOf("source" to source.name)
}

object OpenedAppFirstRun : Event()
object InteractWithSearchURLArea : Event()
object SavedLoginandPassword : Event()
Expand Down Expand Up @@ -38,7 +44,24 @@ sealed class Event {
object OpenedPocketStory : Event()
object DarkModeEnabled : Event()

val extras: Map<String, Any>?
// Interaction Events
data class SearchBarTapped(val source: Source) : Event() {
enum class Source { HOME, BROWSER }
override val extras: Map<String, String>?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a heads up -- the extras map will become Map<Enum, String> in the next android-components release. This lets us check that the keys are valid at compile time rather than run time. Unfortunately, it's a breaking change to the API -- and you're our first external user (lucky you!)

mozilla-mobile/android-components#2403

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually really like this change 👍

get() = mapOf("source" to source.name)
}

data class EnteredUrl(val autoCompleted: Boolean) : Event() {
override val extras: Map<String, String>?
get() = mapOf("autocomplete" to autoCompleted.toString())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly I don't love all these booleans-to-strings, because it's less clear what is happening. @mdboom can Glean support non-string, string maps? I thought maps could be printed out as strings without every item being a string.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good suggestion, @liuche. I've filed a this bug to follow up on this. @boek: I'd suggest you merge this as-is in the PR for now, and we can revisit once the glean team has decided how to move forward with that. It should be easy enough to find these instances and change them...

}

data class PerformedSearch(val fromSearchSuggestion: Boolean) : Event() {
override val extras: Map<String, String>?
get() = mapOf("search_suggestion" to fromSearchSuggestion.toString())
}

open val extras: Map<String, String>?
get() = null
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ package org.mozilla.fenix.customtabs

import org.mozilla.fenix.HomeActivity

class CustomTabActivity : HomeActivity()
class CustomTabActivity : HomeActivity() {
override val isCustomTab = true
}
3 changes: 3 additions & 0 deletions app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import org.mozilla.fenix.DefaultThemeManager
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.utils.ItsNotBrokenSnack
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.archive
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.home.sessions.ArchivedSession
Expand Down Expand Up @@ -134,6 +135,8 @@ class HomeFragment : Fragment(), CoroutineScope {
view.toolbar.setOnClickListener {
val directions = HomeFragmentDirections.actionHomeFragmentToSearchFragment(null)
Navigation.findNavController(it).navigate(directions)

requireComponents.analytics.metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.HOME))
}

// There is currently an issue with visibility changes in ConstraintLayout 2.0.0-alpha3
Expand Down
Loading