Skip to content

Commit 572996f

Browse files
authored
Merge pull request #706 from woocommerce/issue/262-order-status-mark-ii
Order Status Mark II: add Network changes
2 parents 65ede9c + bf9617f commit 572996f

File tree

21 files changed

+636
-56
lines changed

21 files changed

+636
-56
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@
175175
B5C6FCD420A373BB00A4F8E4 /* OrderMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C6FCD320A373BA00A4F8E4 /* OrderMapper.swift */; };
176176
B5C6FCD620A3768900A4F8E4 /* order.json in Resources */ = {isa = PBXBuildFile; fileRef = B5C6FCD520A3768900A4F8E4 /* order.json */; };
177177
B5DAEFF02180DD5A0002356A /* NotificationsRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DAEFEF2180DD5A0002356A /* NotificationsRemote.swift */; };
178+
CE12FBD9221F3A6F00C59248 /* OrderStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE12FBD8221F3A6F00C59248 /* OrderStatus.swift */; };
179+
CE19CB11222486A600E8AF7A /* order-statuses.json in Resources */ = {isa = PBXBuildFile; fileRef = CE19CB10222486A500E8AF7A /* order-statuses.json */; };
178180
CE20179320E3EFA7005B4C18 /* broken-orders.json in Resources */ = {isa = PBXBuildFile; fileRef = CE20179220E3EFA7005B4C18 /* broken-orders.json */; };
179181
CE50345E21B571A7007573C6 /* SitePlanMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE50345D21B571A7007573C6 /* SitePlanMapper.swift */; };
180182
CE50346021B5799F007573C6 /* SitePlan.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE50345F21B5799F007573C6 /* SitePlan.swift */; };
@@ -366,6 +368,8 @@
366368
B5DAEFEF2180DD5A0002356A /* NotificationsRemote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationsRemote.swift; sourceTree = "<group>"; };
367369
BD9439D9B8F2C1ED2EADAA51 /* Pods-NetworkingTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NetworkingTests.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-NetworkingTests/Pods-NetworkingTests.debug.xcconfig"; sourceTree = "<group>"; };
368370
C8F9A8CC6F90A8C9B5EF2EE2 /* Pods-Networking.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Networking.release.xcconfig"; path = "../Pods/Target Support Files/Pods-Networking/Pods-Networking.release.xcconfig"; sourceTree = "<group>"; };
371+
CE12FBD8221F3A6F00C59248 /* OrderStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatus.swift; sourceTree = "<group>"; };
372+
CE19CB10222486A500E8AF7A /* order-statuses.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-statuses.json"; sourceTree = "<group>"; };
369373
CE20179220E3EFA7005B4C18 /* broken-orders.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "broken-orders.json"; sourceTree = "<group>"; };
370374
CE50345D21B571A7007573C6 /* SitePlanMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SitePlanMapper.swift; sourceTree = "<group>"; };
371375
CE50345F21B5799F007573C6 /* SitePlan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SitePlan.swift; sourceTree = "<group>"; };
@@ -612,6 +616,7 @@
612616
741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */,
613617
B5C6FCCE20A3592900A4F8E4 /* OrderItem.swift */,
614618
74C8F06320EEB44800B6EDC9 /* OrderNote.swift */,
619+
CE12FBD8221F3A6F00C59248 /* OrderStatus.swift */,
615620
B5BB1D1120A255EC00112D92 /* OrderStatusKey.swift */,
616621
743E84EB22171F4600FAC9D7 /* ShipmentTracking.swift */,
617622
B56C1EB720EA76F500D749F9 /* Site.swift */,
@@ -653,6 +658,7 @@
653658
743FDB9A210FB36900AC737F /* order-stats-year.json */,
654659
B58D10C52114D1F100107ED4 /* order-stats.json */,
655660
7412A51021702E9700994370 /* order-stats-alt.json */,
661+
CE19CB10222486A500E8AF7A /* order-statuses.json */,
656662
7412A8EF21B6E415005D182A /* report-orders.json */,
657663
74046E20217A73D0007DD7BF /* settings-general.json */,
658664
7492FAE2217FBDBC00ED2C69 /* settings-general-alt.json */,
@@ -935,6 +941,7 @@
935941
743E84F222172D0A00FAC9D7 /* shipment_tracking_plugin_not_active.json in Resources */,
936942
74ABA1C9213F19FE00FFAD30 /* top-performers-month.json in Resources */,
937943
743E84F622172D3E00FAC9D7 /* shipment_tracking_single.json in Resources */,
944+
CE19CB11222486A600E8AF7A /* order-statuses.json in Resources */,
938945
74ABA1CA213F19FE00FFAD30 /* top-performers-year.json in Resources */,
939946
);
940947
runOnlyForDeploymentPostprocessing = 0;
@@ -1029,6 +1036,7 @@
10291036
74ABA1CD213F1B6B00FFAD30 /* TopEarnerStats.swift in Sources */,
10301037
B557DA0220975500005962F4 /* JetpackRequest.swift in Sources */,
10311038
74046E1F217A6B70007DD7BF /* SiteSettingsMapper.swift in Sources */,
1039+
CE12FBD9221F3A6F00C59248 /* OrderStatus.swift in Sources */,
10321040
7426CA1121AF30BD004E9FFC /* SiteAPIMapper.swift in Sources */,
10331041
743E84EC22171F4600FAC9D7 /* ShipmentTracking.swift in Sources */,
10341042
B56C1EB820EA76F500D749F9 /* Site.swift in Sources */,

