From 110e8fe6123bf5b784fdd3578bc350a991bfa26d Mon Sep 17 00:00:00 2001 From: Jan Klausa Date: Fri, 8 Feb 2019 23:04:34 +0100 Subject: [PATCH 1/7] Introduce a new way to handle fetching data for Stats. --- Podfile.lock | 4 +- WordPressKit.podspec | 2 +- WordPressKit.xcodeproj/project.pbxproj | 52 +++++ .../Insights/StatsAllTimesInsight.swift | 43 ++++ ...StatsAnnualAndMostPopularTimeInsight.swift | 93 +++++++++ .../Insights/StatsCommentsInsight.swift | 100 ++++++++++ .../StatsDotComFollowersInsight.swift | 75 +++++++ .../Insights/StatsEmailFollowersInsight.swift | 32 +++ .../Insights/StatsPublicizeInsight.swift | 81 ++++++++ .../StatsTagsAndCategoriesInsight.swift | 103 ++++++++++ WordPressKit/Insights/StatsTodayInsight.swift | 34 ++++ WordPressKit/StatsServiceRemoteV2.swift | 187 ++++++++++++++++++ 12 files changed, 803 insertions(+), 3 deletions(-) create mode 100644 WordPressKit/Insights/StatsAllTimesInsight.swift create mode 100644 WordPressKit/Insights/StatsAnnualAndMostPopularTimeInsight.swift create mode 100644 WordPressKit/Insights/StatsCommentsInsight.swift create mode 100644 WordPressKit/Insights/StatsDotComFollowersInsight.swift create mode 100644 WordPressKit/Insights/StatsEmailFollowersInsight.swift create mode 100644 WordPressKit/Insights/StatsPublicizeInsight.swift create mode 100644 WordPressKit/Insights/StatsTagsAndCategoriesInsight.swift create mode 100644 WordPressKit/Insights/StatsTodayInsight.swift create mode 100644 WordPressKit/StatsServiceRemoteV2.swift diff --git a/Podfile.lock b/Podfile.lock index d95d56e7..8640f3b3 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -27,7 +27,7 @@ PODS: - OHHTTPStubs/Swift (6.1.0): - OHHTTPStubs/Default - UIDeviceIdentifier (1.1.4) - - WordPressKit (2.0.0-beta.4): + - WordPressKit (2.0.0-beta.5): - Alamofire (~> 4.7.3) - CocoaLumberjack (= 3.4.2) - NSObject-SafeExpectations (= 0.0.3) @@ -70,7 +70,7 @@ SPEC CHECKSUMS: OCMock: 43565190abc78977ad44a61c0d20d7f0784d35ab OHHTTPStubs: 1e21c7d2c084b8153fc53d48400d8919d2d432d0 UIDeviceIdentifier: 8f8a24b257a4d978c8d40ad1e7355b944ffbfa8c - WordPressKit: c08da00b4a479cd52cda11d085e78b99a741e202 + WordPressKit: b459959437001fd70c0955de37493754f11a7b03 WordPressShared: a2fc2db66c210a05d317ae9678b5823dd6a4d708 wpxmlrpc: 6ba55c773cfa27083ae4a2173e69b19f46da98e2 diff --git a/WordPressKit.podspec b/WordPressKit.podspec index 1b9275c6..231132ad 100644 --- a/WordPressKit.podspec +++ b/WordPressKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "WordPressKit" - s.version = "2.0.0-beta.4" + s.version = "2.0.0-beta.5" s.summary = "WordPressKit offers a clean and simple WordPress.com and WordPress.org API." s.description = <<-DESC diff --git a/WordPressKit.xcodeproj/project.pbxproj b/WordPressKit.xcodeproj/project.pbxproj index 613f2167..92228cd9 100644 --- a/WordPressKit.xcodeproj/project.pbxproj +++ b/WordPressKit.xcodeproj/project.pbxproj @@ -18,9 +18,18 @@ 240315B0A1D6C2B981572B5B /* Pods_WordPressKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED05C8FF3E61D93CE5BA527E /* Pods_WordPressKitTests.framework */; }; 40247DFA2120D8E100AE1C3C /* AutomatedTransferService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40247DF92120D8E100AE1C3C /* AutomatedTransferService.swift */; }; 40247DFC2120E69600AE1C3C /* AutomatedTransferStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40247DFB2120E69600AE1C3C /* AutomatedTransferStatus.swift */; }; + 4041405E220F9EF500CF7C5B /* StatsDotComFollowersInsight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4041405D220F9EF500CF7C5B /* StatsDotComFollowersInsight.swift */; }; + 40414060220F9F1F00CF7C5B /* StatsAllTimesInsight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4041405F220F9F1F00CF7C5B /* StatsAllTimesInsight.swift */; }; + 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 */; }; 40E4698D2017D2E30030DB5F /* plugin-directory-new.json in Resources */ = {isa = PBXBuildFile; fileRef = 40E4698C2017D2E30030DB5F /* plugin-directory-new.json */; }; + 40E7FEA9220FA4060032834E /* StatsEmailFollowersInsight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40E7FEA8220FA4050032834E /* StatsEmailFollowersInsight.swift */; }; + 40E7FEAE220FAEA10032834E /* StatsPublicizeInsight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40E7FEAD220FAEA10032834E /* StatsPublicizeInsight.swift */; }; + 40E7FEB1220FB3B60032834E /* StatsAnnualAndMostPopularTimeInsight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40E7FEB0220FB3B60032834E /* StatsAnnualAndMostPopularTimeInsight.swift */; }; + 40E7FEB4221063480032834E /* StatsTodayInsight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40E7FEB3221063480032834E /* StatsTodayInsight.swift */; }; + 40E7FEB722106A8D0032834E /* StatsCommentsInsight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40E7FEB622106A8D0032834E /* StatsCommentsInsight.swift */; }; + 40E7FEBA2210894B0032834E /* StatsTagsAndCategoriesInsight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40E7FEB92210894B0032834E /* StatsTagsAndCategoriesInsight.swift */; }; 40F88F601F85723500AE3FAF /* auth-send-verification-email-already-verified-failure.json in Resources */ = {isa = PBXBuildFile; fileRef = 40F88F5F1F85723400AE3FAF /* auth-send-verification-email-already-verified-failure.json */; }; 40F88F621F85799A00AE3FAF /* auth-send-verification-email-success.json in Resources */ = {isa = PBXBuildFile; fileRef = 40F88F611F85799A00AE3FAF /* auth-send-verification-email-success.json */; }; 436D56332118D7AA00CEAA33 /* TransactionsServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436D56322118D7AA00CEAA33 /* TransactionsServiceRemote.swift */; }; @@ -478,9 +487,18 @@ 264F5C834541BBF2018F4964 /* Pods-WordPressKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-WordPressKitTests/Pods-WordPressKitTests.debug.xcconfig"; sourceTree = ""; }; 40247DF92120D8E100AE1C3C /* AutomatedTransferService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomatedTransferService.swift; sourceTree = ""; }; 40247DFB2120E69600AE1C3C /* AutomatedTransferStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomatedTransferStatus.swift; 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 = ""; }; + 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 = ""; }; 40E4698C2017D2E30030DB5F /* plugin-directory-new.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "plugin-directory-new.json"; sourceTree = ""; }; + 40E7FEA8220FA4050032834E /* StatsEmailFollowersInsight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsEmailFollowersInsight.swift; sourceTree = ""; }; + 40E7FEAD220FAEA10032834E /* StatsPublicizeInsight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsPublicizeInsight.swift; sourceTree = ""; }; + 40E7FEB0220FB3B60032834E /* StatsAnnualAndMostPopularTimeInsight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsAnnualAndMostPopularTimeInsight.swift; sourceTree = ""; }; + 40E7FEB3221063480032834E /* StatsTodayInsight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsTodayInsight.swift; sourceTree = ""; }; + 40E7FEB622106A8D0032834E /* StatsCommentsInsight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsCommentsInsight.swift; sourceTree = ""; }; + 40E7FEB92210894B0032834E /* StatsTagsAndCategoriesInsight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsTagsAndCategoriesInsight.swift; sourceTree = ""; }; 40F88F5F1F85723400AE3FAF /* auth-send-verification-email-already-verified-failure.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "auth-send-verification-email-already-verified-failure.json"; sourceTree = ""; }; 40F88F611F85799A00AE3FAF /* auth-send-verification-email-success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "auth-send-verification-email-success.json"; sourceTree = ""; }; 436D56322118D7AA00CEAA33 /* TransactionsServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionsServiceRemote.swift; sourceTree = ""; }; @@ -963,6 +981,29 @@ name = Frameworks; sourceTree = ""; }; + 40414061220F9F2800CF7C5B /* Insights */ = { + isa = PBXGroup; + children = ( + 4041405D220F9EF500CF7C5B /* StatsDotComFollowersInsight.swift */, + 40E7FEA8220FA4050032834E /* StatsEmailFollowersInsight.swift */, + 4041405F220F9F1F00CF7C5B /* StatsAllTimesInsight.swift */, + 40E7FEAD220FAEA10032834E /* StatsPublicizeInsight.swift */, + 40E7FEB0220FB3B60032834E /* StatsAnnualAndMostPopularTimeInsight.swift */, + 40E7FEB3221063480032834E /* StatsTodayInsight.swift */, + 40E7FEB622106A8D0032834E /* StatsCommentsInsight.swift */, + 40E7FEB92210894B0032834E /* StatsTagsAndCategoriesInsight.swift */, + ); + path = Insights; + sourceTree = ""; + }; + 40B01BF3220E534900036D10 /* V2 */ = { + isa = PBXGroup; + children = ( + 40414061220F9F2800CF7C5B /* Insights */, + ); + name = V2; + sourceTree = ""; + }; 436D56362118DC2800CEAA33 /* Transactions */ = { isa = PBXGroup; children = ( @@ -1108,6 +1149,7 @@ 930F52B91ECF8A44002F921B /* Stats */ = { isa = PBXGroup; children = ( + 40B01BF3220E534900036D10 /* V2 */, 9368C7A51EC630270092CE8E /* StatsItem.h */, 9368C7A61EC630270092CE8E /* StatsItem.m */, 9368C7A71EC630270092CE8E /* StatsItemAction.h */, @@ -1316,6 +1358,7 @@ 730E869E21E44EFD00753E1A /* WordPressComServiceRemote+SiteVerticals.swift */, 73A2F38921E7F81E00388609 /* WordPressComServiceRemote+SiteVerticalsPrompt.swift */, 9368C7A11EC62F800092CE8E /* WPStatsServiceRemote.h */, + 40A71C6D220E1D8E002E3D25 /* StatsServiceRemoteV2.swift */, 9368C7A21EC62F800092CE8E /* WPStatsServiceRemote.m */, E182BF691FD961810001D850 /* Endpoint.swift */, 17CD0CC220C58A0D000D9620 /* ReaderSiteSearchServiceRemote.swift */, @@ -2173,10 +2216,12 @@ E11C2AD21FA77FB90023BDE2 /* SitePlugin.swift in Sources */, 74A44DCC1F13C533006CD8F4 /* NotificationSyncServiceRemote.swift in Sources */, 74E229501F1E741B0085F7F2 /* RemotePublicizeConnection.swift in Sources */, + 40E7FEB722106A8D0032834E /* StatsCommentsInsight.swift in Sources */, 9311A6891F22625A00704AC9 /* TaxonomyServiceRemoteREST.m in Sources */, 9AB6D647218705E90008F274 /* RemoteDiff.swift in Sources */, 93BD277C1EE73944002BB00B /* HTTPAuthenticationAlertController.swift in Sources */, 7433BC011EFC4505002D9E92 /* PlanServiceRemote.swift in Sources */, + 4041405E220F9EF500CF7C5B /* StatsDotComFollowersInsight.swift in Sources */, 74650F721F0EA1A700188EDB /* GravatarServiceRemote.swift in Sources */, B5969E1D20A49AC4005E9DF1 /* NSString+MD5.m in Sources */, 8236EB4D2024B9F8007C7CF9 /* RemoteBlogJetpackModulesSettings.swift in Sources */, @@ -2188,6 +2233,7 @@ E1BD95151FD5A2B800CD5CE3 /* PluginDirectoryServiceRemote.swift in Sources */, 7430C9D71F1933210051B8E6 /* RemoteReaderCrossPostMeta.swift in Sources */, 9311A68B1F22625A00704AC9 /* TaxonomyServiceRemoteXMLRPC.m in Sources */, + 40414060220F9F1F00CF7C5B /* StatsAllTimesInsight.swift in Sources */, 93BD277E1EE73944002BB00B /* NSDate+WordPressJSON.m in Sources */, 7E3E7A4820E443370075D159 /* NSMutableAttributedString+extensions.swift in Sources */, E194CB731FBDEF6500B0A8B8 /* PluginState.swift in Sources */, @@ -2205,6 +2251,7 @@ 9F4E52002088E38200424676 /* ObjectValidation.swift in Sources */, 7430C9B81F1927C50051B8E6 /* RemoteReaderTopic.m in Sources */, 7403A3021EF0726E00DED7DC /* AccountSettings.swift in Sources */, + 40E7FEA9220FA4060032834E /* StatsEmailFollowersInsight.swift in Sources */, 9368C7C01EC630CE0092CE8E /* StatsStringUtilities.m in Sources */, 826016F11F9FA13A00533B6C /* ActivityServiceRemote.swift in Sources */, 74BA04FA1F06DC3900ED5CD8 /* RemoteComment.m in Sources */, @@ -2218,6 +2265,7 @@ 7328420421CD786C00126755 /* WordPressComServiceRemote+SiteCreation.swift in Sources */, 826016F31F9FA17B00533B6C /* Activity.swift in Sources */, 7397F01A220A072500C723F3 /* ActivityServiceRemote_ApiVersion1_0.swift in Sources */, + 40E7FEB1220FB3B60032834E /* StatsAnnualAndMostPopularTimeInsight.swift in Sources */, 7E3E7A4A20E443890075D159 /* Scanner+extensions.swift in Sources */, 742362E31F1025B400BD0A7F /* RemoteMenuLocation.m in Sources */, 82FFBF561F460DD400F4573F /* BlogJetpackSettingsServiceRemote.swift in Sources */, @@ -2245,6 +2293,7 @@ 93BD273D1EE73282002BB00B /* AccountServiceRemoteREST.m in Sources */, 82FFBF501F45EFD100F4573F /* RemoteBlogJetpackSettings.swift in Sources */, 74650F741F0EA1E200188EDB /* RemoteGravatarProfile.swift in Sources */, + 40E7FEB4221063480032834E /* StatsTodayInsight.swift in Sources */, 436D563C2118E18D00CEAA33 /* State.swift in Sources */, 439A44DA2107C93000795ED7 /* RemotePlan_ApiVersion1_3.swift in Sources */, 93BD27811EE73944002BB00B /* WordPressOrgXMLRPCApi.swift in Sources */, @@ -2274,10 +2323,13 @@ 74B5F0DA1EF8299B00B411E7 /* BlogServiceRemoteXMLRPC.m in Sources */, E1EF5D5D1F9F329900B6D53E /* SitePluginCapabilities.swift in Sources */, 7E3E7A4520E443060075D159 /* NSAttributedString+extensions.swift in Sources */, + 40E7FEAE220FAEA10032834E /* StatsPublicizeInsight.swift in Sources */, 93F50A411F227C9700B5BEBA /* RemoteProfile.swift in Sources */, + 40E7FEBA2210894B0032834E /* StatsTagsAndCategoriesInsight.swift in Sources */, 439A44D82107C85E00795ED7 /* PlanServiceRemote_ApiVersion1_3.swift in Sources */, 93C674EE1EE834B700BFAF05 /* RemoteBlogSettings.swift in Sources */, 436D5641211B7F4400CEAA33 /* DomainContactInformation.swift in Sources */, + 40A71C6E220E1D8E002E3D25 /* StatsServiceRemoteV2.swift in Sources */, 82FFBF521F45F04100F4573F /* RemoteBlogJetpackMonitorSettings.swift in Sources */, 9309994E1F1657C600F006A1 /* ThemeServiceRemote.m in Sources */, 74E229491F1E73060085F7F2 /* SharingServiceRemote.swift in Sources */, diff --git a/WordPressKit/Insights/StatsAllTimesInsight.swift b/WordPressKit/Insights/StatsAllTimesInsight.swift new file mode 100644 index 00000000..9bd1d2d7 --- /dev/null +++ b/WordPressKit/Insights/StatsAllTimesInsight.swift @@ -0,0 +1,43 @@ +public struct StatsAllTimesInsight { + public let postsCount: Int + public let viewsCount: Int + public let bestViewsDay: Date + public let visitorsCount: Int + public let bestViewsPerDayCount: Int +} + + +extension StatsAllTimesInsight: InsightProtocol { + + //MARK: - InsightProtocol Conformance + public static var queryProperties: [String : AnyObject] { + return [:] + } + + public init?(jsonDictionary: [String: AnyObject]) { + guard + let statsDict = jsonDictionary["stats"] as? [String: AnyObject], + let postsCount = statsDict["posts"] as? Int, + let viewsCount = statsDict["views"] as? Int, + let visitorsCount = statsDict["visitors"] as? Int, + let bestViewsPerDayCount = statsDict["views_best_day_total"] as? Int, + let bestViewsDayString = statsDict["views_best_day"] as? String, + let bestViewsDay = StatsAllTimesInsight.dateFormatter.date(from: bestViewsDayString) + else { + return nil + } + + self.postsCount = postsCount + self.bestViewsPerDayCount = bestViewsPerDayCount + self.visitorsCount = visitorsCount + self.viewsCount = viewsCount + self.bestViewsDay = bestViewsDay + } + + //MARK: - + private static var dateFormatter: DateFormatter { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd"; + return formatter + } +} diff --git a/WordPressKit/Insights/StatsAnnualAndMostPopularTimeInsight.swift b/WordPressKit/Insights/StatsAnnualAndMostPopularTimeInsight.swift new file mode 100644 index 00000000..02047992 --- /dev/null +++ b/WordPressKit/Insights/StatsAnnualAndMostPopularTimeInsight.swift @@ -0,0 +1,93 @@ +public struct StatsAnnualAndMostPopularTimeInsight { + + /// - A `DateComponents` object with one field populated: `weekday`. + public let mostPopularDayOfWeek: DateComponents + public let mostPopularDayOfWeekPercentage: Int + + /// - A `DateComponents` object with one field populated: `hour`. + public let mostPopularHour: DateComponents + public let mostPopularHourPercentage: Int + + public let annualInsightsYear: Int + + public let annualInsightsTotalPostsCount: Int + public let annualInsightsTotalWordsCount: Int + public let annualInsightsAverageWordsCount: Double + + public let annualInsightsTotalLikesCount: Int + public let annualInsightsAverageLikesCount: Double + + public let annualInsightsTotalCommentsCount: Int + public let annualInsightsAverageCommentsCount: Double + + public let annualInsightsTotalImagesCount: Int + public let annualInsightsAverageImagesCount: Double +} + +extension StatsAnnualAndMostPopularTimeInsight: InsightProtocol { + public static var queryProperties: [String : AnyObject] { + return [:] + } + + public static var pathComponent: String { + return "stats/insights" + } + + public init?(jsonDictionary: [String : AnyObject]) { + guard + let highestHour = jsonDictionary["highest_hour"] as? Int, + let highestHourPercentageValue = jsonDictionary["highest_hour_percent"] as? Double, + let highestDayOfWeek = jsonDictionary["highest_day_of_week"] as? Int, + let highestDayOfWeekPercentageValue = jsonDictionary["highest_day_percent"] as? Double, + let yearlyInsights = jsonDictionary["years"] as? [[String: AnyObject]], + let latestYearlyInsight = yearlyInsights.last, + let yearString = latestYearlyInsight["year"] as? String, + let currentYear = Int(yearString), + let postCount = latestYearlyInsight["total_posts"] as? Int, + let wordsCount = latestYearlyInsight["total_words"] as? Int, + let wordsAverage = latestYearlyInsight["avg_words"] as? Double, + let likesCount = latestYearlyInsight["total_likes"] as? Int, + let likesAverage = latestYearlyInsight["avg_likes"] as? Double, + let commentsCount = latestYearlyInsight["total_comments"] as? Int, + let commentsAverage = latestYearlyInsight["avg_comments"] as? Double, + let imagesCount = latestYearlyInsight["total_images"] as? Int, + let imagesAverage = latestYearlyInsight["avg_images"] as? Double + else { + return nil + } + + let mappedWeekday: ((Int) -> Int) = { + // iOS Calendar system is `1-based` and uses Sunday as the first day of the week. + // The data returned from WP.com is `0-based` and uses Monday as the first day of the week. + // This maps the WP.com data to iOS format. + if $0 == 6 { + return 0 + } + + return $0 + 2 + } + + let weekDayComponent = DateComponents(weekday: mappedWeekday(highestDayOfWeek)) + let hourComponents = DateComponents(hour: highestHour) + + self.mostPopularDayOfWeek = weekDayComponent + self.mostPopularDayOfWeekPercentage = Int(highestDayOfWeekPercentageValue) + self.mostPopularHour = hourComponents + self.mostPopularHourPercentage = Int(highestHourPercentageValue) + + self.annualInsightsYear = currentYear + + self.annualInsightsTotalPostsCount = postCount + self.annualInsightsTotalWordsCount = wordsCount + self.annualInsightsAverageWordsCount = wordsAverage + + self.annualInsightsTotalLikesCount = likesCount + self.annualInsightsAverageLikesCount = likesAverage + + self.annualInsightsTotalCommentsCount = commentsCount + self.annualInsightsAverageCommentsCount = commentsAverage + + self.annualInsightsTotalImagesCount = imagesCount + self.annualInsightsAverageImagesCount = imagesAverage + } +} diff --git a/WordPressKit/Insights/StatsCommentsInsight.swift b/WordPressKit/Insights/StatsCommentsInsight.swift new file mode 100644 index 00000000..b2daae36 --- /dev/null +++ b/WordPressKit/Insights/StatsCommentsInsight.swift @@ -0,0 +1,100 @@ +public struct StatsCommentsInsight { + public let topPosts: [StatsTopCommentsPost] + public let topAuthors: [StatsTopCommentsAuthor] +} + +extension StatsCommentsInsight: InsightProtocol { + + //MARK: - InsightProtocol Conformance + public static var queryProperties: [String: AnyObject] { + return [:] + } + + public static var pathComponent: String { + return "stats/comments" + } + + public init?(jsonDictionary: [String: AnyObject]) { + guard + let posts = jsonDictionary["posts"] as? [[String: AnyObject]], + let authors = jsonDictionary["authors"] as? [[String: AnyObject]] + else { + return nil + } + + let topPosts = posts.compactMap { StatsTopCommentsPost(jsonDictionary: $0) } + let topAuthors = authors.compactMap { StatsTopCommentsAuthor(jsonDictionary: $0) } + + self.topPosts = topPosts + self.topAuthors = topAuthors + } + +} + +public struct StatsTopCommentsAuthor { + public let name: String + public let commentCount: Int + public let iconURL: URL? +} + +public struct StatsTopCommentsPost { + public let name: String + public let postID: String + public let commentCount: Int + public let postURL: URL? +} + +private extension StatsTopCommentsAuthor { + init?(jsonDictionary: [String: AnyObject]) { + guard + let name = jsonDictionary["name"] as? String, + let avatar = jsonDictionary["gravatar"] as? String, + let comments = jsonDictionary["comments"] as? String, + let commentCount = Int(comments) + else { + return nil + } + + self.init(name: name, avatar: avatar, commentCount: commentCount) + } + + + init?(name: String, avatar: String, commentCount: Int) { + let url: URL? + + if var components = URLComponents(string: avatar) { + components.query = "d=mm&s=60" // to get a properly-sized avatar. + url = try? components.asURL() + } else { + url = nil + } + + self.name = name + self.commentCount = commentCount + self.iconURL = url + } +} + +private extension StatsTopCommentsPost { + init?(jsonDictionary: [String: AnyObject]) { + guard + let name = jsonDictionary["name"] as? String, + let postID = jsonDictionary["id"] as? String, + let commentString = jsonDictionary["comments"] as? String, + let commentCount = Int(commentString), + let postURL = jsonDictionary["link"] as? String + else { + return nil + } + + self.init(name: name, + postID: postID, + commentCount: commentCount, + postURL: URL(string: postURL)) + } +} + + + + + diff --git a/WordPressKit/Insights/StatsDotComFollowersInsight.swift b/WordPressKit/Insights/StatsDotComFollowersInsight.swift new file mode 100644 index 00000000..7b9a7e2e --- /dev/null +++ b/WordPressKit/Insights/StatsDotComFollowersInsight.swift @@ -0,0 +1,75 @@ +public struct StatsDotComFollowersInsight { + public let dotComFollowersCount: Int + public let topDotComFollowers: [StatsFollower] +} + +extension StatsDotComFollowersInsight: InsightProtocol { + + //MARK: - InsightProtocol Conformance + public static var queryProperties: [String: AnyObject] { + return ["type": "wpcom" as AnyObject, + "max": "7" as AnyObject] + } + + public static var pathComponent: String { + return "stats/followers" + } + + public init?(jsonDictionary: [String: AnyObject]) { + guard + let subscribersCount = jsonDictionary["total_wpcom"] as? Int, + let subscribers = jsonDictionary["subscribers"] as? [[String: AnyObject]] + else { + return nil + } + + let followers = subscribers.compactMap { StatsFollower(jsonDictionary: $0) } + + self.dotComFollowersCount = subscribersCount + self.topDotComFollowers = followers + } + + //MARK: - + fileprivate static let dateFormatter: ISO8601DateFormatter = .init() +} + +public struct StatsFollower { + public let name: String + public let subscribedDate: Date + public let avatarURL: URL? +} + +extension StatsFollower { + + init?(jsonDictionary: [String: AnyObject]) { + guard + let name = jsonDictionary["label"] as? String, + let avatar = jsonDictionary["avatar"] as? String, + let dateString = jsonDictionary["date_subscribed"] as? String + else { + return nil + } + + self.init(name: name, avatar: avatar, date: dateString) + } + + + init?(name: String, avatar: String, date: String) { + guard let date = StatsDotComFollowersInsight.dateFormatter.date(from: date) else { + return nil + } + + let url: URL? + + if var components = URLComponents(string: avatar) { + components.query = "d=mm&s=60" // to get a properly-sized avatar. + url = try? components.asURL() + } else { + url = nil + } + + self.name = name + self.subscribedDate = date + self.avatarURL = url + } +} diff --git a/WordPressKit/Insights/StatsEmailFollowersInsight.swift b/WordPressKit/Insights/StatsEmailFollowersInsight.swift new file mode 100644 index 00000000..46272b5c --- /dev/null +++ b/WordPressKit/Insights/StatsEmailFollowersInsight.swift @@ -0,0 +1,32 @@ +public struct StatsEmailFollowersInsight { + public let emailFollowersCount: Int + public let topEmailFollowers: [StatsFollower] +} + +extension StatsEmailFollowersInsight: InsightProtocol { + + //MARK: - InsightProtocol Conformance + public static var queryProperties: [String: AnyObject] { + return ["type": "email" as AnyObject, + "max": "7" as AnyObject] + } + + public static var pathComponent: String { + return "stats/followers" + } + + public init?(jsonDictionary: [String: AnyObject]) { + guard + let subscribersCount = jsonDictionary["total_email"] as? Int, + let subscribers = jsonDictionary["subscribers"] as? [[String: AnyObject]] + else { + return nil + } + + let followers = subscribers.compactMap { StatsFollower(jsonDictionary: $0) } + + self.emailFollowersCount = subscribersCount + self.topEmailFollowers = followers + } + +} diff --git a/WordPressKit/Insights/StatsPublicizeInsight.swift b/WordPressKit/Insights/StatsPublicizeInsight.swift new file mode 100644 index 00000000..7770ec27 --- /dev/null +++ b/WordPressKit/Insights/StatsPublicizeInsight.swift @@ -0,0 +1,81 @@ +public struct StatsPublicizeInsight { + public let publicizeServices: [StatsPublicizeService] +} + +extension StatsPublicizeInsight: InsightProtocol { + + //MARK: - InsightProtocol Conformance + public static var queryProperties: [String: AnyObject] { + return [:] + } + + public static var pathComponent: String { + return "stats/publicize" + } + + public init?(jsonDictionary: [String: AnyObject]) { + guard + let subscribers = jsonDictionary["services"] as? [[String: AnyObject]] + else { + return nil + } + + let followers = subscribers.compactMap { StatsPublicizeService(publicizeServiceDictionary: $0) } + + self.publicizeServices = followers + } + +} + +public struct StatsPublicizeService { + public let name: String + public let followers: Int + public let iconURL: URL? +} + +private extension StatsPublicizeService { + + init?(publicizeServiceDictionary dictionary: [String: AnyObject]) { + guard + let name = dictionary["service"] as? String, + let followersCount = dictionary["followers"] as? Int else { + return nil + } + + self.init(name: name, followers: followersCount) + } + + init(name: String, followers: Int) { + let niceName: String + let icon: URL? + + switch name { + case "facebook": + niceName = "Facebook" + icon = URL(string: "https://secure.gravatar.com/blavatar/2343ec78a04c6ea9d80806345d31fd78?s=60") + case "twitter": + niceName = "Twitter" + icon = URL(string: "https://secure.gravatar.com/blavatar/7905d1c4e12c54933a44d19fcd5f9356?s=60") + case "tumblr": + niceName = "Tumblr" + icon = URL(string: "https://secure.gravatar.com/blavatar/84314f01e87cb656ba5f382d22d85134?s=60") + case "google_plus": + niceName = "Google+" + icon = URL(string: "https://secure.gravatar.com/blavatar/4a4788c1dfc396b1f86355b274cc26b3?s=60") + case "linkedin": + niceName = "LinkedIn" + icon = URL(string: "https://secure.gravatar.com/blavatar/f54db463750940e0e7f7630fe327845e?s=60") + case "path": + niceName = "path" + icon = URL(string: "https://secure.gravatar.com/blavatar/3a03c8ce5bf1271fb3760bb6e79b02c1?s=60") + default: + niceName = name + icon = nil + } + + self.name = niceName + self.followers = followers + self.iconURL = icon + } +} + diff --git a/WordPressKit/Insights/StatsTagsAndCategoriesInsight.swift b/WordPressKit/Insights/StatsTagsAndCategoriesInsight.swift new file mode 100644 index 00000000..790b538d --- /dev/null +++ b/WordPressKit/Insights/StatsTagsAndCategoriesInsight.swift @@ -0,0 +1,103 @@ +public struct StatsTagsAndCategoriesInsight { + public let topTagsAndCategories: [StatsTagAndCategory] +} + +extension StatsTagsAndCategoriesInsight: InsightProtocol { + public static var queryProperties: [String : AnyObject] { + return [:] + } + + public static var pathComponent: String { + return "stats/tags" + } + + public init?(jsonDictionary: [String : AnyObject]) { + guard + let outerTags = jsonDictionary["tags"] as? [[String: AnyObject]] + // The shape of the API response here leaves... something to be desired. + else { + return nil + } + + let tags = outerTags.compactMap { StatsTagAndCategory(tagsGroup: $0)} + + self.topTagsAndCategories = tags + } +} + +public struct StatsTagAndCategory { + + public enum Kind { + case tag + case category + case folder + } + + public let name: String + public let kind: Kind + public let url: URL? + public let viewsCount: Int? + public let children: [StatsTagAndCategory] +} + +extension StatsTagAndCategory { + init?(tagsGroup: [String: AnyObject]) { + guard + let innerTags = tagsGroup["tags"] as? [[String: AnyObject]] + else { + return nil + } + + // This gets kinda complicated. The API collects some tags/categories + // into groups, and we have to handle that. + if innerTags.count == 1 { + let tag = innerTags.first! + let views = tagsGroup["views"] as? Int + + self.init(singleTag: tag, viewsCount: views) + return + } + + guard let views = tagsGroup["views"] as? Int else { + return nil + } + + let mappedChildren = innerTags.compactMap { StatsTagAndCategory(singleTag: $0) } + let label = mappedChildren.map { $0.name }.joined(separator: ", ") + + self.init(name: label, kind: .folder, url: "", viewsCount: views, children: mappedChildren) + } + + init?(singleTag tag: [String: AnyObject], viewsCount: Int? = 0) { + guard + let name = tag["name"] as? String, + let type = tag["type"] as? String, + let url = tag["link"] as? String + else { + return nil + } + + let kind: Kind + + switch type { + case "category": + kind = .category + case "tag": + kind = .tag + default: + kind = .category + } + + self.init(name: name, kind: kind, url: url, viewsCount: viewsCount, children: []) + } + + + init(name: String, kind: Kind, url: String, viewsCount: Int?, children: [StatsTagAndCategory]) { + self.name = name + self.kind = kind + self.url = URL(string: url) + self.viewsCount = viewsCount + self.children = children + } + +} diff --git a/WordPressKit/Insights/StatsTodayInsight.swift b/WordPressKit/Insights/StatsTodayInsight.swift new file mode 100644 index 00000000..486144f6 --- /dev/null +++ b/WordPressKit/Insights/StatsTodayInsight.swift @@ -0,0 +1,34 @@ +public struct StatsTodayInsight { + public let viewsCount: Int + public let visitorsCount: Int + public let likesCount: Int + public let commentsCount: Int +} + +extension StatsTodayInsight: InsightProtocol { + + //MARK: - InsightProtocol Conformance + public static var queryProperties: [String : AnyObject] { + return [:] + } + + public static var pathComponent: String { + return "stats/summary" + } + + public init?(jsonDictionary: [String: AnyObject]) { + guard + let viewsCount = jsonDictionary["views"] as? Int, + let visitorsCount = jsonDictionary["visitors"] as? Int, + let likesCount = jsonDictionary["likes"] as? Int, + let commentsCount = jsonDictionary["comments"] as? Int + else { + return nil + } + + self.visitorsCount = visitorsCount + self.viewsCount = viewsCount + self.likesCount = likesCount + self.commentsCount = commentsCount + } +} diff --git a/WordPressKit/StatsServiceRemoteV2.swift b/WordPressKit/StatsServiceRemoteV2.swift new file mode 100644 index 00000000..2a5caa44 --- /dev/null +++ b/WordPressKit/StatsServiceRemoteV2.swift @@ -0,0 +1,187 @@ +import Foundation + +// This name isn't great! After finishing the work on StatsRefresh we'll get rid of the "old" +// one and rename this to not have "V2" in it, but we want to keep the old one around +// for a while still. + +public class StatsServiceRemoteV2: ServiceRemoteWordPressComREST { + + public enum ResponseError: Error { + case decodingFailure + } + + private let siteID: Int + private let siteTimezone: TimeZone + + public init(wordPressComRestApi api: WordPressComRestApi, siteID: Int, siteTimezone: TimeZone) { + self.siteID = siteID + self.siteTimezone = siteTimezone + super.init(wordPressComRestApi: api) + } + + + public func getInsight(completion: @escaping ((InsightType?, Error?) -> Void)) { + let properties = InsightType.queryProperties + let pathComponent = InsightType.pathComponent + + let path = self.path(forEndpoint: "sites/\(siteID)/\(pathComponent)/", withVersion: ._1_1) + + wordPressComRestApi.GET(path, parameters: properties, success: { (response, _) in + guard + let jsonResponse = response as? [String: AnyObject], + let insight = InsightType(jsonDictionary: jsonResponse) + else { + completion(nil, ResponseError.decodingFailure) + return + } + + completion(insight, nil) + }, failure: { (error, _) in + completion(nil, error) + }) + } + + // "Last Post" Insights are "fun" in the way that they require multiple requests to actually create them, + // so we do this "fun" dance in a separate method. + public func getInsight(completion: @escaping ((StatsLastPostInsight?, Error?) -> Void)) { + getLastPostInsight(completion: completion) + } + + private func getLastPostInsight(completion: @escaping ((StatsLastPostInsight?, Error?) -> Void)) { + let properties = StatsLastPostInsight.queryProperties + let pathComponent = StatsLastPostInsight.pathComponent + + let path = self.path(forEndpoint: "sites/\(siteID)/\(pathComponent)", withVersion: ._1_1) + + wordPressComRestApi.GET(path, parameters: properties, success: { (response, _) in + guard + let jsonResponse = response as? [String: AnyObject], + let posts = jsonResponse["posts"] as? [[String: AnyObject]], + let post = posts.first, + let postID = post["ID"] as? Int else { + completion(nil, ResponseError.decodingFailure) + return + } + + self.getPostViews(for: postID) { (views, error) in + guard + let views = views, + let insight = StatsLastPostInsight(jsonDictionary: post, views: views) else { + completion(nil, ResponseError.decodingFailure) + return + + } + + completion(insight, nil) + } + }, failure: {(error, _) in + completion(nil, error) + }) + } + + public func getPostViews(`for` postID: Int, completion: @escaping ((Int?, Error?) -> Void)) { + let parameters = ["fields": "views" as AnyObject] + + let path = self.path(forEndpoint: "sites/\(siteID)/stats/post/\(postID)", withVersion: ._1_1) + + wordPressComRestApi.GET(path, + parameters: parameters, + success: { (response, _) in + guard + let jsonResponse = response as? [String: AnyObject], + let views = jsonResponse["views"] as? Int else { + completion(nil, ResponseError.decodingFailure) + return + } + completion(views, nil) + }, failure: { (error, _) in + completion(nil, error) + } + ) + } + +} + +// This serves both as a way to get the query properties in a "nice" way, +// but also as a way to narrow down the generic type in `getInisght:` method. +public protocol InsightProtocol { + static var queryProperties: [String: AnyObject] { get } + static var pathComponent: String { get } + + init?(jsonDictionary: [String: AnyObject]) +} + +extension InsightProtocol { + // A big chunk of those use the `/stats/` endpoint. Let's simplify the protocol conformance in those cases. + public static var pathComponent: String { + return "stats/" + } +} + +// For some god-forsaken reason Swift compiler freaks out if this is not declared _in this file_, +// and refuses to compile the project. +// I'm guessing this has somethign to do with generic specialisation, but I'm not enough +// of a `swiftc` guru to really know. Leaving this in here to appease Swift gods. +// TODO: see if this is still a problem in Swift 5 mode! +public struct StatsLastPostInsight { + public let title: String + public let url: URL + public let publishedDate: Date + public let likesCount: Int + public let commentsCount: Int + public let viewsCount: Int + public let postID: Int +} + +extension StatsLastPostInsight: InsightProtocol { + + //MARK: - InsightProtocol Conformance + public static var queryProperties: [String: AnyObject] { + return ["order_by": "date" as AnyObject, + "number": "1" as AnyObject, + "type": "post" as AnyObject, + "fields": "ID, title, URL, discussion, like_count, date" as AnyObject] + } + + public static var pathComponent: String { + return "posts/" + } + + public init?(jsonDictionary: [String: AnyObject]) { + fatalError("This shouldn't be ever called, instead init?(jsonDictionary:_ views:_) be called instead.") + } + + //MARK: - + + private static let dateFormatter: ISO8601DateFormatter = .init() + + public init?(jsonDictionary: [String: AnyObject], views: Int) { + + guard + let title = jsonDictionary["title"] as? String, + let dateString = jsonDictionary["date"] as? String, + let urlString = jsonDictionary["URL"] as? String, + let likesCount = jsonDictionary["like_count"] as? Int, + let postID = jsonDictionary["ID"] as? Int, + let discussionDict = jsonDictionary["discussion"] as? [String: Any], + let commentsCount = discussionDict["comment_count"] as? Int + else { + return nil + } + + guard + let url = URL(string: urlString), + let date = StatsLastPostInsight.dateFormatter.date(from: dateString) + else { + return nil + } + + self.title = title // TODO: Some magic was done before on this, re-add + self.url = url + self.publishedDate = date + self.likesCount = likesCount + self.commentsCount = commentsCount + self.viewsCount = views + self.postID = postID + } +} From 0bda3a4f93e256b12a40cead39e4f97179ddb40d Mon Sep 17 00:00:00 2001 From: Jan Klausa Date: Mon, 11 Feb 2019 13:14:09 +0100 Subject: [PATCH 2/7] Add a protocol extension for `InsightProtocol.queryProperties` to simplify code in other places --- WordPressKit/Insights/StatsAllTimesInsight.swift | 4 ---- .../Insights/StatsAnnualAndMostPopularTimeInsight.swift | 4 ---- WordPressKit/Insights/StatsCommentsInsight.swift | 4 ---- WordPressKit/Insights/StatsPublicizeInsight.swift | 4 ---- WordPressKit/Insights/StatsTagsAndCategoriesInsight.swift | 4 ---- WordPressKit/Insights/StatsTodayInsight.swift | 4 ---- WordPressKit/StatsServiceRemoteV2.swift | 8 +++++++- 7 files changed, 7 insertions(+), 25 deletions(-) diff --git a/WordPressKit/Insights/StatsAllTimesInsight.swift b/WordPressKit/Insights/StatsAllTimesInsight.swift index 9bd1d2d7..fdb27ef7 100644 --- a/WordPressKit/Insights/StatsAllTimesInsight.swift +++ b/WordPressKit/Insights/StatsAllTimesInsight.swift @@ -10,10 +10,6 @@ public struct StatsAllTimesInsight { extension StatsAllTimesInsight: InsightProtocol { //MARK: - InsightProtocol Conformance - public static var queryProperties: [String : AnyObject] { - return [:] - } - public init?(jsonDictionary: [String: AnyObject]) { guard let statsDict = jsonDictionary["stats"] as? [String: AnyObject], diff --git a/WordPressKit/Insights/StatsAnnualAndMostPopularTimeInsight.swift b/WordPressKit/Insights/StatsAnnualAndMostPopularTimeInsight.swift index 02047992..20cbf17b 100644 --- a/WordPressKit/Insights/StatsAnnualAndMostPopularTimeInsight.swift +++ b/WordPressKit/Insights/StatsAnnualAndMostPopularTimeInsight.swift @@ -25,10 +25,6 @@ public struct StatsAnnualAndMostPopularTimeInsight { } extension StatsAnnualAndMostPopularTimeInsight: InsightProtocol { - public static var queryProperties: [String : AnyObject] { - return [:] - } - public static var pathComponent: String { return "stats/insights" } diff --git a/WordPressKit/Insights/StatsCommentsInsight.swift b/WordPressKit/Insights/StatsCommentsInsight.swift index b2daae36..2e415088 100644 --- a/WordPressKit/Insights/StatsCommentsInsight.swift +++ b/WordPressKit/Insights/StatsCommentsInsight.swift @@ -6,10 +6,6 @@ public struct StatsCommentsInsight { extension StatsCommentsInsight: InsightProtocol { //MARK: - InsightProtocol Conformance - public static var queryProperties: [String: AnyObject] { - return [:] - } - public static var pathComponent: String { return "stats/comments" } diff --git a/WordPressKit/Insights/StatsPublicizeInsight.swift b/WordPressKit/Insights/StatsPublicizeInsight.swift index 7770ec27..185cfda8 100644 --- a/WordPressKit/Insights/StatsPublicizeInsight.swift +++ b/WordPressKit/Insights/StatsPublicizeInsight.swift @@ -5,10 +5,6 @@ public struct StatsPublicizeInsight { extension StatsPublicizeInsight: InsightProtocol { //MARK: - InsightProtocol Conformance - public static var queryProperties: [String: AnyObject] { - return [:] - } - public static var pathComponent: String { return "stats/publicize" } diff --git a/WordPressKit/Insights/StatsTagsAndCategoriesInsight.swift b/WordPressKit/Insights/StatsTagsAndCategoriesInsight.swift index 790b538d..4f7e01cb 100644 --- a/WordPressKit/Insights/StatsTagsAndCategoriesInsight.swift +++ b/WordPressKit/Insights/StatsTagsAndCategoriesInsight.swift @@ -3,10 +3,6 @@ public struct StatsTagsAndCategoriesInsight { } extension StatsTagsAndCategoriesInsight: InsightProtocol { - public static var queryProperties: [String : AnyObject] { - return [:] - } - public static var pathComponent: String { return "stats/tags" } diff --git a/WordPressKit/Insights/StatsTodayInsight.swift b/WordPressKit/Insights/StatsTodayInsight.swift index 486144f6..bba5b0d8 100644 --- a/WordPressKit/Insights/StatsTodayInsight.swift +++ b/WordPressKit/Insights/StatsTodayInsight.swift @@ -8,10 +8,6 @@ public struct StatsTodayInsight { extension StatsTodayInsight: InsightProtocol { //MARK: - InsightProtocol Conformance - public static var queryProperties: [String : AnyObject] { - return [:] - } - public static var pathComponent: String { return "stats/summary" } diff --git a/WordPressKit/StatsServiceRemoteV2.swift b/WordPressKit/StatsServiceRemoteV2.swift index 2a5caa44..cc213269 100644 --- a/WordPressKit/StatsServiceRemoteV2.swift +++ b/WordPressKit/StatsServiceRemoteV2.swift @@ -112,7 +112,13 @@ public protocol InsightProtocol { } extension InsightProtocol { - // A big chunk of those use the `/stats/` endpoint. Let's simplify the protocol conformance in those cases. + + // A big chunk of those use the same endpoint and queryProperties.. Let's simplify the protocol conformance in those cases. + + public static var queryProperties: [String: AnyObject] { + return [:] + } + public static var pathComponent: String { return "stats/" } From 3886f1df782a5d445c8b687b4f6a3279f57bb240 Mon Sep 17 00:00:00 2001 From: Jan Klausa Date: Mon, 11 Feb 2019 14:23:15 +0100 Subject: [PATCH 3/7] Add string sanitisation back --- WordPressKit/StatsServiceRemoteV2.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPressKit/StatsServiceRemoteV2.swift b/WordPressKit/StatsServiceRemoteV2.swift index cc213269..b3f176dc 100644 --- a/WordPressKit/StatsServiceRemoteV2.swift +++ b/WordPressKit/StatsServiceRemoteV2.swift @@ -182,7 +182,7 @@ extension StatsLastPostInsight: InsightProtocol { return nil } - self.title = title // TODO: Some magic was done before on this, re-add + self.title = title.trimmingCharacters(in: CharacterSet.whitespaces).stringByDecodingXMLCharacters() self.url = url self.publishedDate = date self.likesCount = likesCount From b0cb830411d1b05438f0f0822f3ffe6497f5f2d2 Mon Sep 17 00:00:00 2001 From: Jan Klausa Date: Wed, 13 Feb 2019 17:59:43 +0100 Subject: [PATCH 4/7] Update podspec. --- Podfile.lock | 4 ++-- WordPressKit.podspec | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index 8640f3b3..bdccafaf 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -27,7 +27,7 @@ PODS: - OHHTTPStubs/Swift (6.1.0): - OHHTTPStubs/Default - UIDeviceIdentifier (1.1.4) - - WordPressKit (2.0.0-beta.5): + - WordPressKit (2.1.0-beta.1): - Alamofire (~> 4.7.3) - CocoaLumberjack (= 3.4.2) - NSObject-SafeExpectations (= 0.0.3) @@ -70,7 +70,7 @@ SPEC CHECKSUMS: OCMock: 43565190abc78977ad44a61c0d20d7f0784d35ab OHHTTPStubs: 1e21c7d2c084b8153fc53d48400d8919d2d432d0 UIDeviceIdentifier: 8f8a24b257a4d978c8d40ad1e7355b944ffbfa8c - WordPressKit: b459959437001fd70c0955de37493754f11a7b03 + WordPressKit: acf6dcee93c9158286a17e02b3bba6931cf34e8c WordPressShared: a2fc2db66c210a05d317ae9678b5823dd6a4d708 wpxmlrpc: 6ba55c773cfa27083ae4a2173e69b19f46da98e2 diff --git a/WordPressKit.podspec b/WordPressKit.podspec index 231132ad..0b089de0 100644 --- a/WordPressKit.podspec +++ b/WordPressKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "WordPressKit" - s.version = "2.0.0-beta.5" + s.version = "2.1.0-beta.1" s.summary = "WordPressKit offers a clean and simple WordPress.com and WordPress.org API." s.description = <<-DESC From d06e9712f7598e4170f41caf3f81baeb13fc4910 Mon Sep 17 00:00:00 2001 From: Jan Klausa Date: Wed, 13 Feb 2019 18:04:12 +0100 Subject: [PATCH 5/7] Address code review notes. --- WordPressKit/Insights/StatsAllTimesInsight.swift | 2 +- WordPressKit/Insights/StatsDotComFollowersInsight.swift | 2 +- WordPressKit/StatsServiceRemoteV2.swift | 9 ++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/WordPressKit/Insights/StatsAllTimesInsight.swift b/WordPressKit/Insights/StatsAllTimesInsight.swift index fdb27ef7..01222854 100644 --- a/WordPressKit/Insights/StatsAllTimesInsight.swift +++ b/WordPressKit/Insights/StatsAllTimesInsight.swift @@ -33,7 +33,7 @@ extension StatsAllTimesInsight: InsightProtocol { //MARK: - private static var dateFormatter: DateFormatter { let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd"; + formatter.dateFormat = "yyyy-MM-dd" return formatter } } diff --git a/WordPressKit/Insights/StatsDotComFollowersInsight.swift b/WordPressKit/Insights/StatsDotComFollowersInsight.swift index 7b9a7e2e..eb2ea79e 100644 --- a/WordPressKit/Insights/StatsDotComFollowersInsight.swift +++ b/WordPressKit/Insights/StatsDotComFollowersInsight.swift @@ -30,7 +30,7 @@ extension StatsDotComFollowersInsight: InsightProtocol { } //MARK: - - fileprivate static let dateFormatter: ISO8601DateFormatter = .init() + fileprivate static let dateFormatter = ISO8601DateFormatter() } public struct StatsFollower { diff --git a/WordPressKit/StatsServiceRemoteV2.swift b/WordPressKit/StatsServiceRemoteV2.swift index b3f176dc..301a7462 100644 --- a/WordPressKit/StatsServiceRemoteV2.swift +++ b/WordPressKit/StatsServiceRemoteV2.swift @@ -79,7 +79,7 @@ public class StatsServiceRemoteV2: ServiceRemoteWordPressComREST { }) } - public func getPostViews(`for` postID: Int, completion: @escaping ((Int?, Error?) -> Void)) { + private func getPostViews(`for` postID: Int, completion: @escaping ((Int?, Error?) -> Void)) { let parameters = ["fields": "views" as AnyObject] let path = self.path(forEndpoint: "sites/\(siteID)/stats/post/\(postID)", withVersion: ._1_1) @@ -103,7 +103,7 @@ public class StatsServiceRemoteV2: ServiceRemoteWordPressComREST { } // This serves both as a way to get the query properties in a "nice" way, -// but also as a way to narrow down the generic type in `getInisght:` method. +// but also as a way to narrow down the generic type in `getInsight(completion:)` method. public protocol InsightProtocol { static var queryProperties: [String: AnyObject] { get } static var pathComponent: String { get } @@ -124,8 +124,7 @@ extension InsightProtocol { } } -// For some god-forsaken reason Swift compiler freaks out if this is not declared _in this file_, -// and refuses to compile the project. +// Swift compiler doesn't like if this is not declared _in this file_, and refuses to compile the project. // I'm guessing this has somethign to do with generic specialisation, but I'm not enough // of a `swiftc` guru to really know. Leaving this in here to appease Swift gods. // TODO: see if this is still a problem in Swift 5 mode! @@ -159,7 +158,7 @@ extension StatsLastPostInsight: InsightProtocol { //MARK: - - private static let dateFormatter: ISO8601DateFormatter = .init() + private static let dateFormatter = ISO8601DateFormatter() public init?(jsonDictionary: [String: AnyObject], views: Int) { From db40daafc07ae10b763ff407a5fadfc2d8aa0f4c Mon Sep 17 00:00:00 2001 From: Jan Klausa Date: Wed, 13 Feb 2019 18:04:54 +0100 Subject: [PATCH 6/7] Simplify `InsightProtocol` to use stricter types than `AnyObject` and get rid of incessant casting. --- .../Insights/StatsDotComFollowersInsight.swift | 6 +++--- .../Insights/StatsEmailFollowersInsight.swift | 6 +++--- WordPressKit/StatsServiceRemoteV2.swift | 18 +++++++++--------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/WordPressKit/Insights/StatsDotComFollowersInsight.swift b/WordPressKit/Insights/StatsDotComFollowersInsight.swift index eb2ea79e..18a4212d 100644 --- a/WordPressKit/Insights/StatsDotComFollowersInsight.swift +++ b/WordPressKit/Insights/StatsDotComFollowersInsight.swift @@ -6,9 +6,9 @@ public struct StatsDotComFollowersInsight { extension StatsDotComFollowersInsight: InsightProtocol { //MARK: - InsightProtocol Conformance - public static var queryProperties: [String: AnyObject] { - return ["type": "wpcom" as AnyObject, - "max": "7" as AnyObject] + public static var queryProperties: [String: String] { + return ["type": "wpcom", + "max": "7"] } public static var pathComponent: String { diff --git a/WordPressKit/Insights/StatsEmailFollowersInsight.swift b/WordPressKit/Insights/StatsEmailFollowersInsight.swift index 46272b5c..7038ba11 100644 --- a/WordPressKit/Insights/StatsEmailFollowersInsight.swift +++ b/WordPressKit/Insights/StatsEmailFollowersInsight.swift @@ -6,9 +6,9 @@ public struct StatsEmailFollowersInsight { extension StatsEmailFollowersInsight: InsightProtocol { //MARK: - InsightProtocol Conformance - public static var queryProperties: [String: AnyObject] { - return ["type": "email" as AnyObject, - "max": "7" as AnyObject] + public static var queryProperties: [String: String] { + return ["type": "email", + "max": "7"] } public static var pathComponent: String { diff --git a/WordPressKit/StatsServiceRemoteV2.swift b/WordPressKit/StatsServiceRemoteV2.swift index 301a7462..3d2c14a3 100644 --- a/WordPressKit/StatsServiceRemoteV2.swift +++ b/WordPressKit/StatsServiceRemoteV2.swift @@ -21,7 +21,7 @@ public class StatsServiceRemoteV2: ServiceRemoteWordPressComREST { public func getInsight(completion: @escaping ((InsightType?, Error?) -> Void)) { - let properties = InsightType.queryProperties + let properties = InsightType.queryProperties as [String: AnyObject] let pathComponent = InsightType.pathComponent let path = self.path(forEndpoint: "sites/\(siteID)/\(pathComponent)/", withVersion: ._1_1) @@ -48,7 +48,7 @@ public class StatsServiceRemoteV2: ServiceRemoteWordPressComREST { } private func getLastPostInsight(completion: @escaping ((StatsLastPostInsight?, Error?) -> Void)) { - let properties = StatsLastPostInsight.queryProperties + let properties = StatsLastPostInsight.queryProperties as [String: AnyObject] let pathComponent = StatsLastPostInsight.pathComponent let path = self.path(forEndpoint: "sites/\(siteID)/\(pathComponent)", withVersion: ._1_1) @@ -105,7 +105,7 @@ public class StatsServiceRemoteV2: ServiceRemoteWordPressComREST { // This serves both as a way to get the query properties in a "nice" way, // but also as a way to narrow down the generic type in `getInsight(completion:)` method. public protocol InsightProtocol { - static var queryProperties: [String: AnyObject] { get } + static var queryProperties: [String: String] { get } static var pathComponent: String { get } init?(jsonDictionary: [String: AnyObject]) @@ -115,7 +115,7 @@ extension InsightProtocol { // A big chunk of those use the same endpoint and queryProperties.. Let's simplify the protocol conformance in those cases. - public static var queryProperties: [String: AnyObject] { + public static var queryProperties: [String: String] { return [:] } @@ -141,11 +141,11 @@ public struct StatsLastPostInsight { extension StatsLastPostInsight: InsightProtocol { //MARK: - InsightProtocol Conformance - public static var queryProperties: [String: AnyObject] { - return ["order_by": "date" as AnyObject, - "number": "1" as AnyObject, - "type": "post" as AnyObject, - "fields": "ID, title, URL, discussion, like_count, date" as AnyObject] + public static var queryProperties: [String: String] { + return ["order_by": "date", + "number": "1", + "type": "post", + "fields": "ID, title, URL, discussion, like_count, date"] } public static var pathComponent: String { From 8c9a2c69ed3f47467280dad1112303df2b5eb9de Mon Sep 17 00:00:00 2001 From: Jan Klausa Date: Wed, 13 Feb 2019 18:06:26 +0100 Subject: [PATCH 7/7] Simplify an expression to a ternary operator --- .../Insights/StatsAnnualAndMostPopularTimeInsight.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/WordPressKit/Insights/StatsAnnualAndMostPopularTimeInsight.swift b/WordPressKit/Insights/StatsAnnualAndMostPopularTimeInsight.swift index 20cbf17b..3a74eff7 100644 --- a/WordPressKit/Insights/StatsAnnualAndMostPopularTimeInsight.swift +++ b/WordPressKit/Insights/StatsAnnualAndMostPopularTimeInsight.swift @@ -56,11 +56,7 @@ extension StatsAnnualAndMostPopularTimeInsight: InsightProtocol { // iOS Calendar system is `1-based` and uses Sunday as the first day of the week. // The data returned from WP.com is `0-based` and uses Monday as the first day of the week. // This maps the WP.com data to iOS format. - if $0 == 6 { - return 0 - } - - return $0 + 2 + return $0 == 6 ? 0 : $0 + 2 } let weekDayComponent = DateComponents(weekday: mappedWeekday(highestDayOfWeek))