Skip to content

Commit 14bff8e

Browse files
authored
Merge pull request #8270 from woocommerce/issue/8213-analytics-empty-state
[Analytics Hub] Display error messages after an error retrieving order stats
2 parents 0af536a + ff7f715 commit 14bff8e

File tree

6 files changed

+119
-14
lines changed

6 files changed

+119
-14
lines changed

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ private extension AnalyticsHubViewModel {
154154
}
155155

156156
static func revenueCard(currentPeriodStats: OrderStatsV4?, previousPeriodStats: OrderStatsV4?) -> AnalyticsReportCardViewModel {
157+
let showSyncError = currentPeriodStats == nil || previousPeriodStats == nil
157158
let totalDelta = StatsDataTextFormatter.createTotalRevenueDelta(from: previousPeriodStats, to: currentPeriodStats)
158159
let netDelta = StatsDataTextFormatter.createNetRevenueDelta(from: previousPeriodStats, to: currentPeriodStats)
159160

@@ -169,10 +170,13 @@ private extension AnalyticsHubViewModel {
169170
trailingDelta: netDelta.string,
170171
trailingDeltaColor: Constants.deltaColor(for: netDelta.direction),
171172
trailingChartData: StatsIntervalDataParser.getChartData(for: .netRevenue, from: currentPeriodStats),
172-
isRedacted: false)
173+
isRedacted: false,
174+
showSyncError: showSyncError,
175+
syncErrorMessage: Localization.RevenueCard.noRevenue)
173176
}
174177

175178
static func ordersCard(currentPeriodStats: OrderStatsV4?, previousPeriodStats: OrderStatsV4?) -> AnalyticsReportCardViewModel {
179+
let showSyncError = currentPeriodStats == nil || previousPeriodStats == nil
176180
let ordersCountDelta = StatsDataTextFormatter.createOrderCountDelta(from: previousPeriodStats, to: currentPeriodStats)
177181
let orderValueDelta = StatsDataTextFormatter.createAverageOrderValueDelta(from: previousPeriodStats, to: currentPeriodStats)
178182

@@ -188,10 +192,13 @@ private extension AnalyticsHubViewModel {
188192
trailingDelta: orderValueDelta.string,
189193
trailingDeltaColor: Constants.deltaColor(for: orderValueDelta.direction),
190194
trailingChartData: StatsIntervalDataParser.getChartData(for: .averageOrderValue, from: currentPeriodStats),
191-
isRedacted: false)
195+
isRedacted: false,
196+
showSyncError: showSyncError,
197+
syncErrorMessage: Localization.OrderCard.noOrders)
192198
}
193199

194200
static func productCard(currentPeriodStats: OrderStatsV4?, previousPeriodStats: OrderStatsV4?) -> AnalyticsProductCardViewModel {
201+
let showSyncError = currentPeriodStats == nil || previousPeriodStats == nil
195202
let itemsSold = StatsDataTextFormatter.createItemsSoldText(orderStats: currentPeriodStats)
196203
let itemsSoldDelta = StatsDataTextFormatter.createOrderItemsSoldDelta(from: previousPeriodStats, to: currentPeriodStats)
197204

@@ -205,7 +212,8 @@ private extension AnalyticsHubViewModel {
205212
.init(imageURL: imageURL, name: "Love Ficus", details: "Net Sales: $599", value: "5"),
206213
.init(imageURL: imageURL, name: "Bird Of Paradise", details: "Net Sales: $23.50", value: "2")
207214
],
208-
isRedacted: false)
215+
isRedacted: false,
216+
showSyncError: showSyncError)
209217
}
210218

