Skip to content

Conversation

@JorgeMucientes
Copy link
Contributor

@JorgeMucientes JorgeMucientes commented Sep 19, 2025

Closes: WOOMOB-1351

Description

This is a follow-up PR to handle the visibility of the Booking tab dynamically based on the following criteria:

  • Site is CIAB
  • Site has at least 1 bookable product published
  • Feature flag is enabled locally

I've added a Flow to keep any changes around those 3 conditions reactive.

Testing information

To make tests quicker I recommed logging into the app with a WP.com account that has at least access to:

  • A WP.com/Jetpack connected site non CIAB
  • A CIAB site with Bookings plugin installed
  1. Log into a CIAB site with Bookings plugin enabled. Have at least one Bookable product created. You can create such sites from matticspace tools/garden/index.php
  2. Check the new bookings tab is shown.
  3. Edit the bookable product and make it a draft. Notice how the bookings tab is gone
  4. Publish the bookable product again and see how tab is visible again
  5. Delete the product completely and check tab is gone.
  6. Change to a non CIAB site with bookable products published and check the Bookings tab is still gone.

The tests that have been performed

The above ☝🏼

Images/gif

Screen_recording_20250919_183429.mp4
  • I have considered if this change warrants release notes and have added them to RELEASE-NOTES.txt if necessary. Use the "[Internal]" label for non-user-facing changes.

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Sep 19, 2025

📲 You can test the changes from this Pull Request in WooCommerce-Wear Android by scanning the QR code below to install the corresponding build.
App Name WooCommerce-Wear Android
Platform⌚️ Wear OS
FlavorJalapeno
Build TypeDebug
Commit34a3bb1
Direct Downloadwoocommerce-wear-prototype-build-pr14640-34a3bb1.apk

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Sep 19, 2025

📲 You can test the changes from this Pull Request in WooCommerce Android by scanning the QR code below to install the corresponding build.

App Name WooCommerce Android
Platform📱 Mobile
FlavorJalapeno
Build TypeDebug
Commit34a3bb1
Direct Downloadwoocommerce-prototype-build-pr14640-34a3bb1.apk

import com.woocommerce.android.ciab.CIABSiteGateKeeper.Companion.CIAB_GARDEN_NAME
import org.wordpress.android.fluxc.model.SiteModel

fun SiteModel.isCIABSite() = isGardenSite && gardenName == CIAB_GARDEN_NAME
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsure this is the best place for such extension. Open to suggestions

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have SiteModelExt for similar extensions, WDYT?

