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

Commit 9c20c99

Browse files
committed
Extend StatsPostDetails
1 parent a8b31bc commit 9c20c99

File tree

4 files changed

+225
-32
lines changed

4 files changed

+225
-32
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/21352383/WordPressKit.zip",
15-
checksum: "8054b66ecf39b8c23acea6d8c5c6c9f65fda01fcce4da7acf7db4273cb8e9145"
14+
url: "https://github.com/user-attachments/files/21373832/WordPressKit.zip",
15+
checksum: "7cd5420c1a547f34fe2f03baeb950303094ea92d8b3a257c6f11c344102e7bda"
1616
),
1717
]
1818
)

Sources/WordPressKit/Models/Stats/StatsPostDetails.swift

Lines changed: 133 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,87 @@
1+
import Foundation
2+
13
public struct StatsPostDetails: Equatable {
24
public let fetchedDate: Date
35
public let totalViewsCount: Int
4-
6+
57
public let recentWeeks: [StatsWeeklyBreakdown]
68
public let dailyAveragesPerMonth: [StatsPostViews]
79
public let monthlyBreakdown: [StatsPostViews]
810
public let lastTwoWeeks: [StatsPostViews]
11+
12+
public let highestMonth: Int?
13+
public let highestDayAverage: Int?
14+
public let highestWeekAverage: Int?
15+
16+
public let yearlyTotals: [Int: Int]
17+
public let overallAverages: [Int: Int]
18+
19+
public let fields: [String]?
20+
21+
public let post: Post?
22+
23+
public struct Post: Equatable {
24+
public let postID: Int
25+
public let title: String
26+
public let authorID: String?
27+
public let dateGMT: Date?
28+
public let content: String?
29+
public let excerpt: String?
30+
public let status: String?
31+
public let commentStatus: String?
32+
public let password: String?
33+
public let name: String?
34+
public let modifiedGMT: Date?
35+
public let contentFiltered: String?
36+
public let parent: Int?
37+
public let guid: String?
38+
public let type: String?
39+
public let mimeType: String?
40+
public let commentCount: String?
41+
public let permalink: String?
42+
43+
init?(jsonDictionary: [String: AnyObject]) {
44+
guard
45+
let postID = jsonDictionary["ID"] as? Int,
46+
let title = jsonDictionary["post_title"] as? String
47+
else {
48+
return nil
49+
}
50+
51+
let dateFormatter = DateFormatter()
52+
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
53+
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
54+
55+
var dateGMT: Date?
56+
var modifiedGMT: Date?
57+
58+
if let postDateGMTString = jsonDictionary["post_date_gmt"] as? String {
59+
dateGMT = dateFormatter.date(from: postDateGMTString)
60+
}
61+
if let postModifiedGMTString = jsonDictionary["post_modified_gmt"] as? String {
62+
modifiedGMT = dateFormatter.date(from: postModifiedGMTString)
63+
}
64+
65+
self.postID = postID
66+
self.title = title
67+
self.authorID = jsonDictionary["post_author"] as? String
68+
self.dateGMT = dateGMT
69+
self.content = jsonDictionary["post_content"] as? String
70+
self.excerpt = jsonDictionary["post_excerpt"] as? String
71+
self.status = jsonDictionary["post_status"] as? String
72+
self.commentStatus = jsonDictionary["comment_status"] as? String
73+
self.password = jsonDictionary["post_password"] as? String
74+
self.name = jsonDictionary["post_name"] as? String
75+
self.modifiedGMT = modifiedGMT
76+
self.contentFiltered = jsonDictionary["post_content_filtered"] as? String
77+
self.parent = jsonDictionary["post_parent"] as? Int
78+
self.guid = jsonDictionary["guid"] as? String
79+
self.type = jsonDictionary["post_type"] as? String
80+
self.mimeType = jsonDictionary["post_mime_type"] as? String
81+
self.commentCount = jsonDictionary["comment_count"] as? String
82+
self.permalink = jsonDictionary["permalink"] as? String
83+
}
84+
}
985
}
1086

