Skip to content
This repository was archived by the owner on Feb 4, 2025. It is now read-only.

Commit 7475ad2

Browse files
Merge pull request #3106 from wordpress-mobile/woo/subscriptions-off-product
[Woo] Extract subscriptions outside of WCProductModel table
2 parents 7ad6ce4 + 58e794d commit 7475ad2

File tree

14 files changed

+199
-102
lines changed

14 files changed

+199
-102
lines changed

example/src/main/java/org/wordpress/android/fluxc/example/ui/metadata/CustomFieldsViewModel.kt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.update
1313
import kotlinx.coroutines.launch
1414
import org.wordpress.android.fluxc.model.SiteModel
1515
import org.wordpress.android.fluxc.model.metadata.MetaDataParentItemType
16+
import org.wordpress.android.fluxc.model.metadata.MetadataChanges
1617
import org.wordpress.android.fluxc.model.metadata.UpdateMetadataRequest
1718
import org.wordpress.android.fluxc.model.metadata.WCMetaData
1819
import org.wordpress.android.fluxc.store.MetaDataStore
@@ -43,6 +44,7 @@ class CustomFieldsViewModel(
4344
loadCustomFields()
4445
}
4546

47+
@Suppress("LongMethod")
4648
private fun observeLoadingState() {
4749
val customFields = combine(
4850
metaDataStore.observeDisplayableMetaData(
@@ -63,8 +65,8 @@ class CustomFieldsViewModel(
6365
customFields,
6466
pendingUpdateRequest.map {
6567
it.insertedMetadata.isNotEmpty() ||
66-
it.updatedMetadata.isNotEmpty() ||
67-
it.deletedMetadataIds.isNotEmpty()
68+
it.updatedMetadata.isNotEmpty() ||
69+
it.deletedMetadataIds.isNotEmpty()
6870
}
6971
) { loadingState, metaData, hasChanges ->
7072
when (loadingState) {
@@ -74,21 +76,27 @@ class CustomFieldsViewModel(
7476
onDelete = { field ->
7577
pendingUpdateRequest.update {
7678
it.copy(
77-
deletedMetadataIds = it.deletedMetadataIds + field.id
79+
metadataChanges = it.metadataChanges.copy(
80+
deletedMetadataIds = it.deletedMetadataIds + field.id
81+
)
7882
)
7983
}
8084
},
8185
onEdit = { field ->
8286
pendingUpdateRequest.update {
8387
it.copy(
84-
updatedMetadata = it.updatedMetadata + field
88+
metadataChanges = it.metadataChanges.copy(
89+
updatedMetadata = it.updatedMetadata + field
90+
)
8591
)
8692
}
8793
},
8894
onAdd = { field ->
8995
pendingUpdateRequest.update {
9096
it.copy(
91-
insertedMetadata = it.insertedMetadata + field
97+
metadataChanges = it.metadataChanges.copy(
98+
insertedMetadata = it.insertedMetadata + field
99+
)
92100
)
93101
}
94102
},
@@ -134,9 +142,7 @@ class CustomFieldsViewModel(
134142
} else {
135143
pendingUpdateRequest.update {
136144
it.copy(
137-
insertedMetadata = emptyList(),
138-
updatedMetadata = emptyList(),
139-
deletedMetadataIds = emptyList()
145+
metadataChanges = MetadataChanges()
140146
)
141147
}
142148
loadingState.value = LoadingState.Loaded

example/src/test/java/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductDtoMapperTest.kt

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
11
package org.wordpress.android.fluxc.network.rest.wpcom.wc.product
22

3-
import com.google.gson.Gson
43
import org.assertj.core.api.Assertions.assertThat
54
import org.junit.Test
65
import org.mockito.kotlin.any
76
import org.mockito.kotlin.doAnswer
87
import org.mockito.kotlin.mock
98
import org.wordpress.android.fluxc.JsonLoaderUtils.jsonFileAs
109
import org.wordpress.android.fluxc.model.LocalOrRemoteId
11-
import org.wordpress.android.fluxc.model.metadata.WCMetaData
1210
import org.wordpress.android.fluxc.model.addons.RemoteAddonDto
1311
import org.wordpress.android.fluxc.model.addons.RemoteAddonDto.RemotePriceType.FlatFee
1412
import org.wordpress.android.fluxc.model.addons.RemoteAddonDto.RemoteRestrictionsType.AnyText
1513
import org.wordpress.android.fluxc.model.addons.RemoteAddonDto.RemoteType.Checkbox
14+
import org.wordpress.android.fluxc.model.metadata.WCMetaData
1615
import org.wordpress.android.fluxc.model.metadata.get
1716
import kotlin.test.fail
1817

1918
class ProductDtoMapperTest {
2019
private val productDtoMapper = ProductDtoMapper(
21-
gson = Gson(),
2220
stripProductMetaData = mock {
2321
on { invoke(any()) } doAnswer {
2422
@Suppress("UNCHECKED_CAST")
@@ -79,18 +77,6 @@ class ProductDtoMapperTest {
7977
assertThat(addonOptions).isNotEmpty
8078
}
8179

82-
@Test
83-
fun `Product metadata is serialized correctly`() {
84-
val productModelUnderTest =
85-
"wc/product-with-addons.json"
86-
.jsonFileAs(ProductApiResponse::class.java)
87-
?.let { productDtoMapper.mapToModel(LocalOrRemoteId.LocalId(0), it) }
88-
?.product
89-
90-
assertThat(productModelUnderTest).isNotNull
91-
assertThat(productModelUnderTest?.metadata).isNotNull
92-
}
93-
9480
@Test
9581
fun `Bundled product with max size is serialized correctly`() {
9682
val product = "wc/product-bundle-with-max-quantity.json"

example/src/test/java/org/wordpress/android/fluxc/wc/leaderboards/WCLeaderboardsTestFixtures.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ object WCLeaderboardsTestFixtures {
1919
origin = SiteModel.ORIGIN_XMLRPC
2020
}
2121
private val productDtoMapper = ProductDtoMapper(
22-
gson = Gson(),
2322
stripProductMetaData = mock {
2423
on { invoke(any()) } doAnswer {
2524
@Suppress("UNCHECKED_CAST")

fluxc/src/main/java/org/wordpress/android/fluxc/persistence/WellSqlConfig.kt

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ open class WellSqlConfig : DefaultWellConfig {
4141
annotation class AddOn
4242

4343
override fun getDbVersion(): Int {
44-
return 204
44+
return 205
4545
}
4646

4747
override fun getDbName(): String {
@@ -2053,6 +2053,66 @@ open class WellSqlConfig : DefaultWellConfig {
20532053
)
20542054
""".trimIndent())
20552055
}
2056+
2057+
204 -> migrateAddOn(ADDON_WOOCOMMERCE, version) {
2058+
// Drop the column METADATA from WCProductModel
2059+
// Since SQLite version varies across devices, we can't rely on the support of DROP COLUMN,
2060+
// So we'll use an intermediate table
2061+
db.execSQL("""
2062+
CREATE TABLE WCProductModel_temp (
2063+
_id INTEGER PRIMARY KEY AUTOINCREMENT,LOCAL_SITE_ID INTEGER,
2064+
REMOTE_PRODUCT_ID INTEGER,NAME TEXT NOT NULL,SLUG TEXT NOT NULL,PERMALINK TEXT NOT NULL,
2065+
DATE_CREATED TEXT NOT NULL,DATE_MODIFIED TEXT NOT NULL,TYPE TEXT NOT NULL,STATUS TEXT NOT NULL,
2066+
FEATURED INTEGER,CATALOG_VISIBILITY TEXT NOT NULL,DESCRIPTION TEXT NOT NULL,
2067+
SHORT_DESCRIPTION TEXT NOT NULL,SKU TEXT NOT NULL,PRICE TEXT NOT NULL,REGULAR_PRICE TEXT NOT NULL,
2068+
SALE_PRICE TEXT NOT NULL,ON_SALE INTEGER,TOTAL_SALES INTEGER,PURCHASABLE INTEGER,
2069+
DATE_ON_SALE_FROM TEXT NOT NULL,DATE_ON_SALE_TO TEXT NOT NULL,DATE_ON_SALE_FROM_GMT TEXT NOT NULL,
2070+
DATE_ON_SALE_TO_GMT TEXT NOT NULL,VIRTUAL INTEGER,DOWNLOADABLE INTEGER,DOWNLOAD_LIMIT INTEGER,
2071+
DOWNLOAD_EXPIRY INTEGER,SOLD_INDIVIDUALLY INTEGER,EXTERNAL_URL TEXT NOT NULL,BUTTON_TEXT TEXT NOT NULL,
2072+
TAX_STATUS TEXT NOT NULL,TAX_CLASS TEXT NOT NULL,MANAGE_STOCK INTEGER,STOCK_QUANTITY REAL,
2073+
STOCK_STATUS TEXT NOT NULL,BACKORDERS TEXT NOT NULL,BACKORDERS_ALLOWED INTEGER,BACKORDERED INTEGER,
2074+
SHIPPING_REQUIRED INTEGER,SHIPPING_TAXABLE INTEGER,SHIPPING_CLASS TEXT NOT NULL,SHIPPING_CLASS_ID INTEGER,
2075+
REVIEWS_ALLOWED INTEGER,AVERAGE_RATING TEXT NOT NULL,RATING_COUNT INTEGER,PARENT_ID INTEGER,
2076+
PURCHASE_NOTE TEXT NOT NULL,MENU_ORDER INTEGER,CATEGORIES TEXT NOT NULL,TAGS TEXT NOT NULL,
2077+
IMAGES TEXT NOT NULL,ATTRIBUTES TEXT NOT NULL,VARIATIONS TEXT NOT NULL,DOWNLOADS TEXT NOT NULL,
2078+
RELATED_IDS TEXT NOT NULL,CROSS_SELL_IDS TEXT NOT NULL,UPSELL_IDS TEXT NOT NULL,
2079+
GROUPED_PRODUCT_IDS TEXT NOT NULL,WEIGHT TEXT NOT NULL,LENGTH TEXT NOT NULL,WIDTH TEXT NOT NULL,
2080+
HEIGHT TEXT NOT NULL,BUNDLED_ITEMS TEXT NOT NULL,COMPOSITE_COMPONENTS TEXT NOT NULL,
2081+
SPECIAL_STOCK_STATUS TEXT NOT NULL,BUNDLE_MIN_SIZE REAL,BUNDLE_MAX_SIZE REAL,
2082+
MIN_ALLOWED_QUANTITY INTEGER,MAX_ALLOWED_QUANTITY INTEGER,GROUP_OF_QUANTITY INTEGER,
2083+
COMBINE_VARIATION_QUANTITIES INTEGER,PASSWORD TEXT,IS_SAMPLE_PRODUCT INTEGER
2084+
)
2085+
""".trimIndent())
2086+
2087+
db.execSQL("""
2088+
INSERT INTO WCProductModel_temp (
2089+
_id, LOCAL_SITE_ID, REMOTE_PRODUCT_ID, NAME, SLUG, PERMALINK, DATE_CREATED, DATE_MODIFIED, TYPE, STATUS, FEATURED,
2090+
CATALOG_VISIBILITY, DESCRIPTION, SHORT_DESCRIPTION, SKU, PRICE, REGULAR_PRICE, SALE_PRICE, ON_SALE, TOTAL_SALES,
2091+
PURCHASABLE, DATE_ON_SALE_FROM, DATE_ON_SALE_TO, DATE_ON_SALE_FROM_GMT, DATE_ON_SALE_TO_GMT, VIRTUAL, DOWNLOADABLE,
2092+
DOWNLOAD_LIMIT, DOWNLOAD_EXPIRY, SOLD_INDIVIDUALLY, EXTERNAL_URL, BUTTON_TEXT, TAX_STATUS, TAX_CLASS, MANAGE_STOCK,
2093+
STOCK_QUANTITY, STOCK_STATUS, BACKORDERS, BACKORDERS_ALLOWED, BACKORDERED, SHIPPING_REQUIRED, SHIPPING_TAXABLE,
2094+
SHIPPING_CLASS, SHIPPING_CLASS_ID, REVIEWS_ALLOWED, AVERAGE_RATING, RATING_COUNT, PARENT_ID, PURCHASE_NOTE, MENU_ORDER,
2095+
CATEGORIES, TAGS, IMAGES, ATTRIBUTES, VARIATIONS, DOWNLOADS, RELATED_IDS, CROSS_SELL_IDS, UPSELL_IDS, GROUPED_PRODUCT_IDS,
2096+
WEIGHT, LENGTH, WIDTH, HEIGHT, BUNDLED_ITEMS, COMPOSITE_COMPONENTS, SPECIAL_STOCK_STATUS, BUNDLE_MIN_SIZE, BUNDLE_MAX_SIZE,
2097+
MIN_ALLOWED_QUANTITY, MAX_ALLOWED_QUANTITY, GROUP_OF_QUANTITY, COMBINE_VARIATION_QUANTITIES, PASSWORD, IS_SAMPLE_PRODUCT
2098+
)
2099+
SELECT
2100+
_id, LOCAL_SITE_ID, REMOTE_PRODUCT_ID, NAME, SLUG, PERMALINK, DATE_CREATED, DATE_MODIFIED, TYPE, STATUS, FEATURED,
2101+
CATALOG_VISIBILITY, DESCRIPTION, SHORT_DESCRIPTION, SKU, PRICE, REGULAR_PRICE, SALE_PRICE, ON_SALE, TOTAL_SALES,
2102+
PURCHASABLE, DATE_ON_SALE_FROM, DATE_ON_SALE_TO, DATE_ON_SALE_FROM_GMT, DATE_ON_SALE_TO_GMT, VIRTUAL, DOWNLOADABLE,
2103+
DOWNLOAD_LIMIT, DOWNLOAD_EXPIRY, SOLD_INDIVIDUALLY, EXTERNAL_URL, BUTTON_TEXT, TAX_STATUS, TAX_CLASS, MANAGE_STOCK,
2104+
STOCK_QUANTITY, STOCK_STATUS, BACKORDERS, BACKORDERS_ALLOWED, BACKORDERED, SHIPPING_REQUIRED, SHIPPING_TAXABLE,
2105+
SHIPPING_CLASS, SHIPPING_CLASS_ID, REVIEWS_ALLOWED, AVERAGE_RATING, RATING_COUNT, PARENT_ID, PURCHASE_NOTE, MENU_ORDER,
2106+
CATEGORIES, TAGS, IMAGES, ATTRIBUTES, VARIATIONS, DOWNLOADS, RELATED_IDS, CROSS_SELL_IDS, UPSELL_IDS, GROUPED_PRODUCT_IDS,
2107+
WEIGHT, LENGTH, WIDTH, HEIGHT, BUNDLED_ITEMS, COMPOSITE_COMPONENTS, SPECIAL_STOCK_STATUS, BUNDLE_MIN_SIZE, BUNDLE_MAX_SIZE,
2108+
MIN_ALLOWED_QUANTITY, MAX_ALLOWED_QUANTITY, GROUP_OF_QUANTITY, COMBINE_VARIATION_QUANTITIES, PASSWORD, IS_SAMPLE_PRODUCT
2109+
FROM WCProductModel
2110+
""".trimIndent())
2111+
2112+
db.execSQL("DROP TABLE WCProductModel")
2113+
2114+
db.execSQL("ALTER TABLE WCProductModel_temp RENAME TO WCProductModel")
2115+
}
20562116
}
20572117
}
20582118
db.setTransactionSuccessful()

plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/WCProductModel.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,6 @@ data class WCProductModel(@PrimaryKey @Column private var id: Int = 0) : Identif
106106
@Column var width = ""
107107
@Column var height = ""
108108

109-
/**
110-
* This holds just the subscription keys, for the rest of product's metadata please check [ProductWithMetaData]
111-
*/
112-
@Column var metadata = ""
113-
114109
@Column var bundledItems = ""
115110
@Column var compositeComponents = ""
116111
@Column var specialStockStatus = ""
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.wordpress.android.fluxc.model.metadata
2+
3+
import com.google.gson.JsonArray
4+
import com.google.gson.JsonNull
5+
import com.google.gson.JsonObject
6+
7+
data class MetadataChanges(
8+
val insertedMetadata: List<WCMetaData> = emptyList(),
9+
val updatedMetadata: List<WCMetaData> = emptyList(),
10+
val deletedMetadataIds: List<Long> = emptyList(),
11+
) {
12+
init {
13+
// The ID of inserted metadata is ignored, so to ensure that there is no data loss here,
14+
// we require that all inserted metadata have an ID of 0.
15+
require(insertedMetadata.all { it.id == 0L }) {
16+
"Inserted metadata must have an ID of 0"
17+
}
18+
}
19+
20+
internal fun toJsonArray() = JsonArray().apply {
21+
insertedMetadata.forEach {
22+
add(
23+
JsonObject().apply {
24+
addProperty(WCMetaData.KEY, it.key)
25+
add(WCMetaData.VALUE, it.value.jsonValue)
26+
}
27+
)
28+
}
29+
updatedMetadata.forEach {
30+
add(it.toJson())
31+
}
32+
deletedMetadataIds.forEach {
33+
add(
34+
JsonObject().apply {
35+
addProperty(WCMetaData.ID, it)
36+
add(WCMetaData.VALUE, JsonNull.INSTANCE)
37+
}
38+
)
39+
}
40+
}
41+
}

plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/metadata/UpdateMetadataRequest.kt

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,26 @@ package org.wordpress.android.fluxc.model.metadata
33
data class UpdateMetadataRequest(
44
val parentItemId: Long,
55
val parentItemType: MetaDataParentItemType,
6-
val insertedMetadata: List<WCMetaData> = emptyList(),
7-
val updatedMetadata: List<WCMetaData> = emptyList(),
8-
val deletedMetadataIds: List<Long> = emptyList(),
6+
val metadataChanges: MetadataChanges,
97
) {
10-
init {
11-
// The ID of inserted metadata is ignored, so to ensure that there is no data loss here,
12-
// we require that all inserted metadata have an ID of 0.
13-
require(insertedMetadata.all { it.id == 0L }) {
14-
"Inserted metadata must have an ID of 0"
15-
}
16-
}
8+
val insertedMetadata: List<WCMetaData> get() = metadataChanges.insertedMetadata
9+
val updatedMetadata: List<WCMetaData> get() = metadataChanges.updatedMetadata
10+
val deletedMetadataIds: List<Long> get() = metadataChanges.deletedMetadataIds
11+
12+
constructor(
13+
parentItemId: Long,
14+
parentItemType: MetaDataParentItemType,
15+
insertedMetadata: List<WCMetaData> = emptyList(),
16+
updatedMetadata: List<WCMetaData> = emptyList(),
17+
deletedMetadataIds: List<Long> = emptyList(),
18+
) : this(
19+
parentItemId,
20+
parentItemType,
21+
MetadataChanges(
22+
insertedMetadata,
23+
updatedMetadata,
24+
deletedMetadataIds,
25+
)
26+
)
1727
}
28+

plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/metadata/WCMetaData.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ data class WCMetaData(
2525
val displayValue: WCMetaDataValue? = null
2626
) {
2727
constructor(id: Long, key: String, value: String) : this(id, key,
28-
WCMetaDataValue.fromRawString(value)
28+
WCMetaDataValue(value)
2929
)
3030

3131
/**

plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/metadata/WCMetaDataValue.kt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ sealed class WCMetaDataValue {
7878
}
7979

8080
companion object {
81+
operator fun invoke(value: String?): WCMetaDataValue {
82+
if (value == null) return StringValue(null)
83+
84+
return runCatching { JsonParser().parse(value) }
85+
.getOrElse { JsonPrimitive(value) }
86+
.let { fromJsonElement(it) }
87+
}
88+
operator fun invoke(value: Number): WCMetaDataValue = NumberValue(value)
89+
operator fun invoke(value: Boolean): WCMetaDataValue = BooleanValue(value)
90+
8191
internal fun fromJsonElement(element: JsonElement): WCMetaDataValue {
8292
return when {
8393
element.isJsonPrimitive -> {
@@ -95,10 +105,5 @@ sealed class WCMetaDataValue {
95105
else -> StringValue(element.toString())
96106
}
97107
}
98-
99-
fun fromRawString(value: String): WCMetaDataValue =
100-
runCatching { JsonParser().parse(value) }
101-
.getOrElse { JsonPrimitive(value) }
102-
.let { fromJsonElement(it) }
103108
}
104109
}

plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/metadata/MetaDataRestClient.kt

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package org.wordpress.android.fluxc.network.rest.wpcom.wc.metadata
22

3-
import com.google.gson.JsonArray
4-
import com.google.gson.JsonNull
53
import com.google.gson.JsonObject
64
import org.wordpress.android.fluxc.generated.endpoint.WOOCOMMERCE
75
import org.wordpress.android.fluxc.model.SiteModel
@@ -47,32 +45,11 @@ internal class MetaDataRestClient @Inject internal constructor(
4745
MetaDataParentItemType.PRODUCT -> WOOCOMMERCE.products.id(request.parentItemId).pathV3
4846
}
4947

50-
val metaDataJson = JsonArray()
51-
request.insertedMetadata.forEach {
52-
metaDataJson.add(
53-
JsonObject().apply {
54-
addProperty(WCMetaData.KEY, it.key)
55-
add(WCMetaData.VALUE, it.value.jsonValue)
56-
}
57-
)
58-
}
59-
request.updatedMetadata.forEach {
60-
metaDataJson.add(it.toJson())
61-
}
62-
request.deletedMetadataIds.forEach {
63-
metaDataJson.add(
64-
JsonObject().apply {
65-
addProperty(WCMetaData.ID, it)
66-
add(WCMetaData.VALUE, JsonNull.INSTANCE)
67-
}
68-
)
69-
}
70-
7148
val response = wooNetwork.executePostGsonRequest(
7249
site = site,
7350
path = path,
7451
body = mapOf(
75-
"meta_data" to metaDataJson,
52+
"meta_data" to request.metadataChanges.toJsonArray(),
7653
"_fields" to "meta_data"
7754
),
7855
clazz = JsonObject::class.java

0 commit comments

Comments
 (0)