-
Notifications
You must be signed in to change notification settings - Fork 118
[Local catalog] Feature eligibility checker #16276
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Local catalog] Feature eligibility checker #16276
Conversation
This allows us to directly check the feature flag
Generated by 🚫 Danger |
|
|
My experience so far with the local catalog tasks 😀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the work!
It will help a lot with the future tasks.
In practice, it worked well and I couldn't break it.
I made a few comments for now and for the future. I think for tidiness we should move the creation to POSTabCoordinator to have everything in a single place.
Also, consider refreshing strategy, possibly utilizing the foreground dispatcher. If we don't want eligibility to be able to change in the middle of using POS, we could consider just injecting the eligibilityState into any POS controllers instead of the service itself, to guard against the cases of inconsistency.
WooCommerce/Classes/POS/Eligibility/POSLocalCatalogEligibilityService.swift
Outdated
Show resolved
Hide resolved
| // Check local catalog eligibility before initializing infrastructure | ||
| // Try to use pre-created service from eligibility checker, otherwise create it now | ||
| let eligibilityService: POSLocalCatalogEligibilityServiceProtocol | ||
| if let preCreatedService = (eligibilityChecker as? POSTabEligibilityChecker)?.localCatalogEligibilityService { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could include it in POSEntryPointEligibilityCheckerProtocol, or even use POSTabEligibilityChecker directly in POSTabCoordinator. This coordinator doesn't rely on protocols. It's like a factory that builds and uses concrete types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, good point. I'll neaten this up, the optionality here was annoying me anyway.
| private let siteSettingService: POSSiteSettingServiceProtocol | ||
| private let appPasswordSupportState: ApplicationPasswordsExperimentState | ||
|
|
||
| /// Local catalog eligibility service - created asynchronously on init |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at how dependencies are created, and I think this could be moved from POSTabEligibilityChecker to POSTabCoordinator.
POSTabCoordinator is a good choice since:
- It's created at the time of login/site changes
- Responsible for creating/providing POS dependencies
- A single place where we create a local catalog eligibility checker
- We pass
tabEligibilityCheckerinto init which is convenient - We need the dependency to be within
POSTabCoordinatorto check the eligibility
Therefore, we could move the Task { } into the init of POSTabCoordinator and the tab eligibility checker wouldn't be affected. We could still use stores to get credentials and selectedSite.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was there originally. The reason I moved it to the tab eligibility checker was to cleanly check that the store was actually POS eligible in the flow of doing the local catalog eligibility check. I'll look again though, and see if I can do both.
| switch eligibilityService.eligibilityState { | ||
| case .ineligible(reason: .catalogSizeCheckFailed): | ||
| // If we cached a failed check, we can recover by refreshing the value before we next open POS | ||
| await eligibilityService.refreshEligibilityState() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I understand, we will get a refreshed state in 3 other cases:
- After the login
- After changing the site
- Fresh launch of the app
If POS and the app are continued to be used, we're not checking eligibility. If we introduce a remote feature flag, for example, we may want to know earlier if the site is still eligible.
One of the potential places is ForegroundPOSCatalogSyncDispatcher. We not only need to introduce eligibilityState checks into the dispatcher, but it could also refresh the state before checking. We could make sure a task is created to do that. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing I want to avoid is the eligibility changing from while POS is in use. If it did, we'd need to restart POS, or we'd have had to use your other suggestion of passing only the initial state in so it stayed that way throughout.
If POS and the app are continued to be used, we're not checking eligibility. If we introduce a remote feature flag, for example, we may want to know earlier if the site is still eligible.
That's true, and really it's deliberate. I don't know when we actually fetch remote feature flags, but perhaps we should also refresh eligibility when they leave POS – I thought maybe on foreground, but I don't really want to do it if they foreground the app in POS mode.
One of the potential places is ForegroundPOSCatalogSyncDispatcher. We not only need to introduce eligibilityState checks into the dispatcher, but it could also refresh the state before checking
AIUI, the foreground sync lives outside POS's context, and runs while the app is in the foreground, regardless of whether POS is in use. So it is appropriate to respect eligibility changes here, even though that would lead to a degraded experience if it went from eligible to ineligible while the POS was in use. Trouble is, refreshing the state here would result in it being ineligible everywhere.
It's kind of tricky. On one hand, we have a good single source of truth right now for "does the app think that this site is local catalog eligible". It might be outdated... but it can only change at times when we're actually in a position to do something about it – when POS is not in use.
On the other hand, there are arguments for losing that single source of truth in favour of passing a snapshot of the eligibility at a particular point in time to the different parts of the POS. I'd really rather avoid the potential inconsistencies with doing that, though.
I do worry a little about this – we still have POS tab as a discrete mode in the app, but that feels like something which can't last forever, and I'd rather not rely on it too much. While we do have it, it still feels best to only check the eligibility when we're not in POS mode. I recognise that someone using as a kiosk may never get an updated value... but given this is a temporary check (we'll eventually use local catalog for everyone) perhaps that's not too big a risk?
| } | ||
|
|
||
| private func checkLocalCatalogEligibility() { | ||
| isLocalCatalogEligible = localCatalogEligibilityService?.eligibilityState == .eligible |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This kind of implementation (deliberately?) assumes that eligibilityState cannot change while the POS is in use. Which may be what we want, so I just want to make sure it's intended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's intended... but we build that in to the service, so we could just use the service directly and still get the benefit.
| case posTabNotEligible | ||
| case featureFlagDisabled | ||
| case catalogSizeTooLarge(totalCount: Int, limit: Int) | ||
| case catalogSizeCheckFailed(underlyingError: String) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 This is good and tidy, we will be able to include all the ineligibility cases here.
| eligibilityService = preCreatedService | ||
| } else { | ||
| // Fallback: assume we're POS eligible and create service | ||
| eligibilityService = await POSLocalCatalogEligibilityService( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recognize this is not ideal since it would block the UI with no loading indication after pressing the tab. I tested with the network link conditioner, and the delay is fairly manageable, even with Edge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's annoying but I felt like it was an acceptable trade off too. Thanks for checking it. We could make yet another loading screen, but I really want to avoid that, and we can't safely use the existing ones when we don't know the catalog type.
| Task { @MainActor [weak self] in | ||
| guard let self else { return } | ||
|
|
||
| let service = await POSLocalCatalogEligibilityService( |
There was a problem hiding this comment.
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 cannot await checkEligibility() here to set isPOSTabVisible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Visibility and eligibility are different... Ineligible sites can still have the tab visible, where we use it as an upsell.
The ticket (it turns out) was worded as visibility, and we're discussing which it should be here: p1761304079623909/1757587788.433699-slack-C070SJRA8DP
I'm going to keep it as visibility for now, and we can revert the last commit to go back to eligibility if we change our minds.

Merge after: #16277
Description
This PR adds a POSLocalCatalogEligibilityService to check whether the local catalog is available, rather than relying on the presence of GRDBManager/Sync Coordinator. I also took
We check the following conditions at present:
In future, we can add additional conditions:
We create it as part of the
POSTabEligibilityChecker, which means it's updated when we switch store and ready by the time the tab is tapped.Apologies it's so long. Getting it to work properly had more edge cases than I expected.
Testing information
You can test whether the local catalog is used by opening POS and scrolling – it's clear from the loading behaviour which is in use. You can also check the logs for messages including
POSLocalCatalogEligibilityServiceI've tested the following scenarios
RELEASE-NOTES.txtif necessary.