Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.

Commit c78d1f2

Browse files
committed
Add endpoint to fetch stats emails
1 parent 67db92e commit c78d1f2

File tree

6 files changed

+102
-2
lines changed

6 files changed

+102
-2
lines changed

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ let package = Package(
1111
targets: [
1212
.binaryTarget(
1313
name: "WordPressKit",
14-
url: "https://github.com/user-attachments/files/21474850/WordPressKit.zip",
15-
checksum: "4621e8faa2ce9c7ef847008b044ac9e04e733e6d8ede9e4a424eeb8c832b85c3"
14+
url: "https://github.com/user-attachments/files/21488685/WordPressKit.zip",
15+
checksum: "c591b9d12fdfeeedf7f31d884c6ce751722a37ae8ebb396a511fdb045698bccd"
1616
),
1717
]
1818
)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import Foundation
2+
3+
public struct StatsEmailOpensData: Decodable, Equatable {
4+
public let totalSends: Int?
5+
public let uniqueOpens: Int?
6+
public let totalOpens: Int?
7+
public let opensRate: Double?
8+
9+
public init(totalSends: Int?, uniqueOpens: Int?, totalOpens: Int?, opensRate: Double?) {
10+
self.totalSends = totalSends
11+
self.uniqueOpens = uniqueOpens
12+
self.totalOpens = totalOpens
13+
self.opensRate = opensRate
14+
}
15+
16+
private enum CodingKeys: String, CodingKey {
17+
case totalSends = "total_sends"
18+
case uniqueOpens = "unique_opens"
19+
case totalOpens = "total_opens"
20+
case opensRate = "opens_rate"
21+
}
22+
23+
public init(from decoder: any Decoder) throws {
24+
let container = try decoder.container(keyedBy: CodingKeys.self)
25+
totalSends = try container.decodeIfPresent(Int.self, forKey: .totalSends)
26+
uniqueOpens = try container.decodeIfPresent(Int.self, forKey: .uniqueOpens)
27+
totalOpens = try container.decodeIfPresent(Int.self, forKey: .totalOpens)
28+
opensRate = try container.decodeIfPresent(Double.self, forKey: .opensRate)
29+
}
30+
}
31+
32+
extension StatsEmailOpensData {
33+
public init?(jsonDictionary: [String: AnyObject]) {
34+
do {
35+
let jsonData = try JSONSerialization.data(withJSONObject: jsonDictionary, options: [])
36+
let decoder = JSONDecoder()
37+
self = try decoder.decode(Self.self, from: jsonData)
38+
} catch {
39+
return nil
40+
}
41+
}
42+
}

Sources/WordPressKit/Services/StatsServiceRemoteV2.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,28 @@ public extension StatsServiceRemoteV2 {
372372
}
373373
}
374374

375+
// MARK: - Email Opens
376+
377+
public extension StatsServiceRemoteV2 {
378+
func getEmailOpens(for postID: Int, completion: @escaping ((StatsEmailOpensData?, Error?) -> Void)) {
379+
let path = self.path(forEndpoint: "sites/\(siteID)/stats/opens/emails/\(postID)/rate", withVersion: ._1_1)
380+
381+
wordPressComRESTAPI.get(path, parameters: [:], success: { (response, _) in
382+
guard
383+
let jsonResponse = response as? [String: AnyObject],
384+
let emailOpensData = StatsEmailOpensData(jsonDictionary: jsonResponse)
385+
else {
386+
completion(nil, ResponseError.decodingFailure)
387+
return
388+
}
389+
390+
completion(emailOpensData, nil)
391+
}, failure: { (error, _) in
392+
completion(nil, error)
393+
})
394+
}
395+
}
396+
375397
// This serves both as a way to get the query properties in a "nice" way,
376398
// but also as a way to narrow down the generic type in `getInsight(completion:)` method.
377399
public protocol StatsInsightData {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"total_sends": 1,
3+
"unique_opens": 1,
4+
"total_opens": 4,
5+
"opens_rate": 1
6+
}

