Skip to content

Add source to import flow pixels #6310

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@

package com.duckduckgo.autofill.impl.importing

enum class AutofillImportLaunchSource(val value: String) {
PasswordManagementPromo("passwords_management_promo"),
AutofillSettings("autofill_settings"),
PasswordManagementEmpty("passwords_management_empty"),
import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
enum class AutofillImportLaunchSource(val value: String) : Parcelable {
PasswordManagementPromo("password_management_promo"),
PasswordManagementEmptyState("password_management_empty_state"),
PasswordManagementOverflow("password_management_overflow"),
AutofillSettings("autofill_settings_button"),
Unknown("unknown"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.duckduckgo.anvil.annotations.ContributesViewModel
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.autofill.impl.importing.AutofillImportLaunchSource
import com.duckduckgo.autofill.impl.importing.AutofillImportLaunchSource.PasswordManagementPromo
import com.duckduckgo.autofill.impl.importing.promo.ImportInPasswordsPromotionViewModel.Command.DismissImport
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_IMPORT_GOOGLE_PASSWORDS_EMPTY_STATE_CTA_BUTTON_TAPPED
Expand Down Expand Up @@ -56,7 +56,7 @@ class ImportInPasswordsPromotionViewModel @Inject constructor(
fun onPromoShown() {
if (!promoDisplayedPixelSent) {
promoDisplayedPixelSent = true
val params = mapOf("source" to AutofillImportLaunchSource.PasswordManagementPromo.value)
val params = mapOf("source" to PasswordManagementPromo.value)
pixel.fire(AutofillPixelNames.AUTOFILL_IMPORT_GOOGLE_PASSWORDS_EMPTY_STATE_CTA_BUTTON_SHOWN, params)
}
}
Expand All @@ -65,10 +65,10 @@ class ImportInPasswordsPromotionViewModel @Inject constructor(
viewModelScope.launch(dispatchers.io()) {
promoEventDispatcher.emit(
AutofillEffect.LaunchImportPasswords(
source = AutofillImportLaunchSource.PasswordManagementPromo,
source = PasswordManagementPromo,
),
)
val params = mapOf("source" to AutofillImportLaunchSource.PasswordManagementPromo.value)
val params = mapOf("source" to PasswordManagementPromo.value)
pixel.fire(AUTOFILL_IMPORT_GOOGLE_PASSWORDS_EMPTY_STATE_CTA_BUTTON_TAPPED, params)
}
}
Expand All @@ -77,6 +77,11 @@ class ImportInPasswordsPromotionViewModel @Inject constructor(
viewModelScope.launch(dispatchers.io()) {
importInPasswordsVisibility.onPromoDismissed()
command.send(DismissImport)
pixel.fire(
pixel = AutofillPixelNames.AUTOFILL_IMPORT_GOOGLE_PASSWORDS_EMPTY_STATE_CTA_BUTTON_DISMISSED,
parameters = mapOf("source" to PasswordManagementPromo.value),
encodedParameters = emptyMap(),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ enum class AutofillPixelNames(override val pixelName: String) : Pixel.PixelName

AUTOFILL_IMPORT_GOOGLE_PASSWORDS_EMPTY_STATE_CTA_BUTTON_TAPPED("autofill_import_google_passwords_import_button_tapped"),
AUTOFILL_IMPORT_GOOGLE_PASSWORDS_EMPTY_STATE_CTA_BUTTON_SHOWN("autofill_import_google_passwords_import_button_shown"),
AUTOFILL_IMPORT_GOOGLE_PASSWORDS_EMPTY_STATE_CTA_BUTTON_DISMISSED("autofill_import_google_passwords_import_button_dismissed"),
AUTOFILL_IMPORT_GOOGLE_PASSWORDS_OVERFLOW_MENU("autofill_import_google_passwords_overflow_menu_tapped"),
AUTOFILL_IMPORT_GOOGLE_PASSWORDS_PREIMPORT_PROMPT_DISPLAYED("autofill_import_google_passwords_preimport_prompt_displayed"),
AUTOFILL_IMPORT_GOOGLE_PASSWORDS_PREIMPORT_PROMPT_CONFIRMED("autofill_import_google_passwords_preimport_prompt_confirmed"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import com.duckduckgo.autofill.impl.asString
import com.duckduckgo.autofill.impl.deviceauth.DeviceAuthenticator
import com.duckduckgo.autofill.impl.deviceauth.DeviceAuthenticator.AuthConfiguration
import com.duckduckgo.autofill.impl.importing.AutofillImportLaunchSource
import com.duckduckgo.autofill.impl.importing.AutofillImportLaunchSource.PasswordManagementPromo
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_DELETE_LOGIN
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_DISABLED
Expand Down Expand Up @@ -454,7 +455,7 @@ class AutofillPasswordsManagementViewModel @Inject constructor(
viewModelScope.launch(dispatchers.io()) {
autofillEffectDispatcher.effects.collect { effect ->
when {
effect is LaunchImportPasswords -> addCommand(LaunchImportGooglePasswords(showImportInstructions = false))
effect is LaunchImportPasswords -> addCommand(LaunchImportGooglePasswords(PasswordManagementPromo))
}
}
}
Expand Down Expand Up @@ -738,9 +739,9 @@ class AutofillPasswordsManagementViewModel @Inject constructor(
}
}

fun onImportPasswordsFromGooglePasswordManager() {
fun onImportPasswordsFromGooglePasswordManager(importSource: AutofillImportLaunchSource) {
viewModelScope.launch(dispatchers.io()) {
addCommand(LaunchImportGooglePasswords(showImportInstructions = true))
addCommand(LaunchImportGooglePasswords(importSource))
}
}

Expand Down Expand Up @@ -814,7 +815,7 @@ class AutofillPasswordsManagementViewModel @Inject constructor(
fun recordImportGooglePasswordButtonShown() {
if (!importGooglePasswordButtonShownPixelSent) {
importGooglePasswordButtonShownPixelSent = true
val params = mapOf("source" to AutofillImportLaunchSource.PasswordManagementEmpty.value)
val params = mapOf("source" to AutofillImportLaunchSource.PasswordManagementEmptyState.value)
pixel.fire(AUTOFILL_IMPORT_GOOGLE_PASSWORDS_EMPTY_STATE_CTA_BUTTON_SHOWN, params)
}
}
Expand Down Expand Up @@ -904,7 +905,7 @@ class AutofillPasswordsManagementViewModel @Inject constructor(
data object LaunchResetNeverSaveListConfirmation : ListModeCommand()
data class LaunchDeleteAllPasswordsConfirmation(val numberToDelete: Int) : ListModeCommand()
data class PromptUserToAuthenticateMassDeletion(val authConfiguration: AuthConfiguration) : ListModeCommand()
data class LaunchImportGooglePasswords(val showImportInstructions: Boolean) : ListModeCommand()
data class LaunchImportGooglePasswords(val importSource: AutofillImportLaunchSource) : ListModeCommand()
data class LaunchReportAutofillBreakageConfirmation(val eTldPlusOne: String) : ListModeCommand()
data object ShowUserReportSentMessage : ListModeCommand()
data object ReevalutePromotions : ListModeCommand()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ import com.squareup.anvil.annotations.ContributesBinding
import javax.inject.Inject

interface ImportPasswordsPixelSender {
fun onImportPasswordsDialogDisplayed()
fun onImportPasswordsDialogImportButtonClicked()
fun onUserCancelledImportPasswordsDialog()
fun onUserCancelledImportWebFlow(stage: String)
fun onImportSuccessful(savedCredentials: Int, numberSkipped: Int)
fun onImportFailed(reason: UserCannotImportReason)
fun onImportPasswordsDialogDisplayed(source: AutofillImportLaunchSource)
fun onImportPasswordsDialogImportButtonClicked(source: AutofillImportLaunchSource)
fun onUserCancelledImportPasswordsDialog(source: AutofillImportLaunchSource)
fun onUserCancelledImportWebFlow(stage: String, source: AutofillImportLaunchSource)
fun onImportSuccessful(savedCredentials: Int, numberSkipped: Int, source: AutofillImportLaunchSource)
fun onImportFailed(reason: UserCannotImportReason, source: AutofillImportLaunchSource)
fun onImportPasswordsButtonTapped(launchSource: AutofillImportLaunchSource)
fun onImportPasswordsOverflowMenuTapped()
fun onImportPasswordsViaDesktopSyncButtonTapped()
Expand All @@ -53,39 +53,49 @@ class ImportPasswordsPixelSenderImpl @Inject constructor(
private val engagementBucketing: AutofillEngagementBucketing,
) : ImportPasswordsPixelSender {

override fun onImportPasswordsDialogDisplayed() {
pixel.fire(AUTOFILL_IMPORT_GOOGLE_PASSWORDS_PREIMPORT_PROMPT_DISPLAYED)
override fun onImportPasswordsDialogDisplayed(source: AutofillImportLaunchSource) {
val params = mapOf(SOURCE_KEY to source.value)
pixel.fire(AUTOFILL_IMPORT_GOOGLE_PASSWORDS_PREIMPORT_PROMPT_DISPLAYED, params)
}

override fun onImportPasswordsDialogImportButtonClicked() {
pixel.fire(AUTOFILL_IMPORT_GOOGLE_PASSWORDS_PREIMPORT_PROMPT_CONFIRMED)
override fun onImportPasswordsDialogImportButtonClicked(source: AutofillImportLaunchSource) {
val params = mapOf(SOURCE_KEY to source.value)
pixel.fire(AUTOFILL_IMPORT_GOOGLE_PASSWORDS_PREIMPORT_PROMPT_CONFIRMED, params)
}

override fun onUserCancelledImportPasswordsDialog() {
val params = mapOf(CANCELLATION_STAGE_KEY to PRE_IMPORT_DIALOG_STAGE)
override fun onUserCancelledImportPasswordsDialog(source: AutofillImportLaunchSource) {
val params = mapOf(
CANCELLATION_STAGE_KEY to PRE_IMPORT_DIALOG_STAGE,
SOURCE_KEY to source.value,
)
pixel.fire(AUTOFILL_IMPORT_GOOGLE_PASSWORDS_RESULT_FAILURE_USER_CANCELLED, params)
}

override fun onUserCancelledImportWebFlow(stage: String) {
val params = mapOf(CANCELLATION_STAGE_KEY to stage)
override fun onUserCancelledImportWebFlow(stage: String, source: AutofillImportLaunchSource) {
val params = mapOf(
CANCELLATION_STAGE_KEY to stage,
SOURCE_KEY to source.value,
)
pixel.fire(AUTOFILL_IMPORT_GOOGLE_PASSWORDS_RESULT_FAILURE_USER_CANCELLED, params)
}

override fun onImportSuccessful(savedCredentials: Int, numberSkipped: Int) {
override fun onImportSuccessful(savedCredentials: Int, numberSkipped: Int, source: AutofillImportLaunchSource) {
val savedCredentialsBucketed = engagementBucketing.bucketNumberOfCredentials(savedCredentials)
val skippedCredentialsBucketed = engagementBucketing.bucketNumberOfCredentials(numberSkipped)
val params = mapOf(
"saved_credentials" to savedCredentialsBucketed,
"skipped_credentials" to skippedCredentialsBucketed,
SOURCE_KEY to source.value,
)
pixel.fire(AUTOFILL_IMPORT_GOOGLE_PASSWORDS_RESULT_SUCCESS, params)
}

override fun onImportFailed(reason: UserCannotImportReason) {
override fun onImportFailed(reason: UserCannotImportReason, source: AutofillImportLaunchSource) {
val pixelName = when (reason) {
ErrorParsingCsv -> AUTOFILL_IMPORT_GOOGLE_PASSWORDS_RESULT_FAILURE_ERROR_PARSING
}
pixel.fire(pixelName)
val params = mapOf(SOURCE_KEY to source.value)
pixel.fire(pixelName, params)
}

override fun onImportPasswordsButtonTapped(launchSource: AutofillImportLaunchSource) {
Expand All @@ -94,7 +104,8 @@ class ImportPasswordsPixelSenderImpl @Inject constructor(
}

override fun onImportPasswordsOverflowMenuTapped() {
pixel.fire(AUTOFILL_IMPORT_GOOGLE_PASSWORDS_OVERFLOW_MENU)
val params = mapOf(SOURCE_KEY to AutofillImportLaunchSource.PasswordManagementOverflow.value)
pixel.fire(AUTOFILL_IMPORT_GOOGLE_PASSWORDS_OVERFLOW_MENU, params)
}

override fun onImportPasswordsViaDesktopSyncButtonTapped() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.core.content.IntentCompat
import androidx.core.os.BundleCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
Expand All @@ -37,6 +38,9 @@ import com.duckduckgo.app.browser.favicon.FaviconManager
import com.duckduckgo.autofill.impl.R
import com.duckduckgo.autofill.impl.databinding.ContentImportFromGooglePasswordDialogBinding
import com.duckduckgo.autofill.impl.deviceauth.AutofillAuthorizationGracePeriod
import com.duckduckgo.autofill.impl.importing.AutofillImportLaunchSource
import com.duckduckgo.autofill.impl.importing.AutofillImportLaunchSource.PasswordManagementPromo
import com.duckduckgo.autofill.impl.importing.AutofillImportLaunchSource.Unknown
import com.duckduckgo.autofill.impl.importing.CredentialImporter
import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePassword
import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordResult
Expand Down Expand Up @@ -102,18 +106,26 @@ class ImportFromGooglePasswordsDialog : BottomSheetDialogFragment() {
if (activityResult.resultCode == Activity.RESULT_OK) {
lifecycleScope.launch {
activityResult.data?.let { data ->
processImportFlowResult(data)
val launchSource = getLaunchSource()
processImportFlowResult(data, launchSource)
}
}
}
}

private fun ImportFromGooglePasswordsDialog.processImportFlowResult(data: Intent) {
private fun getLaunchSource() =
BundleCompat.getParcelable(arguments ?: Bundle(), KEY_LAUNCH_SOURCE, AutofillImportLaunchSource::class.java) ?: Unknown

private fun ImportFromGooglePasswordsDialog.processImportFlowResult(data: Intent, launchSource: AutofillImportLaunchSource) {
(IntentCompat.getParcelableExtra(data, ImportGooglePasswordResult.RESULT_KEY_DETAILS, ImportGooglePasswordResult::class.java)).let {
when (it) {
is ImportGooglePasswordResult.Success -> viewModel.onImportFlowFinishedSuccessfully()
is ImportGooglePasswordResult.Error -> viewModel.onImportFlowFinishedWithError(it.reason)
is ImportGooglePasswordResult.UserCancelled -> viewModel.onImportFlowCancelledByUser(it.stage, canShowPreImportDialog())
is ImportGooglePasswordResult.Success -> viewModel.onImportFlowFinishedSuccessfully(launchSource)
is ImportGooglePasswordResult.Error -> viewModel.onImportFlowFinishedWithError(it.reason, launchSource)
is ImportGooglePasswordResult.UserCancelled -> viewModel.onImportFlowCancelledByUser(
it.stage,
canShowPreImportDialog(launchSource),
launchSource,
)
else -> {}
}
}
Expand Down Expand Up @@ -203,14 +215,20 @@ class ImportFromGooglePasswordsDialog : BottomSheetDialogFragment() {
}

// check if we should show the initial instructional prompt
if (canShowPreImportDialog()) {
viewModel.shouldShowInitialInstructionalPrompt()
val launchSource = getLaunchSource()
if (canShowPreImportDialog(launchSource)) {
viewModel.shouldShowInitialInstructionalPrompt(launchSource)
} else {
startImportWebFlow()
}
}

private fun canShowPreImportDialog() = arguments?.getBoolean(KEY_SHOW_INITIAL_INSTRUCTIONAL_PROMPT, true) ?: true
private fun canShowPreImportDialog(launchSource: AutofillImportLaunchSource): Boolean {
return when (launchSource) {
PasswordManagementPromo -> false
else -> true
}
}

override fun onCreateView(
inflater: LayoutInflater,
Expand Down Expand Up @@ -263,7 +281,7 @@ class ImportFromGooglePasswordsDialog : BottomSheetDialogFragment() {

private fun onImportGcmButtonClicked() {
startImportWebFlow()
importPasswordsPixelSender.onImportPasswordsDialogImportButtonClicked()
importPasswordsPixelSender.onImportPasswordsDialogImportButtonClicked(getLaunchSource())
}

private fun startImportWebFlow() {
Expand All @@ -285,7 +303,7 @@ class ImportFromGooglePasswordsDialog : BottomSheetDialogFragment() {
return
}

importPasswordsPixelSender.onUserCancelledImportPasswordsDialog()
importPasswordsPixelSender.onUserCancelledImportPasswordsDialog(getLaunchSource())

dismiss()
}
Expand All @@ -298,12 +316,12 @@ class ImportFromGooglePasswordsDialog : BottomSheetDialogFragment() {

companion object {

private const val KEY_SHOW_INITIAL_INSTRUCTIONAL_PROMPT = "showInitialInstructionalPrompt"
private const val KEY_LAUNCH_SOURCE = "launchSource"

fun instance(showInitialInstructionalPrompt: Boolean): ImportFromGooglePasswordsDialog {
fun instance(importSource: AutofillImportLaunchSource): ImportFromGooglePasswordsDialog {
val fragment = ImportFromGooglePasswordsDialog()
fragment.arguments = Bundle().apply {
putBoolean(KEY_SHOW_INITIAL_INSTRUCTIONAL_PROMPT, showInitialInstructionalPrompt)
putParcelable(KEY_LAUNCH_SOURCE, importSource)
}
return fragment
}
Expand Down
Loading
Loading