Skip to content

Commit d877b7f

Browse files
authored
Merge pull request #140 from woocommerce/feature/26-order-detail-notes
Order Notes + Yosemite — Mark 1
2 parents 01c5db0 + 066d43c commit d877b7f

File tree

20 files changed

+968
-344
lines changed

20 files changed

+968
-344
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010
21DB5B99C4107CF69C0A57EC /* Pods_NetworkingTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 69314EDE650855CAF927057E /* Pods_NetworkingTests.framework */; };
1111
6647C0161DAC6AB6570C53A7 /* Pods_Networking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */; };
1212
741B950120EBC8A700DD6E2D /* OrderCouponLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */; };
13+
74C8F06420EEB44800B6EDC9 /* OrderNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */; };
14+
74C8F06620EEB76400B6EDC9 /* order-notes.json in Resources */ = {isa = PBXBuildFile; fileRef = 74C8F06520EEB76400B6EDC9 /* order-notes.json */; };
15+
74C8F06820EEB7BD00B6EDC9 /* OrderNotesMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */; };
16+
74C8F06A20EEBC8C00B6EDC9 /* OrderMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06920EEBC8C00B6EDC9 /* OrderMapperTests.swift */; };
17+
74C8F06C20EEBD5D00B6EDC9 /* broken-order.json in Resources */ = {isa = PBXBuildFile; fileRef = 74C8F06B20EEBD5D00B6EDC9 /* broken-order.json */; };
18+
74C8F06E20EEC1E800B6EDC9 /* OrderNotesMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06D20EEC1E700B6EDC9 /* OrderNotesMapperTests.swift */; };
19+
74C8F07020EEC3A800B6EDC9 /* broken-notes.json in Resources */ = {isa = PBXBuildFile; fileRef = 74C8F06F20EEC3A800B6EDC9 /* broken-notes.json */; };
1320
B505F6CD20BEE37E00BB1B69 /* AccountMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505F6CC20BEE37E00BB1B69 /* AccountMapper.swift */; };
1421
B505F6CF20BEE38B00BB1B69 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505F6CE20BEE38B00BB1B69 /* Account.swift */; };
1522
B505F6D120BEE39600BB1B69 /* AccountRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505F6D020BEE39600BB1B69 /* AccountRemote.swift */; };
@@ -49,7 +56,7 @@
4956
B5C6FCCF20A3592900A4F8E4 /* OrderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C6FCCE20A3592900A4F8E4 /* OrderItem.swift */; };
5057
B5C6FCD420A373BB00A4F8E4 /* OrderMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C6FCD320A373BA00A4F8E4 /* OrderMapper.swift */; };
5158
B5C6FCD620A3768900A4F8E4 /* order.json in Resources */ = {isa = PBXBuildFile; fileRef = B5C6FCD520A3768900A4F8E4 /* order.json */; };
52-
CE20179320E3EFA7005B4C18 /* broken-order.json in Resources */ = {isa = PBXBuildFile; fileRef = CE20179220E3EFA7005B4C18 /* broken-order.json */; };
59+
CE20179320E3EFA7005B4C18 /* broken-orders.json in Resources */ = {isa = PBXBuildFile; fileRef = CE20179220E3EFA7005B4C18 /* broken-orders.json */; };
5360
/* End PBXBuildFile section */
5461