Comment on lines 40 to 42
selectedSite.observe()
.filter { it != null }
.collect { siteModel ->
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We start observing bookable products only when we know for sure a site has been selected. Otherwise the app will crash. For example when loggin into the app for the first time, MainActivity is resumed before the app has finished selecting a site, leading to a crash.

@JorgeMucientes JorgeMucientes marked this pull request as ready for review September 19, 2025 16:46
@codecov-commenter
Copy link

codecov-commenter commented Sep 19, 2025

Codecov Report

❌ Patch coverage is 68.57143% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 38.50%. Comparing base (8e165c0) to head (34a3bb1).
⚠️ Report is 145 commits behind head on trunk.

Files with missing lines Patch % Lines
...id/ui/bookings/tab/ObserveBookingsTabVisibility.kt 79.31% 2 Missing and 4 partials ⚠️
...e/android/ui/bookings/tab/BookingsTabController.kt 0.00% 4 Missing ⚠️
...com/woocommerce/android/ciab/CIABSiteGateKeeper.kt 0.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##              trunk   #14640      +/-   ##
============================================
+ Coverage     38.49%   38.50%   +0.01%     
- Complexity     9767     9771       +4     
============================================
  Files          2064     2064              
  Lines        115429   115448      +19     
  Branches      15370    15372       +2     
============================================
+ Hits          44430    44459      +29     
+ Misses        66883    66872      -11     
- Partials       4116     4117       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@hichamboushaba hichamboushaba self-assigned this Sep 22, 2025
Copy link
Member

@hichamboushaba hichamboushaba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work @JorgeMucientes, the logic is working as expected, but I think we can make some improvements to fix some issues that I shared below, please check the comments.

import com.woocommerce.android.ciab.CIABSiteGateKeeper.Companion.CIAB_GARDEN_NAME
import org.wordpress.android.fluxc.model.SiteModel

fun SiteModel.isCIABSite() = isGardenSite && gardenName == CIAB_GARDEN_NAME
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have SiteModelExt for similar extensions, WDYT?

.onFailure {
// TODO log error or track errors?
selectedSite.observe()
.filter { it != null }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest using filterNotNull here, it will automatically cast the variable and allow you to get rid of the double-bang operator below.

}
.map { productsCount ->
productsCount > 0 &&
siteModel?.isCIABSite() == true &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

siteModel is non-nullable here

Suggested change
siteModel?.isCIABSite() == true &&
siteModel.isCIABSite() == true &&

),
excludeSampleProducts = true
).onStart {
productListRepository.fetchProductList(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this logic, we'll make an API request to fetch bookable products for all sites, WDYT about changing the order of things slightly to make sure we return an early false when the site is not CIAB, soemthing like the following:

    operator fun invoke(siteModel: SiteModel): Flow<Boolean> = flow {
        val isCIABSite = FeatureFlag.BOOKINGS_MVP.isEnabled() && siteModel.isCIABSite()
        if (!isCIABSite) {
            emit(false)
        } else {
            emitAll(
                productListRepository.observeProductsCount(
                    filterOptions = mapOf(
                        ProductFilterOption.STATUS to ProductStatus.PUBLISH.value,
                        ProductFilterOption.TYPE to BOOKING_PRODUCT_TYPE
                    ),
                    excludeSampleProducts = true
                ).map { count ->
                    count > 0
                }.onStart {
                    productListRepository.fetchProductList(
                        productFilterOptions = mapOf(ProductFilterOption.TYPE to BOOKING_PRODUCT_TYPE)
                    ).onFailure {
                        WooLog.w(WooLog.T.BOOKINGS, "Failed to fetch bookable products")
                    }
                }.distinctUntilChanged()
            )
        }
    }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another point related to this, with the fetch happening in onStart, we don't emit any value until it's done, and this results in showing the bookings tab before hiding it when switching to any site that doesn't have the requirements:

Screen_recording_20250922_142723.mp4

This issue affects both your version and the above suggested version (for CIAB sites that don't have bookable products), we can avoid this by making the fetch async:

    .onStart {
        appCoroutineScope.launch {
            productListRepository.fetchProductList(
                productFilterOptions = mapOf(ProductFilterOption.TYPE to BOOKING_PRODUCT_TYPE)
            ).onFailure {
                WooLog.w(WooLog.T.BOOKINGS, "Failed to fetch bookable products")
            }
        }
    }

owner.lifecycle.removeObserver(this)
}

private fun checkBookingsTabVisibility() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related to this PR, but just sharing an observation here, I think that given we use activity.lifecycleScope.launch in this function, it's safe to call it directly from init without making this class a LifecycleObserver, lifecycleScope already takes care of this, and won't start the Coroutine until the Activity is started.

private val showBookingsTab: ShowBookingsTab
) : DefaultLifecycleObserver {
private val observeBookingsTabVisibility: ObserveBookingsTabVisibility,
private val selectedSite: SelectedSite
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Is there a reason, we can't inject the selectedSite in ObserveBookingsTabVisibility and keep all the logic there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't gave much though to it tbh. Any advantages I'm not seeing from having everything in ObserveBookingsTabVisibility?
I feel the current implementation is readable/clean enough, but I'm open to move the selected site to ObserveBookingsTabVisibility.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things I see:

  1. Keeping the whole tab visibility logic in one place.
  2. ObserveBookingsTabVisibilityTest - the BookingsTabController doesn't have tests, and because of Android dependencies, it's a bit trickier to add them. Keeping everything in ObserveBookingsTabVisibility makes it possible to test the whole flow, including the suspending issue mentioned with the previous code.

@JorgeMucientes
Copy link
Contributor Author

JorgeMucientes commented Sep 23, 2025

Excellent feedback @hichamboushaba @AdamGrzybkowski thank you. Great eye for detail! I've addressed all your suggested feedback. Ready for another round :)

Copy link
Member

@hichamboushaba hichamboushaba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work @JorgeMucientes, thanks for taking care of this.

I won't merge this to give you a chance to check Adam's last remark about moving SelectedSite to ObserveBookingsTabVisibility.

Copy link
Contributor

@AdamGrzybkowski AdamGrzybkowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested and looks good 👍

The other comment is non-blocking so approving!

@JorgeMucientes
Copy link
Contributor Author

Thanks for all the feedback folks. I've moved the logic around observing selected site to ObserveBookingsTabVisibility and kept all the logic there as suggested. The provided reasons where good enough to apply the refactor.

@JorgeMucientes JorgeMucientes merged commit add243f into trunk Sep 24, 2025
15 checks passed
@JorgeMucientes JorgeMucientes deleted the issue/woomob-1351-add-logic-to-show-or-hide-the-booking-tab-dynamically branch September 24, 2025 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants