Skip to content

Commit acdb3fc

Browse files
authored
Merge pull request #176 from woocommerce/feature/75-add-note
Order Details: Add a note
2 parents 6e4523e + 42886ac commit acdb3fc

File tree

18 files changed

+668
-11
lines changed

18 files changed

+668
-11
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@
6161
B5C6FCD420A373BB00A4F8E4 /* OrderMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C6FCD320A373BA00A4F8E4 /* OrderMapper.swift */; };
6262
B5C6FCD620A3768900A4F8E4 /* order.json in Resources */ = {isa = PBXBuildFile; fileRef = B5C6FCD520A3768900A4F8E4 /* order.json */; };
6363
CE20179320E3EFA7005B4C18 /* broken-orders.json in Resources */ = {isa = PBXBuildFile; fileRef = CE20179220E3EFA7005B4C18 /* broken-orders.json */; };
64+
CE21B3E72106811000A259D5 /* new-order-note.json in Resources */ = {isa = PBXBuildFile; fileRef = CE21B3E62106811000A259D5 /* new-order-note.json */; };
65+
CE583A0E2109154500D73C1C /* OrderNoteMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE583A0D2109154500D73C1C /* OrderNoteMapper.swift */; };
6466
/* End PBXBuildFile section */
6567

6668
/* Begin PBXContainerItemProxy section */
@@ -133,6 +135,8 @@
133135
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>"; };
134136
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>"; };
135137
CE20179220E3EFA7005B4C18 /* broken-orders.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "broken-orders.json"; sourceTree = "<group>"; };
138+
CE21B3E62106811000A259D5 /* new-order-note.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "new-order-note.json"; sourceTree = "<group>"; };
139+
CE583A0D2109154500D73C1C /* OrderNoteMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderNoteMapper.swift; sourceTree = "<group>"; };
136140
F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Networking.framework; sourceTree = BUILT_PRODUCTS_DIR; };
137141
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>"; };
138142
/* End PBXFileReference section */
@@ -314,6 +318,7 @@
314318
B559EBA820A0B5B100836CD4 /* Responses */ = {
315319
isa = PBXGroup;
316320
children = (
321+
CE21B3E62106811000A259D5 /* new-order-note.json */,
317322
B505F6D420BEE4E600BB1B69 /* me.json */,
318323
B559EBA920A0B5CD00836CD4 /* orders-load-all.json */,
319324
B5C6FCD520A3768900A4F8E4 /* order.json */,
@@ -335,6 +340,7 @@
335340
B567AF2A20A0FA4200AB6C62 /* OrderListMapper.swift */,
336341
B56C1EB520EA757B00D749F9 /* SiteListMapper.swift */,
337342
74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */,
343+
CE583A0D2109154500D73C1C /* OrderNoteMapper.swift */,
338344
);
339345
path = Mapper;
340346
sourceTree = "<group>";
@@ -482,6 +488,7 @@
482488
files = (
483489
74C8F06620EEB76400B6EDC9 /* order-notes.json in Resources */,
484490
74C8F06C20EEBD5D00B6EDC9 /* broken-order.json in Resources */,
491+
CE21B3E72106811000A259D5 /* new-order-note.json in Resources */,
485492
B505F6D520BEE4E700BB1B69 /* me.json in Resources */,
486493
B5C6FCD620A3768900A4F8E4 /* order.json in Resources */,
487494
B559EBAA20A0B5CD00836CD4 /* orders-load-all.json in Resources */,
@@ -575,6 +582,7 @@
575582
B518662220A097C200037A38 /* Network.swift in Sources */,
576583
B518662420A099BF00037A38 /* AlamofireNetwork.swift in Sources */,
577584
B557DA1820979D51005962F4 /* Credentials.swift in Sources */,
585+
CE583A0E2109154500D73C1C /* OrderNoteMapper.swift in Sources */,
578586
B5C6FCCF20A3592900A4F8E4 /* OrderItem.swift in Sources */,
579587
B505F6EC20BEFDC200BB1B69 /* Loader.swift in Sources */,
580588
B5BB1D1220A255EC00112D92 /* OrderStatus.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: OrderNote (Singular)
5+
///
6+
class OrderNoteMapper: Mapper {
7+
8+
/// (Attempts) to convert a dictionary into a single 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(OrderNoteEnvelope.self, from: response).orderNote
15+
}
16+
}
17+
18+
19+
/// OrderNote Disposable Entity:
20+
/// `Add Order Note` endpoint the single added note within the `data` key. This entity
21+
/// allows us to parse all the things with JSONDecoder.
22+
///
23+
private struct OrderNoteEnvelope: Decodable {
24+
let orderNote: OrderNote
25+
26+
private enum CodingKeys: String, CodingKey {
27+
case orderNote = "data"
28+
}
29+
}

Networking/Networking/Remote/OrdersRemote.swift

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,26 @@ public class OrdersRemote: Remote {
6868
let request = JetpackRequest(wooApiVersion: .mark2, method: .post, siteID: siteID, path: path, parameters: parameters)
6969
enqueue(request, mapper: mapper, completion: completion)
7070
}
71+
72+
/// Adds an order note to a specific Order.
73+
///
74+
/// - Parameters:
75+
/// - siteID: Site which hosts the Order.
76+
/// - orderID: Identifier of the Order to be updated.
77+
/// - isCustomerNote: if true, the note will be shown to customers and they will be notified.
78+
/// if false, the note will be for admin reference only. Default is false.
79+
/// - note: The note to be posted.
80+
/// - completion: Closure to be executed upon completion.
81+
///
82+
public func addOrderNote(for siteID: Int, orderID: Int, isCustomerNote: Bool, with note: String, completion: @escaping (OrderNote?, Error?) -> Void) {
83+
let path = "\(Constants.ordersPath)/" + String(orderID) + "/" + "\(Constants.notesPath)"
84+
let parameters = [ParameterKeys.note: note,
85+
ParameterKeys.customerNote: String(isCustomerNote)]
86+
let mapper = OrderNoteMapper()
87+
88+
let request = JetpackRequest(wooApiVersion: .mark2, method: .post, siteID: siteID, path: path, parameters: parameters)
89+
enqueue(request, mapper: mapper, completion: completion)
90+
}
7191
}
7292

7393

@@ -81,8 +101,10 @@ private extension OrdersRemote {
81101
}
82102

83103
enum ParameterKeys {
84-
static let status: String = "status"
85-
static let page: String = "page"
86-
static let perPage: String = "per_page"
104+
static let customerNote: String = "customer_note"
105+
static let note: String = "note"
106+
static let page: String = "page"
107+
static let perPage: String = "per_page"
108+
static let status: String = "status"
87109
}
88110
}

Networking/NetworkingTests/Remote/OrdersRemoteTests.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,21 @@ class OrdersRemoteTests: XCTestCase {
166166

167167
wait(for: [expectation], timeout: Constants.expectationTimeout)
168168
}
169+
170+
/// Verifies that addOrderNote properly parses the `new-order-note` sample response.
171+
///
172+
func testLoadAddOrderNoteProperlyReturnsParsedOrderNote() {
173+
let remote = OrdersRemote(network: network)
174+
let expectation = self.expectation(description: "Add Order Note")
175+
let noteData = "This order would be so much better with ketchup."
176+
177+
network.simulateResponse(requestUrlSuffix: "orders/\(sampleOrderID)/notes", filename: "new-order-note")
178+
179+
remote.addOrderNote(for: sampleSiteID, orderID: sampleOrderID, isCustomerNote: true, with: noteData) { (orderNote, error) in
180+
XCTAssertNil(error)
181+
XCTAssertNotNil(orderNote)
182+
expectation.fulfill()
183+
}
184+
wait(for: [expectation], timeout: Constants.expectationTimeout)
185+
}
169186
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"data": {
3+
"id": 2235,
4+
"date_created": "2018-06-22T11:36:20",
5+
"date_created_gmt": "2018-06-22T15:36:20",
6+
"note": "This order would be so much better with ketchup.",
7+
"customer_note": true,
8+
"_links": {
9+
"self": [
10+
{
11+
"href": "https://jamosova3.mystagingwebsite.com/wp-json/wc/v2/orders/1179/notes/2235"
12+
}
13+
],
14+
"collection": [
15+
{
16+
"href": "https://jamosova3.mystagingwebsite.com/wp-json/wc/v2/orders/1179/notes"
17+
}
18+
],
19+
"up": [
20+
{
21+
"href": "https://jamosova3.mystagingwebsite.com/wp-json/wc/v2/orders/1179"
22+
}
23+
]
24+
}
25+
}
26+
}

WooCommerce/Classes/Extensions/UIView+Helpers.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22
import UIKit
33

44

5-
/// UIView Helper Methods
5+
/// UIView Class Methods
66
///
77
extension UIView {
88

@@ -19,3 +19,23 @@ extension UIView {
1919
return loadNib().instantiate(withOwner: nil, options: nil).first as! T
2020
}
2121
}
22+
23+
24+
/// UIView Extension Methods
25+
///
26+
extension UIView {
27+
28+
/// Returns the first Subview of the specified Type (if any).
29+
///
30+
func firstSubview<T: UIView>(ofType type: T.Type) -> T? {
31+
for subview in subviews {
32+
guard let target = (subview as? T) ?? subview.firstSubview(ofType: type) else {
33+
continue
34+
}
35+
36+
return target
37+
}
38+
39+
return nil
40+
}
41+
}

0 commit comments

Comments
 (0)