211219
static func timeRangeCard(timeRangeSelection: AnalyticsHubTimeRangeSelection) -> AnalyticsTimeRangeCardViewModel {
@@ -233,12 +241,16 @@ private extension AnalyticsHubViewModel {
233241
static let title = NSLocalizedString("REVENUE", comment: "Title for revenue analytics section in the Analytics Hub")
234242
static let leadingTitle = NSLocalizedString("Total Sales", comment: "Label for total sales (gross revenue) in the Analytics Hub")
235243
static let trailingTitle = NSLocalizedString("Net Sales", comment: "Label for net sales (net revenue) in the Analytics Hub")
244+
static let noRevenue = NSLocalizedString("Unable to load revenue analytics",
245+
comment: "Text displayed when there is an error loading revenue stats data.")
236246
}
237247

238248
enum OrderCard {
239249
static let title = NSLocalizedString("ORDERS", comment: "Title for order analytics section in the Analytics Hub")
240250
static let leadingTitle = NSLocalizedString("Total Orders", comment: "Label for total number of orders in the Analytics Hub")
241251
static let trailingTitle = NSLocalizedString("Average Order Value", comment: "Label for average value of orders in the Analytics Hub")
252+
static let noOrders = NSLocalizedString("Unable to load order analytics",
253+
comment: "Text displayed when there is an error loading order stats data.")
242254
}
243255
}
244256
}

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ struct AnalyticsProductCard: View {
2222
///
2323
let isRedacted: Bool
2424

25+
/// Indicates if there was an error loading the data for the card
26+
///
27+
let showSyncError: Bool
28+
2529
var body: some View {
2630
VStack(alignment: .leading) {
2731

@@ -46,8 +50,17 @@ struct AnalyticsProductCard: View {
4650
.shimmering(active: isRedacted)
4751
}
4852

53+
if showSyncError {
54+
Text(Localization.noProducts)
55+
.foregroundColor(Color(.text))
56+
.subheadlineStyle()
57+
.frame(maxWidth: .infinity, alignment: .leading)
58+
.padding(.top, Layout.columnSpacing)
59+
}
60+
4961
TopPerformersView(itemTitle: Localization.title.localizedCapitalized, valueTitle: Localization.itemsSold, rows: itemsSoldData)
5062
.padding(.top, Layout.columnSpacing)
63+
5164
}
5265
.padding(Layout.cardPadding)
5366
}
@@ -58,6 +71,8 @@ private extension AnalyticsProductCard {
5871
enum Localization {
5972
static let title = NSLocalizedString("Products", comment: "Title for the products card on the analytics hub screen.").localizedUppercase
6073
static let itemsSold = NSLocalizedString("Items Sold", comment: "Title for the items sold column on the products card on the analytics hub screen.")
74+
static let noProducts = NSLocalizedString("Unable to load product analytics",
75+
comment: "Text displayed when there is an error loading product stats data.")
6176
}
6277

6378
enum Layout {
@@ -81,7 +96,17 @@ struct AnalyticsProductCardPreviews: PreviewProvider {
8196
.init(imageURL: imageURL, name: "Love Ficus", details: "Net Sales: $599", value: "5"),
8297
.init(imageURL: imageURL, name: "Bird Of Paradise", details: "Net Sales: $23.50", value: "2"),
8398
],
84-
isRedacted: false)
99+
isRedacted: false,
100+
showSyncError: false)
101+
.previewLayout(.sizeThatFits)
102+
103+
AnalyticsProductCard(itemsSold: "-",
104+
delta: "0%",
105+
deltaBackgroundColor: .withColorStudio(.gray, shade: .shade0),
106+
itemsSoldData: [],
107+
isRedacted: false,
108+
showSyncError: true)
85109
.previewLayout(.sizeThatFits)
110+
.previewDisplayName("No data")
86111
}
87112
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ struct AnalyticsProductCardViewModel {
2424
/// Indicates if the values should be hidden (for loading state)
2525
///
2626
let isRedacted: Bool
27+
28+
/// Indicates if there was an error loading the data for the card
29+
///
30+
let showSyncError: Bool
2731
}
2832

2933
extension AnalyticsProductCardViewModel {
@@ -36,7 +40,8 @@ extension AnalyticsProductCardViewModel {
3640
delta: "+50%",
3741
deltaBackgroundColor: .lightGray,
3842
itemsSoldData: [.init(imageURL: nil, name: "Product Name", details: "Net Sales", value: "$5678")],
39-
isRedacted: true)
43+
isRedacted: true,
44+
showSyncError: false)
4045
}
4146

4247
}
@@ -50,5 +55,6 @@ extension AnalyticsProductCard {
5055
self.deltaBackgroundColor = viewModel.deltaBackgroundColor
5156
self.itemsSoldData = viewModel.itemsSoldData
5257
self.isRedacted = viewModel.isRedacted
58+
self.showSyncError = viewModel.showSyncError
5359
}
5460
}

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ struct AnalyticsReportCard: View {
1818

1919
let isRedacted: Bool
2020

21+
let showSyncError: Bool
22+
let syncErrorMessage: String
23+
2124
// Layout metrics that scale based on accessibility changes
2225
@ScaledMetric private var scaledChartWidth: CGFloat = Layout.chartWidth
2326
@ScaledMetric private var scaledChartHeight: CGFloat = Layout.chartHeight
@@ -79,6 +82,13 @@ struct AnalyticsReportCard: View {
7982
}
8083
.frame(maxWidth: .infinity, alignment: .leading)
8184
}
85+
86+
if showSyncError {
87+
Text(syncErrorMessage)
88+
.foregroundColor(Color(.text))
89+
.subheadlineStyle()
90+
.frame(maxWidth: .infinity, alignment: .leading)
91+
}
8292
}
8393
.padding(Layout.cardPadding)
8494
}
@@ -110,7 +120,26 @@ struct Previews: PreviewProvider {
110120
trailingDelta: "-3%",
111121
trailingDeltaColor: .withColorStudio(.red, shade: .shade40),
112122
trailingChartData: [50.0, 15.0, 20.0, 2.0, 10.0, 0.0, 40.0, 15.0, 20.0, 2.0, 10.0, 0.0],
113-
isRedacted: false)
123+
isRedacted: false,
124+
showSyncError: false,
125+
syncErrorMessage: "")
126+
.previewLayout(.sizeThatFits)
127+
128+
AnalyticsReportCard(title: "REVENUE",
129+
leadingTitle: "Total Sales",
130+
leadingValue: "-",
131+
leadingDelta: "0%",
132+
leadingDeltaColor: .withColorStudio(.gray, shade: .shade0),
133+
leadingChartData: [],
134+
trailingTitle: "Net Sales",
135+
trailingValue: "-",
136+
trailingDelta: "0%",
137+
trailingDeltaColor: .withColorStudio(.gray, shade: .shade0),
138+
trailingChartData: [],
139+
isRedacted: false,
140+
showSyncError: true,
141+
syncErrorMessage: "Error loading revenue analytics")
114142
.previewLayout(.sizeThatFits)
143+
.previewDisplayName("No data")
115144
}
116145
}

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ struct AnalyticsReportCardViewModel {
5252
/// Indicates if the values should be hidden (for loading state)
5353
///
5454
let isRedacted: Bool
55+
56+
/// Indicates if there was an error loading the data for the card
57+
///
58+
let showSyncError: Bool
59+
60+
/// Message to display if there was an error loading the data for the card
61+
///
62+
let syncErrorMessage: String
5563
}
5664