Networking/Networking/Mapper/ReportOrderTotalsMapper.swift

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,31 @@ import Foundation
55
///
66
struct ReportOrderTotalsMapper: Mapper {
77

8-
/// (Attempts) to extract order totals report from a given JSON Encoded response.
8+
/// Site Identifier associated to the settings that will be parsed.
9+
/// We're injecting this field via `JSONDecoder.userInfo` because
10+
/// the remote endpoints don't really return the SiteID in any of the
11+
/// settings endpoints.
912
///
10-
func map(response: Data) throws -> [OrderStatusKey: Int] {
11-
let totalsArray = try JSONDecoder().decode(ReportOrderTotalsEnvelope.self, from: response).totals
12-
var returnDict = [OrderStatusKey: Int]()
13-
totalsArray.forEach({ (totalResult) in
14-
guard let slug = totalResult[Constants.slugKey]?.value as? String, !slug.isEmpty else {
15-
return
16-
}
17-
let status = OrderStatusKey(rawValue: slug)
18-
returnDict[status] = totalResult[Constants.totalKey]?.value as? Int ?? 0
19-
})
20-
return returnDict
21-
}
22-
}
23-
13+
let siteID: Int
2414

25-
private extension ReportOrderTotalsMapper {
26-
enum Constants {
27-
static let slugKey = "slug"
28-
static let totalKey = "total"
15+
/// (Attempts) to extract order totals report from a given JSON Encoded response.
16+
///
17+
func map(response: Data) throws -> [OrderStatus] {
18+
let decoder = JSONDecoder()
19+
decoder.userInfo = [
20+
.siteID: siteID
21+
]
22+
return try decoder.decode(ReportOrderTotalsEnvelope.self, from: response).data
2923
}
3024
}
3125

32-
33-
/// The report endpoint returns the totals document within a `data` key. This entity
34-
/// allows us to do parse all the things with JSONDecoder.
26+
/// The report endpoint returns the totals document within a `data` key.
27+
/// This entity allows us to parse all the things with JSONDecoder.
3528
///
3629
private struct ReportOrderTotalsEnvelope: Decodable {
37-
let totals: [[String: AnyDecodable]]
30+
let data: [OrderStatus]
31+
3832
private enum CodingKeys: String, CodingKey {
39-
case totals = "data"
33+
case data
4034
}
4135
}

Networking/Networking/Model/Order.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public struct Order: Decodable {
100100

101101
let number = try container.decode(String.self, forKey: .number)
102102
let statusKey = try container.decode(OrderStatusKey.self, forKey: .status)
103+
103104
let currency = try container.decode(String.self, forKey: .currency)
104105
let customerNote = try container.decode(String.self, forKey: .customerNote)
105106

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import Foundation
2+
3+
4+
/// Represents an OrderStatus Entity.
5+
///
6+
public struct OrderStatus: Decodable {
7+
public let name: String?
8+
public let siteID: Int
9+
public let slug: String
10+
public let total: Int
11+
12+
public var status: OrderStatusKey {
13+
return OrderStatusKey(rawValue: slug)
14+
}
15+
16+
/// OrderStatus struct initializer.
17+
///
18+
public init(name: String?, siteID: Int, slug: String, total: Int) {
19+
self.name = name
20+
self.siteID = siteID
21+
self.slug = slug
22+
self.total = total
23+
}
24+
25+
/// The public initializer for OrderStatus.
26+
///
27+
public init(from decoder: Decoder) throws {
28+
guard let siteID = decoder.userInfo[.siteID] as? Int else {
29+
throw OrderStatusError.missingSiteID
30+
}
31+
32+
let container = try decoder.container(keyedBy: CodingKeys.self)
33+
let name = try container.decode(String.self, forKey: .name)
34+
let slug = try container.decode(String.self, forKey: .slug)
35+
let total = try container.decode(Int.self, forKey: .total)
36+
37+
self.init(name: name, siteID: siteID, slug: slug, total: total) // initialize the struct
38+
}
39+
}
40+
41+
42+
/// Defines all of the OrderStatus's CodingKeys.
43+
///
44+
private extension OrderStatus {
45+
46+
enum CodingKeys: String, CodingKey {
47+
case name
48+
case slug
49+
case total
50+
}
51+
}
52+
53+
54+
// MARK: - Comparable Conformance
55+
//
56+
extension OrderStatus: Comparable {
57+
public static func == (lhs: OrderStatus, rhs: OrderStatus) -> Bool {
58+
return lhs.name == rhs.name &&
59+
lhs.slug == rhs.slug &&
60+
lhs.total == rhs.total
61+
}
62+
63+
public static func < (lhs: OrderStatus, rhs: OrderStatus) -> Bool {
64+
return lhs.total < rhs.total ||
65+
(lhs.total == rhs.total && lhs.slug < rhs.slug)
66+
}
67+
}
68+
69+
70+
// MARK: - Decoding Errors
71+
//
72+
enum OrderStatusError: Error {
73+
case missingSiteID
74+
}

Networking/Networking/Remote/ReportRemote.swift

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Alamofire
77
public class ReportRemote: Remote {
88

99
/// Retrieves all of the order totals for a given site.
10+
/// Wraps the API request.
1011
///
1112
/// *Note:* This is a Woo REST API v3 endpoint! It will not work on any Woo site under v3.5.
1213
///
@@ -15,9 +16,30 @@ public class ReportRemote: Remote {
1516
/// - completion: Closure to be executed upon completion.
1617
///
1718
public func loadOrderTotals(for siteID: Int, completion: @escaping ([OrderStatusKey: Int]?, Error?) -> Void) {
19+
loadReportOrderTotals(for: siteID) { (orderStatuses, error) in
20+
var returnDict = [OrderStatusKey: Int]()
21+
orderStatuses?.forEach({ (orderStatus) in
22+
let status = OrderStatusKey(rawValue: orderStatus.slug)
23+
returnDict[status] = orderStatus.total
24+
})
25+
completion(returnDict, error)
26+
}
27+
}
28+
29+
/// Retrieves all known order statuses.
30+
/// Wraps the API request.
31+
///
32+
public func loadOrderStatuses(for siteID: Int, completion: @escaping ([OrderStatus]?, Error?) -> Void) {
33+
loadReportOrderTotals(for: siteID, completion: completion)
34+
}
35+
36+
/// Retrieves an order totals report
37+
///
38+
private func loadReportOrderTotals(for siteID: Int, completion: @escaping ([OrderStatus]?, Error?) -> Void) {
1839
let path = Constants.orderTotalsPath
1940
let request = JetpackRequest(wooApiVersion: .mark3, method: .get, siteID: siteID, path: path, parameters: nil)
20-
let mapper = ReportOrderTotalsMapper()
41+
let mapper = ReportOrderTotalsMapper(siteID: siteID)
42+
2143
enqueue(request, mapper: mapper, completion: completion)
2244
}
2345
}

Networking/NetworkingTests/Mapper/ReportOrderMapperTests.swift

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import XCTest
66
///
77
class ReportOrderMapperTests: XCTestCase {
88

9+
/// Sample SiteID
10+
///
11+
let siteID = 1234
12+
913
/// Verifies that the broken response causes the mapper to return an unknown status
1014
///
1115
func testBrokenResponseReturnsUnknownStatus() {
@@ -16,17 +20,71 @@ class ReportOrderMapperTests: XCTestCase {
1620
/// Verifies that a valid report totals response is properly parsed (YAY!).
1721
///
1822
func testSampleResponseLoaded() {
19-
let reportTotals = try? mapSuccessfulResponse()
23+
guard let results = try? mapSuccessfulResponse() else {
24+
XCTFail("Sample order report totals didn't load.")
25+
return
26+
}
27+
28+
var reportTotals = [OrderStatusKey: Int]()
29+
results.forEach({ (orderStatus) in
30+
let status = OrderStatusKey(rawValue: orderStatus.slug)
31+
reportTotals[status] = orderStatus.total
32+
})
33+
let orderStatuses = results
34+
2035
XCTAssertNotNil(reportTotals)
21-
XCTAssertEqual(reportTotals?.count, 8)
22-
XCTAssertEqual(reportTotals?[.pending], 123)
23-
XCTAssertEqual(reportTotals?[.processing], 4)
24-
XCTAssertEqual(reportTotals?[.onHold], 5)
25-
XCTAssertEqual(reportTotals?[.completed], 6)
26-
XCTAssertEqual(reportTotals?[.cancelled], 7)
27-
XCTAssertEqual(reportTotals?[.refunded], 8)
28-
XCTAssertEqual(reportTotals?[.failed], 9)
29-
XCTAssertEqual(reportTotals?[OrderStatusKey(rawValue: "cia-investigation")], 10)
36+
XCTAssertEqual(reportTotals.count, 9)
37+
XCTAssertEqual(reportTotals[.pending], 123)
38+
XCTAssertEqual(reportTotals[.processing], 4)
39+
XCTAssertEqual(reportTotals[.onHold], 5)
40+
XCTAssertEqual(reportTotals[.completed], 6)
41+
XCTAssertEqual(reportTotals[.cancelled], 7)
42+
XCTAssertEqual(reportTotals[.refunded], 8)
43+
XCTAssertEqual(reportTotals[.failed], 9)
44+
XCTAssertEqual(reportTotals[OrderStatusKey(rawValue: "cia-investigation")], 10)
45+
XCTAssertEqual(reportTotals[OrderStatusKey(rawValue: "pre-ordered")], 1)
46+
47+
XCTAssertNotNil(orderStatuses)
48+
XCTAssertEqual(orderStatuses.count, 9)
49+
50+
let ciaOrderStatus = OrderStatus(name: "CIA Investigation", siteID: 1234, slug: "cia-investigation", total: 10)
51+
let preorderedOrderStatus = OrderStatus(name: "Pre ordered", siteID: 1234, slug: "pre-ordered", total: 1)
52+
53+
XCTAssertEqual(orderStatuses[0].slug, "pending")
54+
XCTAssertEqual(orderStatuses[0].name, "Pending payment")
55+
XCTAssertEqual(orderStatuses[0].status, .pending)
56+
57+
XCTAssertEqual(orderStatuses[1].slug, "processing")
58+
XCTAssertEqual(orderStatuses[1].name, "Processing")
59+
XCTAssertEqual(orderStatuses[1].status, .processing)
60+
61+
XCTAssertEqual(orderStatuses[2].slug, "on-hold")
62+
XCTAssertEqual(orderStatuses[2].name, "On hold")
63+
XCTAssertEqual(orderStatuses[2].status, .onHold)
64+
65+
XCTAssertEqual(orderStatuses[3].slug, "completed")
66+
XCTAssertEqual(orderStatuses[3].name, "Completed")
67+
XCTAssertEqual(orderStatuses[3].status, .completed)
68+
69+
XCTAssertEqual(orderStatuses[4].slug, "cancelled")
70+
XCTAssertEqual(orderStatuses[4].name, "Cancelled")
71+
XCTAssertEqual(orderStatuses[4].status, .cancelled)
72+
73+
XCTAssertEqual(orderStatuses[5].slug, "refunded")
74+
XCTAssertEqual(orderStatuses[5].name, "Refunded")
75+
XCTAssertEqual(orderStatuses[5].status, .refunded)
76+
77+
XCTAssertEqual(orderStatuses[6].slug, "failed")
78+
XCTAssertEqual(orderStatuses[6].name, "Failed")
79+
XCTAssertEqual(orderStatuses[6].status, .failed)
80+
81+
XCTAssertEqual(orderStatuses[7].slug, "cia-investigation")
82+
XCTAssertEqual(orderStatuses[7].name, "CIA Investigation")
83+
XCTAssertEqual(orderStatuses[7].status, ciaOrderStatus.status)
84+
85+
XCTAssertEqual(orderStatuses[8].slug, "pre-ordered")
86+
XCTAssertEqual(orderStatuses[8].name, "Pre ordered")
87+
XCTAssertEqual(orderStatuses[8].status, preorderedOrderStatus.status)
3088
}
3189
}
3290

@@ -37,21 +95,21 @@ private extension ReportOrderMapperTests {
3795

3896
/// Returns the ReportOrderMapper output upon receiving `filename` (Data Encoded)
3997
///
40-
func mapOrderStatusResult(from filename: String) throws -> [OrderStatusKey: Int] {
98+
func mapOrderStatusResult(from filename: String) throws -> [OrderStatus] {
4199
let response = Loader.contentsOf(filename)!
42-
let mapper = ReportOrderTotalsMapper()
100+
let mapper = ReportOrderTotalsMapper(siteID: 1234)
43101
return try mapper.map(response: response)
44102
}
45103

46104
/// Returns the ReportOrderMapper output upon receiving data from the endpoint
47105
///
48-
func mapSuccessfulResponse() throws -> [OrderStatusKey: Int] {
106+
func mapSuccessfulResponse() throws -> [OrderStatus] {
49107
return try mapOrderStatusResult(from: "report-orders")
50108
}
51109

52110
/// Returns the ReportOrderMapper output upon receiving a broken response.
53111
///
54-
func mapLoadBrokenResponse() throws -> [OrderStatusKey: Int] {
112+
func mapLoadBrokenResponse() throws -> [OrderStatus] {
55113
return try mapOrderStatusResult(from: "generic_error")
56114
}
57115
}

Networking/NetworkingTests/Remote/ReportRemoteTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class ReportRemoteTests: XCTestCase {
3131
remote.loadOrderTotals(for: sampleSiteID) { (reportTotals, error) in
3232
XCTAssertNil(error)
3333
XCTAssertNotNil(reportTotals)
34-
XCTAssertEqual(reportTotals?.count, 8)
34+
XCTAssertEqual(reportTotals?.count, 9)
3535
XCTAssertEqual(reportTotals?[.pending], 123)
3636
XCTAssertEqual(reportTotals?[.processing], 4)
3737
XCTAssertEqual(reportTotals?[.onHold], 5)
@@ -59,7 +59,7 @@ class ReportRemoteTests: XCTestCase {
5959
}
6060

6161
XCTAssert(error == .unauthorized)
62-
XCTAssertNil(reportTotals)
62+
XCTAssertEqual(reportTotals?.isEmpty, true)
6363

6464
expectation.fulfill()
6565
}

0 commit comments

Comments
 (0)