Skip to content

Commit f3c3df5

Browse files
authored
Merge pull request #5939 from woocommerce/feat/5745-visitor-conversion-states
Update visitor and conversion stats view states
2 parents 07b8c1c + 3c4cde3 commit f3c3df5

File tree

3 files changed

+306
-70
lines changed

3 files changed

+306
-70
lines changed

WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsPeriodViewModel.swift

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ final class StoreStatsPeriodViewModel {
1515
/// Updated externally from user interactions with the chart.
1616
@Published var selectedIntervalIndex: Int? = nil
1717

18+
/// Updated externally from visitor stats availability.
19+
@Published var siteVisitStatsMode: SiteVisitStatsMode = .default
20+
1821
/// Emits order stats text values based on order stats and selected time interval.
1922
private(set) lazy var orderStatsText: AnyPublisher<String, Never> =
2023
Publishers.CombineLatest($orderStatsData.eraseToAnyPublisher(), $selectedIntervalIndex.eraseToAnyPublisher())
@@ -64,6 +67,24 @@ final class StoreStatsPeriodViewModel {
6467
shouldReloadChartAnimated.eraseToAnyPublisher()
6568
}
6669

70+
/// Emits the view state for visitor stats based on the visitor stats mode and the selected time interval.
71+
private(set) lazy var visitorStatsViewState: AnyPublisher<StoreStatsDataOrRedactedView.State, Never> =
72+
Publishers.CombineLatest($siteVisitStatsMode.eraseToAnyPublisher(), $selectedIntervalIndex.eraseToAnyPublisher())
73+
.compactMap { [weak self] siteVisitStatsMode, selectedIntervalIndex in
74+
return self?.visitorStatsViewState(siteVisitStatsMode: siteVisitStatsMode, selectedIntervalIndex: selectedIntervalIndex)
75+
}
76+
.removeDuplicates()
77+
.eraseToAnyPublisher()
78+
79+
/// Emits the view state for conversion stats based on the visitor stats view state.
80+
private(set) lazy var conversionStatsViewState: AnyPublisher<StoreStatsDataOrRedactedView.State, Never> =
81+
visitorStatsViewState
82+
.compactMap { [weak self] visitorStatsViewState in
83+
return self?.conversionStatsViewState(visitorStatsViewState: visitorStatsViewState)
84+
}
85+
.removeDuplicates()
86+
.eraseToAnyPublisher()
87+
6788
// MARK: - Private data
6889

6990
@Published private var siteStats: SiteVisitStats?
@@ -135,14 +156,16 @@ private extension StoreStatsPeriodViewModel {
135156
return StatsTimeRangeBarViewModel(startDate: startDate,
136157
endDate: endDate,
137158
timeRange: timeRange,
138-
timezone: siteTimezone)
159+
timezone: siteTimezone,
160+
isMyStoreTabUpdatesEnabled: true)
139161
}
140162
let date = orderStatsIntervals[selectedIndex].dateStart(timeZone: siteTimezone)
141163
return StatsTimeRangeBarViewModel(startDate: startDate,
142164
endDate: endDate,
143165
selectedDate: date,
144166
timeRange: timeRange,
145-
timezone: siteTimezone)
167+
timezone: siteTimezone,
168+
isMyStoreTabUpdatesEnabled: true)
146169
}
147170