Tests/WordPressKitTests/Tests/StatsRemoteV2Tests.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class StatsRemoteV2Tests: RemoteTestCase, RESTTestable {
2727
let toggleSpamStateResponseFilename = "stats-referrer-mark-as-spam.json"
2828
let getStatsSummaryFilename = "stats-summary.json"
2929
let getArchivesDataFilename = "stats-archives-data.json"
30+
let getEmailOpensFilename = "stats-email-opens.json"
3031

3132
// MARK: - Properties
3233

@@ -44,6 +45,7 @@ class StatsRemoteV2Tests: RemoteTestCase, RESTTestable {
4445
var sitePostDetailsEndpoint: String { return "sites/\(siteID)/stats/post/9001" }
4546
var siteStatsSummaryEndpoint: String { return "sites/\(siteID)/stats/summary/" }
4647
var siteArchivesDataEndpoint: String { return "sites/\(siteID)/stats/archives" }
48+
var siteEmailOpensEndpoint: String { return "sites/\(siteID)/stats/opens/emails/231/rate" }
4749

4850
func toggleSpamStateEndpoint(for referrerDomain: String, markAsSpam: Bool) -> String {
4951
let action = markAsSpam ? "new" : "delete"
@@ -832,4 +834,24 @@ class StatsRemoteV2Tests: RemoteTestCase, RESTTestable {
832834
XCTAssertEqual(author.last?.value, "")
833835
XCTAssertEqual(author.last?.views, 2)
834836
}
837+
838+
func testEmailOpens() {
839+
let expect = expectation(description: "It should return email opens data")
840+
841+
stubRemoteResponse(siteEmailOpensEndpoint, filename: getEmailOpensFilename, contentType: .ApplicationJSON)
842+
843+
remote.getEmailOpens(for: 231) { (emailOpens, error) in
844+
XCTAssertNil(error)
845+
XCTAssertNotNil(emailOpens)
846+
847+
XCTAssertEqual(emailOpens?.totalSends, 1)
848+
XCTAssertEqual(emailOpens?.uniqueOpens, 1)
849+
XCTAssertEqual(emailOpens?.totalOpens, 4)
850+
XCTAssertEqual(emailOpens?.opensRate, 1)
851+
852+
expect.fulfill()
853+
}
854+
855+
waitForExpectations(timeout: timeout, handler: nil)
856+
}
835857
}

WordPressKit.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
/* Begin PBXBuildFile section */
1010
01383F7F2BD5545B00496B76 /* StatsEmailsSummaryDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01383F7E2BD5545B00496B76 /* StatsEmailsSummaryDataTests.swift */; };
1111
01383F822BD556B100496B76 /* stats-emails-summary.json in Resources */ = {isa = PBXBuildFile; fileRef = 01383F802BD5549E00496B76 /* stats-emails-summary.json */; };
12+
7B859118F037432BB78618D6 /* stats-email-opens.json in Resources */ = {isa = PBXBuildFile; fileRef = 7B859117F037432BB78618D6 /* stats-email-opens.json */; };
1213
01438D362B6A31540097D60A /* stats-visits-month-unit-week.json in Resources */ = {isa = PBXBuildFile; fileRef = 01438D342B6A2B2C0097D60A /* stats-visits-month-unit-week.json */; };
1314
01438D392B6A361B0097D60A /* stats-summary.json in Resources */ = {isa = PBXBuildFile; fileRef = 01438D372B6A35FB0097D60A /* stats-summary.json */; };
1415
01438D3B2B6A36BF0097D60A /* StatsTotalsSummaryData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01438D3A2B6A36BF0097D60A /* StatsTotalsSummaryData.swift */; };
1516
0152100C28EDA9E400DD6783 /* StatsAnnualAndMostPopularTimeInsightDecodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0152100B28EDA9E400DD6783 /* StatsAnnualAndMostPopularTimeInsightDecodingTests.swift */; };
1617
019C5B8B2BD59CE000A69DB0 /* StatsEmailsSummaryData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019C5B892BD59CE000A69DB0 /* StatsEmailsSummaryData.swift */; };
18+
11B3582786AD49E591C8615E /* StatsEmailOpensData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3582686AD49E591C8615E /* StatsEmailOpensData.swift */; };
1719
0847B92C2A4442730044D32F /* IPLocationRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0847B92B2A4442730044D32F /* IPLocationRemote.swift */; };
1820
08C7493E2A45EA11000DA0E2 /* IPLocationRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C7493D2A45EA11000DA0E2 /* IPLocationRemoteTests.swift */; };
1921
0C0791B22BFE7DE50049C06E /* JetpackAssistantFeatureDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0791B12BFE7DE50049C06E /* JetpackAssistantFeatureDetails.swift */; };
@@ -798,11 +800,13 @@
798800
/* Begin PBXFileReference section */
799801
01383F7E2BD5545B00496B76 /* StatsEmailsSummaryDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsEmailsSummaryDataTests.swift; sourceTree = "<group>"; };
800802
01383F802BD5549E00496B76 /* stats-emails-summary.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "stats-emails-summary.json"; sourceTree = "<group>"; };
803+
7B859117F037432BB78618D6 /* stats-email-opens.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "stats-email-opens.json"; sourceTree = "<group>"; };
801804
01438D342B6A2B2C0097D60A /* stats-visits-month-unit-week.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "stats-visits-month-unit-week.json"; sourceTree = "<group>"; };
802805
01438D372B6A35FB0097D60A /* stats-summary.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "stats-summary.json"; sourceTree = "<group>"; };
803806
01438D3A2B6A36BF0097D60A /* StatsTotalsSummaryData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatsTotalsSummaryData.swift; sourceTree = "<group>"; };
804807
0152100B28EDA9E400DD6783 /* StatsAnnualAndMostPopularTimeInsightDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsAnnualAndMostPopularTimeInsightDecodingTests.swift; sourceTree = "<group>"; };
805808
019C5B892BD59CE000A69DB0 /* StatsEmailsSummaryData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatsEmailsSummaryData.swift; sourceTree = "<group>"; };
809+
11B3582686AD49E591C8615E /* StatsEmailOpensData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatsEmailOpensData.swift; sourceTree = "<group>"; };
806810
0847B92B2A4442730044D32F /* IPLocationRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPLocationRemote.swift; sourceTree = "<group>"; };
807811
08C7493D2A45EA11000DA0E2 /* IPLocationRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPLocationRemoteTests.swift; sourceTree = "<group>"; };
808812
0C0791B12BFE7DE50049C06E /* JetpackAssistantFeatureDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackAssistantFeatureDetails.swift; sourceTree = "<group>"; };
@@ -1617,6 +1621,7 @@
16171621
isa = PBXGroup;
16181622
children = (
16191623
019C5B892BD59CE000A69DB0 /* StatsEmailsSummaryData.swift */,
1624+
11B3582686AD49E591C8615E /* StatsEmailOpensData.swift */,
16201625
);
16211626
path = Emails;
16221627
sourceTree = "<group>";
@@ -2621,6 +2626,7 @@
26212626
01438D342B6A2B2C0097D60A /* stats-visits-month-unit-week.json */,
26222627
40819779221F153A00A298E4 /* stats-visits-week.json */,
26232628
01383F802BD5549E00496B76 /* stats-emails-summary.json */,
2629+
7B859117F037432BB78618D6 /* stats-email-opens.json */,
26242630
436D56392118DE3B00CEAA33 /* supported-countries-success.json */,
26252631
436D56522121F60400CEAA33 /* supported-states-empty.json */,
26262632
436D563D2118E34D00CEAA33 /* supported-states-success.json */,
@@ -3297,6 +3303,7 @@
32973303
7434E1DE1F17C3C900C40DDB /* site-users-update-role-unknown-user-failure.json in Resources */,
32983304
4081977B221F153B00A298E4 /* stats-visits-week.json in Resources */,
32993305
01383F822BD556B100496B76 /* stats-emails-summary.json in Resources */,
3306+
7B859118F037432BB78618D6 /* stats-email-opens.json in Resources */,
33003307
);
33013308
runOnlyForDeploymentPostprocessing = 0;
33023309
};
@@ -3385,6 +3392,7 @@
33853392
40E7FEB722106A8D0032834E /* StatsCommentsInsight.swift in Sources */,
33863393
0C31499B2E2FFBA100AAF9DF /* StatsArchiveTimeIntervalData.swift in Sources */,
33873394
019C5B8B2BD59CE000A69DB0 /* StatsEmailsSummaryData.swift in Sources */,
3395+
11B3582786AD49E591C8615E /* StatsEmailOpensData.swift in Sources */,
33883396
9856BE962630B5C200C12FEB /* RemoteUser+Likes.swift in Sources */,
33893397
3FD634E52BC3A55F00CEDF5E /* WordPressOrgXMLRPCValidator.swift in Sources */,
33903398
3FE2E97B2BC3A332002CA2E1 /* WordPressAPIError+NSErrorBridge.swift in Sources */,

0 commit comments

Comments
 (0)