Skip to content

Commit

Permalink
Merge pull request #2995 from wordpress-mobile/observe-products-count
Browse files Browse the repository at this point in the history
Utilities to improve observing products
  • Loading branch information
0nko authored Apr 26, 2024
2 parents c237f71 + 53b7883 commit 4d21f7e
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ open class WellSqlConfig : DefaultWellConfig {
annotation class AddOn

override fun getDbVersion(): Int {
return 200
return 201
}

override fun getDbName(): String {
Expand Down Expand Up @@ -2004,6 +2004,9 @@ open class WellSqlConfig : DefaultWellConfig {
199 -> migrate(version) {
db.execSQL("ALTER TABLE PostModel ADD DB_TIMESTAMP INTEGER")
}
200 -> migrate(version) {
db.execSQL("ALTER TABLE WCProductModel ADD IS_SAMPLE_PRODUCT BOOLEAN")
}
}
}
db.setTransactionSuccessful()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ 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.OtherKeys
import org.wordpress.android.fluxc.model.WCProductModel.QuantityRulesMetadataKeys
import org.wordpress.android.fluxc.model.WCProductModel.SubscriptionMetadataKeys
import org.wordpress.android.fluxc.utils.EMPTY_JSON_ARRAY
Expand All @@ -31,7 +30,6 @@ class StripProductMetaData @Inject internal constructor(private val gson: Gson)
add(AddOnsMetadataKeys.ADDONS_METADATA_KEY)
addAll(QuantityRulesMetadataKeys.ALL_KEYS)
addAll(SubscriptionMetadataKeys.ALL_KEYS)
add(OtherKeys.HEAD_START_POST)
add(WCMetaData.BundleMetadataKeys.BUNDLE_MIN_SIZE)
add(WCMetaData.BundleMetadataKeys.BUNDLE_MAX_SIZE)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ data class WCProductModel(@PrimaryKey @Column private var id: Int = 0) : Identif
@Column var bundledItems = ""
@Column var compositeComponents = ""
@Column var specialStockStatus = ""
@Column var isSampleProduct = false
@JvmName("setIsSampleProduct")
set

val attributeList: Array<ProductAttribute>
get() = Gson().fromJson(attributes, Array<ProductAttribute>::class.java) ?: emptyArray()
Expand All @@ -120,11 +123,6 @@ data class WCProductModel(@PrimaryKey @Column private var id: Int = 0) : Identif
?.find { it.key == ADDONS_METADATA_KEY }
?.addons

val isSampleProduct: Boolean
get() = Gson().fromJson(metadata, Array<WCMetaData>::class.java)
?.any { it.key == OtherKeys.HEAD_START_POST && it.value == "_hs_extra" }
?: false

val isConfigurable: Boolean
get() = when (type) {
CoreProductType.BUNDLE.value -> {
Expand Down Expand Up @@ -617,8 +615,4 @@ data class WCProductModel(@PrimaryKey @Column private var id: Int = 0) : Identif
ALLOW_COMBINATION
)
}

object OtherKeys {
const val HEAD_START_POST = "_headstart_post"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ data class ProductApiResponse(
bundledItems = response.bundled_items?.toString() ?: ""
compositeComponents = response.composite_components?.toString() ?: ""
metadata = response.metadata?.toString() ?: ""
isSampleProduct = response.metadata?.any {
val metaDataEntry = if (it.isJsonObject) it.asJsonObject else null
metaDataEntry?.let { json ->
json.getString(WCMetaData.KEY) == "_headstart_post"
&& json.getString(WCMetaData.VALUE) == "_hs_extra"
} ?: false
} ?: false

response.dimensions?.asJsonObject?.let { json ->
length = json.getString("length") ?: ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import com.wellsql.generated.WCProductReviewModelTable
import com.wellsql.generated.WCProductShippingClassModelTable
import com.wellsql.generated.WCProductTagModelTable
import com.wellsql.generated.WCProductVariationModelTable
import com.yarolegovich.wellsql.ConditionClauseBuilder
import com.yarolegovich.wellsql.SelectQuery
import com.yarolegovich.wellsql.WellSql
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onStart
Expand Down Expand Up @@ -52,19 +54,47 @@ object ProductSqlUtils {

private val gson by lazy { Gson() }

fun observeProductsCount(
site: SiteModel,
filterOptions: Map<ProductFilterOption, String> = emptyMap(),
excludeSampleProducts: Boolean = false
): Flow<Long> {
return productsUpdatesTrigger
.onStart { emit(Unit) }
.debounce(DEBOUNCE_DELAY_FOR_OBSERVERS)
.mapLatest {
getProductCountForSite(site, filterOptions, excludeSampleProducts)
}
.distinctUntilChanged()
.flowOn(Dispatchers.IO)
}

fun observeProducts(
site: SiteModel,
sortType: ProductSorting = DEFAULT_PRODUCT_SORTING,
filterOptions: Map<ProductFilterOption, String> = emptyMap()
filterOptions: Map<ProductFilterOption, String> = emptyMap(),
excludeSampleProducts: Boolean = false,
limit: Int? = null
): Flow<List<WCProductModel>> {
return productsUpdatesTrigger
.onStart { emit(Unit) }
.debounce(DEBOUNCE_DELAY_FOR_OBSERVERS)
.mapLatest {
if (filterOptions.isEmpty()) {
getProductsForSite(site, sortType)
getProductsForSite(
site = site,
sortType = sortType,
excludeSampleProducts = excludeSampleProducts,
limit = limit
)
} else {
getProducts(site, filterOptions, sortType)
getProducts(
site = site,
filterOptions = filterOptions,
sortType = sortType,
excludeSampleProducts = excludeSampleProducts,
limit = limit
)
}
}
.flowOn(Dispatchers.IO)
Expand Down Expand Up @@ -98,6 +128,7 @@ object ProductSqlUtils {
gson.fromJson(it.compositeComponents, responseType) as? List<WCProductComponent>
} ?: emptyList()
}

private fun getBundledProducts(site: SiteModel, remoteProductId: Long): List<WCBundledProduct> {
val productModel = WellSql.select(WCProductModel::class.java)
.where().beginGroup()
Expand Down Expand Up @@ -227,27 +258,15 @@ object ProductSqlUtils {
sortType: ProductSorting = DEFAULT_PRODUCT_SORTING,
excludedProductIds: List<Long>? = null,
searchQuery: String? = null,
skuSearchOptions: SkuSearchOptions = SkuSearchOptions.Disabled
skuSearchOptions: SkuSearchOptions = SkuSearchOptions.Disabled,
excludeSampleProducts: Boolean = false,
limit: Int? = null
): List<WCProductModel> {
val queryBuilder = WellSql.select(WCProductModel::class.java)
.where().beginGroup()
.equals(WCProductModelTable.LOCAL_SITE_ID, site.id)
.applyProductFilterOptions(filterOptions)

if (filterOptions.containsKey(ProductFilterOption.STATUS)) {
queryBuilder.equals(WCProductModelTable.STATUS, filterOptions[ProductFilterOption.STATUS])
}
if (filterOptions.containsKey(ProductFilterOption.STOCK_STATUS)) {
queryBuilder.equals(WCProductModelTable.STOCK_STATUS, filterOptions[ProductFilterOption.STOCK_STATUS])
}
if (filterOptions.containsKey(ProductFilterOption.TYPE)) {
queryBuilder.equals(WCProductModelTable.TYPE, filterOptions[ProductFilterOption.TYPE])
}
if (filterOptions.containsKey(ProductFilterOption.CATEGORY)) {
// Building a custom filter, because in the table a product's categories are saved as JSON string, e.g:
// [{"id":1377,"name":"Decor","slug":"decor"},{"id":1374,"name":"Hoodies","slug":"hoodies"}]
val categoryFilter = "\"id\":${filterOptions[ProductFilterOption.CATEGORY]},"
queryBuilder.contains(WCProductModelTable.CATEGORIES, categoryFilter)
}
if (searchQuery?.isNotEmpty() == true) {
when(skuSearchOptions) {
SkuSearchOptions.Disabled -> {
Expand All @@ -260,12 +279,14 @@ object ProductSqlUtils {
.contains(WCProductModelTable.SHORT_DESCRIPTION, searchQuery)
.endGroup()
}

SkuSearchOptions.ExactSearch -> {
queryBuilder.beginGroup()
// The search is case sensitive
.equals(WCProductModelTable.SKU, searchQuery)
.endGroup()
}

SkuSearchOptions.PartialMatch -> {
queryBuilder.beginGroup()
.contains(WCProductModelTable.SKU, searchQuery)
Expand All @@ -280,12 +301,17 @@ object ProductSqlUtils {
}
}

if (excludeSampleProducts) {
queryBuilder.equals(WCProductModelTable.IS_SAMPLE_PRODUCT, false)
}

val sortOrder = getSortOrder(sortType)
val sortField = getSortField(sortType)

val products = queryBuilder
.endGroup().endWhere()
.orderBy(sortField, sortOrder)
.apply { limit?.let { limit(it) } }
.asModel

return if (sortType == TITLE_ASC || sortType == TITLE_DESC) {
Expand Down Expand Up @@ -326,15 +352,23 @@ object ProductSqlUtils {

fun getProductsForSite(
site: SiteModel,
sortType: ProductSorting = DEFAULT_PRODUCT_SORTING
sortType: ProductSorting = DEFAULT_PRODUCT_SORTING,
excludeSampleProducts: Boolean = false,
limit: Int? = null
): List<WCProductModel> {
val sortOrder = getSortOrder(sortType)
val sortField = getSortField(sortType)
val products = WellSql.select(WCProductModel::class.java)
.where()
.where().beginGroup()
.equals(WCProductModelTable.LOCAL_SITE_ID, site.id)
.endWhere()
.apply {
if (excludeSampleProducts) {
equals(WCProductModelTable.IS_SAMPLE_PRODUCT, false)
}
}
.endGroup().endWhere()
.orderBy(sortField, sortOrder)
.apply { limit?.let { limit(it) } }
.asModel

return if (sortType == TITLE_ASC || sortType == TITLE_DESC) {
Expand Down Expand Up @@ -426,12 +460,24 @@ object ProductSqlUtils {
.also(::triggerVariationsUpdateIfNeeded)
}

fun getProductCountForSite(site: SiteModel): Long {
fun getProductCountForSite(
site: SiteModel,
filterOptions: Map<ProductFilterOption, String> = emptyMap(),
excludeSampleProducts: Boolean = false
): Long {
return WellSql.select(WCProductModel::class.java)
.where()
.equals(WCProductModelTable.LOCAL_SITE_ID, site.id)
.endWhere()
.count()
.where()
.beginGroup()
.equals(WCProductModelTable.LOCAL_SITE_ID, site.id)
.applyProductFilterOptions(filterOptions)
.apply {
if (excludeSampleProducts) {
equals(WCProductModelTable.IS_SAMPLE_PRODUCT, false)
}
}
.endGroup()
.endWhere()
.count()
}

fun insertOrUpdateProductReviews(productReviews: List<WCProductReviewModel>): Int {
Expand Down Expand Up @@ -885,4 +931,25 @@ object ProductSqlUtils {
private fun triggerCategoriesUpdateIfNeeded(affectedRows: Int) {
if (affectedRows != 0) categoriesUpdatesTrigger.tryEmit(Unit)
}

private fun ConditionClauseBuilder<SelectQuery<WCProductModel>>.applyProductFilterOptions(
filterOptions: Map<ProductFilterOption, String>
): ConditionClauseBuilder<SelectQuery<WCProductModel>> {
if (filterOptions.containsKey(ProductFilterOption.STATUS)) {
equals(WCProductModelTable.STATUS, filterOptions[ProductFilterOption.STATUS])
}
if (filterOptions.containsKey(ProductFilterOption.STOCK_STATUS)) {
equals(WCProductModelTable.STOCK_STATUS, filterOptions[ProductFilterOption.STOCK_STATUS])
}
if (filterOptions.containsKey(ProductFilterOption.TYPE)) {
equals(WCProductModelTable.TYPE, filterOptions[ProductFilterOption.TYPE])
}
if (filterOptions.containsKey(ProductFilterOption.CATEGORY)) {
// Building a custom filter, because in the table a product's categories are saved as JSON string, e.g:
// [{"id":1377,"name":"Decor","slug":"decor"},{"id":1374,"name":"Hoodies","slug":"hoodies"}]
val categoryFilter = "\"id\":${filterOptions[ProductFilterOption.CATEGORY]},"
contains(WCProductModelTable.CATEGORIES, categoryFilter)
}
return this
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -981,9 +981,26 @@ class WCProductStore @Inject constructor(
fun observeProducts(
site: SiteModel,
sortType: ProductSorting = DEFAULT_PRODUCT_SORTING,
filterOptions: Map<ProductFilterOption, String> = emptyMap()
): Flow<List<WCProductModel>> =
ProductSqlUtils.observeProducts(site, sortType, filterOptions)
filterOptions: Map<ProductFilterOption, String> = emptyMap(),
excludeSampleProducts: Boolean = false,
limit: Int? = null
): Flow<List<WCProductModel>> = ProductSqlUtils.observeProducts(
site = site,
sortType = sortType,
filterOptions = filterOptions,
excludeSampleProducts = excludeSampleProducts,
limit = limit
)

fun observeProductsCount(
site: SiteModel,
filterOptions: Map<ProductFilterOption, String> = emptyMap(),
excludeSampleProducts: Boolean = false
): Flow<Long> = ProductSqlUtils.observeProductsCount(
site = site,
filterOptions = filterOptions,
excludeSampleProducts = excludeSampleProducts
)

fun observeVariations(site: SiteModel, productId: Long): Flow<List<WCProductVariationModel>> =
ProductSqlUtils.observeVariations(site, productId)
Expand Down

0 comments on commit 4d21f7e

Please sign in to comment.