Skip to content

Commit

Permalink
Merge pull request #20358 from wordpress-mobile/task/migrate-to-new-b…
Browse files Browse the repository at this point in the history
…laze-fetch-campaigns-endpoint

Task/migrate to new blaze fetch campaigns endpoint
  • Loading branch information
JorgeMucientes authored Mar 8, 2024
2 parents fa61130 + cfe878b commit 5e6a217
Show file tree
Hide file tree
Showing 22 changed files with 125 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class ActivityNavigator @Inject constructor() {

fun navigateToCampaignDetailPage(
context: Context,
campaignId: Int,
campaignId: String,
campaignDetailPageSource: CampaignDetailPageSource
) {
context.startActivity(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ import org.wordpress.android.ui.blaze.blazecampaigns.campaignlisting.CampaignLis
@SuppressLint("ParcelCreator")
sealed class BlazeCampaignPage : Parcelable {
data class CampaignListingPage(val source: CampaignListingPageSource) : BlazeCampaignPage()
data class CampaignDetailsPage(val campaignId: Int, val source: CampaignDetailPageSource) : BlazeCampaignPage()
data class CampaignDetailsPage(val campaignId: String, val source: CampaignDetailPageSource) : BlazeCampaignPage()
object Done: BlazeCampaignPage()
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ private const val CAMPAIGN_DETAIL_CAMPAIGN_ID = "campaign_detail_campaign_id"
@AndroidEntryPoint
class CampaignDetailFragment : Fragment(), CampaignDetailWebViewClient.CampaignDetailWebViewClientListener {
companion object {
fun newInstance(campaignId: Int, source: CampaignDetailPageSource) = CampaignDetailFragment().apply {
fun newInstance(campaignId: String, source: CampaignDetailPageSource) = CampaignDetailFragment().apply {
arguments = Bundle().apply {
putSerializable(CAMPAIGN_DETAIL_PAGE_SOURCE, source)
putInt(CAMPAIGN_DETAIL_CAMPAIGN_ID, campaignId)
putString(CAMPAIGN_DETAIL_CAMPAIGN_ID, campaignId)
}
}
}
Expand Down Expand Up @@ -110,7 +110,7 @@ class CampaignDetailFragment : Fragment(), CampaignDetailWebViewClient.CampaignD
?: CampaignDetailPageSource.UNKNOWN
}

private fun getCampaignId() = requireArguments().getInt(CAMPAIGN_DETAIL_CAMPAIGN_ID)
private fun getCampaignId() = requireArguments().getString(CAMPAIGN_DETAIL_CAMPAIGN_ID) ?: ""

override fun onRedirectToExternalBrowser(url: String) = viewModel.onRedirectToExternalBrowser(url)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ class CampaignDetailViewModel @Inject constructor(
@Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher
) : ScopedViewModel(bgDispatcher) {
private lateinit var pageSource: CampaignDetailPageSource
private var campaignId: Int = 0
private var campaignId: String = ""

private val _actionEvents = Channel<BlazeActionEvent>(Channel.BUFFERED)
val actionEvents = _actionEvents.receiveAsFlow()

private val _uiState = MutableStateFlow<CampaignDetailUiState>(CampaignDetailUiState.Preparing)
val uiState = _uiState as StateFlow<CampaignDetailUiState>

fun start(campaignId: Int, campaignDetailPageSource: CampaignDetailPageSource) {
fun start(campaignId: String, campaignDetailPageSource: CampaignDetailPageSource) {
this.campaignId = campaignId
this.pageSource = campaignDetailPageSource

Expand Down Expand Up @@ -76,7 +76,7 @@ class CampaignDetailViewModel @Inject constructor(
pathComponents = arrayOf(
ADVERTISING_PATH,
CAMPAIGNS_PATH,
campaignId.toString(),
campaignId,
extractAndSanitizeSiteUrl()
),
source = pageSource.trackingName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,26 @@ class FetchCampaignListUseCase @Inject constructor(
private val store: BlazeCampaignsStore,
private val mapper: CampaignListingUIModelMapper
) {
companion object {
const val PAGE_SIZE = 10
}

@Suppress("ReturnCount")
suspend fun execute(site: SiteModel, page: Int): Result<NetworkError, List<CampaignModel>> {
val result = store.fetchBlazeCampaigns(site, page)
if (result.isError || result.model == null) return Result.Failure(GenericError)
suspend fun execute(
site: SiteModel,
offset: Int,
pageSize: Int = PAGE_SIZE
): Result<NetworkResult, FetchedCampaignsResult> {
val result = store.fetchBlazeCampaigns(site = site, offset = offset, perPage = pageSize)
if (result.isError || result.model == null) return Result.Failure(GenericResult)
val campaigns = result.model!!.campaigns
if (campaigns.isEmpty()) return Result.Failure(NoCampaigns)
return Result.Success(mapper.mapToCampaignModels(campaigns))
return Result.Success(
FetchedCampaignsResult(
campaigns = mapper.mapToCampaignModels(campaigns),
totalItems = result.model!!.totalItems
)
)
}
}

Expand All @@ -24,14 +37,19 @@ class GetCampaignListFromDbUseCase @Inject constructor(
private val mapper: CampaignListingUIModelMapper
) {
suspend fun execute(site: SiteModel): Result<NoCampaigns, List<CampaignModel>> {
val result = store.getBlazeCampaigns(site)
if (result.campaigns.isEmpty()) return Result.Failure(NoCampaigns)
return Result.Success(mapper.mapToCampaignModels(result.campaigns))
val campaigns = store.getBlazeCampaigns(site)
if (campaigns.isEmpty()) return Result.Failure(NoCampaigns)
return Result.Success(mapper.mapToCampaignModels(campaigns))
}
}

sealed interface NetworkError
data class FetchedCampaignsResult(
val campaigns: List<CampaignModel>,
val totalItems: Int
)

sealed interface NetworkResult

object GenericError : NetworkError
object GenericResult : NetworkResult

object NoCampaigns : NetworkError
object NoCampaigns : NetworkResult
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import org.wordpress.android.ui.stats.refresh.utils.ONE_THOUSAND
import org.wordpress.android.ui.stats.refresh.utils.StatsUtils
import org.wordpress.android.ui.utils.UiString
import javax.inject.Inject

const val CENTS_IN_DOLLARS = 100
import kotlin.math.roundToInt

class CampaignListingUIModelMapper @Inject constructor(
private val statsUtils: StatsUtils
Expand All @@ -24,7 +23,7 @@ class CampaignListingUIModelMapper @Inject constructor(
featureImageUrl = campaignModel.imageUrl,
impressions = mapToStatsStringIfNeeded(campaignModel.impressions),
clicks = mapToStatsStringIfNeeded(campaignModel.clicks),
budget = convertToDollars(campaignModel.budgetCents)
budget = UiString.UiStringText("$${campaignModel.totalBudget.roundToInt()}")
)
}

Expand All @@ -36,8 +35,4 @@ class CampaignListingUIModelMapper @Inject constructor(
null
}
}

private fun convertToDollars(budgetCents: Long): UiString {
return UiString.UiStringText("$" + (budgetCents / CENTS_IN_DOLLARS).toString())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ class CampaignListingViewModel @Inject constructor(
private val _onSelectedSiteMissing = MutableLiveData<Unit>()
val onSelectedSiteMissing = _onSelectedSiteMissing as LiveData<Unit>

private var page = 1
private var limitPerPage: Int = 10
private var offset = 0
private var isLastPage: Boolean = false

fun start(campaignListingPageSource: CampaignListingPageSource) {
Expand Down Expand Up @@ -79,11 +78,11 @@ class CampaignListingViewModel @Inject constructor(
if (networkUtilsWrapper.isNetworkAvailable().not()) {
showNoNetworkError()
} else {
when (val campaignResult = fetchCampaignListUseCase.execute(site, page)) {
is Result.Success -> showCampaigns(campaignResult.value)
when (val campaignResult = fetchCampaignListUseCase.execute(site, offset)) {
is Result.Success -> showCampaigns(campaignResult.value.campaigns)
is Result.Failure -> {
when (campaignResult.value) {
is GenericError -> showGenericError()
is GenericResult -> showGenericError()
is NoCampaigns -> showNoCampaigns()
}
}
Expand Down Expand Up @@ -119,7 +118,6 @@ class CampaignListingViewModel @Inject constructor(
(_uiState.value as CampaignListingUiState.Success).pagingDetails.loadingNext.not() &&
isLastPage.not()
) {
page++
showLoadingMore()
fetchMoreCampaigns()
}
Expand All @@ -131,16 +129,18 @@ class CampaignListingViewModel @Inject constructor(
disableLoadingMore()
showSnackBar(R.string.campaign_listing_page_error_refresh_no_network_available)
} else {
when (val campaignResult = fetchCampaignListUseCase.execute(site, page)) {
when (val campaignResult = fetchCampaignListUseCase.execute(site, offset)) {
is Result.Success -> {
val currentUiState = _uiState.value as CampaignListingUiState.Success
isLastPage = campaignResult.value.isEmpty() || campaignResult.value.size < limitPerPage
showCampaigns(currentUiState.campaigns + campaignResult.value)
val allCampaigns = currentUiState.campaigns + campaignResult.value.campaigns
isLastPage = allCampaigns.size >= campaignResult.value.totalItems
offset += allCampaigns.size
showCampaigns(allCampaigns)
}

is Result.Failure -> {
when (campaignResult.value) {
is GenericError -> {
is GenericResult -> {
disableLoadingMore()
showSnackBar(R.string.campaign_listing_page_error_refresh_could_not_fetch_campaigns)
}
Expand Down Expand Up @@ -168,7 +168,7 @@ class CampaignListingViewModel @Inject constructor(
}

private fun onCampaignClicked(campaignModel: CampaignModel) {
_navigation.postValue(Event(CampaignListingNavigation.CampaignDetailPage(campaignModel.id.toInt())))
_navigation.postValue(Event(CampaignListingNavigation.CampaignDetailPage(campaignModel.id)))
}

private fun showNoCampaigns() {
Expand All @@ -180,23 +180,23 @@ class CampaignListingViewModel @Inject constructor(
}

fun refreshCampaigns() {
page = 1
launch {
_refresh.postValue(true)
if (!networkUtilsWrapper.isNetworkAvailable()) {
_refresh.postValue(false)
showSnackBar(R.string.campaign_listing_page_error_refresh_no_network_available)
} else {
when (val campaignResult = fetchCampaignListUseCase.execute(site, page)) {
offset = 0
when (val campaignResult = fetchCampaignListUseCase.execute(site, offset)) {
is Result.Success -> {
_refresh.postValue(false)
isLastPage = false
showCampaigns(campaignResult.value)
showCampaigns(campaignResult.value.campaigns)
}

is Result.Failure -> {
when (campaignResult.value) {
is GenericError -> {
is GenericResult -> {
_refresh.postValue(false)
showSnackBar(R.string.campaign_listing_page_error_refresh_could_not_fetch_campaigns)
}
Expand All @@ -223,7 +223,7 @@ enum class CampaignListingPageSource(val trackingName: String) {

sealed class CampaignListingNavigation {
data class CampaignDetailPage(
val campaignId: Int,
val campaignId: String,
val campaignDetailPageSource: CampaignDetailPageSource = CampaignDetailPageSource.CAMPAIGN_LISTING_PAGE
) : CampaignListingNavigation()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ class BlazeCardViewModelSlice @Inject constructor(
Event(SiteNavigationAction.OpenPromoteWithBlazeOverlay(source = BlazeFlowSource.DASHBOARD_CARD))
}

private fun onCampaignClick(campaignId: Int) {
private fun onCampaignClick(campaignId: String) {
_onNavigation.value =
Event(SiteNavigationAction.OpenCampaignDetailPage(campaignId, CampaignDetailPageSource.DASHBOARD_CARD))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,12 +326,12 @@ sealed class MySiteCardAndItem(open val type: Type, open val activeQuickStartIte
val moreMenuOptions: MoreMenuOptions
) : BlazeCard(type = Type.BLAZE_CAMPAIGNS_CARD) {
data class BlazeCampaignsCardItem(
val id: Int,
val id: String,
val title: UiString,
val status: CampaignStatus?,
val featuredImageUrl: String?,
val stats: BlazeCampaignStats?,
val onClick: (campaignId: Int) -> Unit,
val onClick: (campaignId: String) -> Unit,
) {
data class BlazeCampaignStats(
val impressions: UiString,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ sealed class MySiteCardAndItemBuilderParams {
data class CampaignWithBlazeCardBuilderParams(
val campaign: BlazeCampaignModel,
val onCreateCampaignClick: () -> Unit,
val onCampaignClick: (campaignId: Int) -> Unit,
val onCampaignClick: (campaignId: String) -> Unit,
val onCardClick: () -> Unit,
val moreMenuParams: MoreMenuParams
) : BlazeCardBuilderParams() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ sealed class SiteNavigationAction {
data class OpenCampaignListingPage(val campaignListingPageSource: CampaignListingPageSource) :
SiteNavigationAction()

data class OpenCampaignDetailPage(val campaignId: Int, val campaignDetailPageSource: CampaignDetailPageSource) :
data class OpenCampaignDetailPage(val campaignId: String, val campaignDetailPageSource: CampaignDetailPageSource) :
SiteNavigationAction()

object OpenDashboardPersonalization : SiteNavigationAction()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class BlazeCardSource @Inject constructor(
if (networkUtilsWrapper.isNetworkAvailable().not()) {
getMostRecentCampaignFromDb(site)
} else {
when (fetchCampaignListUseCase.execute(site = site, page = 1)) {
when (fetchCampaignListUseCase.execute(site = site, offset = 0)) {
is Result.Success -> getMostRecentCampaignFromDb(site)
// there are no campaigns or if there is an error , show blaze promo card
is Result.Failure -> showPromoteWithBlazeCard()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import org.wordpress.android.util.AppLog
@Suppress("MagicNumber")

enum class CampaignStatus(val status: String, @StringRes val stringResource: Int) {
InModeration("pending", R.string.campaign_status_in_moderation),
Scheduled("scheduled", R.string.campaign_status_scheduled),
Active("active", R.string.campaign_status_active),
Completed("finished", R.string.campaign_status_completed),
Rejected("rejected", R.string.campaign_status_rejected),
Canceled("canceled", R.string.campaign_status_canceled),
Scheduled("scheduled", R.string.campaign_status_scheduled),
InModeration("created", R.string.campaign_status_in_moderation);
Completed("finished", R.string.campaign_status_completed);

companion object {
fun fromString(status: String): CampaignStatus? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class CampaignDetailViewModelTest : BaseUnitTest() {
}
@Test
fun `given valid campaignId and pageSource, when start is called, then trackCampaignDetailsOpened is called`() {
viewModel.start(1, CampaignDetailPageSource.DASHBOARD_CARD)
viewModel.start(campaignId = "1", CampaignDetailPageSource.DASHBOARD_CARD)

verify(blazeFeatureUtils).trackCampaignDetailsOpened(any())
}
Expand All @@ -89,7 +89,7 @@ class CampaignDetailViewModelTest : BaseUnitTest() {
val uiState = mutableListOf<CampaignDetailUiState>()
val actionEvents = mutableListOf<BlazeActionEvent>()
testWithData(actionEvents, uiState) {
viewModel.start(1, CampaignDetailPageSource.DASHBOARD_CARD)
viewModel.start(campaignId = "1", CampaignDetailPageSource.DASHBOARD_CARD)

assertThat(uiState.last()).isInstanceOf(CampaignDetailUiState.GenericError::class.java)
}
Expand All @@ -102,7 +102,7 @@ class CampaignDetailViewModelTest : BaseUnitTest() {
val uiState = mutableListOf<CampaignDetailUiState>()
val actionEvents = mutableListOf<BlazeActionEvent>()
testWithData(actionEvents, uiState) {
viewModel.start(1, CampaignDetailPageSource.DASHBOARD_CARD)
viewModel.start(campaignId = "1", CampaignDetailPageSource.DASHBOARD_CARD)

assertThat(uiState.last()).isInstanceOf(CampaignDetailUiState.GenericError::class.java)
}
Expand All @@ -122,7 +122,7 @@ class CampaignDetailViewModelTest : BaseUnitTest() {
val uiStates = mutableListOf<CampaignDetailUiState>()
val actionEvents = mutableListOf<BlazeActionEvent>()
testWithData(actionEvents, uiStates) {
viewModel.start(1, CampaignDetailPageSource.DASHBOARD_CARD)
viewModel.start(campaignId = "1", CampaignDetailPageSource.DASHBOARD_CARD)

assertThat(uiStates.last()).isInstanceOf(CampaignDetailUiState.Prepared::class.java)
}
Expand All @@ -135,7 +135,7 @@ class CampaignDetailViewModelTest : BaseUnitTest() {
val uiStates = mutableListOf<CampaignDetailUiState>()
val actionEvents = mutableListOf<BlazeActionEvent>()
testWithData(actionEvents, uiStates) {
viewModel.start(1, CampaignDetailPageSource.DASHBOARD_CARD)
viewModel.start(campaignId = "1", CampaignDetailPageSource.DASHBOARD_CARD)

assertThat(uiStates.first()).isInstanceOf(CampaignDetailUiState.Preparing::class.java)
}
Expand Down Expand Up @@ -177,7 +177,7 @@ class CampaignDetailViewModelTest : BaseUnitTest() {
whenever(networkUtilsWrapper.isNetworkAvailable()).thenReturn(false)

testWithData(actionEvents, uiStates) {
viewModel.start(1, CampaignDetailPageSource.DASHBOARD_CARD)
viewModel.start(campaignId = "1", CampaignDetailPageSource.DASHBOARD_CARD)

val uiState = uiStates.last()
assertThat(uiState).isInstanceOf(CampaignDetailUiState.NoNetworkError::class.java)
Expand Down
Loading

0 comments on commit 5e6a217

Please sign in to comment.