From 402557fec20dbf1bc76d3c6f5470183074897111 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Wed, 5 Sep 2018 15:59:09 -0500 Subject: [PATCH 1/8] Added TopEarnerStats + TopEarnerStatsItem to Storage fmwk --- .../Model/Stats/TopEarnerStats.swift | 18 +-- .../Mapper/TopEarnerStatsMapperTests.swift | 8 +- Storage/Storage.xcodeproj/project.pbxproj | 22 +++- Storage/Storage/Model/MIGRATIONS.md | 8 ++ .../Model/TopEarnerStats+CoreDataClass.swift | 7 ++ .../TopEarnerStats+CoreDataProperties.swift | 32 +++++ .../TopEarnerStatsItem+CoreDataClass.swift | 7 ++ ...opEarnerStatsItem+CoreDataProperties.swift | 19 +++ .../.xccurrentversion | 8 ++ .../Model 2.xcdatamodel/contents | 118 ++++++++++++++++++ Yosemite/Yosemite/Model/Model.swift | 4 + 11 files changed, 237 insertions(+), 14 deletions(-) create mode 100644 Storage/Storage/Model/MIGRATIONS.md create mode 100644 Storage/Storage/Model/TopEarnerStats+CoreDataClass.swift create mode 100644 Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift create mode 100644 Storage/Storage/Model/TopEarnerStatsItem+CoreDataClass.swift create mode 100644 Storage/Storage/Model/TopEarnerStatsItem+CoreDataProperties.swift create mode 100644 Storage/Storage/Model/WooCommerce.xcdatamodeld/.xccurrentversion create mode 100644 Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents diff --git a/Networking/Networking/Model/Stats/TopEarnerStats.swift b/Networking/Networking/Model/Stats/TopEarnerStats.swift index ff0930d27ca..30511479243 100644 --- a/Networking/Networking/Model/Stats/TopEarnerStats.swift +++ b/Networking/Networking/Model/Stats/TopEarnerStats.swift @@ -4,7 +4,7 @@ import Foundation /// Represents Top Earner (aka top performer) stats over a specific period. /// public struct TopEarnerStats: Decodable { - public let date: String + public let period: String public let granularity: StatGranularity public let limit: String public let items: [TopEarnerStatsItem]? @@ -15,19 +15,19 @@ public struct TopEarnerStats: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let date = try container.decode(String.self, forKey: .date) + let period = try container.decode(String.self, forKey: .period) let granularity = try container.decode(StatGranularity.self, forKey: .unit) let limit = try container.decode(String.self, forKey: .limit) let items = try container.decode([TopEarnerStatsItem].self, forKey: .items) - self.init(date: date, granularity: granularity, limit: limit, items: items) + self.init(period: period, granularity: granularity, limit: limit, items: items) } /// TopEarnerStats struct initializer. /// - public init(date: String, granularity: StatGranularity, limit: String, items: [TopEarnerStatsItem]?) { - self.date = date + public init(period: String, granularity: StatGranularity, limit: String, items: [TopEarnerStatsItem]?) { + self.period = period self.granularity = granularity self.limit = limit self.items = items @@ -39,7 +39,7 @@ public struct TopEarnerStats: Decodable { /// private extension TopEarnerStats { enum CodingKeys: String, CodingKey { - case date = "date" + case period = "date" case unit = "unit" case limit = "limit" case items = "data" @@ -51,14 +51,14 @@ private extension TopEarnerStats { // extension TopEarnerStats: Comparable { public static func == (lhs: TopEarnerStats, rhs: TopEarnerStats) -> Bool { - return lhs.date == rhs.date && + return lhs.period == rhs.period && lhs.granularity == rhs.granularity && lhs.limit == rhs.limit && lhs.items == rhs.items } public static func < (lhs: TopEarnerStats, rhs: TopEarnerStats) -> Bool { - return lhs.date < rhs.date || - (lhs.date == rhs.date && lhs.limit < rhs.limit) + return lhs.period < rhs.period || + (lhs.period == rhs.period && lhs.limit < rhs.limit) } } diff --git a/Networking/NetworkingTests/Mapper/TopEarnerStatsMapperTests.swift b/Networking/NetworkingTests/Mapper/TopEarnerStatsMapperTests.swift index e3deb7a0ee3..394241060c6 100644 --- a/Networking/NetworkingTests/Mapper/TopEarnerStatsMapperTests.swift +++ b/Networking/NetworkingTests/Mapper/TopEarnerStatsMapperTests.swift @@ -15,7 +15,7 @@ class TopEarnerStatsMapperTests: XCTestCase { } XCTAssertEqual(dayStats.granularity, .day) - XCTAssertEqual(dayStats.date, "2018-06-08") + XCTAssertEqual(dayStats.period, "2018-06-08") XCTAssertEqual(dayStats.limit, "5") XCTAssertEqual(dayStats.items!.count, 1) @@ -38,7 +38,7 @@ class TopEarnerStatsMapperTests: XCTestCase { } XCTAssertEqual(weekStats.granularity, .week) - XCTAssertEqual(weekStats.date, "2018-W12") + XCTAssertEqual(weekStats.period, "2018-W12") XCTAssertEqual(weekStats.limit, "5") XCTAssertEqual(weekStats.items!.count, 3) @@ -70,7 +70,7 @@ class TopEarnerStatsMapperTests: XCTestCase { } XCTAssertEqual(monthStats.granularity, .month) - XCTAssertEqual(monthStats.date, "2018-08") + XCTAssertEqual(monthStats.period, "2018-08") XCTAssertEqual(monthStats.limit, "5") XCTAssertEqual(monthStats.items!.count, 5) @@ -102,7 +102,7 @@ class TopEarnerStatsMapperTests: XCTestCase { } XCTAssertEqual(yearStats.granularity, .year) - XCTAssertEqual(yearStats.date, "2018") + XCTAssertEqual(yearStats.period, "2018") XCTAssertEqual(yearStats.limit, "5") XCTAssertEqual(yearStats.items!.count, 4) diff --git a/Storage/Storage.xcodeproj/project.pbxproj b/Storage/Storage.xcodeproj/project.pbxproj index 7945972568c..dc07f67ff6b 100644 --- a/Storage/Storage.xcodeproj/project.pbxproj +++ b/Storage/Storage.xcodeproj/project.pbxproj @@ -15,6 +15,10 @@ 7426A05120F69D00002A4E07 /* OrderCoupon+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7426A04F20F69D00002A4E07 /* OrderCoupon+CoreDataProperties.swift */; }; 7426A05420F69DA4002A4E07 /* OrderItem+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7426A05220F69DA4002A4E07 /* OrderItem+CoreDataClass.swift */; }; 7426A05520F69DA4002A4E07 /* OrderItem+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7426A05320F69DA4002A4E07 /* OrderItem+CoreDataProperties.swift */; }; + 746A9D21214078080013F6FF /* TopEarnerStats+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 746A9D1D214078080013F6FF /* TopEarnerStats+CoreDataClass.swift */; }; + 746A9D22214078080013F6FF /* TopEarnerStats+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 746A9D1E214078080013F6FF /* TopEarnerStats+CoreDataProperties.swift */; }; + 746A9D23214078080013F6FF /* TopEarnerStatsItem+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 746A9D1F214078080013F6FF /* TopEarnerStatsItem+CoreDataClass.swift */; }; + 746A9D24214078080013F6FF /* TopEarnerStatsItem+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 746A9D20214078080013F6FF /* TopEarnerStatsItem+CoreDataProperties.swift */; }; 74B7D6AD20F90CBB002667AC /* OrderNote+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74B7D6AB20F90CBB002667AC /* OrderNote+CoreDataClass.swift */; }; 74B7D6AE20F90CBB002667AC /* OrderNote+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74B7D6AC20F90CBB002667AC /* OrderNote+CoreDataProperties.swift */; }; B505255420EE6914008090F5 /* StorageType+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505255320EE6914008090F5 /* StorageType+Extensions.swift */; }; @@ -59,6 +63,12 @@ 7426A04F20F69D00002A4E07 /* OrderCoupon+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderCoupon+CoreDataProperties.swift"; sourceTree = ""; }; 7426A05220F69DA4002A4E07 /* OrderItem+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderItem+CoreDataClass.swift"; sourceTree = ""; }; 7426A05320F69DA4002A4E07 /* OrderItem+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderItem+CoreDataProperties.swift"; sourceTree = ""; }; + 746A9D12214071EB0013F6FF /* MIGRATIONS.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = MIGRATIONS.md; sourceTree = ""; }; + 746A9D14214071F90013F6FF /* Model 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Model 2.xcdatamodel"; sourceTree = ""; }; + 746A9D1D214078080013F6FF /* TopEarnerStats+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TopEarnerStats+CoreDataClass.swift"; sourceTree = ""; }; + 746A9D1E214078080013F6FF /* TopEarnerStats+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TopEarnerStats+CoreDataProperties.swift"; sourceTree = ""; }; + 746A9D1F214078080013F6FF /* TopEarnerStatsItem+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TopEarnerStatsItem+CoreDataClass.swift"; sourceTree = ""; }; + 746A9D20214078080013F6FF /* TopEarnerStatsItem+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TopEarnerStatsItem+CoreDataProperties.swift"; sourceTree = ""; }; 74B7D6AB20F90CBB002667AC /* OrderNote+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderNote+CoreDataClass.swift"; sourceTree = ""; }; 74B7D6AC20F90CBB002667AC /* OrderNote+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderNote+CoreDataProperties.swift"; sourceTree = ""; }; 7C81935EDD982072BBDCC837 /* Pods-Storage.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Storage.release.xcconfig"; path = "../Pods/Target Support Files/Pods-Storage/Pods-Storage.release.xcconfig"; sourceTree = ""; }; @@ -236,6 +246,7 @@ B59E11D720A9CFF3004121A4 /* Model */ = { isa = PBXGroup; children = ( + 746A9D12214071EB0013F6FF /* MIGRATIONS.md */, B59E11D820A9D00C004121A4 /* WooCommerce.xcdatamodeld */, B505F6D920BEEA3200BB1B69 /* Account+CoreDataClass.swift */, B505F6D820BEEA3100BB1B69 /* Account+CoreDataProperties.swift */, @@ -249,6 +260,10 @@ 7426A05320F69DA4002A4E07 /* OrderItem+CoreDataProperties.swift */, 74B7D6AB20F90CBB002667AC /* OrderNote+CoreDataClass.swift */, 74B7D6AC20F90CBB002667AC /* OrderNote+CoreDataProperties.swift */, + 746A9D1D214078080013F6FF /* TopEarnerStats+CoreDataClass.swift */, + 746A9D1E214078080013F6FF /* TopEarnerStats+CoreDataProperties.swift */, + 746A9D1F214078080013F6FF /* TopEarnerStatsItem+CoreDataClass.swift */, + 746A9D20214078080013F6FF /* TopEarnerStatsItem+CoreDataProperties.swift */, ); path = Model; sourceTree = ""; @@ -441,17 +456,21 @@ 7426A05520F69DA4002A4E07 /* OrderItem+CoreDataProperties.swift in Sources */, 7426A04720F68F27002A4E07 /* Order+CoreDataClass.swift in Sources */, B54CA5BD20A4BD3B00F38CD1 /* NSManagedObjectContext+Storage.swift in Sources */, + 746A9D21214078080013F6FF /* TopEarnerStats+CoreDataClass.swift in Sources */, 74B7D6AD20F90CBB002667AC /* OrderNote+CoreDataClass.swift in Sources */, B52B0F7920AA287C00477698 /* StorageManagerType.swift in Sources */, 7426A05420F69DA4002A4E07 /* OrderItem+CoreDataClass.swift in Sources */, B505F6E020BEEA8100BB1B69 /* StorageType.swift in Sources */, + 746A9D22214078080013F6FF /* TopEarnerStats+CoreDataProperties.swift in Sources */, B54CA5C920A4C17800F38CD1 /* NSObject+Storage.swift in Sources */, B505F6DA20BEEA3200BB1B69 /* Account+CoreDataProperties.swift in Sources */, 74B7D6AE20F90CBB002667AC /* OrderNote+CoreDataProperties.swift in Sources */, B505255420EE6914008090F5 /* StorageType+Extensions.swift in Sources */, B52B0F7B20AA28A800477698 /* Object.swift in Sources */, 7426A05020F69D00002A4E07 /* OrderCoupon+CoreDataClass.swift in Sources */, + 746A9D23214078080013F6FF /* TopEarnerStatsItem+CoreDataClass.swift in Sources */, B505F6DE20BEEA4F00BB1B69 /* CoreDataManager.swift in Sources */, + 746A9D24214078080013F6FF /* TopEarnerStatsItem+CoreDataProperties.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -742,9 +761,10 @@ B59E11D820A9D00C004121A4 /* WooCommerce.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + 746A9D14214071F90013F6FF /* Model 2.xcdatamodel */, B59E11D920A9D00C004121A4 /* Model.xcdatamodel */, ); - currentVersion = B59E11D920A9D00C004121A4 /* Model.xcdatamodel */; + currentVersion = 746A9D14214071F90013F6FF /* Model 2.xcdatamodel */; path = WooCommerce.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/Storage/Storage/Model/MIGRATIONS.md b/Storage/Storage/Model/MIGRATIONS.md new file mode 100644 index 00000000000..eedd491a1c6 --- /dev/null +++ b/Storage/Storage/Model/MIGRATIONS.md @@ -0,0 +1,8 @@ +# Core Data Migrations + +This file documents changes in the extensions data model. Please explain any changes to the data model as well as any custom migrations. + +## Model 2 +- @bummytime 2018-09-05 +- Added new entity: `TopEarnerStats`, to encapsulate all of the top earner stats for a given site +- Added new entity: `TopEarnerStatsItem`, to encapsulate all the top earner stats for a specific product diff --git a/Storage/Storage/Model/TopEarnerStats+CoreDataClass.swift b/Storage/Storage/Model/TopEarnerStats+CoreDataClass.swift new file mode 100644 index 00000000000..c5d2ba3e48f --- /dev/null +++ b/Storage/Storage/Model/TopEarnerStats+CoreDataClass.swift @@ -0,0 +1,7 @@ +import Foundation +import CoreData + +@objc(TopEarnerStats) +public class TopEarnerStats: NSManagedObject { + +} diff --git a/Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift b/Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift new file mode 100644 index 00000000000..d2e14414ecc --- /dev/null +++ b/Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift @@ -0,0 +1,32 @@ +import Foundation +import CoreData + + +extension TopEarnerStats { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "TopEarnerStats") + } + + @NSManaged public var period: String? + @NSManaged public var granularity: String? + @NSManaged public var limit: String? + @NSManaged public var lastUpdated: Date? + @NSManaged public var items: Set? +} + +// MARK: Generated accessors for items +extension TopEarnerStats { + + @objc(addItemsObject:) + @NSManaged public func addToItems(_ value: TopEarnerStatsItem) + + @objc(removeItemsObject:) + @NSManaged public func removeFromItems(_ value: TopEarnerStatsItem) + + @objc(addItems:) + @NSManaged public func addToItems(_ values: NSSet) + + @objc(removeItems:) + @NSManaged public func removeFromItems(_ values: NSSet) +} diff --git a/Storage/Storage/Model/TopEarnerStatsItem+CoreDataClass.swift b/Storage/Storage/Model/TopEarnerStatsItem+CoreDataClass.swift new file mode 100644 index 00000000000..74624a87b39 --- /dev/null +++ b/Storage/Storage/Model/TopEarnerStatsItem+CoreDataClass.swift @@ -0,0 +1,7 @@ +import Foundation +import CoreData + +@objc(TopEarnerStatsItem) +public class TopEarnerStatsItem: NSManagedObject { + +} diff --git a/Storage/Storage/Model/TopEarnerStatsItem+CoreDataProperties.swift b/Storage/Storage/Model/TopEarnerStatsItem+CoreDataProperties.swift new file mode 100644 index 00000000000..06a790c631b --- /dev/null +++ b/Storage/Storage/Model/TopEarnerStatsItem+CoreDataProperties.swift @@ -0,0 +1,19 @@ +import Foundation +import CoreData + + +extension TopEarnerStatsItem { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "TopEarnerStatsItem") + } + + @NSManaged public var productID: Int64 + @NSManaged public var productName: String? + @NSManaged public var quantity: Int16 + @NSManaged public var price: Double + @NSManaged public var total: Double + @NSManaged public var currency: String? + @NSManaged public var imageUrl: String? + @NSManaged public var stats: TopEarnerStats? +} diff --git a/Storage/Storage/Model/WooCommerce.xcdatamodeld/.xccurrentversion b/Storage/Storage/Model/WooCommerce.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000000..ffe2690fe5a --- /dev/null +++ b/Storage/Storage/Model/WooCommerce.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Model 2.xcdatamodel + + diff --git a/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents b/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents new file mode 100644 index 00000000000..968e980b5ed --- /dev/null +++ b/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Yosemite/Yosemite/Model/Model.swift b/Yosemite/Yosemite/Model/Model.swift index 991571de4b1..7c7cbe2afc9 100644 --- a/Yosemite/Yosemite/Model/Model.swift +++ b/Yosemite/Yosemite/Model/Model.swift @@ -20,6 +20,8 @@ public typealias StatGranularity = Networking.StatGranularity public typealias Site = Networking.Site public typealias SiteVisitStats = Networking.SiteVisitStats public typealias SiteVisitStatsItem = Networking.SiteVisitStatsItem +public typealias TopEarnerStats = Networking.TopEarnerStats +public typealias TopEarnerStatsItem = Networking.TopEarnerStatsItem // MARK: - Exported Storage Symbols @@ -27,3 +29,5 @@ public typealias SiteVisitStatsItem = Networking.SiteVisitStatsItem public typealias StorageAccount = Storage.Account public typealias StorageSite = Storage.Site public typealias StorageOrder = Storage.Order +public typealias StorageTopEarnerStats = Storage.TopEarnerStats +public typealias StorageTopEarnerStatsItem = Storage.TopEarnerStatsItem From 7036ad43cd21da0f4665502d7731d9d342d21a0e Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Thu, 6 Sep 2018 13:50:41 -0500 Subject: [PATCH 2/8] Removed lastUpdated field from TopEarnerStats --- Storage/Storage/Model/MIGRATIONS.md | 2 +- Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift | 1 - .../WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Storage/Storage/Model/MIGRATIONS.md b/Storage/Storage/Model/MIGRATIONS.md index eedd491a1c6..9230e732c72 100644 --- a/Storage/Storage/Model/MIGRATIONS.md +++ b/Storage/Storage/Model/MIGRATIONS.md @@ -4,5 +4,5 @@ This file documents changes in the extensions data model. Please explain any cha ## Model 2 - @bummytime 2018-09-05 -- Added new entity: `TopEarnerStats`, to encapsulate all of the top earner stats for a given site +- Added new entity: `TopEarnerStats`, to encapsulate all of the top earner stats for a given site & granularity - Added new entity: `TopEarnerStatsItem`, to encapsulate all the top earner stats for a specific product diff --git a/Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift b/Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift index d2e14414ecc..d7170aa5113 100644 --- a/Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift +++ b/Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift @@ -11,7 +11,6 @@ extension TopEarnerStats { @NSManaged public var period: String? @NSManaged public var granularity: String? @NSManaged public var limit: String? - @NSManaged public var lastUpdated: Date? @NSManaged public var items: Set? } diff --git a/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents b/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents index 968e980b5ed..493a2909e1a 100644 --- a/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents +++ b/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents @@ -90,7 +90,6 @@ - @@ -112,7 +111,7 @@ - + \ No newline at end of file From 40310a857f9c42100c29a9eaafbcc443e2a799f6 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Thu, 6 Sep 2018 14:57:29 -0500 Subject: [PATCH 3/8] ReadOnlyConvertible imp for TopEarners --- .../TopEarnerStats+CoreDataProperties.swift | 6 ++-- .../Model 2.xcdatamodel/contents | 6 ++-- Yosemite/Yosemite.xcodeproj/project.pbxproj | 8 +++++ .../TopEarnerStats+ReadOnlyConvertible.swift | 27 ++++++++++++++++ ...pEarnerStatsItem+ReadOnlyConvertible.swift | 32 +++++++++++++++++++ 5 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 Yosemite/Yosemite/Model/Storage/TopEarnerStats+ReadOnlyConvertible.swift create mode 100644 Yosemite/Yosemite/Model/Storage/TopEarnerStatsItem+ReadOnlyConvertible.swift diff --git a/Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift b/Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift index d7170aa5113..2d6ddbaf59b 100644 --- a/Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift +++ b/Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift @@ -8,9 +8,9 @@ extension TopEarnerStats { return NSFetchRequest(entityName: "TopEarnerStats") } - @NSManaged public var period: String? - @NSManaged public var granularity: String? - @NSManaged public var limit: String? + @NSManaged public var granularity: String + @NSManaged public var limit: String + @NSManaged public var period: String @NSManaged public var items: Set? } diff --git a/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents b/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents index 493a2909e1a..455a1b920bf 100644 --- a/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents +++ b/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents @@ -89,9 +89,9 @@ - - - + + + diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index a5c0a6ec277..3c7e1a4b220 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ 0E67B79585034C4DD75C8117 /* Pods_Yosemite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C25501C7F936D2FD32FAF3F4 /* Pods_Yosemite.framework */; }; 36941EA7B9242CAB1FF828BC /* Pods_YosemiteTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 991BBCE6E4A92F0A028885D8 /* Pods_YosemiteTests.framework */; }; + 7455D4672141B57600FA8C1F /* TopEarnerStats+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7455D4662141B57600FA8C1F /* TopEarnerStats+ReadOnlyConvertible.swift */; }; + 7455D4692141B59E00FA8C1F /* TopEarnerStatsItem+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7455D4682141B59E00FA8C1F /* TopEarnerStatsItem+ReadOnlyConvertible.swift */; }; 74685D4E20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */; }; 74685D5020F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */; }; 7495C5292114979D00CDD33B /* StatsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7495C5282114979D00CDD33B /* StatsStoreTests.swift */; }; @@ -69,6 +71,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 7455D4662141B57600FA8C1F /* TopEarnerStats+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TopEarnerStats+ReadOnlyConvertible.swift"; sourceTree = ""; }; + 7455D4682141B59E00FA8C1F /* TopEarnerStatsItem+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TopEarnerStatsItem+ReadOnlyConvertible.swift"; sourceTree = ""; }; 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderItem+ReadOnlyConvertible.swift"; sourceTree = ""; }; 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderCoupon+ReadOnlyConvertible.swift"; sourceTree = ""; }; 7495C5282114979D00CDD33B /* StatsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsStoreTests.swift; sourceTree = ""; }; @@ -168,6 +172,8 @@ 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */, 74B7D6AF20F910AF002667AC /* OrderNote+ReadOnlyConvertible.swift */, B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */, + 7455D4662141B57600FA8C1F /* TopEarnerStats+ReadOnlyConvertible.swift */, + 7455D4682141B59E00FA8C1F /* TopEarnerStatsItem+ReadOnlyConvertible.swift */, ); path = Storage; sourceTree = ""; @@ -558,11 +564,13 @@ B52E002E211A3F5500700FDE /* ReadOnlyType.swift in Sources */, B5C9DE162087FF0E006B910A /* Store.swift in Sources */, B52E0034211A449600700FDE /* Site+ReadOnlyType.swift in Sources */, + 7455D4672141B57600FA8C1F /* TopEarnerStats+ReadOnlyConvertible.swift in Sources */, B56C1EBE20EABD2B00D749F9 /* ResultsController.swift in Sources */, B5EED1A820F4F3CF00652449 /* Account+ReadOnlyConvertible.swift in Sources */, B5F2AE9720EBB54A00FEDC59 /* FetchedResultsControllerDelegateWrapper.swift in Sources */, 74A18C58211382A000DCF8A8 /* StatsAction.swift in Sources */, 7499936420EFBC1B00CF01CD /* OrderNoteAction.swift in Sources */, + 7455D4692141B59E00FA8C1F /* TopEarnerStatsItem+ReadOnlyConvertible.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Yosemite/Yosemite/Model/Storage/TopEarnerStats+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Storage/TopEarnerStats+ReadOnlyConvertible.swift new file mode 100644 index 00000000000..8b9d2960385 --- /dev/null +++ b/Yosemite/Yosemite/Model/Storage/TopEarnerStats+ReadOnlyConvertible.swift @@ -0,0 +1,27 @@ +import Foundation +import Storage + + +// MARK: - Storage.TopEarnerStats: ReadOnlyConvertible +// +extension Storage.TopEarnerStats: ReadOnlyConvertible { + + /// Updates the Storage.Order with the ReadOnly. + /// + public func update(with stats: Yosemite.TopEarnerStats) { + period = stats.period + granularity = stats.granularity.rawValue + limit = stats.limit + } + + /// Returns a ReadOnly version of the receiver. + /// + public func toReadOnly() -> Yosemite.TopEarnerStats { + let statItems = items?.map { $0.toReadOnly() } ?? [Yosemite.TopEarnerStatsItem]() + + return TopEarnerStats(period: period, + granularity: StatGranularity(rawValue: granularity) ?? .day, + limit: limit, + items: statItems) + } +} diff --git a/Yosemite/Yosemite/Model/Storage/TopEarnerStatsItem+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Storage/TopEarnerStatsItem+ReadOnlyConvertible.swift new file mode 100644 index 00000000000..c5a664bda5b --- /dev/null +++ b/Yosemite/Yosemite/Model/Storage/TopEarnerStatsItem+ReadOnlyConvertible.swift @@ -0,0 +1,32 @@ +import Foundation +import Storage + + +// MARK: - Storage.TopEarnerStatsItem: ReadOnlyConvertible +// +extension Storage.TopEarnerStatsItem: ReadOnlyConvertible { + + /// Updates the Storage.TopEarnerStatsItem with the ReadOnly. + /// + public func update(with statsItem: Yosemite.TopEarnerStatsItem) { + productID = Int64(statsItem.productID) + productName = statsItem.productName + quantity = Int16(statsItem.quantity) + price = statsItem.price + total = statsItem.total + currency = statsItem.currency + imageUrl = statsItem.imageUrl + } + + /// Returns a ReadOnly version of the receiver. + /// + public func toReadOnly() -> Yosemite.TopEarnerStatsItem { + return TopEarnerStatsItem(productID: Int(productID), + productName: productName ?? "", + quantity: Int(quantity), + price: price, + total: total, + currency: currency ?? "", + imageUrl: imageUrl ?? "") + } +} From 9681bc9e601ac96c1e2dc3df501745b664c84033 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Thu, 6 Sep 2018 17:52:52 -0500 Subject: [PATCH 4/8] WIP: Updates to StatsStore for top earners --- .../Tools/StorageType+Extensions.swift | 14 +++++ Yosemite/Yosemite.xcodeproj/project.pbxproj | 4 ++ Yosemite/Yosemite/Actions/StatsAction.swift | 8 +-- .../TopEarnerStats+ReadOnlyType.swift | 18 ++++++ Yosemite/Yosemite/Stores/StatsStore.swift | 57 +++++++++++++++++++ 5 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 Yosemite/Yosemite/Model/ReadOnly/TopEarnerStats+ReadOnlyType.swift diff --git a/Storage/Storage/Tools/StorageType+Extensions.swift b/Storage/Storage/Tools/StorageType+Extensions.swift index e5ba2a1253f..39b20e2ebd1 100644 --- a/Storage/Storage/Tools/StorageType+Extensions.swift +++ b/Storage/Storage/Tools/StorageType+Extensions.swift @@ -46,4 +46,18 @@ public extension StorageType { let predicate = NSPredicate(format: "noteID = %ld", noteID) return firstObject(ofType: OrderNote.self, matching: predicate) } + + /// Retrieves the Stored TopEarnerStats. + /// + public func loadTopEarnerStats(period: String, granularity: String, limit: String) -> TopEarnerStats? { + let predicate = NSPredicate(format: "period == %@ AND granularity == %@ AND limit == %@", period, granularity, limit) + return firstObject(ofType: TopEarnerStats.self, matching: predicate) + } + + /// Retrieves the Stored TopEarnerStats Item. + /// + public func loadTopEarnerStatsItem(productID: Int) -> TopEarnerStatsItem? { + let predicate = NSPredicate(format: "productID = %ld", productID) + return firstObject(ofType: TopEarnerStatsItem.self, matching: predicate) + } } diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index 3c7e1a4b220..cd8093b8ca5 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 74685D4E20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */; }; 74685D5020F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */; }; 7495C5292114979D00CDD33B /* StatsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7495C5282114979D00CDD33B /* StatsStoreTests.swift */; }; + 749737672141CC8C0008C490 /* TopEarnerStats+ReadOnlyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 749737662141CC8B0008C490 /* TopEarnerStats+ReadOnlyType.swift */; }; 7499936420EFBC1B00CF01CD /* OrderNoteAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */; }; 7499936620EFBC7200CF01CD /* OrderNoteStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */; }; 7499936820EFC0ED00CF01CD /* OrderNoteStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */; }; @@ -76,6 +77,7 @@ 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderItem+ReadOnlyConvertible.swift"; sourceTree = ""; }; 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderCoupon+ReadOnlyConvertible.swift"; sourceTree = ""; }; 7495C5282114979D00CDD33B /* StatsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsStoreTests.swift; sourceTree = ""; }; + 749737662141CC8B0008C490 /* TopEarnerStats+ReadOnlyType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TopEarnerStats+ReadOnlyType.swift"; sourceTree = ""; }; 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteAction.swift; sourceTree = ""; }; 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteStore.swift; sourceTree = ""; }; 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderNoteStoreTests.swift; sourceTree = ""; }; @@ -184,6 +186,7 @@ B52E002F211A439E00700FDE /* Account+ReadOnlyType.swift */, B52E0031211A440D00700FDE /* Order+ReadOnlyType.swift */, B52E0033211A449600700FDE /* Site+ReadOnlyType.swift */, + 749737662141CC8B0008C490 /* TopEarnerStats+ReadOnlyType.swift */, ); path = ReadOnly; sourceTree = ""; @@ -568,6 +571,7 @@ B56C1EBE20EABD2B00D749F9 /* ResultsController.swift in Sources */, B5EED1A820F4F3CF00652449 /* Account+ReadOnlyConvertible.swift in Sources */, B5F2AE9720EBB54A00FEDC59 /* FetchedResultsControllerDelegateWrapper.swift in Sources */, + 749737672141CC8C0008C490 /* TopEarnerStats+ReadOnlyType.swift in Sources */, 74A18C58211382A000DCF8A8 /* StatsAction.swift in Sources */, 7499936420EFBC1B00CF01CD /* OrderNoteAction.swift in Sources */, 7455D4692141B59E00FA8C1F /* TopEarnerStatsItem+ReadOnlyConvertible.swift in Sources */, diff --git a/Yosemite/Yosemite/Actions/StatsAction.swift b/Yosemite/Yosemite/Actions/StatsAction.swift index 75747034982..a3b54dffbad 100644 --- a/Yosemite/Yosemite/Actions/StatsAction.swift +++ b/Yosemite/Yosemite/Actions/StatsAction.swift @@ -6,11 +6,11 @@ import Networking // public enum StatsAction: Action { - // FIXME: We are returning OrderStats in the completion handler...this needs to eventually return an error/nil much like - // OrderAction.retrieveOrders. Update this once OrderStats storage is in place. + // FIXME: We are returning OrderStats in the completion handler...this needs to eventually return an error/nil much like OrderAction.retrieveOrders. Update this once OrderStats storage is in place. case retrieveOrderStats(siteID: Int, granularity: StatGranularity, latestDateToInclude: Date, quantity: Int, onCompletion: (OrderStats?, Error?) -> Void) - // FIXME: We are returning SiteVisitStats in the completion handler...this needs to eventually return an error/nil much like - // OrderAction.retrieveOrders. Update this once SiteVisitStats storage is in place. + // FIXME: We are returning SiteVisitStats in the completion handler...this needs to eventually return an error/nil much like OrderAction.retrieveOrders. Update this once SiteVisitStats storage is in place. case retrieveSiteVisitStats(siteID: Int, granularity: StatGranularity, latestDateToInclude: Date, quantity: Int, onCompletion: (SiteVisitStats?, Error?) -> Void) + + case retrieveTopEarnerStats(siteID: Int, granularity: StatGranularity, latestDateToInclude: Date, onCompletion: (Error?) -> Void) } diff --git a/Yosemite/Yosemite/Model/ReadOnly/TopEarnerStats+ReadOnlyType.swift b/Yosemite/Yosemite/Model/ReadOnly/TopEarnerStats+ReadOnlyType.swift new file mode 100644 index 00000000000..1394927e0cb --- /dev/null +++ b/Yosemite/Yosemite/Model/ReadOnly/TopEarnerStats+ReadOnlyType.swift @@ -0,0 +1,18 @@ +import Foundation +import Storage + + +// MARK: - Yosemite.TopEarnerStats: ReadOnlyType +// +extension Yosemite.TopEarnerStats: ReadOnlyType { + + /// Indicates if the receiver is a representation of a specified Storage.Entity instance. + /// + public func isReadOnlyRepresentation(of storageEntity: Any) -> Bool { + guard let storageTopEarnerStats = storageEntity as? Storage.TopEarnerStats else { + return false + } + + return storageTopEarnerStats.granularity == granularity.rawValue && storageTopEarnerStats.period == period && storageTopEarnerStats.limit == limit + } +} diff --git a/Yosemite/Yosemite/Stores/StatsStore.swift b/Yosemite/Yosemite/Stores/StatsStore.swift index bb2111323d8..fb9b96cd187 100644 --- a/Yosemite/Yosemite/Stores/StatsStore.swift +++ b/Yosemite/Yosemite/Stores/StatsStore.swift @@ -1,5 +1,7 @@ import Foundation import Networking +import Storage + // MARK: - StatsStore // @@ -24,6 +26,8 @@ public class StatsStore: Store { retrieveOrderStats(siteID: siteID, granularity: granularity, latestDateToInclude: latestDateToInclude, quantity: quantity, onCompletion: onCompletion) case .retrieveSiteVisitStats(let siteID, let granularity, let latestDateToInclude, let quantity, let onCompletion): retrieveSiteVisitStats(siteID: siteID, granularity: granularity, latestDateToInclude: latestDateToInclude, quantity: quantity, onCompletion: onCompletion) + case .retrieveTopEarnerStats(let siteID, let granularity, let latestDateToInclude, let onCompletion): + retrieveTopEarnerStats(siteID: siteID, granularity: granularity, latestDateToInclude: latestDateToInclude, onCompletion: onCompletion) } } } @@ -66,6 +70,24 @@ private extension StatsStore { } } + /// Retrieves the top earner stats associated with the provided Site ID (if any!). + /// + func retrieveTopEarnerStats(siteID: Int, granularity: StatGranularity, latestDateToInclude: Date, onCompletion: @escaping (Error?) -> Void) { + + let remote = TopEarnersStatsRemote(network: network) + let formattedDateString = buildDateString(from: latestDateToInclude, with: granularity) + + remote.loadTopEarnersStats(for: siteID, unit: granularity, latestDateToInclude: formattedDateString, limit: 5) { [weak self] (topEarnerStats, error) in + guard let topEarnerStats = topEarnerStats else { + onCompletion(error) + return + } + + self?.upsertStoredTopEarnerStats(readOnlyStats: topEarnerStats) + onCompletion(nil) + } + } + /// Converts a Date into the appropriatly formatted string based on the `OrderStatGranularity` /// func buildDateString(from date: Date, with granularity: StatGranularity) -> String { @@ -81,3 +103,38 @@ private extension StatsStore { } } } + + +// MARK: - Persistence +// +extension StatsStore { + + /// Updates (OR Inserts) the specified ReadOnly TopEarnerStats Entity into the Storage Layer. + /// + func upsertStoredTopEarnerStats(readOnlyStats: Networking.TopEarnerStats) { + assert(Thread.isMainThread) + + let storage = storageManager.viewStorage + let storageTopEarnerStats = storage.loadTopEarnerStats(period: readOnlyStats.period, + granularity: readOnlyStats.granularity.rawValue, + limit: readOnlyStats.limit) ?? storage.insertNewObject(ofType: Storage.TopEarnerStats.self) + storageTopEarnerStats.update(with: readOnlyStats) + handleTopEarnerStatsItems(readOnlyStats, storageTopEarnerStats, storage) + storage.saveIfNeeded() + } + + /// Updates the provided StorageTopEarnerStats' items using the provided read-only TopEarnerStats' items + /// + private func handleTopEarnerStatsItems(_ readOnlyStats: Networking.TopEarnerStats, _ storageTopEarnerStats: Storage.TopEarnerStats, _ storage: StorageType) { + + // Since we are treating the items in core data like a dumb cache, start by nuking all of the existing stored items + storageTopEarnerStats.items?.forEach { storageTopEarnerStats.removeFromItems($0) } + + // Insert the items from the read-only stats + readOnlyStats.items?.forEach({ readOnlyItem in + let newStorageItem = storage.insertNewObject(ofType: Storage.TopEarnerStatsItem.self) + newStorageItem.update(with: readOnlyItem) + storageTopEarnerStats.addToItems(newStorageItem) + }) + } +} From 248ed412cd6590a89d964fe747e1c9ae4065dab1 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Thu, 6 Sep 2018 18:39:23 -0500 Subject: [PATCH 5/8] Added TopEarners store test --- .../Model/Stats/TopEarnerStats.swift | 3 +- .../Responses/top-performers-week.json | 2 +- .../Stores/StatsStoreTests.swift | 74 ++++++++++++++++++- 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/Networking/Networking/Model/Stats/TopEarnerStats.swift b/Networking/Networking/Model/Stats/TopEarnerStats.swift index 30511479243..cfcd6e27aab 100644 --- a/Networking/Networking/Model/Stats/TopEarnerStats.swift +++ b/Networking/Networking/Model/Stats/TopEarnerStats.swift @@ -54,7 +54,8 @@ extension TopEarnerStats: Comparable { return lhs.period == rhs.period && lhs.granularity == rhs.granularity && lhs.limit == rhs.limit && - lhs.items == rhs.items + lhs.items?.count == rhs.items?.count && + lhs.items?.sorted() == rhs.items?.sorted() } public static func < (lhs: TopEarnerStats, rhs: TopEarnerStats) -> Bool { diff --git a/Networking/NetworkingTests/Responses/top-performers-week.json b/Networking/NetworkingTests/Responses/top-performers-week.json index f5ded76202d..4b7a538b963 100644 --- a/Networking/NetworkingTests/Responses/top-performers-week.json +++ b/Networking/NetworkingTests/Responses/top-performers-week.json @@ -31,4 +31,4 @@ "currency": "USD" } ] -} \ No newline at end of file +} diff --git a/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift b/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift index 4228ca97e72..aa8a4c6375f 100644 --- a/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift +++ b/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift @@ -1,6 +1,7 @@ import XCTest @testable import Yosemite @testable import Networking +@testable import Storage /// StatsStoreTests Unit Tests @@ -19,6 +20,12 @@ class StatsStoreTests: XCTestCase { /// private var storageManager: MockupStorageManager! + /// Convenience Property: Returns the StorageType associated with the main thread. + /// + private var viewStorage: StorageType { + return storageManager.viewStorage + } + /// Dummy Site ID /// private let sampleSiteID = 123 @@ -174,6 +181,33 @@ class StatsStoreTests: XCTestCase { statsStore.onAction(action) wait(for: [expectation], timeout: Constants.expectationTimeout) } + + + // MARK: - StatsAction.retrieveTopEarnerStats + + + /// Verifies that `StatsAction.retrieveTopEarnerStats` effectively persists any retrieved TopEarnerStats. + /// + func testRetrieveTopEarnersStatsEffectivelyPersistsRetrievedStats() { + let expectation = self.expectation(description: "Persist top earner stats") + let statsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + + network.simulateResponse(requestUrlSuffix: "sites/\(sampleSiteID)/stats/top-earners/", filename: "top-performers-week") + XCTAssertEqual(viewStorage.countObjects(ofType: Storage.TopEarnerStats.self), 0) + + let action = StatsAction.retrieveTopEarnerStats(siteID: sampleSiteID, granularity: .week, latestDateToInclude: Date()) { error in + XCTAssertNil(error) + XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.TopEarnerStats.self), 1) + XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.TopEarnerStatsItem.self), 3) + let readOnlyTopEarnerStats = self.viewStorage.firstObject(ofType: Storage.TopEarnerStats.self)?.toReadOnly() + XCTAssertEqual(readOnlyTopEarnerStats, self.sampleTopEarnerStats()) + + expectation.fulfill() + } + + statsStore.onAction(action) + wait(for: [expectation], timeout: Constants.expectationTimeout) + } } @@ -235,8 +269,46 @@ private extension StatsStoreTests { rawData: ["2015-01-01", 14808, 1629, 1492, 0, 1268, 571]) } + // MARK: - Top Earner Stats Sample + + func sampleTopEarnerStats() -> Networking.TopEarnerStats { + return TopEarnerStats(period: "2018-W12", + granularity: .week, + limit: "5", + items: [sampleTopEarnerStatsItem1(), sampleTopEarnerStatsItem2(), sampleTopEarnerStatsItem3()]) + } + + func sampleTopEarnerStatsItem1() -> Networking.TopEarnerStatsItem { + return TopEarnerStatsItem(productID: 296, + productName: "Funky Hoodie", + quantity: 1, + price: 40, + total: 0, + currency: "USD", + imageUrl: "https://jamosova3.mystagingwebsite.com/wp-content/uploads/2017/05/hoodie-with-logo.jpg?w=801") + } + + func sampleTopEarnerStatsItem2() -> Networking.TopEarnerStatsItem { + return TopEarnerStatsItem(productID: 373, + productName: "Black Dress (H&M)", + quantity: 4, + price: 30, + total: 120, + currency: "USD", + imageUrl: "https://jamosova3.mystagingwebsite.com/wp-content/uploads/2017/07/hm-black.jpg?w=640") + } + + func sampleTopEarnerStatsItem3() -> Networking.TopEarnerStatsItem { + return TopEarnerStatsItem(productID: 1033, + productName: "Smile T-Shirt", + quantity: 2, + price: 80, + total: 160, + currency: "USD", + imageUrl: "https://jamosova3.mystagingwebsite.com/wp-content/uploads/2018/04/smile.gif?w=480") + } - // MARK: - Misc + // MARK: - Misc func date(with dateString: String) -> Date { guard let date = DateFormatter.Defaults.dateTimeFormatter.date(from: dateString) else { From 9641a8ae30dabfd0f1dedb233b507cda06d0b2d0 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Thu, 6 Sep 2018 19:13:21 -0500 Subject: [PATCH 6/8] More tests --- .../Networking.xcodeproj/project.pbxproj | 4 ++ .../Responses/top-performers-week-alt.json | 25 +++++++++ .../Tools/StorageType+Extensions.swift | 4 +- Yosemite/Yosemite/Stores/StatsStore.swift | 3 +- .../Stores/StatsStoreTests.swift | 53 +++++++++++++++++++ 5 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 Networking/NetworkingTests/Responses/top-performers-week-alt.json diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index eb12f70de04..8854c6693b8 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 748D424A210F92EA00CF7D1B /* OrderStatsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D4249210F92EA00CF7D1B /* OrderStatsItem.swift */; }; 748D424C210FA34400CF7D1B /* OrderStatsMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */; }; 748D424E210FB1F500CF7D1B /* order-stats-day.json in Resources */ = {isa = PBXBuildFile; fileRef = 748D424D210FB1F500CF7D1B /* order-stats-day.json */; }; + 7497376A2141F2BE0008C490 /* top-performers-week-alt.json in Resources */ = {isa = PBXBuildFile; fileRef = 749737692141F2BE0008C490 /* top-performers-week-alt.json */; }; 74A1196C2110F4BB00E1E5F0 /* OrderStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A1196B2110F4BB00E1E5F0 /* OrderStats.swift */; }; 74A1D263211898F000931DFA /* site-visits-day.json in Resources */ = {isa = PBXBuildFile; fileRef = 74A1D25F211898F000931DFA /* site-visits-day.json */; }; 74A1D264211898F000931DFA /* site-visits-week.json in Resources */ = {isa = PBXBuildFile; fileRef = 74A1D260211898F000931DFA /* site-visits-week.json */; }; @@ -132,6 +133,7 @@ 748D4249210F92EA00CF7D1B /* OrderStatsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsItem.swift; sourceTree = ""; }; 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsMapper.swift; sourceTree = ""; }; 748D424D210FB1F500CF7D1B /* order-stats-day.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-day.json"; sourceTree = ""; }; + 749737692141F2BE0008C490 /* top-performers-week-alt.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "top-performers-week-alt.json"; sourceTree = ""; }; 74A1196B2110F4BB00E1E5F0 /* OrderStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStats.swift; sourceTree = ""; }; 74A1D25F211898F000931DFA /* site-visits-day.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "site-visits-day.json"; sourceTree = ""; }; 74A1D260211898F000931DFA /* site-visits-week.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "site-visits-week.json"; sourceTree = ""; }; @@ -447,6 +449,7 @@ 743BF8BD21191B63008A9D87 /* site-visits.json */, 74ABA1C4213F17AA00FFAD30 /* top-performers-day.json */, 74ABA1C8213F19FE00FFAD30 /* top-performers-week.json */, + 749737692141F2BE0008C490 /* top-performers-week-alt.json */, 74ABA1C6213F19FD00FFAD30 /* top-performers-month.json */, 74ABA1C7213F19FE00FFAD30 /* top-performers-year.json */, ); @@ -620,6 +623,7 @@ 74ABA1C9213F19FE00FFAD30 /* top-performers-month.json in Resources */, B58D10C82114D21D00107ED4 /* generic_error.json in Resources */, B5147876211B9227007562E5 /* broken-orders-mark-2.json in Resources */, + 7497376A2141F2BE0008C490 /* top-performers-week-alt.json in Resources */, 743BF8BE21191B63008A9D87 /* site-visits.json in Resources */, B505F6D520BEE4E700BB1B69 /* me.json in Resources */, B5C6FCD620A3768900A4F8E4 /* order.json in Resources */, diff --git a/Networking/NetworkingTests/Responses/top-performers-week-alt.json b/Networking/NetworkingTests/Responses/top-performers-week-alt.json new file mode 100644 index 00000000000..4324d720457 --- /dev/null +++ b/Networking/NetworkingTests/Responses/top-performers-week-alt.json @@ -0,0 +1,25 @@ +{ + "date": "2018-W12", + "unit": "week", + "limit": "4", + "data": [ + { + "ID": 996, + "name": "Funky Hoodie 2", + "total": 2, + "quantity": 444, + "price": 40, + "image": "https:\/\/jamosova3.mystagingwebsite.com\/wp-content\/uploads\/2017\/05\/hoodie-with-logo.jpg", + "currency": "USD" + }, + { + "ID": 933, + "name": "Smile T-Shirt 2", + "total": 161.00, + "quantity": 555, + "price": 55.44, + "image": "https:\/\/jamosova3.mystagingwebsite.com\/wp-content\/uploads\/2018\/04\/smile.gif", + "currency": "USD" + } + ] +} diff --git a/Storage/Storage/Tools/StorageType+Extensions.swift b/Storage/Storage/Tools/StorageType+Extensions.swift index 39b20e2ebd1..81e6a795493 100644 --- a/Storage/Storage/Tools/StorageType+Extensions.swift +++ b/Storage/Storage/Tools/StorageType+Extensions.swift @@ -49,8 +49,8 @@ public extension StorageType { /// Retrieves the Stored TopEarnerStats. /// - public func loadTopEarnerStats(period: String, granularity: String, limit: String) -> TopEarnerStats? { - let predicate = NSPredicate(format: "period == %@ AND granularity == %@ AND limit == %@", period, granularity, limit) + public func loadTopEarnerStats(period: String, granularity: String) -> TopEarnerStats? { + let predicate = NSPredicate(format: "period == %@ AND granularity == %@", period, granularity) return firstObject(ofType: TopEarnerStats.self, matching: predicate) } diff --git a/Yosemite/Yosemite/Stores/StatsStore.swift b/Yosemite/Yosemite/Stores/StatsStore.swift index fb9b96cd187..aeb250b3abd 100644 --- a/Yosemite/Yosemite/Stores/StatsStore.swift +++ b/Yosemite/Yosemite/Stores/StatsStore.swift @@ -116,8 +116,7 @@ extension StatsStore { let storage = storageManager.viewStorage let storageTopEarnerStats = storage.loadTopEarnerStats(period: readOnlyStats.period, - granularity: readOnlyStats.granularity.rawValue, - limit: readOnlyStats.limit) ?? storage.insertNewObject(ofType: Storage.TopEarnerStats.self) + granularity: readOnlyStats.granularity.rawValue) ?? storage.insertNewObject(ofType: Storage.TopEarnerStats.self) storageTopEarnerStats.update(with: readOnlyStats) handleTopEarnerStatsItems(readOnlyStats, storageTopEarnerStats, storage) storage.saveIfNeeded() diff --git a/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift b/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift index aa8a4c6375f..783dd830f40 100644 --- a/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift +++ b/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift @@ -208,6 +208,32 @@ class StatsStoreTests: XCTestCase { statsStore.onAction(action) wait(for: [expectation], timeout: Constants.expectationTimeout) } + + /// Verifies that `StatsAction.retrieveTopEarnerStats` effectively persists any updated TopEarnerStatsItems. + /// + func testRetrieveTopEarnersStatsEffectivelyPersistsUpdatedItems() { + let expectation = self.expectation(description: "Persist updated top earner stats items") + let statsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + + XCTAssertEqual(viewStorage.countObjects(ofType: Storage.TopEarnerStats.self), 0) + statsStore.upsertStoredTopEarnerStats(readOnlyStats: sampleTopEarnerStats()) + XCTAssertEqual(viewStorage.countObjects(ofType: Storage.TopEarnerStats.self), 1) + XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.TopEarnerStatsItem.self), 3) + + network.simulateResponse(requestUrlSuffix: "sites/\(sampleSiteID)/stats/top-earners/", filename: "top-performers-week-alt") + let action = StatsAction.retrieveTopEarnerStats(siteID: sampleSiteID, granularity: .week, latestDateToInclude: Date()) { error in + XCTAssertNil(error) + XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.TopEarnerStats.self), 1) + //XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.TopEarnerStatsItem.self), 2) + let readOnlyTopEarnerStats = self.viewStorage.firstObject(ofType: Storage.TopEarnerStats.self)?.toReadOnly() + XCTAssertEqual(readOnlyTopEarnerStats, self.sampleTopEarnerStatsMutated()) + + expectation.fulfill() + } + + statsStore.onAction(action) + wait(for: [expectation], timeout: Constants.expectationTimeout) + } } @@ -308,6 +334,33 @@ private extension StatsStoreTests { imageUrl: "https://jamosova3.mystagingwebsite.com/wp-content/uploads/2018/04/smile.gif?w=480") } + func sampleTopEarnerStatsMutated() -> Networking.TopEarnerStats { + return TopEarnerStats(period: "2018-W12", + granularity: .week, + limit: "4", + items: [sampleTopEarnerStatsMutatedItem1(), sampleTopEarnerStatsMutatedItem2()]) + } + + func sampleTopEarnerStatsMutatedItem1() -> Networking.TopEarnerStatsItem { + return TopEarnerStatsItem(productID: 996, + productName: "Funky Hoodie 2", + quantity: 444, + price: 40, + total: 2, + currency: "USD", + imageUrl: "https://jamosova3.mystagingwebsite.com/wp-content/uploads/2017/05/hoodie-with-logo.jpg") + } + + func sampleTopEarnerStatsMutatedItem2() -> Networking.TopEarnerStatsItem { + return TopEarnerStatsItem(productID: 933, + productName: "Smile T-Shirt 2", + quantity: 555, + price: 55.44, + total: 161.00, + currency: "USD", + imageUrl: "https://jamosova3.mystagingwebsite.com/wp-content/uploads/2018/04/smile.gif") + } + // MARK: - Misc func date(with dateString: String) -> Date { From 928739b5cd3f6d785dda1e2c35204e7f468917c6 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Fri, 7 Sep 2018 10:24:07 -0500 Subject: [PATCH 7/8] Updates + final tests for top earner in Storage + Yosemite --- .../Model/Stats/TopEarnerStats.swift | 18 ++--- .../Mapper/TopEarnerStatsMapperTests.swift | 8 +-- .../TopEarnerStats+CoreDataProperties.swift | 2 +- .../Model 2.xcdatamodel/contents | 2 +- .../Tools/StorageType+Extensions.swift | 4 +- .../TopEarnerStats+ReadOnlyType.swift | 2 +- .../TopEarnerStats+ReadOnlyConvertible.swift | 4 +- Yosemite/Yosemite/Stores/StatsStore.swift | 9 ++- .../Stores/StatsStoreTests.swift | 68 ++++++++++++++++++- 9 files changed, 91 insertions(+), 26 deletions(-) diff --git a/Networking/Networking/Model/Stats/TopEarnerStats.swift b/Networking/Networking/Model/Stats/TopEarnerStats.swift index cfcd6e27aab..d9158805df8 100644 --- a/Networking/Networking/Model/Stats/TopEarnerStats.swift +++ b/Networking/Networking/Model/Stats/TopEarnerStats.swift @@ -4,7 +4,7 @@ import Foundation /// Represents Top Earner (aka top performer) stats over a specific period. /// public struct TopEarnerStats: Decodable { - public let period: String + public let date: String public let granularity: StatGranularity public let limit: String public let items: [TopEarnerStatsItem]? @@ -15,19 +15,19 @@ public struct TopEarnerStats: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let period = try container.decode(String.self, forKey: .period) + let date = try container.decode(String.self, forKey: .date) let granularity = try container.decode(StatGranularity.self, forKey: .unit) let limit = try container.decode(String.self, forKey: .limit) let items = try container.decode([TopEarnerStatsItem].self, forKey: .items) - self.init(period: period, granularity: granularity, limit: limit, items: items) + self.init(date: date, granularity: granularity, limit: limit, items: items) } /// TopEarnerStats struct initializer. /// - public init(period: String, granularity: StatGranularity, limit: String, items: [TopEarnerStatsItem]?) { - self.period = period + public init(date: String, granularity: StatGranularity, limit: String, items: [TopEarnerStatsItem]?) { + self.date = date self.granularity = granularity self.limit = limit self.items = items @@ -39,7 +39,7 @@ public struct TopEarnerStats: Decodable { /// private extension TopEarnerStats { enum CodingKeys: String, CodingKey { - case period = "date" + case date = "date" case unit = "unit" case limit = "limit" case items = "data" @@ -51,7 +51,7 @@ private extension TopEarnerStats { // extension TopEarnerStats: Comparable { public static func == (lhs: TopEarnerStats, rhs: TopEarnerStats) -> Bool { - return lhs.period == rhs.period && + return lhs.date == rhs.date && lhs.granularity == rhs.granularity && lhs.limit == rhs.limit && lhs.items?.count == rhs.items?.count && @@ -59,7 +59,7 @@ extension TopEarnerStats: Comparable { } public static func < (lhs: TopEarnerStats, rhs: TopEarnerStats) -> Bool { - return lhs.period < rhs.period || - (lhs.period == rhs.period && lhs.limit < rhs.limit) + return lhs.date < rhs.date || + (lhs.date == rhs.date && lhs.limit < rhs.limit) } } diff --git a/Networking/NetworkingTests/Mapper/TopEarnerStatsMapperTests.swift b/Networking/NetworkingTests/Mapper/TopEarnerStatsMapperTests.swift index 394241060c6..e3deb7a0ee3 100644 --- a/Networking/NetworkingTests/Mapper/TopEarnerStatsMapperTests.swift +++ b/Networking/NetworkingTests/Mapper/TopEarnerStatsMapperTests.swift @@ -15,7 +15,7 @@ class TopEarnerStatsMapperTests: XCTestCase { } XCTAssertEqual(dayStats.granularity, .day) - XCTAssertEqual(dayStats.period, "2018-06-08") + XCTAssertEqual(dayStats.date, "2018-06-08") XCTAssertEqual(dayStats.limit, "5") XCTAssertEqual(dayStats.items!.count, 1) @@ -38,7 +38,7 @@ class TopEarnerStatsMapperTests: XCTestCase { } XCTAssertEqual(weekStats.granularity, .week) - XCTAssertEqual(weekStats.period, "2018-W12") + XCTAssertEqual(weekStats.date, "2018-W12") XCTAssertEqual(weekStats.limit, "5") XCTAssertEqual(weekStats.items!.count, 3) @@ -70,7 +70,7 @@ class TopEarnerStatsMapperTests: XCTestCase { } XCTAssertEqual(monthStats.granularity, .month) - XCTAssertEqual(monthStats.period, "2018-08") + XCTAssertEqual(monthStats.date, "2018-08") XCTAssertEqual(monthStats.limit, "5") XCTAssertEqual(monthStats.items!.count, 5) @@ -102,7 +102,7 @@ class TopEarnerStatsMapperTests: XCTestCase { } XCTAssertEqual(yearStats.granularity, .year) - XCTAssertEqual(yearStats.period, "2018") + XCTAssertEqual(yearStats.date, "2018") XCTAssertEqual(yearStats.limit, "5") XCTAssertEqual(yearStats.items!.count, 4) diff --git a/Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift b/Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift index 2d6ddbaf59b..2af7e2875ab 100644 --- a/Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift +++ b/Storage/Storage/Model/TopEarnerStats+CoreDataProperties.swift @@ -10,7 +10,7 @@ extension TopEarnerStats { @NSManaged public var granularity: String @NSManaged public var limit: String - @NSManaged public var period: String + @NSManaged public var date: String @NSManaged public var items: Set? } diff --git a/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents b/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents index 455a1b920bf..22943df1b52 100644 --- a/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents +++ b/Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 2.xcdatamodel/contents @@ -89,9 +89,9 @@ + - diff --git a/Storage/Storage/Tools/StorageType+Extensions.swift b/Storage/Storage/Tools/StorageType+Extensions.swift index 81e6a795493..42ae822fee7 100644 --- a/Storage/Storage/Tools/StorageType+Extensions.swift +++ b/Storage/Storage/Tools/StorageType+Extensions.swift @@ -49,8 +49,8 @@ public extension StorageType { /// Retrieves the Stored TopEarnerStats. /// - public func loadTopEarnerStats(period: String, granularity: String) -> TopEarnerStats? { - let predicate = NSPredicate(format: "period == %@ AND granularity == %@", period, granularity) + public func loadTopEarnerStats(date: String, granularity: String) -> TopEarnerStats? { + let predicate = NSPredicate(format: "date ==[c] %@ AND granularity ==[c] %@", date, granularity) return firstObject(ofType: TopEarnerStats.self, matching: predicate) } diff --git a/Yosemite/Yosemite/Model/ReadOnly/TopEarnerStats+ReadOnlyType.swift b/Yosemite/Yosemite/Model/ReadOnly/TopEarnerStats+ReadOnlyType.swift index 1394927e0cb..79374684440 100644 --- a/Yosemite/Yosemite/Model/ReadOnly/TopEarnerStats+ReadOnlyType.swift +++ b/Yosemite/Yosemite/Model/ReadOnly/TopEarnerStats+ReadOnlyType.swift @@ -13,6 +13,6 @@ extension Yosemite.TopEarnerStats: ReadOnlyType { return false } - return storageTopEarnerStats.granularity == granularity.rawValue && storageTopEarnerStats.period == period && storageTopEarnerStats.limit == limit + return storageTopEarnerStats.granularity == granularity.rawValue && storageTopEarnerStats.date == date } } diff --git a/Yosemite/Yosemite/Model/Storage/TopEarnerStats+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Storage/TopEarnerStats+ReadOnlyConvertible.swift index 8b9d2960385..41d500d5f0b 100644 --- a/Yosemite/Yosemite/Model/Storage/TopEarnerStats+ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Model/Storage/TopEarnerStats+ReadOnlyConvertible.swift @@ -9,7 +9,7 @@ extension Storage.TopEarnerStats: ReadOnlyConvertible { /// Updates the Storage.Order with the ReadOnly. /// public func update(with stats: Yosemite.TopEarnerStats) { - period = stats.period + date = stats.date granularity = stats.granularity.rawValue limit = stats.limit } @@ -19,7 +19,7 @@ extension Storage.TopEarnerStats: ReadOnlyConvertible { public func toReadOnly() -> Yosemite.TopEarnerStats { let statItems = items?.map { $0.toReadOnly() } ?? [Yosemite.TopEarnerStatsItem]() - return TopEarnerStats(period: period, + return TopEarnerStats(date: date, granularity: StatGranularity(rawValue: granularity) ?? .day, limit: limit, items: statItems) diff --git a/Yosemite/Yosemite/Stores/StatsStore.swift b/Yosemite/Yosemite/Stores/StatsStore.swift index aeb250b3abd..e190214a0e9 100644 --- a/Yosemite/Yosemite/Stores/StatsStore.swift +++ b/Yosemite/Yosemite/Stores/StatsStore.swift @@ -115,7 +115,7 @@ extension StatsStore { assert(Thread.isMainThread) let storage = storageManager.viewStorage - let storageTopEarnerStats = storage.loadTopEarnerStats(period: readOnlyStats.period, + let storageTopEarnerStats = storage.loadTopEarnerStats(date: readOnlyStats.date, granularity: readOnlyStats.granularity.rawValue) ?? storage.insertNewObject(ofType: Storage.TopEarnerStats.self) storageTopEarnerStats.update(with: readOnlyStats) handleTopEarnerStatsItems(readOnlyStats, storageTopEarnerStats, storage) @@ -126,8 +126,11 @@ extension StatsStore { /// private func handleTopEarnerStatsItems(_ readOnlyStats: Networking.TopEarnerStats, _ storageTopEarnerStats: Storage.TopEarnerStats, _ storage: StorageType) { - // Since we are treating the items in core data like a dumb cache, start by nuking all of the existing stored items - storageTopEarnerStats.items?.forEach { storageTopEarnerStats.removeFromItems($0) } + // Since we are treating the items in core data like a dumb cache, start by nuking all of the existing stored TopEarnerStatsItems + storageTopEarnerStats.items?.forEach { + storageTopEarnerStats.removeFromItems($0) + storage.deleteObject($0) + } // Insert the items from the read-only stats readOnlyStats.items?.forEach({ readOnlyItem in diff --git a/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift b/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift index 783dd830f40..f968f52c3e1 100644 --- a/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift +++ b/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift @@ -224,7 +224,7 @@ class StatsStoreTests: XCTestCase { let action = StatsAction.retrieveTopEarnerStats(siteID: sampleSiteID, granularity: .week, latestDateToInclude: Date()) { error in XCTAssertNil(error) XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.TopEarnerStats.self), 1) - //XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.TopEarnerStatsItem.self), 2) + XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.TopEarnerStatsItem.self), 2) let readOnlyTopEarnerStats = self.viewStorage.firstObject(ofType: Storage.TopEarnerStats.self)?.toReadOnly() XCTAssertEqual(readOnlyTopEarnerStats, self.sampleTopEarnerStatsMutated()) @@ -234,6 +234,68 @@ class StatsStoreTests: XCTestCase { statsStore.onAction(action) wait(for: [expectation], timeout: Constants.expectationTimeout) } + + /// Verifies that OrderAction.retrieveOrder returns an error whenever there is an error response from the backend. + /// + func testRetrieveTopEarnersStatsReturnsErrorUponReponseError() { + let expectation = self.expectation(description: "Retrieve top earner stats error response") + let statsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + + network.simulateResponse(requestUrlSuffix: "sites/\(sampleSiteID)/stats/top-earners/", filename: "generic_error") + let action = StatsAction.retrieveTopEarnerStats(siteID: sampleSiteID, granularity: .week, latestDateToInclude: Date()) { error in + XCTAssertNotNil(error) + expectation.fulfill() + } + + statsStore.onAction(action) + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + + /// Verifies that `StatsAction.retrieveTopEarnerStats` returns an error whenever there is no backend response. + /// + func testRetrieveTopEarnersStatsReturnsErrorUponEmptyResponse() { + let expectation = self.expectation(description: "Retrieve top earner stats empty response") + let statsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + + let action = StatsAction.retrieveTopEarnerStats(siteID: sampleSiteID, granularity: .week, latestDateToInclude: Date()) { error in + XCTAssertNotNil(error) + expectation.fulfill() + } + + statsStore.onAction(action) + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + + /// Verifies that `upsertStoredTopEarnerStats` effectively inserts a new Order, with the specified payload. + /// + func testUpsertStoredTopEarnersStatsEffectivelyPersistsNewTopEarnersStats() { + let statsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + let remoteTopEarnersStats = sampleTopEarnerStats() + + XCTAssertNil(viewStorage.loadTopEarnerStats(date: "2018-W12", granularity: StatGranularity.week.rawValue)) + statsStore.upsertStoredTopEarnerStats(readOnlyStats: remoteTopEarnersStats) + + let storageTopEarnersStats = viewStorage.loadTopEarnerStats(date: "2018-W12", granularity: StatGranularity.week.rawValue) + XCTAssertEqual(storageTopEarnersStats?.toReadOnly(), remoteTopEarnersStats) + } + + /// Verifies that `upsertStoredTopEarnerStats` does not produce duplicate entries. + /// + func testUpdateStoredTopEarnersStatsEffectivelyUpdatesPreexistantTopEarnersStats() { + let statsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + + XCTAssertNil(viewStorage.loadTopEarnerStats(date: "2018-W12", granularity: StatGranularity.week.rawValue)) + statsStore.upsertStoredTopEarnerStats(readOnlyStats: sampleTopEarnerStats()) + XCTAssertEqual(viewStorage.countObjects(ofType: Storage.TopEarnerStats.self), 1) + XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.TopEarnerStatsItem.self), 3) + statsStore.upsertStoredTopEarnerStats(readOnlyStats: sampleTopEarnerStatsMutated()) + XCTAssertEqual(viewStorage.countObjects(ofType: Storage.TopEarnerStats.self), 1) + XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.TopEarnerStatsItem.self), 2) + + let expectedTopEarnerStats = sampleTopEarnerStatsMutated() + let storageTopEarnerStats = viewStorage.loadTopEarnerStats(date: "2018-W12", granularity: StatGranularity.week.rawValue) + XCTAssertEqual(storageTopEarnerStats?.toReadOnly(), expectedTopEarnerStats) + } } @@ -298,7 +360,7 @@ private extension StatsStoreTests { // MARK: - Top Earner Stats Sample func sampleTopEarnerStats() -> Networking.TopEarnerStats { - return TopEarnerStats(period: "2018-W12", + return TopEarnerStats(date: "2018-W12", granularity: .week, limit: "5", items: [sampleTopEarnerStatsItem1(), sampleTopEarnerStatsItem2(), sampleTopEarnerStatsItem3()]) @@ -335,7 +397,7 @@ private extension StatsStoreTests { } func sampleTopEarnerStatsMutated() -> Networking.TopEarnerStats { - return TopEarnerStats(period: "2018-W12", + return TopEarnerStats(date: "2018-W12", granularity: .week, limit: "4", items: [sampleTopEarnerStatsMutatedItem1(), sampleTopEarnerStatsMutatedItem2()]) From 9bbb12190f295f089196bde6c4a5af0ec8eab966 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Fri, 7 Sep 2018 12:18:08 -0500 Subject: [PATCH 8/8] Doc comment fix --- Yosemite/YosemiteTests/Stores/StatsStoreTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift b/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift index f968f52c3e1..f36bf823b70 100644 --- a/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift +++ b/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift @@ -266,7 +266,7 @@ class StatsStoreTests: XCTestCase { wait(for: [expectation], timeout: Constants.expectationTimeout) } - /// Verifies that `upsertStoredTopEarnerStats` effectively inserts a new Order, with the specified payload. + /// Verifies that `upsertStoredTopEarnerStats` effectively inserts a new TopEarnerStats, with the specified payload. /// func testUpsertStoredTopEarnersStatsEffectivelyPersistsNewTopEarnersStats() { let statsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network)