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

Commit

Permalink
For #6556: Adds Telemetry counts for Search Access Points
Browse files Browse the repository at this point in the history
Added two new sources to be counted ('widget' and 'shortcut') besides 'action' and
'suggestion'. Also modified/fixed some tests.
  • Loading branch information
ValentinTimisica committed Dec 20, 2019
1 parent dd14337 commit 791c566
Show file tree
Hide file tree
Showing 16 changed files with 226 additions and 49 deletions.
6 changes: 4 additions & 2 deletions app/metrics.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -325,15 +325,17 @@ metrics:
If the search engine is bundled with Fenix `search-engine-name` will be the name of the search engine. If it's a
custom search engine (defined: https://github.com/mozilla-mobile/fenix/issues/1607) the value will be `custom`.
`source` will either be `action` or `suggestion`
`source` will be: `action`, `suggestion`, `widget` or `shortcut` (depending on the source from which the search started)
send_in_pings:
- metrics
- baseline
bugs:
- https://github.com/mozilla-mobile/fenix/issues/1158
- https://github.com/mozilla-mobile/fenix/issues/6556
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/1677
- https://github.com/mozilla-mobile/fenix/pull/5216
- https://github.com/mozilla-mobile/fenix/pull/7310
notification_emails:
- fenix-core@mozilla.com
expires: "2020-03-01"
Expand Down Expand Up @@ -1669,4 +1671,4 @@ search_suggestions:
- https://github.com/mozilla-mobile/fenix/pull/6746
notification_emails:
- fenix-core@mozilla.com
expires: "2020-03-01"
expires: "2020-03-01"
2 changes: 1 addition & 1 deletion app/src/main/java/org/mozilla/fenix/HomeActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ open class HomeActivity : AppCompatActivity() {

private val externalSourceIntentProcessors by lazy {
listOf(
SpeechProcessingIntentProcessor(this),
SpeechProcessingIntentProcessor(this, components.analytics.metrics),
StartSearchIntentProcessor(components.analytics.metrics),
DeepLinkIntentProcessor(this),
OpenBrowserIntentProcessor(this, ::getIntentSessionId)
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/org/mozilla/fenix/components/metrics/Metrics.kt
Original file line number Diff line number Diff line change
Expand Up @@ -271,17 +271,23 @@ sealed class Event {
sealed class EventSource {
data class Suggestion(val engineSource: EngineSource) : EventSource()
data class Action(val engineSource: EngineSource) : EventSource()
data class Widget(val engineSource: EngineSource) : EventSource()
data class Shortcut(val engineSource: EngineSource) : EventSource()

private val source: EngineSource
get() = when (this) {
is Suggestion -> engineSource
is Action -> engineSource
is Widget -> engineSource
is Shortcut -> engineSource
}

private val label: String
get() = when (this) {
is Suggestion -> "suggestion"
is Action -> "action"
is Widget -> "widget"
is Shortcut -> "shortcut"
}

val countLabel: String
Expand All @@ -291,6 +297,10 @@ sealed class Event {
get() = "${source.descriptor}.$label"
}

enum class SearchAccessPoint {
SUGGESTION, ACTION, WIDGET, SHORTCUT, NONE
}

override val extras: Map<Events.performedSearchKeys, String>?
get() = mapOf(Events.performedSearchKeys.source to eventSource.sourceLabel)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* 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 mozilla.components.browser.search.SearchEngine
import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.ACTION
import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.NONE
import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.SHORTCUT
import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.SUGGESTION
import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.WIDGET
import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore
import org.mozilla.fenix.ext.searchEngineManager

object MetricsUtils {
fun createSearchEvent(
engine: SearchEngine,
context: Context,
searchAccessPoint: Event.PerformedSearch.SearchAccessPoint
): Event.PerformedSearch? {
val isShortcut = engine != context.searchEngineManager.defaultSearchEngine
val isCustom = CustomSearchEngineStore.isCustomSearchEngine(context, engine.identifier)

val engineSource =
if (isShortcut) Event.PerformedSearch.EngineSource.Shortcut(engine, isCustom)
else Event.PerformedSearch.EngineSource.Default(engine, isCustom)

return when (searchAccessPoint) {
SUGGESTION -> Event.PerformedSearch(Event.PerformedSearch.EventSource.Suggestion(engineSource))
ACTION -> Event.PerformedSearch(Event.PerformedSearch.EventSource.Action(engineSource))
WIDGET -> Event.PerformedSearch(Event.PerformedSearch.EventSource.Widget(engineSource))
SHORTCUT -> Event.PerformedSearch(Event.PerformedSearch.EventSource.Shortcut(engineSource))
NONE -> null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,32 @@ import android.content.Intent
import androidx.navigation.NavController
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.components.metrics.MetricsUtils
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.widget.VoiceSearchActivity.Companion.SPEECH_PROCESSING

/**
* The search widget has a microphone button to let users search with their voice.
* Once the search is complete then a new search should be started.
*/
class SpeechProcessingIntentProcessor(
private val activity: HomeActivity
private val activity: HomeActivity,
private val metrics: MetricController
) : HomeIntentProcessor {

override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
return if (intent.extras?.getBoolean(HomeActivity.OPEN_TO_BROWSER_AND_LOAD) == true) {
out.putExtra(HomeActivity.OPEN_TO_BROWSER_AND_LOAD, false)

val searchEvent = MetricsUtils.createSearchEvent(
activity.components.search.provider.getDefaultEngine(activity),
activity,
Event.PerformedSearch.SearchAccessPoint.WIDGET
)
searchEvent?.let { metrics.track(it) }

activity.openToBrowserAndLoad(
searchTermOrURL = intent.getStringExtra(SPEECH_PROCESSING).orEmpty(),
newTab = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,36 @@ class StartSearchIntentProcessor(

override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
val event = intent.extras?.getString(HomeActivity.OPEN_TO_SEARCH)
var source: Event.PerformedSearch.SearchAccessPoint? = null
return if (event != null) {
when (event) {
SEARCH_WIDGET -> metrics.track(Event.SearchWidgetNewTabPressed)
STATIC_SHORTCUT_NEW_TAB -> metrics.track(Event.PrivateBrowsingStaticShortcutTab)
STATIC_SHORTCUT_NEW_PRIVATE_TAB -> metrics.track(Event.PrivateBrowsingStaticShortcutPrivateTab)
PRIVATE_BROWSING_PINNED_SHORTCUT -> metrics.track(Event.PrivateBrowsingPinnedShortcutPrivateTab)
SEARCH_WIDGET -> {
metrics.track(Event.SearchWidgetNewTabPressed)
source = Event.PerformedSearch.SearchAccessPoint.WIDGET
}
STATIC_SHORTCUT_NEW_TAB -> {
metrics.track(Event.PrivateBrowsingStaticShortcutTab)
source = Event.PerformedSearch.SearchAccessPoint.SHORTCUT
}
STATIC_SHORTCUT_NEW_PRIVATE_TAB -> {
metrics.track(Event.PrivateBrowsingStaticShortcutPrivateTab)
source = Event.PerformedSearch.SearchAccessPoint.SHORTCUT
}
PRIVATE_BROWSING_PINNED_SHORTCUT -> {
metrics.track(Event.PrivateBrowsingPinnedShortcutPrivateTab)
source = Event.PerformedSearch.SearchAccessPoint.SHORTCUT
}
}

out.removeExtra(HomeActivity.OPEN_TO_SEARCH)

val directions = NavGraphDirections.actionGlobalSearch(
sessionId = null
)
navController.nav(null, directions)
val directions = source?.let {
NavGraphDirections.actionGlobalSearch(
sessionId = null,
searchAccessPoint = it
)
}
directions?.let { navController.nav(null, it) }
true
} else {
false
Expand Down
56 changes: 31 additions & 25 deletions app/src/main/java/org/mozilla/fenix/search/SearchController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore
import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.ACTION
import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.NONE
import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.SUGGESTION
import org.mozilla.fenix.components.metrics.MetricsUtils
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.searchEngineManager

/**
* An interface that handles the view manipulation of the Search, triggered by the Interactor
Expand Down Expand Up @@ -54,10 +56,21 @@ class DefaultSearchController(
val event = if (url.isUrl()) {
Event.EnteredUrl(false)
} else {
createSearchEvent(store.state.searchEngineSource.searchEngine, false)
val searchAccessPoint = when (store.state.searchAccessPoint) {
NONE -> ACTION
else -> store.state.searchAccessPoint
}

searchAccessPoint?.let { sap ->
MetricsUtils.createSearchEvent(
store.state.searchEngineSource.searchEngine,
context,
sap
)
}
}

context.metrics.track(event)
event?.let { context.metrics.track(it) }
}
}

Expand Down Expand Up @@ -97,8 +110,19 @@ class DefaultSearchController(
forceSearch = true
)

val event = createSearchEvent(store.state.searchEngineSource.searchEngine, true)
context.metrics.track(event)
val searchAccessPoint = when (store.state.searchAccessPoint) {
NONE -> SUGGESTION
else -> store.state.searchAccessPoint
}

val event = searchAccessPoint?.let { sap ->
MetricsUtils.createSearchEvent(
store.state.searchEngineSource.searchEngine,
context,
sap
)
}
event?.let { context.metrics.track(it) }
}

override fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine) {
Expand All @@ -121,22 +145,4 @@ class DefaultSearchController(
navController.nav(R.id.searchFragment, directions)
context.components.core.sessionManager.select(session)
}

private fun createSearchEvent(
engine: SearchEngine,
isSuggestion: Boolean
): Event.PerformedSearch {
val isShortcut = engine != context.searchEngineManager.defaultSearchEngine
val isCustom = CustomSearchEngineStore.isCustomSearchEngine(context, engine.identifier)

val engineSource =
if (isShortcut) Event.PerformedSearch.EngineSource.Shortcut(engine, isCustom)
else Event.PerformedSearch.EngineSource.Default(engine, isCustom)

val source =
if (isSuggestion) Event.PerformedSearch.EventSource.Suggestion(engineSource)
else Event.PerformedSearch.EventSource.Action(engineSource)

return Event.PerformedSearch(source)
}
}
6 changes: 5 additions & 1 deletion app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ class SearchFragment : Fragment(), UserInteractionHandler {
?.let(SearchFragmentArgs.Companion::fromBundle)
?.let { it.pastedText }

val searchAccessPoint = arguments
?.let(SearchFragmentArgs.Companion::fromBundle)?.searchAccessPoint

val view = inflater.inflate(R.layout.fragment_search, container, false)
val url = session?.url.orEmpty()
val currentSearchEngine = SearchEngineSource.Default(
Expand Down Expand Up @@ -107,7 +110,8 @@ class SearchFragment : Fragment(), UserInteractionHandler {
showHistorySuggestions = requireContext().settings().shouldShowHistorySuggestions,
showBookmarkSuggestions = requireContext().settings().shouldShowBookmarkSuggestions,
session = session,
pastedText = pastedText
pastedText = pastedText,
searchAccessPoint = searchAccessPoint
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import mozilla.components.browser.session.Session
import mozilla.components.lib.state.Action
import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store
import org.mozilla.fenix.components.metrics.Event

/**
* The [Store] for holding the [SearchFragmentState] and applying [SearchFragmentAction]s.
Expand Down Expand Up @@ -55,7 +56,8 @@ data class SearchFragmentState(
val showHistorySuggestions: Boolean,
val showBookmarkSuggestions: Boolean,
val session: Session?,
val pastedText: String? = null
val pastedText: String? = null,
val searchAccessPoint: Event.PerformedSearch.SearchAccessPoint?
) : State

/**
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/navigation/nav_graph.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="search_access_point"
app:argType="org.mozilla.fenix.components.metrics.Event$PerformedSearch$SearchAccessPoint"
android:defaultValue="NONE" />
</fragment>

<fragment
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.mozilla.fenix.components.metrics

import io.mockk.every
import io.mockk.mockk
import mozilla.components.browser.search.SearchEngine
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.TestApplication
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(application = TestApplication::class)
class MetricsUtilsTest {

@Test
fun createSearchEvent() {
val engine: SearchEngine = mockk(relaxed = true)
val context = testContext

every { engine.identifier } returns ENGINE_SOURCE_IDENTIFIER

assertEquals(
"$ENGINE_SOURCE_IDENTIFIER.suggestion",
MetricsUtils.createSearchEvent(
engine,
context,
Event.PerformedSearch.SearchAccessPoint.SUGGESTION
)?.eventSource?.countLabel
)
assertEquals(
"$ENGINE_SOURCE_IDENTIFIER.action",
MetricsUtils.createSearchEvent(
engine,
context,
Event.PerformedSearch.SearchAccessPoint.ACTION
)?.eventSource?.countLabel
)
assertEquals(
"$ENGINE_SOURCE_IDENTIFIER.widget",
MetricsUtils.createSearchEvent(
engine,
context,
Event.PerformedSearch.SearchAccessPoint.WIDGET
)?.eventSource?.countLabel
)
assertEquals(
"$ENGINE_SOURCE_IDENTIFIER.shortcut",
MetricsUtils.createSearchEvent(
engine,
context,
Event.PerformedSearch.SearchAccessPoint.SHORTCUT
)?.eventSource?.countLabel
)
}

companion object {
const val ENGINE_SOURCE_IDENTIFIER = "google-2018"
}
}
Loading

0 comments on commit 791c566

Please sign in to comment.