Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions WordPressKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
404057DC221C9FD80060250C /* stats-referrer-data.json in Resources */ = {isa = PBXBuildFile; fileRef = 404057DB221C9FD70060250C /* stats-referrer-data.json */; };
4041405E220F9EF500CF7C5B /* StatsDotComFollowersInsight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4041405D220F9EF500CF7C5B /* StatsDotComFollowersInsight.swift */; };
40414060220F9F1F00CF7C5B /* StatsAllTimesInsight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4041405F220F9F1F00CF7C5B /* StatsAllTimesInsight.swift */; };
4081976F221DDE9B00A298E4 /* PostsStatsType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4081976E221DDE9B00A298E4 /* PostsStatsType.swift */; };
40819771221DFDB700A298E4 /* stats-posts-data.json in Resources */ = {isa = PBXBuildFile; fileRef = 40819770221DFDB600A298E4 /* stats-posts-data.json */; };
40819773221E10C900A298E4 /* PublishedPostsStatsType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40819772221E10C900A298E4 /* PublishedPostsStatsType.swift */; };
40819775221E497D00A298E4 /* stats-published-posts.json in Resources */ = {isa = PBXBuildFile; fileRef = 40819774221E497C00A298E4 /* stats-published-posts.json */; };
40A71C6E220E1D8E002E3D25 /* StatsServiceRemoteV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40A71C6D220E1D8E002E3D25 /* StatsServiceRemoteV2.swift */; };
Expand Down Expand Up @@ -519,8 +521,11 @@
404057DB221C9FD70060250C /* stats-referrer-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "stats-referrer-data.json"; sourceTree = "<group>"; };
4041405D220F9EF500CF7C5B /* StatsDotComFollowersInsight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsDotComFollowersInsight.swift; sourceTree = "<group>"; };
4041405F220F9F1F00CF7C5B /* StatsAllTimesInsight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsAllTimesInsight.swift; sourceTree = "<group>"; };
4081976E221DDE9B00A298E4 /* PostsStatsType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostsStatsType.swift; sourceTree = "<group>"; };
40819770221DFDB600A298E4 /* stats-posts-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "stats-posts-data.json"; sourceTree = "<group>"; };
40819772221E10C900A298E4 /* PublishedPostsStatsType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishedPostsStatsType.swift; sourceTree = "<group>"; };
40819774221E497C00A298E4 /* stats-published-posts.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "stats-published-posts.json"; sourceTree = "<group>"; };

40A71C6D220E1D8E002E3D25 /* StatsServiceRemoteV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsServiceRemoteV2.swift; sourceTree = "<group>"; };
40AB1AD9200FED25009B533D /* PluginDirectoryFeedPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginDirectoryFeedPage.swift; sourceTree = "<group>"; };
40E4698A2017C2840030DB5F /* plugin-directory-popular.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "plugin-directory-popular.json"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1022,6 +1027,7 @@
children = (
404057C4221B30400060250C /* SearchTermStatsType.swift */,
404057C8221B789B0060250C /* AuthorsStatsType.swift */,
4081976E221DDE9B00A298E4 /* PostsStatsType.swift */,
404057CD221C38130060250C /* VideosStatsType.swift */,
404057D1221C56AB0060250C /* CountryStatsType.swift */,
404057D5221C92660060250C /* ClicksStatsType.swift */,
Expand Down Expand Up @@ -1545,6 +1551,7 @@
93BD27421EE73384002BB00B /* Mock Data */ = {
isa = PBXGroup;
children = (
40819770221DFDB600A298E4 /* stats-posts-data.json */,
40819774221E497C00A298E4 /* stats-published-posts.json */,
404057DB221C9FD70060250C /* stats-referrer-data.json */,
404057D7221C98690060250C /* stats-clicks-data.json */,
Expand Down Expand Up @@ -2046,6 +2053,7 @@
7403A2FD1EF06FEB00DED7DC /* me-settings-change-primary-site-success.json in Resources */,
93AC8ED41ED32FD000900F5A /* stats-v1.1-summary.json in Resources */,
74C473B71EF3229B009918F2 /* site-delete-unexpected-json-failure.json in Resources */,
40819771221DFDB700A298E4 /* stats-posts-data.json in Resources */,
93AC8ECD1ED32FD000900F5A /* stats-v1.1-insights.json in Resources */,
740B23EE1F17FB7E00067A2A /* xmlrpc-malformed-request-xml-error.xml in Resources */,
826016F91F9FAF6300533B6C /* activity-log-success-3.json in Resources */,
Expand Down Expand Up @@ -2302,6 +2310,7 @@
74E2295C1F1E77290085F7F2 /* KeyringConnectionExternalUser.swift in Sources */,
E1BD95151FD5A2B800CD5CE3 /* PluginDirectoryServiceRemote.swift in Sources */,
7430C9D71F1933210051B8E6 /* RemoteReaderCrossPostMeta.swift in Sources */,
4081976F221DDE9B00A298E4 /* PostsStatsType.swift in Sources */,
9311A68B1F22625A00704AC9 /* TaxonomyServiceRemoteXMLRPC.m in Sources */,
40414060220F9F1F00CF7C5B /* StatsAllTimesInsight.swift in Sources */,
93BD277E1EE73944002BB00B /* NSDate+WordPressJSON.m in Sources */,
Expand Down
25 changes: 25 additions & 0 deletions WordPressKit/Time-based data/AuthorsStatsType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,21 @@ public struct StatsTopAuthor {


public struct StatsTopPost {

public enum Kind {
case unknown
case post
case page
case homepage
}


public let title: String
public let postID: Int
public let postURL: URL?
public let viewsCount: Int
public let kind: Kind

}

extension AuthorsStatsType: TimeStatsProtocol {
Expand Down Expand Up @@ -83,6 +94,20 @@ extension StatsTopPost {
self.postID = postID
self.viewsCount = viewsCount
self.postURL = URL(string: postURL)
self.kind = type(of: self).kind(from: jsonDictionary["type"] as? String)
}

static func kind(from kindString: String?) -> Kind {
switch kindString {
case "post":
return .post
case "homepage":
return .homepage
case "page":
return .page
default:
return .unknown
}
}
}

55 changes: 55 additions & 0 deletions WordPressKit/Time-based data/PostsStatsType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
public struct PostsStatsType {
public let period: StatsPeriodUnit
public let periodEndDate: Date

public let totalViewsCount: Int
public let otherViewsCount: Int
public let topPosts: [StatsTopPost]
}

extension PostsStatsType: TimeStatsProtocol {
public static var pathComponent: String {
return "stats/top-posts"
}

public init?(date: Date, period: StatsPeriodUnit, jsonDictionary: [String : AnyObject]) {
guard
let unwrappedDays = type(of: self).unwrapDaysDictionary(jsonDictionary: jsonDictionary),
let posts = unwrappedDays["postviews"] as? [[String: AnyObject]],
let totalViews = unwrappedDays["total_views"] as? Int,
let otherViews = unwrappedDays["other_views"] as? Int
else {
return nil
}

self.periodEndDate = date
self.period = period
self.totalViewsCount = totalViews
self.otherViewsCount = otherViews
self.topPosts = posts.compactMap { StatsTopPost(topPostsJSONDictionary: $0) }
}
}

private extension StatsTopPost {

// the objects returned from this endpoint are _almost_ the same as the ones from `top-posts`,
// but with keys just subtly different enough that we need a custom init here.
init?(topPostsJSONDictionary jsonDictionary: [String: AnyObject]) {
guard
let url = jsonDictionary["href"] as? String,
let postID = jsonDictionary["id"] as? Int,
let title = jsonDictionary["title"] as? String,
let viewsCount = jsonDictionary["views"] as? Int,
let typeString = jsonDictionary["type"] as? String
else {
return nil
}

self.title = title
self.postID = postID
self.postURL = URL(string: url)
self.viewsCount = viewsCount
self.kind = type(of: self).kind(from: typeString)
}

}
1 change: 1 addition & 0 deletions WordPressKit/Time-based data/PublishedPostsStatsType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ private extension StatsTopPost {
self.title = title
self.postURL = URL(string: urlString)
self.viewsCount = 0
self.kind = .unknown
}
}
102 changes: 102 additions & 0 deletions WordPressKitTests/Mock Data/stats-posts-data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{
"date": "2019-01-31",
"days": {
"2019-01-01": {
"postviews": [
{
"id": 0,
"href": "http://officetoday.wordpress.com/",
"date": null,
"title": "Home page / Archives",
"type": "homepage",
"views": 2816,
"video_play": false
},
{
"id": 2396,
"href": "http://officetoday.wordpress.com/2019/01/04/valizas-uruguay/",
"date": "2019-01-04 11:40:27",
"title": "Valizas, Uruguay",
"type": "post",
"views": 146,
"video_play": false
},
{
"id": 2413,
"href": "http://officetoday.wordpress.com/2019/01/24/dundee-scotland-2/",
"date": "2019-01-24 14:34:07",
"title": "Dundee, Scotland",
"type": "page",
"views": 141,
"video_play": false
},
{
"id": 2376,
"href": "http://officetoday.wordpress.com/2018/12/24/porto-portugal/",
"date": "2018-12-24 10:43:12",
"title": "Porto, Portugal",
"type": "post",
"views": 36,
"video_play": false
},
{
"id": 2403,
"href": "http://officetoday.wordpress.com/2019/01/09/fairport-ny/",
"date": "2019-01-09 21:52:59",
"title": "Fairport, NY",
"type": "post",
"views": 29,
"video_play": false
},
{
"id": 2367,
"href": "http://officetoday.wordpress.com/2018/12/18/canberra-australia/",
"date": "2018-12-18 03:35:52",
"title": "Canberra, Australia",
"type": "post",
"views": 24,
"video_play": false
},
{
"id": 2394,
"href": "http://officetoday.wordpress.com/2018/12/28/thalys-train-amsterdam-to-brussels/",
"date": "2018-12-28 10:25:55",
"title": "Thalys Train - Amsterdam to Brussels",
"type": "post",
"views": 21,
"video_play": false
},
{
"id": 2412,
"href": "http://officetoday.wordpress.com/2019/01/20/prague-czech-republic-16/",
"date": "2019-01-20 11:17:23",
"title": "Prague, Czech Republic",
"type": "post",
"views": 19,
"video_play": false
},
{
"id": 2384,
"href": "http://officetoday.wordpress.com/2018/12/27/odori-park-sapporo-japan/",
"date": "2018-12-27 23:05:24",
"title": "Odori Park, Sapporo, Japan",
"type": "post",
"views": 17,
"video_play": false
},
{
"id": 2317,
"href": "http://officetoday.wordpress.com/2018/11/15/atacama-desert-chile/",
"date": "2018-11-15 13:58:16",
"title": "Atacama Desert, Chile",
"type": "post",
"views": 13,
"video_play": false
}
],
"total_views": 3499,
"other_views": 237
}
},
"period": "month"
}
48 changes: 46 additions & 2 deletions WordPressKitTests/StatsRemoteV2Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class StatsRemoteV2Tests: RemoteTestCase, RESTTestable {
let getCountriesMockFilename = "stats-countries-data.json"
let getClicksMockFilename = "stats-clicks-data.json"
let getReferrersMockFilename = "stats-referrer-data.json"
let getPostsMockFilename = "stats-posts-data.json"
let getPublishedPostsFilename = "stats-published-posts.json"

// MARK: - Properties
Expand All @@ -26,6 +27,7 @@ class StatsRemoteV2Tests: RemoteTestCase, RESTTestable {
var siteCountriesDataEndpoint: String { return "sites/\(siteID)/stats/country-views/" }
var siteClicksDataEndpoint: String { return "sites/\(siteID)/stats/clicks/" }
var siteReferrerDataEndpoint: String { return "sites/\(siteID)/stats/referrers/" }
var sitePostsDataEndpoint: String { return "sites/\(siteID)/stats/top-posts/" }
var sitePublishedPostsEndpoint: String { return "sites/\(siteID)/posts/" }

var remote: StatsServiceRemoteV2!
Expand Down Expand Up @@ -291,6 +293,47 @@ class StatsRemoteV2Tests: RemoteTestCase, RESTTestable {
waitForExpectations(timeout: timeout, handler: nil)
}

func testTopPosts() {
let expect = expectation(description: "It should return posts data for a year")

stubRemoteResponse(sitePostsDataEndpoint, filename: getPostsMockFilename, contentType: .ApplicationJSON)

let jan31 = DateComponents(year: 2019, month: 1, day: 31)
let date = Calendar.autoupdatingCurrent.date(from: jan31)!

remote.getData(for: .month, endingOn: date) { (topPosts: PostsStatsType?, error: Error?) in
XCTAssertNil(error)
XCTAssertNotNil(topPosts)

XCTAssertEqual(topPosts?.totalViewsCount, 3499)
XCTAssertEqual(topPosts?.otherViewsCount, 237)

XCTAssertEqual(topPosts?.topPosts.count, 10)

XCTAssertEqual(topPosts?.topPosts.first?.viewsCount, 2816)
XCTAssertEqual(topPosts?.topPosts.first?.kind, .homepage)
XCTAssertEqual(topPosts?.topPosts.first?.title, "Home page / Archives")
XCTAssertEqual(topPosts?.topPosts.first?.postID, 0)
XCTAssertEqual(topPosts?.topPosts.first?.postURL, URL(string: "http://officetoday.wordpress.com/"))

XCTAssertEqual(topPosts?.topPosts[1].viewsCount, 146)
XCTAssertEqual(topPosts?.topPosts[1].kind, .post)
XCTAssertEqual(topPosts?.topPosts[1].title, "Valizas, Uruguay")
XCTAssertEqual(topPosts?.topPosts[1].postID, 2396)
XCTAssertEqual(topPosts?.topPosts[1].postURL, URL(string: "http://officetoday.wordpress.com/2019/01/04/valizas-uruguay/"))

XCTAssertEqual(topPosts?.topPosts[2].viewsCount, 141)
XCTAssertEqual(topPosts?.topPosts[2].kind, .page)
XCTAssertEqual(topPosts?.topPosts[2].title, "Dundee, Scotland")
XCTAssertEqual(topPosts?.topPosts[2].postID, 2413)
XCTAssertEqual(topPosts?.topPosts[2].postURL, URL(string: "http://officetoday.wordpress.com/2019/01/24/dundee-scotland-2/"))

expect.fulfill()
}

waitForExpectations(timeout: timeout, handler: nil)
}

func testsPublishedPosts() {
let expect = expectation(description: "It should return published posts for a specified window")

Expand All @@ -309,10 +352,11 @@ class StatsRemoteV2Tests: RemoteTestCase, RESTTestable {
XCTAssertEqual(publishedPosts?.publishedPosts[0].postURL, URL(string: "http://en.blog.wordpress.com/2019/01/14/newspack-by-wordpress-com/"))
XCTAssertEqual(publishedPosts?.publishedPosts[0].title, "Announcing Newspack by WordPress.com &#8212; A New Publishing Solution for News Organizations")


expect.fulfill()
}

waitForExpectations(timeout: timeout, handler: nil)
}

}
}