148171
func createOrderStatsText(orderStatsData: OrderStatsData, selectedIntervalIndex: Int?) -> String {
@@ -172,17 +195,41 @@ private extension StoreStatsPeriodViewModel {
172195
func createConversionStats(orderStatsData: OrderStatsData, siteStats: SiteVisitStats?, selectedIntervalIndex: Int?) -> String {
173196
let visitors = visitorCount(at: selectedIntervalIndex, siteStats: siteStats)
174197
let orders = orderCount(at: selectedIntervalIndex, orderStats: orderStatsData.stats, orderStatsIntervals: orderStatsData.intervals)
175-
if let visitors = visitors, let orders = orders, visitors > 0 {
198+
199+
let numberFormatter = NumberFormatter()
200+
numberFormatter.numberStyle = .percent
201+
numberFormatter.minimumFractionDigits = 1
202+
203+
if let visitors = visitors, let orders = orders {
176204
// Maximum conversion rate is 100%.
177-
let conversionRate = min(orders/visitors, 1)
178-
let numberFormatter = NumberFormatter()
179-
numberFormatter.numberStyle = .percent
180-
numberFormatter.minimumFractionDigits = 1
205+
let conversionRate = visitors > 0 ? min(orders/visitors, 1): 0
181206
return numberFormatter.string(from: conversionRate as NSNumber) ?? Constants.placeholderText
182207
} else {
183208
return Constants.placeholderText
184209
}
185210
}
211+
212+
func visitorStatsViewState(siteVisitStatsMode: SiteVisitStatsMode, selectedIntervalIndex: Int?) -> StoreStatsDataOrRedactedView.State {
213+
switch siteVisitStatsMode {
214+
case .default:
215+
return timeRange == .today && selectedIntervalIndex != nil ? .redacted: .data
216+
case .redactedDueToJetpack:
217+
return .redactedDueToJetpack
218+
case .hidden:
219+
return .redacted
220+
}
221+
}
222+
223+
func conversionStatsViewState(visitorStatsViewState: StoreStatsDataOrRedactedView.State) -> StoreStatsDataOrRedactedView.State {
224+
switch visitorStatsViewState {
225+
case .data:
226+
return .data
227+
case .redactedDueToJetpack:
228+
return .redacted
229+
case .redacted:
230+
return .redacted
231+
}
232+
}
186233
}
187234

188235
// MARK: - Private data helpers

WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsV4PeriodViewController.swift

Lines changed: 19 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ final class StoreStatsV4PeriodViewController: UIViewController {
2222

2323
var siteVisitStatsMode: SiteVisitStatsMode = .default {
2424
didSet {
25-
updateSiteVisitStats(mode: siteVisitStatsMode)
25+
viewModel.siteVisitStatsMode = siteVisitStatsMode
2626
}
2727
}
2828

@@ -154,6 +154,8 @@ final class StoreStatsV4PeriodViewController: UIViewController {
154154
observeSelectedBarIndex()
155155
observeTimeRangeBarViewModel()
156156
observeReloadChartAnimated()
157+
observeVisitorStatsViewState()
158+
observeConversionStatsViewState()
157159
}
158160

159161
override func viewWillAppear(_ animated: Bool) {
@@ -204,8 +206,6 @@ private extension StoreStatsV4PeriodViewController {
204206
self.visitorsDataOrRedactedView.isHighlighted = isHighlighted
205207
self.conversionDataOrRedactedView.isHighlighted = isHighlighted
206208
self.revenueData.textColor = textColor
207-
208-
self.updateSiteVisitStatsAndConversionRate(selectedIndex: selectedIndex)
209209
}.store(in: &cancellables)
210210
}
211211

@@ -220,6 +220,22 @@ private extension StoreStatsV4PeriodViewController {
220220
self?.reloadChart(animateChart: animated)
221221
}.store(in: &cancellables)
222222
}
223+
224+
func observeVisitorStatsViewState() {
225+
viewModel.visitorStatsViewState
226+
.sink { [weak self] viewState in
227+
guard let self = self, self.visitorsDataOrRedactedView != nil else { return }
228+
self.visitorsDataOrRedactedView.state = viewState
229+
}.store(in: &cancellables)
230+
}
231+
232+
func observeConversionStatsViewState() {
233+
viewModel.conversionStatsViewState
234+
.sink { [weak self] viewState in
235+
guard let self = self, self.conversionDataOrRedactedView != nil else { return }
236+
self.conversionDataOrRedactedView.state = viewState
237+
}.store(in: &cancellables)
238+
}
223239
}
224240

225241
// MARK: - Public Interface
@@ -301,9 +317,6 @@ private extension StoreStatsV4PeriodViewController {
301317
// Data
302318
updateStatsDataToDefaultStyles()
303319

304-
// Visibility
305-
updateSiteVisitStats(mode: siteVisitStatsMode)
306-
307320
// Accessibility elements
308321
xAxisAccessibilityView.isAccessibilityElement = true
309322
xAxisAccessibilityView.accessibilityTraits = .staticText
@@ -400,14 +413,6 @@ private extension StoreStatsV4PeriodViewController {
400413
}
401414
}
402415

403-
// MARK: - UI Updates
404-
//
405-
private extension StoreStatsV4PeriodViewController {
406-
func updateSiteVisitStats(mode: SiteVisitStatsMode) {
407-
reloadSiteVisitUI()
408-
}
409-
}
410-
411416
// MARK: - ChartViewDelegate Conformance (Charts)
412417
//
413418
extension StoreStatsV4PeriodViewController: ChartViewDelegate {
@@ -433,36 +438,6 @@ private extension StoreStatsV4PeriodViewController {
433438
func updateUI(selectedBarIndex selectedIndex: Int?) {
434439
viewModel.selectedIntervalIndex = selectedIndex
435440
}
436-
437-
/// Updates visitor and conversion stats based on the selected bar index.
438-
///
439-
/// - Parameter selectedIndex: the index of interval data for the bar chart. Nil if no bar is selected.
440-
func updateSiteVisitStatsAndConversionRate(selectedIndex: Int?) {
441-
let mode: SiteVisitStatsMode
442-
443-
// Hides site visit stats for "today" when an interval bar is selected.
444-
if timeRange == .today, selectedIndex != nil {
445-
mode = .hidden
446-
} else {
447-
mode = siteVisitStatsMode
448-
}
449-
450-
updateSiteVisitStats(mode: mode)
451-
452-
switch siteVisitStatsMode {
453-
case .hidden, .redactedDueToJetpack:
454-
break
455-
case .default:
456-
guard selectedIndex != nil else {
457-
reloadSiteVisitUI()
458-
return
459-
}
460-
guard visitorsDataOrRedactedView != nil else {
461-
return
462-
}
463-
visitorsDataOrRedactedView.state = .data
464-
}
465-
}
466441
}
467442

468443
// MARK: - IAxisValueFormatter Conformance (Charts)
@@ -571,7 +546,6 @@ private extension StoreStatsV4PeriodViewController {
571546

572547
func reloadAllFields(animateChart: Bool = true) {
573548
viewModel.selectedIntervalIndex = nil
574-
reloadSiteVisitUI()
575549
reloadChart(animateChart: animateChart)
576550

577551
view.accessibilityElements = [ordersTitle as Any,
@@ -587,17 +561,6 @@ private extension StoreStatsV4PeriodViewController {
587561
chartAccessibilityView as Any]
588562
}
589563

590-
func reloadSiteVisitUI() {
591-
switch siteVisitStatsMode {
592-
case .hidden:
593-
visitorsDataOrRedactedView.state = .redacted
594-
case .redactedDueToJetpack:
595-
visitorsDataOrRedactedView.state = .redactedDueToJetpack
596-
case .default:
597-
visitorsDataOrRedactedView.state = .data
598-
}
599-
}
600-
601564
func reloadChart(animateChart: Bool = true) {
602565
guard lineChartView != nil else {
603566
return

0 commit comments

Comments
 (0)