Skip to content

[POS as a tab i2] Refresh POS eligibility in ineligible UI: WC plugin ineligible cases #15908

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

Conversation

jaclync
Copy link
Contributor

@jaclync jaclync commented Jul 14, 2025

For WOOMOB-854
Just one review is required.

Apology for the larger diffs, close to 300 diffs were on the unit tests.

Description

This PR completes the POS eligibility refresh implementation for plugin reasons, utilizing the new POSSystemStatusService from #15906 to efficiently refresh both WooCommerce plugin information and POS feature switch values (for plugin version 10.0+) with a single API request. Now a system status API request is made for plugin & POS feature switch checks on every POS tab tap and refresh.

Key changes:

  • Improved checkPluginEligibility method: Refactored to call POSSystemStatusService to load the WC plugin & POS feature switch value from one system status API request.
  • Updated refreshEligibility method: Now handles unsupportedWooCommerceVersion and wooCommercePluginNotFound cases by calling checkEligibility that includes checkPluginEligibility to reload the WC plugin & POS feature switch value.
  • Removed PluginsService dependency: Cleaned up POSTabEligibilityChecker by removing the unused PluginsService dependency since all plugin data now comes through POSSystemStatusService. This simplifies the architecture and reduces the number of dependencies.
  • Removed POSIneligibleReason.featureSwitchSyncFailure: Eliminated this reason since POS feature switch loading is now combined with WooCommerce plugin loading through the system status service, making separate sync failure handling unnecessary.
  • Added persistence for plugins from system status API request: Updated POSSystemStatusService to fetch both active and inactive plugins, upsert all plugins to storage using modern async/await patterns, and ensure data consistency by loading from storage after updates.
  • System status API timing: The system status API request is made on the first POS tab tap when plugin eligibility needs to be checked (otherwise, the plugin from storage can be outdated upon store launch), and each refresh from plugin ineligible reasons to ensure the most up-to-date plugin information.
  • Updated/added unit tests: Added test cases with storage mocking and helper methods for better maintainability. Updated all tests to remove PluginsService dependencies and use only POSSystemStatusService.
  • Added async/await storage extension: Created modern async/await wrapper for StorageManagerType.performAndSave to enable cleaner asynchronous storage operations.
  • (Not directly related to the PR, but discovered during the PR work) Updated HubMenu: Uses LegacyPOSTabEligibilityChecker when the pointOfSaleOrdersi1 feature flag is disabled. This feature flag will be removed in the next sprint.

Steps to reproduce

Unsupported WooCommerce version