5562
/* Begin PBXContainerItemProxy section */
@@ -65,6 +72,13 @@
6572
/* Begin PBXFileReference section */
6673
69314EDE650855CAF927057E /* Pods_NetworkingTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NetworkingTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6774
741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderCouponLine.swift; sourceTree = "<group>"; };
75+
74C8F06320EEB44800B6EDC9 /* OrderNote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNote.swift; sourceTree = "<group>"; };
76+
74C8F06520EEB76400B6EDC9 /* order-notes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-notes.json"; sourceTree = "<group>"; };
77+
74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNotesMapper.swift; sourceTree = "<group>"; };
78+
74C8F06920EEBC8C00B6EDC9 /* OrderMapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderMapperTests.swift; sourceTree = "<group>"; };
79+
74C8F06B20EEBD5D00B6EDC9 /* broken-order.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "broken-order.json"; sourceTree = "<group>"; };
80+
74C8F06D20EEC1E700B6EDC9 /* OrderNotesMapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNotesMapperTests.swift; sourceTree = "<group>"; };
81+
74C8F06F20EEC3A800B6EDC9 /* broken-notes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "broken-notes.json"; sourceTree = "<group>"; };
6882
753D6504FF01F09F6A33B73E /* Pods-Networking.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Networking.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-Networking/Pods-Networking.debug.xcconfig"; sourceTree = "<group>"; };
6983
B505F6CC20BEE37E00BB1B69 /* AccountMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountMapper.swift; sourceTree = "<group>"; };
7084
B505F6CE20BEE38B00BB1B69 /* Account.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; };
@@ -110,7 +124,7 @@
110124
B5C6FCD520A3768900A4F8E4 /* order.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = order.json; sourceTree = "<group>"; };
111125
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>"; };
112126
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>"; };
113-
CE20179220E3EFA7005B4C18 /* broken-order.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "broken-order.json"; sourceTree = "<group>"; };
127+
CE20179220E3EFA7005B4C18 /* broken-orders.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "broken-orders.json"; sourceTree = "<group>"; };
114128
F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Networking.framework; sourceTree = BUILT_PRODUCTS_DIR; };
115129
F6CEE1CA2AD376C0C28AE9F6 /* Pods-NetworkingTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NetworkingTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-NetworkingTests/Pods-NetworkingTests.release.xcconfig"; sourceTree = "<group>"; };
116130
/* End PBXFileReference section */
@@ -279,10 +293,11 @@
279293
children = (
280294
B505F6CE20BEE38B00BB1B69 /* Account.swift */,
281295
B5BB1D0F20A237FB00112D92 /* Address.swift */,
282-
741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */,
283296
B557DA1C20979E7D005962F4 /* Order.swift */,
284-
B5BB1D1120A255EC00112D92 /* OrderStatus.swift */,
297+
741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */,
285298
B5C6FCCE20A3592900A4F8E4 /* OrderItem.swift */,
299+
74C8F06320EEB44800B6EDC9 /* OrderNote.swift */,
300+
B5BB1D1120A255EC00112D92 /* OrderStatus.swift */,
286301
);
287302
path = Model;
288303
sourceTree = "<group>";
@@ -293,7 +308,10 @@
293308
B505F6D420BEE4E600BB1B69 /* me.json */,
294309
B559EBA920A0B5CD00836CD4 /* orders-load-all.json */,
295310
B5C6FCD520A3768900A4F8E4 /* order.json */,
296-
CE20179220E3EFA7005B4C18 /* broken-order.json */,
311+
CE20179220E3EFA7005B4C18 /* broken-orders.json */,
312+
74C8F06B20EEBD5D00B6EDC9 /* broken-order.json */,
313+
74C8F06520EEB76400B6EDC9 /* order-notes.json */,
314+
74C8F06F20EEC3A800B6EDC9 /* broken-notes.json */,
297315
);
298316
path = Responses;
299317
sourceTree = "<group>";
@@ -305,6 +323,7 @@
305323
B505F6CC20BEE37E00BB1B69 /* AccountMapper.swift */,
306324
B5C6FCD320A373BA00A4F8E4 /* OrderMapper.swift */,
307325
B567AF2A20A0FA4200AB6C62 /* OrderListMapper.swift */,
326+
74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */,
308327
);
309328
path = Mapper;
310329
sourceTree = "<group>";
@@ -330,6 +349,8 @@
330349
children = (
331350
B505F6D220BEE3A500BB1B69 /* AccountMapperTests.swift */,
332351
B5C6FCCC20A34B8300A4F8E4 /* OrderListMapperTests.swift */,
352+
74C8F06920EEBC8C00B6EDC9 /* OrderMapperTests.swift */,
353+
74C8F06D20EEC1E700B6EDC9 /* OrderNotesMapperTests.swift */,
333354
);
334355
path = Mapper;
335356
sourceTree = "<group>";
@@ -447,10 +468,13 @@
447468
isa = PBXResourcesBuildPhase;
448469
buildActionMask = 2147483647;
449470
files = (
471+
74C8F06620EEB76400B6EDC9 /* order-notes.json in Resources */,
472+
74C8F06C20EEBD5D00B6EDC9 /* broken-order.json in Resources */,
450473
B505F6D520BEE4E700BB1B69 /* me.json in Resources */,
451474
B5C6FCD620A3768900A4F8E4 /* order.json in Resources */,
452475
B559EBAA20A0B5CD00836CD4 /* orders-load-all.json in Resources */,
453-
CE20179320E3EFA7005B4C18 /* broken-order.json in Resources */,
476+
CE20179320E3EFA7005B4C18 /* broken-orders.json in Resources */,
477+
74C8F07020EEC3A800B6EDC9 /* broken-notes.json in Resources */,
454478
);
455479
runOnlyForDeploymentPostprocessing = 0;
456480
};
@@ -522,6 +546,7 @@
522546
files = (
523547
B557DA1A20979D66005962F4 /* Settings.swift in Sources */,
524548
741B950120EBC8A700DD6E2D /* OrderCouponLine.swift in Sources */,
549+
74C8F06420EEB44800B6EDC9 /* OrderNote.swift in Sources */,
525550
B5BB1D0C20A2050300112D92 /* DateFormatter+Woo.swift in Sources */,
526551
B567AF2520A0CCA300AB6C62 /* AuthenticatedRequest.swift in Sources */,
527552
B505F6EA20BEFC3700BB1B69 /* MockupNetwork.swift in Sources */,
@@ -537,6 +562,7 @@
537562
B5C6FCCF20A3592900A4F8E4 /* OrderItem.swift in Sources */,
538563
B505F6EC20BEFDC200BB1B69 /* Loader.swift in Sources */,
539564
B5BB1D1220A255EC00112D92 /* OrderStatus.swift in Sources */,
565+
74C8F06820EEB7BD00B6EDC9 /* OrderNotesMapper.swift in Sources */,
540566
B5BB1D1020A237FB00112D92 /* Address.swift in Sources */,
541567
B557DA0420975500005962F4 /* OrdersRemote.swift in Sources */,
542568
B5C6FCD420A373BB00A4F8E4 /* OrderMapper.swift in Sources */,
@@ -554,10 +580,12 @@
554580
files = (
555581
B505F6D320BEE3A500BB1B69 /* AccountMapperTests.swift in Sources */,
556582
B5C6FCC820A32E4800A4F8E4 /* DateFormatterWooTests.swift in Sources */,
583+
74C8F06A20EEBC8C00B6EDC9 /* OrderMapperTests.swift in Sources */,
557584
B567AF3120A0FB8F00AB6C62 /* JetpackRequestTests.swift in Sources */,
558585
B505F6D720BEE58800BB1B69 /* AccountRemoteTests.swift in Sources */,
559586
B518662A20A09C6F00037A38 /* OrdersRemoteTests.swift in Sources */,
560587
B5969E1520A47F99005E9DF1 /* RemoteTests.swift in Sources */,
588+
74C8F06E20EEC1E800B6EDC9 /* OrderNotesMapperTests.swift in Sources */,
561589
B567AF2F20A0FB8F00AB6C62 /* AuthenticatedRequestTests.swift in Sources */,
562590
B5C6FCCD20A34B8300A4F8E4 /* OrderListMapperTests.swift in Sources */,
563591
B518663520A0A2E800037A38 /* Constants.swift in Sources */,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Foundation
2+
3+
4+
/// Mapper: OrderNotes
5+
///
6+
class OrderNotesMapper: Mapper {
7+
8+
/// (Attempts) to convert a dictionary into [OrderNote].
9+
///
10+
func map(response: Data) throws -> [OrderNote] {
11+
let decoder = JSONDecoder()
12+
decoder.dateDecodingStrategy = .formatted(DateFormatter.Defaults.dateTimeFormatter)
13+
14+
return try decoder.decode(OrderNotesEnvelope.self, from: response).orderNotes
15+
}
16+
}
17+
18+
19+
/// OrderNote Disposable Entity:
20+
/// `Load Order Notes` endpoint returns all of its notes within the `data` key. This entity
21+
/// allows us to do parse all the things with JSONDecoder.
22+
///
23+
private struct OrderNotesEnvelope: Decodable {
24+
let orderNotes: [OrderNote]
25+
26+
private enum CodingKeys: String, CodingKey {
27+
case orderNotes = "data"
28+
}
29+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import Foundation
2+
3+
4+
/// Represents an Order's Note Entity.
5+
///
6+
public struct OrderNote: Decodable {
7+
public let noteId: Int
8+
public let dateCreated: Date
9+
public let note: String
10+
public let isCustomerNote: Bool
11+
12+
/// Order struct initializer.
13+
///
14+
init(noteId: Int, dateCreated: Date, note: String, isCustomerNote: Bool) {
15+
self.noteId = noteId
16+
self.dateCreated = dateCreated
17+
self.note = note
18+
self.isCustomerNote = isCustomerNote
19+
}
20+
21+
/// The public initializer for OrderNote.
22+
///
23+
public init(from decoder: Decoder) throws {
24+
let container = try decoder.container(keyedBy: CodingKeys.self)
25+
let noteId = try container.decode(Int.self, forKey: .noteId)
26+
let dateCreated = try container.decodeIfPresent(Date.self, forKey: .dateCreated) ?? Date()
27+
let note = try container.decode(String.self, forKey: .note)
28+
let isCustomerNote = try container.decode(Bool.self, forKey: .isCustomerNote)
29+
30+
self.init(noteId: noteId, dateCreated: dateCreated, note: note, isCustomerNote: isCustomerNote) // initialize the struct
31+
}
32+
}
33+
34+
35+
/// Defines all of the OrderNote's CodingKeys.
36+
///
37+
private extension OrderNote {
38+
39+
enum CodingKeys: String, CodingKey {
40+
case noteId = "id"
41+
case dateCreated = "date_created_gmt"
42+
case note = "note"
43+
case isCustomerNote = "customer_note"
44+
}
45+
}
46+
47+
48+
// MARK: - Comparable Conformance
49+
//
50+
extension OrderNote: Comparable {
51+
public static func == (lhs: OrderNote, rhs: OrderNote) -> Bool {
52+
return lhs.noteId == rhs.noteId &&
53+
lhs.dateCreated == rhs.dateCreated &&
54+
lhs.note == rhs.note &&
55+
lhs.isCustomerNote == rhs.isCustomerNote
56+
}
57+
58+
public static func < (lhs: OrderNote, rhs: OrderNote) -> Bool {
59+
return lhs.noteId < rhs.noteId ||
60+
(lhs.noteId == rhs.noteId && lhs.dateCreated < rhs.dateCreated) ||
61+
(lhs.noteId == rhs.noteId && lhs.dateCreated == rhs.dateCreated && lhs.note < rhs.note)
62+
}
63+
}

Networking/Networking/Remote/OrdersRemote.swift

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ public class OrdersRemote: Remote {
2525
/// Retrieves a specific `Order`
2626
///
2727
/// - Parameters:
28-
/// - siteID: Site for which we'll fetch remote orders.
29-
/// - orderID: Order for which we'll fetch remote orders.
28+
/// - siteID: Site which hosts the Order.
29+
/// - orderID: Identifier of the Order.
3030
/// - completion: Closure to be executed upon completion.
3131
///
3232
public func loadOrder(for siteID: Int, orderID: Int, completion: @escaping (Order?, Error?) -> Void) {
@@ -37,6 +37,21 @@ public class OrdersRemote: Remote {
3737
enqueue(request, mapper: mapper, completion: completion)
3838
}
3939

40+
/// Retrieves the notes for a specific `Order`
41+
///
42+
/// - Parameters:
43+
/// - siteID: Site which hosts the Order.
44+
/// - orderID: Identifier of the Order.
45+
/// - completion: Closure to be executed upon completion.
46+
///
47+
public func loadOrderNotes(for siteID: Int, orderID: Int, completion: @escaping ([OrderNote]?, Error?) -> Void) {
48+
let path = "\(Constants.ordersPath)/\(orderID)/\(Constants.notesPath)/"
49+
let request = JetpackRequest(wooApiVersion: .mark2, method: .get, siteID: siteID, path: path, parameters: nil)
50+
let mapper = OrderNotesMapper()
51+
52+
enqueue(request, mapper: mapper, completion: completion)
53+
}
54+
4055
/// Updates the `OrderStatus` of a given Order.
4156
///
4257
/// - Parameters:
@@ -60,8 +75,9 @@ public class OrdersRemote: Remote {
6075
//
6176
private extension OrdersRemote {
6277
enum Constants {
63-
static let defaultPageSize: Int = 75
64-
static let ordersPath: String = "orders"
78+
static let defaultPageSize: Int = 75
79+
static let ordersPath: String = "orders"
80+
static let notesPath: String = "notes"
6581
}
6682

6783
enum ParameterKeys {

Networking/NetworkingTests/Mapper/OrderListMapperTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class OrderListMapperTests: XCTestCase {
8686
let brokenOrder = orders[0]
8787
let format = DateFormatter()
8888
format.dateStyle = .short
89-
89+
9090
let orderCreatedString = format.string(from: brokenOrder.dateCreated)
9191
let todayCreatedString = format.string(from: Date())
9292
XCTAssertEqual(orderCreatedString, todayCreatedString)
@@ -120,6 +120,6 @@ private extension OrderListMapperTests {
120120
/// Returns the OrderlistMapper output upon receiving `broken-order`
121121
///
122122
func mapLoadBrokenOrderResponse() -> [Order] {
123-
return mapOrders(from: "broken-order")
123+
return mapOrders(from: "broken-orders")
124124
}
125125
}

0 commit comments

Comments
 (0)