-
Notifications
You must be signed in to change notification settings - Fork 113
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
Dynamic Dashboard: Reviews Card Reload Functionality #12752
Conversation
… and add it to the data.
Conflicts: WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift WooCommerce/Classes/ViewRelated/Dashboard/Reviews/ReviewsDashboardCard.swift
WooCommerce/Classes/ViewRelated/Dashboard/Reviews/ReviewsDashboardCard.swift
Outdated
Show resolved
Hide resolved
📲 You can test the changes from this Pull Request in WooCommerce iOS by scanning the QR code below to install the corresponding build.
|
func updateReviewsResults() async { | ||
let reviews = productReviewsResultsController.fetchedObjects.prefix(Constants.numberOfItems) | ||
let reviewProductIDs = reviews.map { $0.productID } | ||
|
||
// Load products that matches the review product IDs | ||
if reviewProductIDs.isNotEmpty { | ||
await loadReviewProducts(for: reviewProductIDs) | ||
} | ||
|
||
data = reviews | ||
.map { review in | ||
let product = reviewProducts.first { $0.productID == review.productID } | ||
// TODO: also fetch notification | ||
return ReviewViewModel(review: review, product: product, notification: nil) | ||
} | ||
} |
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'd like to get more opinion on this area.
The fetch result for reviews only includes data for product ID related to the reviews, while the UI requires the product name to be displayed. Thus, another call loadReviewProducts()
is needed to get the product names.
I'm not 100% sure if this is the best way to do this. It feels to me that it's going through many steps, but seems inevitable if we want to use storage as single source of truth:
- remote call for reviews
- handle db update for reviews
- remote call for products
- handle db update for products
- compare local reviews and local products to build data
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 explaining, Hafiz!
I think you can try fetching from storage before sending a remote call for products.
You can use the following predicate to fetch multiple products from storage.
NSPredicate(format: "siteID == %lld AND productID IN %@", siteID, arrayOfProductIDs)
What do you think?
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 updated the logic in 4ad2e89
Now it will fetch products from storage first and only do remote call if they're not found.
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 left a comment about reading from storage before sending a request to retrieve products. Please let me know your thoughts.
WooCommerce/Classes/ViewRelated/Dashboard/Reviews/ReviewsDashboardCard.swift
Outdated
Show resolved
Hide resolved
WooCommerce/Classes/ViewRelated/Dashboard/Reviews/ReviewsDashboardCard.swift
Outdated
Show resolved
Hide resolved
func updateReviewsResults() async { | ||
let reviews = productReviewsResultsController.fetchedObjects.prefix(Constants.numberOfItems) | ||
let reviewProductIDs = reviews.map { $0.productID } | ||
|
||
// Load products that matches the review product IDs | ||
if reviewProductIDs.isNotEmpty { | ||
await loadReviewProducts(for: reviewProductIDs) | ||
} | ||
|
||
data = reviews | ||
.map { review in | ||
let product = reviewProducts.first { $0.productID == review.productID } | ||
// TODO: also fetch notification | ||
return ReviewViewModel(review: review, product: product, notification: nil) | ||
} | ||
} |
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 explaining, Hafiz!
I think you can try fetching from storage before sending a remote call for products.
You can use the following predicate to fetch multiple products from storage.
NSPredicate(format: "siteID == %lld AND productID IN %@", siteID, arrayOfProductIDs)
What do you think?
WooCommerce/Classes/ViewRelated/Dashboard/Reviews/ReviewsDashboardCard.swift
Show resolved
Hide resolved
WooCommerce/Classes/ViewRelated/Dashboard/Reviews/ReviewsDashboardCard.swift
Outdated
Show resolved
Hide resolved
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 updates to read from storage, @hafizrahman! 👍
I left a few comments/questions about the logic. Please let me know your thoughts.
WooCommerce/Classes/ViewRelated/Dashboard/Reviews/ReviewsDashboardCard.swift
Outdated
Show resolved
Hide resolved
WooCommerce/Classes/ViewRelated/Dashboard/Reviews/ReviewsDashboardCardViewModel.swift
Outdated
Show resolved
Hide resolved
WooCommerce/Classes/ViewRelated/Dashboard/Reviews/ReviewsDashboardCardViewModel.swift
Outdated
Show resolved
Hide resolved
WooCommerce/Classes/ViewRelated/Dashboard/Reviews/ReviewsDashboardCardViewModel.swift
Outdated
Show resolved
Hide resolved
1. reviewProducts is now a computed var to simplify fetchProducts() 2. productsResultsController now initialized first and updated later when productIDs exist with updateProductsResultsControllerPredicate() 3. reviewProductIDs is now simply passed as function parameters and not kept as class variable 4. Update logic for checking existing products before needing to fetch 5. Code organization with MARK for Storage-related code
Conflicts: WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift
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 updates, Hafiz!
This looks good to me. I left a few non-blocking suggestions. 🚀
WooCommerce/Classes/ViewRelated/Dashboard/Reviews/ReviewsDashboardCardViewModel.swift
Outdated
Show resolved
Hide resolved
|
||
func updateProductsResultsControllerPredicate(with productIDs: [Int64]) { | ||
let predicates = NSCompoundPredicate(andPredicateWithSubpredicates: [sitePredicate(), | ||
NSPredicate(format: "productID IN %@", productIDs)]) |
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.
Super nit: Indentation fix needed.
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 think this looks odd but perhaps correct because it's an array as the parameter.
Either using enter manually or Editor > Structure > Re-indent results the same like above:
Screen.Recording.2024-05-20.at.10.25.40.mov
|
||
self.productsResultsController = ResultsController<StorageProduct>(storageManager: storageManager, | ||
matching: nil, | ||
sortedBy: []) |
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.
nit: We could mention the fetchLimit: Constants.numberOfItems
here as well.
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.
Good thinking. I had assumed that when the predicate is updated and it's requesting up to 3 product IDs, it would always return up to 3 items anyway since they're distinct. But I agree it's safer to limit it here too. Added in 37fc482
stores.dispatch(ProductAction.retrieveProducts(siteID: siteID, | ||
productIDs: reviewProductIDs | ||
) { result in | ||
continuation.resume(with: result) |
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.
Non blocking suggestion:
We could replace this with the following to skip returning stuff from this function.
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
The same applies to func loadReviews() async throws -> [ProductReview]
method as well.
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, I added the updates for these functions on 40bb44a
// Ignoring the result from remote as we're using storage as the single source of truth | ||
_ = try await retrieveProducts(for: reviewProductIDs) | ||
} catch { | ||
syncingError = error |
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.
Suggestion: I believe that we can display the reviews even if retrieving products fails. Could we ignore the error and just display the reviews without product info?
If we think about it further, we could even load the products silently in the background and update the UI. What do you think?
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 a neat suggestion and I can try adding it on the next PR that deals with loading states. I do notice that it can take a while waiting for products and notifications to be fetched, and showing partial result first can be more useful to users. 👍🏼
…oardCardViewModel.swift let/var fix Co-authored-by: Sharma Elanthiraiyan <sharma.elanthiraiyan@automattic.com>
Part of: #12742
Description
This PR includes:
The main purpose of this PR is to get more eyes on the reviews and products fetching logic, and make sure the logic is sound, before going forward with more functionality. Because of that, some functionalities are missing and will be added in subsequent PR:
Testing instructions
Screenshots