From 7cb9042b92c4dba730a7210a0eab415b12e02360 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Mon, 22 Apr 2024 20:56:10 -0400 Subject: [PATCH 01/12] Add subscriber chart API endpoint --- CHANGELOG.md | 1 + Package.resolved | 10 +- .../Stats/StatsSubscribersSummaryData.swift | 52 + .../Services/StatsServiceRemoteV2.swift | 24 + .../Mock Data/stats-subscribers.json | 921 ++++++++++++++++++ .../V2/StatsSubscribersSummaryDataTests.swift | 24 + 6 files changed, 1027 insertions(+), 5 deletions(-) create mode 100644 Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift create mode 100644 Tests/WordPressKitTests/Mock Data/stats-subscribers.json create mode 100644 Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 61ccdee6..a70f2f88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ _None._ - Add `getPost(withID)` to `PostServiceRemoteExtended` [#785] - Add support for metadata to `PostServiceRemoteExtended` [#783] - Add fetching of `StatsEmailsSummaryData` to `StatsService` [#794] +- Add fetching of `StatsSubscribersSummaryData` to `StatsService` [#795] ### Bug Fixes diff --git a/Package.resolved b/Package.resolved index a25dc7e5..14fa1cb5 100644 --- a/Package.resolved +++ b/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", "state" : { - "revision" : "7892a123f7e8d0fe62f9f03728b17bbd4f94df5c", - "version" : "1.8.1" + "revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0", + "version" : "1.8.2" } }, { @@ -96,7 +96,7 @@ "location" : "https://github.com/wordpress-mobile/WordPress-iOS-Shared.git", "state" : { "branch" : "mokagio/swiftlint-read-as-dependency", - "revision" : "422950b28f01d7cc11218e7d70a6cd65004d23ae" + "revision" : "8b6afb494f64070b46e970337f541a3548999980" } }, { @@ -113,8 +113,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/jpsim/Yams.git", "state" : { - "revision" : "8a835d918245ca22f36663dd3862138805d7f707", - "version" : "5.1.0" + "revision" : "9234124cff5e22e178988c18d8b95a8ae8007f76", + "version" : "5.1.2" } } ], diff --git a/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift new file mode 100644 index 00000000..515713b8 --- /dev/null +++ b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift @@ -0,0 +1,52 @@ +import Foundation +import WordPressShared + +public struct StatsSubscribersSummaryData: Decodable, Equatable { + let history: [SubscriberData] + + private enum CodingKeys: String, CodingKey { + case history = "data" + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + history = try container.decode([SubscriberData].self, forKey: .history) + } + + public struct SubscriberData: Decodable, Equatable { + let date: Date + let count: Int + + private enum CodingKeys: Int, CodingKey { + case date = 0 + case count = 1 + } + + public init(from decoder: any Decoder) throws { + var container = try decoder.unkeyedContainer() + + date = ISO8601DateFormatter().date(from: try container.decode(String.self)) ?? Date() + count = try container.decode(Int.self) + } + } +} + +extension StatsSubscribersSummaryData { + public static var pathComponent: String { + return "stats/subscribers" + } + + public init?(jsonDictionary: [String: AnyObject]) { + do { + let jsonData = try JSONSerialization.data(withJSONObject: jsonDictionary, options: []) + let decoder = JSONDecoder.apiDecoder + self = try decoder.decode(Self.self, from: jsonData) + } catch { + return nil + } + } + + public static func queryProperties(quantity: Int, unit: String) -> [String: String] { + return ["quantity": String(quantity), "unit": unit] + } +} diff --git a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift index 2ae1b68b..5ad7b473 100644 --- a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift +++ b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift @@ -316,6 +316,30 @@ private extension StatsServiceRemoteV2 { } } +// MARK: - Subscribers Data + +public extension StatsServiceRemoteV2 { + func getSubscribers(completion: @escaping ((Result) -> Void)) { + let pathComponent = StatsSubscribersSummaryData.pathComponent + let path = self.path(forEndpoint: "sites/\(siteID)/\(pathComponent)/", withVersion: ._1_1) + let properties = StatsSubscribersSummaryData.queryProperties(quantity: 30, unit: "day") as [String: AnyObject] + + wordPressComRESTAPI.get(path, parameters: properties, success: { [weak self] (response, _) in + guard let self, + let jsonResponse = response as? [String: AnyObject], + let subscribersSummaryData = StatsSubscribersSummaryData(jsonDictionary: jsonResponse) + else { + completion(.failure(ResponseError.decodingFailure)) + return + } + + completion(.success(subscribersSummaryData)) + }, failure: { (error, _) in + completion(.failure(error)) + }) + } +} + // MARK: - Emails Summary public extension StatsServiceRemoteV2 { diff --git a/Tests/WordPressKitTests/Mock Data/stats-subscribers.json b/Tests/WordPressKitTests/Mock Data/stats-subscribers.json new file mode 100644 index 00000000..803ff7b5 --- /dev/null +++ b/Tests/WordPressKitTests/Mock Data/stats-subscribers.json @@ -0,0 +1,921 @@ +{ + "date": "2024-04-22", + "unit": "day", + "fields": [ + "period", + "subscribers", + "subscribers_change" + ], + "data": [ + [ + "2024-04-22", + 77, + 0, + [ + "2024-04-22" + ] + ], + [ + "2024-04-21", + 78, + 0, + [ + "2024-04-21" + ] + ], + [ + "2024-04-20", + 78, + 0, + [ + "2024-04-20" + ] + ], + [ + "2024-04-19", + 78, + 0, + [ + "2024-04-19" + ] + ], + [ + "2024-04-18", + 78, + 0, + [ + "2024-04-18" + ] + ], + [ + "2024-04-17", + 78, + 0, + [ + "2024-04-17" + ] + ], + [ + "2024-04-16", + 78, + 0, + [ + "2024-04-16" + ] + ], + [ + "2024-04-15", + 78, + 0, + [ + "2024-04-15" + ] + ], + [ + "2024-04-14", + 78, + 0, + [ + "2024-04-14" + ] + ], + [ + "2024-04-13", + 78, + 0, + [ + "2024-04-13" + ] + ], + [ + "2024-04-12", + 78, + 0, + [ + "2024-04-12" + ] + ], + [ + "2024-04-11", + 78, + 0, + [ + "2024-04-11" + ] + ], + [ + "2024-04-10", + 78, + 0, + [ + "2024-04-10" + ] + ], + [ + "2024-04-09", + 78, + 0, + [ + "2024-04-09" + ] + ], + [ + "2024-04-08", + 76, + 0, + [ + "2024-04-08" + ] + ], + [ + "2024-04-07", + 76, + 0, + [ + "2024-04-07" + ] + ], + [ + "2024-04-06", + 76, + 0, + [ + "2024-04-06" + ] + ], + [ + "2024-04-05", + 76, + 0, + [ + "2024-04-05" + ] + ], + [ + "2024-04-04", + 78, + 0, + [ + "2024-04-04" + ] + ], + [ + "2024-04-03", + 78, + 0, + [ + "2024-04-03" + ] + ], + [ + "2024-04-02", + 78, + 0, + [ + "2024-04-02" + ] + ], + [ + "2024-04-01", + 78, + 0, + [ + "2024-04-01" + ] + ], + [ + "2024-03-31", + 78, + 0, + [ + "2024-03-31" + ] + ], + [ + "2024-03-30", + 78, + 0, + [ + "2024-03-30" + ] + ], + [ + "2024-03-29", + 79, + 0, + [ + "2024-03-29" + ] + ], + [ + "2024-03-28", + 79, + 0, + [ + "2024-03-28" + ] + ], + [ + "2024-03-27", + 79, + 0, + [ + "2024-03-27" + ] + ], + [ + "2024-03-26", + 80, + 0, + [ + "2024-03-26" + ] + ], + [ + "2024-03-25", + 78, + 0, + [ + "2024-03-25" + ] + ], + [ + "2024-03-24", + 78, + 0, + [ + "2024-03-24" + ] + ] + ], + "periods": [ + { + "label": "2024-03-24", + "dates": [ + "2024-03-24" + ] + }, + { + "label": "2024-03-25", + "dates": [ + "2024-03-25" + ] + }, + { + "label": "2024-03-26", + "dates": [ + "2024-03-26" + ] + }, + { + "label": "2024-03-27", + "dates": [ + "2024-03-27" + ] + }, + { + "label": "2024-03-28", + "dates": [ + "2024-03-28" + ] + }, + { + "label": "2024-03-29", + "dates": [ + "2024-03-29" + ] + }, + { + "label": "2024-03-30", + "dates": [ + "2024-03-30" + ] + }, + { + "label": "2024-03-31", + "dates": [ + "2024-03-31" + ] + }, + { + "label": "2024-04-01", + "dates": [ + "2024-04-01" + ] + }, + { + "label": "2024-04-02", + "dates": [ + "2024-04-02" + ] + }, + { + "label": "2024-04-03", + "dates": [ + "2024-04-03" + ] + }, + { + "label": "2024-04-04", + "dates": [ + "2024-04-04" + ] + }, + { + "label": "2024-04-05", + "dates": [ + "2024-04-05" + ] + }, + { + "label": "2024-04-06", + "dates": [ + "2024-04-06" + ] + }, + { + "label": "2024-04-07", + "dates": [ + "2024-04-07" + ] + }, + { + "label": "2024-04-08", + "dates": [ + "2024-04-08" + ] + }, + { + "label": "2024-04-09", + "dates": [ + "2024-04-09" + ] + }, + { + "label": "2024-04-10", + "dates": [ + "2024-04-10" + ] + }, + { + "label": "2024-04-11", + "dates": [ + "2024-04-11" + ] + }, + { + "label": "2024-04-12", + "dates": [ + "2024-04-12" + ] + }, + { + "label": "2024-04-13", + "dates": [ + "2024-04-13" + ] + }, + { + "label": "2024-04-14", + "dates": [ + "2024-04-14" + ] + }, + { + "label": "2024-04-15", + "dates": [ + "2024-04-15" + ] + }, + { + "label": "2024-04-16", + "dates": [ + "2024-04-16" + ] + }, + { + "label": "2024-04-17", + "dates": [ + "2024-04-17" + ] + }, + { + "label": "2024-04-18", + "dates": [ + "2024-04-18" + ] + }, + { + "label": "2024-04-19", + "dates": [ + "2024-04-19" + ] + }, + { + "label": "2024-04-20", + "dates": [ + "2024-04-20" + ] + }, + { + "label": "2024-04-21", + "dates": [ + "2024-04-21" + ] + }, + { + "label": "2024-04-22", + "dates": [ + "2024-04-22" + ] + } + ], + "next_date_subscriber_total_before_change": 78, + "all_days": [ + "2024-03-23", + "2024-03-24", + "2024-03-25", + "2024-03-26", + "2024-03-27", + "2024-03-28", + "2024-03-29", + "2024-03-30", + "2024-03-31", + "2024-04-01", + "2024-04-02", + "2024-04-03", + "2024-04-04", + "2024-04-05", + "2024-04-06", + "2024-04-07", + "2024-04-08", + "2024-04-09", + "2024-04-10", + "2024-04-11", + "2024-04-12", + "2024-04-13", + "2024-04-14", + "2024-04-15", + "2024-04-16", + "2024-04-17", + "2024-04-18", + "2024-04-19", + "2024-04-20", + "2024-04-21", + "2024-04-22", + "2024-04-23" + ], + "utc_all_dates_subscriber_total_before_change": { + "2024-04-23": null, + "2024-04-22": 77, + "2024-04-21": 78, + "2024-04-20": 78, + "2024-04-19": 78, + "2024-04-18": 78, + "2024-04-17": 78, + "2024-04-16": 78, + "2024-04-15": 78, + "2024-04-14": 78, + "2024-04-13": 78, + "2024-04-12": 78, + "2024-04-11": 78, + "2024-04-10": 78, + "2024-04-09": 76, + "2024-04-08": 76, + "2024-04-07": 76, + "2024-04-06": 76, + "2024-04-05": 76, + "2024-04-04": 78, + "2024-04-03": 78, + "2024-04-02": 78, + "2024-04-01": 78, + "2024-03-31": 78, + "2024-03-30": 78, + "2024-03-29": 79, + "2024-03-28": 79, + "2024-03-27": 79, + "2024-03-26": 78, + "2024-03-25": 78, + "2024-03-24": 78, + "2024-03-23": 78 + }, + "subscription_count": 77, + "utc_period_dates_data": { + "2024-04-22": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "77", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": "-1", + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": "-1", + "hour_14": null, + "hour_15": "-1", + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": "-1", + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-04-21": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": "-1", + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": null, + "hour_15": "-2", + "hour_16": null, + "hour_17": null, + "hour_18": "-5", + "hour_19": "-3", + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": "-2" + }, + "2024-04-20": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": "-1", + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": "-1", + "hour_12": null, + "hour_13": null, + "hour_14": null, + "hour_15": "-1", + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-04-19": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": "-1", + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": "-2", + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": "-1", + "hour_15": null, + "hour_16": "-1", + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-04-18": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": "-1", + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": "-1", + "hour_11": null, + "hour_12": "-1", + "hour_13": null, + "hour_14": null, + "hour_15": null, + "hour_16": "-1", + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-04-17": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": "-1", + "hour_08": null, + "hour_09": "-2", + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": "-2", + "hour_15": "-1", + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-04-09": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "2", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": null, + "hour_15": "0", + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-04-04": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": null, + "hour_15": null, + "hour_16": "-1", + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-04-01": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": null, + "hour_15": null, + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": "-1", + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-03-31": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": null, + "hour_15": "-2", + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-03-29": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "79", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": null, + "hour_15": null, + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": "-2", + "hour_23": null + }, + "2024-03-26": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "80", + "total": "2", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": "2", + "hour_15": null, + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + } + }, + "utc_period_dates_subscriber_total": { + "2024-04-22": 77, + "2024-04-21": 78, + "2024-04-20": 78, + "2024-04-19": 78, + "2024-04-18": 78, + "2024-04-17": 78, + "2024-04-09": 78, + "2024-04-04": 78, + "2024-04-01": 78, + "2024-03-31": 78, + "2024-03-29": 79, + "2024-03-26": 80 + }, + "timezone_all_dates_subscriber_total": { + "2024-04-22": 77, + "2024-04-21": 78, + "2024-04-20": 78, + "2024-04-19": 78, + "2024-04-18": 78, + "2024-04-17": 78, + "2024-04-09": 78, + "2024-04-04": 78, + "2024-04-01": 78, + "2024-03-31": 78, + "2024-03-29": 79, + "2024-03-26": 80 + }, + "timezone": 0, + "gmt_offset": "0", + "detail": { + "blog_id": "123", + "domain": "example.wordpress.com", + "path": "/", + "site_id": "1", + "registered": "2020-07-23 14:06:59", + "last_updated": "2024-04-22 22:57:22", + "public": "-1", + "archived": "0", + "mature": "0", + "spam": "0", + "deleted": "0", + "lang_id": "1", + "ds_blog": "45", + "ds_stats": "0", + "blogname": "Example", + "siteurl": "http://example.wordpress.com", + "post_count": "1877", + "home": "http://example.wordpress.com" + }, + "earliest_date": { + "date": "2024-03-24 00:00:00.000000", + "timezone_type": 1, + "timezone": "+00:00" + }, + "end_date": "2024-04-22 00:00:00" +} diff --git a/Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift b/Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift new file mode 100644 index 00000000..7c21c001 --- /dev/null +++ b/Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift @@ -0,0 +1,24 @@ +import XCTest +@testable import WordPressKit + +final class StatsSubscribersSummaryDataTests: XCTestCase { + func testEmailsSummaryDecoding() throws { + let json = getJSON("stats-subscribers") + + let summary = StatsSubscribersSummaryData(jsonDictionary: json) + XCTAssertNotNil(summary, "StatsSubscribersSummaryData not decoded as expected") + let history = summary!.history + let firstDay = history[0] + + XCTAssertEqual(firstDay.date, ISO8601DateFormatter().date(from: "2024-04-22")) + XCTAssertEqual(firstDay.count, 77) + } +} + +private extension StatsSubscribersSummaryDataTests { + func getJSON(_ fileName: String) -> [String: AnyObject] { + let path = Bundle(for: type(of: self)).path(forResource: fileName, ofType: "json")! + let data = try! Data(contentsOf: URL(fileURLWithPath: path)) + return try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: AnyObject] + } +} From 50562f857c69cb00b90d5e105fb9d223ddb8d2af Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Mon, 22 Apr 2024 20:56:10 -0400 Subject: [PATCH 02/12] Add subscriber chart API endpoint --- CHANGELOG.md | 1 + Package.resolved | 10 +- .../Stats/StatsSubscribersSummaryData.swift | 52 + .../Services/StatsServiceRemoteV2.swift | 24 + .../Mock Data/stats-subscribers.json | 921 ++++++++++++++++++ .../V2/StatsSubscribersSummaryDataTests.swift | 24 + 6 files changed, 1027 insertions(+), 5 deletions(-) create mode 100644 Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift create mode 100644 Tests/WordPressKitTests/Mock Data/stats-subscribers.json create mode 100644 Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 61ccdee6..a70f2f88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ _None._ - Add `getPost(withID)` to `PostServiceRemoteExtended` [#785] - Add support for metadata to `PostServiceRemoteExtended` [#783] - Add fetching of `StatsEmailsSummaryData` to `StatsService` [#794] +- Add fetching of `StatsSubscribersSummaryData` to `StatsService` [#795] ### Bug Fixes diff --git a/Package.resolved b/Package.resolved index a25dc7e5..14fa1cb5 100644 --- a/Package.resolved +++ b/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", "state" : { - "revision" : "7892a123f7e8d0fe62f9f03728b17bbd4f94df5c", - "version" : "1.8.1" + "revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0", + "version" : "1.8.2" } }, { @@ -96,7 +96,7 @@ "location" : "https://github.com/wordpress-mobile/WordPress-iOS-Shared.git", "state" : { "branch" : "mokagio/swiftlint-read-as-dependency", - "revision" : "422950b28f01d7cc11218e7d70a6cd65004d23ae" + "revision" : "8b6afb494f64070b46e970337f541a3548999980" } }, { @@ -113,8 +113,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/jpsim/Yams.git", "state" : { - "revision" : "8a835d918245ca22f36663dd3862138805d7f707", - "version" : "5.1.0" + "revision" : "9234124cff5e22e178988c18d8b95a8ae8007f76", + "version" : "5.1.2" } } ], diff --git a/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift new file mode 100644 index 00000000..515713b8 --- /dev/null +++ b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift @@ -0,0 +1,52 @@ +import Foundation +import WordPressShared + +public struct StatsSubscribersSummaryData: Decodable, Equatable { + let history: [SubscriberData] + + private enum CodingKeys: String, CodingKey { + case history = "data" + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + history = try container.decode([SubscriberData].self, forKey: .history) + } + + public struct SubscriberData: Decodable, Equatable { + let date: Date + let count: Int + + private enum CodingKeys: Int, CodingKey { + case date = 0 + case count = 1 + } + + public init(from decoder: any Decoder) throws { + var container = try decoder.unkeyedContainer() + + date = ISO8601DateFormatter().date(from: try container.decode(String.self)) ?? Date() + count = try container.decode(Int.self) + } + } +} + +extension StatsSubscribersSummaryData { + public static var pathComponent: String { + return "stats/subscribers" + } + + public init?(jsonDictionary: [String: AnyObject]) { + do { + let jsonData = try JSONSerialization.data(withJSONObject: jsonDictionary, options: []) + let decoder = JSONDecoder.apiDecoder + self = try decoder.decode(Self.self, from: jsonData) + } catch { + return nil + } + } + + public static func queryProperties(quantity: Int, unit: String) -> [String: String] { + return ["quantity": String(quantity), "unit": unit] + } +} diff --git a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift index be9d554d..94778463 100644 --- a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift +++ b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift @@ -316,6 +316,30 @@ private extension StatsServiceRemoteV2 { } } +// MARK: - Subscribers Data + +public extension StatsServiceRemoteV2 { + func getSubscribers(completion: @escaping ((Result) -> Void)) { + let pathComponent = StatsSubscribersSummaryData.pathComponent + let path = self.path(forEndpoint: "sites/\(siteID)/\(pathComponent)/", withVersion: ._1_1) + let properties = StatsSubscribersSummaryData.queryProperties(quantity: 30, unit: "day") as [String: AnyObject] + + wordPressComRESTAPI.get(path, parameters: properties, success: { [weak self] (response, _) in + guard let self, + let jsonResponse = response as? [String: AnyObject], + let subscribersSummaryData = StatsSubscribersSummaryData(jsonDictionary: jsonResponse) + else { + completion(.failure(ResponseError.decodingFailure)) + return + } + + completion(.success(subscribersSummaryData)) + }, failure: { (error, _) in + completion(.failure(error)) + }) + } +} + // MARK: - Emails Summary public extension StatsServiceRemoteV2 { diff --git a/Tests/WordPressKitTests/Mock Data/stats-subscribers.json b/Tests/WordPressKitTests/Mock Data/stats-subscribers.json new file mode 100644 index 00000000..803ff7b5 --- /dev/null +++ b/Tests/WordPressKitTests/Mock Data/stats-subscribers.json @@ -0,0 +1,921 @@ +{ + "date": "2024-04-22", + "unit": "day", + "fields": [ + "period", + "subscribers", + "subscribers_change" + ], + "data": [ + [ + "2024-04-22", + 77, + 0, + [ + "2024-04-22" + ] + ], + [ + "2024-04-21", + 78, + 0, + [ + "2024-04-21" + ] + ], + [ + "2024-04-20", + 78, + 0, + [ + "2024-04-20" + ] + ], + [ + "2024-04-19", + 78, + 0, + [ + "2024-04-19" + ] + ], + [ + "2024-04-18", + 78, + 0, + [ + "2024-04-18" + ] + ], + [ + "2024-04-17", + 78, + 0, + [ + "2024-04-17" + ] + ], + [ + "2024-04-16", + 78, + 0, + [ + "2024-04-16" + ] + ], + [ + "2024-04-15", + 78, + 0, + [ + "2024-04-15" + ] + ], + [ + "2024-04-14", + 78, + 0, + [ + "2024-04-14" + ] + ], + [ + "2024-04-13", + 78, + 0, + [ + "2024-04-13" + ] + ], + [ + "2024-04-12", + 78, + 0, + [ + "2024-04-12" + ] + ], + [ + "2024-04-11", + 78, + 0, + [ + "2024-04-11" + ] + ], + [ + "2024-04-10", + 78, + 0, + [ + "2024-04-10" + ] + ], + [ + "2024-04-09", + 78, + 0, + [ + "2024-04-09" + ] + ], + [ + "2024-04-08", + 76, + 0, + [ + "2024-04-08" + ] + ], + [ + "2024-04-07", + 76, + 0, + [ + "2024-04-07" + ] + ], + [ + "2024-04-06", + 76, + 0, + [ + "2024-04-06" + ] + ], + [ + "2024-04-05", + 76, + 0, + [ + "2024-04-05" + ] + ], + [ + "2024-04-04", + 78, + 0, + [ + "2024-04-04" + ] + ], + [ + "2024-04-03", + 78, + 0, + [ + "2024-04-03" + ] + ], + [ + "2024-04-02", + 78, + 0, + [ + "2024-04-02" + ] + ], + [ + "2024-04-01", + 78, + 0, + [ + "2024-04-01" + ] + ], + [ + "2024-03-31", + 78, + 0, + [ + "2024-03-31" + ] + ], + [ + "2024-03-30", + 78, + 0, + [ + "2024-03-30" + ] + ], + [ + "2024-03-29", + 79, + 0, + [ + "2024-03-29" + ] + ], + [ + "2024-03-28", + 79, + 0, + [ + "2024-03-28" + ] + ], + [ + "2024-03-27", + 79, + 0, + [ + "2024-03-27" + ] + ], + [ + "2024-03-26", + 80, + 0, + [ + "2024-03-26" + ] + ], + [ + "2024-03-25", + 78, + 0, + [ + "2024-03-25" + ] + ], + [ + "2024-03-24", + 78, + 0, + [ + "2024-03-24" + ] + ] + ], + "periods": [ + { + "label": "2024-03-24", + "dates": [ + "2024-03-24" + ] + }, + { + "label": "2024-03-25", + "dates": [ + "2024-03-25" + ] + }, + { + "label": "2024-03-26", + "dates": [ + "2024-03-26" + ] + }, + { + "label": "2024-03-27", + "dates": [ + "2024-03-27" + ] + }, + { + "label": "2024-03-28", + "dates": [ + "2024-03-28" + ] + }, + { + "label": "2024-03-29", + "dates": [ + "2024-03-29" + ] + }, + { + "label": "2024-03-30", + "dates": [ + "2024-03-30" + ] + }, + { + "label": "2024-03-31", + "dates": [ + "2024-03-31" + ] + }, + { + "label": "2024-04-01", + "dates": [ + "2024-04-01" + ] + }, + { + "label": "2024-04-02", + "dates": [ + "2024-04-02" + ] + }, + { + "label": "2024-04-03", + "dates": [ + "2024-04-03" + ] + }, + { + "label": "2024-04-04", + "dates": [ + "2024-04-04" + ] + }, + { + "label": "2024-04-05", + "dates": [ + "2024-04-05" + ] + }, + { + "label": "2024-04-06", + "dates": [ + "2024-04-06" + ] + }, + { + "label": "2024-04-07", + "dates": [ + "2024-04-07" + ] + }, + { + "label": "2024-04-08", + "dates": [ + "2024-04-08" + ] + }, + { + "label": "2024-04-09", + "dates": [ + "2024-04-09" + ] + }, + { + "label": "2024-04-10", + "dates": [ + "2024-04-10" + ] + }, + { + "label": "2024-04-11", + "dates": [ + "2024-04-11" + ] + }, + { + "label": "2024-04-12", + "dates": [ + "2024-04-12" + ] + }, + { + "label": "2024-04-13", + "dates": [ + "2024-04-13" + ] + }, + { + "label": "2024-04-14", + "dates": [ + "2024-04-14" + ] + }, + { + "label": "2024-04-15", + "dates": [ + "2024-04-15" + ] + }, + { + "label": "2024-04-16", + "dates": [ + "2024-04-16" + ] + }, + { + "label": "2024-04-17", + "dates": [ + "2024-04-17" + ] + }, + { + "label": "2024-04-18", + "dates": [ + "2024-04-18" + ] + }, + { + "label": "2024-04-19", + "dates": [ + "2024-04-19" + ] + }, + { + "label": "2024-04-20", + "dates": [ + "2024-04-20" + ] + }, + { + "label": "2024-04-21", + "dates": [ + "2024-04-21" + ] + }, + { + "label": "2024-04-22", + "dates": [ + "2024-04-22" + ] + } + ], + "next_date_subscriber_total_before_change": 78, + "all_days": [ + "2024-03-23", + "2024-03-24", + "2024-03-25", + "2024-03-26", + "2024-03-27", + "2024-03-28", + "2024-03-29", + "2024-03-30", + "2024-03-31", + "2024-04-01", + "2024-04-02", + "2024-04-03", + "2024-04-04", + "2024-04-05", + "2024-04-06", + "2024-04-07", + "2024-04-08", + "2024-04-09", + "2024-04-10", + "2024-04-11", + "2024-04-12", + "2024-04-13", + "2024-04-14", + "2024-04-15", + "2024-04-16", + "2024-04-17", + "2024-04-18", + "2024-04-19", + "2024-04-20", + "2024-04-21", + "2024-04-22", + "2024-04-23" + ], + "utc_all_dates_subscriber_total_before_change": { + "2024-04-23": null, + "2024-04-22": 77, + "2024-04-21": 78, + "2024-04-20": 78, + "2024-04-19": 78, + "2024-04-18": 78, + "2024-04-17": 78, + "2024-04-16": 78, + "2024-04-15": 78, + "2024-04-14": 78, + "2024-04-13": 78, + "2024-04-12": 78, + "2024-04-11": 78, + "2024-04-10": 78, + "2024-04-09": 76, + "2024-04-08": 76, + "2024-04-07": 76, + "2024-04-06": 76, + "2024-04-05": 76, + "2024-04-04": 78, + "2024-04-03": 78, + "2024-04-02": 78, + "2024-04-01": 78, + "2024-03-31": 78, + "2024-03-30": 78, + "2024-03-29": 79, + "2024-03-28": 79, + "2024-03-27": 79, + "2024-03-26": 78, + "2024-03-25": 78, + "2024-03-24": 78, + "2024-03-23": 78 + }, + "subscription_count": 77, + "utc_period_dates_data": { + "2024-04-22": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "77", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": "-1", + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": "-1", + "hour_14": null, + "hour_15": "-1", + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": "-1", + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-04-21": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": "-1", + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": null, + "hour_15": "-2", + "hour_16": null, + "hour_17": null, + "hour_18": "-5", + "hour_19": "-3", + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": "-2" + }, + "2024-04-20": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": "-1", + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": "-1", + "hour_12": null, + "hour_13": null, + "hour_14": null, + "hour_15": "-1", + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-04-19": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": "-1", + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": "-2", + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": "-1", + "hour_15": null, + "hour_16": "-1", + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-04-18": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": "-1", + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": "-1", + "hour_11": null, + "hour_12": "-1", + "hour_13": null, + "hour_14": null, + "hour_15": null, + "hour_16": "-1", + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-04-17": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": "-1", + "hour_08": null, + "hour_09": "-2", + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": "-2", + "hour_15": "-1", + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-04-09": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "2", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": null, + "hour_15": "0", + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-04-04": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": null, + "hour_15": null, + "hour_16": "-1", + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-04-01": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": null, + "hour_15": null, + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": "-1", + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-03-31": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "78", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": null, + "hour_15": "-2", + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + }, + "2024-03-29": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "79", + "total": "0", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": null, + "hour_15": null, + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": "-2", + "hour_23": null + }, + "2024-03-26": { + "blog_id": "180619633", + "subscriber_type": "0", + "subscriber_total": "80", + "total": "2", + "hour_00": null, + "hour_01": null, + "hour_02": null, + "hour_03": null, + "hour_04": null, + "hour_05": null, + "hour_06": null, + "hour_07": null, + "hour_08": null, + "hour_09": null, + "hour_10": null, + "hour_11": null, + "hour_12": null, + "hour_13": null, + "hour_14": "2", + "hour_15": null, + "hour_16": null, + "hour_17": null, + "hour_18": null, + "hour_19": null, + "hour_20": null, + "hour_21": null, + "hour_22": null, + "hour_23": null + } + }, + "utc_period_dates_subscriber_total": { + "2024-04-22": 77, + "2024-04-21": 78, + "2024-04-20": 78, + "2024-04-19": 78, + "2024-04-18": 78, + "2024-04-17": 78, + "2024-04-09": 78, + "2024-04-04": 78, + "2024-04-01": 78, + "2024-03-31": 78, + "2024-03-29": 79, + "2024-03-26": 80 + }, + "timezone_all_dates_subscriber_total": { + "2024-04-22": 77, + "2024-04-21": 78, + "2024-04-20": 78, + "2024-04-19": 78, + "2024-04-18": 78, + "2024-04-17": 78, + "2024-04-09": 78, + "2024-04-04": 78, + "2024-04-01": 78, + "2024-03-31": 78, + "2024-03-29": 79, + "2024-03-26": 80 + }, + "timezone": 0, + "gmt_offset": "0", + "detail": { + "blog_id": "123", + "domain": "example.wordpress.com", + "path": "/", + "site_id": "1", + "registered": "2020-07-23 14:06:59", + "last_updated": "2024-04-22 22:57:22", + "public": "-1", + "archived": "0", + "mature": "0", + "spam": "0", + "deleted": "0", + "lang_id": "1", + "ds_blog": "45", + "ds_stats": "0", + "blogname": "Example", + "siteurl": "http://example.wordpress.com", + "post_count": "1877", + "home": "http://example.wordpress.com" + }, + "earliest_date": { + "date": "2024-03-24 00:00:00.000000", + "timezone_type": 1, + "timezone": "+00:00" + }, + "end_date": "2024-04-22 00:00:00" +} diff --git a/Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift b/Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift new file mode 100644 index 00000000..7c21c001 --- /dev/null +++ b/Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift @@ -0,0 +1,24 @@ +import XCTest +@testable import WordPressKit + +final class StatsSubscribersSummaryDataTests: XCTestCase { + func testEmailsSummaryDecoding() throws { + let json = getJSON("stats-subscribers") + + let summary = StatsSubscribersSummaryData(jsonDictionary: json) + XCTAssertNotNil(summary, "StatsSubscribersSummaryData not decoded as expected") + let history = summary!.history + let firstDay = history[0] + + XCTAssertEqual(firstDay.date, ISO8601DateFormatter().date(from: "2024-04-22")) + XCTAssertEqual(firstDay.count, 77) + } +} + +private extension StatsSubscribersSummaryDataTests { + func getJSON(_ fileName: String) -> [String: AnyObject] { + let path = Bundle(for: type(of: self)).path(forResource: fileName, ofType: "json")! + let data = try! Data(contentsOf: URL(fileURLWithPath: path)) + return try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: AnyObject] + } +} From 4dd23613859c907d74aee054da42e388bc35bd8a Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Tue, 23 Apr 2024 18:15:21 -0400 Subject: [PATCH 03/12] Fix subscriber history query --- .../Stats/StatsSubscribersSummaryData.swift | 23 +++++++++++++++---- .../Services/StatsServiceRemoteV2.swift | 5 ++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift index 515713b8..e10bbadc 100644 --- a/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift +++ b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift @@ -2,7 +2,11 @@ import Foundation import WordPressShared public struct StatsSubscribersSummaryData: Decodable, Equatable { - let history: [SubscriberData] + public let history: [SubscriberData] + + public init(history: [SubscriberData]) { + self.history = history + } private enum CodingKeys: String, CodingKey { case history = "data" @@ -14,8 +18,13 @@ public struct StatsSubscribersSummaryData: Decodable, Equatable { } public struct SubscriberData: Decodable, Equatable { - let date: Date - let count: Int + public let date: Date + public let count: Int + + public init(date: Date, count: Int) { + self.date = date + self.count = count + } private enum CodingKeys: Int, CodingKey { case date = 0 @@ -46,7 +55,11 @@ extension StatsSubscribersSummaryData { } } - public static func queryProperties(quantity: Int, unit: String) -> [String: String] { - return ["quantity": String(quantity), "unit": unit] + public static func queryProperties(quantity: Int, unit: StatsSubscribersSummaryData.Unit) -> [String: String] { + return ["quantity": String(quantity), "unit": unit.rawValue] + } + + public enum Unit: String { + case day = "day" } } diff --git a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift index 94778463..9699d82c 100644 --- a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift +++ b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift @@ -319,10 +319,11 @@ private extension StatsServiceRemoteV2 { // MARK: - Subscribers Data public extension StatsServiceRemoteV2 { - func getSubscribers(completion: @escaping ((Result) -> Void)) { + func getSubscribers(unit: StatsSubscribersSummaryData.Unit, + completion: @escaping ((Result) -> Void)) { let pathComponent = StatsSubscribersSummaryData.pathComponent let path = self.path(forEndpoint: "sites/\(siteID)/\(pathComponent)/", withVersion: ._1_1) - let properties = StatsSubscribersSummaryData.queryProperties(quantity: 30, unit: "day") as [String: AnyObject] + let properties = StatsSubscribersSummaryData.queryProperties(quantity: 30, unit: unit) as [String: AnyObject] wordPressComRESTAPI.get(path, parameters: properties, success: { [weak self] (response, _) in guard let self, From 0591961da12f20a257368f09ab93cf4c9b01b54b Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Tue, 23 Apr 2024 18:17:26 -0400 Subject: [PATCH 04/12] Reverted unnecessary change --- Package.resolved | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Package.resolved b/Package.resolved index 14fa1cb5..a25dc7e5 100644 --- a/Package.resolved +++ b/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", "state" : { - "revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0", - "version" : "1.8.2" + "revision" : "7892a123f7e8d0fe62f9f03728b17bbd4f94df5c", + "version" : "1.8.1" } }, { @@ -96,7 +96,7 @@ "location" : "https://github.com/wordpress-mobile/WordPress-iOS-Shared.git", "state" : { "branch" : "mokagio/swiftlint-read-as-dependency", - "revision" : "8b6afb494f64070b46e970337f541a3548999980" + "revision" : "422950b28f01d7cc11218e7d70a6cd65004d23ae" } }, { @@ -113,8 +113,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/jpsim/Yams.git", "state" : { - "revision" : "9234124cff5e22e178988c18d8b95a8ae8007f76", - "version" : "5.1.2" + "revision" : "8a835d918245ca22f36663dd3862138805d7f707", + "version" : "5.1.0" } } ], From 72dc8b55fcb3a7631650ef8a1712afac9cc97263 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Tue, 23 Apr 2024 21:13:04 -0400 Subject: [PATCH 05/12] Fixed subscriber data parsing --- .../Stats/StatsSubscribersSummaryData.swift | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift index e10bbadc..70b71f60 100644 --- a/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift +++ b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift @@ -7,15 +7,19 @@ public struct StatsSubscribersSummaryData: Decodable, Equatable { public init(history: [SubscriberData]) { self.history = history } +} - private enum CodingKeys: String, CodingKey { - case history = "data" +extension StatsSubscribersSummaryData { + public static var pathComponent: String { + return "stats/subscribers" } - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - history = try container.decode([SubscriberData].self, forKey: .history) - } + static var dateFormatter: DateFormatter = { + let df = DateFormatter() + df.locale = Locale(identifier: "en_US_POS") + df.dateFormat = "yyyy-MM-dd" + return df + }() public struct SubscriberData: Decodable, Equatable { public let date: Date @@ -25,34 +29,32 @@ public struct StatsSubscribersSummaryData: Decodable, Equatable { self.date = date self.count = count } + } - private enum CodingKeys: Int, CodingKey { - case date = 0 - case count = 1 + public init?(jsonDictionary: [String: AnyObject]) { + guard + let fields = jsonDictionary["fields"] as? [String], + let data = jsonDictionary["data"] as? [[Any]], + let dateIndex = fields.firstIndex(of: "period"), + let countIndex = fields.firstIndex(of: "subscribers") + else { + return nil } - public init(from decoder: any Decoder) throws { - var container = try decoder.unkeyedContainer() + let history: [SubscriberData?] = data.map { elements in + guard elements.indices.contains(dateIndex) && elements.indices.contains(countIndex), + let dateString = elements[dateIndex] as? String, + let date = StatsSubscribersSummaryData.dateFormatter.date(from: dateString), + let count = elements[countIndex] as? Int + else { + return nil + } - date = ISO8601DateFormatter().date(from: try container.decode(String.self)) ?? Date() - count = try container.decode(Int.self) + return SubscriberData(date: date, count: count) } - } -} -extension StatsSubscribersSummaryData { - public static var pathComponent: String { - return "stats/subscribers" - } - - public init?(jsonDictionary: [String: AnyObject]) { - do { - let jsonData = try JSONSerialization.data(withJSONObject: jsonDictionary, options: []) - let decoder = JSONDecoder.apiDecoder - self = try decoder.decode(Self.self, from: jsonData) - } catch { - return nil - } + let sorted = history.compactMap { $0 }.sorted(by: { $0.date.compare($1.date) == .orderedAscending }) + self = .init(history: sorted) } public static func queryProperties(quantity: Int, unit: StatsSubscribersSummaryData.Unit) -> [String: String] { From a7ccb2e6810eb9d546f9a85cd30057be88a9760b Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Tue, 23 Apr 2024 21:15:59 -0400 Subject: [PATCH 06/12] Reverted changes to Package.resolved --- Package.resolved | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Package.resolved b/Package.resolved index 14fa1cb5..a25dc7e5 100644 --- a/Package.resolved +++ b/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", "state" : { - "revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0", - "version" : "1.8.2" + "revision" : "7892a123f7e8d0fe62f9f03728b17bbd4f94df5c", + "version" : "1.8.1" } }, { @@ -96,7 +96,7 @@ "location" : "https://github.com/wordpress-mobile/WordPress-iOS-Shared.git", "state" : { "branch" : "mokagio/swiftlint-read-as-dependency", - "revision" : "8b6afb494f64070b46e970337f541a3548999980" + "revision" : "422950b28f01d7cc11218e7d70a6cd65004d23ae" } }, { @@ -113,8 +113,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/jpsim/Yams.git", "state" : { - "revision" : "9234124cff5e22e178988c18d8b95a8ae8007f76", - "version" : "5.1.2" + "revision" : "8a835d918245ca22f36663dd3862138805d7f707", + "version" : "5.1.0" } } ], From 5a7d637de6f7ac173a3e8c34f33203d3932affb2 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Tue, 23 Apr 2024 21:48:06 -0400 Subject: [PATCH 07/12] added missing files, fixed tests --- .../WordPressKit/Services/StatsServiceRemoteV2.swift | 2 +- .../Stats/V2/StatsSubscribersSummaryDataTests.swift | 6 +++--- WordPressKit.xcodeproj/project.pbxproj | 12 ++++++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift index 7af51598..9699d82c 100644 --- a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift +++ b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift @@ -324,7 +324,7 @@ public extension StatsServiceRemoteV2 { let pathComponent = StatsSubscribersSummaryData.pathComponent let path = self.path(forEndpoint: "sites/\(siteID)/\(pathComponent)/", withVersion: ._1_1) let properties = StatsSubscribersSummaryData.queryProperties(quantity: 30, unit: unit) as [String: AnyObject] - + wordPressComRESTAPI.get(path, parameters: properties, success: { [weak self] (response, _) in guard let self, let jsonResponse = response as? [String: AnyObject], diff --git a/Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift b/Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift index 7c21c001..b994c09e 100644 --- a/Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift +++ b/Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift @@ -8,10 +8,10 @@ final class StatsSubscribersSummaryDataTests: XCTestCase { let summary = StatsSubscribersSummaryData(jsonDictionary: json) XCTAssertNotNil(summary, "StatsSubscribersSummaryData not decoded as expected") let history = summary!.history - let firstDay = history[0] + let mostRecentDay = history.last! - XCTAssertEqual(firstDay.date, ISO8601DateFormatter().date(from: "2024-04-22")) - XCTAssertEqual(firstDay.count, 77) + XCTAssertEqual(mostRecentDay.date, StatsSubscribersSummaryData.dateFormatter.date(from: "2024-04-22")) + XCTAssertEqual(mostRecentDay.count, 77) } } diff --git a/WordPressKit.xcodeproj/project.pbxproj b/WordPressKit.xcodeproj/project.pbxproj index d9e17c81..1b62cab2 100644 --- a/WordPressKit.xcodeproj/project.pbxproj +++ b/WordPressKit.xcodeproj/project.pbxproj @@ -573,6 +573,9 @@ B04D8C0B2BB7895A002717A2 /* stats-insight.json in Resources */ = {isa = PBXBuildFile; fileRef = B04D8C002BB7895A002717A2 /* stats-insight.json */; }; B04D8C0C2BB7895A002717A2 /* stats.json in Resources */ = {isa = PBXBuildFile; fileRef = B04D8C012BB7895A002717A2 /* stats.json */; }; B04D8C0D2BB7895A002717A2 /* StatsInsightDecodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B04D8C032BB7895A002717A2 /* StatsInsightDecodingTests.swift */; }; + B0E02FBA2BD899F600A0A561 /* StatsSubscribersSummaryDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0E02FB62BD8999B00A0A561 /* StatsSubscribersSummaryDataTests.swift */; }; + B0E02FBB2BD89A0400A0A561 /* stats-subscribers.json in Resources */ = {isa = PBXBuildFile; fileRef = B0E02FB82BD899C500A0A561 /* stats-subscribers.json */; }; + B0E02FBD2BD89AAB00A0A561 /* StatsSubscribersSummaryData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0E02FBC2BD89AAB00A0A561 /* StatsSubscribersSummaryData.swift */; }; B5969E1C20A49AC4005E9DF1 /* NSString+MD5.h in Headers */ = {isa = PBXBuildFile; fileRef = B5969E1920A49AC4005E9DF1 /* NSString+MD5.h */; settings = {ATTRIBUTES = (Public, ); }; }; B5969E1D20A49AC4005E9DF1 /* NSString+MD5.m in Sources */ = {isa = PBXBuildFile; fileRef = B5969E1A20A49AC4005E9DF1 /* NSString+MD5.m */; }; B5A4822B20AC6C0B009D95F6 /* WPKitLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A4822A20AC6C0B009D95F6 /* WPKitLogging.swift */; }; @@ -1329,6 +1332,9 @@ B04D8C002BB7895A002717A2 /* stats-insight.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "stats-insight.json"; sourceTree = ""; }; B04D8C012BB7895A002717A2 /* stats.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = stats.json; sourceTree = ""; }; B04D8C032BB7895A002717A2 /* StatsInsightDecodingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatsInsightDecodingTests.swift; sourceTree = ""; }; + B0E02FB62BD8999B00A0A561 /* StatsSubscribersSummaryDataTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatsSubscribersSummaryDataTests.swift; sourceTree = ""; }; + B0E02FB82BD899C500A0A561 /* stats-subscribers.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "stats-subscribers.json"; sourceTree = ""; }; + B0E02FBC2BD89AAB00A0A561 /* StatsSubscribersSummaryData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatsSubscribersSummaryData.swift; sourceTree = ""; }; B5969E1920A49AC4005E9DF1 /* NSString+MD5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+MD5.h"; sourceTree = ""; }; B5969E1A20A49AC4005E9DF1 /* NSString+MD5.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+MD5.m"; sourceTree = ""; }; B5A4822A20AC6C0B009D95F6 /* WPKitLogging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WPKitLogging.swift; sourceTree = ""; }; @@ -1683,6 +1689,7 @@ 3FE2E9362BB10EC7002CA2E1 /* Stats */ = { isa = PBXGroup; children = ( + B0E02FBC2BD89AAB00A0A561 /* StatsSubscribersSummaryData.swift */, 019C5B8A2BD59CE000A69DB0 /* Emails */, 404057C3221B30140060250C /* Time Interval */, 40414061220F9F2800CF7C5B /* Insights */, @@ -2267,6 +2274,7 @@ 93BD27421EE73384002BB00B /* Mock Data */ = { isa = PBXGroup; children = ( + B0E02FB82BD899C500A0A561 /* stats-subscribers.json */, F4B0F4742ACB4176003ABC61 /* Domains */, 937250EB267A15060086075F /* stats-referrer-mark-as-spam.json */, 465F8892263B094900F4C950 /* BlockEditorSettings */, @@ -2628,6 +2636,7 @@ F3FF8A1C279C86F600E5C90F /* V2 */ = { isa = PBXGroup; children = ( + B0E02FB62BD8999B00A0A561 /* StatsSubscribersSummaryDataTests.swift */, 019C5B882BD59C7800A69DB0 /* Emails */, 01383F7D2BD5542300496B76 /* TimeInterval */, F3FF8A1D279C86FE00E5C90F /* Insights */, @@ -2866,6 +2875,7 @@ files = ( 01438D392B6A361B0097D60A /* stats-summary.json in Resources */, 01438D362B6A31540097D60A /* stats-visits-month-unit-week.json in Resources */, + B0E02FBB2BD89A0400A0A561 /* stats-subscribers.json in Resources */, 32A29A1F236BE4CC009488C2 /* post-autosave-mapping-success.json in Resources */, CEAD827B25E421DE00758DF2 /* reader-post-comments-subscribe-failure.json in Resources */, 7403A2FC1EF06FEB00DED7DC /* me-settings-change-lastname-success.json in Resources */, @@ -3384,6 +3394,7 @@ 404057DA221C9D560060250C /* StatsTopReferrersTimeIntervalData.swift in Sources */, 826016F11F9FA13A00533B6C /* ActivityServiceRemote.swift in Sources */, 74BA04FA1F06DC3900ED5CD8 /* RemoteComment.m in Sources */, + B0E02FBD2BD89AAB00A0A561 /* StatsSubscribersSummaryData.swift in Sources */, 40F98809221AC88700B7B369 /* StatsPostingStreakInsight.swift in Sources */, 465F889E263B0C5500F4C950 /* BlockEditorSettingsServiceRemote.swift in Sources */, 32FC1D29255C91ED00CD0A7B /* JetpackScanServiceRemote.swift in Sources */, @@ -3538,6 +3549,7 @@ B04D8C0D2BB7895A002717A2 /* StatsInsightDecodingTests.swift in Sources */, 9F3E0BAE20873836009CB5BA /* ReaderTopicServiceRemoteTest+Subscriptions.swift in Sources */, 4A05E79E2B30F3C500C25E3B /* URLRequest+HTTPBodyText.swift in Sources */, + B0E02FBA2BD899F600A0A561 /* StatsSubscribersSummaryDataTests.swift in Sources */, BA0637ED2492382200AF8419 /* PluginStateTests.swift in Sources */, 7328420621CD798A00126755 /* WordPressComServiceRemoteTests+SiteCreation.swift in Sources */, FACBDD3825ECB4480026705B /* ReaderPostServiceRemote+RelatedPostsTests.swift in Sources */, From 5d1359cecbab3667a66f21cdbd54d896fc17eede Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Tue, 23 Apr 2024 21:53:25 -0400 Subject: [PATCH 08/12] fix linter issue --- Sources/WordPressKit/Services/StatsServiceRemoteV2.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift index 9699d82c..656fc524 100644 --- a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift +++ b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift @@ -326,8 +326,7 @@ public extension StatsServiceRemoteV2 { let properties = StatsSubscribersSummaryData.queryProperties(quantity: 30, unit: unit) as [String: AnyObject] wordPressComRESTAPI.get(path, parameters: properties, success: { [weak self] (response, _) in - guard let self, - let jsonResponse = response as? [String: AnyObject], + guard let jsonResponse = response as? [String: AnyObject], let subscribersSummaryData = StatsSubscribersSummaryData(jsonDictionary: jsonResponse) else { completion(.failure(ResponseError.decodingFailure)) From 3197069de70b1e2b57ca8f399896edfdd908fe4a Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Tue, 23 Apr 2024 21:58:33 -0400 Subject: [PATCH 09/12] fix another linter issue --- Sources/WordPressKit/Services/StatsServiceRemoteV2.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift index 656fc524..d949a8c3 100644 --- a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift +++ b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift @@ -325,7 +325,7 @@ public extension StatsServiceRemoteV2 { let path = self.path(forEndpoint: "sites/\(siteID)/\(pathComponent)/", withVersion: ._1_1) let properties = StatsSubscribersSummaryData.queryProperties(quantity: 30, unit: unit) as [String: AnyObject] - wordPressComRESTAPI.get(path, parameters: properties, success: { [weak self] (response, _) in + wordPressComRESTAPI.get(path, parameters: properties, success: { (response, _) in guard let jsonResponse = response as? [String: AnyObject], let subscribersSummaryData = StatsSubscribersSummaryData(jsonDictionary: jsonResponse) else { From 7d6e0e705c1166511acee2202a816573921a4797 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Wed, 24 Apr 2024 10:09:39 +0300 Subject: [PATCH 10/12] Conform to StatsTimeIntervalData --- .../Stats/StatsSubscribersSummaryData.swift | 43 +++++++++++++------ .../Services/StatsServiceRemoteV2.swift | 24 ----------- .../V2/StatsSubscribersSummaryDataTests.swift | 2 +- 3 files changed, 31 insertions(+), 38 deletions(-) diff --git a/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift index 70b71f60..94a15faa 100644 --- a/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift +++ b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift @@ -1,15 +1,19 @@ import Foundation import WordPressShared -public struct StatsSubscribersSummaryData: Decodable, Equatable { +public struct StatsSubscribersSummaryData: Equatable { public let history: [SubscriberData] + public let period: StatsPeriodUnit + public let periodEndDate: Date - public init(history: [SubscriberData]) { + public init(history: [SubscriberData], period: StatsPeriodUnit, periodEndDate: Date) { self.history = history + self.period = period + self.periodEndDate = periodEndDate } } -extension StatsSubscribersSummaryData { +extension StatsSubscribersSummaryData: StatsTimeIntervalData { public static var pathComponent: String { return "stats/subscribers" } @@ -21,7 +25,14 @@ extension StatsSubscribersSummaryData { return df }() - public struct SubscriberData: Decodable, Equatable { + static var weeksDateFormatter: DateFormatter = { + let df = DateFormatter() + df.locale = Locale(identifier: "en_US_POS") + df.dateFormat = "yyyy'W'MM'W'dd" + return df + }() + + public struct SubscriberData: Equatable { public let date: Date public let count: Int @@ -31,7 +42,7 @@ extension StatsSubscribersSummaryData { } } - public init?(jsonDictionary: [String: AnyObject]) { + public init?(date: Date, period: StatsPeriodUnit, jsonDictionary: [String: AnyObject]) { guard let fields = jsonDictionary["fields"] as? [String], let data = jsonDictionary["data"] as? [[Any]], @@ -43,9 +54,9 @@ extension StatsSubscribersSummaryData { let history: [SubscriberData?] = data.map { elements in guard elements.indices.contains(dateIndex) && elements.indices.contains(countIndex), - let dateString = elements[dateIndex] as? String, - let date = StatsSubscribersSummaryData.dateFormatter.date(from: dateString), - let count = elements[countIndex] as? Int + let dateString = elements[dateIndex] as? String, + let date = StatsSubscribersSummaryData.parsedDate(from: dateString, for: period), + let count = elements[countIndex] as? Int else { return nil } @@ -54,14 +65,20 @@ extension StatsSubscribersSummaryData { } let sorted = history.compactMap { $0 }.sorted(by: { $0.date.compare($1.date) == .orderedAscending }) - self = .init(history: sorted) + + self = .init(history: sorted, period: period, periodEndDate: date) } - public static func queryProperties(quantity: Int, unit: StatsSubscribersSummaryData.Unit) -> [String: String] { - return ["quantity": String(quantity), "unit": unit.rawValue] + private static func parsedDate(from dateString: String, for period: StatsPeriodUnit) -> Date? { + switch period { + case .week: + return self.weeksDateFormatter.date(from: dateString) + case .day, .month, .year: + return self.dateFormatter.date(from: dateString) + } } - public enum Unit: String { - case day = "day" + public static func queryProperties(with date: Date, period: StatsPeriodUnit, maxCount: Int) -> [String: String] { + return ["quantity": String(maxCount), "unit": period.stringValue] } } diff --git a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift index d949a8c3..be9d554d 100644 --- a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift +++ b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift @@ -316,30 +316,6 @@ private extension StatsServiceRemoteV2 { } } -// MARK: - Subscribers Data - -public extension StatsServiceRemoteV2 { - func getSubscribers(unit: StatsSubscribersSummaryData.Unit, - completion: @escaping ((Result) -> Void)) { - let pathComponent = StatsSubscribersSummaryData.pathComponent - let path = self.path(forEndpoint: "sites/\(siteID)/\(pathComponent)/", withVersion: ._1_1) - let properties = StatsSubscribersSummaryData.queryProperties(quantity: 30, unit: unit) as [String: AnyObject] - - wordPressComRESTAPI.get(path, parameters: properties, success: { (response, _) in - guard let jsonResponse = response as? [String: AnyObject], - let subscribersSummaryData = StatsSubscribersSummaryData(jsonDictionary: jsonResponse) - else { - completion(.failure(ResponseError.decodingFailure)) - return - } - - completion(.success(subscribersSummaryData)) - }, failure: { (error, _) in - completion(.failure(error)) - }) - } -} - // MARK: - Emails Summary public extension StatsServiceRemoteV2 { diff --git a/Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift b/Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift index b994c09e..71a0ac3e 100644 --- a/Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift +++ b/Tests/WordPressKitTests/Tests/Models/Stats/V2/StatsSubscribersSummaryDataTests.swift @@ -5,7 +5,7 @@ final class StatsSubscribersSummaryDataTests: XCTestCase { func testEmailsSummaryDecoding() throws { let json = getJSON("stats-subscribers") - let summary = StatsSubscribersSummaryData(jsonDictionary: json) + let summary = StatsSubscribersSummaryData(date: Date(), period: .day, jsonDictionary: json) XCTAssertNotNil(summary, "StatsSubscribersSummaryData not decoded as expected") let history = summary!.history let mostRecentDay = history.last! From 1b312425f37a0acee9027494f30adf8af69a6ce7 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Thu, 25 Apr 2024 12:37:56 -0400 Subject: [PATCH 11/12] Made StatsPeriodUnit stringValue public --- .../WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift | 2 +- Sources/WordPressKit/Services/StatsServiceRemoteV2.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift index 94a15faa..54e8d82d 100644 --- a/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift +++ b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift @@ -64,7 +64,7 @@ extension StatsSubscribersSummaryData: StatsTimeIntervalData { return SubscriberData(date: date, count: count) } - let sorted = history.compactMap { $0 }.sorted(by: { $0.date.compare($1.date) == .orderedAscending }) + let sorted = history.compactMap { $0 }.sorted { $0.date < $1.date } self = .init(history: sorted, period: period, periodEndDate: date) } diff --git a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift index be9d554d..6cf675a6 100644 --- a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift +++ b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift @@ -395,7 +395,7 @@ extension StatsTimeIntervalData { // We'll bring `StatsPeriodUnit` into this file when the "old" `WPStatsServiceRemote` gets removed. // For now we can piggy-back off the old type and add this as an extension. -extension StatsPeriodUnit { +public extension StatsPeriodUnit { var stringValue: String { switch self { case .day: From 7f47dbc791d59d0b5ad8c958441c3060b784ec43 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Thu, 25 Apr 2024 19:20:27 -0400 Subject: [PATCH 12/12] Fix subscriber fetch new sites --- .../Models/Stats/StatsSubscribersSummaryData.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift index 54e8d82d..5ed9ee16 100644 --- a/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift +++ b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift @@ -55,12 +55,13 @@ extension StatsSubscribersSummaryData: StatsTimeIntervalData { let history: [SubscriberData?] = data.map { elements in guard elements.indices.contains(dateIndex) && elements.indices.contains(countIndex), let dateString = elements[dateIndex] as? String, - let date = StatsSubscribersSummaryData.parsedDate(from: dateString, for: period), - let count = elements[countIndex] as? Int + let date = StatsSubscribersSummaryData.parsedDate(from: dateString, for: period) else { return nil } + let count = elements[countIndex] as? Int ?? 0 + return SubscriberData(date: date, count: count) }