Skip to content

Commit 01b2cf4

Browse files
authored
Merge pull request #6037 from woocommerce/issue/5952-API-for-dismissing-Inbox-Notes
Dismiss an Inbox Note from `DELETE /wc-analytics/admin/notes/delete/{{id}}` - Networking layer
2 parents 2c21e64 + f53d082 commit 01b2cf4

File tree

7 files changed

+238
-1
lines changed

7 files changed

+238
-1
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@
219219
451274A625276C82009911FF /* product-variation.json in Resources */ = {isa = PBXBuildFile; fileRef = 451274A525276C82009911FF /* product-variation.json */; };
220220
4513382027A8227F00AE5E78 /* InboxNotesRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4513381F27A8227F00AE5E78 /* InboxNotesRemote.swift */; };
221221
4513382227A8409000AE5E78 /* InboxNotesRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4513382127A8409000AE5E78 /* InboxNotesRemoteTests.swift */; };
222+
4513382427A951B300AE5E78 /* InboxNoteMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4513382327A951B300AE5E78 /* InboxNoteMapper.swift */; };
223+
4513382627A96DB700AE5E78 /* InboxNoteMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4513382527A96DB700AE5E78 /* InboxNoteMapperTests.swift */; };
224+
4513382827A96DE700AE5E78 /* inbox-note.json in Resources */ = {isa = PBXBuildFile; fileRef = 4513382727A96DE700AE5E78 /* inbox-note.json */; };
222225
45150A9A268340D2006922EA /* Country.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45150A99268340D2006922EA /* Country.swift */; };
223226
45150A9C2683417A006922EA /* StateOfACountry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45150A9B2683417A006922EA /* StateOfACountry.swift */; };
224227
45150A9E26836A57006922EA /* CountryListMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45150A9D26836A57006922EA /* CountryListMapper.swift */; };
@@ -873,6 +876,9 @@
873876
451274A525276C82009911FF /* product-variation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "product-variation.json"; sourceTree = "<group>"; };
874877
4513381F27A8227F00AE5E78 /* InboxNotesRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxNotesRemote.swift; sourceTree = "<group>"; };
875878
4513382127A8409000AE5E78 /* InboxNotesRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxNotesRemoteTests.swift; sourceTree = "<group>"; };
879+
4513382327A951B300AE5E78 /* InboxNoteMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxNoteMapper.swift; sourceTree = "<group>"; };
880+
4513382527A96DB700AE5E78 /* InboxNoteMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxNoteMapperTests.swift; sourceTree = "<group>"; };
881+
4513382727A96DE700AE5E78 /* inbox-note.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "inbox-note.json"; sourceTree = "<group>"; };
876882
45150A99268340D2006922EA /* Country.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Country.swift; sourceTree = "<group>"; };
877883
45150A9B2683417A006922EA /* StateOfACountry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateOfACountry.swift; sourceTree = "<group>"; };
878884
45150A9D26836A57006922EA /* CountryListMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountryListMapper.swift; sourceTree = "<group>"; };
@@ -1975,6 +1981,7 @@
19751981
DEC51AEC2768A0AD009F3DF4 /* systemStatusWithPluginsOnly.json */,
19761982
077F39D726A58EB600ABEADC /* systemStatus.json */,
19771983
45CCFCE927A2E59B0012E8CB /* inbox-note-list.json */,
1984+
4513382727A96DE700AE5E78 /* inbox-note.json */,
19781985
);
19791986
path = Responses;
19801987
sourceTree = "<group>";
@@ -1994,6 +2001,7 @@
19942001
B524193E21AC5FE400D6FC0A /* DotcomDeviceMapper.swift */,
19952002
24F98C572502EA8800F49B68 /* FeatureFlagMapper.swift */,
19962003
AEF9458A27297FF6001DCCFB /* IgnoringResponseMapper.swift */,
2004+
4513382327A951B300AE5E78 /* InboxNoteMapper.swift */,
19972005
45CCFCE527A2E3710012E8CB /* InboxNoteListMapper.swift */,
19982006
26B2F74424C5573F0065CCC8 /* LeaderboardListMapper.swift */,
19992007
020D07BD23D8570800FD9580 /* MediaListMapper.swift */,
@@ -2136,6 +2144,7 @@
21362144
45150A9F26837357006922EA /* CountryListMapperTests.swift */,
21372145
B524194221AC622500D6FC0A /* DotcomDeviceMapperTests.swift */,
21382146
AED8AEBB272A997500663FCC /* IgnoringResponseMapperTests.swift */,
2147+
4513382527A96DB700AE5E78 /* InboxNoteMapperTests.swift */,
21392148
45CCFCE727A2E5020012E8CB /* InboxNoteListMapperTests.swift */,
21402149
B554FA922180C17200C54DFF /* NoteHashListMapperTests.swift */,
21412150
B5C151BF217EE3FB00C7BDC1 /* NoteListMapperTests.swift */,
@@ -2472,6 +2481,7 @@
24722481
02C254D72563999300A04423 /* order-shipping-labels.json in Resources */,
24732482
45B204BC24890B1200FE6526 /* category.json in Resources */,
24742483
022902D422E2436400059692 /* stats_module_disabled_error.json in Resources */,
2484+
4513382827A96DE700AE5E78 /* inbox-note.json in Resources */,
24752485
31A451D227863A2E00FE81AA /* stripe-account-restricted-pending.json in Resources */,
24762486
45CCFCEA27A2E59B0012E8CB /* inbox-note-list.json in Resources */,
24772487
025CA2C8238F4FF400B05C81 /* product-shipping-classes-load-all.json in Resources */,
@@ -2771,6 +2781,7 @@
27712781
74A1D26821189A7100931DFA /* SiteVisitStats.swift in Sources */,
27722782
02C254B0256378D000A04423 /* ShippingLabelRefundStatus.swift in Sources */,
27732783
4568E2242459D3230007E478 /* Post.swift in Sources */,
2784+
4513382427A951B300AE5E78 /* InboxNoteMapper.swift in Sources */,
27742785
DE2095BF279583A100171F1C /* CouponReportListMapper.swift in Sources */,
27752786
B557DA0320975500005962F4 /* Remote.swift in Sources */,
27762787
B53EF53C21814900003E146F /* SuccessResultMapper.swift in Sources */,
@@ -2985,6 +2996,7 @@
29852996
D88D5A4F230BD276007B6E01 /* ProductReviewListMapperTests.swift in Sources */,
29862997
B567AF3120A0FB8F00AB6C62 /* JetpackRequestTests.swift in Sources */,
29872998
7412A8F221B6E47A005D182A /* ReportRemoteTests.swift in Sources */,
2999+
4513382627A96DB700AE5E78 /* InboxNoteMapperTests.swift in Sources */,
29883000
7412A8EE21B6E335005D182A /* ReportOrderMapperTests.swift in Sources */,
29893001
AED8AEBC272A997500663FCC /* IgnoringResponseMapperTests.swift in Sources */,
29903002
74CF0A8C22414D7800DB993F /* ProductMapperTests.swift in Sources */,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Foundation
2+
3+
/// Mapper: Inbox Note
4+
///
5+
struct InboxNoteMapper: Mapper {
6+
7+
/// Site we're parsing `InboxNote`s for
8+
/// We're injecting this field by copying it in after parsing response, because `siteID` is not returned in any of the Inbox Note endpoints.
9+
///
10+
let siteID: Int64
11+
12+
/// (Attempts) to convert a dictionary into an Inbox Note.
13+
///
14+
func map(response: Data) throws -> InboxNote {
15+
let decoder = JSONDecoder()
16+
decoder.dateDecodingStrategy = .formatted(DateFormatter.Defaults.dateTimeFormatter)
17+
decoder.userInfo = [
18+
.siteID: siteID
19+
]
20+
return try decoder.decode(InboxNote.self, from: response)
21+
}
22+
}

