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

Custom Fields: Local editing logic #14029

Merged
merged 10 commits into from
Sep 30, 2024

Conversation

hafizrahman
Copy link
Contributor

@hafizrahman hafizrahman commented Sep 24, 2024

Part of: #13493

Please do not merge until target is trunk

Description

The flow for editing/adding/deleting custom fields can be described as follows:

  1. Each item in the Custom Fields List screen can open the Custom Field Editor screen to be edited. In the editor, merchants can change things and then tap "Done" when they're done editing. Doing so will return them to the list screen and the change will be shown.
  2. Merchant can also delete a field from its Custom Field Editor screen. This removes the field from the list screen.
  3. Custom Fields List also has an add button that opens the editor, then merchant can also tap "Done" to keep the change. This adds a field to the list screen.
  4. All changes from edit/delete/add actions above are still pending, similar to making changes to various aspects of products in Product Details screen. The Custom Fields List has a "Save" button, which acts as the only way to do remote API call to save. It will save all existing changes at once, matching the behavior in core.

This PR handles the local editing behavior, by adding these:

  1. CustomFieldsListViewModel which handles the business logic.
  2. The viewmodel contains editedFields and addedFields arrays to store the pending edit and addition. It also has combinedList which is an array of the latest combined changes. These later can be used for making the API call.
  3. The viewmodel has editField() and addField() functions that can later be used when editing or adding a field.
  4. Unit tests for the viewmodel.

Not in this PR and will be added separately:

  1. Handling for deletion.
  2. UI functionality for editing or adding field.
  3. UI functionality for remote saving

Steps to reproduce

The handling is not used anywhere in the app yet, so for now just check the code and ensure the unit tests also passes.

Reviewer (or Author, in the case of optional code reviews):

Please make sure these conditions are met before approving the PR, or request changes if the PR needs improvement:

  • The PR is small and has a clear, single focus, or a valid explanation is provided in the description. If needed, please request to split it into smaller PRs.
  • Ensure Adequate Unit Test Coverage: The changes are reasonably covered by unit tests or an explanation is provided in the PR description.
  • Manual Testing: The author listed all the tests they ran, including smoke tests when needed (e.g., for refactorings). The reviewer confirmed that the PR works as expected on all devices (phone/tablet) and no regressions are added.

Contains logic for locally editing custom fields while keeping track of two main arrays, editedFields and addedFields.
@dangermattic
Copy link
Collaborator

dangermattic commented Sep 24, 2024

1 Warning
⚠️ View files have been modified, but no screenshot or video is included in the pull request. Consider adding some for clarity.

Generated by 🚫 Danger

@hafizrahman hafizrahman marked this pull request as ready for review September 24, 2024 15:14
@hafizrahman hafizrahman added this to the 20.6 milestone Sep 24, 2024
@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Sep 24, 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 Numberpr14029-f280d61
Version20.5
Bundle IDcom.automattic.alpha.woocommerce
Commitf280d61
App Center BuildWooCommerce - Prototype Builds #11041
Automatticians: You can use our internal self-serve MC tool to give yourself access to App Center if needed.

@hafizrahman hafizrahman added the type: task An internally driven task. label Sep 24, 2024
Copy link
Member

@pmusolino pmusolino left a comment

Choose a reason for hiding this comment

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

In general everything looks good @hafizrahman.

I just left some minor nits and two changes:

  1. one related to an id that is not unique if nil.
  2. more unit tests.

Comment on lines 35 to 59
if newField.id == nil {
// If there's no id, it means we're now editing a newly added field.
if let addedIndex = addedFields.firstIndex(where: { $0.key == oldField.key }) {
if newField.key == oldField.key {
// If the key hasn't changed, update the existing added field
addedFields[addedIndex] = newField
} else {
// If the key has changed, remove the old field and add the new one
addedFields.remove(at: addedIndex)
addedFields.append(newField)
}
} else {
// This case should not happen in normal flow
DDLogError("⛔️ Error: Editing a newly updated field that doesn't exist in combinedList")

}
} else {
// For when editing an already edited field
if let editedIndex = editedFields.firstIndex(where: { $0.id == oldField.id }) {
editedFields[editedIndex] = newField
} else {
// For the first time a field is edited
editedFields.append(newField)
}
}
Copy link
Member

Choose a reason for hiding this comment

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

nit: this logic can be even clearer if you split it into two different private methods.

Eg:

if newField.id == nil {
      handleNewlyAddedField(oldField: oldField, newField: newField)
} else {
      handleEditedField(oldField: oldField, newField: newField)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Refactored in 98cec53

Comment on lines 71 to 76
func updateCombinedList() {
let editedList = originalCustomFields.map { field in
editedFields.first { $0.id == field.id } ?? CustomFieldUI(customField: field)
}
combinedList = editedList + addedFields
}
Copy link
Member

Choose a reason for hiding this comment

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

nit: indentation here looks wrong

Copy link
Contributor Author

Choose a reason for hiding this comment

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

2dde854

thanks, good catch

} else {
// This case should not happen in normal flow
DDLogError("⛔️ Error: Editing a newly updated field that doesn't exist in combinedList")

Copy link
Member

Choose a reason for hiding this comment

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

nit: empty return that should be deleted

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed as part of refactor 98cec53

Comment on lines 80 to 96
struct CustomFieldUI: Identifiable {
let key: String
let value: String
let id: Int64?

init(key: String, value: String, id: Int64? = nil) {
self.key = key
self.value = value
self.id = id
}

init(customField: CustomFieldViewModel) {
self.key = customField.title
self.value = customField.content
self.id = customField.id
}
}
Copy link
Member

Choose a reason for hiding this comment

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

This implementation can cause some issues in SwiftUI, which expects each item in a list to have a unique identifier to properly handle updates, deletions, and reordering.

If the id property is optional (Int64?) and there are two instances of CustomFieldUI with a nil id, both instances will not have a unique identifier.

To solve this problem, you can ensure that CustomFieldUI generates a unique identifier when id is nil. A common solution is to use an automatically generated UUID. Since a custom field can also have its own ID, I suggest adding an optional customFieldID. The id will become a unique UUID if customFieldID is nil, or it will be equal to the customFieldID if it is provided. This is just a proposal; you may have a better solution in mind.

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! Addressed in 0e29067

@pmusolino pmusolino self-assigned this Sep 25, 2024
@hafizrahman
Copy link
Contributor Author

Thanks for the review! Good for another round @pmusolino

Copy link
Member

@pmusolino pmusolino left a comment

Choose a reason for hiding this comment

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

Changes looks good, thanks @hafizrahman !

@wpmobilebot wpmobilebot modified the milestones: 20.6, 20.7 Sep 27, 2024
@wpmobilebot
Copy link
Collaborator

Version 20.6 has now entered code-freeze, so the milestone of this PR has been updated to 20.7.

Base automatically changed from feat/13493-refactor-aztec-make-more-generic to trunk September 30, 2024 08:23
@hafizrahman hafizrahman merged commit 1168651 into trunk Sep 30, 2024
14 checks passed
@hafizrahman hafizrahman deleted the feat/13493-custom-fields-local-editing branch September 30, 2024 08:28
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