-
Notifications
You must be signed in to change notification settings - Fork 120
[Local Catalog] Show items from GRDB in POS #16235
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] Show items from GRDB in POS #16235
Conversation
|
@staskus Sorry it took longer than I thought it would, but here's the second PR as mentioned this morning. I'm AFK next week – happy for you to merge them then if you prefer not to review this afternoon. As long as there's nothing major needed as changes! |
| import enum Yosemite.POSItem | ||
|
|
||
| enum ItemListState { | ||
| case initial |
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 added this state for more accurate modelling of what's happening.
My intention is to delay showing the loading indicators when we open variations with GRDB – it causes a slight flicker at the moment. This is some groundwork for that.
|
|
|
@staskus I also tested that it observes changes: this branch adds an incremental sync button to the ... menu. I used that by editing products on the web and then syncing. Trashing a product didn't remove it, which is probably down to how we request them or filter the products from the db... but I'll fix it in a separate PR. https://linear.app/a8c/issue/WOOMOB-1493/woo-poslocal-catalog-ensure-that-trashed-products-are-updated-to-not Other changes and additions worked as expected. |
| func loadItems(base: ItemListBaseItem) async { | ||
| switch base { | ||
| case .root: | ||
| hasLoadedProducts = true |
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.
Should we delay it until the items are loaded? Same with other similar flags.
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.
Done in 2bdf4ab
| func refreshItems(base: ItemListBaseItem) async { | ||
| switch base { | ||
| case .root: | ||
| dataSource.refresh() |
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.
So, this is not implemented. Is the idea to trigger an incremental sync when refresh is called?
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.
Yes, that's right
| } | ||
|
|
||
| // Error state | ||
| if let error = dataSource.error, items.isEmpty { |
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.
What kind of errors can we expect here? Since this comes from database, only something super-unexpected I suppose?
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 should be extremely rare – these are really simple select queries...
- Database corruption
- Programming errors during development, e.g. relationships missing, or incorrect query setup – but those shouldn't ever happen in production
Errors are more likely to appear when we sync the data, because we're relying on it coming down with the correct schema and logically consistent. These will happen when we write stuff to the DB, and will prevent the whole write transacion if they're not handled in other code... so they won't get as far as being shown here. It's primarily included for completeness with the existing code, and to make it easier to spot bugs in dev.
I noticed that we only have one error property, shared between variations and products, which could result in an error state being shown for the wrong screen. I've separated them out. 46b634a
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.
Thank you, @joshheald, for work! It took some time to review and understand. I'm not yet that familiar with GRDB, plus there's complexity in clean state management and observability. We don't want the views to stuck in some "no man's land" state.
I think it's mostly good. The comments are in both PRs. I tested with smaller and larger stores. I think there are a couple of things that need to be addressed. I don't see much value in pushing this to trunk without addressing and understanding them properly. I will see if I find time to come back to this after finishing the synchronization work. Otherwise, we can discuss next week and fix things or move to other tasks and merge this work.
|
Version |
eba2394 to
5706749
Compare
Adds a new `.initial` case to `ItemListState` to represent "not yet loaded" state, distinct from "actively loading". Benefits: - More explicit state machine: initial → loading → loaded/empty/error - Foundation for delayed loading indicators (300ms threshold) - Better UX for instant GRDB loads (no flashing skeleton) Updated existing `PointOfSaleItemsController` to: - Start with `.initial` instead of `.loading([])` - Transition to `.loading([])` on first load - Preserve existing behavior for refreshes
Creates a new controller that wraps `POSObservableDataSourceProtocol` for GRDB-based item loading. Key features: - Conforms to `PointOfSaleItemsControllerProtocol` (same interface as existing controller) - Computed `itemsViewState` from observable data source properties - Automatic UI updates via observation (no manual state management) - Supports products and variations with `.initial` state - Tracks current parent for variation state mapping Implementation notes: - No pagination tracking needed (data source handles it) - No search support (out of scope for this PR)
Comprehensive test coverage for the new observable controller: - Initial state (loading container, initial root) - Product loading (loaded, empty, error states) - Variation loading (loaded, empty states) - Pagination (hasMoreItems flags) - Independence of products and variations - Parent switching behavior - Error handling - Loading state preservation Fix tests to use proper POSItem construction Use helper methods to create POSSimpleProduct and POSVariation structs instead of non-existent .fake() method, matching the pattern used in MockPOSItemProvider. Changes: - Added makeSimpleProduct() helper - Added makeVariation() helper - Updated all 12 tests to use proper construction
When grdbManager, catalogSyncCoordinator, and the pointOfSaleLocalCatalogi1 feature flag are all present, use the new PointOfSaleObservableItemsController backed by GRDBObservableDataSource instead of the standard PointOfSaleItemsController. This enables reactive database-driven item lists for stores with local catalog sync enabled.
5706749 to
ee974cc
Compare
staskus
left a comment
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 retested on Friday and today as well comparing the new version with the previous version and all the same products, including variable ones are loaded. The pagination works, including with large datasets.
As you mentioned, there are still ways to make it solid, and handle the cases where the catalog is not loaded yet but otherwise it works well and is super fast 👍
Previously, a single error property was shared between products and variations, which could cause errors to leak between the root and variation parent contexts. Now using separate productError and variationError properties so that: - Product loading errors only affect the root state - Variation loading errors only affect the parent item state - Errors don't interfere with each other Updated: - POSObservableDataSourceProtocol - GRDBObservableDataSource implementation - PointOfSaleObservableItemsController state computation - MockPOSObservableDataSource for tests
When loading variations for a new parent product, clear any previous variation error to prevent it from leaking to the new parent's context. This ensures each parent starts with a clean error state, matching the behavior of clearing variationItems.

Merge after: #16231
Description
This PR starts using the observable GRDB data source added in #16231 to show items from GRDB in the item list.
Note that there's nothing to enforce that GRDB actually has items in it at the moment... so you can definitely break this, but those guards are covered by other tickets and this is feature flagged.
Steps to reproduce
Check we're not breaking production...
pointOfSaleLocalCatalogi1feature flag to falseTest the GRDB item controller
Example sync log messages:
Optional: test with a larger site
POSCatalogSyncCoordinator.Constants.defaultSizeLimitForPOSCatalogto 100000Note that with larger catalogs, you start to hit scrolling performance issues. These are outside the scope of current work, but we may want to address them before too long. I don't think it's down to the mapping happening on the main thread, and it may even be that we're hitting some SwiftUI limitations.
Testing information
Note that Search and Barcode Scanning are still using the network-based controller, so have delays.
Screenshots
GRDB.items.mp4
RELEASE-NOTES.txtif necessary.