Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
133 changes: 133 additions & 0 deletions PixelDefinitions/pixels/personal_information_removal.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
{
"dbp_optout_process_submit-success": {
"description": "Fired when an opt-out submission succeeds.",
"owners": ["karlenDimla", "landomen"],
"triggers": ["other"],
"suffixes": ["form_factor"],
"parameters": [
"appVersion",
{
"key": "data_broker",
"description": "The URL of the data broker that this opt-out attempt targets",
"type": "string"
},
{
"key": "parent",
"description": "The parent data broker of the one this opt-out attempt targets",
"type": "string"
},
{
"key": "attempt_id",
"description": "Client-generated ID of the opt-out attempt",
"type": "string"
},
{
"key": "duration",
"description": "Total duration of the opt-out attempt in milliseconds",
"type": "string"
},
{
"key": "tries",
"description": "The number of tries it took to submit successfully",
"type": "string"
},
{
"key": "pattern",
"description": "Email pattern used during submission, when available",
"type": "string"
},
{
"key": "vpn_connection_state",
"description": "Reported VPN connection state when the submission succeeded",
"type": "string"
},
{
"key": "vpn_bypass",
"description": "VPN bypass status when the submission succeeded",
"type": "string",
"enum": ["on", "off", "unsupported"]
}
]
},
"dbp_optout_process_failure": {
"description": "Fired when an opt-out attempt fails.",
"owners": ["karlenDimla", "landomen"],
"triggers": ["other"],
"suffixes": ["form_factor"],
"parameters": [
"appVersion",
{
"key": "data_broker",
"description": "The URL of the data broker that this opt-out attempt targets",
"type": "string"
},
{
"key": "parent",
"description": "The parent data broker of the one this opt-out attempt targets",
"type": "string"
},
{
"key": "broker_version",
"description": "The version of the broker JSON file",
"type": "string"
},
{
"key": "attempt_id",
"description": "Client-generated ID of the opt-out attempt",
"type": "string"
},
{
"key": "duration",
"description": "Total duration of the opt-out attempt in milliseconds",
"type": "string"
},
{
"key": "stage",
"description": "The stage where the failure occurred",
"type": "string"
},
{
"key": "tries",
"description": "The number of tries recorded when the failure happened",
"type": "string"
},
{
"key": "pattern",
"description": "Email pattern used during the attempt, when available",
"type": "string"
},
{
"key": "action_id",
"description": "Predefined identifier of the broker action that failed",
"type": "string"
},
{
"key": "action_type",
"description": "Type of action that failed",
"type": "string",
"enum": [
"extract",
"navigate",
"fillForm",
"click",
"expectation",
"emailConfirmation",
"getCaptchaInfo",
"solveCaptcha",
"condition"
]
},
{
"key": "vpn_connection_state",
"description": "Reported VPN connection state when the failure happened",
"type": "string"
},
{
"key": "vpn_bypass",
"description": "VPN bypass status when the failure happened",
"type": "string",
"enum": ["on", "off", "unsupported"]
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.duckduckgo.pir.impl.common.NativeBrokerActionHandler.NativeActionResu
import com.duckduckgo.pir.impl.common.NativeBrokerActionHandler.NativeActionResult.Success.NativeSuccessData.CaptchaTransactionIdReceived
import com.duckduckgo.pir.impl.service.DbpService.CaptchaSolutionMeta
import com.duckduckgo.pir.impl.store.PirRepository
import com.duckduckgo.pir.impl.store.PirRepository.GeneratedEmailData
import kotlinx.coroutines.withContext

interface NativeBrokerActionHandler {
Expand Down Expand Up @@ -62,7 +63,7 @@ interface NativeBrokerActionHandler {
) : NativeActionResult() {
sealed class NativeSuccessData {
data class Email(
val email: String,
val generatedEmailData: GeneratedEmailData,
) : NativeSuccessData()

data class CaptchaTransactionIdReceived(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ class RealPirActionsRunner @AssistedInject constructor(
if (it is Success) {
engine?.dispatch(
EmailReceived(
email = (it.data as Email).email,
generatedEmailData = (it.data as Email).generatedEmailData,
),
)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,23 @@ import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerOptOu
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerRecordEmailConfirmationCompleted
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerRecordEmailConfirmationNeeded
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerRecordEmailConfirmationStarted
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerRecordOptOutCompleted
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerRecordOptOutFailed
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerRecordOptOutStarted
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerRecordOptOutSubmitted
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerScanActionFailed
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerScanActionSucceeded
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerScheduledScanCompleted
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerScheduledScanStarted
import com.duckduckgo.pir.impl.models.AddressCityState
import com.duckduckgo.pir.impl.models.ExtractedProfile
import com.duckduckgo.pir.impl.models.scheduling.JobRecord.OptOutJobRecord
import com.duckduckgo.pir.impl.pixels.PirPixelSender
import com.duckduckgo.pir.impl.pixels.PirStage
import com.duckduckgo.pir.impl.scheduling.JobRecordUpdater
import com.duckduckgo.pir.impl.scripts.models.BrokerAction
import com.duckduckgo.pir.impl.scripts.models.PirSuccessResponse
import com.duckduckgo.pir.impl.scripts.models.PirSuccessResponse.ExtractedResponse
import com.duckduckgo.pir.impl.scripts.models.asActionType
import com.duckduckgo.pir.impl.store.PirEventsRepository
import com.duckduckgo.pir.impl.store.PirRepository
import com.duckduckgo.pir.impl.store.PirSchedulingRepository
Expand Down Expand Up @@ -127,12 +132,24 @@ interface PirRunStateHandler {
val extractedProfile: ExtractedProfile,
) : PirRunState(brokerName)

data class BrokerRecordOptOutCompleted(
data class BrokerRecordOptOutSubmitted(
override val brokerName: String,
val extractedProfile: ExtractedProfile,
val attemptId: String,
val startTimeInMillis: Long,
val endTimeInMillis: Long,
val isSubmitSuccess: Boolean,
val emailPattern: String?,
) : PirRunState(brokerName)

data class BrokerRecordOptOutFailed(
override val brokerName: String,
val extractedProfile: ExtractedProfile,
val attemptId: String,
val startTimeInMillis: Long,
val endTimeInMillis: Long,
val failedAction: BrokerAction,
val stage: PirStage,
val emailPattern: String?,
) : PirRunState(brokerName)

data class BrokerOptOutActionSucceeded(
Expand Down Expand Up @@ -177,7 +194,8 @@ class RealPirRunStateHandler @Inject constructor(
is BrokerScanActionSucceeded -> handleBrokerScanActionSucceeded(pirRunState)
is BrokerScanActionFailed -> handleBrokerScanActionFailed(pirRunState)
is BrokerRecordOptOutStarted -> handleRecordOptOutStarted(pirRunState)
is BrokerRecordOptOutCompleted -> handleRecordOptOutCompleted(pirRunState)
is BrokerRecordOptOutSubmitted -> handleBrokerRecordOptOutSubmitted(pirRunState)
is BrokerRecordOptOutFailed -> handleBrokerRecordOptOutFailed(pirRunState)
is BrokerOptOutActionSucceeded -> handleBrokerOptOutActionSucceeded(pirRunState)
is BrokerOptOutActionFailed -> handleBrokerOptOutActionFailed(pirRunState)
is BrokerRecordEmailConfirmationNeeded -> handleBrokerRecordEmailConfirmationNeeded(pirRunState)
Expand Down Expand Up @@ -406,19 +424,55 @@ class RealPirRunStateHandler @Inject constructor(
)
}

private suspend fun handleRecordOptOutCompleted(state: BrokerRecordOptOutCompleted) {
updateOptOutRecord(state.isSubmitSuccess, state.extractedProfile.dbId)
pixelSender.reportOptOutCompleted(
private suspend fun handleBrokerRecordOptOutSubmitted(state: BrokerRecordOptOutSubmitted) {
val broker = repository.getBrokerForName(state.brokerName)
val optOutJobRecord = updateOptOutRecord(true, state.extractedProfile.dbId)

if (broker == null || optOutJobRecord == null) return

pixelSender.reportOptOutSubmitted(
brokerUrl = broker.url,
parent = broker.parent ?: "",
attemptId = state.attemptId,
durationMs = state.endTimeInMillis - state.startTimeInMillis,
tries = optOutJobRecord.attemptCount,
emailPattern = state.emailPattern,
)

eventsRepository.saveOptOutCompleted(
brokerName = state.brokerName,
totalTimeInMillis = state.endTimeInMillis - state.startTimeInMillis,
isSuccess = state.isSubmitSuccess,
extractedProfile = state.extractedProfile,
startTimeInMillis = state.startTimeInMillis,
endTimeInMillis = state.endTimeInMillis,
isSubmitSuccess = true,
)
}

private suspend fun handleBrokerRecordOptOutFailed(state: BrokerRecordOptOutFailed) {
val broker = repository.getBrokerForName(state.brokerName)
val optOutJobRecord = updateOptOutRecord(false, state.extractedProfile.dbId)

if (broker == null || optOutJobRecord == null) return

pixelSender.reportOptOutFailed(
brokerUrl = broker.url,
parent = broker.parent ?: "",
brokerJsonVersion = broker.version,
attemptId = state.attemptId,
durationMs = state.endTimeInMillis - state.startTimeInMillis,
tries = optOutJobRecord.attemptCount,
emailPattern = state.emailPattern,
stage = state.stage,
actionId = state.failedAction.id,
actionType = state.failedAction.asActionType(),
)

eventsRepository.saveOptOutCompleted(
brokerName = state.brokerName,
extractedProfile = state.extractedProfile,
startTimeInMillis = state.startTimeInMillis,
endTimeInMillis = state.endTimeInMillis,
isSubmitSuccess = state.isSubmitSuccess,
isSubmitSuccess = false,
)
}

Expand Down Expand Up @@ -457,8 +511,8 @@ class RealPirRunStateHandler @Inject constructor(
private suspend fun updateOptOutRecord(
isSubmitted: Boolean,
extractedProfileId: Long,
) {
if (isSubmitted) {
): OptOutJobRecord? {
return if (isSubmitted) {
jobRecordUpdater.updateOptOutRequested(extractedProfileId)
} else {
jobRecordUpdater.updateOptOutError(extractedProfileId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ import com.duckduckgo.pir.impl.common.PirJob.RunType.EMAIL_CONFIRMATION
import com.duckduckgo.pir.impl.common.PirRunStateHandler
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerManualScanCompleted
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerRecordEmailConfirmationCompleted
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerRecordOptOutCompleted
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerRecordOptOutFailed
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerRecordOptOutSubmitted
import com.duckduckgo.pir.impl.common.PirRunStateHandler.PirRunState.BrokerScheduledScanCompleted
import com.duckduckgo.pir.impl.common.actions.EventHandler.Next
import com.duckduckgo.pir.impl.common.actions.PirActionsRunnerStateEngine.Event
import com.duckduckgo.pir.impl.common.actions.PirActionsRunnerStateEngine.Event.BrokerStepCompleted
import com.duckduckgo.pir.impl.common.actions.PirActionsRunnerStateEngine.Event.ExecuteNextBrokerStep
import com.duckduckgo.pir.impl.common.actions.PirActionsRunnerStateEngine.State
import com.duckduckgo.pir.impl.pixels.PirStage
import com.squareup.anvil.annotations.ContributesMultibinding
import javax.inject.Inject
import kotlin.reflect.KClass
Expand Down Expand Up @@ -76,6 +78,7 @@ class BrokerStepCompletedEventHandler @Inject constructor(
state.copy(
currentBrokerStepIndex = state.currentBrokerStepIndex + 1,
actionRetryCount = 0,
generatedEmailData = null,
),
nextEvent = ExecuteNextBrokerStep,
)
Expand Down Expand Up @@ -115,15 +118,32 @@ class BrokerStepCompletedEventHandler @Inject constructor(

RunType.OPTOUT -> {
val currentOptOutStep = currentBrokerStep as OptOutStep
pirRunStateHandler.handleState(
BrokerRecordOptOutCompleted(
if (isSuccess) {
BrokerRecordOptOutSubmitted(
brokerName = currentOptOutStep.brokerName,
extractedProfile = currentOptOutStep.profileToOptOut,
attemptId = state.attemptId ?: "no-attempt-id",
startTimeInMillis = state.brokerStepStartTime,
endTimeInMillis = currentTimeProvider.currentTimeMillis(),
isSubmitSuccess = isSuccess,
),
)
emailPattern = state.generatedEmailData?.pattern,
)
} else {
// Whatever last action that was executed is the last action that failed.
val lastAction = currentBrokerStep.actions[state.currentBrokerStepIndex]

BrokerRecordOptOutFailed(
brokerName = currentOptOutStep.brokerName,
extractedProfile = currentOptOutStep.profileToOptOut,
startTimeInMillis = state.brokerStepStartTime,
endTimeInMillis = currentTimeProvider.currentTimeMillis(),
attemptId = state.attemptId ?: "no-attempt-id",
failedAction = lastAction,
stage = PirStage.OTHER, // TODO: Integrate stages properly later on
emailPattern = state.generatedEmailData?.pattern,
)
}.also {
pirRunStateHandler.handleState(it)
}
}

EMAIL_CONFIRMATION -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class EmailReceivedEventHandler @Inject constructor() : EventHandler {
val currentBrokerStep = state.brokerStepsToExecute[state.currentBrokerStepIndex] as OptOutStep

val updatedProfileWithEmail = currentBrokerStep.profileToOptOut.copy(
email = (event as EmailReceived).email,
email = (event as EmailReceived).generatedEmailData.emailAddress,
)

val updatedBrokerSteps = state.brokerStepsToExecute.toMutableList().apply {
Expand All @@ -60,6 +60,7 @@ class EmailReceivedEventHandler @Inject constructor() : EventHandler {
return Next(
nextState = state.copy(
brokerStepsToExecute = updatedBrokerSteps,
generatedEmailData = event.generatedEmailData,
),
nextEvent = ExecuteBrokerStepAction(
actionRequestData = UserProfile(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.duckduckgo.pir.impl.scripts.models.PirError
import com.duckduckgo.pir.impl.scripts.models.PirScriptRequestData
import com.duckduckgo.pir.impl.scripts.models.PirSuccessResponse
import com.duckduckgo.pir.impl.scripts.models.PirSuccessResponse.GetCaptchaInfoResponse.ResponseData
import com.duckduckgo.pir.impl.store.PirRepository.GeneratedEmailData
import kotlinx.coroutines.flow.Flow

interface PirActionsRunnerStateEngine {
Expand Down Expand Up @@ -53,6 +54,7 @@ interface PirActionsRunnerStateEngine {
val transactionID: String = "",
val pendingUrl: String? = null,
val actionRetryCount: Int = 0,
val generatedEmailData: GeneratedEmailData? = null,
)

/**
Expand All @@ -76,7 +78,7 @@ interface PirActionsRunnerStateEngine {
) : Event()

data class EmailReceived(
val email: String,
val generatedEmailData: GeneratedEmailData,
) : Event()

data object ExecuteNextBrokerStep : Event()
Expand Down
Loading
Loading