diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 3af96d5c66f..ae8cac89e65 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -8,6 +8,8 @@ - [*] Orders with Coupons: Users can now select a coupon from a list when adding it to an order. [https://github.com/woocommerce/woocommerce-ios/pull/10255] - [Internal] Orders: Improved error message when orders can't be loaded due to a parsing (decoding) error. [https://github.com/woocommerce/woocommerce-ios/pull/10252] - [**] Product discounts: Users can now add discounts to products when creating an order. [https://github.com/woocommerce/woocommerce-ios/pull/10244] +- [Internal] Fixed a bug preventing the "We couldn't load your data" error banner from appearing on the My store dashboard. [https://github.com/woocommerce/woocommerce-ios/pull/10262] + - [Internal] A new way to create a product from an image using AI is being A/B tested. [https://github.com/woocommerce/woocommerce-ios/pull/10253] 14.5 diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewController.swift b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewController.swift index b479018a56b..383b41bbb46 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewController.swift @@ -931,6 +931,7 @@ private extension DashboardViewController { func onPullToRefresh() async { ServiceLocator.analytics.track(.dashboardPulledToRefresh) + hideTopBannerView() // Hide error banner optimistically on pull to refresh await withTaskGroup(of: Void.self) { group in group.addTask { [weak self] in guard let self else { return } diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift index 824a8beff9e..e8e103e8ec8 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift @@ -113,6 +113,11 @@ final class DashboardViewModel { timeRange: StatsTimeRangeV4, latestDateToInclude: Date, onCompletion: ((Result) -> Void)? = nil) { + guard stores.isAuthenticatedWithoutWPCom == false else { // Visit stats are only available for stores connected to WPCom + onCompletion?(.success(())) + return + } + let action = StatsActionV4.retrieveSiteVisitStats(siteID: siteID, siteTimezone: siteTimezone, timeRange: timeRange, @@ -132,6 +137,11 @@ final class DashboardViewModel { timeRange: StatsTimeRangeV4, latestDateToInclude: Date, onCompletion: ((Result) -> Void)? = nil) { + guard stores.isAuthenticatedWithoutWPCom == false else { // Summary stats are only available for stores connected to WPCom + onCompletion?(.success(())) + return + } + let action = StatsActionV4.retrieveSiteSummaryStats(siteID: siteID, siteTimezone: siteTimezone, period: timeRange.summaryStatsGranularity, diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsAndTopPerformersViewController.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsAndTopPerformersViewController.swift index e0c21105131..8e73c3e527a 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsAndTopPerformersViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsAndTopPerformersViewController.swift @@ -238,9 +238,9 @@ private extension StoreStatsAndTopPerformersViewController { DDLogError("⛔️ Error synchronizing order stats: \(error)") periodSyncError = error } - group.leave() periodGroup.leave() periodStoreStatsGroup.leave() + group.leave() // Leave this group last so `syncError` is set, if needed } group.enter() @@ -254,9 +254,9 @@ private extension StoreStatsAndTopPerformersViewController { DDLogError("⛔️ Error synchronizing visitor stats: \(error)") periodSyncError = error } - group.leave() periodGroup.leave() periodStoreStatsGroup.leave() + group.leave() // Leave this group last so `syncError` is set, if needed } group.enter() @@ -270,9 +270,9 @@ private extension StoreStatsAndTopPerformersViewController { DDLogError("⛔️ Error synchronizing summary stats: \(error)") periodSyncError = error } - group.leave() periodGroup.leave() periodStoreStatsGroup.leave() + group.leave() // Leave this group last so `syncError` is set, if needed } group.enter() @@ -286,8 +286,8 @@ private extension StoreStatsAndTopPerformersViewController { DDLogError("⛔️ Error synchronizing top earners stats: \(error)") periodSyncError = error } - group.leave() periodGroup.leave() + group.leave() // Leave this group last so `syncError` is set, if needed vc.removeTopPerformersGhostContent() } diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/DashboardViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/DashboardViewModelTests.swift index 89ab03a220d..4bb5f1921d6 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/DashboardViewModelTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/DashboardViewModelTests.swift @@ -773,6 +773,36 @@ final class DashboardViewModelTests: XCTestCase { let dictionary = try XCTUnwrap(userDefaults[.hasDismissedBlazeBanner] as? [String: Bool]) XCTAssertEqual(dictionary["\(sampleSiteID)"], true) } + + func test_wpcom_stats_not_synced_when_authenticated_without_wpcom() { + // Given + stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: true, isWPCom: false)) + stores.whenReceivingAction(ofType: StatsActionV4.self) { action in + switch action { + case .retrieveSiteVisitStats, .retrieveSiteSummaryStats: + XCTFail("WPCom stats should not be synced when store is authenticated without WPCom") + default: + XCTFail("Received unsupported action: \(action)") + } + } + let viewModel = DashboardViewModel(siteID: sampleSiteID, stores: stores) + + // When + let siteVisitStatsResult: Result = waitFor { promise in + viewModel.syncSiteVisitStats(for: self.sampleSiteID, siteTimezone: .current, timeRange: .thisMonth, latestDateToInclude: .init()) { result in + promise(result) + } + } + let siteSummaryStatsResult: Result = waitFor { promise in + viewModel.syncSiteSummaryStats(for: self.sampleSiteID, siteTimezone: .current, timeRange: .thisMonth, latestDateToInclude: .init()) { result in + promise(result) + } + } + + // Then + XCTAssertTrue(siteVisitStatsResult.isSuccess) + XCTAssertTrue(siteSummaryStatsResult.isSuccess) + } } private extension DashboardViewModelTests {