From 42c79d22df054d9b3ef8f579cee7af0dd7111e78 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Tue, 17 Jun 2025 15:09:45 +0800 Subject: [PATCH 1/6] Track POS tab selected/reselected events. Reselected event likely won't be triggered. --- WooCommerce/Classes/Analytics/WooAnalyticsStat.swift | 2 ++ WooCommerce/Classes/ViewRelated/MainTabBarController.swift | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift index 0e923535c26..07ac952d9c5 100644 --- a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift +++ b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift @@ -1276,6 +1276,8 @@ enum WooAnalyticsStat: String { case backgroundUpdatesDisabled = "background_updates_disabled" // MARK: Point of Sale events + case pointOfSaleTabSelected = "main_tab_pos_selected" + case pointOfSaleTabReselected = "main_tab_pos_reselected" case pointOfSaleLoaded = "loaded" case pointOfSaleItemsFetched = "items_fetched" case pointOfSaleItemsPullToRefresh = "items_pull_to_refresh" diff --git a/WooCommerce/Classes/ViewRelated/MainTabBarController.swift b/WooCommerce/Classes/ViewRelated/MainTabBarController.swift index cac23a15200..2f54e887972 100644 --- a/WooCommerce/Classes/ViewRelated/MainTabBarController.swift +++ b/WooCommerce/Classes/ViewRelated/MainTabBarController.swift @@ -348,7 +348,7 @@ private extension MainTabBarController { case .hubMenu: ServiceLocator.analytics.track(.hubMenuTabSelected) case .pointOfSale: - // TODO: WOOMOB-571 - analytics + ServiceLocator.analytics.track(.pointOfSaleTabSelected) break } } @@ -369,7 +369,7 @@ private extension MainTabBarController { ServiceLocator.analytics.track(.hubMenuTabReselected) break case .pointOfSale: - // TODO: WOOMOB-571 - analytics + ServiceLocator.analytics.track(.pointOfSaleTabReselected) break } } From f9348eace762daa5ab726b62e6c7acff0160371c Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Tue, 17 Jun 2025 15:21:41 +0800 Subject: [PATCH 2/6] Track `pos_tab_visibility_checked` event when eligibility is checked with a test case. --- .../Classes/Analytics/WooAnalyticsStat.swift | 1 + .../ViewRelated/MainTabBarController.swift | 1 + .../MainTabBarControllerTests.swift | 35 +++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift index 07ac952d9c5..35a9c60a209 100644 --- a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift +++ b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift @@ -1278,6 +1278,7 @@ enum WooAnalyticsStat: String { // MARK: Point of Sale events case pointOfSaleTabSelected = "main_tab_pos_selected" case pointOfSaleTabReselected = "main_tab_pos_reselected" + case pointOfSaleTabVisibilityChecked = "pos_tab_visibility_checked" case pointOfSaleLoaded = "loaded" case pointOfSaleItemsFetched = "items_fetched" case pointOfSaleItemsPullToRefresh = "items_pull_to_refresh" diff --git a/WooCommerce/Classes/ViewRelated/MainTabBarController.swift b/WooCommerce/Classes/ViewRelated/MainTabBarController.swift index 2f54e887972..da21a0eda1d 100644 --- a/WooCommerce/Classes/ViewRelated/MainTabBarController.swift +++ b/WooCommerce/Classes/ViewRelated/MainTabBarController.swift @@ -667,6 +667,7 @@ private extension MainTabBarController { guard let self, let posEligibilityChecker else { return } let eligibility = await posEligibilityChecker.checkEligibility() let isPOSTabVisible = eligibility == .eligible + analytics.track(.pointOfSaleTabVisibilityChecked, withProperties: ["is_visible": isPOSTabVisible]) updateTabViewControllers(isPOSTabVisible: isPOSTabVisible) viewModel.loadHubMenuTabBadge() } diff --git a/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarControllerTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarControllerTests.swift index 2c4c18322cd..a2a630bf688 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarControllerTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarControllerTests.swift @@ -450,6 +450,41 @@ final class MainTabBarControllerTests: XCTestCase { // Resets the tab bar controller mock at the end of the test. TestingAppDelegate.mockTabBarController = nil } + + func test_event_is_tracked_after_eligibility_check() throws { + // Given + let featureFlagService = MockFeatureFlagService() + featureFlagService.isFeatureFlagEnabledReturnValue[.pointOfSaleAsATabi1] = true + + let mockPOSEligibilityChecker = MockPOSEligibilityChecker() + mockPOSEligibilityChecker.result = .eligible + + let storesManager = MockStoresManager(sessionManager: .makeForTesting()) + + guard let tabBarController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController(creator: { coder in + return MainTabBarController(coder: coder, + featureFlagService: featureFlagService, + analytics: self.analytics, + stores: storesManager, + posEligibilityCheckerFactory: { _ in mockPOSEligibilityChecker }) + }) else { + return + } + + // Trigger `viewDidLoad` + XCTAssertNotNil(tabBarController.view) + + // When + storesManager.updateDefaultStore(storeID: 322) + + // Then + waitUntil { + tabBarController.tabRootViewControllers.count == 5 + } + + let indexOfEvent = try XCTUnwrap(analyticsProvider.receivedEvents.firstIndex(of: WooAnalyticsStat.pointOfSaleTabVisibilityChecked.rawValue)) + assertEqual(true, analyticsProvider.receivedProperties[safe: indexOfEvent]?["is_visible"] as? Bool) + } } extension MainTabBarController { From 5dcf6894e3df4aa9ff4c8de36fefc1f244eda540 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Tue, 17 Jun 2025 15:21:56 +0800 Subject: [PATCH 3/6] Enable `pointOfSaleAsATabi1` feature flag. --- Modules/Sources/Experiments/DefaultFeatureFlagService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Sources/Experiments/DefaultFeatureFlagService.swift b/Modules/Sources/Experiments/DefaultFeatureFlagService.swift index 1ea9713a6d8..dd8564a647e 100644 --- a/Modules/Sources/Experiments/DefaultFeatureFlagService.swift +++ b/Modules/Sources/Experiments/DefaultFeatureFlagService.swift @@ -111,7 +111,7 @@ public struct DefaultFeatureFlagService: FeatureFlagService { // Enables a simulated barcode scanner in dev builds for testing. Do not ship this one! return buildConfig == .localDeveloper || buildConfig == .alpha case .pointOfSaleAsATabi1: - return buildConfig == .localDeveloper || buildConfig == .alpha + return true default: return true } From 46cdaeae7d974ccd3d8a47841d19f3876630b367 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Tue, 17 Jun 2025 15:25:13 +0800 Subject: [PATCH 4/6] Update release notes. --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index f487f0d607f..ca75e89a093 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -3,7 +3,7 @@ 22.7 ----- - +- [**] POS: a POS tab in the tab bar is now available in the app for stores eligible for Point of Sale, instead of the previous entry point in the Menu tab. [https://github.com/woocommerce/woocommerce-ios/pull/15766] 22.6 ----- From 844618bf6a2b790048cf0b7485c592f15ba4ef1e Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Wed, 18 Jun 2025 13:50:35 +0800 Subject: [PATCH 5/6] Remove tracking in unexpected POS tab reselected case and replace it with `assertionFailure` in case it can be triggered. --- WooCommerce/Classes/Analytics/WooAnalyticsStat.swift | 1 - WooCommerce/Classes/ViewRelated/MainTabBarController.swift | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift index 35a9c60a209..8ab003392c5 100644 --- a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift +++ b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift @@ -1277,7 +1277,6 @@ enum WooAnalyticsStat: String { // MARK: Point of Sale events case pointOfSaleTabSelected = "main_tab_pos_selected" - case pointOfSaleTabReselected = "main_tab_pos_reselected" case pointOfSaleTabVisibilityChecked = "pos_tab_visibility_checked" case pointOfSaleLoaded = "loaded" case pointOfSaleItemsFetched = "items_fetched" diff --git a/WooCommerce/Classes/ViewRelated/MainTabBarController.swift b/WooCommerce/Classes/ViewRelated/MainTabBarController.swift index 387a29f567f..b27b1649300 100644 --- a/WooCommerce/Classes/ViewRelated/MainTabBarController.swift +++ b/WooCommerce/Classes/ViewRelated/MainTabBarController.swift @@ -373,7 +373,7 @@ private extension MainTabBarController { ServiceLocator.analytics.track(.hubMenuTabReselected) break case .pointOfSale: - ServiceLocator.analytics.track(.pointOfSaleTabReselected) + assertionFailure("Point of Sale tab should not be reselected as it cannot be selected from `tabBarController(_:shouldSelect:)`.") break } } From b9622f8a5e2c917880e4fed9f65f19a1c52a2b26 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Wed, 18 Jun 2025 13:58:24 +0800 Subject: [PATCH 6/6] Fix lint error: Files should have a single trailing newline. --- .../ViewRelated/MainTabBarControllerTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarControllerTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarControllerTests.swift index 3caf7f7f98d..cfde0034772 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarControllerTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/MainTabBarControllerTests.swift @@ -669,4 +669,4 @@ private final class MockAsyncPOSEligibilityChecker: POSEntryPointEligibilityChec } } } -} \ No newline at end of file +}