Skip to content

Commit 8ee3cc5

Browse files
authored
Merge pull request #7848 from woocommerce/issue/7741-add-customer-upsert-logic
Customer Search: Coordinate `Customer` and `CustomerSearchResults` entities with Storage
2 parents 4a22eb5 + 0a79129 commit 8ee3cc5

File tree

14 files changed

+401
-27
lines changed

14 files changed

+401
-27
lines changed

Fakes/Fakes/Networking.generated.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ extension Customer {
186186
///
187187
public static func fake() -> Customer {
188188
.init(
189+
siteID: .fake(),
189190
customerID: .fake(),
190191
email: .fake(),
191192
firstName: .fake(),
@@ -1694,6 +1695,17 @@ extension User {
16941695
)
16951696
}
16961697
}
1698+
extension WCAnalyticsCustomer {
1699+
/// Returns a "ready to use" type filled with fake values.
1700+
///
1701+
public static func fake() -> WCAnalyticsCustomer {
1702+
.init(
1703+
siteID: .fake(),
1704+
userID: .fake(),
1705+
name: .fake()
1706+
)
1707+
}
1708+
}
16971709
extension WCPayAccountStatusEnum {
16981710
/// Returns a "ready to use" type filled with fake values.
16991711
///

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@
324324
57BE08D82409B63800F6DCED /* reviews-missing-avatar-urls.json in Resources */ = {isa = PBXBuildFile; fileRef = 57BE08D72409B63700F6DCED /* reviews-missing-avatar-urls.json */; };
325325
57E8FED3246616AC0057CD68 /* Result+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E8FED2246616AC0057CD68 /* Result+Extensions.swift */; };
326326
6647C0161DAC6AB6570C53A7 /* Pods_Networking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */; };
327+
688908AE28FF920C0081A07E /* customer-2.json in Resources */ = {isa = PBXBuildFile; fileRef = 688908AD28FF920C0081A07E /* customer-2.json */; };
327328
68BD37B328D9B8BD00C2A517 /* CustomerRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68BD37B228D9B8BD00C2A517 /* CustomerRemoteTests.swift */; };
328329
68C87B342862D40E00A99054 /* setting-all-except-countries.json in Resources */ = {isa = PBXBuildFile; fileRef = 68C87B332862D40E00A99054 /* setting-all-except-countries.json */; };
329330
68CB800C28D87BC800E169F8 /* Customer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68CB800B28D87BC800E169F8 /* Customer.swift */; };
@@ -1033,6 +1034,7 @@
10331034
5726F7332460A8F00031CAAC /* CopiableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopiableTests.swift; sourceTree = "<group>"; };
10341035
57BE08D72409B63700F6DCED /* reviews-missing-avatar-urls.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "reviews-missing-avatar-urls.json"; sourceTree = "<group>"; };
10351036
57E8FED2246616AC0057CD68 /* Result+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Extensions.swift"; sourceTree = "<group>"; };
1037+
688908AD28FF920C0081A07E /* customer-2.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "customer-2.json"; sourceTree = "<group>"; };
10361038
68BD37B228D9B8BD00C2A517 /* CustomerRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerRemoteTests.swift; sourceTree = "<group>"; };
10371039
68C87B332862D40E00A99054 /* setting-all-except-countries.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "setting-all-except-countries.json"; sourceTree = "<group>"; };
10381040
68CB800B28D87BC800E169F8 /* Customer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Customer.swift; sourceTree = "<group>"; };
@@ -2147,6 +2149,7 @@
21472149
68C87B332862D40E00A99054 /* setting-all-except-countries.json */,
21482150
68CB801528D8A39700E169F8 /* customer.json */,
21492151
68F48B1228E3E5750045C15B /* wc-analytics-customers.json */,
2152+
688908AD28FF920C0081A07E /* customer-2.json */,
21502153
);
21512154
path = Responses;
21522155
sourceTree = "<group>";
@@ -2580,6 +2583,7 @@
25802583
D823D91422377EE600C90817 /* shipment_tracking_providers.json in Resources */,
25812584
4599FC5C24A6276F0056157A /* product-tags-all.json in Resources */,
25822585
03DCB77E262738E300C8953D /* coupon.json in Resources */,
2586+
688908AE28FF920C0081A07E /* customer-2.json in Resources */,
25832587
034480C327A42F9100DFACD2 /* order-with-charge.json in Resources */,
25842588
74A7B4BE217A841400E85A8B /* broken-settings-general.json in Resources */,
25852589
026CF624237D839B009563D4 /* product-variations-load-all.json in Resources */,

