Skip to content

Commit 9f19ec0

Browse files
authored
Improve sync observability around disabling (#6623)
Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1211103035810276?focus=true ### Description Adds a pixel for when sync is disabled, and one for when it is disabled with account deletion. ### Steps to test this PR Add logcat filter `Pixel url request:.*sync_disabled` - [x] Enable sync (`Settings -> Sync & Backup -> Sync and Back Up This Device`) - [x] Tap `Turn Off Sync & Backup` and confirm. Verify `sync_disabled` pixel in logs. - [x] Enable sync again - [x] Tap `Turn Off and Delete Server Data` and confirm. - [x] Verify `sync_disabledanddeleted` pixel in logs, and it has param `connected_devices` Co-authored-by: Craig Russell <[email protected]>
1 parent dad094b commit 9f19ec0

File tree

5 files changed

+72
-0
lines changed

5 files changed

+72
-0
lines changed

sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncAccountRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,8 @@ class AppSyncAccountRepository @Inject constructor(
564564
}
565565

566566
override fun deleteAccount(): Result<Boolean> {
567+
syncPixels.fireUserConfirmedToTurnOffSyncAndDelete(connectedDevicesCached.size)
568+
567569
val token = syncStore.token.takeUnless {
568570
it.isNullOrEmpty()
569571
} ?: return Error(reason = "Delete account: Token Empty").alsoFireDeleteAccountErrorPixel()

sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/pixels/SyncPixels.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,24 @@ package com.duckduckgo.sync.impl.pixels
1919
import android.content.SharedPreferences
2020
import androidx.core.content.edit
2121
import com.duckduckgo.app.statistics.pixels.Pixel
22+
import com.duckduckgo.common.utils.plugins.pixel.PixelParamRemovalPlugin
23+
import com.duckduckgo.common.utils.plugins.pixel.PixelParamRemovalPlugin.PixelParameter
24+
import com.duckduckgo.common.utils.plugins.pixel.PixelParamRemovalPlugin.PixelParameter.Companion.removeAtb
2225
import com.duckduckgo.di.scopes.AppScope
2326
import com.duckduckgo.sync.api.engine.SyncableType
2427
import com.duckduckgo.sync.impl.API_CODE
2528
import com.duckduckgo.sync.impl.Result.Error
2629
import com.duckduckgo.sync.impl.pixels.SyncPixelName.SYNC_DAILY
2730
import com.duckduckgo.sync.impl.pixels.SyncPixelName.SYNC_DAILY_SUCCESS_RATE_PIXEL
2831
import com.duckduckgo.sync.impl.pixels.SyncPixelName.SYNC_OBJECT_LIMIT_EXCEEDED_DAILY
32+
import com.duckduckgo.sync.impl.pixels.SyncPixelParameters.CONNECTED_DEVICES_WHEN_DELETING
2933
import com.duckduckgo.sync.impl.pixels.SyncPixelParameters.SYNC_FEATURE_PROMOTION_SOURCE
3034
import com.duckduckgo.sync.impl.pixels.SyncPixelParameters.SYNC_SETUP_SCREEN_TYPE
3135
import com.duckduckgo.sync.impl.pixels.SyncPixels.ScreenType
3236
import com.duckduckgo.sync.impl.stats.SyncStatsRepository
3337
import com.duckduckgo.sync.store.SharedPrefsProvider
3438
import com.squareup.anvil.annotations.ContributesBinding
39+
import com.squareup.anvil.annotations.ContributesMultibinding
3540
import dagger.SingleInstanceIn
3641
import java.time.Instant
3742
import java.time.format.DateTimeFormatter
@@ -108,6 +113,8 @@ interface SyncPixels {
108113
fun fireSetupDeepLinkFlowStarted()
109114
fun fireSetupDeepLinkFlowSuccess()
110115
fun fireSetupDeepLinkFlowAbandoned()
116+
fun fireUserConfirmedToTurnOffSync()
117+
fun fireUserConfirmedToTurnOffSyncAndDelete(connectedDevices: Int)
111118
}
112119

113120
@ContributesBinding(AppScope::class)
@@ -322,6 +329,15 @@ class RealSyncPixels @Inject constructor(
322329
pixel.fire(SyncPixelName.SYNC_SETUP_DEEP_LINK_FLOW_ABANDONED)
323330
}
324331

332+
override fun fireUserConfirmedToTurnOffSync() {
333+
pixel.fire(SyncPixelName.SYNC_USER_CONFIRMED_TO_TURN_OFF_SYNC)
334+
}
335+
336+
override fun fireUserConfirmedToTurnOffSyncAndDelete(connectedDevices: Int) {
337+
val params = mapOf(CONNECTED_DEVICES_WHEN_DELETING to connectedDevices.toString())
338+
pixel.fire(SyncPixelName.SYNC_USER_CONFIRMED_TO_TURN_OFF_SYNC_AND_DELETE, parameters = params)
339+
}
340+
325341
override fun fireSyncBarcodeScreenShown(screenType: ScreenType) {
326342
val params = mapOf(SYNC_SETUP_SCREEN_TYPE to screenType.value)
327343
pixel.fire(SyncPixelName.SYNC_SETUP_BARCODE_SCREEN_SHOWN, parameters = params)
@@ -434,6 +450,8 @@ enum class SyncPixelName(override val pixelName: String) : Pixel.PixelName {
434450
SYNC_SETUP_MANUAL_CODE_ENTERED_FAILED("sync_setup_manual_code_entered_failed"),
435451
SYNC_SETUP_ENDED_ABANDONED("sync_setup_ended_abandoned"),
436452
SYNC_SETUP_ENDED_SUCCESS("sync_setup_ended_successful"),
453+
SYNC_USER_CONFIRMED_TO_TURN_OFF_SYNC("sync_disabled"),
454+
SYNC_USER_CONFIRMED_TO_TURN_OFF_SYNC_AND_DELETE("sync_disabledanddeleted"),
437455
}
438456

439457
object SyncPixelParameters {
@@ -455,4 +473,18 @@ object SyncPixelParameters {
455473
const val SYNC_FEATURE_PROMOTION_SOURCE = "source"
456474
const val GET_OTHER_DEVICES_SCREEN_LAUNCH_SOURCE = "source"
457475
const val SYNC_SETUP_SCREEN_TYPE = "source"
476+
const val CONNECTED_DEVICES_WHEN_DELETING = "connected_devices"
477+
}
478+
479+
@ContributesMultibinding(
480+
scope = AppScope::class,
481+
boundType = PixelParamRemovalPlugin::class,
482+
)
483+
object SyncPixelsRequiringDataCleaning : PixelParamRemovalPlugin {
484+
override fun names(): List<Pair<String, Set<PixelParameter>>> {
485+
return listOf(
486+
SyncPixelName.SYNC_USER_CONFIRMED_TO_TURN_OFF_SYNC.pixelName to removeAtb(),
487+
SyncPixelName.SYNC_USER_CONFIRMED_TO_TURN_OFF_SYNC_AND_DELETE.pixelName to removeAtb(),
488+
)
489+
}
458490
}

sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/SyncActivityViewModel.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ class SyncActivityViewModel @Inject constructor(
264264

265265
fun onTurnOffSyncConfirmed(connectedDevice: ConnectedDevice) {
266266
viewModelScope.launch(dispatchers.io()) {
267+
syncPixels.fireUserConfirmedToTurnOffSync()
268+
267269
viewState.value = viewState.value.hideAccount()
268270
when (val result = syncAccountRepository.logout(connectedDevice.deviceId)) {
269271
is Error -> {

sync/sync-impl/src/test/java/com/duckduckgo/sync/impl/AppSyncAccountRepositoryTest.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,32 @@ class AppSyncAccountRepositoryTest {
883883
}
884884
}
885885

886+
@Test
887+
fun whenAccountDeletedWithASingleConnectedDeviceThenPixelFired() {
888+
prepareForExchangeSuccess()
889+
whenever(syncApi.deleteAccount(token)).thenReturn(deleteAccountSuccess)
890+
configureAsSignedWithConnectedDevices(1)
891+
syncRepo.deleteAccount()
892+
verify(syncPixels).fireUserConfirmedToTurnOffSyncAndDelete(eq(1))
893+
}
894+
895+
@Test
896+
fun whenAccountDeletedWithMultipleConnectedDevicesThenPixelFired() {
897+
prepareForExchangeSuccess()
898+
whenever(syncApi.deleteAccount(token)).thenReturn(deleteAccountSuccess)
899+
configureAsSignedWithConnectedDevices(10)
900+
syncRepo.deleteAccount()
901+
verify(syncPixels).fireUserConfirmedToTurnOffSyncAndDelete(eq(10))
902+
}
903+
904+
@Test
905+
fun whenAccountDeletedWithNoConnectedDevicesThenPixelFired() {
906+
prepareForExchangeSuccess()
907+
whenever(syncApi.deleteAccount(token)).thenReturn(deleteAccountSuccess)
908+
syncRepo.deleteAccount()
909+
verify(syncPixels).fireUserConfirmedToTurnOffSyncAndDelete(eq(0))
910+
}
911+
886912
private fun configureUrlWrappedCodeFeatureFlagState(enabled: Boolean) {
887913
syncFeature.syncSetupBarcodeIsUrlBased().setRawStoredState(State(enable = enabled))
888914
}

sync/sync-impl/src/test/java/com/duckduckgo/sync/impl/ui/SyncActivityViewModelTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,16 @@ class SyncActivityViewModelTest {
263263
verify(syncAccountRepository).logout(deviceId)
264264
}
265265

266+
@Test
267+
fun whenTurnOffSyncConfirmedThenPixelFired() = runTest {
268+
whenever(syncAccountRepository.getThisConnectedDevice()).thenReturn(connectedDevice)
269+
whenever(syncAccountRepository.logout(deviceId)).thenReturn(Result.Success(true))
270+
271+
testee.onTurnOffSyncConfirmed(connectedDevice)
272+
273+
verify(syncPixels).fireUserConfirmedToTurnOffSync()
274+
}
275+
266276
@Test
267277
fun whenLogoutSuccessThenUpdateViewState() = runTest {
268278
givenAuthenticatedUser()

0 commit comments

Comments
 (0)