diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModel.swift index 9157ef364d7..915019cbeea 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModel.swift @@ -126,7 +126,10 @@ private extension AnalyticsHubViewModel { try await self.retrieveOrderStats(currentTimeRange: currentTimeRange, previousTimeRange: previousTimeRange) } group.addTask { - try await self.retrieveVisitorStats(currentTimeRange: currentTimeRange, previousTimeRange: previousTimeRange) + try await self.retrieveItemsSoldStats(currentTimeRange: currentTimeRange, previousTimeRange: previousTimeRange) + } + group.addTask { + try await self.retrieveSiteStats(currentTimeRange: currentTimeRange) } try await group.waitForAll() } @@ -147,7 +150,7 @@ private extension AnalyticsHubViewModel { } @MainActor - func retrieveVisitorStats(currentTimeRange: AnalyticsHubTimeRange, previousTimeRange: AnalyticsHubTimeRange) async throws { + func retrieveItemsSoldStats(currentTimeRange: AnalyticsHubTimeRange, previousTimeRange: AnalyticsHubTimeRange) async throws { async let itemsSoldRequest = retrieveTopItemsSoldStats(earliestDateToInclude: currentTimeRange.start, latestDateToInclude: currentTimeRange.end, forceRefresh: true) @@ -156,6 +159,14 @@ private extension AnalyticsHubViewModel { self.itemsSoldStats = itemsSoldStats } + @MainActor + func retrieveSiteStats(currentTimeRange: AnalyticsHubTimeRange) async throws { + async let siteStatsRequest = retrieveSiteSummaryStats(latestDateToInclude: currentTimeRange.end) + + let summaryStats = try await siteStatsRequest + self.siteStats = summaryStats + } + @MainActor func retrieveStats(earliestDateToInclude: Date, latestDateToInclude: Date, @@ -191,6 +202,25 @@ private extension AnalyticsHubViewModel { stores.dispatch(action) } } + + @MainActor + /// Retrieves site summary stats using the `retrieveSiteSummaryStats` action. + /// + func retrieveSiteSummaryStats(latestDateToInclude: Date) async throws -> SiteSummaryStats? { + guard let period = timeRangeSelectionType.period else { + return nil + } + + return try await withCheckedThrowingContinuation { continuation in + let action = StatsActionV4.retrieveSiteSummaryStats(siteID: siteID, + period: period, + quantity: timeRangeSelectionType.quantity, + latestDateToInclude: latestDateToInclude) { result in + continuation.resume(with: result) + } + stores.dispatch(action) + } + } } // MARK: Data - UI mapping @@ -202,6 +232,7 @@ private extension AnalyticsHubViewModel { self.ordersCard = ordersCard.redacted self.productsStatsCard = productsStatsCard.redacted self.itemsSoldCard = itemsSoldCard.redacted + self.sessionsCard = sessionsCard.redacted } @MainActor @@ -209,6 +240,7 @@ private extension AnalyticsHubViewModel { self.currentOrderStats = nil self.previousOrderStats = nil self.itemsSoldStats = nil + self.siteStats = nil } func bindViewModelsWithData() { @@ -229,8 +261,8 @@ private extension AnalyticsHubViewModel { self.itemsSoldCard = AnalyticsHubViewModel.productsItemsSoldCard(itemsSoldStats: itemsSoldStats) }.store(in: &subscriptions) - Publishers.CombineLatest($currentOrderStats, $siteStats) - .sink { [weak self] currentOrderStats, siteStats in + $currentOrderStats.zip($siteStats) + .sink { [weak self] (currentOrderStats, siteStats) in guard let self else { return } self.sessionsCard = AnalyticsHubViewModel.sessionsCard(currentPeriodStats: currentOrderStats, siteStats: siteStats) diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/Time Range/AnalyticsHubTimeRangeSelection.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/Time Range/AnalyticsHubTimeRangeSelection.swift index 45f63fbd325..1292fb149ca 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/Time Range/AnalyticsHubTimeRangeSelection.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/Time Range/AnalyticsHubTimeRangeSelection.swift @@ -126,6 +126,38 @@ extension AnalyticsHubTimeRangeSelection { } } + /// The period used to request site summary stats from the given SelectedType. + /// + /// Returns `nil` if there isn't a `StatGranularity` period that can be used to fetch stats for the given SelectedType. + /// + var period: StatGranularity? { + switch self { + case .custom: + return nil + case .today, .yesterday: + return .day + case .weekToDate, .lastWeek: + return .week + case .monthToDate, .lastMonth, .quarterToDate, .lastQuarter: + return .month + case .yearToDate, .lastYear: + return .year + } + } + + /// The quantity of periods used to request site summary stats from the given SelectedType. + /// + /// Defaults to 1 (a single period) except for ranges not matching a `StatGranularity` period. + /// + var quantity: Int { + switch self { + case .quarterToDate, .lastQuarter: + return 3 // Stats summary calculated from 3 months of data + default: + return 1 + } + } + init(_ statsTimeRange: StatsTimeRangeV4) { switch statsTimeRange { case .today: diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModelTests.swift index d09f4e870c6..6e51f4ebb96 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModelTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModelTests.swift @@ -26,6 +26,9 @@ final class AnalyticsHubViewModelTests: XCTestCase { case let .retrieveTopEarnerStats(_, _, _, _, _, _, _, completion): let topEarners = TopEarnerStats.fake().copy(items: [.fake()]) completion(.success(topEarners)) + case let .retrieveSiteSummaryStats(_, _, _, _, completion): + let siteStats = SiteSummaryStats.fake().copy(visitors: 30, views: 53) + completion(.success(siteStats)) default: break } @@ -39,12 +42,14 @@ final class AnalyticsHubViewModelTests: XCTestCase { XCTAssertFalse(vm.ordersCard.isRedacted) XCTAssertFalse(vm.productsStatsCard.isRedacted) XCTAssertFalse(vm.itemsSoldCard.isRedacted) + XCTAssertFalse(vm.sessionsCard.isRedacted) XCTAssertEqual(vm.revenueCard.leadingValue, "$62") XCTAssertEqual(vm.ordersCard.leadingValue, "15") XCTAssertEqual(vm.productsStatsCard.itemsSold, "5") - XCTAssertEqual(vm.itemsSoldCard.itemsSoldData.count, 1) + XCTAssertEqual(vm.sessionsCard.leadingValue, "53") + XCTAssertEqual(vm.sessionsCard.trailingValue, "50%") } func test_cards_viewmodels_show_sync_error_after_getting_error_from_network() async { @@ -56,6 +61,8 @@ final class AnalyticsHubViewModelTests: XCTestCase { completion(.failure(NSError(domain: "Test", code: 1))) case let .retrieveTopEarnerStats(_, _, _, _, _, _, _, completion): completion(.failure(NSError(domain: "Test", code: 1))) + case let .retrieveSiteSummaryStats(_, _, _, _, completion): + completion(.failure(NSError(domain: "Test", code: 1))) default: break } @@ -69,6 +76,7 @@ final class AnalyticsHubViewModelTests: XCTestCase { XCTAssertTrue(vm.ordersCard.showSyncError) XCTAssertTrue(vm.productsStatsCard.showStatsError) XCTAssertTrue(vm.itemsSoldCard.showItemsSoldError) + XCTAssertTrue(vm.sessionsCard.showSyncError) } func test_cards_viewmodels_redacted_while_updating_from_network() async { @@ -78,6 +86,7 @@ final class AnalyticsHubViewModelTests: XCTestCase { var loadingOrdersCard: AnalyticsReportCardViewModel? var loadingProductsCard: AnalyticsProductsStatsCardViewModel? var loadingItemsSoldCard: AnalyticsItemsSoldViewModel? + var loadingSessionsCard: AnalyticsReportCardCurrentPeriodViewModel? stores.whenReceivingAction(ofType: StatsActionV4.self) { action in switch action { case let .retrieveCustomStats(_, _, _, _, _, _, completion): @@ -90,6 +99,10 @@ final class AnalyticsHubViewModelTests: XCTestCase { case let .retrieveTopEarnerStats(_, _, _, _, _, _, _, completion): let topEarners = TopEarnerStats.fake().copy(items: [.fake()]) completion(.success(topEarners)) + case let .retrieveSiteSummaryStats(_, _, _, _, completion): + let siteStats = SiteSummaryStats.fake() + loadingSessionsCard = vm.sessionsCard + completion(.success(siteStats)) default: break } @@ -103,5 +116,6 @@ final class AnalyticsHubViewModelTests: XCTestCase { XCTAssertEqual(loadingOrdersCard?.isRedacted, true) XCTAssertEqual(loadingProductsCard?.isRedacted, true) XCTAssertEqual(loadingItemsSoldCard?.isRedacted, true) + XCTAssertEqual(loadingSessionsCard?.isRedacted, true) } }