Skip to content

Conversation

@jaclync
Copy link
Contributor

@jaclync jaclync commented Oct 31, 2025

Part of WOOMOB-1609

Just one review is required.

⚠️ Based on #16298, please ensure it's approved without significant change requests before reviewing this PR

Description

This PR integrates the catalog API endpoints into the POS catalog sync flow behind a feature flag. The implementation adds support for forcing catalog regeneration and handles edge cases where the API returns variations without their parent products.

Key changes:

  • Feature flag integration: Added pointOfSaleCatalogAPI feature flag to control whether to use the new catalog API or the existing paginated endpoints. Default to false, as the catalog API is still not fully ready in core
  • Catalog regeneration: Implemented regenerateCatalog parameter to force catalog generation from merchant-initiated actions (e.g., manual refresh in catalog settings)
  • Full sync with catalog API: Integrated full sync with catalog API pcShBQ-3wD-p2#comment-5426
  • Orphaned variation filtering: Added logic to filter out variations whose parent products are not included in the catalog (occurs when variations have public status but parent products don't - right now, the API fetches simple, variable, variation product types with publish status. As a future improvement, we can update the product DB arguments later to include all status cases to avoid this workaround)

Test Steps

Prerequisites:

  • Web: install the WooCommerce and AI Product Feed plugins (zips at a Google Drive folder at secret store 13636) on a test site where WooCommerce isn't automatically managed by the host.
    • Ensure you have products with variations in the store, including some edge cases:
      • Variable products with public variations
      • Products with different statuses (some public, some not)
    • The self-managed-fun-shop (small store, < 30s), largefuntesting (~5 minutes), and largerfuntesting (~9 minutes) Pressable test sites already have this setup, please DM me if you'd like access.
  • iOS local changes:
    • If you're testing on a store with large catalog, update POSLocalCatalogEligibilityService's defaultCatalogSizeLimit from 1000 to a large number like 10000000.
    • Enable pointOfSaleCatalogAPI feature flag in DefaultFeatureFlagService.
  1. In the iOS app, wait for the initial POS full sync if it hasn't been synced before
  2. Navigate to POS settings > Catalog
  3. Manually trigger a catalog refresh using the refresh button
  4. Verify the catalog downloads successfully and variations are properly filtered

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

…-flag

# Conflicts:
#	Modules/Sources/Experiments/DefaultFeatureFlagService.swift
#	Modules/Sources/Experiments/FeatureFlag.swift
…er-initiated action, currently from the CTA in catalog settings.
…WOOMOB-1609-catalog-api-behind-feature-flag

# Conflicts:
#	Modules/Sources/Networking/Remote/POSCatalogSyncRemote.swift
#	Modules/Sources/Yosemite/Tools/POS/POSCatalogSyncCoordinator.swift
#	WooCommerce/Classes/Tools/BackgroundTasks/ForegroundPOSCatalogSyncDispatcher.swift
… currently returns all products and variations with public status.
@jaclync jaclync added status: feature-flagged Behind a feature flag. Milestone is not strongly held. feature: POS labels Oct 31, 2025
@jaclync jaclync added this to the 23.7 milestone Oct 31, 2025
@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Oct 31, 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 Numberpr16301-e1990ed
Version23.6
Bundle IDcom.automattic.alpha.woocommerce
Commite1990ed
Installation URL67jgi5g3dutgg
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@jaclync jaclync changed the title POS: Integrate catalog API with feature flag [Local Catalog] Integrate catalog API behind a feature flag Oct 31, 2025
@jaclync jaclync requested a review from joshheald October 31, 2025 07:45
@joshheald joshheald self-assigned this Oct 31, 2025
Copy link
Contributor

@joshheald joshheald left a comment

Choose a reason for hiding this comment

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

Works really well, thanks for integrating it! A few small points inline.

}

func pollForCatalogCompletion(siteID: Int64, syncRemote: POSCatalogSyncRemoteProtocol) async throws -> String {
// Each attempt is made 1 second after the last one completes.
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps we should have some backoff here – e.g. after a minute of checking every second, maybe every 5 seconds is fine even if it means someone's waiting 4 seconds longer than they would with 1s polling.

I also wonder whether we might have any rate limiting issues doing this with some hosts, WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Perhaps we should have some backoff here

Great idea, some progressive backoff (1s → 5s after 1 minute like you suggested) would be good here. Polling every second for potentially long-running catalog generations unnecessarily consumes server resources. I also plan to add some basic retry mechanism for the polling, so that one failing request doesn't end the full sync. I will implement both in a future PR.

I also wonder whether we might have any rate limiting issues doing this with some hosts, WDYT?

I did some search about rate limiting in WP/WC, there doesn't seem to be a built-in rate limiting system for the plugins and WC API. The WC Store API does have rate limiting support, with default maximum of 25 requests per 10 seconds. Some plugins like WordFence offer the rate limiting feature. Other findings:

  • Major hosting providers (WP Engine, Kinsta, SiteGround, Bluehost) don't seem to implement explicit REST API rate limiting but use resource-based limits (CPU, bandwidth, visits) instead.
  • WordPress VIP has global rate-limits (REST APIs aren't explicitly mentioned like XML-RPC and login endpoints, but I assume rate limiting applies to API requests too) of 10 requests per second.

Right now, the polling frequency is a bit under 1 request per second due to the overhead of the request (one request is made after the end of the previous one), and likely fine for the common default settings. However, the backoff implementation will further help reduce the risk of encountering server resource issues.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the research!

let variations = catalog.variations.filter { variation in
let parentExists = productIDs.contains { $0 == variation.productID }
if !parentExists {
DDLogWarn("Variation \(variation.productVariationID) references missing product \(variation.productID)")
Copy link
Contributor

Choose a reason for hiding this comment

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

Good thinking logging this.

Suggested change
DDLogWarn("Variation \(variation.productVariationID) references missing product \(variation.productID)")
DDLogWarn("Variation \(variation.productVariationID) references missing product \(variation.productID) - it will not be available in POS.")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated in 786a89a.

}
return downloadURL
case .pending, .processing:
DDLogInfo("🟣 Catalog request \(response.status)... (attempt \(attempts + 1)/\(maxAttempts))")
Copy link
Contributor

Choose a reason for hiding this comment

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

This will flood the logs, for large catalogs...

Currently we log with .verbose even in prod. Perhaps we should change that to .info in AppDelegate, and then update these to be Debug logs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch! As an alternative to making global changes for the whole app, I reduced the logging noise by only logging every 10th polling attempt in e1990ed. This keeps the logs useful for debugging (every ~10 seconds with 1s polling or ~50 seconds with 5s backoff polling later) without flooding them for long-running catalog generations. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

That's fine, yeah... but I do think we should change this separately anyway.

IMO, the right way to do this would be to make this a .debug or even .verbose level log. Then we keep debug builds log output set to verbose and set production logging to info. We'd have to check there aren't other places where we're using debug log levels that we want for debugging customer issues, so it makes sense not to do that in this PR.

Base automatically changed from feat/WOOMOB-1609-catalog-api-networking-layer to trunk November 3, 2025 07:07
@jaclync
Copy link
Contributor Author

jaclync commented Nov 4, 2025

Merging now, will work on the follow-up tasks in #16301 (comment). Feel free to comment on this PR still.

@jaclync jaclync merged commit df2e5eb into trunk Nov 4, 2025
14 checks passed
@jaclync jaclync deleted the feat/WOOMOB-1609-catalog-api-behind-feature-flag branch November 4, 2025 01:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: POS status: feature-flagged Behind a feature flag. Milestone is not strongly held.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants