Skip to content

Commit e9c7c10

Browse files
authored
Merge pull request #8421 from woocommerce/issue/8173-sitesummarystate-yosemite
[My Store] Add support for conditionally storing site summary stats
2 parents 88bbc06 + 4fcd0eb commit e9c7c10

File tree

8 files changed

+132
-12
lines changed

8 files changed

+132
-12
lines changed

Storage/Storage/Tools/StorageType+Extensions.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,13 @@ public extension StorageType {
149149
return firstObject(ofType: OrderStatsV4Interval.self, matching: predicate)
150150
}
151151

152+
/// Retrieves the Stored SiteSummaryStats.
153+
///
154+
func loadSiteSummaryStats(date: String, period: String) -> SiteSummaryStats? {
155+
let predicate = \SiteSummaryStats.date =~ date && \SiteSummaryStats.period =~ period
156+
return firstObject(ofType: SiteSummaryStats.self, matching: predicate)
157+
}
158+
152159
// MARK: - Order Statuses
153160

154161
/// Retrieves all of the Stores OrderStatuses for the provided siteID.

WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModel.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,8 @@ private extension AnalyticsHubViewModel {
215215
let action = StatsActionV4.retrieveSiteSummaryStats(siteID: siteID,
216216
period: period,
217217
quantity: timeRangeSelectionType.quantity,
218-
latestDateToInclude: latestDateToInclude) { result in
218+
latestDateToInclude: latestDateToInclude,
219+
saveInStorage: false) { result in
219220
continuation.resume(with: result)
220221
}
221222
stores.dispatch(action)

WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModelTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ final class AnalyticsHubViewModelTests: XCTestCase {
2626
case let .retrieveTopEarnerStats(_, _, _, _, _, _, _, completion):
2727
let topEarners = TopEarnerStats.fake().copy(items: [.fake()])
2828
completion(.success(topEarners))
29-
case let .retrieveSiteSummaryStats(_, _, _, _, completion):
29+
case let .retrieveSiteSummaryStats(_, _, _, _, _, completion):
3030
let siteStats = SiteSummaryStats.fake().copy(visitors: 30, views: 53)
3131
completion(.success(siteStats))
3232
default:
@@ -61,7 +61,7 @@ final class AnalyticsHubViewModelTests: XCTestCase {
6161
completion(.failure(NSError(domain: "Test", code: 1)))
6262
case let .retrieveTopEarnerStats(_, _, _, _, _, _, _, completion):
6363
completion(.failure(NSError(domain: "Test", code: 1)))
64-
case let .retrieveSiteSummaryStats(_, _, _, _, completion):
64+
case let .retrieveSiteSummaryStats(_, _, _, _, _, completion):
6565
completion(.failure(NSError(domain: "Test", code: 1)))
6666
default:
6767
break
@@ -99,7 +99,7 @@ final class AnalyticsHubViewModelTests: XCTestCase {
9999
case let .retrieveTopEarnerStats(_, _, _, _, _, _, _, completion):
100100
let topEarners = TopEarnerStats.fake().copy(items: [.fake()])
101101
completion(.success(topEarners))
102-
case let .retrieveSiteSummaryStats(_, _, _, _, completion):
102+
case let .retrieveSiteSummaryStats(_, _, _, _, _, completion):
103103
let siteStats = SiteSummaryStats.fake()
104104
loadingSessionsCard = vm.sessionsCard
105105
completion(.success(siteStats))

Yosemite/Yosemite.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@
334334
CC2C036C262F316600928C9C /* ShippingLabelAccountSettings+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC2C036B262F316600928C9C /* ShippingLabelAccountSettings+ReadOnlyConvertible.swift */; };
335335
CC2C0372262F32D800928C9C /* ShippingLabelPaymentMethod+ReadonlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC2C0371262F32D800928C9C /* ShippingLabelPaymentMethod+ReadonlyConvertible.swift */; };
336336
CC6A054628773F75002C144E /* OrderMetaData+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6A054528773F75002C144E /* OrderMetaData+ReadOnlyConvertible.swift */; };
337+
CC80E40C294B454A00D5FF45 /* SiteSummaryStats+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC80E40B294B454A00D5FF45 /* SiteSummaryStats+ReadOnlyConvertible.swift */; };
337338
CE01014F2368C41600783459 /* Refund+ReadOnlyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE01014E2368C41600783459 /* Refund+ReadOnlyType.swift */; };
338339
CE0DB6C0233EB3F300A27E7A /* OrderRefundCondensed+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE0DB6BF233EB3F300A27E7A /* OrderRefundCondensed+ReadOnlyConvertible.swift */; };
339340
CE12FBDB221F406100C59248 /* OrderStatus+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE12FBDA221F406100C59248 /* OrderStatus+ReadOnlyConvertible.swift */; };
@@ -768,6 +769,7 @@
768769
CC2C036B262F316600928C9C /* ShippingLabelAccountSettings+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShippingLabelAccountSettings+ReadOnlyConvertible.swift"; sourceTree = "<group>"; };
769770
CC2C0371262F32D800928C9C /* ShippingLabelPaymentMethod+ReadonlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShippingLabelPaymentMethod+ReadonlyConvertible.swift"; sourceTree = "<group>"; };
770771
CC6A054528773F75002C144E /* OrderMetaData+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderMetaData+ReadOnlyConvertible.swift"; sourceTree = "<group>"; };
772+
CC80E40B294B454A00D5FF45 /* SiteSummaryStats+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SiteSummaryStats+ReadOnlyConvertible.swift"; sourceTree = "<group>"; };
771773
CE01014E2368C41600783459 /* Refund+ReadOnlyType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Refund+ReadOnlyType.swift"; sourceTree = "<group>"; };
772774
CE0DB6BF233EB3F300A27E7A /* OrderRefundCondensed+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderRefundCondensed+ReadOnlyConvertible.swift"; sourceTree = "<group>"; };
773775
CE12FBDA221F406100C59248 /* OrderStatus+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderStatus+ReadOnlyConvertible.swift"; sourceTree = "<group>"; };
@@ -1293,6 +1295,7 @@
12931295
2618707B2540B6A4006522A1 /* ShippingLineTax+ReadOnlyConvertible.swift */,
12941296
B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */,
12951297
7492FAD8217FAD1000ED2C69 /* SiteSetting+ReadOnlyConvertible.swift */,
1298+
CC80E40B294B454A00D5FF45 /* SiteSummaryStats+ReadOnlyConvertible.swift */,
12961299
744A3216216D55F80051439B /* SiteVisitStats+ReadOnlyConvertible.swift */,
12971300
744A3217216D55F80051439B /* SiteVisitStatsItem+ReadOnlyConvertible.swift */,
12981301
45E4620F2684C63700011BF2 /* StateOfACountry+ReadOnlyConvertible.swift */,
@@ -1978,6 +1981,7 @@
19781981
03FBDA222631521100ACE257 /* CouponAction.swift in Sources */,
19791982
CE4FD4562350FD4800A16B31 /* Refund+ReadOnlyConvertible.swift in Sources */,
19801983
CE3B7AD92229C3570050FE4B /* OrderStatus+ReadOnlyType.swift in Sources */,
1984+
CC80E40C294B454A00D5FF45 /* SiteSummaryStats+ReadOnlyConvertible.swift in Sources */,
19811985
026CF62C237D92DC009563D4 /* ProductVariationAttribute+ReadOnlyConvertible.swift in Sources */,
19821986
247CE7AB2582DB9300F9D9D1 /* String+Extensions.swift in Sources */,
19831987
FE28F6F026844231004465C7 /* UserStore.swift in Sources */,

