Skip to content

Commit 84412ad

Browse files
authored
Merge pull request #15061 from woocommerce/issue/WOOMOB-1462-fix-cached-removed-booking-issue
[Bookings] Refactor caching strategy to handle stale data cleanup for all filters
2 parents df0c7cd + 6e45b92 commit 84412ad

File tree

6 files changed

+85
-82
lines changed

6 files changed

+85
-82
lines changed

libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/bookings/BookingDto.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ data class BookingDto(
2121
@SerializedName("parent_id") val parentId: Long,
2222
@SerializedName("person_counts") val personCounts: List<Int>?,
2323
@SerializedName("local_timezone") val localTimezone: String,
24-
@SerializedName("attendance_status") val attendanceStatus: String = "",
24+
@SerializedName("attendance_status") val attendanceStatus: String?,
2525
@SerializedName("note") val note: String? = null,
2626
)

libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/bookings/BookingDtoMapper.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ internal class BookingDtoMapper @Inject constructor(
4949
parentId = parentId,
5050
personCounts = personCounts?.map { it.toLong() },
5151
localTimezone = localTimezone,
52-
attendanceStatus = BookingEntity.AttendanceStatus.fromKey(attendanceStatus),
52+
attendanceStatus = BookingEntity.AttendanceStatus.fromKey(attendanceStatus.orEmpty()),
5353
note = note.orEmpty(),
5454
order = orderEntity?.toBookingOrderInfo(orderItemId) ?: BookingOrderInfo(
5555
productInfo = productsDao.getProduct(localSiteId.value, productId)?.let {

libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/bookings/BookingsStore.kt

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import org.wordpress.android.fluxc.store.WCOrderStore
1515
import org.wordpress.android.fluxc.tools.CoroutineEngine
1616
import org.wordpress.android.fluxc.utils.HeadersParser
1717
import org.wordpress.android.util.AppLog
18-
import java.time.Clock
1918
import javax.inject.Inject
2019
import javax.inject.Singleton
2120

@@ -27,7 +26,6 @@ class BookingsStore @Inject internal constructor(
2726
private val bookingDtoMapper: BookingDtoMapper,
2827
private val headersParser: HeadersParser,
2928
private val coroutineEngine: CoroutineEngine,
30-
private val clock: Clock,
3129
) {
3230
suspend fun fetchBookings(
3331
site: SiteModel,
@@ -56,31 +54,23 @@ class BookingsStore @Inject internal constructor(
5654
)
5755
}
5856
}
59-
// Clear existing bookings when fetching the first page.
60-
// If filters are applied, only clear entries that match the applied filters,
61-
// otherwise (no filters) clear all entries for the site.
62-
if (page == 1 && query.isNullOrEmpty()) {
63-
when {
64-
filters == BookingFilters.EMPTY -> {
65-
bookingsDao.replaceAllForSite(site.localId(), entities)
66-
}
6757

68-
filters.dateRange != null && filters.isTodayOrUpcoming -> {
69-
bookingsDao.cleanAndUpsertBookings(
70-
site.localId(),
71-
filters.dateRange,
72-
entities
73-
)
74-
}
58+
when {
59+
page == 1 && query.isNullOrEmpty() && filters == BookingFilters.EMPTY -> {
60+
// Refreshing with no filters applied: perform a bulk delete.
61+
bookingsDao.replaceAllForSite(site.localId(), entities)
62+
}
7563

76-
else -> {
77-
// For any other filters, avoid deletions to prevent removing unrelated cached items
78-
bookingsDao.insertOrReplace(entities)
79-
}
64+
page == 1 && query.isNullOrEmpty() -> {
65+
// Refreshing with filters applied: selectively remove only the stale entries.
66+
bookingsDao.cleanAndUpsertBookings(site.localId(), filters, entities)
67+
}
68+
69+
else -> {
70+
bookingsDao.upsert(entities)
8071
}
81-
} else {
82-
bookingsDao.insertOrReplace(entities)
8372
}
73+
8474
val totalPages = headersParser.getTotalPages(response.headers)
8575
// Determine if we can load more from the total pages header if available, otherwise
8676
// infer it from the number of items returned
@@ -98,13 +88,6 @@ class BookingsStore @Inject internal constructor(
9888
}
9989
}
10090

101-
private val BookingFilters.isTodayOrUpcoming: Boolean
102-
get() {
103-
val today = BookingsDateRangePresets.today(clock)
104-
val upcoming = BookingsDateRangePresets.upcoming(clock)
105-
return (dateRange == today || dateRange == upcoming) && enabledFiltersCount == 1
106-
}
107-
10891
fun observeBookings(
10992
site: SiteModel,
11093
limit: Int? = null,
@@ -150,7 +133,7 @@ class BookingsStore @Inject internal constructor(
150133
orderEntity = orderResult?.model,
151134
)
152135
}
153-
bookingsDao.insertOrReplace(listOf(entity))
136+
bookingsDao.upsert(listOf(entity))
154137
WooResult(entity)
155138
}
156139

@@ -186,7 +169,7 @@ class BookingsStore @Inject internal constructor(
186169
val entity = with(bookingDtoMapper) {
187170
response.result.toEntity(site.localId())
188171
}
189-
bookingsDao.insertOrReplace(entity)
172+
bookingsDao.upsert(entity)
190173
WooResult(entity)
191174
}
192175

@@ -230,7 +213,7 @@ class BookingsStore @Inject internal constructor(
230213
if (updatedBookingEntity == null) {
231214
return@withDefaultContext WooResult(WooError(GENERIC_ERROR, UNKNOWN))
232215
} else {
233-
bookingsDao.insertOrReplace(updatedBookingEntity)
216+
bookingsDao.upsert(updatedBookingEntity)
234217
return@withDefaultContext WooResult(updatedBookingEntity)
235218
}
236219
}

libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/BookingsDao.kt

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import androidx.room.Transaction
88
import kotlinx.coroutines.flow.Flow
99
import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId
1010
import org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings.BookingFilters
11-
import org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings.BookingsFilterOption
1211
import org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings.BookingsOrderOption
1312
import org.wordpress.android.fluxc.persistence.entity.BookingEntity
1413
import org.wordpress.android.fluxc.persistence.entity.BookingResourceEntity
@@ -57,18 +56,18 @@ interface BookingsDao {
5756
fun observeBookingsCount(localSiteId: LocalId): Flow<Long>
5857

5958
@Insert(onConflict = OnConflictStrategy.REPLACE)
60-
suspend fun insertOrReplace(entity: BookingEntity): Long
59+
suspend fun upsert(entity: BookingEntity): Long
6160

6261
@Insert(onConflict = OnConflictStrategy.REPLACE)
63-
suspend fun insertOrReplace(entities: List<BookingEntity>)
62+
suspend fun upsert(entities: List<BookingEntity>)
6463

6564
@Query("DELETE FROM Bookings WHERE localSiteId = :localSiteId")
6665
suspend fun deleteAllForSite(localSiteId: LocalId)
6766

6867
@Transaction
6968
suspend fun replaceAllForSite(siteId: LocalId, entities: List<BookingEntity>) {
7069
deleteAllForSite(siteId)
71-
insertOrReplace(entities)
70+
upsert(entities)
7271
}
7372

7473
@Suppress("LongParameterList")
@@ -78,46 +77,69 @@ interface BookingsDao {
7877
WHERE localSiteId = :localSiteId
7978
AND (:startDateBefore IS NULL OR start <= :startDateBefore)
8079
AND (:startDateAfter IS NULL OR start >= :startDateAfter)
81-
AND ((:idsSize = 0) OR id NOT IN (:ids))
80+
AND (:customerId IS NULL OR customerId = :customerId)
81+
AND ((:attendanceStatusesSize = 0) OR attendanceStatus IN (:attendanceStatuses))
82+
AND ((:resourceIdsSize = 0) OR resourceId IN (:resourceIds))
83+
AND ((:productIdsSize = 0) OR productId IN (:productIds))
84+
AND ((:keepIdsSize = 0) OR id NOT IN (:keepIds))
8285
"""
8386
)
84-
suspend fun deleteForSiteWithDateRangeFilter(
87+
suspend fun deleteStaleBookings(
8588
localSiteId: LocalId,
8689
startDateBefore: Long?,
8790
startDateAfter: Long?,
88-
ids: List<Long>,
89-
idsSize: Int,
91+
customerId: Long?,
92+
resourceIds: List<Long>,
93+
resourceIdsSize: Int,
94+
attendanceStatuses: List<String>,
95+
attendanceStatusesSize: Int,
96+
productIds: List<Long>,
97+
productIdsSize: Int,
98+
keepIds: List<Long>,
99+
keepIdsSize: Int,
90100
)
91101

92-
private suspend fun deleteForSiteWithDateRangeFilter(
102+
private suspend fun deleteStaleBookings(
93103
localSiteId: LocalId,
94-
dateRange: BookingsFilterOption.DateRange,
95-
ids: List<Long>
104+
filters: BookingFilters,
105+
keepIds: List<Long>
96106
) {
97-
deleteForSiteWithDateRangeFilter(
107+
val resourceIdsKeySet = filters.teamMembers.values.map { it.value }
108+
val attendanceStatusKeySet = filters.attendanceStatuses.values.map { it.key }
109+
val productIds = filters.serviceEvents.values.map { it.productId }
110+
111+
deleteStaleBookings(
98112
localSiteId = localSiteId,
99-
startDateBefore = dateRange.before?.epochSecond,
100-
startDateAfter = dateRange.after?.epochSecond,
101-
ids = ids,
102-
idsSize = ids.size,
113+
startDateBefore = filters.dateRange.before?.epochSecond,
114+
startDateAfter = filters.dateRange.after?.epochSecond,
115+
customerId = filters.customer?.customerId,
116+
resourceIds = resourceIdsKeySet.toList(),
117+
resourceIdsSize = resourceIdsKeySet.size,
118+
attendanceStatuses = attendanceStatusKeySet.toList(),
119+
attendanceStatusesSize = attendanceStatusKeySet.size,
120+
productIds = productIds,
121+
productIdsSize = productIds.size,
122+
keepIds = keepIds,
123+
keepIdsSize = keepIds.size
103124
)
104125
}
105126

106127
/**
107-
* Delete Booking entities that are not present in the new list and then insert the new entities
128+
* Delete Booking entities that match the filters but are not present in the new list,
129+
* and then insert the new entities.
108130
*/
109131
@Transaction
110132
suspend fun cleanAndUpsertBookings(
111133
localSiteId: LocalId,
112-
dateRange: BookingsFilterOption.DateRange,
134+
filters: BookingFilters,
113135
entities: List<BookingEntity>,
114136
) {
115-
deleteForSiteWithDateRangeFilter(
137+
deleteStaleBookings(
116138
localSiteId = localSiteId,
117-
dateRange = dateRange,
118-
ids = entities.map { it.id.value },
139+
filters = filters,
140+
keepIds = entities.map { it.id.value },
119141
)
120-
insertOrReplace(entities)
142+
upsert(entities)
121143
}
122144

123145
fun observeBookings(
@@ -161,17 +183,17 @@ interface BookingsDao {
161183
suspend fun getResource(localSiteId: LocalId, resourceId: Long): BookingResourceEntity?
162184

163185
@Insert(onConflict = OnConflictStrategy.REPLACE)
164-
suspend fun insertOrReplace(resource: BookingResourceEntity): Long
186+
suspend fun upsert(resource: BookingResourceEntity): Long
165187

166188
@Insert(onConflict = OnConflictStrategy.REPLACE)
167-
suspend fun insertOrReplaceResources(resources: List<BookingResourceEntity>)
189+
suspend fun upsertResources(resources: List<BookingResourceEntity>)
168190

169191
@Query("DELETE FROM BookingResources WHERE localSiteId = :localSiteId")
170192
suspend fun deleteAllResourcesForSite(localSiteId: LocalId)
171193

172194
@Transaction
173195
suspend fun replaceAllResourcesForSite(siteId: LocalId, resources: List<BookingResourceEntity>) {
174196
deleteAllResourcesForSite(siteId)
175-
insertOrReplaceResources(resources)
197+
upsertResources(resources)
176198
}
177199
}

libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/network/rest/wpcom/wc/bookings/BookingDtoMapperTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,8 @@ class BookingDtoMapperTest {
270270
orderItemId = orderItemId,
271271
parentId = parentId,
272272
personCounts = personCounts,
273-
localTimezone = localTimezone
273+
localTimezone = localTimezone,
274+
attendanceStatus = null,
274275
)
275276

276277
private fun createSampleOrderEntity() = OrderEntity(

0 commit comments

Comments
 (0)