5765
extension AnalyticsReportCardViewModel {
@@ -71,7 +79,9 @@ extension AnalyticsReportCardViewModel {
7179
trailingDelta: "+50%",
7280
trailingDeltaColor: .lightGray,
7381
trailingChartData: [],
74-
isRedacted: true)
82+
isRedacted: true,
83+
showSyncError: false,
84+
syncErrorMessage: "")
7585
}
7686
}
7787

@@ -91,5 +101,7 @@ extension AnalyticsReportCard {
91101
self.trailingDeltaColor = viewModel.trailingDeltaColor
92102
self.trailingChartData = viewModel.trailingChartData
93103
self.isRedacted = viewModel.isRedacted
104+
self.showSyncError = viewModel.showSyncError
105+
self.syncErrorMessage = viewModel.syncErrorMessage
94106
}
95107
}

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

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ final class AnalyticsHubViewModelTests: XCTestCase {
3333
XCTAssertEqual(vm.productCard.itemsSold, "5")
3434
}
3535

36-
func test_cards_viewmodels_show_empty_data_after_getting_error_from_network() async {
36+
func test_cards_viewmodels_show_sync_error_after_getting_error_from_network() async {
3737
// Given
3838
let vm = AnalyticsHubViewModel(siteID: 123, statsTimeRange: .thisMonth, stores: stores)
3939
stores.whenReceivingAction(ofType: StatsActionV4.self) { action in
@@ -46,12 +46,33 @@ final class AnalyticsHubViewModelTests: XCTestCase {
4646
await vm.updateData()
4747

4848
// Then
49-
XCTAssertFalse(vm.revenueCard.isRedacted)
50-
XCTAssertFalse(vm.ordersCard.isRedacted)
51-
XCTAssertFalse(vm.productCard.isRedacted)
49+
XCTAssertTrue(vm.revenueCard.showSyncError)
50+
XCTAssertTrue(vm.ordersCard.showSyncError)
51+
XCTAssertTrue(vm.productCard.showSyncError)
52+
}
5253

53-
XCTAssertEqual(vm.revenueCard.leadingValue, "-")
54-
XCTAssertEqual(vm.ordersCard.leadingValue, "-")
55-
XCTAssertEqual(vm.productCard.itemsSold, "-")
54+
func test_cards_viewmodels_redacted_while_updating_from_network() async {
55+
// Given
56+
let vm = AnalyticsHubViewModel(siteID: 123, statsTimeRange: .thisMonth, stores: stores)
57+
let stats = OrderStatsV4.fake().copy(totals: .fake().copy(totalOrders: 15, totalItemsSold: 5, grossRevenue: 62))
58+
var loadingRevenueCard: AnalyticsReportCardViewModel?
59+
var loadingOrdersCard: AnalyticsReportCardViewModel?
60+
var loadingProductsCard: AnalyticsProductCardViewModel?
61+
stores.whenReceivingAction(ofType: StatsActionV4.self) { action in
62+
if case let .retrieveCustomStats(_, _, _, _, _, _, completion) = action {
63+
loadingRevenueCard = vm.revenueCard
64+
loadingOrdersCard = vm.ordersCard
65+
loadingProductsCard = vm.productCard
66+
completion(.success(stats))
67+
}
68+
}
69+
70+
// When
71+
await vm.updateData()
72+
73+
// Then
74+
XCTAssertEqual(loadingRevenueCard?.isRedacted, true)
75+
XCTAssertEqual(loadingOrdersCard?.isRedacted, true)
76+
XCTAssertEqual(loadingProductsCard?.isRedacted, true)
5677
}
5778
}

0 commit comments

Comments
 (0)