Bug 1855137 - Add review checker vendor ordering logic

fenix/120.0
rahulsainani 1 year ago committed by mergify[bot]
parent aa1e02bf8d
commit 0af50d1b52

@ -11,6 +11,7 @@ import mozilla.components.feature.tabs.TabsUseCases
import org.mozilla.fenix.shopping.middleware.DefaultNetworkChecker
import org.mozilla.fenix.shopping.middleware.DefaultReviewQualityCheckPreferences
import org.mozilla.fenix.shopping.middleware.DefaultReviewQualityCheckService
import org.mozilla.fenix.shopping.middleware.DefaultReviewQualityCheckVendorsService
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckNavigationMiddleware
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckNetworkMiddleware
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckPreferencesMiddleware
@ -37,7 +38,7 @@ object ReviewQualityCheckMiddlewareProvider {
scope: CoroutineScope,
): List<ReviewQualityCheckMiddleware> =
listOf(
providePreferencesMiddleware(settings, scope),
providePreferencesMiddleware(settings, browserStore, scope),
provideNetworkMiddleware(browserStore, context, scope),
provideNavigationMiddleware(
TabsUseCases.SelectOrAddUseCase(browserStore),
@ -48,9 +49,11 @@ object ReviewQualityCheckMiddlewareProvider {
private fun providePreferencesMiddleware(
settings: Settings,
browserStore: BrowserStore,
scope: CoroutineScope,
) = ReviewQualityCheckPreferencesMiddleware(
reviewQualityCheckPreferences = DefaultReviewQualityCheckPreferences(settings),
reviewQualityCheckVendorsService = DefaultReviewQualityCheckVendorsService(browserStore),
scope = scope,
)

@ -20,6 +20,7 @@ import org.mozilla.fenix.shopping.store.ReviewQualityCheckState
*/
class ReviewQualityCheckPreferencesMiddleware(
private val reviewQualityCheckPreferences: ReviewQualityCheckPreferences,
private val reviewQualityCheckVendorsService: ReviewQualityCheckVendorsService,
private val scope: CoroutineScope,
) : ReviewQualityCheckMiddleware {
@ -50,12 +51,14 @@ class ReviewQualityCheckPreferencesMiddleware(
val hasUserOptedIn = reviewQualityCheckPreferences.enabled()
val isProductRecommendationsEnabled =
reviewQualityCheckPreferences.productRecommendationsEnabled()
store.dispatch(
ReviewQualityCheckAction.UpdateUserPreferences(
hasUserOptedIn = hasUserOptedIn,
isProductRecommendationsEnabled = isProductRecommendationsEnabled,
),
)
val updateUserPreferences = if (hasUserOptedIn) {
ReviewQualityCheckAction.OptInCompleted(isProductRecommendationsEnabled)
} else {
val productVendors = reviewQualityCheckVendorsService.productVendors()
ReviewQualityCheckAction.OptOutCompleted(productVendors)
}
store.dispatch(updateUserPreferences)
}
}
@ -64,10 +67,7 @@ class ReviewQualityCheckPreferencesMiddleware(
val isProductRecommendationsEnabled =
reviewQualityCheckPreferences.productRecommendationsEnabled()
store.dispatch(
ReviewQualityCheckAction.UpdateUserPreferences(
hasUserOptedIn = true,
isProductRecommendationsEnabled = isProductRecommendationsEnabled,
),
ReviewQualityCheckAction.OptInCompleted(isProductRecommendationsEnabled),
)
// Update the preference

@ -0,0 +1,78 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.shopping.middleware
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.ProductVendor
import java.net.URI
import java.net.URISyntaxException
private const val AMAZON_COM = "amazon.com"
private const val BEST_BUY_COM = "bestbuy.com"
private const val WALMART_COM = "walmart.com"
private val defaultVendorsList = enumValues<ProductVendor>().toList()
/**
* Service for getting the list of product vendors.
*/
interface ReviewQualityCheckVendorsService {
/**
* Returns the list of product vendors in order.
*/
fun productVendors(): List<ProductVendor>
}
/**
* Default implementation of [ReviewQualityCheckVendorsService] that uses the [BrowserStore] to
* identify the selected tab.
*
* @property browserStore The [BrowserStore] instance to use.
*/
class DefaultReviewQualityCheckVendorsService(
private val browserStore: BrowserStore,
) : ReviewQualityCheckVendorsService {
override fun productVendors(): List<ProductVendor> {
val selectedTabUrl = browserStore.state.selectedTab?.content?.url
return if (selectedTabUrl == null) {
defaultVendorsList
} else {
val host = selectedTabUrl.toJavaUri()?.host
when {
host == null -> defaultVendorsList
host.contains(AMAZON_COM) -> createProductVendorsList(ProductVendor.AMAZON)
host.contains(BEST_BUY_COM) -> createProductVendorsList(ProductVendor.BEST_BUY)
host.contains(WALMART_COM) -> createProductVendorsList(ProductVendor.WALMART)
else -> defaultVendorsList
}
}
}
/**
* Creates list of product vendors using the firstVendor param as the first item in the list.
*/
private fun createProductVendorsList(firstVendor: ProductVendor): List<ProductVendor> =
listOf(firstVendor) + defaultVendorsList.filterNot { it == firstVendor }
/**
* Convenience function to converts a given string to a [URI] instance. Returns null if the
* string is not a valid URI.
*/
private fun String.toJavaUri(): URI? {
return try {
URI.create(this)
} catch (e: URISyntaxException) {
Logger.error("Unable to create URI with the given string $this", e)
null
} catch (e: IllegalArgumentException) {
Logger.error("Unable to create URI with the given string $this", e)
null
}
}
}

@ -53,15 +53,20 @@ sealed interface ReviewQualityCheckAction : Action {
object ToggleProductRecommendation : PreferencesMiddlewareAction, UpdateAction
/**
* Triggered as a result of a [PreferencesMiddlewareAction] to update the state.
* Triggered as a result of a [OptIn] or [Init] whe user has opted in for shopping experience.
*
* @property hasUserOptedIn True when user has opted in for shopping experience.
* @property isProductRecommendationsEnabled Reflects the user preference update to display
* recommended product. Null when product recommendations feature is disabled.
*/
data class UpdateUserPreferences(
val hasUserOptedIn: Boolean,
val isProductRecommendationsEnabled: Boolean?,
data class OptInCompleted(val isProductRecommendationsEnabled: Boolean?) : UpdateAction
/**
* Triggered as a result of [Init] when user has opted out of shopping experience.
*
* @property productVendors List of product vendors in relevant order.
*/
data class OptOutCompleted(
val productVendors: List<ReviewQualityCheckState.ProductVendor>,
) : UpdateAction
/**

@ -22,14 +22,10 @@ sealed interface ReviewQualityCheckState : State {
/**
* The state when the user has not opted in for the feature.
*
* @property retailers List of retailer names to be displayed in order in the onboarding UI.
* @property productVendors List of vendors to be displayed in order in the onboarding UI.
*/
data class NotOptedIn(
val retailers: List<ProductVendor> = listOf(
ProductVendor.AMAZON,
ProductVendor.BEST_BUY,
ProductVendor.WALMART,
),
val productVendors: List<ProductVendor> = enumValues<ProductVendor>().toList(),
) : ReviewQualityCheckState
/**

@ -41,19 +41,18 @@ private fun mapStateForUpdateAction(
action: ReviewQualityCheckAction.UpdateAction,
): ReviewQualityCheckState {
return when (action) {
is ReviewQualityCheckAction.UpdateUserPreferences -> {
if (action.hasUserOptedIn) {
if (state is ReviewQualityCheckState.OptedIn) {
state.copy(productRecommendationsPreference = action.isProductRecommendationsEnabled)
} else {
ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = action.isProductRecommendationsEnabled,
)
}
is ReviewQualityCheckAction.OptInCompleted -> {
if (state is ReviewQualityCheckState.OptedIn) {
state.copy(productRecommendationsPreference = action.isProductRecommendationsEnabled)
} else {
ReviewQualityCheckState.NotOptedIn()
ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = action.isProductRecommendationsEnabled,
)
}
}
is ReviewQualityCheckAction.OptOutCompleted -> {
ReviewQualityCheckState.NotOptedIn(action.productVendors)
}
ReviewQualityCheckAction.OptOut -> {
ReviewQualityCheckState.NotOptedIn()

@ -42,7 +42,7 @@ fun ReviewQualityCheckBottomSheet(
when (val state = reviewQualityCheckState) {
is ReviewQualityCheckState.NotOptedIn -> {
ReviewQualityCheckContextualOnboarding(
retailers = state.retailers,
productVendors = state.productVendors,
onPrimaryButtonClick = {
store.dispatch(ReviewQualityCheckAction.OptIn)
},

@ -34,7 +34,7 @@ const val PLACEHOLDER_URL = "www.fakespot.com"
* A placeholder UI for review quality check contextual onboarding. The actual UI will be
* implemented as part of Bug 1840103 with the illustration.
*
* @param retailers List of retailers to be displayed in order.
* @param productVendors List of retailers to be displayed in order.
* @param onLearnMoreClick Invoked when a user clicks on the learn more link.
* @param onPrivacyPolicyClick Invoked when a user clicks on the privacy policy link.
* @param onTermsOfUseClick Invoked when a user clicks on the terms of use link.
@ -44,7 +44,7 @@ const val PLACEHOLDER_URL = "www.fakespot.com"
@Suppress("LongParameterList", "LongMethod")
@Composable
fun ReviewQualityCheckContextualOnboarding(
retailers: List<ProductVendor>,
productVendors: List<ProductVendor>,
onLearnMoreClick: () -> Unit,
onPrivacyPolicyClick: () -> Unit,
onTermsOfUseClick: () -> Unit,
@ -68,7 +68,7 @@ fun ReviewQualityCheckContextualOnboarding(
Spacer(modifier = Modifier.height(16.dp))
Text(
text = createDescriptionString(retailers),
text = createDescriptionString(productVendors),
color = FirefoxTheme.colors.textSecondary,
style = FirefoxTheme.typography.body2,
)

@ -7,7 +7,7 @@ package org.mozilla.fenix.shopping.fake
import org.mozilla.fenix.shopping.middleware.NetworkChecker
class FakeNetworkChecker(
private val isConnected: Boolean,
private val isConnected: Boolean = true,
) : NetworkChecker {
override fun isConnected(): Boolean = isConnected
}

@ -0,0 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.shopping.fake
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckVendorsService
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.ProductVendor
class FakeReviewQualityCheckVendorsService(
private val productVendors: List<ProductVendor> = listOf(
ProductVendor.BEST_BUY,
ProductVendor.WALMART,
ProductVendor.AMAZON,
),
) : ReviewQualityCheckVendorsService {
override fun productVendors(): List<ProductVendor> = productVendors
}

@ -0,0 +1,140 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.shopping.middleware
import kotlinx.coroutines.test.runTest
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import org.junit.Assert.assertEquals
import org.junit.Test
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.ProductVendor
class DefaultReviewQualityCheckVendorsServiceTest {
@Test
fun `WHEN selected tab is an amazon page THEN amazon is first in product vendors list`() =
runTest {
val tab = createTab(
url = "https://www.amazon.com/product",
id = "test-tab",
)
val browserState = BrowserState(
tabs = listOf(tab),
selectedTabId = tab.id,
)
val tested = DefaultReviewQualityCheckVendorsService(BrowserStore(browserState))
val actual = tested.productVendors()
val expected = listOf(
ProductVendor.AMAZON,
ProductVendor.BEST_BUY,
ProductVendor.WALMART,
)
assertEquals(expected, actual)
}
@Test
fun `WHEN selected tab is a walmart page THEN walmart is first in product vendors list`() =
runTest {
val tab = createTab(
url = "https://www.walmart.com/product",
id = "test-tab",
)
val browserState = BrowserState(
tabs = listOf(tab),
selectedTabId = tab.id,
)
val tested = DefaultReviewQualityCheckVendorsService(BrowserStore(browserState))
val actual = tested.productVendors()
val expected = listOf(
ProductVendor.WALMART,
ProductVendor.AMAZON,
ProductVendor.BEST_BUY,
)
assertEquals(expected, actual)
}
@Test
fun `WHEN selected tab is a best buy page THEN best buy is first in product vendors list`() =
runTest {
val tab = createTab(
url = "https://www.bestbuy.com/product",
id = "test-tab",
)
val browserState = BrowserState(
tabs = listOf(tab),
selectedTabId = tab.id,
)
val tested = DefaultReviewQualityCheckVendorsService(BrowserStore(browserState))
val actual = tested.productVendors()
val expected = listOf(
ProductVendor.BEST_BUY,
ProductVendor.AMAZON,
ProductVendor.WALMART,
)
assertEquals(expected, actual)
}
@Test
fun `WHEN selected tab is a not a vendor page THEN default product vendors list is returned`() =
runTest {
val tab = createTab(
url = "https://www.shopping.xyz/product",
id = "test-tab",
)
val browserState = BrowserState(
tabs = listOf(tab),
selectedTabId = tab.id,
)
val tested = DefaultReviewQualityCheckVendorsService(BrowserStore(browserState))
val actual = tested.productVendors()
val expected = listOf(
ProductVendor.AMAZON,
ProductVendor.BEST_BUY,
ProductVendor.WALMART,
)
assertEquals(expected, actual)
}
@Test
fun `WHEN selected tab is a not a valid uri THEN default product vendors list is returned`() =
runTest {
val tab = createTab(
url = "not a url",
id = "test-tab",
)
val browserState = BrowserState(
tabs = listOf(tab),
selectedTabId = tab.id,
)
val tested = DefaultReviewQualityCheckVendorsService(BrowserStore(browserState))
val actual = tested.productVendors()
val expected = listOf(
ProductVendor.AMAZON,
ProductVendor.BEST_BUY,
ProductVendor.WALMART,
)
assertEquals(expected, actual)
}
}

@ -15,6 +15,7 @@ import org.mozilla.fenix.shopping.ProductAnalysisTestData
import org.mozilla.fenix.shopping.fake.FakeNetworkChecker
import org.mozilla.fenix.shopping.fake.FakeReviewQualityCheckPreferences
import org.mozilla.fenix.shopping.fake.FakeReviewQualityCheckService
import org.mozilla.fenix.shopping.fake.FakeReviewQualityCheckVendorsService
import org.mozilla.fenix.shopping.middleware.AnalysisStatusDto
import org.mozilla.fenix.shopping.middleware.NetworkChecker
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckNetworkMiddleware
@ -22,6 +23,7 @@ import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckPreferences
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckPreferencesMiddleware
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckService
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.OptedIn.ProductReviewState.AnalysisPresent.AnalysisStatus
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.ProductVendor
class ReviewQualityCheckStoreTest {
@ -39,13 +41,26 @@ class ReviewQualityCheckStoreTest {
isEnabled = false,
isProductRecommendationsEnabled = false,
),
reviewQualityCheckVendorsService = FakeReviewQualityCheckVendorsService(
productVendors = listOf(
ProductVendor.BEST_BUY,
ProductVendor.AMAZON,
ProductVendor.WALMART,
),
),
),
)
tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
val expected = ReviewQualityCheckState.NotOptedIn()
val expected = ReviewQualityCheckState.NotOptedIn(
productVendors = listOf(
ProductVendor.BEST_BUY,
ProductVendor.AMAZON,
ProductVendor.WALMART,
),
)
assertEquals(expected, tested.state)
}
@ -331,29 +346,22 @@ class ReviewQualityCheckStoreTest {
}
private fun provideReviewQualityCheckMiddleware(
reviewQualityCheckPreferences: ReviewQualityCheckPreferences,
reviewQualityCheckService: ReviewQualityCheckService? = null,
networkChecker: NetworkChecker? = null,
reviewQualityCheckPreferences: ReviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(),
reviewQualityCheckVendorsService: FakeReviewQualityCheckVendorsService = FakeReviewQualityCheckVendorsService(),
reviewQualityCheckService: ReviewQualityCheckService = FakeReviewQualityCheckService(),
networkChecker: NetworkChecker = FakeNetworkChecker(),
): List<ReviewQualityCheckMiddleware> {
return if (reviewQualityCheckService != null && networkChecker != null) {
listOf(
ReviewQualityCheckPreferencesMiddleware(
reviewQualityCheckPreferences = reviewQualityCheckPreferences,
scope = this.scope,
),
ReviewQualityCheckNetworkMiddleware(
reviewQualityCheckService = reviewQualityCheckService,
networkChecker = networkChecker,
scope = this.scope,
),
)
} else {
listOf(
ReviewQualityCheckPreferencesMiddleware(
reviewQualityCheckPreferences = reviewQualityCheckPreferences,
scope = this.scope,
),
)
}
return listOf(
ReviewQualityCheckPreferencesMiddleware(
reviewQualityCheckPreferences = reviewQualityCheckPreferences,
reviewQualityCheckVendorsService = reviewQualityCheckVendorsService,
scope = this.scope,
),
ReviewQualityCheckNetworkMiddleware(
reviewQualityCheckService = reviewQualityCheckService,
networkChecker = networkChecker,
scope = this.scope,
),
)
}
}

Loading…
Cancel
Save