From 9e4d53540d8628fa36f092c5fb06c020bf261eaa Mon Sep 17 00:00:00 2001 From: Jan Klausa Date: Wed, 20 Feb 2019 23:02:11 +0100 Subject: [PATCH 1/3] Add support for fetching top posts --- WordPressKit.xcodeproj/project.pbxproj | 8 ++ .../Time-based data/AuthorsStatsType.swift | 25 +++++ .../Time-based data/PostsStatsType.swift | 55 ++++++++++ .../Mock Data/stats-posts-data.json | 102 ++++++++++++++++++ WordPressKitTests/StatsRemoteV2Tests.swift | 45 ++++++++ 5 files changed, 235 insertions(+) create mode 100644 WordPressKit/Time-based data/PostsStatsType.swift create mode 100644 WordPressKitTests/Mock Data/stats-posts-data.json diff --git a/WordPressKit.xcodeproj/project.pbxproj b/WordPressKit.xcodeproj/project.pbxproj index 48faef8a..654adde5 100644 --- a/WordPressKit.xcodeproj/project.pbxproj +++ b/WordPressKit.xcodeproj/project.pbxproj @@ -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 */; }; 40A71C6E220E1D8E002E3D25 /* StatsServiceRemoteV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40A71C6D220E1D8E002E3D25 /* StatsServiceRemoteV2.swift */; }; 40AB1ADA200FED25009B533D /* PluginDirectoryFeedPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40AB1AD9200FED25009B533D /* PluginDirectoryFeedPage.swift */; }; 40E4698B2017C2840030DB5F /* plugin-directory-popular.json in Resources */ = {isa = PBXBuildFile; fileRef = 40E4698A2017C2840030DB5F /* plugin-directory-popular.json */; }; @@ -516,6 +518,8 @@ 404057DB221C9FD70060250C /* stats-referrer-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "stats-referrer-data.json"; sourceTree = ""; }; 4041405D220F9EF500CF7C5B /* StatsDotComFollowersInsight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsDotComFollowersInsight.swift; sourceTree = ""; }; 4041405F220F9F1F00CF7C5B /* StatsAllTimesInsight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsAllTimesInsight.swift; sourceTree = ""; }; + 4081976E221DDE9B00A298E4 /* PostsStatsType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostsStatsType.swift; sourceTree = ""; }; + 40819770221DFDB600A298E4 /* stats-posts-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "stats-posts-data.json"; sourceTree = ""; }; 40A71C6D220E1D8E002E3D25 /* StatsServiceRemoteV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsServiceRemoteV2.swift; sourceTree = ""; }; 40AB1AD9200FED25009B533D /* PluginDirectoryFeedPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginDirectoryFeedPage.swift; sourceTree = ""; }; 40E4698A2017C2840030DB5F /* plugin-directory-popular.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "plugin-directory-popular.json"; sourceTree = ""; }; @@ -1016,6 +1020,7 @@ children = ( 404057C4221B30400060250C /* SearchTermStatsType.swift */, 404057C8221B789B0060250C /* AuthorsStatsType.swift */, + 4081976E221DDE9B00A298E4 /* PostsStatsType.swift */, 404057CD221C38130060250C /* VideosStatsType.swift */, 404057D1221C56AB0060250C /* CountryStatsType.swift */, 404057D5221C92660060250C /* ClicksStatsType.swift */, @@ -1537,6 +1542,7 @@ 93BD27421EE73384002BB00B /* Mock Data */ = { isa = PBXGroup; children = ( + 40819770221DFDB600A298E4 /* stats-posts-data.json */, 404057DB221C9FD70060250C /* stats-referrer-data.json */, 404057D7221C98690060250C /* stats-clicks-data.json */, 404057D3221C5FC30060250C /* stats-countries-data.json */, @@ -2037,6 +2043,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 */, @@ -2293,6 +2300,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 */, diff --git a/WordPressKit/Time-based data/AuthorsStatsType.swift b/WordPressKit/Time-based data/AuthorsStatsType.swift index 628ff95c..e0ac1d06 100644 --- a/WordPressKit/Time-based data/AuthorsStatsType.swift +++ b/WordPressKit/Time-based data/AuthorsStatsType.swift @@ -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 { @@ -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 + } } } diff --git a/WordPressKit/Time-based data/PostsStatsType.swift b/WordPressKit/Time-based data/PostsStatsType.swift new file mode 100644 index 00000000..7f81ee64 --- /dev/null +++ b/WordPressKit/Time-based data/PostsStatsType.swift @@ -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) + } + +} diff --git a/WordPressKitTests/Mock Data/stats-posts-data.json b/WordPressKitTests/Mock Data/stats-posts-data.json new file mode 100644 index 00000000..c72ff859 --- /dev/null +++ b/WordPressKitTests/Mock Data/stats-posts-data.json @@ -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" +} diff --git a/WordPressKitTests/StatsRemoteV2Tests.swift b/WordPressKitTests/StatsRemoteV2Tests.swift index 9a9f6ce6..230f883d 100644 --- a/WordPressKitTests/StatsRemoteV2Tests.swift +++ b/WordPressKitTests/StatsRemoteV2Tests.swift @@ -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" // MARK: - Properties @@ -25,6 +26,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 remote: StatsServiceRemoteV2! @@ -288,4 +290,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) + + } } From 263f75a1f4399cd0af4d074f222e0dad3aff837e Mon Sep 17 00:00:00 2001 From: Jan Klausa Date: Thu, 21 Feb 2019 21:14:30 +0100 Subject: [PATCH 2/3] omit pointless `?` --- WordPressKit/Time-based data/AuthorsStatsType.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WordPressKit/Time-based data/AuthorsStatsType.swift b/WordPressKit/Time-based data/AuthorsStatsType.swift index e0ac1d06..525b58dd 100644 --- a/WordPressKit/Time-based data/AuthorsStatsType.swift +++ b/WordPressKit/Time-based data/AuthorsStatsType.swift @@ -99,11 +99,11 @@ extension StatsTopPost { static func kind(from kindString: String?) -> Kind { switch kindString { - case "post"?: + case "post": return .post - case "homepage"?: + case "homepage": return .homepage - case "page"?: + case "page": return .page default: return .unknown From 9e3935db5fa15f395e5c2fe10d7717f9f37f71eb Mon Sep 17 00:00:00 2001 From: Jan Klausa Date: Thu, 21 Feb 2019 22:45:26 +0100 Subject: [PATCH 3/3] unbreak build after bad merge --- WordPressKit/Time-based data/PublishedPostsStatsType.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPressKit/Time-based data/PublishedPostsStatsType.swift b/WordPressKit/Time-based data/PublishedPostsStatsType.swift index 7283a1b8..a693f9c5 100644 --- a/WordPressKit/Time-based data/PublishedPostsStatsType.swift +++ b/WordPressKit/Time-based data/PublishedPostsStatsType.swift @@ -35,5 +35,6 @@ private extension StatsTopPost { self.title = title self.postURL = URL(string: urlString) self.viewsCount = 0 + self.kind = .unknown } }