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:
|
||||
- interaction
|
||||
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 androidx.annotation.VisibleForTesting
|
||||
import androidx.core.net.toUri
|
||||
import mozilla.components.service.nimbus.GleanMetrics.MicroSurvey
|
||||
import mozilla.components.service.nimbus.GleanMetrics.Messaging as GleanMessaging
|
||||
|
||||
/**
|
||||
|
|
@ -46,6 +47,19 @@ open class NimbusMessagingController(
|
|||
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.
|
||||
*
|
||||
|
|
@ -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 =
|
||||
if (action.startsWith("://")) {
|
||||
"$deepLinkScheme$action".toUri()
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import android.content.Intent
|
|||
* - [onMessageClicked] and [getIntentForMessage] to be called when the user taps on
|
||||
* the message, and to get the action for 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 {
|
||||
/**
|
||||
|
|
@ -47,6 +48,18 @@ interface NimbusMessagingControllerInterface {
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -11571,29 +11571,6 @@ debug_drawer:
|
|||
- android-probes@mozilla.com
|
||||
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:
|
||||
home_search_tapped:
|
||||
type: event
|
||||
|
|
|
|||
|
|
@ -188,6 +188,20 @@ sealed class AppAction : Action {
|
|||
* Indicates the given [message] was dismissed.
|
||||
*/
|
||||
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.MessageClicked
|
||||
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.UpdateMessageToShow
|
||||
import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.UpdateMessages
|
||||
|
|
@ -58,6 +59,10 @@ class MessagingMiddleware(
|
|||
|
||||
is MessageDismissed -> onMessageDismissed(context, action.message)
|
||||
|
||||
is MicrosurveyAction.Completed -> {
|
||||
onMicrosurveyCompleted(context, action.message, action.answer)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// no-op
|
||||
}
|
||||
|
|
@ -65,6 +70,19 @@ class MessagingMiddleware(
|
|||
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(
|
||||
oldMessage: Message,
|
||||
context: AppStoreMiddlewareContext,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import org.mozilla.fenix.messaging.MessagingState
|
|||
* Reducer for [MessagingState].
|
||||
*/
|
||||
internal object MessagingReducer {
|
||||
fun reduce(state: AppState, action: AppAction.MessagingAction): AppState = when (action) {
|
||||
fun reduce(state: AppState, action: AppAction): AppState = when (action) {
|
||||
is UpdateMessageToShow -> {
|
||||
val messageToShow = state.messaging.messageToShow.toMutableMap()
|
||||
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