1187
public struct StatsWeeklyBreakdown: Equatable {
@@ -15,6 +91,7 @@ public struct StatsWeeklyBreakdown: Equatable {
1591
public let totalViewsCount: Int
1692
public let averageViewsCount: Int
1793
public let changePercentage: Double
94+
public let isChangeInfinity: Bool
1895

1996
public let days: [StatsPostViews]
2097
}
@@ -35,8 +112,8 @@ extension StatsPostDetails {
35112
let monthlyAverages = jsonDictionary["averages"] as? [String: AnyObject],
36113
let recentWeeks = jsonDictionary["weeks"] as? [[String: AnyObject]],
37114
let data = jsonDictionary["data"] as? [[Any]]
38-
else {
39-
return nil
115+
else {
116+
return nil
40117
}
41118

42119
self.fetchedDate = date
@@ -50,6 +127,42 @@ extension StatsPostDetails {
50127
self.monthlyBreakdown = StatsPostViews.mapMonthlyBreakdown(jsonDictionary: monthlyBreakdown)
51128
self.dailyAveragesPerMonth = StatsPostViews.mapMonthlyBreakdown(jsonDictionary: monthlyAverages)
52129
self.lastTwoWeeks = StatsPostViews.mapDailyData(data: Array(data.suffix(14)))
130+
131+
// Parse new fields
132+
self.highestMonth = jsonDictionary["highest_month"] as? Int
133+
self.highestDayAverage = jsonDictionary["highest_day_average"] as? Int
134+
self.highestWeekAverage = jsonDictionary["highest_week_average"] as? Int
135+
136+
self.fields = jsonDictionary["fields"] as? [String]
137+
138+
// Parse yearly totals
139+
var yearlyTotals: [Int: Int] = [:]
140+
if let years = monthlyBreakdown as? [String: [String: AnyObject]] {
141+
for (yearKey, yearData) in years {
142+
if let yearInt = Int(yearKey), let total = yearData["total"] as? Int {
143+
yearlyTotals[yearInt] = total
144+
}
145+
}
146+
}
147+
self.yearlyTotals = yearlyTotals
148+
149+
// Parse overall averages
150+
var overallAverages: [Int: Int] = [:]
151+
if let averages = monthlyAverages as? [String: [String: AnyObject]] {
152+
for (yearKey, yearData) in averages {
153+
if let yearInt = Int(yearKey), let overall = yearData["overall"] as? Int {
154+
overallAverages[yearInt] = overall
155+
}
156+
}
157+
}
158+
self.overallAverages = overallAverages
159+
160+
// Parse post object using the new Post model
161+
if let postDict = jsonDictionary["post"] as? [String: AnyObject] {
162+
self.post = Post(jsonDictionary: postDict)
163+
} else {
164+
self.post = nil
165+
}
53166
}
54167

55168
static var dateFormatter: DateFormatter {
@@ -93,19 +206,30 @@ extension StatsPostViews {
93206
let totalViews = $0["total"] as? Int,
94207
let averageViews = $0["average"] as? Int,
95208
let days = $0["days"] as? [[String: AnyObject]]
96-
else {
97-
return nil
209+
else {
210+
return nil
98211
}
99212

100-
let change = ($0["change"] as? Double) ?? 0.0
213+
var change: Double = 0.0
214+
var isChangeInfinity = false
215+
216+
if let changeValue = $0["change"] {
217+
if let changeDict = changeValue as? [String: AnyObject],
218+
let isInfinity = changeDict["isInfinity"] as? Bool {
219+
isChangeInfinity = isInfinity
220+
change = isInfinity ? Double.infinity : 0.0
221+
} else if let changeDouble = changeValue as? Double {
222+
change = changeDouble
223+
}
224+
}
101225

102226
let mappedDays: [StatsPostViews] = days.compactMap {
103227
guard
104228
let dayString = $0["day"] as? String,
105229
let date = StatsPostDetails.dateFormatter.date(from: dayString),
106230
let viewsCount = $0["count"] as? Int
107-
else {
108-
return nil
231+
else {
232+
return nil
109233
}
110234

111235
return StatsPostViews(period: .day,
@@ -122,9 +246,9 @@ extension StatsPostViews {
122246
totalViewsCount: totalViews,
123247
averageViewsCount: averageViews,
124248
changePercentage: change,
249+
isChangeInfinity: isChangeInfinity,
125250
days: mappedDays)
126251
}
127-
128252
}
129253
}
130254

Tests/WordPressKitTests/Mock Data/stats-post-details.json

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5424,5 +5424,31 @@
54245424
"highest_month": 8800,
54255425
"highest_day_average": 283,
54265426
"highest_week_average": 334,
5427-
"post": null
5427+
"post": {
5428+
"ID": 12345,
5429+
"post_author": "1234567",
5430+
"post_date": "2019-01-15 12:30:00",
5431+
"post_date_gmt": "2019-01-15 12:30:00",
5432+
"post_content": "<!-- wp:paragraph -->\n<p>This is a sample blog post content.</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>Sample Heading</h2>\n<!-- /wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>\n<!-- /wp:paragraph -->",
5433+
"post_title": "Sample Blog Post Title",
5434+
"post_excerpt": "This is a sample excerpt.",
5435+
"post_status": "publish",
5436+
"comment_status": "open",
5437+
"ping_status": "open",
5438+
"post_password": "",
5439+
"post_name": "sample-blog-post-title",
5440+
"to_ping": "",
5441+
"pinged": "",
5442+
"post_modified": "2019-01-15 14:45:00",
5443+
"post_modified_gmt": "2019-01-15 14:45:00",
5444+
"post_content_filtered": "",
5445+
"post_parent": 0,
5446+
"guid": "https://example.wordpress.com/?p=12345",
5447+
"menu_order": 0,
5448+
"post_type": "post",
5449+
"post_mime_type": "",
5450+
"comment_count": "3",
5451+
"filter": "raw",
5452+
"permalink": "http://example.wordpress.com/2019/01/15/sample-blog-post-title/"
5453+
}
54285454
}

Tests/WordPressKitTests/Tests/StatsRemoteV2Tests.swift

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -475,76 +475,117 @@ class StatsRemoteV2Tests: RemoteTestCase, RESTTestable {
475475

476476
func testFetchPostDetail() {
477477
let expect = expectation(description: "It should return post detail")
478-
478+
479479
stubRemoteResponse(sitePostDetailsEndpoint, filename: getPostsDetailsFilename, contentType: .ApplicationJSON)
480-
480+
481481
let feb21 = DateComponents(year: 2019, month: 2, day: 21)
482482
let date = Calendar.autoupdatingCurrent.date(from: feb21)!
483-
483+
484484
remote.getDetails(forPostID: 9001) { (postDetails, error) in
485485
XCTAssertNil(error)
486486
XCTAssertNotNil(postDetails)
487-
487+
488488
XCTAssertEqual(postDetails?.fetchedDate, date)
489489
XCTAssertEqual(postDetails?.totalViewsCount, 163343)
490-
490+
491491
let dailyAverages = 10 + 12 + 12 + 12 + 2
492492
XCTAssertEqual(postDetails?.dailyAveragesPerMonth.count, postDetails?.monthlyBreakdown.count)
493493
XCTAssertEqual(postDetails?.dailyAveragesPerMonth.count, dailyAverages)
494-
494+
495495
let feb19Averages = postDetails?.dailyAveragesPerMonth.first { $0.date == DateComponents(year: 2019, month: 2) }
496496
XCTAssertNotNil(feb19Averages)
497497
XCTAssertEqual(feb19Averages?.period, .month)
498498
XCTAssertEqual(feb19Averages?.viewsCount, 112)
499-
499+
500500
let feb19Views = postDetails?.monthlyBreakdown.first { $0.date == DateComponents(year: 2019, month: 2) }
501501
XCTAssertNotNil(feb19Views)
502502
XCTAssertEqual(feb19Views?.period, .month)
503503
XCTAssertEqual(feb19Views?.viewsCount, 2578)
504-
504+
505505
XCTAssertEqual(postDetails?.lastTwoWeeks.count, 14)
506-
506+
507507
XCTAssertEqual(postDetails?.lastTwoWeeks.first?.viewsCount, 112)
508508
XCTAssertEqual(postDetails?.lastTwoWeeks.first?.period, .day)
509509
XCTAssertEqual(postDetails?.lastTwoWeeks.first?.date, DateComponents(year: 2019, month: 2, day: 08))
510-
510+
511511
XCTAssertEqual(postDetails?.lastTwoWeeks.last?.viewsCount, 324)
512512
XCTAssertEqual(postDetails?.lastTwoWeeks.last?.period, .day)
513513
XCTAssertEqual(postDetails?.lastTwoWeeks.last?.date, DateComponents(year: 2019, month: 2, day: 21))
514-
514+
515515
XCTAssertEqual(postDetails?.recentWeeks.count, 6)
516-
516+
517517
let leastRecentWeek = postDetails?.recentWeeks.first
518518
let mostRecentWeek = postDetails?.recentWeeks.last
519-
519+
520520
XCTAssertNotNil(leastRecentWeek)
521521
XCTAssertNotNil(mostRecentWeek)
522-
522+
523523
XCTAssertEqual(leastRecentWeek?.totalViewsCount, 688)
524524
XCTAssertEqual(leastRecentWeek?.averageViewsCount, 98)
525525
XCTAssertEqual(leastRecentWeek!.changePercentage, 0.0, accuracy: 0.0000000001)
526+
XCTAssertFalse(leastRecentWeek!.isChangeInfinity)
526527
XCTAssertEqual(leastRecentWeek?.startDay, DateComponents(year: 2019, month: 01, day: 14))
527528
XCTAssertEqual(leastRecentWeek?.endDay, DateComponents(year: 2019, month: 01, day: 20))
528529
XCTAssertEqual(leastRecentWeek?.days.count, 7)
529530
XCTAssertEqual(leastRecentWeek?.days.first?.date, leastRecentWeek?.startDay)
530531
XCTAssertEqual(leastRecentWeek?.days.last?.date, leastRecentWeek?.endDay)
531532
XCTAssertEqual(leastRecentWeek?.days.first?.viewsCount, 174)
532533
XCTAssertEqual(leastRecentWeek?.days.last?.viewsCount, 60)
533-
534+
534535
XCTAssertEqual(mostRecentWeek?.totalViewsCount, 867)
535536
XCTAssertEqual(mostRecentWeek?.averageViewsCount, 181)
536537
XCTAssertEqual(mostRecentWeek!.changePercentage, 38.7732, accuracy: 0.001)
538+
XCTAssertFalse(mostRecentWeek!.isChangeInfinity)
537539
XCTAssertEqual(mostRecentWeek?.startDay, DateComponents(year: 2019, month: 02, day: 18))
538540
XCTAssertEqual(mostRecentWeek?.endDay, DateComponents(year: 2019, month: 02, day: 21))
539541
XCTAssertEqual(mostRecentWeek?.days.count, 4)
540542
XCTAssertEqual(mostRecentWeek?.days.first?.date, mostRecentWeek?.startDay)
541543
XCTAssertEqual(mostRecentWeek?.days.last?.date, mostRecentWeek?.endDay)
542544
XCTAssertEqual(mostRecentWeek?.days.first?.viewsCount, 157)
543545
XCTAssertEqual(mostRecentWeek?.days.last?.viewsCount, 324)
544-
546+
547+
// Test newly added fields
548+
XCTAssertEqual(postDetails?.highestMonth, 8800)
549+
XCTAssertEqual(postDetails?.highestDayAverage, 283)
550+
XCTAssertEqual(postDetails?.highestWeekAverage, 334)
551+
552+
// Test yearly totals
553+
XCTAssertEqual(postDetails?.yearlyTotals[2015], 37861)
554+
XCTAssertEqual(postDetails?.yearlyTotals[2016], 36447)
555+
XCTAssertEqual(postDetails?.yearlyTotals[2017], 37529)
556+
XCTAssertEqual(postDetails?.yearlyTotals[2018], 45429)
557+
XCTAssertEqual(postDetails?.yearlyTotals[2019], 6077)
558+
559+
// Test overall averages
560+
XCTAssertEqual(postDetails?.overallAverages[2015], 130)
561+
XCTAssertEqual(postDetails?.overallAverages[2016], 99)
562+
XCTAssertEqual(postDetails?.overallAverages[2017], 102)
563+
XCTAssertEqual(postDetails?.overallAverages[2018], 124)
564+
XCTAssertEqual(postDetails?.overallAverages[2019], 112)
565+
566+
// Test fields array
567+
XCTAssertEqual(postDetails?.fields, ["period", "views"])
568+
569+
// Test post object
570+
XCTAssertNotNil(postDetails?.post)
571+
XCTAssertEqual(postDetails?.post?.postID, 12345)
572+
XCTAssertEqual(postDetails?.post?.title, "Sample Blog Post Title")
573+
XCTAssertEqual(postDetails?.post?.authorID, "1234567")
574+
XCTAssertEqual(postDetails?.post?.status, "publish")
575+
XCTAssertEqual(postDetails?.post?.type, "post")
576+
XCTAssertEqual(postDetails?.post?.excerpt, "This is a sample excerpt.")
577+
XCTAssertEqual(postDetails?.post?.name, "sample-blog-post-title")
578+
XCTAssertEqual(postDetails?.post?.commentStatus, "open")
579+
XCTAssertEqual(postDetails?.post?.password, "")
580+
XCTAssertEqual(postDetails?.post?.parent, 0)
581+
XCTAssertEqual(postDetails?.post?.guid, "https://example.wordpress.com/?p=12345")
582+
XCTAssertEqual(postDetails?.post?.mimeType, "")
583+
XCTAssertEqual(postDetails?.post?.commentCount, "3")
584+
XCTAssertEqual(postDetails?.post?.permalink, "http://example.wordpress.com/2019/01/15/sample-blog-post-title/")
585+
545586
expect.fulfill()
546587
}
547-
588+
548589
waitForExpectations(timeout: timeout, handler: nil)
549590
}
550591

@@ -576,9 +617,11 @@ class StatsRemoteV2Tests: RemoteTestCase, RESTTestable {
576617
XCTAssertEqual(summary?.summaryData[9].likesCount, 126)
577618
XCTAssertEqual(summary?.summaryData[9].commentsCount, 0)
578619

579-
XCTAssertEqual(summary?.summaryData[9].periodStartDate, Calendar.autoupdatingCurrent.date(byAdding: .day,
580-
value: 7 * 9, // 7 days * nine objects
581-
to: dec17Date))
620+
XCTAssertEqual(summary?.summaryData[9].periodStartDate, Calendar.autoupdatingCurrent.date(
621+
byAdding: .day,
622+
value: 7 * 9, // 7 days * nine objects
623+
to: dec17Date
624+
))
582625

583626
expect.fulfill()
584627
}

0 commit comments

Comments
 (0)