Networking/Networking/Remote/InboxNotesRemote.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ public protocol InboxNotesRemoteProtocol {
1212
type: [InboxNotesRemote.NoteType]?,
1313
status: [InboxNotesRemote.Status]?,
1414
completion: @escaping (Result<[InboxNote], Error>) -> ())
15+
16+
func dismissInboxNote(for siteID: Int64,
17+
noteID: Int64,
18+
completion: @escaping (Result<InboxNote, Error>) -> ())
1519
}
1620

1721

@@ -64,6 +68,31 @@ public final class InboxNotesRemote: Remote, InboxNotesRemoteProtocol {
6468

6569
enqueue(request, mapper: mapper, completion: completion)
6670
}
71+
72+
// MARK: - DISMISS Inbox Note
73+
74+
/// Dismiss one `InboxNote`.
75+
/// This internally marks a notification’s is_deleted field to true and such notifications do not show in the results anymore.
76+
///
77+
/// - Parameters:
78+
/// - siteID: The site for which we'll fetch InboxNotes.
79+
/// - noteID: The ID of the note that should be marked as dismissed.
80+
/// - completion: Closure to be executed upon completion.
81+
///
82+
public func dismissInboxNote(for siteID: Int64,
83+
noteID: Int64,
84+
completion: @escaping (Result<InboxNote, Error>) -> ()) {
85+
86+
let request = JetpackRequest(wooApiVersion: .wcAnalytics,
87+
method: .delete,
88+
siteID: siteID,
89+
path: Path.notes + "/\(noteID)",
90+
parameters: nil)
91+
92+
let mapper = InboxNoteMapper(siteID: siteID)
93+
94+
enqueue(request, mapper: mapper, completion: completion)
95+
}
6796
}
6897

