forked from mirrors/gecko-dev
Bug 1895560 - Create a new message controller for Microsurveys r=android-reviewers,amejiamarmol
Differential Revision: https://phabricator.services.mozilla.com/D209740
This commit is contained in:
parent
5bcf1b508a
commit
c0221e385c
10 changed files with 256 additions and 24 deletions
|
|
@ -108,3 +108,26 @@ messaging:
|
||||||
data_sensitivity:
|
data_sensitivity:
|
||||||
- interaction
|
- interaction
|
||||||
expires: never
|
expires: never
|
||||||
|
|
||||||
|
micro_survey:
|
||||||
|
response:
|
||||||
|
type: event
|
||||||
|
description: User response data for a micro survey.
|
||||||
|
extra_keys:
|
||||||
|
survey_id:
|
||||||
|
description: The id of the survey.
|
||||||
|
type: string
|
||||||
|
user_selection:
|
||||||
|
description: |
|
||||||
|
The users selected option. For example, possible values are:
|
||||||
|
"Very satisfied", "satisfied", "neutral", "dissatisfied" and "Very dissatisfied".
|
||||||
|
type: string
|
||||||
|
bugs:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1891509
|
||||||
|
data_reviews:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1891509
|
||||||
|
data_sensitivity:
|
||||||
|
- interaction
|
||||||
|
notification_emails:
|
||||||
|
- android-probes@mozilla.com
|
||||||
|
expires: never
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import mozilla.components.service.nimbus.GleanMetrics.MicroSurvey
|
||||||
import mozilla.components.service.nimbus.GleanMetrics.Messaging as GleanMessaging
|
import mozilla.components.service.nimbus.GleanMetrics.Messaging as GleanMessaging
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -46,6 +47,19 @@ open class NimbusMessagingController(
|
||||||
messagingStorage.updateMetadata(updatedMetadata)
|
messagingStorage.updateMetadata(updatedMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a microsurvey attached to a message has been completed by the user.
|
||||||
|
*
|
||||||
|
* @param message The message containing the microsurvey that was completed.
|
||||||
|
* @param answer The user's response to the microsurvey question.
|
||||||
|
*/
|
||||||
|
override suspend fun onMicrosurveyCompleted(message: Message, answer: String) {
|
||||||
|
val messageMetadata = message.metadata
|
||||||
|
sendMicrosurveyCompletedTelemetry(messageMetadata.id, answer)
|
||||||
|
val updatedMetadata = messageMetadata.copy(pressed = true)
|
||||||
|
messagingStorage.updateMetadata(updatedMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once the user has clicked on a message.
|
* Called once the user has clicked on a message.
|
||||||
*
|
*
|
||||||
|
|
@ -112,6 +126,10 @@ open class NimbusMessagingController(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun sendMicrosurveyCompletedTelemetry(messageId: String, answer: String?) {
|
||||||
|
MicroSurvey.response.record(MicroSurvey.ResponseExtra(surveyId = messageId, userSelection = answer))
|
||||||
|
}
|
||||||
|
|
||||||
private fun convertActionIntoDeepLinkSchemeUri(action: String): Uri =
|
private fun convertActionIntoDeepLinkSchemeUri(action: String): Uri =
|
||||||
if (action.startsWith("://")) {
|
if (action.startsWith("://")) {
|
||||||
"$deepLinkScheme$action".toUri()
|
"$deepLinkScheme$action".toUri()
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import android.content.Intent
|
||||||
* - [onMessageClicked] and [getIntentForMessage] to be called when the user taps on
|
* - [onMessageClicked] and [getIntentForMessage] to be called when the user taps on
|
||||||
* the message, and to get the action for the message.
|
* the message, and to get the action for the message.
|
||||||
* - [onMessageDismissed] to be called when the user dismisses the message.
|
* - [onMessageDismissed] to be called when the user dismisses the message.
|
||||||
|
* - [onMicrosurveyCompleted] to be called when a microsurvey has been completed by the user.
|
||||||
*/
|
*/
|
||||||
interface NimbusMessagingControllerInterface {
|
interface NimbusMessagingControllerInterface {
|
||||||
/**
|
/**
|
||||||
|
|
@ -47,6 +48,18 @@ interface NimbusMessagingControllerInterface {
|
||||||
*/
|
*/
|
||||||
suspend fun onMessageDismissed(message: Message)
|
suspend fun onMessageDismissed(message: Message)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a microsurvey has been completed by the user.
|
||||||
|
*
|
||||||
|
* This function should be called when a microsurvey associated with a message
|
||||||
|
* has been completed by the user, providing the message object and the user's
|
||||||
|
* response in the form of LikertAnswers.
|
||||||
|
*
|
||||||
|
* @param message The message associated with the microsurvey.
|
||||||
|
* @param answer The user's response to the microsurvey, represented as LikertAnswers.
|
||||||
|
*/
|
||||||
|
suspend fun onMicrosurveyCompleted(message: Message, answer: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once the user has clicked on a message.
|
* Called once the user has clicked on a message.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -11571,29 +11571,6 @@ debug_drawer:
|
||||||
- android-probes@mozilla.com
|
- android-probes@mozilla.com
|
||||||
expires: never
|
expires: never
|
||||||
|
|
||||||
micro_survey:
|
|
||||||
response:
|
|
||||||
type: event
|
|
||||||
description: User response data for a micro survey.
|
|
||||||
extra_keys:
|
|
||||||
feature_name:
|
|
||||||
description: The name of the feature the survey is for e.g. "printing PDF".
|
|
||||||
type: string
|
|
||||||
user_selection:
|
|
||||||
description: |
|
|
||||||
The users selected option. For example, possible values are:
|
|
||||||
"Very satisfied", "satisfied", "neutral", "dissatisfied" and "Very dissatisfied".
|
|
||||||
type: string
|
|
||||||
bugs:
|
|
||||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1891509
|
|
||||||
data_reviews:
|
|
||||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1891509
|
|
||||||
data_sensitivity:
|
|
||||||
- interaction
|
|
||||||
notification_emails:
|
|
||||||
- android-probes@mozilla.com
|
|
||||||
expires: never
|
|
||||||
|
|
||||||
navigation_bar:
|
navigation_bar:
|
||||||
home_search_tapped:
|
home_search_tapped:
|
||||||
type: event
|
type: event
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,20 @@ sealed class AppAction : Action {
|
||||||
* Indicates the given [message] was dismissed.
|
* Indicates the given [message] was dismissed.
|
||||||
*/
|
*/
|
||||||
data class MessageDismissed(val message: Message) : MessagingAction()
|
data class MessageDismissed(val message: Message) : MessagingAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sealed class representing actions related to microsurveys within messaging functionality.
|
||||||
|
* Extends [MessagingAction].
|
||||||
|
*/
|
||||||
|
sealed class MicrosurveyAction : MessagingAction() {
|
||||||
|
/**
|
||||||
|
* Indicates that the microsurvey associated with the [message] has been completed.
|
||||||
|
*
|
||||||
|
* @property message The message associated with the completed microsurvey.
|
||||||
|
* @property answer The answer provided for the microsurvey.
|
||||||
|
*/
|
||||||
|
data class Completed(val message: Message, val answer: String) : MicrosurveyAction()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
/* 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.messaging
|
||||||
|
|
||||||
|
import mozilla.components.service.nimbus.messaging.Message
|
||||||
|
import org.mozilla.fenix.BrowserDirection
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.components.AppStore
|
||||||
|
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.MessageClicked
|
||||||
|
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.MessageDismissed
|
||||||
|
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.MicrosurveyAction.Completed
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles interactions with the microsurveys.
|
||||||
|
*/
|
||||||
|
class MicrosurveyMessageController(
|
||||||
|
private val appStore: AppStore,
|
||||||
|
private val homeActivity: HomeActivity,
|
||||||
|
) : MessageController {
|
||||||
|
|
||||||
|
override fun onMessagePressed(message: Message) {
|
||||||
|
appStore.dispatch(MessageClicked(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMessageDismissed(message: Message) {
|
||||||
|
appStore.dispatch(MessageDismissed(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the click event on the privacy link within a message.
|
||||||
|
* @param message The message containing the privacy link.
|
||||||
|
* @param privacyLink The URL of the privacy link.
|
||||||
|
*/
|
||||||
|
// Suppress unused parameter to work around the CI.
|
||||||
|
// message will be called by the UI when privacy noticed is clicked.
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
fun onPrivacyLinkClicked(message: Message, privacyLink: String) {
|
||||||
|
homeActivity.openToBrowserAndLoad(
|
||||||
|
searchTermOrURL = privacyLink,
|
||||||
|
newTab = true,
|
||||||
|
from = BrowserDirection.FromHome,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an action when a survey is completed.
|
||||||
|
* @param message The message containing the completed survey.
|
||||||
|
* @param answer The answer provided in the survey.
|
||||||
|
*/
|
||||||
|
fun onSurveyCompleted(message: Message, answer: String) {
|
||||||
|
appStore.dispatch(Completed(message, answer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.ConsumeMe
|
||||||
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.Evaluate
|
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.Evaluate
|
||||||
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.MessageClicked
|
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.MessageClicked
|
||||||
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.MessageDismissed
|
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.MessageDismissed
|
||||||
|
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.MicrosurveyAction
|
||||||
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.Restore
|
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.Restore
|
||||||
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.UpdateMessageToShow
|
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.UpdateMessageToShow
|
||||||
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.UpdateMessages
|
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.UpdateMessages
|
||||||
|
|
@ -58,6 +59,10 @@ class MessagingMiddleware(
|
||||||
|
|
||||||
is MessageDismissed -> onMessageDismissed(context, action.message)
|
is MessageDismissed -> onMessageDismissed(context, action.message)
|
||||||
|
|
||||||
|
is MicrosurveyAction.Completed -> {
|
||||||
|
onMicrosurveyCompleted(context, action.message, action.answer)
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
|
|
@ -65,6 +70,19 @@ class MessagingMiddleware(
|
||||||
next(action)
|
next(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onMicrosurveyCompleted(
|
||||||
|
context: AppStoreMiddlewareContext,
|
||||||
|
message: Message,
|
||||||
|
answer: String,
|
||||||
|
) {
|
||||||
|
val newMessages = removeMessage(context, message = message)
|
||||||
|
context.store.dispatch(UpdateMessages(newMessages))
|
||||||
|
consumeMessageToShowIfNeeded(context, message)
|
||||||
|
coroutineScope.launch {
|
||||||
|
controller.onMicrosurveyCompleted(message, answer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onMessagedDisplayed(
|
private fun onMessagedDisplayed(
|
||||||
oldMessage: Message,
|
oldMessage: Message,
|
||||||
context: AppStoreMiddlewareContext,
|
context: AppStoreMiddlewareContext,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import org.mozilla.fenix.messaging.MessagingState
|
||||||
* Reducer for [MessagingState].
|
* Reducer for [MessagingState].
|
||||||
*/
|
*/
|
||||||
internal object MessagingReducer {
|
internal object MessagingReducer {
|
||||||
fun reduce(state: AppState, action: AppAction.MessagingAction): AppState = when (action) {
|
fun reduce(state: AppState, action: AppAction): AppState = when (action) {
|
||||||
is UpdateMessageToShow -> {
|
is UpdateMessageToShow -> {
|
||||||
val messageToShow = state.messaging.messageToShow.toMutableMap()
|
val messageToShow = state.messaging.messageToShow.toMutableMap()
|
||||||
messageToShow[action.message.surface] = action.message
|
messageToShow[action.message.surface] = action.message
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/* 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 mozilla.components.service.nimbus.messaging.microsurvey
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model containing the required data from a raw [MicrosurveyUiData] object in a UI state.
|
||||||
|
*/
|
||||||
|
data class MicrosurveyUiData(
|
||||||
|
val type: Type,
|
||||||
|
@DrawableRes val imageRes: Int,
|
||||||
|
val title: String,
|
||||||
|
val description: String,
|
||||||
|
val primaryButtonLabel: String,
|
||||||
|
val secondaryButtonLabel: String,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Model for different types of Microsurvey ratings.
|
||||||
|
*
|
||||||
|
* @property fivePointScaleItems Identifier for the likert scale rating.
|
||||||
|
*/
|
||||||
|
enum class Type(
|
||||||
|
val fivePointScaleItems: String,
|
||||||
|
) {
|
||||||
|
VERY_DISSATISFIED(
|
||||||
|
fivePointScaleItems = "very_dissatisfied",
|
||||||
|
),
|
||||||
|
DISSATISFIED(
|
||||||
|
fivePointScaleItems = "dissatisfied",
|
||||||
|
),
|
||||||
|
NEUTRAL(
|
||||||
|
fivePointScaleItems = "neutral",
|
||||||
|
),
|
||||||
|
SATISFIED(
|
||||||
|
fivePointScaleItems = "satisfied",
|
||||||
|
),
|
||||||
|
VERY_SATISFIED(
|
||||||
|
fivePointScaleItems = "very_satisfied",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
/* 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.messaging
|
||||||
|
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import mozilla.components.service.nimbus.messaging.Message
|
||||||
|
import mozilla.components.support.test.robolectric.testContext
|
||||||
|
import mozilla.telemetry.glean.testing.GleanTestRule
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.components.AppStore
|
||||||
|
import org.mozilla.fenix.components.appstate.AppAction
|
||||||
|
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.MessageClicked
|
||||||
|
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.MicrosurveyAction.Completed
|
||||||
|
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||||
|
|
||||||
|
@RunWith(FenixRobolectricTestRunner::class)
|
||||||
|
class MicrosurveyMessageControllerTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val gleanTestRule = GleanTestRule(testContext)
|
||||||
|
|
||||||
|
private val homeActivity: HomeActivity = mockk(relaxed = true)
|
||||||
|
private val message: Message = mockk(relaxed = true)
|
||||||
|
private lateinit var microsurveyMessageController: MicrosurveyMessageController
|
||||||
|
private val appStore: AppStore = mockk(relaxed = true)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
microsurveyMessageController = MicrosurveyMessageController(
|
||||||
|
appStore = appStore,
|
||||||
|
homeActivity = homeActivity,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN calling onMessagePressed THEN update the app store with the MessageClicked action`() {
|
||||||
|
microsurveyMessageController.onMessagePressed(message)
|
||||||
|
verify { appStore.dispatch(MessageClicked(message)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN calling onMessageDismissed THEN update the app store with the MessageDismissed action`() {
|
||||||
|
microsurveyMessageController.onMessageDismissed(message)
|
||||||
|
|
||||||
|
verify { appStore.dispatch(AppAction.MessagingAction.MessageDismissed(message)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN calling onPrivacyLinkClicked THEN open the privacy URL in a new tab`() {
|
||||||
|
val privacyURL = "www.mozilla.com"
|
||||||
|
microsurveyMessageController.onPrivacyLinkClicked(message, privacyURL)
|
||||||
|
|
||||||
|
verify { homeActivity.openToBrowserAndLoad(any(), newTab = true, any(), any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN calling onSurveyCompleted THEN update the app store with the SurveyCompleted action`() {
|
||||||
|
val answer = "satisfied"
|
||||||
|
microsurveyMessageController.onSurveyCompleted(message, answer)
|
||||||
|
|
||||||
|
verify { appStore.dispatch(Completed(message, answer)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue