Skip to content
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

Merged
merged 27 commits into from
May 20, 2024

Conversation

hafizrahman
Copy link
Contributor

@hafizrahman hafizrahman commented May 16, 2024

Part of: #12742

Description

This PR includes:

  • initial logic for fetching reviews (and products), and displaying it on the UI.
  • functionality for viewing all reviews

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:

  1. The card doesn't load notifications yet, which is needed to determine balloon color (read or not read). The loading logic should be similar to products loading, so should be relatively simple to add once the products loading is confirmed.
  2. Filtering is not done yet. Still discussing in Slack what's the best way forward.
  3. Tapping each review is not added yet.
  4. Empty and error states are not added yet.

Testing instructions

  1. Run app, use a test site with at least 1 review on it.
  2. Enable the "Most recent reviews" card in the dashboard if you haven't,
  3. wait and ensure it loads and shows three reviews, with the correct subject, review content, and star.
  4. Try the "View all reviews" and ensure it opens the Reviews list screen,
  5. Go back to Dashboard and ensure the card still shows 3 cards as before.

Screenshots

@hafizrahman hafizrahman added the type: task An internally driven task. label May 16, 2024
@hafizrahman hafizrahman added this to the 18.7 milestone May 16, 2024
@hafizrahman hafizrahman marked this pull request as ready for review May 16, 2024 06:42
@wpmobilebot
Copy link
Collaborator

wpmobilebot commented May 16, 2024

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

App NameWooCommerce iOS WooCommerce iOS
Build Numberpr12752-40bb44a
Version18.6
Bundle IDcom.automattic.alpha.woocommerce
Commit40bb44a
App Center BuildWooCommerce - Prototype Builds #9134
Automatticians: You can use our internal self-serve MC tool to give yourself access to App Center if needed.

Comment on lines 153 to 168
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)
}
}
Copy link
Contributor Author

@hafizrahman hafizrahman May 16, 2024

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:

  1. remote call for reviews
  2. handle db update for reviews
    1. remote call for products
    2. handle db update for products
    3. compare local reviews and local products to build data

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 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?

Copy link
Contributor Author

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.

@selanthiraiyan selanthiraiyan self-assigned this May 16, 2024
Copy link
Contributor

@selanthiraiyan selanthiraiyan left a 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.

Comment on lines 153 to 168
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)
}
}
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 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?

Copy link
Contributor

@selanthiraiyan selanthiraiyan left a 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.

@itsmeichigo itsmeichigo modified the milestones: 18.7, 18.8 May 17, 2024
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
Copy link
Contributor

@selanthiraiyan selanthiraiyan left a 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. 🚀


func updateProductsResultsControllerPredicate(with productIDs: [Int64]) {
let predicates = NSCompoundPredicate(andPredicateWithSubpredicates: [sitePredicate(),
NSPredicate(format: "productID IN %@", productIDs)])
Copy link
Contributor

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.

Copy link
Contributor Author

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: [])
Copy link
Contributor

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.

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 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)
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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?

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 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. 👍🏼

@hafizrahman hafizrahman merged commit c262c13 into trunk May 20, 2024
22 checks passed
@hafizrahman hafizrahman deleted the feat/12742-review-card-functionality branch May 20, 2024 04:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: task An internally driven task.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants