Skip to content

Commit 9a30390

Browse files
authored
PIR: Add support for storing edited profiles (#6879)
Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1210822763676213?focus=true ### Description Adds support for storing, deleting and updating profiles as a result of profile edit. Note: handling scan/opt-out jobs will be done in next PRs, this only contains the profile storing logic There are three commits: - First one is renaming only - no actual code changes - Second one has the changes needed to support the storing logic - Third one updates tests ### Steps to test this PR https://app.asana.com/1/137249556945/task/1211524792263651?focus=true ### UI changes No UI changes
1 parent 3424f58 commit 9a30390

25 files changed

+1675
-257
lines changed

pir/pir-impl/schemas/com.duckduckgo.pir.impl.store.PirDatabase/11.json

Lines changed: 890 additions & 0 deletions
Large diffs are not rendered by default.

pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebAddAddressToCurrentUserProfileMessageHandler.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import com.duckduckgo.js.messaging.api.JsMessaging
2323
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages
2424
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageRequest
2525
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse
26-
import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder
26+
import com.duckduckgo.pir.impl.dashboard.state.PirWebProfileStateHolder
2727
import com.squareup.anvil.annotations.ContributesMultibinding
2828
import logcat.logcat
2929
import javax.inject.Inject
@@ -36,7 +36,7 @@ import javax.inject.Inject
3636
boundType = PirWebJsMessageHandler::class,
3737
)
3838
class PirWebAddAddressToCurrentUserProfileMessageHandler @Inject constructor(
39-
private val pirWebOnboardingStateHolder: PirWebOnboardingStateHolder,
39+
private val pirWebProfileStateHolder: PirWebProfileStateHolder,
4040
) : PirWebJsMessageHandler() {
4141

4242
override val message = PirDashboardWebMessages.ADD_ADDRESS_TO_CURRENT_USER_PROFILE
@@ -65,7 +65,7 @@ class PirWebAddAddressToCurrentUserProfileMessageHandler @Inject constructor(
6565
}
6666

6767
// attempting to add a duplicate address should return success=false
68-
if (!pirWebOnboardingStateHolder.addAddress(city, state)) {
68+
if (!pirWebProfileStateHolder.addAddress(city, state)) {
6969
logcat { "PIR-WEB: PirWebAddAddressToCurrentUserProfileMessageHandler: address already exists" }
7070
jsMessaging.sendResponse(
7171
jsMessage = jsMessage,

pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebAddNameToCurrentUserProfileMessageHandler.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import com.duckduckgo.js.messaging.api.JsMessaging
2323
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages
2424
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageRequest
2525
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse
26-
import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder
26+
import com.duckduckgo.pir.impl.dashboard.state.PirWebProfileStateHolder
2727
import com.squareup.anvil.annotations.ContributesMultibinding
2828
import logcat.logcat
2929
import javax.inject.Inject
@@ -36,7 +36,7 @@ import javax.inject.Inject
3636
boundType = PirWebJsMessageHandler::class,
3737
)
3838
class PirWebAddNameToCurrentUserProfileMessageHandler @Inject constructor(
39-
private val pirWebOnboardingStateHolder: PirWebOnboardingStateHolder,
39+
private val pirWebProfileStateHolder: PirWebProfileStateHolder,
4040
) : PirWebJsMessageHandler() {
4141

4242
override val message = PirDashboardWebMessages.ADD_NAME_TO_CURRENT_USER_PROFILE
@@ -66,7 +66,7 @@ class PirWebAddNameToCurrentUserProfileMessageHandler @Inject constructor(
6666
}
6767

6868
// attempting to add a duplicate name should return success=false
69-
if (!pirWebOnboardingStateHolder.addName(
69+
if (!pirWebProfileStateHolder.addName(
7070
firstName = firstName,
7171
middleName = middleName,
7272
lastName = lastName,

pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebGetCurrentUserProfileMessageHandler.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.duckduckgo.js.messaging.api.JsMessageCallback
2424
import com.duckduckgo.js.messaging.api.JsMessaging
2525
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages
2626
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse
27+
import com.duckduckgo.pir.impl.dashboard.state.PirWebProfileStateHolder
2728
import com.duckduckgo.pir.impl.store.PirRepository
2829
import com.duckduckgo.pir.impl.store.db.UserName
2930
import com.squareup.anvil.annotations.ContributesMultibinding
@@ -44,6 +45,7 @@ class PirWebGetCurrentUserProfileMessageHandler @Inject constructor(
4445
private val repository: PirRepository,
4546
private val dispatcherProvider: DispatcherProvider,
4647
@AppCoroutineScope private val appCoroutineScope: CoroutineScope,
48+
private val pirWebProfileStateHolder: PirWebProfileStateHolder,
4749
) : PirWebJsMessageHandler() {
4850

4951
override val message = PirDashboardWebMessages.GET_CURRENT_USER_PROFILE
@@ -56,7 +58,10 @@ class PirWebGetCurrentUserProfileMessageHandler @Inject constructor(
5658
logcat { "PIR-WEB: PirWebGetCurrentUserProfileMessageHandler: process $jsMessage" }
5759

5860
appCoroutineScope.launch(dispatcherProvider.io()) {
61+
// TODO consider moving the deprecated filtering to the DB layer when updating job handling for new profiles
5962
val profiles = repository.getUserProfileQueries()
63+
.filter { !it.deprecated }
64+
.also { pirWebProfileStateHolder.setLoadedProfileQueries(it) }
6065

6166
if (profiles.isEmpty()) {
6267
logcat { "PIR-WEB: GetCurrentUserProfileMessageHandler: no user profiles found" }

pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebRemoveAddressAtIndexFromCurrentUserProfileMessageHandler.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import com.duckduckgo.js.messaging.api.JsMessaging
2323
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages
2424
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageRequest
2525
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse
26-
import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder
26+
import com.duckduckgo.pir.impl.dashboard.state.PirWebProfileStateHolder
2727
import com.squareup.anvil.annotations.ContributesMultibinding
2828
import logcat.logcat
2929
import javax.inject.Inject
@@ -33,7 +33,7 @@ import javax.inject.Inject
3333
boundType = PirWebJsMessageHandler::class,
3434
)
3535
class PirWebRemoveAddressAtIndexFromCurrentUserProfileMessageHandler @Inject constructor(
36-
private val pirWebOnboardingStateHolder: PirWebOnboardingStateHolder,
36+
private val pirWebProfileStateHolder: PirWebProfileStateHolder,
3737
) : PirWebJsMessageHandler() {
3838

3939
override val message: PirDashboardWebMessages = PirDashboardWebMessages.REMOVE_ADDRESS_AT_INDEX_FROM_CURRENT_USER_PROFILE
@@ -46,7 +46,7 @@ class PirWebRemoveAddressAtIndexFromCurrentUserProfileMessageHandler @Inject con
4646
logcat { "PIR-WEB: PirWebRemoveAddressAtIndexFromCurrentUserProfileMessageHandler: process $message" }
4747

4848
val request = jsMessage.toRequestMessage(PirWebMessageRequest.RemoveAddressAtIndexFromCurrentUserProfileRequest::class)
49-
if (request == null || !pirWebOnboardingStateHolder.removeAddressAtIndex(request.index)) {
49+
if (request == null || !pirWebProfileStateHolder.removeAddressAtIndex(request.index)) {
5050
logcat { "PIR-WEB: PirWebRemoveAddressAtIndexFromCurrentUserProfileMessageHandler: failed to remove address at index ${request?.index}" }
5151
jsMessaging.sendResponse(
5252
jsMessage = jsMessage,

pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebRemoveNameAtIndexFromCurrentUserProfileMessageHandler.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import com.duckduckgo.js.messaging.api.JsMessaging
2323
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages
2424
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageRequest
2525
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse
26-
import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder
26+
import com.duckduckgo.pir.impl.dashboard.state.PirWebProfileStateHolder
2727
import com.squareup.anvil.annotations.ContributesMultibinding
2828
import logcat.logcat
2929
import javax.inject.Inject
@@ -33,7 +33,7 @@ import javax.inject.Inject
3333
boundType = PirWebJsMessageHandler::class,
3434
)
3535
class PirWebRemoveNameAtIndexFromCurrentUserProfileMessageHandler @Inject constructor(
36-
private val pirWebOnboardingStateHolder: PirWebOnboardingStateHolder,
36+
private val pirWebProfileStateHolder: PirWebProfileStateHolder,
3737
) : PirWebJsMessageHandler() {
3838

3939
override val message: PirDashboardWebMessages = PirDashboardWebMessages.REMOVE_NAME_AT_INDEX_FROM_CURRENT_USER_PROFILE
@@ -46,7 +46,7 @@ class PirWebRemoveNameAtIndexFromCurrentUserProfileMessageHandler @Inject constr
4646
logcat { "PIR-WEB: PirWebRemoveNameAtIndexFromCurrentUserProfileMessageHandler: process $message" }
4747

4848
val request = jsMessage.toRequestMessage(PirWebMessageRequest.RemoveNameAtIndexFromCurrentUserProfileRequest::class)
49-
if (request == null || !pirWebOnboardingStateHolder.removeNameAtIndex(request.index)) {
49+
if (request == null || !pirWebProfileStateHolder.removeNameAtIndex(request.index)) {
5050
logcat { "PIR-WEB: PirWebRemoveNameAtIndexFromCurrentUserProfileMessageHandler: failed to remove name at index ${request?.index}" }
5151
jsMessaging.sendResponse(
5252
jsMessage = jsMessage,

pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebSaveProfileMessageHandler.kt

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import com.duckduckgo.js.messaging.api.JsMessageCallback
2727
import com.duckduckgo.js.messaging.api.JsMessaging
2828
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages
2929
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse
30-
import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder
30+
import com.duckduckgo.pir.impl.dashboard.state.PirWebProfileStateHolder
31+
import com.duckduckgo.pir.impl.models.ProfileQuery
3132
import com.duckduckgo.pir.impl.scan.PirForegroundScanService
3233
import com.duckduckgo.pir.impl.scan.PirScanScheduler
3334
import com.duckduckgo.pir.impl.store.PirRepository
@@ -45,7 +46,7 @@ import javax.inject.Inject
4546
boundType = PirWebJsMessageHandler::class,
4647
)
4748
class PirWebSaveProfileMessageHandler @Inject constructor(
48-
private val pirWebOnboardingStateHolder: PirWebOnboardingStateHolder,
49+
private val pirWebProfileStateHolder: PirWebProfileStateHolder,
4950
private val repository: PirRepository,
5051
private val dispatcherProvider: DispatcherProvider,
5152
private val context: Context,
@@ -64,7 +65,7 @@ class PirWebSaveProfileMessageHandler @Inject constructor(
6465
logcat { "PIR-WEB: PirWebSaveProfileMessageHandler: process $jsMessage" }
6566

6667
// validate that we have the complete profile information
67-
if (!pirWebOnboardingStateHolder.isProfileComplete) {
68+
if (!pirWebProfileStateHolder.isProfileComplete) {
6869
logcat { "PIR-WEB: PirWebSaveProfileMessageHandler: incomplete profile information" }
6970
jsMessaging.sendResponse(
7071
jsMessage = jsMessage,
@@ -74,8 +75,9 @@ class PirWebSaveProfileMessageHandler @Inject constructor(
7475
}
7576

7677
appCoroutineScope.launch(dispatcherProvider.io()) {
77-
val profileQueries = pirWebOnboardingStateHolder.toProfileQueries(currentTimeProvider.localDateTimeNow().year)
78-
if (!repository.saveProfileQueries(profileQueries)) {
78+
val isProfileUpdateSuccess = handleProfileQueryUpdates()
79+
80+
if (!isProfileUpdateSuccess) {
7981
logcat { "PIR-WEB: PirWebSaveProfileMessageHandler: failed to save all user profiles" }
8082
jsMessaging.sendResponse(
8183
jsMessage = jsMessage,
@@ -92,8 +94,66 @@ class PirWebSaveProfileMessageHandler @Inject constructor(
9294
// start the initial scan at this point as startScanAndOptOut message is not reliable
9395
startAndScheduleInitialScan()
9496

95-
pirWebOnboardingStateHolder.clear()
97+
pirWebProfileStateHolder.clear()
98+
}
99+
}
100+
101+
/**
102+
* Storing the profile queries is a bit more complex than just replacing them as we need to consider
103+
* that some profile queries might have already extracted profiles associated to them. In that case,
104+
* we cannot delete those profile queries but we need to mark them as deprecated instead.
105+
*
106+
* This is so that the opt-outs for those deprecated profiles can be completed.
107+
*
108+
* https://app.asana.com/1/137249556945/project/481882893211075/task/1211369193255074?focus=true
109+
*/
110+
private suspend fun handleProfileQueryUpdates(): Boolean {
111+
// profiles queries that already exist in the database
112+
// TODO consider moving the deprecated filtering to the DB layer when updating job handling for new profiles
113+
val existingProfileQueries = repository.getUserProfileQueries().filterNot { it.deprecated }
114+
115+
// new profile queries that are the result of user changes (editing names or addresses)
116+
val newProfileQueries = pirWebProfileStateHolder.toProfileQueries(currentTimeProvider.localDateTimeNow().year)
117+
118+
// the profile queries we need to create are the one that exist in the new ones but not in the database
119+
val profileQueriesToCreate = newProfileQueries.filter { newProfileQuery ->
120+
existingProfileQueries.none { existingProfileQuery ->
121+
// ignore ID when comparing as database profile queries will have an actual id
122+
newProfileQuery == existingProfileQuery.copy(id = 0)
123+
}
96124
}
125+
126+
// the ones that we need to remove are the ones that exist in the database but not in the new ones
127+
val profileQueriesToRemove = existingProfileQueries.filter { existingProfileQuery ->
128+
newProfileQueries.none { newProfileQuery ->
129+
// ignore ID when comparing as database profile queries will have an actual id
130+
newProfileQuery == existingProfileQuery.copy(id = 0)
131+
}
132+
}.toMutableList()
133+
134+
// if profile query has extracted profiles associated to it, do not delete it but mark it as deprecated
135+
val profileQueriesToUpdate = mutableListOf<ProfileQuery>()
136+
val extractedProfileQueryIds = repository.getAllExtractedProfiles().map { it.profileQueryId }.toSet()
137+
profileQueriesToRemove.removeAll { profileQueryToRemove ->
138+
if (profileQueryToRemove.id in extractedProfileQueryIds) {
139+
profileQueriesToUpdate.add(profileQueryToRemove.copy(deprecated = true))
140+
true
141+
} else {
142+
false
143+
}
144+
}
145+
146+
if (profileQueriesToCreate.isEmpty() && profileQueriesToUpdate.isEmpty() && profileQueriesToRemove.isEmpty()) {
147+
// nothing to do
148+
return true
149+
}
150+
151+
// store the changes in a single transaction to ensure data consistency
152+
return repository.updateProfileQueries(
153+
profileQueriesToAdd = profileQueriesToCreate,
154+
profileQueriesToUpdate = profileQueriesToUpdate,
155+
profileQueryIdsToDelete = profileQueriesToRemove.map { it.id },
156+
)
97157
}
98158

99159
private fun startAndScheduleInitialScan() {

pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebSetAddressAtIndexInCurrentUserProfileMessageHandler.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import com.duckduckgo.js.messaging.api.JsMessaging
2323
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages
2424
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageRequest
2525
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse
26-
import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder
26+
import com.duckduckgo.pir.impl.dashboard.state.PirWebProfileStateHolder
2727
import com.squareup.anvil.annotations.ContributesMultibinding
2828
import logcat.logcat
2929
import javax.inject.Inject
@@ -33,7 +33,7 @@ import javax.inject.Inject
3333
boundType = PirWebJsMessageHandler::class,
3434
)
3535
class PirWebSetAddressAtIndexInCurrentUserProfileMessageHandler @Inject constructor(
36-
private val pirWebOnboardingStateHolder: PirWebOnboardingStateHolder,
36+
private val pirWebProfileStateHolder: PirWebProfileStateHolder,
3737
) : PirWebJsMessageHandler() {
3838

3939
override val message: PirDashboardWebMessages = PirDashboardWebMessages.SET_ADDRESS_AT_INDEX_IN_CURRENT_USER_PROFILE
@@ -62,7 +62,7 @@ class PirWebSetAddressAtIndexInCurrentUserProfileMessageHandler @Inject construc
6262
}
6363

6464
// attempting to add a duplicate address should return success=false
65-
if (!pirWebOnboardingStateHolder.setAddressAtIndex(
65+
if (!pirWebProfileStateHolder.setAddressAtIndex(
6666
index = index,
6767
city = city,
6868
state = state,

pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebSetBirthYearForCurrentUserProfile.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import com.duckduckgo.js.messaging.api.JsMessaging
2323
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages
2424
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageRequest
2525
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse
26-
import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder
26+
import com.duckduckgo.pir.impl.dashboard.state.PirWebProfileStateHolder
2727
import com.squareup.anvil.annotations.ContributesMultibinding
2828
import logcat.logcat
2929
import javax.inject.Inject
@@ -36,7 +36,7 @@ import javax.inject.Inject
3636
boundType = PirWebJsMessageHandler::class,
3737
)
3838
class PirWebSetBirthYearForCurrentUserProfile @Inject constructor(
39-
private val pirWebOnboardingStateHolder: PirWebOnboardingStateHolder,
39+
private val pirWebProfileStateHolder: PirWebProfileStateHolder,
4040
) : PirWebJsMessageHandler() {
4141

4242
override val message = PirDashboardWebMessages.SET_BIRTH_YEAR_FOR_CURRENT_USER_PROFILE
@@ -53,7 +53,7 @@ class PirWebSetBirthYearForCurrentUserProfile @Inject constructor(
5353
)?.year ?: 0
5454

5555
// store the new birth year in the current user profile
56-
pirWebOnboardingStateHolder.setBirthYear(birthYear)
56+
pirWebProfileStateHolder.setBirthYear(birthYear)
5757

5858
jsMessaging.sendResponse(
5959
jsMessage = jsMessage,

pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebSetNameAtIndexInCurrentUserProfileMessageHandler.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import com.duckduckgo.js.messaging.api.JsMessaging
2323
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages
2424
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageRequest
2525
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse
26-
import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder
26+
import com.duckduckgo.pir.impl.dashboard.state.PirWebProfileStateHolder
2727
import com.squareup.anvil.annotations.ContributesMultibinding
2828
import logcat.logcat
2929
import javax.inject.Inject
@@ -33,7 +33,7 @@ import javax.inject.Inject
3333
boundType = PirWebJsMessageHandler::class,
3434
)
3535
class PirWebSetNameAtIndexInCurrentUserProfileMessageHandler @Inject constructor(
36-
private val pirWebOnboardingStateHolder: PirWebOnboardingStateHolder,
36+
private val pirWebProfileStateHolder: PirWebProfileStateHolder,
3737
) : PirWebJsMessageHandler() {
3838

3939
override val message: PirDashboardWebMessages =
@@ -64,7 +64,7 @@ class PirWebSetNameAtIndexInCurrentUserProfileMessageHandler @Inject constructor
6464
}
6565

6666
// attempting to add a duplicate address should return success=false
67-
if (!pirWebOnboardingStateHolder.setNameAtIndex(
67+
if (!pirWebProfileStateHolder.setNameAtIndex(
6868
index = index,
6969
firstName = firstName,
7070
middleName = middleName,

0 commit comments

Comments
 (0)