6998
// MARK: - Constants

Networking/NetworkingTests/Mapper/InboxNoteListMapperTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ final class InboxNoteListMapperTests: XCTestCase {
6262
///
6363
private extension InboxNoteListMapperTests {
6464

65-
/// Returns the CouponListMapper output upon receiving `filename` (Data Encoded)
65+
/// Returns the InboxNoteListMapper output upon receiving `filename` (Data Encoded)
6666
///
6767
func mapInboxNoteList(from filename: String) throws -> [InboxNote] {
6868
guard let response = Loader.contentsOf(filename) else {
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import XCTest
2+
@testable import Networking
3+
4+
final class InboxNoteMapperTests: XCTestCase {
5+
6+
/// Dummy Site ID.
7+
///
8+
private let dummySiteID: Int64 = 12983476
9+
10+
/// Verifies that the whole list is parsed.
11+
///
12+
func test_InboxNoteMapper_parses_the_InboxNote_in_response() throws {
13+
let inboxNote = try mapLoadInboxNoteResponse()
14+
XCTAssertNotNil(inboxNote)
15+
}
16+
17+
/// Verifies that the `siteID` is added in the mapper, because it's not provided by the API endpoint.
18+
///
19+
func test_InboxNoteMapper_includes_siteID_in_parsed_result() throws {
20+
let inboxNote = try mapLoadInboxNoteResponse()
21+
XCTAssertEqual(inboxNote?.siteID, dummySiteID)
22+
}
23+
24+
/// Verifies that the fields are all parsed correctly.
25+
///
26+
func test_InboxNoteMapper_parses_all_fields_in_result() throws {
27+
// Given
28+
let inboxNote = try mapLoadInboxNoteResponse()
29+
30+
// When
31+
let dateFormatter = DateFormatter.Defaults.dateTimeFormatter
32+
let url = "https://woocommerce.com/products/woocommerce-bookings/"
33+
let content = "Your subscription expired on October 22nd. Get a new subscription to continue receiving updates and access to support."
34+
let expectedInboxNote = InboxNote(siteID: dummySiteID,
35+
id: 296,
36+
name: "wc-admin-wc-helper-subscription",
37+
type: "warning",
38+
status: "unactioned",
39+
actions: [InboxAction(id: 13329,
40+
name: "renew-subscription",
41+
label: "Renew Subscription",
42+
status: "actioned",
43+
url: url)],
44+
title: "WooCommerce Bookings subscription expired",
45+
content: content,
46+
isDeleted: true,
47+
isRead: false,
48+
dateCreated: dateFormatter.date(from: "2022-01-31T14:25:32")!)
49+
50+
// Then
51+
XCTAssertEqual(inboxNote, expectedInboxNote)
52+
}
53+
}
54+
55+
56+
// MARK: - Test Helpers
57+
///
58+
private extension InboxNoteMapperTests {
59+
60+
/// Returns the InboxNoteMapper output upon receiving `filename` (Data Encoded)
61+
///
62+
func mapInboxNote(from filename: String) throws -> InboxNote? {
63+
guard let response = Loader.contentsOf(filename) else {
64+
return nil
65+
}
66+
67+
return try InboxNoteMapper(siteID: dummySiteID).map(response: response)
68+
}
69+
70+
/// Returns the InboxNoteMapper output from `inbox-note.json`
71+
///
72+
func mapLoadInboxNoteResponse() throws -> InboxNote? {
73+
return try mapInboxNote(from: "inbox-note")
74+
}
75+
}

Networking/NetworkingTests/Remote/InboxNotesRemoteTests.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,51 @@ final class InboxNotesRemoteTests: XCTestCase {
100100
let resultError = try XCTUnwrap(result.failure as? NetworkError)
101101
XCTAssertNotNil(resultError)
102102
}
103+
104+
// MARK: - Dismiss an Inbox Note tests
105+
106+
/// Verifies that dismissInboxNote properly parses the `InboxNote` sample response.
107+
///
108+
func test_dismissInboxNote_properly_returns_parsed_InboxNote() throws {
109+
// Given
110+
let remote = InboxNotesRemote(network: network)
111+
let sampleInboxNoteID: Int64 = 296
112+
113+
network.simulateResponse(requestUrlSuffix: "admin/notes/\(sampleInboxNoteID)", filename: "inbox-note")
114+
115+
// When
116+
let result = waitFor { promise in
117+
remote.dismissInboxNote(for: self.sampleSiteID, noteID: sampleInboxNoteID) { result in
118+
promise(result)
119+
}
120+
}
121+
122+
// Then
123+
XCTAssert(result.isSuccess)
124+
let inboxNote = try XCTUnwrap(result.get())
125+
XCTAssertEqual(inboxNote.id, sampleInboxNoteID)
126+
}
127+
128+
/// Verifies that dismissInboxNote properly relays Networking Layer errors.
129+
///
130+
func test_dismissInboxNote_properly_relays_networking_errors() throws {
131+
// Given
132+
let remote = InboxNotesRemote(network: network)
133+
let sampleInboxNoteID: Int64 = 296
134+
135+
let error = NetworkError.unacceptableStatusCode(statusCode: 500)
136+
network.simulateError(requestUrlSuffix: "admin/notes/\(sampleInboxNoteID)", error: error)
137+
138+
// When
139+
let result = waitFor { promise in
140+
remote.dismissInboxNote(for: self.sampleSiteID, noteID: sampleInboxNoteID) { result in
141+
promise(result)
142+
}
143+
}
144+
145+
// Then
146+
XCTAssertTrue(result.isFailure)
147+
let resultError = try XCTUnwrap(result.failure as? NetworkError)
148+
XCTAssertEqual(resultError, .unacceptableStatusCode(statusCode: 500))
149+
}
103150
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"id": 296,
3+
"name": "wc-admin-wc-helper-subscription",
4+
"type": "warning",
5+
"locale": "en_US",
6+
"title": "WooCommerce Bookings subscription expired",
7+
"content": "Your subscription expired on October 22nd. Get a new subscription to continue receiving updates and access to support.",
8+
"content_data": {
9+
"product_id": 390890,
10+
"product_name": "WooCommerce Bookings",
11+
"expired": true,
12+
"expires": 1634860800,
13+
"expires_date": "October 22nd"
14+
},
15+
"status": "unactioned",
16+
"source": "woocommerce-admin",
17+
"date_created": "2022-01-31T08:55:32",
18+
"date_reminder": null,
19+
"is_snoozable": false,
20+
"actions": [
21+
{
22+
"id": 13329,
23+
"name": "renew-subscription",
24+
"label": "Renew Subscription",
25+
"query": "https://woocommerce.com/products/woocommerce-bookings/",
26+
"status": "actioned",
27+
"primary": false,
28+
"actioned_text": "",
29+
"nonce_action": null,
30+
"nonce_name": null,
31+
"url": "https://woocommerce.com/products/woocommerce-bookings/"
32+
}
33+
],
34+
"layout": "plain",
35+
"image": "",
36+
"is_deleted": true,
37+
"is_read": false,
38+
"date_created_gmt": "2022-01-31T14:25:32",
39+
"date_reminder_gmt": null,
40+
"_links": {
41+
"self": [
42+
{
43+
"href": "https://mywootesting.mystagingwebsite.com/wp-json/wc-analytics/admin/notes/296"
44+
}
45+
],
46+
"collection": [
47+
{
48+
"href": "https://mywootesting.mystagingwebsite.com/wp-json/wc-analytics/admin/notes"
49+
}
50+
]
51+
}
52+
}

0 commit comments

Comments
 (0)