mirror of
https://github.com/fork-maintainers/iceraven-browser
synced 2024-11-15 18:12:54 +00:00
Bug 1867061 - Display and hide rc product recommendations on toggle
This commit is contained in:
parent
6217bb0a3a
commit
ba9bbd9ab9
@ -80,37 +80,16 @@ class ReviewQualityCheckNetworkMiddleware(
|
||||
ReviewQualityCheckAction.AnalyzeProduct,
|
||||
ReviewQualityCheckAction.RestoreReanalysis,
|
||||
-> {
|
||||
val reanalysis = reviewQualityCheckService.reanalyzeProduct()
|
||||
store.onReanalyze()
|
||||
}
|
||||
|
||||
if (reanalysis == null) {
|
||||
store.updateProductReviewState(ProductReviewState.Error.GenericError)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val status = pollForAnalysisStatus()
|
||||
|
||||
if (status == null ||
|
||||
status == AnalysisStatusDto.PENDING ||
|
||||
status == AnalysisStatusDto.IN_PROGRESS
|
||||
ReviewQualityCheckAction.ToggleProductRecommendation -> {
|
||||
val state = store.state
|
||||
if (state is ReviewQualityCheckState.OptedIn &&
|
||||
state.productReviewState is ProductReviewState.AnalysisPresent &&
|
||||
state.productRecommendationsPreference == true
|
||||
) {
|
||||
// poll failed, reset to previous state
|
||||
val state = store.state
|
||||
if (state is ReviewQualityCheckState.OptedIn) {
|
||||
if (state.productReviewState is ProductReviewState.NoAnalysisPresent) {
|
||||
store.updateProductReviewState(ProductReviewState.NoAnalysisPresent())
|
||||
} else if (state.productReviewState is ProductReviewState.AnalysisPresent) {
|
||||
store.updateProductReviewState(
|
||||
state.productReviewState.copy(
|
||||
analysisStatus = AnalysisStatus.NEEDS_ANALYSIS,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// poll succeeded, update state
|
||||
val productAnalysis = reviewQualityCheckService.fetchProductReview()
|
||||
val productReviewState = productAnalysis.toProductReviewState()
|
||||
store.updateProductReviewState(productReviewState)
|
||||
store.updateRecommendedProductState()
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,6 +104,39 @@ class ReviewQualityCheckNetworkMiddleware(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun Store<ReviewQualityCheckState, ReviewQualityCheckAction>.onReanalyze() {
|
||||
val reanalysis = reviewQualityCheckService.reanalyzeProduct()
|
||||
|
||||
if (reanalysis == null) {
|
||||
updateProductReviewState(ProductReviewState.Error.GenericError)
|
||||
return
|
||||
}
|
||||
|
||||
val status = pollForAnalysisStatus()
|
||||
|
||||
if (status == null ||
|
||||
status == AnalysisStatusDto.PENDING ||
|
||||
status == AnalysisStatusDto.IN_PROGRESS
|
||||
) {
|
||||
// poll failed, reset to previous state
|
||||
val state = this.state
|
||||
if (state is ReviewQualityCheckState.OptedIn) {
|
||||
if (state.productReviewState is ProductReviewState.NoAnalysisPresent) {
|
||||
updateProductReviewState(ProductReviewState.NoAnalysisPresent())
|
||||
} else if (state.productReviewState is ProductReviewState.AnalysisPresent) {
|
||||
updateProductReviewState(
|
||||
state.productReviewState.copy(analysisStatus = AnalysisStatus.NEEDS_ANALYSIS),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// poll succeeded, update state
|
||||
val productAnalysis = reviewQualityCheckService.fetchProductReview()
|
||||
val productReviewState = productAnalysis.toProductReviewState()
|
||||
updateProductReviewState(productReviewState)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun pollForAnalysisStatus(): AnalysisStatusDto? =
|
||||
retry(
|
||||
predicate = { it.isPendingOrInProgress() },
|
||||
|
@ -68,6 +68,7 @@ class DefaultReviewQualityCheckService(
|
||||
private val browserStore: BrowserStore,
|
||||
) : ReviewQualityCheckService {
|
||||
|
||||
private val recommendationsCache: MutableMap<String, ProductRecommendation> = mutableMapOf()
|
||||
private val logger = Logger("DefaultReviewQualityCheckService")
|
||||
|
||||
override suspend fun fetchProductReview(): ProductAnalysis? = withContext(Dispatchers.Main) {
|
||||
@ -125,25 +126,34 @@ class DefaultReviewQualityCheckService(
|
||||
withContext(Dispatchers.Main) {
|
||||
suspendCoroutine { continuation ->
|
||||
browserStore.state.selectedTab?.let { tab ->
|
||||
tab.engineState.engineSession?.requestProductRecommendations(
|
||||
url = tab.content.url,
|
||||
onResult = {
|
||||
if (it.isEmpty()) {
|
||||
if (shouldRecordAvailableTelemetry) {
|
||||
Shopping.surfaceNoAdsAvailable.record()
|
||||
|
||||
if (recommendationsCache.containsKey(tab.content.url)) {
|
||||
continuation.resume(recommendationsCache[tab.content.url])
|
||||
} else {
|
||||
tab.engineState.engineSession?.requestProductRecommendations(
|
||||
url = tab.content.url,
|
||||
onResult = {
|
||||
if (it.isEmpty()) {
|
||||
if (shouldRecordAvailableTelemetry) {
|
||||
Shopping.surfaceNoAdsAvailable.record()
|
||||
}
|
||||
} else {
|
||||
Shopping.adsExposure.record()
|
||||
}
|
||||
} else {
|
||||
Shopping.adsExposure.record()
|
||||
}
|
||||
// Return the first available recommendation since ui requires only
|
||||
// one recommendation.
|
||||
continuation.resume(it.firstOrNull())
|
||||
},
|
||||
onException = {
|
||||
logger.error("Error fetching product recommendation", it)
|
||||
continuation.resume(null)
|
||||
},
|
||||
)
|
||||
// Return the first available recommendation since ui requires only
|
||||
// one recommendation.
|
||||
continuation.resume(
|
||||
it.firstOrNull()?.also { recommendation ->
|
||||
recommendationsCache[tab.content.url] = recommendation
|
||||
},
|
||||
)
|
||||
},
|
||||
onException = {
|
||||
logger.error("Error fetching product recommendation", it)
|
||||
continuation.resume(null)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ sealed interface ReviewQualityCheckAction : Action {
|
||||
/**
|
||||
* Triggered when the user has enabled or disabled product recommendations.
|
||||
*/
|
||||
object ToggleProductRecommendation : PreferencesMiddlewareAction, UpdateAction, TelemetryAction
|
||||
object ToggleProductRecommendation : PreferencesMiddlewareAction, UpdateAction, NetworkAction, TelemetryAction
|
||||
|
||||
/**
|
||||
* Triggered as a result of a [OptIn] or [Init] whe user has opted in for shopping experience.
|
||||
|
@ -6,6 +6,7 @@ package org.mozilla.fenix.shopping.middleware
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import mozilla.components.browser.state.state.BrowserState
|
||||
import mozilla.components.browser.state.state.createTab
|
||||
@ -239,4 +240,74 @@ class DefaultReviewQualityCheckServiceTest {
|
||||
|
||||
assertNull(actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN product recommendations is called WHEN onResult is invoked with the result THEN recommendations returns the same result without re-fetching again`() =
|
||||
runTest {
|
||||
val engineSession = mockk<EngineSession>()
|
||||
val expected = ProductRecommendationTestData.productRecommendation()
|
||||
val productRecommendations = listOf(expected)
|
||||
|
||||
every {
|
||||
engineSession.requestProductRecommendations(any(), any(), any())
|
||||
}.answers {
|
||||
secondArg<(List<ProductRecommendation>) -> Unit>().invoke(productRecommendations)
|
||||
}
|
||||
|
||||
val tab = createTab(
|
||||
url = "https://www.shopping.org/product",
|
||||
id = "test-tab",
|
||||
engineSession = engineSession,
|
||||
)
|
||||
val browserState = BrowserState(
|
||||
tabs = listOf(tab),
|
||||
selectedTabId = tab.id,
|
||||
)
|
||||
|
||||
val tested = DefaultReviewQualityCheckService(BrowserStore(browserState))
|
||||
|
||||
tested.productRecommendation(false)
|
||||
tested.productRecommendation(false)
|
||||
val actual = tested.productRecommendation(false)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
|
||||
verify(exactly = 1) {
|
||||
engineSession.requestProductRecommendations(any(), any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN product recommendations is called WHEN onResult is invoked with the empty result THEN recommendations fetches every time`() =
|
||||
runTest {
|
||||
val engineSession = mockk<EngineSession>()
|
||||
|
||||
every {
|
||||
engineSession.requestProductRecommendations(any(), any(), any())
|
||||
}.answers {
|
||||
secondArg<(List<ProductRecommendation>) -> Unit>().invoke(emptyList())
|
||||
}
|
||||
|
||||
val tab = createTab(
|
||||
url = "https://www.shopping.org/product",
|
||||
id = "test-tab",
|
||||
engineSession = engineSession,
|
||||
)
|
||||
val browserState = BrowserState(
|
||||
tabs = listOf(tab),
|
||||
selectedTabId = tab.id,
|
||||
)
|
||||
|
||||
val tested = DefaultReviewQualityCheckService(BrowserStore(browserState))
|
||||
|
||||
tested.productRecommendation(false)
|
||||
tested.productRecommendation(false)
|
||||
val actual = tested.productRecommendation(false)
|
||||
|
||||
assertNull(actual)
|
||||
|
||||
verify(exactly = 3) {
|
||||
engineSession.requestProductRecommendations(any(), any(), any())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -216,6 +216,135 @@ class ReviewQualityCheckStoreTest {
|
||||
assertEquals(expected, tested.state)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN product recommendations are available WHEN the user turns product recommendations off THEN state should not contain product recommendation`() =
|
||||
runTest {
|
||||
setAndResetLocale {
|
||||
var productRecommendationsFetchCounter = 0
|
||||
val tested = ReviewQualityCheckStore(
|
||||
middleware = provideReviewQualityCheckMiddleware(
|
||||
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(
|
||||
isEnabled = true,
|
||||
isProductRecommendationsEnabled = true,
|
||||
),
|
||||
reviewQualityCheckService = FakeReviewQualityCheckService(
|
||||
productAnalysis = { ProductAnalysisTestData.productAnalysis() },
|
||||
productRecommendation = {
|
||||
productRecommendationsFetchCounter++
|
||||
ProductRecommendationTestData.productRecommendation()
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
tested.waitUntilIdle()
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
tested.dispatch(ReviewQualityCheckAction.FetchProductAnalysis).joinBlocking()
|
||||
tested.waitUntilIdle()
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
assertEquals(1, productRecommendationsFetchCounter)
|
||||
|
||||
tested.dispatch(ReviewQualityCheckAction.ToggleProductRecommendation).joinBlocking()
|
||||
tested.waitUntilIdle()
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
val expected = ReviewQualityCheckState.OptedIn(
|
||||
productRecommendationsPreference = false,
|
||||
productRecommendationsExposure = true,
|
||||
productVendor = ProductVendor.BEST_BUY,
|
||||
productReviewState = ProductAnalysisTestData.analysisPresent(),
|
||||
)
|
||||
assertEquals(expected, tested.state)
|
||||
assertEquals(1, productRecommendationsFetchCounter)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN product recommendations are available WHEN the user turns product recommendations off and then back on THEN state should contain product recommendation`() =
|
||||
runTest {
|
||||
setAndResetLocale {
|
||||
val tested = ReviewQualityCheckStore(
|
||||
middleware = provideReviewQualityCheckMiddleware(
|
||||
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(
|
||||
isEnabled = true,
|
||||
isProductRecommendationsEnabled = true,
|
||||
),
|
||||
reviewQualityCheckService = FakeReviewQualityCheckService(
|
||||
productAnalysis = { ProductAnalysisTestData.productAnalysis() },
|
||||
productRecommendation = { ProductRecommendationTestData.productRecommendation() },
|
||||
),
|
||||
shoppingExperienceFeature = FakeShoppingExperienceFeature(
|
||||
productRecommendationsExposureEnabled = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
tested.waitUntilIdle()
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
tested.dispatch(ReviewQualityCheckAction.FetchProductAnalysis).joinBlocking()
|
||||
tested.waitUntilIdle()
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
tested.dispatch(ReviewQualityCheckAction.ToggleProductRecommendation).joinBlocking()
|
||||
tested.waitUntilIdle()
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
tested.dispatch(ReviewQualityCheckAction.ToggleProductRecommendation).joinBlocking()
|
||||
tested.waitUntilIdle()
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
val expected = ReviewQualityCheckState.OptedIn(
|
||||
productRecommendationsPreference = true,
|
||||
productRecommendationsExposure = true,
|
||||
productVendor = ProductVendor.BEST_BUY,
|
||||
productReviewState = ProductAnalysisTestData.analysisPresent(
|
||||
recommendedProductState = ProductRecommendationTestData.product(),
|
||||
),
|
||||
)
|
||||
assertEquals(expected, tested.state)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN product recommendations are available but analysis failed WHEN the user turns product recommendations on THEN recommendations should not be fetched`() =
|
||||
runTest {
|
||||
setAndResetLocale {
|
||||
var productRecommendationsFetchCounter = 0
|
||||
val tested = ReviewQualityCheckStore(
|
||||
middleware = provideReviewQualityCheckMiddleware(
|
||||
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(
|
||||
isEnabled = true,
|
||||
isProductRecommendationsEnabled = false,
|
||||
),
|
||||
reviewQualityCheckService = FakeReviewQualityCheckService(
|
||||
productAnalysis = { null },
|
||||
productRecommendation = {
|
||||
productRecommendationsFetchCounter++
|
||||
ProductRecommendationTestData.productRecommendation()
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
tested.waitUntilIdle()
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
tested.dispatch(ReviewQualityCheckAction.FetchProductAnalysis).joinBlocking()
|
||||
tested.waitUntilIdle()
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
assertEquals(0, productRecommendationsFetchCounter)
|
||||
|
||||
tested.dispatch(ReviewQualityCheckAction.ToggleProductRecommendation).joinBlocking()
|
||||
tested.waitUntilIdle()
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
val expected = ReviewQualityCheckState.OptedIn(
|
||||
productRecommendationsPreference = true,
|
||||
productRecommendationsExposure = true,
|
||||
productVendor = ProductVendor.BEST_BUY,
|
||||
productReviewState = ReviewQualityCheckState.OptedIn.ProductReviewState.Error.GenericError,
|
||||
)
|
||||
assertEquals(expected, tested.state)
|
||||
assertEquals(0, productRecommendationsFetchCounter)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN the user has opted in the feature WHEN there is existing card state data for a pdp THEN it should be restored`() =
|
||||
runTest {
|
||||
|
Loading…
Reference in New Issue
Block a user