Yosemite/Yosemite/Actions/StatsActionV4.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@ public enum StatsActionV4: Action {
4949
saveInStorage: Bool,
5050
onCompletion: (Result<TopEarnerStats, Error>) -> Void)
5151

52-
/// Retrieves the site summary stats for the provided site ID, period(s), and date, without saving them to the Storage layer.
52+
/// Retrieves the site summary stats for the provided site ID, period(s), and date.
53+
/// Conditionally saves them to storage, if a single period is retrieved.
5354
///
5455
case retrieveSiteSummaryStats(siteID: Int64,
5556
period: StatGranularity,
5657
quantity: Int,
5758
latestDateToInclude: Date,
59+
saveInStorage: Bool,
5860
onCompletion: (Result<SiteSummaryStats, Error>) -> Void)
5961
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Foundation
2+
import Storage
3+
4+
5+
// MARK: - Storage.SiteSummaryStats: ReadOnlyConvertible
6+
//
7+
extension Storage.SiteSummaryStats: ReadOnlyConvertible {
8+
9+
/// Updates the Storage.SiteSummaryStats with the ReadOnly.
10+
///
11+
public func update(with stats: Yosemite.SiteSummaryStats) {
12+
siteID = stats.siteID
13+
date = stats.date
14+
period = stats.period.rawValue
15+
visitors = Int64(stats.visitors)
16+
views = Int64(stats.views)
17+
}
18+
19+
/// Returns a ReadOnly version of the receiver.
20+
///
21+
public func toReadOnly() -> Yosemite.SiteSummaryStats {
22+
SiteSummaryStats(siteID: siteID,
23+
date: date,
24+
period: StatGranularity(rawValue: period) ?? .day,
25+
visitors: Int(visitors),
26+
views: Int(views))
27+
}
28+
}