Prerequisite: for a store that is eligible for POS, downgrade the WooCommerce plugin to version 9.5.0 or lower. For easier testing, I'd recommend using the WooCommerce Beta Tester plugin on a store where the WC plugin is not managed by the host like a JN store (for WPCOM stores and default Pressable WC stores, WC plugin is automatically managed by the host. In Pressable, there is an option to manually manage the WC plugin when creating a site).

  • Launch the app with the site in the prerequisite --> the POS tab should appear shortly
  • Tap on the POS tab --> after the loading state, an ineligible screen should be shown about the unsupported WC version
  • Tap Retry --> the CTA should be in loading state, then back to normal state (the WC version hasn't been fixed)
  • In wp-admin, update the WooCommerce plugin to version 9.6.0 or higher
  • Back in the app, tap Retry --> the CTA should be in loading state, then the app enters POS to the state where products are loaded
  • Exit POS and tap the POS tab again --> POS should be launched right away

Testing information

I tested the above case with both WC plugin version scenarios (below 10.0 and 10.0+) and verified the feature switch handling works correctly for version 10.0+. The storage improvements ensure that plugin data is properly synchronized and persisted for consistent eligibility checks and other use cases in the app like Menu > Settings > Plugins.

Example screencast

ineligible UI with WC version 9.5.2 -> refresh without changes -> update WC version to 10.0+ in wp-admin -> refresh -> POS -> exit and enter POS again -> relaunch app and tap to enter POS

ineligible-wc-version-v2.mp4

  • I have considered if this change warrants user-facing release notes and have added them to RELEASE-NOTES.txt if necessary.

jaclync added 2 commits July 14, 2025 10:33
…ntOfSaleOrdersi1` is disabled (it's currently enabled in production). Will be removed when `pointOfSaleOrdersi1` feature flag is removed.
@jaclync jaclync added type: task An internally driven task. feature: POS labels Jul 14, 2025
@jaclync jaclync added this to the 22.9 milestone Jul 14, 2025
@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Jul 14, 2025

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

App NameWooCommerce iOS Prototype
Build Numberpr15908-1098a34
Version22.8
Bundle IDcom.automattic.alpha.woocommerce
Commit1098a34
Installation URL5tdeqb8pdqm4g
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

jaclync added 6 commits July 14, 2025 11:47
…sert all plugins to storage so that later use cases have the latest value.
…o fix an issue where the plugin from storage is outdated before it is synced externally with a tradeoff of an extra API request when POS tab is tapped for the first time.
…ature switch loading is combined with WC plugin. Update test cases.
Comment on lines +69 to +98
// Active and inactive plugins share identical structure, but are stored in separate parts of the remote response
// (and without an active attribute in the response). So we apply the correct value for active (or not)
let readonlySystemPlugins: [SystemPlugin] = {
let activePlugins = systemStatus.activePlugins.map {
$0.copy(active: true)
}

let inactivePlugins = systemStatus.inactivePlugins.map {
$0.copy(active: false)
}

return activePlugins + inactivePlugins
}()

let storedPlugins = storage.loadSystemPlugins(siteID: siteID, matching: readonlySystemPlugins.map { $0.name })
readonlySystemPlugins.forEach { readonlySystemPlugin in
// Loads or creates new StorageSystemPlugin matching the readonly one.
let storageSystemPlugin: StorageSystemPlugin = {
if let systemPlugin = storedPlugins.first(where: { $0.name == readonlySystemPlugin.name }) {
return systemPlugin
}
return storage.insertNewObject(ofType: StorageSystemPlugin.self)
}()

storageSystemPlugin.update(with: readonlySystemPlugin)
}

// Removes stale system plugins.
let currentSystemPlugins = readonlySystemPlugins.map(\.name)
storage.deleteStaleSystemPlugins(siteID: siteID, currentSystemPlugins: currentSystemPlugins)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🗒️ This is mostly the same as the upsert logic in SystemStatusStore

func upsertSystemPlugins(siteID: Int64, readonlySystemInformation: SystemStatus, in storage: StorageType) {
/// Active and in-active plugins share identical structure, but are stored in separate parts of the remote response
/// (and without an active attribute in the response). So... we use the same decoder for active and in-active plugins
/// and here we apply the correct value for active (or not)
///
let readonlySystemPlugins: [SystemPlugin] = {
let activePlugins = readonlySystemInformation.activePlugins.map {
$0.copy(active: true)
}
let inactivePlugins = readonlySystemInformation.inactivePlugins.map {
$0.copy(active: false)
}
return activePlugins + inactivePlugins
}()
let storedPlugins = storage.loadSystemPlugins(siteID: siteID, matching: readonlySystemPlugins.map { $0.name })
readonlySystemPlugins.forEach { readonlySystemPlugin in
// load or create new StorageSystemPlugin matching the readonly one
let storageSystemPlugin: StorageSystemPlugin = {
if let systemPlugin = storedPlugins.first(where: { $0.name == readonlySystemPlugin.name }) {
return systemPlugin
}
return storage.insertNewObject(ofType: StorageSystemPlugin.self)
}()
storageSystemPlugin.update(with: readonlySystemPlugin)
}
// remove stale system plugins
let currentSystemPlugins = readonlySystemPlugins.map(\.name)
storage.deleteStaleSystemPlugins(siteID: siteID, currentSystemPlugins: currentSystemPlugins)
}

In WOOMOB-859, I plan to refactor the upsert logic for reuse with some enhancements to ensure we're matching plugins by the plugin path instead of name.

@jaclync jaclync requested review from iamgabrielma and staskus July 14, 2025 20:52
@staskus staskus self-assigned this Jul 15, 2025
Copy link
Contributor

@staskus staskus left a comment

Choose a reason for hiding this comment

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

👍 Looks good. Tested various cases with different WooCommerce versions and done additional testing with disabling other types of settings. The implementation holds well.

@jaclync jaclync merged commit 53e3bca into trunk Jul 15, 2025
14 checks passed
@jaclync jaclync deleted the feat/WOOMOB-854-refresh-pos-plugin-ineligible-reasons-with-system-status branch July 15, 2025 13:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature: POS type: task An internally driven task.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants