diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubView.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubView.swift index 6262247b783..901336f276d 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubView.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubView.swift @@ -15,8 +15,11 @@ final class AnalyticsHubHostingViewController: UIHostingController() + /// Analytics Usage Tracks Event Emitter + /// + private let usageTracksEventEmitter: StoreStatsUsageTracksEventEmitter + init(siteID: Int64, statsTimeRange: StatsTimeRangeV4, + usageTracksEventEmitter: StoreStatsUsageTracksEventEmitter, stores: StoresManager = ServiceLocator.stores) { let selectedType = AnalyticsHubTimeRangeSelection.SelectionType(statsTimeRange) let timeRangeSelection = AnalyticsHubTimeRangeSelection(selectionType: selectedType) @@ -22,7 +27,8 @@ final class AnalyticsHubViewModel: ObservableObject { self.stores = stores self.timeRangeSelectionType = selectedType self.timeRangeSelection = timeRangeSelection - self.timeRangeCard = AnalyticsHubViewModel.timeRangeCard(timeRangeSelection: timeRangeSelection) + self.timeRangeCard = AnalyticsHubViewModel.timeRangeCard(timeRangeSelection: timeRangeSelection, usageTracksEventEmitter: usageTracksEventEmitter) + self.usageTracksEventEmitter = usageTracksEventEmitter bindViewModelsWithData() } @@ -84,6 +90,12 @@ final class AnalyticsHubViewModel: ObservableObject { DDLogWarn("⚠️ Error fetching analytics data: \(error)") } } + + /// Tracks interactions for analytics usage event + /// + func trackAnalyticsInteraction() { + usageTracksEventEmitter.interacted() + } } // MARK: Networking @@ -189,7 +201,8 @@ private extension AnalyticsHubViewModel { .sink { [weak self] newSelectionType in guard let self else { return } self.timeRangeSelection = AnalyticsHubTimeRangeSelection(selectionType: newSelectionType) - self.timeRangeCard = AnalyticsHubViewModel.timeRangeCard(timeRangeSelection: self.timeRangeSelection) + self.timeRangeCard = AnalyticsHubViewModel.timeRangeCard(timeRangeSelection: self.timeRangeSelection, + usageTracksEventEmitter: self.usageTracksEventEmitter) Task.init { await self.updateData() } @@ -266,10 +279,12 @@ private extension AnalyticsHubViewModel { } } - static func timeRangeCard(timeRangeSelection: AnalyticsHubTimeRangeSelection) -> AnalyticsTimeRangeCardViewModel { + static func timeRangeCard(timeRangeSelection: AnalyticsHubTimeRangeSelection, + usageTracksEventEmitter: StoreStatsUsageTracksEventEmitter) -> AnalyticsTimeRangeCardViewModel { return AnalyticsTimeRangeCardViewModel(selectedRangeTitle: timeRangeSelection.rangeSelectionDescription, currentRangeSubtitle: timeRangeSelection.currentRangeDescription, - previousRangeSubtitle: timeRangeSelection.previousRangeDescription) + previousRangeSubtitle: timeRangeSelection.previousRangeDescription, + usageTracksEventEmitter: usageTracksEventEmitter) } } diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsTimeRangeCard.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsTimeRangeCard.swift index a2c27d89696..ea5b3486921 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsTimeRangeCard.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsTimeRangeCard.swift @@ -11,10 +11,13 @@ struct AnalyticsTimeRangeCard: View { @State private var showTimeRangeSelectionView: Bool = false + private let usageTracksEventEmitter: StoreStatsUsageTracksEventEmitter + init(viewModel: AnalyticsTimeRangeCardViewModel, selectionType: Binding) { self.timeRangeTitle = viewModel.selectedRangeTitle self.currentRangeDescription = viewModel.currentRangeSubtitle self.previousRangeDescription = viewModel.previousRangeSubtitle + self.usageTracksEventEmitter = viewModel.usageTracksEventEmitter self._selectionType = selectionType } @@ -25,6 +28,7 @@ struct AnalyticsTimeRangeCard: View { items: AnalyticsHubTimeRangeSelection.SelectionType.allCases, contentKeyPath: \.description, selected: $selectionType) { selection in + usageTracksEventEmitter.interacted() ServiceLocator.analytics.track(event: .AnalyticsHub.dateRangeOptionSelected(selection.rawValue)) } } @@ -33,6 +37,7 @@ struct AnalyticsTimeRangeCard: View { private func createTimeRangeContent() -> some View { VStack(alignment: .leading, spacing: Layout.verticalSpacing) { Button(action: { + usageTracksEventEmitter.interacted() ServiceLocator.analytics.track(event: .AnalyticsHub.dateRangeButtonTapped()) showTimeRangeSelectionView.toggle() }, label: { @@ -103,7 +108,8 @@ struct TimeRangeCard_Previews: PreviewProvider { static var previews: some View { let viewModel = AnalyticsTimeRangeCardViewModel(selectedRangeTitle: "Month to Date", currentRangeSubtitle: "Nov 1 - 23, 2022", - previousRangeSubtitle: "Oct 1 - 23, 2022") + previousRangeSubtitle: "Oct 1 - 23, 2022", + usageTracksEventEmitter: StoreStatsUsageTracksEventEmitter()) AnalyticsTimeRangeCard(viewModel: viewModel, selectionType: .constant(.monthToDate)) } } diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsTimeRangeCardViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsTimeRangeCardViewModel.swift index e73da58d30f..ba40e5133ad 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsTimeRangeCardViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsTimeRangeCardViewModel.swift @@ -15,4 +15,8 @@ struct AnalyticsTimeRangeCardViewModel { /// Previous Range Subtitle. /// let previousRangeSubtitle: String + + /// Analytics Usage Tracks Event Emitter + /// + let usageTracksEventEmitter: StoreStatsUsageTracksEventEmitter } diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsAndTopPerformersPeriodViewController.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsAndTopPerformersPeriodViewController.swift index 70ee4e08a50..259127dfbdb 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsAndTopPerformersPeriodViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsAndTopPerformersPeriodViewController.swift @@ -358,7 +358,7 @@ private extension StoreStatsAndTopPerformersPeriodViewController { @objc func seeMoreButtonTapped() { ServiceLocator.analytics.track(event: .AnalyticsHub.seeMoreAnalyticsTapped()) - let analyticsHubVC = AnalyticsHubHostingViewController(siteID: siteID, timeRange: timeRange) + let analyticsHubVC = AnalyticsHubHostingViewController(siteID: siteID, timeRange: timeRange, usageTracksEventEmitter: usageTracksEventEmitter) show(analyticsHubVC, sender: self) } } diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsUsageTracksEventEmitter.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsUsageTracksEventEmitter.swift index 52577451d1e..41657070376 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsUsageTracksEventEmitter.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Stats v4/StoreStatsUsageTracksEventEmitter.swift @@ -4,15 +4,18 @@ import Foundation /// considered as a _usage_ of the UI. /// /// See p91TBi-6Cl-p2 for more information about the algorithm. +/// See pe5pgL-153-p2 for background about adding Analytics Hub interactions to the algorithm. /// /// The UI should call `interacted` when these events happen: /// -/// - Scrolling -/// - Pull-to-refresh +/// - Scrolling (My Store or Analytics) +/// - Pull-to-refresh (My Store or Analytics) /// - Tapping on the bars in the chart /// - Changing the tab /// - Navigating to the My Store tab /// - Tapping on a product in the Top Performers list +/// - Tapping on the Analytics date range +/// - Selecting an Analytics date range option /// /// If we ever change the algorithm in the future, we should probably consider renaming the Tracks event to avoid /// incorrect comparisons with old events. We should also make sure to change the Android code if we're changing anything @@ -35,6 +38,8 @@ final class StoreStatsUsageTracksEventEmitter { /// - Changing the tab /// - Navigating to the My Store tab /// - Tapping on a product in the Top Performers list + /// - Tapping on the date range in the Analytics Hub + /// - Selecting a date range option in the Analytics Hub private let interactionsThreshold = 5 /// The maximum number of seconds in between interactions before we will consider the diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModelTests.swift index 31a37f2ff16..a2f7d2f59d6 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModelTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModelTests.swift @@ -5,14 +5,18 @@ import Yosemite final class AnalyticsHubViewModelTests: XCTestCase { private var stores: MockStoresManager! + private var eventEmitter: StoreStatsUsageTracksEventEmitter! override func setUp() { stores = MockStoresManager(sessionManager: .makeForTesting()) + let analyticsProvider = MockAnalyticsProvider() + let analytics = WooAnalytics(analyticsProvider: analyticsProvider) + eventEmitter = StoreStatsUsageTracksEventEmitter(analytics: analytics) } func test_cards_viewmodels_show_correct_data_after_updating_from_network() async { // Given - let vm = AnalyticsHubViewModel(siteID: 123, statsTimeRange: .thisMonth, stores: stores) + let vm = AnalyticsHubViewModel(siteID: 123, statsTimeRange: .thisMonth, usageTracksEventEmitter: eventEmitter, stores: stores) stores.whenReceivingAction(ofType: StatsActionV4.self) { action in switch action { @@ -43,7 +47,7 @@ final class AnalyticsHubViewModelTests: XCTestCase { func test_cards_viewmodels_show_sync_error_after_getting_error_from_network() async { // Given - let vm = AnalyticsHubViewModel(siteID: 123, statsTimeRange: .thisMonth, stores: stores) + let vm = AnalyticsHubViewModel(siteID: 123, statsTimeRange: .thisMonth, usageTracksEventEmitter: eventEmitter, stores: stores) stores.whenReceivingAction(ofType: StatsActionV4.self) { action in switch action { case let .retrieveCustomStats(_, _, _, _, _, _, completion): @@ -67,7 +71,7 @@ final class AnalyticsHubViewModelTests: XCTestCase { func test_cards_viewmodels_redacted_while_updating_from_network() async { // Given - let vm = AnalyticsHubViewModel(siteID: 123, statsTimeRange: .thisMonth, stores: stores) + let vm = AnalyticsHubViewModel(siteID: 123, statsTimeRange: .thisMonth, usageTracksEventEmitter: eventEmitter, stores: stores) var loadingRevenueCard: AnalyticsReportCardViewModel? var loadingOrdersCard: AnalyticsReportCardViewModel? var loadingProductsCard: AnalyticsProductCardViewModel?