-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Strip product metadata #2756
Strip product metadata #2756
Changes from all commits
bed1545
64bd5fe
6630e81
ef77cfe
db3112a
a4e1737
9fe02e0
03dc8f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package org.wordpress.android.fluxc.model | ||
|
||
import com.google.gson.Gson | ||
import com.google.gson.JsonArray | ||
import com.google.gson.JsonObject | ||
import org.wordpress.android.fluxc.model.WCProductModel.AddOnsMetadataKeys | ||
import org.wordpress.android.fluxc.model.WCProductModel.QuantityRulesMetadataKeys | ||
import org.wordpress.android.fluxc.utils.EMPTY_JSON_ARRAY | ||
import org.wordpress.android.fluxc.utils.isElementNullOrEmpty | ||
import javax.inject.Inject | ||
|
||
class StripProductMetaData @Inject internal constructor(private val gson: Gson) { | ||
operator fun invoke(metadata: String?): String { | ||
if (metadata.isNullOrEmpty()) return EMPTY_JSON_ARRAY | ||
|
||
return gson.fromJson(metadata, JsonArray::class.java) | ||
.mapNotNull { it as? JsonObject } | ||
.asSequence() | ||
.filter { jsonObject -> | ||
val isNullOrEmpty = jsonObject[WCMetaData.VALUE].isElementNullOrEmpty() | ||
jsonObject[WCMetaData.KEY]?.asString.orEmpty() in SUPPORTED_KEYS && isNullOrEmpty.not() | ||
}.toList() | ||
.takeIf { it.isNotEmpty() } | ||
?.let { gson.toJson(it) } ?: EMPTY_JSON_ARRAY | ||
} | ||
|
||
companion object { | ||
val SUPPORTED_KEYS: Set<String> = buildSet { | ||
add(AddOnsMetadataKeys.ADDONS_METADATA_KEY) | ||
addAll(QuantityRulesMetadataKeys.ALL_KEYS) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ import org.wordpress.android.fluxc.generated.endpoint.WPAPI | |
import org.wordpress.android.fluxc.generated.endpoint.WPCOMREST | ||
import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId | ||
import org.wordpress.android.fluxc.model.SiteModel | ||
import org.wordpress.android.fluxc.model.StripProductMetaData | ||
import org.wordpress.android.fluxc.model.StripProductVariationMetaData | ||
import org.wordpress.android.fluxc.model.WCProductCategoryModel | ||
import org.wordpress.android.fluxc.model.WCProductImageModel | ||
|
@@ -82,6 +83,7 @@ class ProductRestClient @Inject constructor( | |
private val wooNetwork: WooNetwork, | ||
private val wpComNetwork: WPComNetwork, | ||
private val coroutineEngine: CoroutineEngine, | ||
private val stripProductMetaData: StripProductMetaData, | ||
private val stripProductVariationMetaData: StripProductVariationMetaData | ||
) { | ||
/** | ||
|
@@ -291,6 +293,7 @@ class ProductRestClient @Inject constructor( | |
response.data?.let { | ||
val newModel = it.asProductModel().apply { | ||
localSiteId = site.id | ||
metadata = stripProductMetaData(metadata) | ||
} | ||
RemoteProductPayload(newModel, site) | ||
} ?: RemoteProductPayload( | ||
|
@@ -410,7 +413,10 @@ class ProductRestClient @Inject constructor( | |
when (response) { | ||
is WPAPIResponse.Success -> { | ||
val productModels = response.data?.map { | ||
it.asProductModel().apply { localSiteId = site.id } | ||
it.asProductModel().apply { | ||
localSiteId = site.id | ||
metadata = stripProductMetaData(metadata) | ||
} | ||
}.orEmpty() | ||
|
||
val loadedMore = offset > 0 | ||
|
@@ -520,7 +526,10 @@ class ProductRestClient @Inject constructor( | |
return response.toWooPayload { products -> | ||
products.map { | ||
it.asProductModel() | ||
.apply { localSiteId = site.id } | ||
.apply { | ||
localSiteId = site.id | ||
metadata = stripProductMetaData(metadata) | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -876,6 +885,7 @@ class ProductRestClient @Inject constructor( | |
response.data?.let { | ||
val newModel = it.asProductModel().apply { | ||
localSiteId = site.id | ||
metadata = stripProductMetaData(metadata) | ||
} | ||
val payload = RemoteUpdateProductPayload(site, newModel) | ||
dispatcher.dispatch(WCProductActionBuilder.newUpdatedProductAction(payload)) | ||
|
@@ -1189,6 +1199,7 @@ class ProductRestClient @Inject constructor( | |
response.data?.let { | ||
val newModel = it.asProductModel().apply { | ||
localSiteId = site.id | ||
metadata = stripProductMetaData(metadata) | ||
} | ||
val payload = RemoteUpdateProductImagesPayload(site, newModel) | ||
dispatcher.dispatch(WCProductActionBuilder.newUpdatedProductImagesAction(payload)) | ||
|
@@ -1486,6 +1497,7 @@ class ProductRestClient @Inject constructor( | |
val newModel = product.asProductModel().apply { | ||
id = product.id?.toInt() ?: 0 | ||
localSiteId = site.id | ||
metadata = stripProductMetaData(metadata) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I I was wondering if maybe the But I also understand that since the stripping happens through a use case, the dependency injection will get weird and we would have to create the use case object manually, so if you think it's not worth the effort, it's OK too. I'm only concerned with making sure that we won't easily forget to strip that metadata in future changes. WDYT? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Good point @ThomazFB. ☝️
That was also why I avoided adding the use case manually. I decided to go this way because I saw that we also had this line There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! Thanks for that! |
||
} | ||
val payload = RemoteAddProductPayload(site, newModel) | ||
dispatcher.dispatch(WCProductActionBuilder.newAddedProductAction(payload)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package org.wordpress.android.fluxc.utils | ||
|
||
import com.google.gson.JsonElement | ||
|
||
const val EMPTY_JSON_ARRAY = "[]" | ||
|
||
fun JsonElement?.isElementNullOrEmpty(): Boolean { | ||
return this?.let { | ||
when{ | ||
this.isJsonObject -> this.asJsonObject.size() == 0 | ||
this.isJsonArray -> this.asJsonArray.size()== 0 | ||
this.isJsonPrimitive && this.asJsonPrimitive.isString -> this.asString.isEmpty() | ||
this.isJsonNull -> true | ||
else -> false | ||
} | ||
} ?: true | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
package org.wordpress.android.fluxc.network.rest.wpcom.wc.product | ||
|
||
import com.google.gson.Gson | ||
import com.google.gson.JsonArray | ||
import com.google.gson.JsonObject | ||
import org.assertj.core.api.Assertions | ||
import org.junit.Test | ||
import org.wordpress.android.fluxc.model.StripProductMetaData | ||
import org.wordpress.android.fluxc.model.WCMetaData | ||
import org.wordpress.android.fluxc.utils.EMPTY_JSON_ARRAY | ||
|
||
class StripProductMetaDataTest { | ||
private val gson = Gson() | ||
private val sut = StripProductMetaData(gson) | ||
|
||
|
||
@Test | ||
fun `when metadata contains not supported keys, then NOT supported keys are stripped`() { | ||
val result = sut.invoke(notOnlySupportedMetadata) | ||
val jsonResult = gson.fromJson(result, JsonArray::class.java) | ||
|
||
jsonResult.map { it as? JsonObject }.forEach { jsonObject -> | ||
Assertions.assertThat(jsonObject).isNotNull | ||
Assertions.assertThat(jsonObject?.get(WCMetaData.KEY)?.asString) | ||
.isIn(StripProductMetaData.SUPPORTED_KEYS) | ||
} | ||
} | ||
|
||
@Test | ||
fun `when metadata contains a supported key with a NULL value, then strip the supported key`() { | ||
val supportedKey = StripProductMetaData.SUPPORTED_KEYS.first() | ||
val value: String? = null | ||
|
||
val metadata = getOneItemMetadata(supportedKey, value) | ||
val result = sut.invoke(metadata) | ||
|
||
Assertions.assertThat(result).isEqualTo(EMPTY_JSON_ARRAY) | ||
} | ||
|
||
@Test | ||
fun `when metadata contains a supported key with a EMPTY value, then strip the supported key`() { | ||
val supportedKey = StripProductMetaData.SUPPORTED_KEYS.first() | ||
val value = "" | ||
|
||
val metadata = getOneItemMetadata(supportedKey, value) | ||
val result = sut.invoke(metadata) | ||
|
||
Assertions.assertThat(result).isEqualTo(EMPTY_JSON_ARRAY) | ||
} | ||
|
||
@Test | ||
fun `when metadata contains a supported key with a NOT EMPTY value, then value is kept`() { | ||
val supportedKey = StripProductMetaData.SUPPORTED_KEYS.first() | ||
val value = "valid" | ||
|
||
val metadata = getOneItemMetadata(supportedKey, value) | ||
val result = sut.invoke(metadata) | ||
|
||
val jsonResult = gson.fromJson(result, JsonArray::class.java) | ||
val resultItem = jsonResult[0] as JsonObject | ||
Assertions.assertThat(result).isNotNull | ||
Assertions.assertThat(resultItem[WCMetaData.KEY].asString).isEqualTo(supportedKey) | ||
Assertions.assertThat(resultItem[WCMetaData.VALUE].asString).isEqualTo(value) | ||
} | ||
|
||
@Test | ||
fun `when metadata is null, then empty array is return`() { | ||
val result = sut.invoke(null) | ||
Assertions.assertThat(result).isEqualTo(EMPTY_JSON_ARRAY) | ||
} | ||
|
||
private fun getOneItemMetadata(itemKey: String, itemValue: String?): String { | ||
val item = JsonObject().apply { | ||
addProperty(WCMetaData.KEY, itemKey) | ||
itemValue?.let { | ||
addProperty(WCMetaData.VALUE, it) | ||
} ?: add(WCMetaData.VALUE, null) | ||
} | ||
val jsonArray = JsonArray().apply { | ||
add(item) | ||
} | ||
|
||
return gson.toJson(jsonArray) | ||
} | ||
|
||
private val notOnlySupportedMetadata = """ | ||
[ | ||
{ | ||
"id": 11749, | ||
"key": "_wpcom_is_markdown", | ||
"value": "1" | ||
}, | ||
{ | ||
"id": 11750, | ||
"key": "_last_editor_used_jetpack", | ||
"value": "classic-editor" | ||
}, | ||
{ | ||
"id": 11754, | ||
"key": "_product_addons", | ||
"value": [{ | ||
"id": 11773, | ||
"key": "_wc_gla_sync_status", | ||
"value": "synced" | ||
}, | ||
{ | ||
"id": 11774, | ||
"key": "group_of_quantity", | ||
"value": "" | ||
}] | ||
}, | ||
{ | ||
"id": 11755, | ||
"key": "_product_addons_exclude_global", | ||
"value": "1" | ||
}, | ||
{ | ||
"id": 11772, | ||
"key": "_wc_gla_visibility", | ||
"value": "sync-and-show" | ||
}, | ||
{ | ||
"id": 11773, | ||
"key": "_wc_gla_sync_status", | ||
"value": "synced" | ||
}, | ||
{ | ||
"id": 11774, | ||
"key": "group_of_quantity", | ||
"value": "" | ||
}, | ||
{ | ||
"id": 11775, | ||
"key": "minimum_allowed_quantity", | ||
"value": "" | ||
}, | ||
{ | ||
"id": 11776, | ||
"key": "maximum_allowed_quantity", | ||
"value": "" | ||
}, | ||
{ | ||
"id": 11777, | ||
"key": "minmax_do_not_count", | ||
"value": "no" | ||
}, | ||
{ | ||
"id": 11778, | ||
"key": "minmax_cart_exclude", | ||
"value": "no" | ||
}, | ||
{ | ||
"id": 11779, | ||
"key": "minmax_category_group_of_exclude", | ||
"value": "no" | ||
}, | ||
{ | ||
"id": 11780, | ||
"key": "_wcsatt_disabled", | ||
"value": "yes" | ||
}, | ||
{ | ||
"id": 11781, | ||
"key": "_subscription_one_time_shipping", | ||
"value": "no" | ||
}, | ||
{ | ||
"id": 11782, | ||
"key": "_wcsatt_force_subscription", | ||
"value": "no" | ||
}, | ||
{ | ||
"id": 11785, | ||
"key": "_subscription_downloads_ids", | ||
"value": "" | ||
}, | ||
{ | ||
"id": 11786, | ||
"key": "minimum_allowed_quantity", | ||
"value": "40" | ||
}, | ||
{ | ||
"id": 11787, | ||
"key": "_wc_pre_orders_fee", | ||
"value": "" | ||
}, | ||
{ | ||
"id": 11788, | ||
"key": "_wpas_done_all", | ||
"value": "1" | ||
}, | ||
{ | ||
"id": 11909, | ||
"key": "_wc_gla_mc_status", | ||
"value": "not_synced" | ||
}, | ||
{ | ||
"id": 12406, | ||
"key": "_wc_gla_synced_at", | ||
"value": "1686193267" | ||
}, | ||
{ | ||
"id": 12407, | ||
"key": "_wc_gla_google_ids", | ||
"value": { | ||
"US": "online:en:US:gla_418" | ||
} | ||
}, | ||
{ | ||
"key": "_satt_data", | ||
"value": { | ||
"subscription_schemes": [] | ||
} | ||
} | ||
] | ||
""".trimIndent() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just out of curiosity, I remember reviewing a code like this in the past. Did we implement a metadata strip before that got removed? I'm not finding anything like this in the trunk code base 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but it was for product variations, not products. This is the strip use case class
StripProductVariationMetaData