Yosemite/Yosemite/Stores/StatsStoreV4.swift

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,13 @@ public final class StatsStoreV4: Store {
9494
let period,
9595
let quantity,
9696
let latestDateToInclude,
97+
let saveInStorage,
9798
let onCompletion):
9899
retrieveSiteSummaryStats(siteID: siteID,
99100
period: period,
100101
quantity: quantity,
101102
latestDateToInclude: latestDateToInclude,
103+
saveInStorage: saveInStorage,
102104
onCompletion: onCompletion)
103105
}
104106
}
@@ -189,19 +191,24 @@ private extension StatsStoreV4 {
189191
}
190192
}
191193

192-
/// Retrieves the site summary stats for the provided site ID, period(s), and date, without saving them to the Storage layer.
194+
/// Retrieves the site summary stats for the provided site ID, period(s), and date.
195+
/// Conditionally saves them to storage, if a single period is retrieved.
193196
///
194197
func retrieveSiteSummaryStats(siteID: Int64,
195198
period: StatGranularity,
196199
quantity: Int,
197200
latestDateToInclude: Date,
201+
saveInStorage: Bool,
198202
onCompletion: @escaping (Result<SiteSummaryStats, Error>) -> Void) {
199203
if quantity == 1 {
200204
siteStatsRemote.loadSiteSummaryStats(for: siteID,
201205
period: period,
202-
includingDate: latestDateToInclude) { result in
206+
includingDate: latestDateToInclude) { [weak self] result in
203207
switch result {
204208
case .success(let siteSummaryStats):
209+
if saveInStorage {
210+
self?.upsertStoredSiteSummaryStats(readOnlyStats: siteSummaryStats)
211+
}
205212
onCompletion(.success(siteSummaryStats))
206213
case .failure(let error):
207214
onCompletion(.failure(SiteStatsStoreError(error: error)))
@@ -486,6 +493,20 @@ extension StatsStoreV4 {
486493
}
487494
}
488495

496+
// MARK: Site summary stats
497+
extension StatsStoreV4 {
498+
/// Updates (OR Inserts) the specified ReadOnly SiteSummaryStats Entity into the Storage Layer.
499+
///
500+
func upsertStoredSiteSummaryStats(readOnlyStats: Networking.SiteSummaryStats) {
501+
assert(Thread.isMainThread)
502+
503+
let storage = storageManager.viewStorage
504+
let storageSiteSummaryStats = storage.loadSiteSummaryStats(date: readOnlyStats.date, period: readOnlyStats.period.rawValue)
505+
?? storage.insertNewObject(ofType: Storage.SiteSummaryStats.self)
506+
storageSiteSummaryStats.update(with: readOnlyStats)
507+
storage.saveIfNeeded()
508+
}
509+
}
489510

490511
// MARK: Convert Leaderboard into TopEarnerStats
491512
//

Yosemite/YosemiteTests/Stores/StatsStoreV4Tests.swift

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,8 @@ final class StatsStoreV4Tests: XCTestCase {
518518
let action = StatsActionV4.retrieveSiteSummaryStats(siteID: self.sampleSiteID,
519519
period: .day,
520520
quantity: 1,
521-
latestDateToInclude: DateFormatter.dateFromString(with: "2022-12-09T17:06:55")) { result in
521+
latestDateToInclude: DateFormatter.dateFromString(with: "2022-12-09T17:06:55"),
522+
saveInStorage: false) { result in
522523
promise(result)
523524
}
524525
store.onAction(action)
@@ -541,7 +542,8 @@ final class StatsStoreV4Tests: XCTestCase {
541542
let action = StatsActionV4.retrieveSiteSummaryStats(siteID: self.sampleSiteID,
542543
period: .month,
543544
quantity: 3,
544-
latestDateToInclude: DateFormatter.dateFromString(with: "2022-12-31T17:06:55")) { _ in
545+
latestDateToInclude: DateFormatter.dateFromString(with: "2022-12-31T17:06:55"),
546+
saveInStorage: false) { _ in
545547
promise(())
546548
}
547549
store.onAction(action)
@@ -567,7 +569,8 @@ final class StatsStoreV4Tests: XCTestCase {
567569
let action = StatsActionV4.retrieveSiteSummaryStats(siteID: self.sampleSiteID,
568570
period: .month,
569571
quantity: 3,
570-
latestDateToInclude: DateFormatter.dateFromString(with: "2022-12-31T17:06:55")) { result in
572+
latestDateToInclude: DateFormatter.dateFromString(with: "2022-12-31T17:06:55"),
573+
saveInStorage: false) { result in
571574
promise(result)
572575
}
573576
store.onAction(action)
@@ -591,7 +594,8 @@ final class StatsStoreV4Tests: XCTestCase {
591594
let action = StatsActionV4.retrieveSiteSummaryStats(siteID: self.sampleSiteID,
592595
period: .day,
593596
quantity: 1,
594-
latestDateToInclude: DateFormatter.dateFromString(with: "2022-12-09T17:06:55")) { result in
597+
latestDateToInclude: DateFormatter.dateFromString(with: "2022-12-09T17:06:55"),
598+
saveInStorage: false) { result in
595599
promise(result)
596600
}
597601
store.onAction(action)
@@ -612,7 +616,8 @@ final class StatsStoreV4Tests: XCTestCase {
612616
let action = StatsActionV4.retrieveSiteSummaryStats(siteID: self.sampleSiteID,
613617
period: .day,
614618
quantity: 1,
615-
latestDateToInclude: DateFormatter.dateFromString(with: "2022-12-09T17:06:55")) { result in
619+
latestDateToInclude: DateFormatter.dateFromString(with: "2022-12-09T17:06:55"),
620+
saveInStorage: false) { result in
616621
promise(result)
617622
}
618623
store.onAction(action)
@@ -621,6 +626,50 @@ final class StatsStoreV4Tests: XCTestCase {
621626
// Then
622627
XCTAssertTrue(result.isFailure)
623628
}
629+
630+
/// Verifies that `StatsActionV4.retrieveSiteSummaryStats` effectively persists any retrieved SiteSummaryStats.
631+
///
632+
func test_retrieveSiteSummaryStats_effectively_persists_retrieved_stats() {
633+
// Given
634+
let store = StatsStoreV4(dispatcher: dispatcher, storageManager: storageManager, network: network)
635+
network.simulateResponse(requestUrlSuffix: "sites/\(sampleSiteID)/stats/summary/", filename: "site-summary-stats")
636+
XCTAssertEqual(viewStorage.countObjects(ofType: Storage.SiteSummaryStats.self), 0)
637+
638+
// When
639+
let result: Result<Networking.SiteSummaryStats, Error> = waitFor { promise in
640+
let action = StatsActionV4.retrieveSiteSummaryStats(siteID: self.sampleSiteID,
641+
period: .day,
642+
quantity: 1,
643+
latestDateToInclude: DateFormatter.dateFromString(with: "2022-12-09T17:06:55"),
644+
saveInStorage: true) { result in
645+
promise(result)
646+
}
647+
store.onAction(action)
648+
}
649+
650+
// Then
651+
XCTAssertTrue(result.isSuccess)
652+
XCTAssertEqual(viewStorage.countObjects(ofType: Storage.SiteSummaryStats.self), 1)
653+
654+
let readOnlySiteSummaryStats = viewStorage.firstObject(ofType: Storage.SiteSummaryStats.self)?.toReadOnly()
655+
XCTAssertEqual(readOnlySiteSummaryStats, sampleSiteSummaryStats())
656+
}
657+
658+
/// Verifies that `upsertStoredSiteSummaryStats` does not produce duplicate entries.
659+
///
660+
func test_upsertStoredSiteSummaryStats_effectively_updates_preexistant_SiteSummaryStats() {
661+
let statsStore = StatsStoreV4(dispatcher: dispatcher, storageManager: storageManager, network: network)
662+
663+
XCTAssertNil(viewStorage.loadSiteSummaryStats(date: "2022-12-09", period: StatGranularity.day.rawValue))
664+
statsStore.upsertStoredSiteSummaryStats(readOnlyStats: sampleSiteSummaryStats())
665+
XCTAssertEqual(viewStorage.countObjects(ofType: Storage.SiteSummaryStats.self), 1)
666+
statsStore.upsertStoredSiteSummaryStats(readOnlyStats: sampleSiteSummaryStatsMutated())
667+
XCTAssertEqual(viewStorage.countObjects(ofType: Storage.SiteSummaryStats.self), 1)
668+
669+
let expectedSiteSummaryStats = sampleSiteSummaryStatsMutated()
670+
let storageSiteSummaryStats = viewStorage.loadSiteSummaryStats(date: "2022-12-09", period: StatGranularity.day.rawValue)
671+
XCTAssertEqual(storageSiteSummaryStats?.toReadOnly(), expectedSiteSummaryStats)
672+
}
624673
}
625674

626675

@@ -812,6 +861,14 @@ private extension StatsStoreV4Tests {
812861
views: 123)
813862
}
814863

864+
func sampleSiteSummaryStatsMutated() -> Networking.SiteSummaryStats {
865+
return SiteSummaryStats(siteID: sampleSiteID,
866+
date: "2022-12-09",
867+
period: .day,
868+
visitors: 15,
869+
views: 127)
870+
}
871+
815872
func sampleSiteSummaryStatsQuarter() -> Networking.SiteSummaryStats {
816873
return SiteSummaryStats(siteID: sampleSiteID,
817874
date: "2022-12-09",

0 commit comments

Comments
 (0)