Networking/Networking/Model/Copiable/Models+Copiable.generated.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,15 @@ extension CouponReport {
171171

172172
extension Customer {
173173
public func copy(
174+
siteID: CopiableProp<Int64> = .copy,
174175
customerID: CopiableProp<Int64> = .copy,
175176
email: CopiableProp<String> = .copy,
176177
firstName: NullableCopiableProp<String> = .copy,
177178
lastName: NullableCopiableProp<String> = .copy,
178179
billing: NullableCopiableProp<Address> = .copy,
179180
shipping: NullableCopiableProp<Address> = .copy
180181
) -> Customer {
182+
let siteID = siteID ?? self.siteID
181183
let customerID = customerID ?? self.customerID
182184
let email = email ?? self.email
183185
let firstName = firstName ?? self.firstName
@@ -186,6 +188,7 @@ extension Customer {
186188
let shipping = shipping ?? self.shipping
187189

188190
return Customer(
191+
siteID: siteID,
189192
customerID: customerID,
190193
email: email,
191194
firstName: firstName,
@@ -1948,6 +1951,24 @@ extension TopEarnerStatsItem {
19481951
}
19491952
}
19501953

1954+
extension WCAnalyticsCustomer {
1955+
public func copy(
1956+
siteID: CopiableProp<Int64> = .copy,
1957+
userID: CopiableProp<Int64> = .copy,
1958+
name: NullableCopiableProp<String> = .copy
1959+
) -> WCAnalyticsCustomer {
1960+
let siteID = siteID ?? self.siteID
1961+
let userID = userID ?? self.userID
1962+
let name = name ?? self.name
1963+
1964+
return WCAnalyticsCustomer(
1965+
siteID: siteID,
1966+
userID: userID,
1967+
name: name
1968+
)
1969+
}
1970+
}
1971+
19511972
extension WCPayCardPaymentDetails {
19521973
public func copy(
19531974
brand: CopiableProp<WCPayCardBrand> = .copy,

Networking/Networking/Model/Customer.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import Codegen
55
/// https://woocommerce.github.io/woocommerce-rest-api-docs/#customer-properties
66
///
77
public struct Customer: Codable, GeneratedCopiable, GeneratedFakeable {
8+
/// The siteID for the customer
9+
public let siteID: Int64
810

911
/// Unique identifier for the customer
1012
public let customerID: Int64
@@ -26,12 +28,14 @@ public struct Customer: Codable, GeneratedCopiable, GeneratedFakeable {
2628

2729
/// Customer struct initializer
2830
///
29-
public init(customerID: Int64,
31+
public init(siteID: Int64,
32+
customerID: Int64,
3033
email: String,
3134
firstName: String?,
3235
lastName: String?,
3336
billing: Address?,
3437
shipping: Address?) {
38+
self.siteID = siteID
3539
self.customerID = customerID
3640
self.email = email
3741
self.firstName = firstName
@@ -43,6 +47,10 @@ public struct Customer: Codable, GeneratedCopiable, GeneratedFakeable {
4347
/// Public initializer for the Customer
4448
///
4549
public init(from decoder: Decoder) throws {
50+
guard let siteID = decoder.userInfo[.siteID] as? Int64 else {
51+
throw CustomerDecodingError.missingSiteID
52+
}
53+
4654
let container = try decoder.container(keyedBy: CodingKeys.self)
4755

4856
let customerID = try container.decode(Int64.self, forKey: .customerID)
@@ -52,7 +60,8 @@ public struct Customer: Codable, GeneratedCopiable, GeneratedFakeable {
5260
let billing = try? container.decode(Address.self, forKey: .billing)
5361
let shipping = try? container.decode(Address.self, forKey: .shipping)
5462

55-
self.init(customerID: customerID,
63+
self.init(siteID: siteID,
64+
customerID: customerID,
5665
email: email,
5766
firstName: firstName,
5867
lastName: lastName,
@@ -73,4 +82,8 @@ extension Customer {
7382
case billing
7483
case shipping
7584
}
85+
86+
enum CustomerDecodingError: Error {
87+
case missingSiteID
88+
}
7689
}
Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import Foundation
2+
import Codegen
23

3-
public struct WCAnalyticsCustomer: Codable {
4+
public struct WCAnalyticsCustomer: Codable, GeneratedCopiable, GeneratedFakeable {
5+
/// The siteID for the WCAnalyticsCustomer
6+
public let siteID: Int64
47

58
/// Unique identifier for the user
69
public let userID: Int64
@@ -10,20 +13,25 @@ public struct WCAnalyticsCustomer: Codable {
1013

1114
/// WCAnalyticsCustomer struct Initializer
1215
///
13-
public init(userID: Int64, name: String?) {
16+
public init(siteID: Int64, userID: Int64, name: String?) {
17+
self.siteID = siteID
1418
self.userID = userID
1519
self.name = name
1620
}
1721

1822
/// Public initializer for WCAnalyticsCustomer
1923
///
2024
public init(from decoder: Decoder) throws {
25+
guard let siteID = decoder.userInfo[.siteID] as? Int64 else {
26+
throw DecodingError.missingSiteID
27+
}
28+
2129
let container = try decoder.container(keyedBy: CodingKeys.self)
2230

2331
let userID = try container.decode(Int64.self, forKey: .userID)
2432
let name = try container.decode(String.self, forKey: .name)
2533

26-
self.init(userID: userID, name: name)
34+
self.init(siteID: siteID, userID: userID, name: name)
2735
}
2836
}
2937

@@ -32,4 +40,8 @@ extension WCAnalyticsCustomer {
3240
case userID = "user_id"
3341
case name = "name"
3442
}
43+
44+
enum DecodingError: Error {
45+
case missingSiteID
46+
}
3547
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"data": {
3+
"id": 26,
4+
"date_created": "2017-03-21T16:09:28",
5+
"date_created_gmt": "2017-03-21T19:09:28",
6+
"date_modified": "2017-03-21T16:09:30",
7+
"date_modified_gmt": "2017-03-21T19:09:30",
8+
"email": "[email protected]",
9+
"first_name": "Johnny",
10+
"last_name": "Doe",
11+
"role": "customer",
12+
"username": "johnny.doe",
13+
"billing": {
14+
"first_name": "John",
15+
"last_name": "Doe",
16+
"company": "",
17+
"address_1": "969 Market",
18+
"address_2": "",
19+
"city": "San Francisco",
20+
"state": "CA",
21+
"postcode": "94103",
22+
"country": "US",
23+
"email": "[email protected]",
24+
"phone": "(555) 555-5555"
25+
},
26+
"shipping": {
27+
"first_name": "John",
28+
"last_name": "Doe",
29+
"company": "",
30+
"address_1": "969 Market",
31+
"address_2": "",
32+
"city": "San Francisco",
33+
"state": "CA",
34+
"postcode": "94103",
35+
"country": "US"
36+
},
37+
"is_paying_customer": false,
38+
"avatar_url": "https://secure.gravatar.com/avatar/8eb1b522f60d11fa897de1dc6351b7e8?s=96",
39+
"meta_data": [],
40+
"_links": {
41+
"self": [
42+
{
43+
"href": "https://example.com/wp-json/wc/v3/customers/25"
44+
}
45+
],
46+
"collection": [
47+
{
48+
"href": "https://example.com/wp-json/wc/v3/customers"
49+
}
50+
]
51+
}
52+
}
53+
}
54+
55+

Storage/Storage/Tools/StorageType+Extensions.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,21 @@ public extension StorageType {
599599
return firstObject(ofType: WCPayCharge.self, matching: predicate)
600600
}
601601

602+
// MARK: - Customers
603+
604+
/// Returns a single Customer given a `siteID` and `customerID`
605+
///
606+
func loadCustomer(siteID: Int64, customerID: Int64) -> Customer? {
607+
let predicate = \Customer.siteID == siteID && \Customer.customerID == customerID
608+
return firstObject(ofType: Customer.self, matching: predicate)
609+
}
610+
/// Returns a CustomerSearchResult given a `siteID` and a `keyword`
611+
///
612+
func loadCustomerSearchResult(siteID: Int64, keyword: String) -> CustomerSearchResult? {
613+
let predicate = \CustomerSearchResult.siteID == siteID && \CustomerSearchResult.keyword == keyword
614+
return firstObject(ofType: CustomerSearchResult.self, matching: predicate)
615+
}
616+
602617
// MARK: - System plugins
603618

604619
/// Returns all stored system plugins for a provided `siteID`.

Storage/StorageTests/Tools/StorageTypeExtensionsTests.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,34 @@ final class StorageTypeExtensionsTests: XCTestCase {
147147
XCTAssertEqual(coupon, storedCoupon)
148148
}
149149

150+
func test_loadCustomer_by_siteID_and_customerID() throws {
151+
// Given
152+
let customerID: Int64 = 123
153+
let customer = storage.insertNewObject(ofType: Customer.self)
154+
customer.siteID = sampleSiteID
155+
customer.customerID = customerID
156+
157+
// When
158+
let storedCustomer = try XCTUnwrap(storage.loadCustomer(siteID: sampleSiteID, customerID: customerID))
159+
160+
// Then
161+
XCTAssertEqual(customer, storedCustomer)
162+
}
163+
164+
func test_loadCustomerSearchResult_by_siteID_and_keyword() throws {
165+
// Given
166+
let keyword: String = "some keyword"
167+
let customerSearchResult = storage.insertNewObject(ofType: CustomerSearchResult.self)
168+
customerSearchResult.siteID = sampleSiteID
169+
customerSearchResult.keyword = keyword
170+
171+
// When
172+
let storedCustomerSearchResult = try XCTUnwrap(storage.loadCustomerSearchResult(siteID: sampleSiteID, keyword: keyword ))
173+
174+
// Then
175+
XCTAssertEqual(customerSearchResult, storedCustomerSearchResult)
176+
}
177+
150178
func test_loadOrderFeeLine_by_siteID_feeID() throws {
151179
// Given
152180
let feeID: Int64 = 123

WooCommerce/Classes/ViewRelated/Orders/Order Creation/CustomerSection/CreateOrderAddressFormViewModel.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,19 @@ final class CreateOrderAddressFormViewModel: AddressFormViewModel, AddressFormVi
107107
siteID: siteID,
108108
keyword: "hello") { result in
109109
switch result {
110-
case .success(_):
111-
print("Success!")
110+
case .success(let customers):
111+
let storage = ServiceLocator.storageManager
112+
guard let result = storage.viewStorage.loadCustomerSearchResult(siteID: self.siteID, keyword: "hello") else {
113+
return
114+
}
115+
print("Site ID: \(result.siteID), keyword: \(result.keyword), Customers: \(result.customers?.count as Any)")
116+
for eachCustomer in customers {
117+
let output = """
118+
Customer: \(eachCustomer.customerID),
119+
Name: \(String(describing: eachCustomer.firstName)) \(String(describing: eachCustomer.lastName))
120+
"""
121+
print(output)
122+
}
112123
case .failure(let error):
113124
print(error)
114125
}

Yosemite/Yosemite.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@
213213
578CE7972475FD8200492EBF /* MockProductReview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 578CE7962475FD8200492EBF /* MockProductReview.swift */; };
214214
57DFCC7925003C4000251E0C /* FetchResultSnapshotsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57DFCC7825003C4000251E0C /* FetchResultSnapshotsProvider.swift */; };
215215
681D952B28E0F62B00C4039E /* CustomerAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681D952A28E0F62B00C4039E /* CustomerAction.swift */; };
216+
6889089F28F7B8540081A07E /* Customer+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6889089E28F7B8540081A07E /* Customer+ReadOnlyConvertible.swift */; };
216217
689D11D52891B9A400F6A83F /* WooFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 689D11D42891B9A400F6A83F /* WooFoundation.framework */; };
217218
68BD37B528DB2E9800C2A517 /* CustomerStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68BD37B428DB2E9800C2A517 /* CustomerStore.swift */; };
218219
68BD37B928DB323D00C2A517 /* CustomerStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68BD37B828DB323D00C2A517 /* CustomerStoreTests.swift */; };
@@ -617,6 +618,7 @@
617618
57DFCC7825003C4000251E0C /* FetchResultSnapshotsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchResultSnapshotsProvider.swift; sourceTree = "<group>"; };
618619
585B973F61632665297738A3 /* Pods-Yosemite.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Yosemite.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-Yosemite/Pods-Yosemite.release-alpha.xcconfig"; sourceTree = "<group>"; };
619620
681D952A28E0F62B00C4039E /* CustomerAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerAction.swift; sourceTree = "<group>"; };
621+
6889089E28F7B8540081A07E /* Customer+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Customer+ReadOnlyConvertible.swift"; sourceTree = "<group>"; };
620622
689D11D42891B9A400F6A83F /* WooFoundation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WooFoundation.framework; sourceTree = BUILT_PRODUCTS_DIR; };
621623
68BD37B428DB2E9800C2A517 /* CustomerStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerStore.swift; sourceTree = "<group>"; };
622624
68BD37B828DB323D00C2A517 /* CustomerStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerStoreTests.swift; sourceTree = "<group>"; };
@@ -1270,6 +1272,7 @@
12701272
031C1EAB27B1873200298699 /* WCPayCardPresentReceiptDetails+ReadOnlyConvertible.swift */,
12711273
031C1EAF27B1879C00298699 /* WCPayCardPaymentDetails+ReadOnlyConvertible.swift */,
12721274
031C1EAD27B1877000298699 /* WCPayCardPresentPaymentDetails+ReadOnlyConvertible.swift */,
1275+
6889089E28F7B8540081A07E /* Customer+ReadOnlyConvertible.swift */,
12731276
);
12741277
path = Storage;
12751278
sourceTree = "<group>";
@@ -2106,6 +2109,7 @@
21062109
7499936420EFBC1B00CF01CD /* OrderNoteAction.swift in Sources */,
21072110
7455D4692141B59E00FA8C1F /* TopEarnerStatsItem+ReadOnlyConvertible.swift in Sources */,
21082111
D8C11A5222DF2DA200D4A88D /* StatsActionV4.swift in Sources */,
2112+
6889089F28F7B8540081A07E /* Customer+ReadOnlyConvertible.swift in Sources */,
21092113
68BD37B528DB2E9800C2A517 /* CustomerStore.swift in Sources */,
21102114
CE179D55235F4E1700C24EB3 /* RefundAction.swift in Sources */,
21112115
CE5F9A7A22B2D455001755E8 /* Array+Helpers.swift in Sources */,

0 commit comments

Comments
 (0)